16 Commits

Author SHA1 Message Date
b005969ee1 Add marine flora & fauna (life in the ocean)
The biota density + population layers were hard-gated on elevation<=sea, so
the ocean was barren in the flora/fauna views and held no organisms.

- Density: ocean cells (not under polar Ice) get a marine primary productivity
  in computeFloraDensity -- base + (1-base)*max(shelf, coast), where shelf is
  shallowness (light) and coast is a BFS ring-distance from land (nutrients).
  Rich shelves/coasts, lower open ocean, zero under ice; sMoist (a land field)
  is not used at sea. computeFaunaDensity now skips only Ice, so marine fauna =
  flora*productivity with the existing carnivore prey-gate clustering big
  predators on rich shelves. Funga stays land-only.
- Population: append Ocean-masked archetypes (Kelp/Seagrass/Phytoplankton;
  Forage fish/Reef fish/Shark/Baleen whale/Seal/Squid; moistMin=0, SST-zoned).
  generateBiota fills ocean cells (skip Ice; no marine funga). fillFlora/
  fillFauna unchanged -- their biome-mask filter zones marine vs terrestrial.
  Append-only, so v7 saves are unaffected.
- Render: distinct marine ramps (marineFloraColor blue->teal/green bloom,
  marineFaunaColor blue->cyan->warm) for water cells in the 8/9 views; land
  ramps + funga view unchanged.
- Config: bioMarineBase/bioMarineShelfDepth/bioMarineCoastRings (self-describing
  config -> no save bump).
- test_biota.cpp updated: zero life under ice, marine flora/fauna present at sea
  + populate ocean tiles, funga 0 on water; capacity/carnivore-gate/budgets/
  determinism still hold. All five headless suites pass; GUI build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 12:04:53 +02:00
2b0e1f8633 Harden save loading and derived state resets 2026-06-28 21:44:41 +02:00
1358e81426 Persist step-back history in saves (v12): rewind storms past a loaded moment
Weather is integrated and path-dependent, so reversing it can only restore recorded snapshots --
and a save held only the current moment, so after a load there was no past to step back to and
loaded storms froze on backward step. Save bumped to v12: it now appends the most recent
wxSaveMax(40) step-back frames (each = humidity/cloud/rain + storms + RNG) after the planet
state. loadGame restores them (and still drops any stale pre-load history), so after a load you
can step backward ~40 steps and the storms reverse along their saved track.

Pre-v12 saves load with no step-back history (you can still step forward then back). Adds ~10 MB
to a save (the user chose the short window). All five suites pass; GUI build clean. Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:30:54 +02:00
b19d2a1703 Fix load: save weather systems (v11) + clear stale step-back history on load
After loading, storms vanished on forward play/step but reappeared on step-back. Two causes:
the moving weather systems weren't saved (transient), so a load started with none; and loadGame
didn't clear the wxUndo step-back ring, so stepping back restored STALE snapshots from before
the load (which still held the old session's storms) -- hence 'back shows them, forward doesn't'.

Save bumped to v11: the weather block now also persists sStorms + sWeatherRng + sStormNextId,
so a load resumes the active storms and stepping forward continues them deterministically.
Pre-v11 saves load with no active storms (they respawn); pre-v10 still spin weather up live.
loadGame now clears wxUndo + followId so a load can't restore stale pre-load weather or follow a
gone storm. test_weather.cpp checks the storms round-trip; all five suites pass; GUI build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:11:39 +02:00
f748940004 Fix: step-back now reverses run-born storms (record undo history continuously)
The step-back undo history was cleared on every continuous-run frame, so weather systems
(storms/hurricanes) created during a normal run had no recorded past. Stepping back then only
rewound the deterministic sky and left the storm frozen at its current spot, resuming motion
only on a forward step.

liveAdvance() now records a snapshot of the pre-advance weather state at ~one-step cadence on
ANY forward advance (continuous run or manual '.'), not just manual steps -- the interval
scales with liveRate, so it's ~one snapshot per real second at any clock rate, in a bounded
ring. liveStepBack() searches the ring for the most recent snapshot before the current time and
restores it (clock + humidity/cloud/rain + storms + RNG), so storms reverse regardless of when
they were born. The clear-on-run was removed; entering Live World still resets the ring.

All five suites pass; GUI build clean. Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:01:07 +02:00
8efb1e343b Live World stepper: step weather & storms back too (undo history)
Backward stepping previously only rewound the deterministic sky (day/night, tides, seasons,
moons) and held the weather, because the cloud/rain/storm state is integrated forward and not
analytically reversible. Now a forward step snapshots the full weather state via new
Planet::captureWeather()/restoreWeather() (humidity/cloud/rain/storms + the storm RNG/next-id)
into a bounded wxUndo ring, and the backward step restores the previous snapshot -- so '.' then
',' reverses EVERYTHING, including clouds, rain and moving storms.

Both steps auto-pause (video frame-step feel). A continuous run (unpause) clears the undo
history, after which ',' falls back to the sky-only rewind. Restoring also reseeds the storm RNG
so re-stepping forward replays deterministically. test_weather.cpp adds a capture/restore
round-trip check; all five suites pass; GUI build clean. Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:44:28 +02:00
47fee0b25f Live World viewer controls: storm follow-cam, 2D map zoom, clock stepper
Three viewer features over the Live World sim:

* Storm follow-cam (Y): the 3D camera locks onto a weather system and keeps it centred as it
moves, by pointing along rotateZ(storm.pos,+axialTilt) (model->world) via camYaw/camPitch.
Tracked by a new stable WeatherSystem.id (assigned at spawn from sStormNextId; transient, no
RNG/determinism impact). Cycles by descending strength, off after the last; orbit-drag disabled
while following; auto-releases if the storm dissipates; wheel still zooms.

* 2D map zoom/pan: a virtual projection rect (mapViewRect = mapRect scaled about its centre +
mapPanX/Y) routes every map projection call while the scissor/frame stay mapRect (drawMapTris
now derives y from the rect, not the fixed m.pos, so both axes zoom). Wheel over the map zooms
toward the cursor (1-8x); drag pans when zoomed, else rotates mapLon; 2D picking inverts the
same rect.

* Live-clock stepper: the stepSim live body is factored into liveAdvance(dtClock,dtWeather).
'.' steps forward and ',' back by liveRate hours; backward rewinds the deterministic sky
(day/night, tides, seasons, moon phases) but holds weather (not reversible). S in Live World
aliases the forward step (no longer runs a stray tectonic tick).

All five headless suites pass; GUI build clean. Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 18:32:02 +02:00
ca844a5d3f Live World weather: moving systems (lows, hurricanes & typhoons)
The base cloud/rain field relaxes to a static pattern under fixed forcing, so it looked
frozen. stepWeather now also runs a population of drifting WeatherSystem agents (world
objects, not cells; transient/not saved; separate sWeatherRng seeded from cfg.seed so
tectonic determinism is intact): spawn over warm tropical ocean (5-25 deg, SST gate) or a
mid-latitude ocean low (capped at weatherSystemMax); move along the steering wind (sWind at
the nearest cell) plus a poleward recurve at weatherSystemSpeed; intensify over warm sea,
decay and cull over land/cold; and stamp a Gaussian cloud/rain shield onto the grid so cloud
clusters travel and dissipate behind them. A tropical system past weatherHurricaneStr is a
hurricane/typhoon.

Render: an animated cyclonic spiral marker per system (red + eye for cyclones, blue lows;
spins with liveTime by hemisphere) in 3D + 2D, HUD system/cyclone counts, and a basin-named
storm list in the Sky & tides panel -- all under K. New weather* storm knobs. test_weather.cpp
adds: systems spawn, move between steps, thicken cloud, RNG isolation, determinism. All five
suites pass; GUI build clean. Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:40:58 +02:00
f84e06507a Live World weather: dynamic clouds & rain cycle (save v10)
A per-cell humidity/cloud/rain cycle advanced on the live clock (PlanetWeather.cpp,
raylib-free): evaporate over warm sunlit seas -> advect humidity & cloud along the prevailing
wind (upwind differencing) -> condense into cloud (saturation vs temperature + windward
orographic lift) -> rain out thick cloud -> dissipate. Bounded exp-rate forms keep it stable at
any timestep, so it runs cleanly from hours/sec up to a month/sec. initWeather() spins the
fields up from the moisture climatology; fully deterministic (no RNG).

Render: a translucent cloud shell over the 3D globe (white -> dark slate where it rains,
alpha = cover) plus a matching drawWeather2D layer on the 2D map (shared drawMapTris
rasterizer), toggled with K (default on). stepSim runs stepWeather each live frame at the
sim-hours added to liveTime (held when paused). Cell-info shows cloud/humidity/raining.

Saved as v10 (humidity/cloud/rain, flag-gated; pre-v10 saves spin weather up live). New
weather* config knobs. Reseed/regen now also drops out of Live World. test_weather.cpp:
fields in range, clouds form + rain falls, oceans moister than land, determinism, v10
round-trip; the other four suites still pass; GUI build clean. Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:01:34 +02:00
d4b46afe00 Cap tides on small enclosed seas (inland lakes stay calm)
A small closed-off ocean body cannot build a real tidal range -- the equilibrium tide
assumes a connected global ocean. computeTides now flood-fills connected ocean bodies and
caps any body under 10 cells to 0.01 m/cell + 0.03 m (a one-cell sea ~0.04 m); open oceans
(>=10 cells) keep the full equilibrium tide. The Sky & tides panel reads the capped level so
it stays consistent with the cell-info panel. test_ocean.cpp covers both cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 13:42:17 +02:00
aa32e38dad Live World stage: clock, day/night, seasons, moons, tides & ocean currents
Adds the slow real-time "Live World" mode (key W on a settled world) that runs the
finished planet on an hours->weeks/months clock (liveTime/liveRate, [ / ] ramp the rate)
with geology frozen. Everything new is a derived per-cell field flowed over the fixed grid.

Day/night & seasons (PlanetLive.cpp): a raylib-free per-cell insolation field
(computeInsolation) drives a moving day/night terminator (N) from time-of-day rotation +
seasonal declination (axialTilt); a live seasonal temperature cycles the static summer/winter
fields over the year (computeLiveSeason); a moving snow/sea-ice line tracks it. Day/night +
snow are render overlays over any colour mode (3D + 2D). Save v8 stores the live clock.

Sky & tides (PlanetOcean.cpp): 1-3 random moons (separate RNG, saved v9) orbit on the
clock and, with the now small/distant sun, raise an equilibrium tide (computeTides -> sTide),
shown as a tide-coloured coastline (T, buildCoastline + tideColor). Moons render with sun-lit
phases, orbit rings and eclipses (solar shadow spot in the day/night overlay, lunar dimming).
The 2D map is left-aligned; the freed space holds a Live-World "Sky & tides" panel (per-moon
phase + a selected coastal tile's tidal phase).

Ocean currents + climate feedback (PlanetOcean.cpp): computeOceanCurrents builds a
per-ocean-cell tangent velocity from wind stress + Coriolis deflection + coast-following
(gyres); computeClimate feeds warm (poleward) / cold (equatorward) currents back into sTemp as
a bounded coastal anomaly (climateCurrentFactor) before seasons, so biomes shift. Rendered as
warm/cold current arrows (O).

Config: dayLengthHours/yearLengthDays/snowTemp/seaIceTemp/tideAmplitude/tideSunFactor/
climateCurrentFactor. Save header v7->v9 (version-gated; older saves load fine). Headless
test_live.cpp + test_ocean.cpp; test_logic/test_biota still pass; GUI build clean. Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 10:04:45 +02:00
8ed9ae4515 Seasons (obliquity): per-cell summer/winter temps + richer cold biomes
axialTilt was visual-only ("groundwork for seasons"). It now drives climate.

computeClimate() adds derived sTempSummer/sTempWinter around the annual mean:
  summer/winter = sTemp +/- A,  A = seasonAmpMax * sin(tilt)/sin(23.44) *
                                    latShape * continentality
- tiltFactor: 0 tilt -> no seasons, Earth tilt -> 1.
- latShape (pow(|lat|/90, seasonLatExp)): poles swing most.
- continentality: a multi-source BFS ring-distance from ocean cells -- oceans
  and coasts are muted by thermal inertia, interiors swing most.
Result: ~0 swing at the equatorial coast, large at high-latitude interiors.

classifyBiomes() blends WINTER temp into the Tundra/Taiga cold cutoffs via
biomeSeasonWeight (0 = annual-mean only = unchanged biomes; default 0.6), so
cold-winter continental interiors become boreal/tundra (Siberia effect). The
amplitude is geographically shaped, so cold biomes expand only where seasons
bite. Fields are derived/not-saved -> no save-format change.

Viewer: color key 6 now CYCLES Temperature -> summer -> winter -> seasonality
(new seasonColor ramp + labels); cell-info shows summer/winter. New season* +
biomeSeasonWeight config knobs (planet.cfg, validated). Docs updated.

Headless (test/season): summer >= mean >= winter; equator swing ~1.6 C vs
~20 C at high latitude; interior land >> ocean; tilt=0 -> no seasons; higher
tilt -> bigger swing; biomeSeasonWeight=0 leaves biomes unchanged; cold-biome
count rises with seasons; deterministic. test_logic + test_biota pass; full
app builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:25:45 +02:00
708b23d774 Show more rivers (riverThreshold 50->25) + remove globe subgrid overlay
Two fixes from user feedback on the hydrology view:

1. Rivers looked too sparse. Investigated coupling rainfall into discharge
   (climate precip -> river rainfall): it does NOT work as hoped because river
   *location* is set by drainage topology (where water collects), not local
   rainfall -- weighting rainfall mostly changes river *size*, and concentrating
   a fixed water budget pushes mid-size rivers below the display threshold, so
   FEWER rivers show. Reverted that approach. Instead lower the default
   riverThreshold 50 -> 25 so tributaries render too: a richer, more visible
   network (~2x river cells on test worlds) without descending into noise.
   Tunable in planet.cfg.

2. Clicking a tile overlaid a low-res, always-elevation-coloured subgrid patch
   on the 3D globe that clashed with the active colour mode and read as a
   "strange pattern". Removed the globe overlay; the clicked tile's high-res
   subgrid still shows in the right-side detail panel.

test_logic + test_biota pass; full app builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:04:49 +02:00
4aacfdabdd Soft probabilistic peak cap: spread mountain heights, drop the 9000 m plateau
Drift-time peaks used to rail into the hard elevation clamp and flatten into a
9000 m plateau (the clamp lived in step(), erode() AND hydrology(), so erosion
re-flattened them every tick). Replace the hard ceiling with a probabilistic
soft cap: above peakSoftCapStart (7000 m) the chance a tick's uplift "takes"
falls linearly to 0 at peakSoftCapEnd (12000 m); a lost grow roll forfeits the
uplift and shaves a random 0..peakFailDrop (200 m) off. Peaks now spread smoothly
across a height band (strong orogeny reaches ~10-11 km, most cluster lower) with
zero cells pinned at the ceiling.

- The roll is a pure hash of (cellIndex, erodeIter, seed): never touches
  rngState, bit-identical across OpenMP thread counts, and since erodeIter is
  saved (step/erode run 1:1 in drift) F5/F9 resumes bit-identical -- no save bump.
- Drift-only (gated on Planet::drifting) so Phase-1 forming still auto-settles.
- The hard clamp's upper bound now tracks peakSoftCapEnd in all three places
  (step/erode/hydrology); lower -11000 m unchanged.
- New planet.cfg knobs peakSoftCapStart / peakSoftCapEnd / peakFailDrop with
  validation (+ start < end cross-rule). Docs updated (CLAUDE.md, BUILD.md).

Verified headless: smooth 8.5->10.5 km taper, 0 pinned, determinism + exact
resume intact, test_logic + test_biota pass, full app builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:26:38 +02:00
53e371bb2f Add Biota stage: flora, fauna & funga (density + slot/point population)
World-Creation stage after biomes. Two layers (raylib-free engine):
- Per-cell density scalars (flora/fauna/funga in [0,1]) derived from the
  climate fields each tick (drive color modes 8/9/0). Flora = Liebig-min of
  temp & moisture; fauna ~ flora with carnivores gated on local prey; funga =
  moisture/organic-matter-led + cold-tolerant. Zero on water/ice.
- On-demand discrete population (key L, saved as v7): each land cell draws
  broad archetypes from a comprehensive table into a per-kind slot cap +
  density-scaled point budget (size -> cost), weighted by biome/climate
  suitability and a regional bonus for same-biome neighbours. Separate RNG
  seeded from cfg.seed so generating biota never perturbs tectonic determinism.

Organisms are labelled by taxonomy (Family + Size + role, e.g. "Felidae
(Big, Carnivore)") with the full Class > Order > Family tree stored, never an
informal common name. Cell-info panel word-wraps + aggregates duplicates so the
lists no longer get cut off.

New: src/sim/PlanetBiota.{hpp,cpp} + PlanetFlora/Fauna/FungiGen.cpp, color
modes/colors, bio* config knobs, save v7 (older saves load with empty
population), test_biota.cpp (densities, fauna<=capacity, carnivore gating,
slot/point budgets, determinism + RNG isolation, v7 round-trip). Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 13:56:03 +02:00
acc0e5eec9 Initial commit: fanworgen planet sim
C++/raylib semi-realistic fantasy/sci-fi planet generator on a fixed icosphere
grid (Eulerian: properties flow over fixed cells). World-creation stages:
tectonics, continental drift & erosion, hydrology (rivers/lakes), climate
(temperature + orographic precipitation), and biome classification. Engine in
src/sim (raylib-free, headless-testable), viewer in src/render. See CLAUDE.md
and docs/ (design-notes.md, fauna-flora-plan.md = next step).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 15:08:25 +02:00