Natura Algorithms -- implementable detail per system + decisions & lessons
The deepest capture, written while the design is fully in hand (operator: "are you sure you've
maximized what you can from the knowledge you hold?"). SYSTEMS.md says WHAT each system is; this says
HOW -- the actual state, update equations, coupling transfer functions, and the emergence test to aim
at, detailed enough that building is transcription, not re-derivation. Plus Part Z: the design decisions
(the WHY) and the hard-won lessons, so the next builder doesn't repeat them.
Conventions (from README): grid N x N, cell k=j*N+i, world [-1,1]^2, time in seconds, seeded rng,
fields read from ctx.fields.<system>.<field> (one-frame-delayed). dt_h = clamp(dt/3600, 0, ~1) is a
per-step hour fraction; most rates below are per-hour and scaled by dt_h.
TERRAIN erosion coupling (close the weather<->land loop) [BUILD NEXT]
Wrap the Terrain Engine; add a slow erosion step driven by weather moisture.
- State: the
Terraininstance's heightfieldH[k](mutable copy), plussoil[k](loose sediment). - Step (per sim-day, not per hour -- erosion is slow):
wetness = ctx.fields.weather.moisture[k](accumulated rain).- stream power:
E[k] = Kf * pow(flow[k], m) * pow(slope[k], n) * wetness(Kf~1e-4, m~0.5, n~1) -- detach bedrock where water is fast + steep + wet. H[k] -= E[k]*dt; soil[k] += E[k]*dt;then transport soil downslope (D8): move a fraction ofsoilto the lowest neighbour; deposit where slope flattens (soilsettles whenslope < angle_of_repose).- Never below a bedrock floor (bound runaway, as the Terrain Engine already does).
- OUT: elevation (evolving), slope, flow, biome. IN: weather.moisture, weather.rain.
- Emergence test: after many sim-years under a fixed prevailing weather, the windward (wet) slope is MORE incised than the lee (dry) -- erosion inherits the rain-shadow. Terrain + weather co-evolve.
TIME / SEASONS [EASY, do early]
- Params:
latitude(rad),axialTilt(~0.41 rad = 23.5deg). - Solar declination:
decl = axialTilt * sin(2*PI*(yearPhase - 0.22))(peak near summer solstice). - Solar elevation at noon:
elev = PI/2 - |latitude - decl|. Day length:dayLen = acos(-tan(lat)*tan(decl))/PI(longer in summer). Instantaneous insolation:max(0, sin(dayPhase*2PI - PI/2)) * max(0, sin(elev)). - OUT: season, daylight, dayLength, solarElevation, insolation. IN: clock (+ orbit.sunPos later).
OCEAN / HYDROLOGY [after terrain+weather]
- State:
soilWater[k],discharge[k](river),lake[k]. - Step:
soilWater += rain - ET - infiltration; runoffR = max(0, soilWater - capacity); route R down the D8 network accumulatingdischarge(a system's discharge = its R + upstream discharges -- one pass in topological/height order).discharge > bankFull->flood. EvaporatesoilWaterby temp. - OUT: soilWater, discharge, lakes, floods. IN: weather.rain/moisture/temp, terrain.flow/slope.
- Emergence: rivers swell after storms and shrink in drought; a basin fills to a lake.
VEGETATION [HIGH VALUE -- the real "greening under rain"]
- State:
biomass[k]in [0,1] (a field), optional per-plant instances for form (L-system). - Carrying capacity from climate:
K[k] = f(soilWater, temp, insolation, biomeSuitability)in [0,1] (0 in desert/ice, ~1 in warm+wet). - Logistic growth + spread (per sim-day):
growth = r * biomass * (1 - biomass/max(K,eps)) * seasonFactor(temp,daylight)(r ~ growth rate).spread = D * laplacian(biomass)(seeds disperse to neighbours; D small).loss = respiration*biomass + grazing(ctx.fields.ecosystem) + drought(if soilWater<wilt).biomass += (growth + spread - loss)*dt; dead biomass ->litter[k]-> soil fertility (feeds back).
- Individual FORM (for render): the walk's tree grower / L-systems, instanced where biomass is high, species by biome. OUT: biomass, canopy, food, fuel(=dry biomass), litter, greenness. IN: weather(moisture, temp), ocean(soilWater), time(season, insolation), terrain(biome, soil), ecosystem(grazing).
- Emergence test: green tracks the rain-shadow (windward high biomass, lee low); biomass swings with the seasons; a cleared patch regrows over years. THIS replaces the weather-world viz's soil hack with real plants.
LIFE-CYCLE [a creature's life as a state machine]
- State per creature:
{species, stage, age, energy, health, pos, sex}; stage in {egg, juvenile, adult, senescent, dead}. - Step (per sim-day):
age += dt;energy += eat(ctx.fields.vegetation | prey) - metabolism(bodyMass,temp); clamp health by energy (starving if energy<0 -> health drops). Stage transitions by age+energy thresholds (egg->juvenile at hatchAge; juvenile->adult at maturityAge if fed; ->senescent at oldAge; ->dead when health<=0 or age>maxAge). Adult + fed + breeding-season -> reproduce (spawn offspring egg). Dead -> carcass (decays over days ->nutrientsto soil; scavengers may eat first). - OUT: creatures, carcasses, births/deaths(events). IN: vegetation(food), ecosystem(predation, mates), weather(cold/heat stress), time(breeding/hibernation season).
- Emergence: a cohort born in spring, matures, breeds, and dies; a hard winter culls the weak.
ECOSYSTEM / POPULATION [the food web + herds]
- Population dynamics per species pair (Lotka-Volterra / logistic-predation):
- prey:
dNp = Np*(rp*(1 - Np/Kp) - a*Nc); predator:dNc = Nc*(b*a*Np - dc)(a=attack rate, b=conversion, dc=death). Solve on the sim-day; multi-species = a food-web matrixa[i][j].
- prey:
- Spatial layer (the visible agents): BOIDS (the walk's flock craft) for herds/flocks/schools -- separation/ alignment/cohesion + a resource-gradient attraction (toward high vegetation) + a predator-avoidance term (flee from predator agents). Migration: seasonal drift along a temperature/resource gradient.
- OUT: populations, herds(agent positions), predation/migration events. IN: vegetation(base food), life-cycle(individuals), terrain(barriers/habitat), weather(migration triggers).
- Emergence test: predator-prey populations OSCILLATE (out of phase); a herd coheres and migrates seasonally; overgrazing crashes vegetation then the herd. (This is the flagship emergence -- the operator's "two moose".)
- As BUILT (
ecosystem.js), three algorithmic choices that matter:- Prey refuge (oscillation damper): a predator's catch succeeds only if
rng() > refuge, whererefuge = min(refugeMax, refugeK * biomass(prey)). Dense vegetation shelters prey -> a Type-III low-density refuge -> the boom-bust stops swinging through zero. Couples predation to the vegetation field. - dt-invariant motion: speeds are domain-units per DAY and displacement is
v * dd(dd = dt/DAY). So the same run at any time-step (a slow smooth day, a fast time-lapse) yields the same biology -- viewing speed is decoupled from dynamics. (Metabolism, reproduction chance, immigration already scale by dd.) - Boundary = a choice: torus
wrap(sealed world, toroidal sensing + movement) OR open-zoneemigrate(off-edge = migrated to the neighbour zone) +immHerb/immPredinflux -> a metapopulation window. Open zones persist by SPACE + migration, not by landmass-tuned params, and may locally go extinct (honest).
- Prey refuge (oscillation damper): a predator's catch succeeds only if
BIOME DYNAMICS [biomes spread & shrink]
- State:
biome[k]+succession[k](progress toward the climate-target biome). - Step (per sim-year): target biome =
biomeOf(longRunTemp[k], longRunPrecip[k])(running averages of weather, EMA).successionadvances toward target at a rate set by vegetation (fast in productive climes, slow in harsh); when succession completes,biomeflips. Edges advance because neighbours' vegetation seeds in. OUT: biome, transitions. IN: weather(climate EMAs), vegetation. - Emergence: a warming trend marches forest uphill / desert into savanna over decades.
FIRE [drought -> fire -> regrowth, a gorgeous loop]
- State:
fuel[k](=dry vegetation),burning[k](0/1/intensity),burnt[k],ash[k]. - Ignition: lightning (a weather storm event at (x,y) in dry fuel) OR heat > ignitionTemp. Spread (fast,
sub-stepped): a burning cell ignites a neighbour with probability
p = base * fuel_n * dryness_n * (1 + windAlign) * (1 + upslope)where dryness=1-soilWater, windAlign = wind . direction-to-neighbour. Burning consumesfuelover a few steps ->ash+burnt; vegetation.biomass there -> 0. - OUT: fire, burnScar, smoke, ash(->soil fertility). IN: vegetation(fuel), weather(wind, dryness, lightning), terrain(slope). Emergence test: a fire lit in dry fuel with wind sweeps DOWNWIND + UPHILL and stops at a river/wet patch; ash makes the regrowth greener.
TECTONICS / PLANET [bigger worlds, deep time]
- State:
plates(each a region id + velocity v),H[k]continental height,age[k](seafloor age). - Step (per Myr): advect plate ids by v; at CONVERGENT boundaries (plates moving together) uplift
H += convergence * dt(mountains/arcs); DIVERGENT -> rift (Hdrops, new young seafloor, age=0); seafloorage += dt, older = deeper (thermal subsidencedepth = -c*sqrt(age)); hotspots add volcanoes. Generalize the island-mask to plates on a plane (or a sphere via lon/lat). OUT: elevation, plateBoundaries, volcanism, seafloorAge. IN: clock(Myr). Emergence: two plates collide -> a mountain belt rises over Myr.
ORBITS + STELLAR [the cosmic pole, deep time]
- Orbits -- DONE (
celestial.js): Kepler per body --M = phase0 + 2PI*t/period; solveE - e*sinE = M(Newton, 6 iters); true anomalynu = atan2(sqrt(1-e^2) sinE, cosE - e), radiusr = a(1 - e cosE); position in the orbital plane, tilted about the node line by the orbit inclination (+ node regression). Positions resolve up the parent chain (moon rel planet rel star), all ANALYTIC int(resumable). - Derived sky (DONE): solar declination
decl = tilt * sin(yearAngle); hour angleH = (dayFrac - 0.5)*2PI; sun altitudesinAlt = sin(lat)sin(decl) + cos(lat)cos(decl)cos(H),daylight = max(0, sinAlt); day length from the sunrise equationcosH0 = -tan(lat)tan(decl)(clamp -> polar day/night). Moon phase from the elongationelongbetween moon and sun as seen from the planet,illum = (1-cos elong)/2. TIDES: semidiurnaltide = (cos 2*Hmoon + k*cos 2*H)/(1+k); spring/neap= |cos elong|(spring at syzygy, neap at quadrature). Eclipses: at syzygy (elong~0new /~PIfull) AND the moon near a node (|ecliptic-lat| < r). - Stellar evolution -- TODO (
stellar.js): luminosity vs age for a ~1 solar-mass star -- main sequenceL ~ L0*(1 + 0.4*(age/t_ms))slowly brightening overt_ms ~ 10 Gyr; then subgiant/red-giantLrises ~1000x over ~1 Gyr as the core contracts + envelope swells (Rhuge); helium flash; then the envelope is shed and a WHITE DWARF cools:L ~ Lwd * (age_wd)^(-1.3)toward dark over Gyr. ExposeL(t). - OUT: sunPos, moonPhase, insolation(=L/r^2), tides, starLuminosity, starRadius. IN: clock(Gyr).
- Emergence (Scenario Pole 3): the habitable zone marches outward as L rises; a fixed planet warms from frozen -> temperate -> scorched over billions of years.
MICRO [the small pole -- reuse existing craft, wrap as systems]
- FROST/RIME: dielectric-breakdown / DLA (How Carefully It Listens) -- grow ice toward the vapour gradient with branching set by the eta exponent; ACTIVATES only where weather.temp<0 + humidity high on a cold surface.
- DROPLET / SMOKE-RING: Lagrangian vortex-blob + Biot-Savart (fluids-and-flow) -- zero-diffusion, leapfrogs.
- FLAME: the combustion/dielectric hybrid; a CRYSTAL: DLA on a lattice; PATTERN: reaction-diffusion (Gray-Scott).
- Each already exists as a technique in the library; the work is the standard-interface wrapper + the activation coupling (a micro system only wakes under the right macro conditions).
Part Z -- DESIGN DECISIONS (the WHY) and LESSONS (the scars)
Decisions
- Geostrophic wind, not full Navier-Stokes, for weather. At the landscape/regional scale, wind follows
the pressure gradient (rotated by Coriolis); a full incompressible NS projection is overkill + slow. We get
circulation around lows/highs from
wind = rot90(grad P)+ a prevailing term, and stability from semi-Lagrangian ADVECTION (Stam) of the scalars. Keep NS for MICRO fluid pieces only. - Grid FIELDS for the continuous (weather, vegetation, biome, fire, water); AGENTS for the discrete (creatures, herds). Don't force one representation. Ecosystem is both: population FIELDS + a few visible boid AGENTS sampled from them.
- One-frame-delayed coupling (pre-step field snapshot). Order-independent, simple, correct for these timescales. Only matters for tight sub-hour feedback -- note it, don't over-engineer.
- Determinism as a first principle (seeded rng, clock+dt as inputs, no wall-clock). Buys reproducibility,
testability, resumability, AND satisfies the sandbox's no-
Date.now/Math.randomrule for free. - Terrain comes in as a SAMPLER, not a hard dependency, so weather (and every system) stands alone and
tests alone; wiring the real Terrain Engine is a thin later step. Keep systems decoupled from each other's
internals -- they talk only through
fields(). - Multi-timescale stepping. Weather steps in hours, erosion in days, biomes in years, tectonics in Myr,
stars in Gyr -- all off the ONE clock. A system reads the clock and does its own thing at its own cadence
(e.g.
if (floor(t/DAY) != floor((t-dt)/DAY)) doDailyStep()).
Lessons (paid in the weather build -- do not re-pay)
- A grid atmosphere's failure mode is GLOBAL SATURATION -- humidity fills to the saturation cap
everywhere, so
cloud = hum/sat = 1everywhere and it rains uniformly (a haze, no weather). The cure has three parts, all needed: (1) SATURATION well ABOVE typical humidity (clear sky is the default, not the edge); (2) a real SINK -- base drizzle-loss PLUS SUBSIDENCE drying under high pressure, so descending air is genuinely dry and clouds are confined to lows/fronts; (3) reduce the evaporation SOURCE so supply can't outrun the sink. Balance source vs sink so most cells sit BELOW saturation. - The accumulated field can hold the signal the instantaneous field loses. The rain-shadow is crisp in
accumulated
moisture(integrated over time) even while the instantaneouscloudis noisy/uniform. When a system's live field looks flat, integrate it -- the pattern is often in the accumulation. - Render distinct FEATURES, not raw fields. A humidity/cloud grid rendered directly reads as fog. Render clouds from CONDENSATION ACTIVITY (where it's raining/rising) so they read as drifting storms, and render the WORLD'S RESPONSE (greening, rivers) which carries the coupling's meaning, not the driver field.
- Tune against a TEST, not the eye alone. The rain-shadow ASSERTION (windward moisture > lee) let me rebalance the humidity budget repeatedly without regressing the physics -- the number caught what a pretty frame would have hidden. Every system needs its emergence assertion before its viz.