Days in a full year.
Real seconds per day — the day clock's dayLength.
Freeze/unfreeze the phase (setPhase still works while paused).
Current phase, 0–1 (0 = midwinter, 0.5 = midsummer).
Whole years elapsed since construction — incremented as the phase wraps.
Jump the year clock (value wraps into 0–1). Does not touch the year count.
Advance the phase by whole days — the turn-based entry point.
Advance by dt real seconds, at dayLength seconds per day and
daysPerYear days per year (no-op while paused). Feed it the same dt the
DayNightCycle gets and the two clocks stay locked together.
The current moment in the year. The returned object is reused across calls — copy anything you keep.
How far the current phase pulls temperature down at a given normalized latitude (0 = pole, 1 = equator). Exposed because gameplay that wants to ask "how cold will it be there in three weeks" should use the same curve the renderer does.
Under 'local' scope the latitude is ignored, which is the whole point of
that scope — one map, one season.
Rewrites every cell's snow depth and season-adjusted temperature from its base temperature and the current phase, then marks the texture for upload.
Snow is eased because it accumulates; temperature is always written exact,
because it is a reading rather than a quantity that piles up. Ice is not
written at all — each liquid derives its own from the temperature against
its freezePoint, which is how water can skin over while acid beside it
stays liquid.
Optionaldt: numberReal seconds since the last call. Omit to snap the snow straight to its target (deterministic — the turn-based path). Supply it to ease toward the target at SeasonOptions.accumulationRate / SeasonOptions.meltRate, so a thaw reads as a thaw.
The year clock, and the pass that turns it into snow on the ground.
Deliberately shaped like DayNightCycle one level up: a 0–1 phase where 0 is the darkest/coldest point and 0.5 the brightest/warmest,
advance(dt)to run it,setPhaseto scrub it,pausedto freeze it.The model
Seasons don't recompute climate — they bias it. The generator's temperature field already folds latitude, elevation, and noise into one 0–1 number per cell, so a season is a single subtraction against it:
with
amplitudeinterpolating from SeasonOptions.polarAmplitude at the poles to SeasonOptions.equatorAmplitude at the equator. Snow lies whereeffectivefalls under SeasonOptions.snowThreshold; ice is left to each liquid's ownfreezePoint, since water and acid do not give up at the same cold. Because elevation cooling is already baked intobase, mountains cross those thresholds first and keep their caps year-round, and the snowline walks down the map through autumn without a line of latitude appearing anywhere in the code.…and the other model
That is
scope: 'continental', and it is right exactly when the map is big enough for one end to have a different climate from the other. On a valley it isn't: a snowline creeping across a few kilometres reads as a bug. So SeasonOptions.scope'local'inverts which term dominates —— and the map's own field, which was the climate, becomes a small stagger on top of a season the whole map shares. Every cell crosses each threshold within a few days of every other, so a hard winter strips every tree and spring blooms all of them, while the high ground still leads by a little.
Nothing downstream knows which model ran. Snow, per-liquid ice, the foliage tint, blossom, precipitation and
climate.snowDepthall read the same two bytes; the scope only changes what gets written into them.Two ways to run it
apply(climate)snaps every cell to the phase's target — deterministic, and what a turn-based game wants, where one turn is one day.apply(climate, dt)eases toward it at SeasonOptions.accumulationRate and SeasonOptions.meltRate, so snow builds and thaws over real seconds instead of popping between frames.Example
Example
Example