Cultures stop being static one-per-continent blocs. Culture identities are
now stateful: the list is append-only (seeded once at the dawn, schism
children appended later, records frozen after creation) and the
per-settlement culture is mutable state.
- stepCulture(year) in the yearly tick (pure hashes, no RNG):
assimilation (a conquered settlement adopts its ruler's culture, which
drops the Step-5 revolt cultBonus -> assimilation pacifies provinces),
border conversion (population x trade-prestige pressure; realm capitals
exempt), schism (a far-flung coherent cluster -- typically overseas
colonies -- breaks away as a new people with a local-bank NameGen name
and ethos/faith re-derived from its own lands).
- computeCultures() became a pure derived refresh (seeds only when the
list is empty); seedCultures() reproduces the old per-continent peoples
byte-identically for the dawn and pre-v23 loads.
- Colonies inherit the founder's culture at founding; the Step-3
mono-cultural vassalage rule is culture-matched (regionId fallback) so
schism clusters found their own realms -> colonial independence wars.
- Per-cell culture view colours by the owning settlement's culture, so a
conquered city keeps its colour until it assimilates.
- Save v23: culture identities + sSettleCulture + next-id counter, also in
the step-back frames; snapshots rewind via truncate-and-replay. Pre-v23
saves re-seed on the next refresh.
- Kind-7 world events; Cultures tab hides extinct peoples and shows a
schism child's founding year. Knobs cult* in planet.cfg.
- New test_cultevo.cpp (30 checks); all 15 existing suites still pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two things:
1. Fix: pressing R (reseed) after civilization existed left stale territory/
culture/war/diplomacy/trade overlay lines drawing on the new world.
regenWorld() now clears the civ overlay segment vectors + their toggles and
resets a civ colour mode back to Biome.
2. Feature: the settlement set is no longer fixed after U. stepColonization()
runs once per sim year: a kingdom+ realm (totalPop >= civColonyMinPop) may
found a new settlement on the best unclaimed, habitable, well-spaced cell
within reach -- overland, or across water from a coastal member (so islands /
other continents get colonised). The colony is APPENDED and bound to the
founder's realm by allegiance (Step 5) + a colonial supply trade link
(computeTrade, Step 7) so it survives on marginal land while the founder
lives; when the founder's capital falls, allegiance clears -> supply ends ->
the colony subsists locally and may wither. Deterministic (pure hashes).
The growing set needs no save-format change: the v20 settlement block is
variable-length, and restoreWeather now TRUNCATES settlements to the
snapshot's count (step-back drops colonies founded after; they re-found
identically on replay). Founding logs a kind=3 event. civColon* config knobs.
(Determinism fix: within one stepColonization call the per-settlement arrays
sSettleNation/sSettleAllegiance/sCivCond are resized on each append, else the
next realm's member loop read out of bounds.)
test_colony.cpp: colonies founded (set grows), colony in founder's realm + a
supply link, cross-water/island colonies, founder loss frees + de-supplies,
step-back truncates, determinism, save round-trip. All 16 suites green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>