#pragma once #include "Vec3.hpp" #include #include #include // Data structures shared across the Planet engine (raylib-free). The Planet // class itself lives in Planet.hpp; these are the per-cell / per-plate / config // types it operates on. Geometry never moves -- properties flow over the fixed // grid (see CLAUDE.md core principle). // Fine-resolution subgrid (phases 4/5 hook). Generated on demand for one macro // cell: a high-res patch of the sphere around that cell, with elevation blended // from the cell + its neighbors so it transitions smoothly across boundaries. struct SubCell { Vec3 unit; // direction on the unit sphere double elevation = 0.0; // meters (blended + fine detail noise) int nearestMacro = -1;// macro cell (this cell or a neighbor) it lies under }; struct SubGrid { int macroCell = -1; int res = 0; // grid is res x res, row-major std::vector sub; }; enum class PlateType { Oceanic, Continental }; // A moving weather system (Live World): a drifting low-pressure disturbance that travels with the // steering wind and stamps clouds & rain onto the weather fields. Geometry is fixed, so this is a // world-object agent (a point on the sphere, like a moon), not a cell. The intense tropical ones // (strength past weatherHurricaneStrength) are hurricanes/typhoons. Saved with the weather state. struct WeatherSystem { uint32_t id = 0; // stable id (for the viewer follow-cam; assigned at spawn) Vec3 pos; // unit position on the sphere double strength = 0.0; // intensity 0..1 (drives cloud/rain boost + marker size) double radius = 0.15; // angular radius (radians) double age = 0.0; // hours alive double life = 120.0; // total lifetime (hours) double spin = 1.0; // cyclonic sense: +1 CCW (N hemisphere) / -1 CW (S) bool tropical = false; // warm-core tropical (can become a cyclone) vs extratropical low }; // Phase-3 (climate & biomes) classification of a cell, derived from elevation, // latitude (temperature) and hydrology/coast (moisture). Stored per cell (uint8, // serialized) so Phase-4 civilization can read it. Keep Ocean == 0 so a default- // constructed cell reads as ocean. Extend by appending new entries (don't reorder // -- the numeric value is saved). enum class Biome : uint8_t { Ocean, Ice, Lake, Beach, Wetland, Grassland, Savanna, Desert, Forest, Taiga, Tundra, Hills, Mountains }; // Edit mode (manual world editing, save v24): a per-cell bitmask of which fields are // LOCKED against automatic per-tick recomputation. An edit with a field's bit clear is a // one-off "nudge" -- it changes current state but the ongoing simulation keeps evolving it // (like a meteor impact); with the bit set, the normal recompute for that field skips this // cell entirely until unlocked. Elevation/Plate/Crust/Biome lock the Cell field itself (no // extra storage -- see PlanetTectonics/Erosion/Hydrology/Drift/Biomes.cpp); Climate/Biota/ // Habitability have no persistent backing of their own (fully recomputed from scratch each // tick), so locking them also pins the frozen value in a small sparse map (see PlanetEdit.cpp). // Population/Allegiance/Culture are keyed by a settlement's home cell and gate its yearly // update in PlanetCiv/PlanetConflict/PlanetCulture.cpp. enum EditLock : uint16_t { LockElevation = 1u << 0, LockPlate = 1u << 1, LockCrust = 1u << 2, // oceanic + geoAge (crust identity) LockBiome = 1u << 3, LockClimate = 1u << 4, // temperature + moisture together LockBiota = 1u << 5, // flora/fauna/funga density together LockHabitability = 1u << 6, LockPopulation = 1u << 7, // settlement: population growth/decline frozen LockAllegiance = 1u << 8, // settlement: overlord assignment frozen LockCulture = 1u << 9, // settlement: culture assignment frozen }; // A natural satellite (Live World). Geometry is fixed, so the moon is a world object // (not a cell): it orbits on the live clock, raises tides, and renders as a small sphere. // Generated 1-3 per world from a separate RNG (so it never perturbs tectonic determinism) // and saved (v9). orbitRadius is in planet radii (render scale); tideWeight is the tide- // raising mass proxy; inclination tilts the orbit plane off the equator. struct Moon { double orbitRadius = 16.0; // planet radii (visual orbit distance) double periodDays = 20.0; // orbital period (planetary days) double phase = 0.0; // orbital phase offset (radians) double inclination = 0.0; // orbit-plane tilt off the equator (radians) double tideWeight = 1.0; // tide-raising strength (Moon mass proxy) double dispRadius = 0.10; // display sphere radius (visual size) }; // A volcano (Live World): a fixed point on the grid (one cell) placed by tectonic context when the // world enters Live World -- high probability on young spreading ridges ("new plate"), medium on // plate borders, low elsewhere (hotspots). It is a small stateful lifecycle agent: it grows, can // go dormant, explodes after dormancy, then regrows weaker. Saved (v15). struct Volcano { uint32_t id = 0; // stable id (markers / cell-info) int cell = -1; // the grid cell it sits on (fixed geometry) uint8_t kind = 2; // 0 = ridge (new plate), 1 = plate border, 2 = hotspot/interior uint8_t submarine = 0; // 1 if its baseElev is below sea level (can build an island) uint8_t phase = 0; // 0 = growing, 1 = dormant double activity = 0.5; // 0..1 eruption vigour (drives cadence + build rate) double baseElev = 0.0; // m: cell elevation captured at placement (build adds on top) double built = 0.0; // m: current height built above baseElev double timer = 0.0; // h: dormancy countdown double ashTimer = 0.0; // h: sustained ash emission after an explosion double ashCarry = 0.0; // fractional ash-puff accumulator }; // Result of one stepVolcanoes() call, telling the viewer how much of the view to rebuild: // `recolor` if any vent's cell elevation changed (cone/island grew/shrank), `breach` if a // submarine vent crossed sea level (a new island/sunk island -> needs biome reclassification). struct VolcanoUpdate { bool recolor = false; bool breach = false; }; // Civilization Step 5: an active war between two realms (identified by their capital SETTLEMENT INDEX, // a stable id across the yearly territory recompute). Stateful & saved (v21) + snapshotted for step-back. struct War { uint32_t id = 0; int attacker = -1; // capital settlement index of the aggressor realm int defender = -1; // capital settlement index of the defending realm long startYear = 0; double warscore = 0.0; // + favours the attacker, - the defender (accumulates from battles) int battles = 0; // number of resolved battle-years }; // Civilization Step 6: diplomacy. A standing relationship between two realms (by capital SETTLEMENT // INDEX, a humidity, cloud, rain; std::vector storms; std::vector volcanoes; uint32_t rng = 0, nextId = 0; uint32_t volRng = 0; // Civilization: settlement populations (the only mutable per-step civ state, since the set is // fixed after placement). Restored on a step back so towns rewind/replay with the clock. std::vector settlementPop; // Civilization Step 5 (conflict): mutable war state -- per-settlement allegiance (conquest), the // active wars and the war RNG. Restored on a step back so conquests/revolts rewind with the clock. std::vector settlementAllegiance; std::vector wars; uint32_t warRng = 0, warNextId = 0; // Civilization Step 6 (diplomacy): standing realm relations (alliances / rivalries / truces). std::vector diplomacy; // Civilization Step 8 (cultural evolution): per-settlement culture (mutable via conversion / // assimilation) + the culture-list LENGTH. Culture records are append-only and immutable after // creation, so a step back TRUNCATES the list (like colonies truncate settlements) and a // deterministic replay re-creates any schism child identically -- no string-bearing records here // (the viewer's persisted history frames are written as raw POD). std::vector settlementCulture; uint32_t cultureCount = 0, cultureNextId = 0; }; struct Plate { int id = 0; PlateType type = PlateType::Oceanic; // initial crust type seeded onto cells Vec3 driftAxis; // rotation axis (unit) for tangential drift on sphere double driftSpeed; // small value used by the Phase-1 uplift stress double angSpeed = 0; // Phase-2 advection: rotation rate in radians / My double speedCmYr = 0;// surface drift speed in cm/year (for display) bool baby = false; // Phase-2: young spreading proto-plate, not yet promoted }; // A fixed cell on the planet. Geometry never moves; properties flow. struct Cell { Vec3 unit; // unit-sphere direction (fixed) double elevation = 0.0; // meters, relative to sea level (continuous, fine) int plateId = -1; double geoAge = 0.0; // My since this crust was (re)formed at a ridge bool oceanic = true;// crust type travels WITH the cell (Phase-2 advection) Biome biome = Biome::Ocean; // Phase-3 climate/biome classification (derived) uint16_t editLock = 0; // Edit mode: EditLock bits (save v24), see above // Phase-2 advection accumulator: signed convergence distance built up with // the dominant other-plate neighbor (+ encroaching, - rifting), and which // plate is encroaching. double drift = 0.0; int invader = -1; std::vector neighbors; // Phase 4/5 hook: optional fine-resolution subgrid. Null until needed. std::shared_ptr subgrid; }; struct PlanetConfig { double radius = 6.371e6; // meters (Earth default) int subdivisions = 5; // icosphere level double seaLevel = 0.0; // meters int plateCount = 12; uint32_t seed = 1337; double axialTilt = 23.44; // obliquity, degrees (Earth ~23.4). Visual tilt of // the 3D globe + spin axis; groundwork for seasons. // --- Tectonic tuning (Phase 1) ------------------------------------------ // Relief builds gradually toward an isostatic equilibrium instead of // saturating: per-tick uplift competes with a relaxation pull toward the // plate's base elevation, so peaks asymptote at base + uplift/relax. double continentBase = 300.0; // m, resting elevation of continental crust double oceanBase = -6000.0; // m, deep abyssal floor (oldest oceanic crust) double upliftGain = 1.3e5; // m/tick per unit convergence stress int beltWidth = 3; // cell-rings a mountain belt spreads inland double relax = 0.02; // isostatic relaxation toward base, per tick // --- Orogeny: collision uplift + isostatic persistence (Phase 2 inc. 4) -- // Continent-continent collisions build the tallest ranges; thick (high) // continental crust resists isostatic relaxation, so ranges stand and are // erosion-limited rather than snapping back to continentBase. double collisionFactor = 1.8; // continent-continent uplift multiplier (Himalaya) double arcFactor = 1.4; // continental subduction-arc uplift (Andes) double isostaticPersist = 0.85; // how much high crust resists relax (0..<1) double rootScale = 2500.0;// m above continentBase where persistence saturates // --- Soft peak cap (drift-only): spread mountain heights, no hard plateau -- // Above peakSoftCapStart the chance that a tick's uplift "takes" falls linearly // to 0 at peakSoftCapEnd, so peaks settle across a height band instead of all // railing at one ceiling. A lost grow roll forfeits that tick's uplift and // shaves a random 0..peakFailDrop metres off. The hard elevation clamp's upper // bound tracks peakSoftCapEnd. Active only during drift (forming still settles). double peakSoftCapStart = 7000.0; // m: below this, uplift always takes (P=1) double peakSoftCapEnd = 12000.0; // m: at/above this, uplift never takes (P=0) double peakFailDrop = 200.0; // m: max random drop when the grow roll loses // --- Plate drift (Phase 2) ---------------------------------------------- double maxDriftSpeed = 20.0; // cm/year; fastest plate (Earth is 1-10) double ridgeDepth = -2500.0; // m, elevation of brand-new crust at a ridge // --- Seafloor aging -> depth (Phase 2 inc. 4) --------------------------- // Oceanic crust subsides as it ages (half-space cooling): depth = // ridgeDepth - seafloorSubsidence * sqrt(geoAge), clamped at oceanBase. double seafloorSubsidence = 280.0; // m per sqrt(My) of crustal age double seafloorSeedAge = 80.0; // My, initial oceanic age spread at generation // --- Plate dynamics (Phase 2): fission, stalemate kick, spreading plates -- // Periodic checks run every `splitCheckEvery` drift iterations. A plate over // `splitFraction` of all cells may rift in two with probability // splitProbBase + splitProbSlope * (percentOverThreshold) // (20%->5%, 21%->10%, ... 39%->100%). A plate whose cell count barely changed // over a window gets a stalemate "kick" (new direction + a speed boost). Young // rift crust grows on a "baby" proto-plate; once it reaches `babyPromoteFrac` // of all cells it becomes a real plate and grows volcanic landmass. // (Related spreading/volcanic fields are at bottom for binary save compat.) int splitCheckEvery = 10; // drift iterations between periodic checks double splitFraction = 0.20; // plate share of cells that may rift apart double splitProbBase = 0.05; // split probability at the threshold double splitProbSlope = 0.05; // added per 1 percentage-point over threshold double stalemateEps = 0.005; // |dCells|/cells below this over a window = stuck int stalemateWindows = 4; // consecutive stuck windows required before a kick double stalemateBoost = 1.5; // speed multiplier when kicking a stuck plate int miniPlateCells = 50; // a non-baby plate smaller than this is "mini" int fuseMinPlates = 3; // distinct mini plates in a cluster to fuse + steal // --- Erosion + sea level (Phase 2 increment 2) -------------------------- // erode() moves sediment downhill (highs wear down, basins/seas fill); a // proportional sea-level controller holds a target geographic land fraction. double erosionLandRate = 0.08; // subaerial erosion fraction / My double erosionSeaRate = 0.02; // submarine erosion fraction / My (slower) double landFractionTarget = 0.30; // geographic land goal (cells above seaLevel) double seaLevelStep = 100.0; // m, fixed nudge per adjustment when off target double seaLevelTol = 0.02; // deadband (land-fraction) where sea level rests int seaLevelEvery = 100; // erode calls between sea-level adjustments // Hard bounds on the controller: if a world's buoyant (continental) crust area // permanently sits below landFractionTarget (crust generation is independent of // this geographic target -- no guarantee they match), there is no seaLevel low // enough to reach the target using real land, and ageing oceanic crust keeps // deepening out of reach -- so an unbounded controller sinks seaLevel forever, // eventually misclassifying deep aging seafloor as "geographic land". These // clamp the candidate each adjustSeaLevel() step so it settles at a plausible // coastal offset instead of running away (a slightly-off land fraction beats a // physically nonsensical sea level). double seaLevelMin = -3000.0; // m, lowest the controller may sink seaLevel double seaLevelMax = 3000.0; // m, highest the controller may raise seaLevel // --- Spreading & volcanic (Phase 2 plate dynamics, kept here for binary compat) int babyMinCells = 4; // baby blobs smaller than this dissolve (noise) double babyPromoteFrac = 0.007; // baby-patch size (x N cells) to become a plate double volcanicLandFrac = 0.30; // fraction of a promoted patch turned into land double volcanicElev = 400.0; // m, volcanic-island starting elevation double landBand = 0.10; // soft land clamp: +/- around targetLand // --- Phase 3: hydrology (rivers, lakes, fluvial erosion) ---------------- // Macro drainage network on the fixed grid: depression-fill -> lakes, // steepest-descent routing -> rivers, stream-power incision + downstream // sediment transport/deposition (mass-conserving). Drift keeps running but // Phase 3 uses a finer timestep (cflDtMy * phase3DtScale). double phase3AfterMy = 300.0; // My of drift before the Phase-3 prompt double phase3DtScale = 0.2; // Phase-3 timestep = cflDtMy() * this (finer) double rainfall = 1.0; // uniform precip per cell (drainage-area unit) double riverThreshold = 25.0; // discharge above which a cell counts as a river // (lower = richer network incl. tributaries shown) double riverIncision = 0.02; // K in stream-power incision K*Q^m*S^n*dt double riverDischargeExp = 0.5; // m: discharge exponent in stream power double riverSlopeExp = 1.0; // n: slope exponent in stream power double riverTransport = 0.10; // transport-capacity coefficient (cap=this*Q*S) double depFrac = 0.25; // fraction of excess load deposited per cell // --- Phase 3: biome classification thresholds (see PlanetBiomes.cpp) ----- // Temperature model (deg C): equator-warm curve cooling super-linearly toward the // poles minus an elevation lapse. Moisture comes from latitude belts + hydrology. double biomeEquatorTemp = 30.0; // C at the equator, sea level double biomePoleDrop = 58.0; // C drop from equator to pole double biomeLatExp = 1.3; // >1 keeps mid-latitudes temperate (cold near poles) double biomeElevLapse = 0.0060; // C lost per metre above sea level double biomeIceTemp = -9.5; // below -> Ice (polar caps + glaciated peaks); raise = bigger caps double biomeTundraTemp = 2.0; // below (and above ice) -> Tundra/Taiga double biomeTaigaTemp = 10.0; // cool + wet -> boreal forest double biomeSavannaTemp = 22.0; // warm + moderate moisture -> savanna double biomeMountainElev= 3000.0; // m above sea level -> Mountains double biomeHillsElev = 1200.0; // m above sea level -> Hills double biomeBeachBand = 60.0; // m above sea level + adjacent ocean -> Beach double biomeLowlandElev = 500.0; // wetlands only below this elevation double biomeWetlandMoist= 0.72; // moisture above this (low lowland) -> Wetland double biomeDesertMoist = 0.28; // moisture below this -> Desert double biomeGrassMoist = 0.50; // moisture below this -> Grassland/Savanna, else Forest double biomeTaigaMoist = 0.40; // cool + above this -> Taiga (else Tundra) double biomeLakeMinDepth= 20.0; // filled-basin depth above sea level counting as a Lake double biomeSeasonWeight= 0.6; // how much winter temp (vs annual mean) sets the cold // Tundra/Taiga cutoffs (0 = mean only/old behaviour, 1 = winter) // --- Phase 3: climate (orographic precipitation) -- see PlanetClimate.cpp -- // Temperature reuses the biome* temperature fields above. Precipitation advects // ocean moisture along prevailing (zonal) winds: it rains on windward upslopes and // dries out leeward (rain shadow) and far inland (continentality). double climateOceanMoisture = 1.0; // moisture air carries leaving the ocean (source) double climateRainEfficiency = 0.5; // fraction of available moisture*belt that rains per cell double climateOrographic = 3.0; // extra rain per unit normalized upslope (windward) double climateOroRefHeight = 500.0; // m of upslope that counts as one orographic unit double climateContinentality = 0.05; // moisture lost per land cell crossed (dries interiors) int climateWindPasses = 50; // moisture-advection iterations (steady state) int climateMoistureSmooth = 12; // precipitation diffusion passes (wet/dry transition zones) double climateCurrentFactor = 4.0; // C: max coastal warming/cooling from ocean currents // (warm poleward currents raise, cold equatorward lower) // --- Seasons (obliquity) -- see PlanetClimate.cpp ----------------------- // axialTilt (above) drives a per-cell seasonal temperature range around the annual // mean sTemp: summer/winter = mean +/- A, with A = seasonAmpMax * tiltFactor * // latShape * continentality. Big swings at high-latitude continental interiors, // small near coasts/equator. Static fields (warmest/coldest month), not animated. double seasonAmpMax = 18.0; // max seasonal half-amplitude (C) at full tilt/lat/interior double seasonLatExp = 1.2; // latitude shape exponent (>1 concentrates swing toward poles) double seasonOceanFactor = 0.15; // continentality floor: ocean/coast seasonal swing fraction int seasonContinentRings = 6; // ocean-distance rings to reach full continentality (1 = ~223 km) // --- Biota: flora / fauna / funga (see PlanetBiota.cpp + *Gen.cpp) ------- // Density scalars (derived each tick) drive the colour views; the discrete // slot/point population (generated on demand, saved) draws archetypes by size. double bioVegTempMin = -5.0; // C below which plants don't grow double bioVegTempOpt = 15.0; // C at/above which temperature isn't limiting double bioVegMoistRef = 0.5; // normalized moisture where water isn't limiting double bioFaunaProductivity = 0.9; // herbivore capacity per unit vegetation double bioCarnPreyMin = 0.30; // min local prey (fauna density) to support carnivores double bioCarnScale = 1.0; // carnivore weight ramp above the prey threshold double bioFungaMoistRef = 0.4; // normalized moisture where fungi aren't water-limited double bioFungaFloraWeight = 0.6; // how much fungi lean on flora (organic matter) 0..1 double bioFungaTempMin = -15.0; // C above which fungi are not cold-limited (cold-tolerant) double bioRegionBonus = 0.5; // weight boost for archetypes present in same-biome neighbours double bioMarineBase = 0.15; // open-ocean baseline marine flora density (deep, far from land) double bioMarineShelfDepth = 2500.0; // m of depth over which shelf (light) productivity fades to base int bioMarineCoastRings = 3; // ocean rings from land over which coastal richness fades to base int bioFloraSlots = 12; // max distinct flora per cell (point budget caps abundance) int bioFaunaSlots = 10; // max distinct fauna per cell int bioFungaSlots = 8; // max distinct funga per cell int bioFloraPoints = 20; // flora point budget at full density (scaled by density) int bioFaunaPoints = 16; // fauna point budget at full density int bioFungaPoints = 14; // funga point budget at full density // --- Live World (slow real-time clock) -- see PlanetLive.cpp ------------- // The finished planet can run on a slow real-time clock (hours -> weeks/months) with a // moving day/night terminator, a live seasonal temperature cycle and a moving snow line. // dayLengthHours/yearLengthDays set the calendar; snowTemp/seaIceTemp the freezing lines. double dayLengthHours = 24.0; // hours in one planetary day (rotation -> day/night) double yearLengthDays = 365.25; // days in one planetary year (orbit -> seasons) double snowTemp = 0.0; // C: land below the live temperature shows snow double seaIceTemp = -2.0; // C: ocean below the live temperature shows sea ice double tideAmplitude = 0.6; // m: equilibrium-tide scale per unit tide-raising weight double tideSunFactor = 0.46; // sun's tide weight relative to a unit moon (Earth ~0.46) // --- Weather (Live World dynamic clouds & rain) -- see PlanetWeather.cpp ----- // A per-cell humidity/cloud/rain cycle advanced on the live clock: evaporate over warm // sunlit seas, advect along the prevailing wind, condense into cloud (more on windward // upslopes), rain out, and dissipate. Rates are per simulated hour. double weatherEvapRate = 0.4; // /h: ocean evaporation toward marine saturation double weatherWindKmh = 45.0; // km/h: prevailing wind speed for advecting humidity/cloud double weatherSatBase = 0.4; // air saturation humidity at 0 C (warmer air holds more) double weatherSatTempCoef = 0.025; // saturation rise per +1 C double weatherCondense = 0.6; // /h: fraction of supersaturation that becomes cloud double weatherOrographic = 0.0009; // extra condensation per m of windward upslope double weatherRainThresh = 0.5; // cloud cover above this precipitates double weatherRainRate = 0.5; // /h: rain rate from excess cloud double weatherCloudDissip = 0.12; // /h: cloud clearing (half returns to humidity) // --- Weather systems (moving lows / hurricanes / typhoons) -- PlanetWeather.cpp --- // Drifting low-pressure disturbances travel with the steering wind and stamp cloud/rain onto // the grid, so the sky visibly evolves; the intense tropical ones become tropical cyclones. int weatherSystemMax = 8; // max concurrent weather systems double weatherSpawnRate = 0.06; // /h: genesis probability scale (when below the cap) double weatherSystemSpeed = 28.0; // km/h: steering speed at which systems drift double weatherTropicalSST = 26.0; // C: min sea-surface temp for tropical genesis double weatherSystemRadius= 0.16; // rad: angular radius of a system's cloud/rain shield double weatherSystemCloud = 1.2; // /h: cloud stamped at a system's core (scaled by strength) double weatherSystemRain = 1.6; // /h: rain intensity at a system's core double weatherHurricaneStr= 0.6; // strength above which a tropical system is a hurricane/typhoon // --- Volcanoes (Live World) -- see PlanetVolcano.cpp ------------------------ // Placed once on entering Live World by tectonic context, then evolve statefully on the live // clock (step-back snapshots preserve/reverse that state). Submarine volcanoes build up to // breach sea level into new volcanic islands. double volcanoProbRidge = 0.55; // per-cell placement prob on a young spreading-ridge cell double volcanoProbBorder = 0.06; // per-cell placement prob on a normal plate-border cell double volcanoProbInterior = 0.003; // per-cell placement prob elsewhere (intraplate hotspots) int volcanoMaxCount = 60; // global cap on placed volcanoes double volcanoBuildRate = 0.02; // m/h at activity=1 while growing double volcanoFreeHeight = 1000.0;// m absolute elevation below which vents cannot go dormant double volcanoInitialBuildMax = 2500.0; // m: max pre-built height on Live World entry double volcanoMaxHeight = 3200.0;// m built height where dormancy becomes certain double volcanoDormancyRate = 1.0; // /year hazard scale once above volcanoFreeHeight double volcanoDormantMinYears = 120.0; // min dormancy before explosion double volcanoDormantMaxYears = 1200.0; // max dormancy before explosion double volcanoExplodeDropFrac = 0.20; // fraction of built height shaved by an explosion double volcanoActivityDecay = 0.70; // activity multiplier after each explosion double volcanoDeadActivity = 0.05; // activity floor below which growth stops double volcanoBlastRadius = 0.09; // rad: wide ash blast radius around the vent double volcanoBlastCloud = 1.5; // cloud added inside the explosion blast double volcanoAshMinYears = 0.5; // min sustained ash emission after explosion double volcanoAshMaxYears = 3.0; // max sustained ash emission after explosion double volcanoAshPuffCellsPerWeek = 2.0;// average local cells puffed per week while ashTimer runs double volcanoAshCloud = 0.9; // cloud cover injected at the vent per erupting hour (ash plume) double volcanoAshCooling = 6.0; // C: peak local cooling under an active ash plume // --- Geography (the atlas) -- see PlanetGeography.cpp ---------------------- // Thresholds for extracting + naming geographic features from the frozen terrain. Generated once // on a settled world (key M), saved (v17+). Tune to control what counts as a continent vs island, // an ocean vs sea, a named mountain range / major river, and to cap label clutter. int geoContinentMinCells = 40; // land component >= this many cells = Continent (else Island) int geoSeaMaxCells = 60; // ocean/basin <= this many cells = Sea (else Ocean) double geoOceanSepRadians = 1.40; // min angular separation between ocean-basin centres (radians; ~4-6 oceans) int geoOceanDeep = 4; // min rings from land for a cell to seed an ocean basin double geoMountainElev = 2500.0;// m: min elevation for mountain-range membership int geoRangeMinCells = 4; // min cells for a named mountain range double geoRiverMinDischarge = 80.0; // min mouth discharge for a named river int geoMaxRivers = 40; // cap on named rivers (largest by discharge) int geoMaxPeaks = 40; // cap on named peaks (highest) // --- Civilization: settlements & habitability -- see PlanetCiv.cpp ---------- // Placed once on a settled world ("dawn of civilization"); population then grows/declines on the // Live World clock toward a food-driven carrying capacity. Habitability blends climate comfort, // water access and food (flora/fauna + ecoregion productivity). int civMaxSettlements = 80; // cap on settlement sites double civMinSpacingRadians = 0.06; // soft suppression scale: a new settlement softens the weight of // cells within ~this angle (organic clustering, not a hard grid) double civClusterExp = 3.0; // habitability weighting exponent for placement (higher = settlements // cluster harder on the best land; 1 = mild, 0 = uniform among habitable) double civMinHabitability = 0.22; // don't place a settlement below this habitability double civSeedPopulation = 250.0; // initial village population at placement double civGrowthRate = 0.02; // logistic growth rate per year (toward carrying capacity) double civMaxPopulation = 1.0e6; // carrying-capacity SCALE (multiplied by habitability, site // quality and trade -- a top hub's K is several x this; the // real metropolis ceiling is the civMetropolisPop crowding) double civTownPop = 5000.0; // population at/above which a settlement is a Town double civCityPop = 100000.0;// population at/above which a settlement is a City double civAbandonPop = 50.0; // below this a settlement is abandoned (dormant; can revive) double civHabWaterWeight = 0.45; // habitability weight of water access (rivers/lakes/coast) double civHabFoodWeight = 0.40; // habitability weight of food (flora/fauna + ecoregion) double civHabTempOpt = 18.0; // C: most comfortable annual-mean temperature double civHabElevPenalty = 2500.0; // m above which high terrain steeply reduces habitability // Dynamic environment (PlanetCiv.cpp): growth differs by local conditions and varies over time // (harvests, droughts, cold years, floods, storms) so settlements aren't static. Deterministic // functions of (cell, year, seed) -> reversible with the live stepper; no save change. double civSiteVariety = 1.0; // 0 = flat capacities, 1 = full site-quality spread (big rivers/coasts host large cities) double civGrowthMin = 0.25; // growth-rate fraction at habitability 0 (1 = at habitability 1) double civHarvestVar = 0.25; // base year-to-year harvest swing amplitude (scaled by climate variability) double civDroughtStrength = 0.70; // how hard a full drought cuts a region's carrying capacity double civDroughtPeriod = 8.0; // years per drought-noise epoch (drought duration scale) double civDroughtThresh = -0.15; // drought-onset threshold on the slow noise (lower = rarer) double civDroughtArid = 0.50; // extra drought-proneness in arid regions (× aridity) double civColdYearStrength = 0.50; // crop loss in a rare cold year, × the cell's cold exposure double civFloodBonus = 0.25; // fertile-silt bonus on river cells most years (rare flood disaster) double civFamineRate = 0.15; // /year accelerated population loss when food < population double civStormDeathRate = 0.50; // /year population loss for a full-strength storm over a settlement double civHurricaneDeathMult= 3.0; // extra storm death multiplier for a hurricane/typhoon // Big-city demography: pre-industrial metropolises were population sinks (disease, crowding, food // logistics), so growth meets a headwind that rises with city size -- cities PLATEAU (~1-2M for the // best trade hubs) instead of exponentially chasing a huge carrying capacity for millennia. Plus // rare deterministic PLAGUES: probability rises with population and trade connectivity (contagion // is the cost of being a hub). All pure functions of (id, year, seed) -> reversible, no save change. double civMetropolisPop = 1.5e6; // crowding scale: mortality = civCrowdingLoss x (P/this)^2 per year double civCrowdingLoss = 0.02; // /yr crowding mortality at P = civMetropolisPop (0 = no plateau) double civCondBoomCap = 1.25; // cap on the good-year condition upside in the K target (droughts uncapped) double civPlagueRate = 0.01; // /yr per-settlement plague-outbreak chance (hash gate) double civPlagueDeathMin = 0.20; // min total wave kill fraction at full exposure double civPlagueDeathMax = 0.40; // max total wave kill fraction (also caps the per-year loss) double civPlagueTradeWeight = 0.5; // exposure share driven by trade connectivity vs pure size // Territory & nations (PlanetNation.cpp): influence range each settlement projects (size-scaled), // realm grouping (vassals/kingdoms), and the empire threshold. Derived -> recomputed, not saved. double civTerritoryBase = 0.035; // rad: base influence range of a seed-size village (~220 km) double civTerritoryScale = 0.05; // rad added per log10 of (population / seed) -- big cities reach far double civTerritoryMax = 0.35; // rad: cap on a single settlement's reach (~2200 km) double civVassalRange = 1.5; // a capital annexes smaller settlements within this x its range int civEmpireMinMembers = 5; // realm of >= this many settlements counts as an Empire double civEmpirePop = 2.5e6; // ...or total population >= this counts as an Empire // Civilization Step 5: conflict & war (stateful, saved v21). Neighbouring realms grow hostile and // fight; casualties shrink frontier cities, winners conquer (flip) or sack (raze) them, empires // fracture as provinces revolt. All rolls come from a separate war RNG (tectonic stream intact). int warMaxConcurrent = 6; // cap on simultaneous active wars double warDeclareRate = 0.12; // per-year war-declaration chance scale (x hostility) double warAmbition = 1.0; // hostility weight of the size gap (strong preys on weak) double warIdeology = 0.8; // hostility weight of culture + faith difference double warBorder = 0.5; // hostility weight of contested-frontier length double warWarlikeMult = 1.4; // military-strength multiplier for a Warlike-ethos realm double warCasualtyRate = 0.06; // per war-year frontier-city population loss (loser more) double warConquerScore = 0.6; // |warscore| past which the winner takes a frontier city double warSackChance = 0.3; // chance a taken city is razed to ruins instead of flipped double warExhaustion = 1.5; // |warscore| (or battle count) past which a war ends in peace double warRevoltRate = 0.04; // per-year base revolt chance of a held foreign/distant city double warMinRealmPop = 2000.0; // realms below this population don't start wars // Civilization Step 6: diplomacy (stateful, saved v22). Realm attitudes drift into alliances / // rivalries; allies don't fight + join each other's wars; wars end in truces. double diploDriftRate = 0.06; // per-year attitude change scale double diploAffinity = 1.0; // attitude pull from shared culture + faith (vs difference) double diploWarPenalty = 0.5; // extra per-year attitude drop while two realms are at war double diploTruceYears = 12.0; // post-war truce length (no new war between the two) double diploWarGrudge = 0.4; // one-off attitude drop when a war between them ends (a scar) double diploAllyThreshold = 0.5; // attitude at/above which two realms are allied double diploNonAggThreshold = 0.2; // attitude at/above which they sign a non-aggression pact double diploRivalThreshold = -0.5; // attitude at/below which they become rivals // Civilization Step 7: trade & economy (derived, not saved). Trade routes link nearby settlements; // prosperity accrues at hubs and boosts growth; trade feeds back into diplomacy + war motivation. double tradeLandRange = 0.10; // rad: overland trade reach (~630 km) double tradeSeaRange = 0.30; // rad: extra reach when both settlements are coastal (sea route) double tradeRiverBonus = 0.06; // rad: reach bonus when either settlement sits on a river double tradeMinVolume = 0.05; // links below this volume are dropped double tradeProsperityWeight= 0.8; // how much a hub's prosperity multiplies its carrying capacity double tradeWarBlock = 0.0; // trade-volume multiplier between realms at war (0 = blockade) double tradeAllyBonus = 1.5; // trade-volume multiplier between allied realms double tradeDiploBonus = 0.1; // attitude nudge/year between trade-partner realms (reward alliance) double tradeTemptWar = 0.3; // war-hostility weight of a wealthy target (rich neighbours tempt war) // Civilization colonization (kingdoms found new settlements over time, incl. islands). New settlements // are appended, bound to the founder's realm by allegiance + a colonial supply trade link. Derived + // deterministic; the settlement set grows (saved as-is, step-back truncates), no save-format change. double civColonizeRate = 0.15; // per-eligible-realm per-year chance to found a colony double civColonyMinPop = 2.0e5; // a realm must reach this total population to colonize (kingdoms+) double civColonyMinHab = 0.30; // minimum habitability of a colony site double civColonyReach = 0.12; // rad: overland colonization reach from a realm settlement double civColonySeaReach = 0.35; // rad: reach over water from a COASTAL member (islands / abroad) double civColonySpacing = 0.05; // rad: a colony must be at least this far from every settlement int civMaxColonies = 120; // cap on colonies founded beyond the initial civMaxSettlements double civColonySupply = 2.0; // prosperity from the overlord supply link (keeps colonies alive) // Civilization Step 8: cultural evolution (stateful, saved v23). Cultures spread along borders // (settlements convert under dominant foreign cultural pressure), conquered settlements assimilate // into their ruler's culture, and far-flung cultures (overseas colonies) schism into new peoples. double cultAssimRate = 0.03; // per-year chance a settlement under foreign allegiance adopts its ruler's culture double cultConvertRate = 0.02; // per-year chance scale for border conversion under dominant foreign pressure double cultConvertDominance = 2.5; // foreign cultural pressure must exceed this x the own-culture support double cultSpreadRange = 0.25; // rad: how far a settlement projects cultural pressure (~1600 km) double cultPrestigeWeight = 0.5; // how much trade prosperity boosts a settlement's cultural weight int cultSchismMinMembers = 6; // a culture needs at least this many living settlements to schism double cultSchismRange = 0.55; // rad from the culture's population centroid past which members are "distant" int cultSchismMinCluster = 2; // distant settlements needed to break away together double cultSchismRate = 0.08; // per-year chance a qualifying distant cluster becomes a new people // Dawn seeding (seedCultures()): a populous continent starts as SEVERAL distinct peoples instead // of one continent-spanning culture -- without this, decades of border conversion/backfill tend // to erode a single per-continent culture into one super-dominant people across the whole world. int cultDawnSettlementsPerCulture = 5; // roughly this many dawn settlements per initial culture on a continent int cultDawnMaxPerContinent = 6; // hard cap on how many initial peoples one continent can seed };