ludic/packages/ludic.weather/README.md
Orkuncakilkaya 63e29f024c refactor(packages): the mechanic packages use ludic_base only, and ask through ports
ludic.clock, .effects, .inventory, .wallet, .weather and .tracks say
'uses ludic_base' (ludic.base uses nothing). ClockWorld, PackRules,
WeatherWorld and TracksWorld are export ports whose defaults are the old
fallbacks; clock_bind, inv_bind, weather_bind and tracks_bind are gone,
and the tests bind their fakes. The base README's toy does the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 05:36:11 +03:00

86 lines
4.9 KiB
Markdown

# ludic.weather
The sky as a chain of spells: clear, cloud, rain, a storm, snow - or whatever kinds the game
hands in. Each spell lasts a few game hours, eases in and out at its edges, sets the wind and
wets the ground, and gives way to the next by odds the game may lean on (a season, a wet week).
A forecast is drawn each morning, and the story can ask for a front or for a clear night. Uses
[`ludic.base`](../ludic.base/README.md) and nothing else.
```ludic
import "ludic.weather"
```
A kind is the game's number: its place in the list handed to `weather_kinds`. The package knows
what a kind DOES (it is wet, it throws lightning, it holds the cold) and never what it is called
in a sentence; the game draws the sky and says the notices.
Only the machine that owns the world runs the spells. Every machine eases the ground's wetness
and throws its own lightning out of a storm it is told of (`weather_told`), from the package's
own dice - never the world's.
## The kinds
```ludic
property WeatherKind {
name: string # for weather_name
clear: bool # the kind a sky eases out to (amount 0); the first one starts a trip
wet: bool # rain or snow is falling (weather_wet)
soak: float # how wet the ground gets under it, 0..1
overcast: float # times the amount: weather_overcast
cold: float # times the amount: weather_cold, in the game's units
wind: float # the strength the wind builds toward
wind_dir: float # radians the wind blows toward; below 0, the valley's daily turn
thunder: bool # a storm: lightning every 9-34 s while the amount is over a half
spell_lo, spell_hi # game hours a spell lasts
odds: []float # weights for the next spell, one per kind
}
```
## The port
```ludic
export port WeatherWorld { # every member has a default, so any may be left out
owns_world: fn() -> bool # does this machine run the world? (unbound: yes)
hours: fn() -> float # the hour, for the wind's daily turn (unbound: noon)
is_night: fn() -> bool # a front clearing at night is no WEATHER_CLEARED
odds: fn(int) -> []float # the next spell's weights from a kind (unbound: the kind's own)
bias: fn() -> int # what the story wants now: WEATHER_NO_BIAS, _WANT_WET, _HOLD, _WANT_CLEAR
front: fn() -> int # the kind a wanted front comes as (unbound: the first wet kind)
front_hours: fn() -> float # the least a wanted front lasts (never under 5)
seed: fn() -> int # a new trip's dice (unbound: 1)
}
```
The game fills it once, where it is put together - a declaration, not a call:
```ludic
bind WeatherWorld { owns_world: fn ses_owns_world, hours: fn clock_hours, odds: fn sky_odds }
```
The bias is asked every tick on the owner. `WANT_WET` on a dry sky forecasts a front
(`WEATHER_FRONT`), due within an hour and a half; `HOLD` keeps the current spell going at least
1.2 hours more; `WANT_CLEAR` clears within half an hour and keeps it clear. The game decides WHEN
it wants them (daylight, a tarp up, a stars job open).
## API
| | |
| --- | --- |
| `bind WeatherWorld { ... }`, `weather_kinds(ks)`, `weather_config_wind(down, up, from, to)` | the port, the kinds, and the valley's wind: toward `down` by night, toward `up` from `from` to `to` |
| `weather_run(dt, gh)` | one step (the system's tick does this from `Tick.dt` and `Tick.hours`) |
| `weather_morning()` | a new day's forecast: seven times in ten it becomes the next spell |
| `weather_reset()` | a new trip: the first clear kind, nothing forecast, dice from the seed |
| `weather_set(kind, next, left)`, `weather_set_wind(s, dir)`, `weather_set_ground(w)`, `weather_set_flash(f)` | the sky outright (staging, tests) |
| `weather_told(kind, next, left, amount, wind, dir, forecast)` | what the owner says the sky is; a new spell is a fact here too |
| `weather_state()`, `weather_next()`, `weather_left()`, `weather_amount()`, `weather_forecast()`, `weather_forced()` | the spell |
| `weather_wet()`, `weather_overcast()`, `weather_cold()`, `weather_wind()`, `weather_dir()`, `weather_ground_wet()`, `weather_flash()` | what it does |
| `weather_after_front()`, `weather_wet_yesterday()` | the half hour of clear light after a front; whether yesterday had rain or snow in it |
| `weather_kind(k)`, `weather_kind_count()`, `weather_name(k)`, `weather_draw_from(w, from, roll)` | the kinds, and the draw itself |
| `weather_facts() -> Queue<WeatherFact>` | `{ what, kind, from }`: `WEATHER_BEGAN` (a new spell), `WEATHER_CLEARED` (a wet spell gave way to clear sky by day), `WEATHER_THUNDER`, `WEATHER_FRONT` (a wanted front is forecast) |
| `weather_system() -> System` | `"weather"`, `PH_SIMULATE`: reset, tick, and its save section (`weather_save`, `weather_load`: the spell, the forecast, the wind's direction, the ground, the dice) |
## Tests
```bash
ludic build packages/ludic.weather/tests/weather_test.ludic --headless -o /tmp/weather_test && /tmp/weather_test
```