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>
208 lines
12 KiB
Markdown
208 lines
12 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)
|
|
SPACE pause while forming / re-evolve once settled (or the on-screen button)
|
|
[ / ] drift speed (My per real second, Phase 2/3)
|
|
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 v6): 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)
|
|
|
|
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
|
|
|
|
## 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/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp \
|
|
src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp src/sim/PlanetIO.cpp \
|
|
-o /tmp/t && /tmp/t
|
|
|
|
# Biota suite: same source list, swap test_logic.cpp -> test_biota.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.
|