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>
328 lines
20 KiB
Markdown
328 lines
20 KiB
Markdown
# 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)
|
|
K toggle weather clouds/rain cover (Live World)
|
|
V toggle volcano markers (Live World; cones + eruption glow, build into islands)
|
|
M toggle place-name labels (names continents/oceans/ranges/rivers/lakes; Atlas tab)
|
|
E ecoregions colour view (names ecological provinces on first use; Eco tab)
|
|
I habitability heat map (where civilization can thrive)
|
|
U settlements: the dawn of civilization on first press, then toggle markers (Civ tab)
|
|
Y follow-cam: cycle the 3D camera through active storms (Live World; off after last)
|
|
. / , step the live clock forward / back by one rate-unit (auto-pauses; back also
|
|
rewinds weather + storms via an undo history)
|
|
wheel over the 2D map: zoom toward cursor (1-8x); drag pans when zoomed
|
|
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 (in Live World: step the clock forward, like '.')
|
|
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 v17: +geography/atlas; v16
|
|
+event log; v15 +stateful
|
|
volcanoes; v13 +Live World clock rate; v12 +step-back history
|
|
(~40 frames, so a load can rewind storms); v11 +weather systems/storms;
|
|
v10 +weather fields; 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.
|
|
|
|
Weather (PlanetConfig, Live World): a dynamic clouds/rain cycle on the live clock (key K),
|
|
saved v10. Evaporate over warm seas -> advect along the wind -> condense -> rain -> dissipate.
|
|
|
|
weatherEvapRate 0.4 /h ocean evaporation toward marine saturation
|
|
weatherWindKmh 45 km/h wind speed advecting humidity/cloud
|
|
weatherSatBase 0.4 air saturation at 0 C (lower = cloudier)
|
|
weatherSatTempCoef 0.025 saturation rise per +1 C
|
|
weatherCondense 0.6 /h supersaturation -> cloud
|
|
weatherOrographic 0.0009 extra condensation per m of windward upslope
|
|
weatherRainThresh 0.5 cloud cover above this rains
|
|
weatherRainRate 0.5 /h rain rate from excess cloud
|
|
weatherCloudDissip 0.12 /h cloud clearing
|
|
weatherSystemMax 8 max concurrent moving weather systems
|
|
weatherSpawnRate 0.06 /h genesis probability scale
|
|
weatherSystemSpeed 28 km/h drift speed of systems
|
|
weatherTropicalSST 26 C min sea-surface temp for tropical genesis
|
|
weatherSystemRadius 0.16 rad angular radius of a system's cloud/rain shield
|
|
weatherSystemCloud 1.2 /h cloud stamped at a system core
|
|
weatherSystemRain 1.6 /h rain at a system core
|
|
weatherHurricaneStr 0.6 strength above which a tropical system is a hurricane/typhoon
|
|
|
|
Live info tabs (Live World): the panel beside the 2D map has Sky, Tides, Weather and Events.
|
|
Events are a saved, capped journal (newest 200) for storm genesis/intensification, volcano
|
|
dormancy, eruptions and volcanic-island breaches; clicking an event selects and centres its cell.
|
|
|
|
Volcanoes (PlanetConfig, Live World, key V): placed by tectonic context on entering Live World,
|
|
then integrated as stateful lifecycle agents: grow, go dormant, explode, puff ash, and regrow
|
|
weaker. Step-back snapshots include volcano state. Saved v15+.
|
|
|
|
volcanoProbRidge 0.55 per-cell placement prob on a young spreading-ridge cell
|
|
volcanoProbBorder 0.06 per-cell placement prob on a normal plate-border cell
|
|
volcanoProbInterior 0.003 per-cell placement prob elsewhere (hotspots)
|
|
volcanoMaxCount 60 global cap (reservoir-sampled, ratios preserved)
|
|
volcanoBuildRate 0.02 m/h of growth at activity=1 while growing
|
|
volcanoFreeHeight 1000 m absolute height below which vents cannot go dormant
|
|
volcanoInitialBuildMax 2500 m max pre-built height when Live World starts
|
|
volcanoMaxHeight 3200 m built height where dormancy becomes certain
|
|
volcanoDormancyRate 1.0 per-year dormancy hazard scale above free height
|
|
volcanoDormantMinYears 120 minimum dormancy before explosion
|
|
volcanoDormantMaxYears 1200 maximum dormancy before explosion
|
|
volcanoExplodeDropFrac 0.20 fraction of built height shaved by explosion
|
|
volcanoActivityDecay 0.70 activity multiplier after each explosion
|
|
volcanoDeadActivity 0.05 growth stops at/below this activity
|
|
volcanoBlastRadius 0.09 radian radius of the instant explosion ash blast
|
|
volcanoBlastCloud 1.5 cloud cover added inside the blast
|
|
volcanoAshMinYears 0.5 minimum sustained ash emission after explosion
|
|
volcanoAshMaxYears 3.0 maximum sustained ash emission after explosion
|
|
volcanoAshPuffCellsPerWeek 2.0 average local cells puffed per week
|
|
volcanoAshCloud 0.9 cloud cover injected by sustained ash
|
|
volcanoAshCooling 6 C peak local cooling under an active ash plume
|
|
|
|
Geography / atlas (PlanetConfig, key M): extract + name geographic features from the frozen
|
|
terrain (continents, islands, oceans, seas, lakes, mountain ranges, peaks, rivers). Generated
|
|
once on a settled world, saved v17. The foundation of the civilization arc.
|
|
|
|
geoContinentMinCells 40 land component >= this many cells = Continent (else Island)
|
|
geoSeaMaxCells 60 ocean basin <= this many cells = Sea (else Ocean)
|
|
geoOceanSepRadians 1.4 min angular gap between ocean-basin centres (higher = fewer oceans)
|
|
geoOceanDeep 4 min rings from land for a cell to seed an ocean basin
|
|
geoMountainElev 2500 m min elevation for a mountain-range cell
|
|
geoRangeMinCells 4 min cells for a named mountain range
|
|
geoRiverMinDischarge 80 min mouth discharge for a named river
|
|
geoMaxRivers 40 cap on named rivers (largest by discharge)
|
|
geoMaxPeaks 40 cap on named peaks (highest)
|
|
|
|
Civilization (PlanetConfig, key U): settlements placed once on the best fertile cells, then their
|
|
population grows/declines on the Live World clock toward a food-driven carrying capacity. Saved v20.
|
|
|
|
civMaxSettlements 80 cap on settlement sites
|
|
civMinSpacingRadians 0.10 min angular gap between sites (~640 km)
|
|
civMinHabitability 0.22 don't place a settlement below this habitability
|
|
civSeedPopulation 250 initial village population
|
|
civGrowthRate 0.02 logistic growth rate per year
|
|
civMaxPopulation 2e6 carrying capacity at habitability 1
|
|
civTownPop 5000 population at/above which a settlement is a Town
|
|
civCityPop 100000 population at/above which a settlement is a City
|
|
civAbandonPop 50 below this = abandoned/ruins (can revive)
|
|
civHabWaterWeight 0.45 habitability weight of water access (rivers/lakes/coast)
|
|
civHabFoodWeight 0.40 habitability weight of food (flora/fauna + ecoregion productivity)
|
|
civHabTempOpt 18 C most comfortable annual-mean temperature
|
|
civHabElevPenalty 2500 m high terrain steeply penalised above this
|
|
|
|
## 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/PlanetWeather.cpp src/sim/PlanetVolcano.cpp \
|
|
src/sim/PlanetBiota.cpp \
|
|
src/sim/PlanetFloraGen.cpp src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp \
|
|
src/sim/NameGen.cpp src/sim/PlanetGeography.cpp src/sim/PlanetEcoregions.cpp \
|
|
src/sim/PlanetCiv.cpp \
|
|
src/sim/PlanetIO.cpp -o /tmp/t && /tmp/t
|
|
|
|
# Same source list for every suite: swap test_logic.cpp -> test_biota / test_live / test_ocean /
|
|
# test_weather / test_volcano / test_geography / test_ecoregions / test_civ.
|
|
# The CMake build also includes test_events for the viewer event journal.
|
|
|
|
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.
|