planetsim/BUILD.md
Jonas Reith 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

13 KiB

Planet Sim - World Creation

Fixed icosphere geometry; properties (elevation, plate, age, climate, biome) flow over it. World creation runs as continuous, overlapping stages on a geological clock: tectonics (boundary stress forms mountains/trenches) → continental drift & erosion → hydrology (rivers/lakes) → climate (temperature/precipitation) → biomes → biota (flora/fauna/funga). Initial terrain forming is a generator (a couple hundred paced ticks, ~3 s, auto-pauses at equilibrium), then drift/erosion/etc. continue. Press R to reseed, SPACE to pause. (The eventual goal is a separate slow real-time "Live World" weather/life mode.)

Build (Nobara / Linux)

Dependencies (raylib build needs these dev headers):

sudo dnf install cmake gcc-c++ mesa-libGL-devel libX11-devel \
    libXrandr-devel libXinerama-devel libXcursor-devel libXi-devel \
    wayland-devel libxkbcommon-devel

Then:

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
./build/planetsim

raylib 5.5 is fetched automatically via CMake FetchContent.

OpenMP parallelizes the tectonic step (auto-detected by CMake; ships with gcc, no extra package needed). It's optional -- without it the sim still runs, serial. The step is memory-bandwidth-bound, so OMP_NUM_THREADS=4 to 8 gives the full ~2.8x speedup; the default uses all cores for no extra gain:

OMP_NUM_THREADS=6 ./build/planetsim

Controls

LMB drag    orbit camera (in the 3D view)
wheel       zoom
hover       show cell info (works in the 3D globe and the 2D map)
click       open tile detail panel (subtiles grid, hoverable) + overlay
C           close the detail panel
1 .. 7      color by elevation / plate / age / crust type / biome / temperature / precipitation
8 / 9 / 0   color by biota density: flora / fauna / funga
B           toggle plate borders (on by default)
D           toggle per-plate drift arrows + P<id> labels (on by default)
G           toggle lat/lon graticule (+ degree numbers on the 2D map edges)
J           toggle rivers (Phase 2.5 hydrology)
H           start/stop Phase 2.5 (hydrology: rivers, lakes, fluvial erosion)
L           generate biota population (flora/fauna/funga; settled world; re-press regenerates)
W           enter / leave Live World (slow real-time clock; settled world)
N           toggle the day/night terminator (Live World)
T           toggle the tide-coloured coastline (Live World)
O           toggle ocean-current arrows (warm = poleward/red, cold = equatorward/blue)
SPACE       pause while forming / re-evolve once settled (or the on-screen button)
[ / ]       drift speed (My/s) -- in Live World: live clock rate (hours/s, hour->month)
S           single tectonic tick
F           fast-forward Phase-1 forming to settled (instant)
R           reseed planet (restart forming)
+ / -       subdivision level (detail), 1..7
F5 / F9     save / load full state (planet.save)
F12         screenshot to screenshot.png
F2          reload planet.cfg (validated) and regenerate

CLI flags (applied before the first load/generate):

--seed N        override the config seed
--config PATH   use an alternate config file instead of planet.cfg

Files (written in the working directory)

planet.cfg   human-editable key=value config of every PlanetConfig parameter;
             auto-created on first run, reload live with F2. Range-checked on
             load; an invalid file reverts to safe defaults (not overwritten).
planet.save  binary snapshot (versioned, currently v9: +moons; v8 +Live World clock;
             v7 +biota): seed + config + full planet state; F5 writes it, F9 reloads and
             resumes deterministically. As of v6
             the config is stored as a self-describing key=value block (like
             planet.cfg), so adding/removing config fields no longer breaks saves
             (unknown keys ignored, missing keys default). v6 cannot load pre-v6
             saves (one-time break) -- regenerate them.

Config (planet.cfg, or PlanetConfig defaults in code)

radius        6.371e6 m (Earth)   -- free to change
subdivisions  5  (~10k cells, ~223 km/cell)
plateCount    12
seaLevel      0 m
axialTilt     23.44 deg  obliquity: leans the 3D globe + spin axis (visual; seasons later)

Tectonic tuning (also PlanetConfig): relief builds gradually toward an isostatic equilibrium instead of saturating to the clamp.

continentBase  300 m      resting elevation of continental crust
oceanBase     -6000 m     deep abyssal floor (oldest oceanic crust)
upliftGain     1.3e5      m/tick per unit convergence stress
beltWidth      3          cell-rings a mountain belt spreads inland
relax          0.02       isostatic relaxation toward base, per tick

Orogeny + seafloor (PlanetConfig): tall, persistent mountains and age-based ocean depth. The orogeny boosts (collision/arc/persistence) are drift-only -- Phase 1 forms as before; tall mountains grow during Phase-2 drift.

collisionFactor    1.8    continent-continent uplift (Himalaya); raise = taller
arcFactor          1.4    continental subduction-arc uplift (Andes)
isostaticPersist   0.85   how strongly high crust resists relax (0..<1)
rootScale          2500   m above continentBase where persistence saturates
peakSoftCapStart   7000 m below this a tick's uplift always takes (P=1)
peakSoftCapEnd    12000 m at/above this uplift never takes (P=0); also the elev clamp
peakFailDrop       200 m  max random drop when the grow roll loses (drift-only)
ridgeDepth        -2500 m shallow elevation of brand-new crust at a ridge
seafloorSubsidence 280    m per sqrt(My): seafloor deepens with crustal age
seafloorSeedAge    80 My  initial oceanic age spread at generation

Hydrology (PlanetConfig, Phase 2.5): macro drainage network + fluvial erosion. Drift keeps running at a finer timestep during hydrology. (Config keys keep the phase3* names for save/cfg compatibility; the UI labels this "Phase 2.5".)

phase3AfterMy      300 My drift before the "Start Phase 2.5?" prompt
phase3DtScale      0.2    hydrology timestep = cflDtMy()*this (finer = slower drift)
rainfall           1.0    uniform precip per cell (drainage-area unit)
riverThreshold     25     discharge above which a cell is a river (lower = richer network)
riverIncision      0.02   K in stream-power incision K*Q^m*S^n*dt (raise = carve faster)
riverDischargeExp  0.5    m (discharge exponent)
riverSlopeExp      1.0    n (slope exponent)
riverTransport     0.1    transport capacity coefficient (cap = this*Q*S)
depFrac            0.25   fraction of excess sediment deposited per cell

Biomes (PlanetConfig, Phase 3): per-cell biome classification thresholds. Temperature = biomeEquatorTemp - biomePoleDrop*(|lat|/90)^biomeLatExp - biomeElevLapse*elevAbove.

biomeEquatorTemp   30 C    temperature at the equator, sea level
biomePoleDrop      58 C    equator->pole temperature drop
biomeLatExp        1.3     >1 keeps mid-latitudes temperate (cold concentrates at poles)
biomeElevLapse     0.006   C lost per metre above sea level
biomeIceTemp      -9.5 C   below -> Ice (polar caps + snowcaps); raise = bigger caps
biomeTundraTemp    2 C     below (and above ice) -> Tundra/Taiga
biomeTaigaTemp     10 C    cool + wet -> boreal forest
biomeSavannaTemp   22 C    warm + moderate moisture -> savanna
biomeMountainElev  3000 m  above sea level -> Mountains
biomeHillsElev     1200 m  above sea level -> Hills
biomeBeachBand     60 m    above sea level + adjacent ocean -> Beach
biomeLowlandElev   500 m   wetlands only below this elevation
biomeWetlandMoist  0.72    moisture above this (low lowland) -> Wetland
biomeDesertMoist   0.28    moisture below this -> Desert
biomeGrassMoist    0.50    moisture below this -> Grassland/Savanna, else Forest
biomeTaigaMoist    0.40    cool + above this -> Taiga (else Tundra)
biomeLakeMinDepth  20 m    filled-basin depth above sea level counting as a Lake
biomeSeasonWeight  0.6     winter temp weight in the Tundra/Taiga cutoffs (0 = annual mean only)

Seasons (PlanetConfig): axialTilt (above) drives per-cell summer/winter temps; color key 6 cycles temperature -> summer -> winter -> seasonality.

seasonAmpMax       18 C    max seasonal half-range at full tilt/latitude/interior
seasonLatExp       1.2     latitude shape exponent (>1 pushes swing toward the poles)
seasonOceanFactor  0.15    ocean/coast seasonal-swing floor (thermal inertia)
seasonContinentRings 6     ocean-distance rings to reach full continentality (1 ~ 223 km)

Climate (PlanetConfig, Phase 3): temperature uses the biome* temp params above; precipitation advects ocean moisture along zonal winds (windward rain, leeward rain shadow, dry interiors) then diffuses it. Color modes 6 (temperature) / 7 (precipitation).

climateOceanMoisture   1.0    moisture air carries leaving the ocean (source)
climateRainEfficiency  0.5    fraction of available moisture*belt that rains per cell
climateOrographic      3.0    extra rain per unit normalized upslope (windward)
climateOroRefHeight    500 m  upslope that counts as one orographic unit
climateContinentality  0.05   fractional moisture lost per inland cell (dries interiors)
climateWindPasses      50     moisture-advection iterations (steady state)
climateMoistureSmooth  12     precipitation diffusion passes (raise = smoother, more grass/forest)
climateCurrentFactor   4 C    max coastal warming/cooling from ocean currents (0 = off; O shows arrows)

Biota (PlanetConfig): flora/fauna/funga. Density scalars drive color modes 8/9/0; the discrete slot/point population is generated on demand (L) and saved (v7).

bioVegTempMin        -5 C    below this no plant growth
bioVegTempOpt        15 C    at/above this temperature isn't limiting (flora)
bioVegMoistRef       0.5     normalized moisture where water isn't limiting (flora)
bioFaunaProductivity 0.9     herbivore carrying capacity per unit vegetation
bioCarnPreyMin       0.30    min local prey (fauna density) to support carnivores
bioCarnScale         1.0     carnivore weight ramp above the prey threshold
bioFungaMoistRef     0.4     normalized moisture where fungi aren't water-limited
bioFungaFloraWeight  0.6     how much fungi lean on flora (organic matter), 0..1
bioFungaTempMin     -15 C    above this fungi are not cold-limited (cold-tolerant)
bioRegionBonus       0.5     weight boost for archetypes present in same-biome neighbours
bioFloraSlots        12      max distinct flora per cell (point budget caps abundance)
bioFaunaSlots        10      max distinct fauna per cell
bioFungaSlots        8       max distinct funga per cell
bioFloraPoints       20      flora point budget at full density (scaled by density)
bioFaunaPoints       16      fauna point budget at full density
bioFungaPoints       14      funga point budget at full density

Live World (PlanetConfig): the slow real-time clock (hours -> weeks/months) over the finished planet -- a moving day/night terminator, a live seasonal temperature cycle and a moving snow line. axialTilt (above) drives the seasonal declination. Press W to enter.

dayLengthHours   24      hours in one planetary day (rotation -> day/night)
yearLengthDays   365.25  days in one planetary year (orbit -> seasons)
snowTemp         0 C     land below the live temperature shows snow
seaIceTemp      -2 C     ocean below the live temperature shows sea ice
tideAmplitude    0.6 m   equilibrium-tide scale per unit tide-raising weight
tideSunFactor    0.46    sun's tide weight relative to a unit moon (Earth ~0.46)

Moons (1-3, randomized in generateMoons() + saved v9): orbit on the live clock, raise the tides with the sun, and render as small lit spheres with phases, orbit rings and eclipses.

Headless logic test (no display)

g++ -std=c++17 -O2 -Isrc/sim test_logic.cpp src/sim/IcoSphere.cpp \
    src/sim/Planet.cpp src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp \
    src/sim/PlanetErosion.cpp src/sim/PlanetHydrology.cpp \
    src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp src/sim/PlanetLive.cpp \
    src/sim/PlanetOcean.cpp src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp \
    src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp src/sim/PlanetIO.cpp \
    -o /tmp/t && /tmp/t

# Biota / Live World / Ocean suites: same source list, swap test_logic.cpp ->
# test_biota.cpp, test_live.cpp or test_ocean.cpp

Verifies geometry, plate assignment, gradual non-saturating relief and determinism. Run after changing Planet::step().

Notes for later phases

  • Cell has a subgrid shared_ptr hook (null in phase 1) for the future fine-resolution per-cell mesh (phases 4/5: civilization/culture).
  • neighbors adjacency already built -> reuse for diffusion, climate, cross-cell-boundary interaction.
  • elevation is continuous meters (double); climate in phase 3 can read it as a smooth function, not coarse bands.