34 Commits

Author SHA1 Message Date
e27b91a128 Civ Step 7: trade & economy
Settlements no longer prosper on local food alone. A derived economic layer:
trade routes link nearby settlements, prosperity accrues at hubs, and prosperity
boosts population growth. Like territory/culture it's a pure function of the
(saved) settlements + geography + wars/diplomacy -- recomputed each sim year,
NO new saved state and NO save-version bump.

- PlanetTrade.{hpp,cpp}: computeTrade() builds TradeLinks (sea when both coastal
  -> longer reach / river / overland) with volume from populations x proximity x
  a political factor (blockaded by war, boosted by an alliance); prosperity =
  base + sum of link volumes (hubs get rich); a per-cell wealth field via a new
  sCellSettleOwner filled by computeTerritory.
- Prosperity feeds carrying capacity in stepCivilization (K *= 1 +
  tradeProsperityWeight*prosperity), so connected coastal/river hubs grow bigger.
- Light feedback in stepConflict (reads last year's economy): trade partners get
  a diplomacy attitude nudge (reward alliance); a wealthy weak neighbour raises
  war hostility (tempt war).
- Render: a Wealth heat map (wealthColor) + trade-route overlay (sea cyan /
  land+river amber) under key Z, a gold wealth dot per settlement in the Civ tab,
  a cell-info prosperity/route line. trade* config knobs (no save block).
- test_trade.cpp: links form (sea over longer range), hubs richer than isolated,
  war blocks trade, prosperity raises carrying capacity, determinism, RNG
  isolation, save->load->recompute parity. All 15 suites green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 13:36:41 +02:00
8cd487a5dc Civ Step 6: diplomacy, alliances & coalitions (save v22)
Wars stop being isolated 1-v-1 grudges. Each pair of nearby realms carries an
attitude that drifts over time, crystallising into alliances / non-aggression
pacts / rivalries. Allies don't fight and join each other's wars (coalitions),
and wars end in real peace treaties. Extends the Step-5 conflict subsystem
in place (same stepConflict tick, same war RNG), saved v22 + snapshotted.

- PlanetTypes.hpp: DiploKind + DiploTie {a,b capital indices, attitude, truceUntil,
  kind}; diplo* config knobs; WeatherSnapshot carries diplomacy.
- PlanetConflict.cpp: a diplomacy pass in stepConflict (revolts -> prosecute ->
  diplomacy -> declare). Attitude drifts from culture/faith affinity + a war
  penalty + truce recovery + per-pair noise; reclassified by threshold with
  hysteresis (kind-6 events on change). War-declare skips allied/non-aggression/
  truced pairs; hostility now rises as attitude falls (rivals fight); a defender's
  allies join by declaring their own war on the aggressor; a war end sets a truce
  + grudge. diploBetween/realmsAllied helpers.
- Save v22 + step-back: allegiance/wars block joined by a diplomacy block (new
  hasDiplo readState param + per-frame history block); captureWeather/restoreWeather
  carry the ties.
- Render: green alliance / dark-red rivalry great-circle arcs over the Territory
  view (P), per-realm ally/rival counts + active-wars list in the Realms tab, a
  cell-info allies/rivals line, kind-6 events (= icon).
- test_diplomacy.cpp: alliances/rivalries form, allies never war, coalitions form,
  truces after peace, determinism, RNG isolation, save-v22 + snapshot round-trip.
  All 14 suites green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 13:07:10 +02:00
cc6813e211 Live World: raise max clock speed to ~20 years/second
The live clock capped at 1 month/second (liveRate <= 720 sim-hours/s), far too
slow to watch civilizations, wars and empires evolve over centuries. Raise the
ceiling to ~20 sim-years/second (a century in ~5 s).

- liveRateMax() helper (20 x dayLengthHours x yearLengthDays); the [ / ] ramp and
  the load-resume clamp both use it (was the hardcoded 720 in two places).
- HUD rate label gains a "yr/s" tier above "mo/s".
- The per-year sim tick (wars + territory) now catches up one year at a time over
  any years a frame skips (bounded to 12/frame), so at high speed wars/borders are
  still simulated for every year instead of teleporting; a backward step still just
  refreshes territory without re-running wars.

Render/input-only: no engine, save-format or config change. Build clean, 13/13 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 09:01:15 +02:00
4a1f3377ba Civ Step 5: conflict, war & shifting borders (save v21)
Realms stop coexisting peacefully and go to war. Unlike the derived Steps 3-4,
war is stateful & path-dependent, so it adds saved state (allegiance + wars +
a war RNG), extends the step-back snapshot, and bumps the save to v21.

- PlanetConflict.{hpp,cpp}: stepConflict(year) runs once per sim year. Neighbouring
  realms (adjacency by settlement proximity) grow hostile from ambition (size gap)
  + ideology (culture/faith difference) + contested frontier + a yearly streak, and
  declare wars (cap warMaxConcurrent). Each war-year runs a battle (strength =
  totalPop x Warlike bonus x defender home advantage), inflicts casualties on
  frontier cities, and the winner conquers a loser frontier city (allegiance flips
  to the victor) or sacks it (ruins). Conquered foreign/distant cities revolt over
  time; a realm that loses its capital collapses -> empires rise and fall. Separate
  sWarRng keeps tectonics deterministic.
- computeTerritory() honours sSettleAllegiance (overriding the mono-cultural rule)
  with a chain-resolving capital lookup, so borders move as cities change hands.
- Save v21: allegiance + wars + war RNG (new hasConflict readState param + a
  per-frame block in the step-back history); WeatherSnapshot + capture/restoreWeather
  extended, so ,/. rewind conquests + revolts.
- Render: red war-front lines + a red at-war marker & active-wars list in the Realms
  tab over the Territory view (P), a cell-info AT WAR flag, a HUD war count, and
  kind=5 events (declare / capture / sack / revolt / peace).
- war* config knobs; test_conflict.cpp covers wars erupting, conquest moving the
  border, revolts, determinism, RNG isolation, save-v21 + snapshot round-trip.
  All 13 suites green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 08:33:38 +02:00
91f1b90a93 Civ Step 4: culture, beliefs & governments
Give the world peoples and faiths on top of Step 3's realms. All derived
deterministically from the (saved) settlement set + geography (pure hashes,
no RNG, recomputed each sim year -> no save-version bump, step-back free).

- PlanetCulture.{hpp,cpp}: computeCultures() groups living settlements into one
  culture per inhabited continent, each with a generated people name, a dominant
  ethos picked from its cells' environment (Seafaring/Highland/Nomadic/Agrarian,
  else a hashed Mercantile/Warlike), and a religion (a Faith focus biased by the
  dominant biome + a generated faith name). Runs after computeTerritory().
- Governments: each realm (Nation) gets a GovType from its tier + a deterministic
  pick, folded into its name ("Republic of X", "Duchy of X", "X Theocracy", "X
  Confederation", "X Dominion", ...) -- culture-driven renaming.
- Culture-driven grouping: realm vassalage is now restricted to the same
  continent, so every kingdom/empire is mono-cultural.
- Render: a Culture colour mode (cultureColor, cultural blocs) + pale
  buildCultureBorders lines + a Cultures tab + a cell-info culture/faith line +
  government-aware realm labels, all under key X. Cultures recompute with
  territory in rebuildTerritory().
- test_culture.cpp: one culture per continent, valid ethos/faith, governments per
  tier reflected in names, mono-cultural realms, determinism, RNG isolation,
  save->load->recompute parity. All 12 suites green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 07:58:58 +02:00
0638398d44 Auto-pause when switching to another phase
Entering Live World (W), and starting/stopping hydrology (H or the Start Phase 3
button), now pause the sim first instead of immediately resuming, so you can look
at the world before the new stage runs (press SPACE to start). Leaving Live World
already paused; the continuous forming->drift geological clock is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:07:17 +02:00
ee2ef3655b Civ Step 3: territory, nations & political borders
Group settlements into realms (city-states / kingdoms / empires) and give them
territory + political borders, all derived deterministically from the saved
settlement set (no save-version bump, step-back free).

- PlanetNation.{hpp,cpp}: computeTerritory() — size-scaled influence range per
  settlement, realm grouping (a town joins the nearest larger capital within its
  annexation reach, else founds its own nation), tier by member count / total pop,
  and per-cell ownership maximising range - angular-distance (wilderness frontiers
  where no settlement reaches). No RNG → tectonic stream untouched.
- Territory colour mode + nationColor, buildNationBorders (plate dual-contour
  reused), realm labels at capitals, a Realms info tab, cell-info realm line, and
  kind=4 WorldEvents (realm founded / rises to empire / collapsed). Key P toggles
  the view + borders; territory recomputed once per sim year, on placement, load
  and step-back.
- civTerritory*/civVassalRange/civEmpire* config knobs (self-describing → no save
  break); test_nation.cpp covers ownership/wilderness, empires vs city-states,
  realm grouping, tiers, determinism, RNG isolation, save/load parity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:00:32 +02:00
868cc90667 Civ: cluster settlement placement (habitability-weighted, irregular)
Placement used greedy farthest-first with a hard minimum spacing, so settlements
came out as a near-uniform lattice (unrealistic). Now placeSettlements() does
habitability-weighted random sampling (weight = habitability^civClusterExp) with
a soft Gaussian suppression (civMinSpacingRadians) around each pick, so towns
cluster on good land (rivers/coasts/fertile valleys) at irregular spacing and
leave empty stretches between. Nearest-neighbour distances now span ~0.04..0.39
rad (was ~uniform) and placement concentrates on the better cells.

- New knob civClusterExp (3.0; higher = tighter clustering on the best land);
  civMinSpacingRadians repurposed as the soft suppression scale (0.10 -> 0.06).
- Separate sCivRng + deterministic, so determinism / RNG isolation hold.
- test_civ: replaced the hard-spacing assertion with clustering checks
  (nearest-neighbour spacing varies; placed cells beat the habitable mean).

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:25:25 +02:00
3655dc0661 Civ settlements: environment-driven dynamic growth (droughts, harvests, storms)
Settlements were static -- every town grew with one global rate to the same
habitability cap (frozen in Live World), so they grew "the same amount
everywhere". Now stepCivilization(dtHours, liveTime) is environment-driven and
time-varying. All drivers are deterministic functions of (region, year, seed),
so step-back replays them with no new saved/snapshot state (population is
already snapshotted).

- Differentiated growth: rate scales with habitability (fertile boom, marginal
  crawl); capacity K = maxPop * habitability * siteQuality * conditions.
- Site quality: max city size varies by an order of magnitude with location --
  a great river (log-scaled discharge) or coast hosts a metropolis, a dry inland
  cell a town (civSiteVariety). This spreads final sizes ~8-9x instead of all
  saturating equally.
- Conditions (per ~20deg region, per year): multi-year droughts (worse in arid
  regions), year-to-year harvests (bigger swings inland), rare cold years, river
  floods, and the existing volcano ash. Regional, so different parts of a
  continent have different fortunes.
- Storms kill directly: a system within its radius of a town deals deaths scaled
  by strength (hurricanes x civHurricaneDeathMult) -- a parked hurricane can gut
  a coastal city. Reads the already-snapshotted storms().
- Famine: accelerated loss when food < population; sustained drought / acute
  disaster can collapse a settlement to ruins (revives when conditions return).
- Viewer: markers withered-tinted by hardship (3D + 2D), cell-info "drought/
  conditions" line, and cause-attributed kind=3 events ("Hurricane X devastates
  Y", "Famine shrinks Y to a Town", tier up/down/abandon).

New civ* knobs (self-describing config -> no save bump). test_civ extended:
sizes vary widely (not uniform), bad-year declines, a hurricane over a town
kills its population. All 10 suites pass; GUI build clean. Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:26:56 +02:00
2351fb79a5 Ecoregions atlas + Civilizations Step 2 (habitability & settlements, save v20)
Two features (the ecoregions layer was authored locally and was still
uncommitted; civilization Step 2 builds on top and is intermingled in shared
files, so they land together):

Ecoregions (v19, PlanetEcoregions.*, key E):
- generateEcoregions() flood-fills cells sharing biome + land/water context +
  productivity band into named ecological provinces (dominant flora/fauna/funga
  + per-kind productivity), a separate sEcoRng. ColorMode::Ecoregion + an Eco
  tab + cell-info dominants. Saved v19 (v18 geography reshuffle salt already in).

Civilizations Step 2 (v20, PlanetCiv.*, keys U/I):
- computeHabitability(): derived per-cell food/livability (climate comfort +
  water access (rivers/lakes/coast) + food (flora/fauna + ecoregion
  productivity), gated by freezing winters / high terrain). ColorMode::
  Habitability (key I).
- placeSettlements() (key U, "the dawn"): one-time greedy placement on the best
  well-spaced fertile cells (separate sCivRng; named from the continent's
  NameGen bank). The set is fixed, so the only mutable per-step state is each
  settlement's population.
- stepCivilization(): logistic growth toward K = civMaxPopulation*habitability,
  cut where an active volcano ashes the area, so settlements grow / decline /
  are abandoned (floored at 1 so a site can revive). Tiers village->town->city.
  Runs in liveAdvance; detectLiveEvents logs kind=3 events.
- Step-back snapshots only the population vector (WeatherSnapshot.settlementPop).
  3D + 2D tier-sized markers + city/town labels, a Civ tab, cell-info line.
  buildGeometry() clears settlements on reseed. Save v20; sCellSettlement
  rebuilt on load. civ* config knobs.

New test_ecoregions.cpp + test_civ.cpp; all 10 headless suites pass; GUI build
clean. CLAUDE.md / design-notes / BUILD.md updated (roadmap Step 2 done).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:01:43 +02:00
554878ef5e Add geography atlas reshuffle 2026-06-29 21:38:23 +02:00
842a7b4229 Reset geography and volcano state on rebuild 2026-06-29 21:18:21 +02:00
8ae2f14bae Geography polish: ocean basins, unique name roots, name new islands
Feedback fixes on the just-shipped atlas:

- Ocean basins: the connected world ocean was always one feature ("1 ocean",
  unrealistic). Now generateGeography partitions it via a distance-from-land
  watershed -- greedy farthest-first deep-water centres (geoOceanSepRadians apart,
  >= geoOceanDeep rings from land) + multi-source BFS Voronoi over the ocean
  graph. Each basin -> a named Ocean (or Sea if small). Defaults (sep 1.4 rad,
  deep 4) give ~4-6 oceans on an Earth-like world, 1 on a waterworld. New geo*
  knobs geoOceanSepRadians / geoOceanDeep.
- Shared names: features now dedupe on the PROPER-NOUN root (not the formatted
  string), so a continent "Karn", a "Karn River" and "Karn Mountains" can't
  coexist. NameGen also avoids identical adjacent syllables ("shio-shio").
- New islands: Planet::nameNewLand(cell) adds a volcanic island to the atlas on
  the fly when it breaches the sea -- joins an adjacent landmass or mints a fresh
  unique Island name; the island-formation WorldEvent now carries that name.

test_geography adds: proper-noun-root uniqueness, nameNewLand join vs mint.
All 8 headless suites pass; GUI build clean. Docs updated. (Save still v17 --
GeoFeature layout unchanged.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:24:58 +02:00
a4a06996fa Civilizations Step 1: geography & place-naming (the atlas, save v17)
The foundation of the civilization arc: name the world so everything civic
(territory, borders, place-of-origin) can reference it. This is pure derived
geometry + a deterministic namer, so it lives in the raylib-free engine and is
fully testable headless. No agents/clock yet -- those come in later steps.

- NameGen.{hpp,cpp} (new): deterministic procedural name generator (xorshift
  syllable banks; bankForRegion gives each continent a "language" so its rivers/
  mountains share a sound). Reused by the whole civ arc later.
- PlanetGeography.{hpp,cpp} (new): Planet::generateGeography() extracts named
  features by connectivity over the fixed grid -- continents/islands (connected
  land), oceans/seas (connected water), lakes (inland filled basins), mountain
  ranges + peaks (connected high terrain), rivers (largest discharge mouths
  traced upstream via flowTo). Separate RNG (sGeoRng) keeps tectonic determinism
  intact; per-cell index arrays (sCellLand/Water/Range/River) give O(1) lookup.
- Save v17: geography block (feature records with names + per-cell region arrays)
  appended in writeState/readState; readState gains hasGeography; pre-v17 saves
  load with none (regenerated on M). geo* config knobs + validation.
- Render: key M toggles place-name labels on globe (manual projection) + 2D map
  (minor features only when zoomed); a 5th "Atlas" live-info tab lists features
  by kind (click a row -> focusCell); cell-info shows a "region" line. Generated
  lazily on a settled world (M) or on entering Live World (W).
- test_geography.cpp (new, in CMake foreach): extraction, per-cell membership,
  river-traces-to-sink, names unique/deterministic, RNG isolation, v17 round-trip.
  All 8 headless suites pass; GUI build clean. Docs updated (CLAUDE/design-notes/
  BUILD), incl. the multi-step civilization roadmap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:10:06 +02:00
0c8dbed3d7 Add stateful volcano lifecycle and event log 2026-06-29 14:35:51 +02:00
275511713c Add volcanoes & volcanic islands (Live World, save v14)
On entering Live World a one-time pass places volcanoes by tectonic context
(very high prob on young spreading-ridge/"new-plate" cells, medium on plate
borders, low elsewhere). Over the live clock they erupt; submarine vents build
up and breach sea level into new volcanic islands, land vents grow cones, and
each eruption injects a drifting ash cloud + local cooling into the weather.

Design: eruption state (built height + intensity) is a PURE FUNCTION of liveTime
(like insolation/tides/seasons), so the live stepper rewinds islands & eruptions
for free -- no per-cell snapshot, no volcano undo history. The only integrated
side-effect is the ash plume into sCloud (reverts via the weather snapshot).

- src/sim/PlanetVolcano.cpp (new): placeVolcanoes (separate RNG, reservoir-
  sampled to volcanoMaxCount; tectonic determinism intact) + stepVolcanoes
  (reassert elevation = baseElev + built(liveTime); breach/un-breach; ash).
- Volcano struct + volcano* config knobs (PlanetTypes.hpp); Planet members +
  decls; readState gains hasVolcanoes; CONFIG_FIELDS + validateConfig.
- Save bumped to v14: flag-gated volcano block (set + sVolRng) in writeState/
  readState; pre-v14 saves load with none and place on next Live World entry.
- Render: 3D cone + eruption glow/ash-plume (DrawCylinderEx) and 2D triangle
  markers, key V toggle, HUD line, cell-info volcano line. Lazy placement on W
  entry and on loading a live-world save with no volcanoes.
- test_volcano.cpp (new, registered in CMake): determinism, RNG isolation,
  context classification + probability ordering, monotonic build + sea-level
  breach + step-back recede (pure function of liveTime), ash->cloud, v14
  round-trip. All six headless suites pass; GUI build clean. Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 12:48:45 +02:00
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
695e07ba99 Fix deterministic live weather rewind 2026-06-28 20:42:14 +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
7631eb4964 Live stepper robustness: manual steps always snapshot; clearer no-history status
Moved the step-back snapshot out of liveAdvance: a manual forward step now ALWAYS records the
pre-step state (rate-independent, so changing the rate or mixing play+step no longer makes the
back-step jump), while a continuous run records a throttled snapshot (~1/sec). Step-back with no
recorded past (e.g. immediately after a load) now shows a clear status instead of silently
freezing the storms. Forward-then-back reverses weather + storms as before.

Note: a load restores the current storms (v11) but not their past trajectory, so stepping back
BEFORE the loaded moment can only rewind the sky -- reversing loaded storms needs the rewind
history, which isn't persisted (see follow-up).

Build clean; logic/weather suites pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:25:35 +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