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>
423 lines
28 KiB
C++
423 lines
28 KiB
C++
#pragma once
|
|
#include "Vec3.hpp"
|
|
#include "IcoSphere.hpp"
|
|
#include "PlanetTypes.hpp" // Cell, Plate, SubGrid/SubCell, PlanetConfig
|
|
#include "PlanetBiota.hpp" // BiotaKind, Organism, CellBiota
|
|
#include "PlanetGeography.hpp" // FeatureKind, GeoFeature
|
|
#include "PlanetCiv.hpp" // Settlement, SettleTier, CivUpdate
|
|
#include "PlanetNation.hpp" // Nation, NationTier, GovType
|
|
#include "PlanetCulture.hpp" // Culture, CultureEthos, Faith
|
|
#include "PlanetConflict.hpp" // WarEvent, ConflictUpdate (War is in PlanetTypes.hpp)
|
|
#include "PlanetTrade.hpp" // TradeKind, TradeLink
|
|
#include "PlanetEcoregions.hpp" // Ecoregion
|
|
#include <vector>
|
|
#include <memory>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <iosfwd>
|
|
|
|
class Planet {
|
|
public:
|
|
PlanetConfig cfg;
|
|
std::vector<Cell> cells;
|
|
std::vector<Plate> plates;
|
|
std::vector<Moon> moons; // Live World: 1-3 natural satellites (generated + saved)
|
|
std::vector<Volcano> volcanoes; // Live World: stateful lifecycle volcanoes (saved v15)
|
|
std::vector<GeoFeature> geoFeatures; // named geographic features / the atlas (saved v17+)
|
|
std::vector<Ecoregion> ecoRegions; // named ecological provinces (saved v19+)
|
|
std::vector<Settlement> settlements; // civilization: settlements placed once, grow/decline (saved v20+)
|
|
std::vector<Nation> nations; // realms grouped from settlements (derived each computeTerritory, not saved)
|
|
std::vector<Culture> cultures; // peoples/cultures, one per inhabited continent (derived, not saved)
|
|
std::vector<War> wars; // civ Step 5: active wars between realms (stateful, saved v21)
|
|
std::vector<DiploTie> diplomacy; // civ Step 6: standing realm relations (stateful, saved v22)
|
|
|
|
// Phase flag: false during Phase-1 forming (modest, original tectonics that
|
|
// settle), true during Phase-2 drift. Gates the increment-4 orogeny boosts
|
|
// (collision/arc uplift + isostatic persistence) so Phase 1 stays unchanged
|
|
// and tall persistent mountains only grow during drift (where erode() limits
|
|
// them). Not serialized -- the orchestrator sets it from the phase.
|
|
bool drifting = false;
|
|
|
|
void generate(const PlanetConfig& c);
|
|
double step(); // one tectonic tick; returns max |elevation change| (m) this tick
|
|
|
|
// Phase 2: stable timestep (My) from the fastest plate (CFL ~ half a cell).
|
|
double cflDtMy() const;
|
|
// Phase 2: advect plate membership + carried crust (plateId, elevation,
|
|
// oceanic, geoAge) over the fixed grid by dt My. Opening gaps become new
|
|
// young oceanic crust (spreading); overlaps subduct.
|
|
void advect(double dtMy);
|
|
|
|
// Phase 2: erode the elevation field by dt My (highs wear down, sediment
|
|
// deposits downhill in basins / below sea level) and, every seaLevelEvery
|
|
// calls, nudge seaLevel toward landFractionTarget. Crust type is untouched.
|
|
void erode(double dtMy);
|
|
|
|
// Phase 3: one hydrology tick over the fixed grid -- recompute the drainage
|
|
// network (depression-fill -> lakes, steepest-descent routing -> rivers,
|
|
// flow accumulation -> discharge) and apply mass-conserving fluvial erosion
|
|
// (stream-power incision + downstream sediment transport/deposition) to the
|
|
// elevation field by dt My. computeHydrology() does the routing only (no
|
|
// erosion) so the viewer can show rivers/lakes when paused / after load.
|
|
void hydrology(double dtMy);
|
|
void computeHydrology();
|
|
|
|
// Phase 3 (climate): compute per-cell temperature + precipitation fields from
|
|
// elevation, latitude and prevailing-wind orographic moisture transport (windward
|
|
// rain, leeward rain shadow, dry continental interiors). Derived (not saved);
|
|
// call before classifyBiomes(), which consumes these fields.
|
|
void computeClimate();
|
|
const std::vector<double>& temperature() const { return sTemp; } // deg C, annual mean
|
|
const std::vector<double>& precipitation() const { return sPrecip; } // relative units
|
|
const std::vector<double>& moisture() const { return sMoist; } // 0..1 (median land -> 0.5)
|
|
const std::vector<double>& summerTemp() const { return sTempSummer; } // deg C, warmest month
|
|
const std::vector<double>& winterTemp() const { return sTempWinter; } // deg C, coldest month
|
|
|
|
// Live World stage (PlanetLive.cpp): the slow real-time clock's derived fields.
|
|
// computeInsolation() = instantaneous solar incidence cos(sun angle), 0..1 -- the
|
|
// physical foundation for live weather + the day/night terminator. computeLiveSeason()
|
|
// = the live temperature cycling between winterTemp/summerTemp over the year (drives the
|
|
// live temperature view + the moving snow line). Both derived/not-saved; call after
|
|
// computeClimate(). dayOfYear01/timeOfDay01 are fractions in [0,1).
|
|
void computeInsolation(double dayOfYear01, double timeOfDay01);
|
|
void computeLiveSeason(double dayOfYear01);
|
|
const std::vector<double>& insolation() const { return sInsolation; } // 0..1 cos incidence
|
|
const std::vector<double>& liveTemp() const { return sLiveTemp; } // deg C, current season
|
|
|
|
// Live World sky + oceans (PlanetOcean.cpp). Sub-solar / sub-lunar directions in model
|
|
// space at a clock fraction (drive insolation, tides and the 3D sun/moons -- one source of
|
|
// truth). generateMoons() seeds 1-3 moons from a separate RNG (tectonic determinism intact).
|
|
Vec3 sunDirection(double dayOfYear01, double timeOfDay01) const;
|
|
Vec3 moonDirection(int moonIdx, double timeOfDay01, double timeDays) const;
|
|
Vec3 moonOrbitNormal(int moonIdx, double timeOfDay01) const; // orbit-plane normal (for the ring)
|
|
const std::vector<Moon>& getMoons() const { return moons; }
|
|
void generateMoons();
|
|
// Tides: equilibrium tidal height (m) per cell from the moons + sun at the given clock.
|
|
// Derived/not saved; recomputed each frame like insolation.
|
|
void computeTides(double dayOfYear01, double timeOfDay01, double timeDays);
|
|
const std::vector<double>& tide() const { return sTide; }
|
|
|
|
// Ocean surface currents (PlanetOcean.cpp): a per-ocean-cell tangent velocity from wind
|
|
// stress + Coriolis deflection + coast-following (gyres). Derived/not saved; needs sWind
|
|
// (computeClimate() computes it, then calls this and feeds warm/cold currents back into
|
|
// sTemp -- see climateCurrentFactor). Zero on land cells.
|
|
void computeOceanCurrents();
|
|
const std::vector<Vec3>& current() const { return sCurrent; }
|
|
|
|
// Weather (Live World): dynamic per-cell humidity / cloud cover / rain advanced on the live
|
|
// clock. initWeather() spins the fields up from the climatology; stepWeather(dtHours) runs
|
|
// one cycle (evaporate over warm seas -> advect along the wind -> condense into cloud, with
|
|
// orographic lift -> rain out -> dissipate). Reads sInsolation/sTemp/sWind/sUpwind/sMoist
|
|
// (computeClimate + computeInsolation set those). Saved (v10).
|
|
void initWeather();
|
|
void stepWeather(double dtHours);
|
|
const std::vector<double>& humidity() const { return sHumidity; }
|
|
const std::vector<double>& cloud() const { return sCloud; }
|
|
const std::vector<double>& rain() const { return sRain; }
|
|
const std::vector<WeatherSystem>& storms() const { return sStorms; }
|
|
// Snapshot / restore the full weather state (humidity/cloud/rain/storms/RNG) for the viewer's
|
|
// step-back undo history -- weather is an integrated path, so backward stepping restores a frame.
|
|
WeatherSnapshot captureWeather() const;
|
|
void restoreWeather(const WeatherSnapshot& s);
|
|
|
|
// Volcanoes (Live World, PlanetVolcano.cpp). placeVolcanoes() runs once on entering Live World:
|
|
// it seeds a set by tectonic context (high on young ridges, medium on borders, low elsewhere)
|
|
// from a separate RNG (tectonic determinism intact), capturing each vent's baseElev and initial
|
|
// built height. stepVolcanoes() integrates growth/dormancy/explosions forward, reasserts vent
|
|
// elevations, breaches submarine vents into islands, and injects ash cloud + local cooling.
|
|
// Saved (v15); step-back restores them via WeatherSnapshot.
|
|
void placeVolcanoes(double liveTime = 0.0);
|
|
VolcanoUpdate stepVolcanoes(double dtHours);
|
|
double volcanoBuilt(const Volcano& v) const; // m built above baseElev
|
|
double volcanoErupting(const Volcano& v) const; // 0..1 current visual eruption intensity
|
|
|
|
// Phase 3 (biomes): classify every cell into a Biome from elevation + the climate
|
|
// fields (temperature + normalized precipitation). Derived + written back into
|
|
// cell.biome (saved). Assumes computeClimate() ran this tick. Re-run as terrain evolves.
|
|
void classifyBiomes();
|
|
|
|
// Biota stage (flora/fauna/funga). computeBiotaDensity() builds the derived
|
|
// per-cell density scalars (0..1) each tick (like climate; not saved); call it
|
|
// after classifyBiomes(). generateBiota() does the on-demand slot/point fill of
|
|
// the discrete population into sBiota (saved) -- NOT called per tick. See
|
|
// PlanetBiota.cpp + PlanetFloraGen/FaunaGen/FungiGen.cpp.
|
|
void computeBiotaDensity();
|
|
void generateBiota();
|
|
bool biotaPopulated() const;
|
|
const std::vector<double>& floraDensity() const { return sFloraDensity; }
|
|
const std::vector<double>& faunaDensity() const { return sFaunaDensity; }
|
|
const std::vector<double>& fungaDensity() const { return sFungaDensity; }
|
|
const std::vector<CellBiota>& biota() const { return sBiota; }
|
|
|
|
// Derived hydrology fields (recomputed each route; not saved). Empty until
|
|
// the first computeHydrology()/hydrology() call.
|
|
const std::vector<double>& lakeDepth() const { return sLakeDepth; }
|
|
const std::vector<double>& discharge() const { return sDischarge; }
|
|
const std::vector<int>& flowTo() const { return sFlowTo; }
|
|
|
|
// Geography stage (the atlas, PlanetGeography.cpp). generateGeography() extracts named
|
|
// geographic features from the frozen terrain by connectivity (continents/islands, oceans/seas,
|
|
// lakes, mountain ranges/peaks, rivers) and names them with a separate RNG (tectonic determinism
|
|
// intact). Saved (v17; active reshuffle salt saved v18). The per-cell index arrays give O(1)
|
|
// "which features is this cell in".
|
|
void generateGeography(uint32_t nameSalt = 0);
|
|
// Rebuild the atlas against the current terrain and use a new naming salt, for manually
|
|
// rechecking/relabeling geography after moving between world-building phases.
|
|
void reshuffleGeography();
|
|
// Name a cell that has just become land (e.g. a volcanic island breaching the sea): join an
|
|
// adjacent existing landmass, else create + name a new Island. Returns the land feature's name
|
|
// ("" if the atlas isn't built). Appended to geoFeatures (saved). Called by the viewer on a
|
|
// volcanic island-formation event so the new island enters the atlas.
|
|
std::string nameNewLand(int cell);
|
|
bool geographyBuilt() const { return !geoFeatures.empty(); }
|
|
const std::vector<GeoFeature>& geography() const { return geoFeatures; }
|
|
const std::vector<int>& cellLand() const { return sCellLand; } // continent/island feature index (-1)
|
|
const std::vector<int>& cellWater() const { return sCellWater; } // ocean/sea/lake feature index (-1)
|
|
const std::vector<int>& cellRange() const { return sCellRange; } // mountain-range feature index (-1)
|
|
const std::vector<int>& cellRiver() const { return sCellRiver; } // river feature index (-1)
|
|
|
|
// Ecoregion atlas (PlanetEcoregions.cpp): connected ecological provinces built from current
|
|
// biome, land/water context, productivity and dominant broad biota. Saved (v19).
|
|
void generateEcoregions();
|
|
bool ecoregionsBuilt() const { return !ecoRegions.empty(); }
|
|
const std::vector<Ecoregion>& ecoregions() const { return ecoRegions; }
|
|
const std::vector<int>& cellEcoregion() const { return sCellEcoregion; } // ecoregion index (-1)
|
|
|
|
// Civilization Step 2 (PlanetCiv.cpp). computeHabitability() builds the derived per-cell
|
|
// habitability/food score (0..1; like climate, not saved). placeSettlements() seeds the fixed
|
|
// settlement set once on the best-spaced fertile cells (separate RNG; tectonic determinism intact;
|
|
// auto-builds geography/ecoregions for naming + productivity). stepCivilization() advances each
|
|
// settlement's population on the live clock toward a food-driven carrying capacity (grows / shrinks
|
|
// / is abandoned). Saved (v20); step-back restores populations via WeatherSnapshot.
|
|
void computeHabitability();
|
|
void placeSettlements();
|
|
CivUpdate stepCivilization(double dtHours, double liveTime);
|
|
// Colonization (once per sim year): a kingdom+ realm may found a new settlement on good unclaimed land
|
|
// (or an island, from a coastal member) bound to it by allegiance. Appends to `settlements`; returns
|
|
// the founding events. Deterministic (pure hashes) so step-back replays it.
|
|
std::vector<WarEvent> stepColonization(long year);
|
|
bool settlementsPlaced() const { return !settlements.empty(); }
|
|
const std::vector<int>& cellSettlement() const { return sCellSettlement; } // settlement index per cell (-1)
|
|
const std::vector<double>& habitability() const { return sHabitability; } // 0..1 per cell (derived)
|
|
|
|
// Territory & nations (PlanetNation.cpp). computeTerritory() groups settlements into realms
|
|
// (capital + vassal towns) and claims cells within each settlement's size-scaled influence range
|
|
// (wilderness frontiers between realms). Purely derived from the settlement set, so it is recomputed
|
|
// (on placement / load / each sim year), not saved -- step-back replays it as populations restore.
|
|
void computeTerritory();
|
|
bool nationsBuilt() const { return !nations.empty(); }
|
|
const std::vector<Nation>& nationList() const { return nations; }
|
|
const std::vector<int>& cellNation() const { return sCellNation; } // nation index per cell (-1 = wilderness/sea)
|
|
const std::vector<int>& settleNation() const { return sSettleNation; } // nation index per settlement (-1 = dead)
|
|
|
|
// Cultures, beliefs & governments (PlanetCulture.cpp). computeCultures() is the DERIVED refresh:
|
|
// it seeds the cultures once (one per inhabited continent) when none exist, then only recomputes
|
|
// tallies, governments (folded into nation.name) and the per-cell culture view. Runs AFTER
|
|
// computeTerritory() (reads nations/sCellNation/sCellSettleOwner). Culture identities + the
|
|
// per-settlement culture are STATEFUL since Step 8 (saved v23 + snapshotted); stepCulture() is
|
|
// the once-per-sim-year mutation pass (border conversion / assimilation / schism, pure hashes --
|
|
// no RNG touched, so step-back replays it exactly).
|
|
void computeCultures();
|
|
std::vector<WarEvent> stepCulture(long year);
|
|
bool culturesBuilt() const { return !cultures.empty(); }
|
|
const std::vector<Culture>& cultureList() const { return cultures; }
|
|
const std::vector<int>& cellCulture() const { return sCellCulture; } // culture index per cell (-1 = none)
|
|
const std::vector<int>& settleCulture() const { return sSettleCulture; } // culture index per settlement (-1 = none)
|
|
|
|
// Conflict & war (PlanetConflict.cpp; civ Step 5). stepConflict() runs once per sim year: adjacent
|
|
// realms grow hostile, declare wars, fight (casualties in frontier cities), conquer (a city's
|
|
// allegiance flips to the victor) or sack (raze) cities, and conquered provinces revolt. Stateful +
|
|
// path-dependent -> saved (v21) + snapshotted. computeTerritory() reads sSettleAllegiance so borders
|
|
// move as cities change hands. Uses a separate war RNG (tectonic determinism intact).
|
|
ConflictUpdate stepConflict(long year);
|
|
const std::vector<War>& warList() const { return wars; }
|
|
const std::vector<int>& settleAllegiance() const { return sSettleAllegiance; } // overlord capital settlement index (-1 = free)
|
|
bool realmsAtWar(int nationA, int nationB) const; // are these two nation indices in an active war?
|
|
// Diplomacy (civ Step 6). Relations are updated inside stepConflict(); these query them by nation index.
|
|
const std::vector<DiploTie>& diploList() const { return diplomacy; }
|
|
DiploKind diploBetween(int nationA, int nationB) const; // relation kind between two nation indices
|
|
bool realmsAllied(int nationA, int nationB) const; // are these two realms allied?
|
|
|
|
// Trade & economy (PlanetTrade.cpp; civ Step 7). computeTrade() links nearby settlements (sea/river/
|
|
// overland), accrues prosperity at hubs (which feeds population growth) and fills a per-cell wealth
|
|
// field. A pure derived function of settlements + geography + wars/diplomacy -- recomputed, not saved.
|
|
void computeTrade();
|
|
bool tradeBuilt() const { return !sProsperity.empty(); }
|
|
const std::vector<double>& prosperity() const { return sProsperity; } // per settlement (0..~)
|
|
const std::vector<TradeLink>& tradeLinks() const { return sTradeLinks; }
|
|
const std::vector<double>& cellWealth() const { return sCellWealth; } // per cell (heat map)
|
|
// Per-settlement live conditions (derived each stepCivilization; not saved). condition = the combined
|
|
// environmental multiplier on carrying capacity (1 = normal, <1 = hardship, >1 = boom); drought =
|
|
// current drought severity 0..1. Parallel to `settlements`. Used by the viewer for tint + events.
|
|
const std::vector<double>& settlementCondition() const { return sCivCond; }
|
|
const std::vector<double>& settlementDrought() const { return sCivDrought; }
|
|
|
|
// Build a fine-resolution subgrid patch for one macro cell (phase 4/5 hook).
|
|
std::shared_ptr<SubGrid> makeSubGrid(int cellIndex, int res) const;
|
|
|
|
// Save/load the full simulation state (binary). readState rebuilds geometry
|
|
// from the saved cfg.subdivisions, so only dynamic per-cell fields are stored.
|
|
// Reloading resumes the simulation exactly (deterministic continuation).
|
|
void writeState(std::ostream& os) const;
|
|
// hasBiome: whether the stream carries the per-cell biome byte (save v4+). For
|
|
// older saves (v3) pass false -- biomes are reclassified after the cells load.
|
|
// hasBiota: whether the stream carries the biota population block (save v7+).
|
|
// hasMoons: whether the stream carries the moons block (save v9+); older saves
|
|
// synthesize moons from the seed instead. hasWeather: the weather block (save v10+);
|
|
// older saves leave weather to spin up on entering Live World.
|
|
// hasVolcanoes: whether the stream carries a volcano block (save v14+). hasStatefulVolcanoes
|
|
// means v15+ lifecycle volcanoes; v14's old pure-function block is consumed and discarded.
|
|
// hasGeography: whether the stream carries the geography/atlas block (save v17+); hasGeoSalt:
|
|
// whether the stream carries the active reshuffle salt (save v18+); hasEcoregions: whether the
|
|
// stream carries the ecoregion atlas (save v19+). Older saves regenerate on demand.
|
|
// hasSettlements: whether the stream carries the civilization settlements block (save v20+); older
|
|
// saves load with none (re-seeded on demand via the civ key). hasCultures: whether the stream
|
|
// carries the stateful culture block (save v23+); older saves re-seed one culture per continent
|
|
// on the next computeCultures().
|
|
bool readState(std::istream& is, bool hasBiome = true, bool hasBiota = true,
|
|
bool hasMoons = true, bool hasWeather = true, bool hasStorms = true,
|
|
bool hasVolcanoes = true, bool hasStatefulVolcanoes = true, bool hasGeography = true,
|
|
bool hasGeoSalt = true, bool hasEcoregions = true, bool hasSettlements = true,
|
|
bool hasConflict = true, bool hasDiplo = true, bool hasCultures = true);
|
|
|
|
// Helpers for rendering / info.
|
|
double cellWidthMeters() const; // approx lateral cell spacing
|
|
double minElevation() const;
|
|
double maxElevation() const;
|
|
const std::vector<int>& triIndices() const { return sphere.triIndices; }
|
|
|
|
private:
|
|
IcoSphere sphere;
|
|
uint32_t rngState = 1;
|
|
int targetLand = -1; // land-cell count to conserve during drift
|
|
|
|
uint32_t rnd();
|
|
double rndf(); // [0,1)
|
|
void buildGeometry(); // build icosphere + per-cell unit/neighbors
|
|
bool readStateImpl(std::istream& is, bool hasBiome, bool hasBiota,
|
|
bool hasMoons, bool hasWeather, bool hasStorms,
|
|
bool hasVolcanoes, bool hasStatefulVolcanoes, bool hasGeography,
|
|
bool hasGeoSalt, bool hasEcoregions, bool hasSettlements,
|
|
bool hasConflict, bool hasDiplo, bool hasCultures);
|
|
void clearDerivedState(); // clear geometry-dependent scratch/derived fields
|
|
void assignPlates();
|
|
void seedInitialRelief();
|
|
Vec3 driftVelocity(int plateId, const Vec3& pos) const;
|
|
double oceanicBase(double age) const; // age-dependent seafloor depth
|
|
|
|
// Phase-2 plate-dynamics helpers (see PlanetConfig above).
|
|
void setPlateSpeed(Plate& p, double cmYr); // cm/yr -> driftSpeed + angSpeed
|
|
void randomizePlateDrift(Plate& p); // random axis + random speed
|
|
int acquirePlate(); // reuse a dead slot or append one
|
|
void splitPlate(int pid); // fission: cut a plate roughly in two
|
|
void maybeSplitPlates(const std::vector<int>& cnt);
|
|
void kickStalemates(const std::vector<int>& cnt);
|
|
void deleteEnclosedPlates(); // absorb plates ringed by one other
|
|
void fuseMiniPlates(); // cluster of mini plates steals + merges
|
|
void coalesceBabyPlates(); // merge connected baby cells into one id
|
|
void promoteBabyPlates(const std::vector<int>& cnt);
|
|
void adjustSeaLevel(); // nudge seaLevel toward land target
|
|
|
|
// Phase-3 hydrology helpers (see hydrology()).
|
|
void routeFlow(); // depression-fill -> lakes, flow, discharge
|
|
|
|
// Biota helpers (PlanetFloraGen/FaunaGen/FungiGen.cpp). compute*Density write the
|
|
// derived scalars; fill* draw the per-cell population (nbr = already-filled,
|
|
// same-biome neighbours, for regional consistency).
|
|
void computeFloraDensity();
|
|
void computeFaunaDensity();
|
|
void computeFungaDensity();
|
|
double neighbourhoodPrey(int i) const; // mean fauna density over i + neighbours
|
|
std::vector<Organism> fillFlora(int i, const std::vector<int>& nbr, uint32_t& rng);
|
|
std::vector<Organism> fillFauna(int i, const std::vector<int>& nbr, uint32_t& rng);
|
|
std::vector<Organism> fillFunga(int i, const std::vector<int>& nbr, uint32_t& rng);
|
|
|
|
int driftIter = 0; // counts advect() calls (gates periodic checks)
|
|
int erodeIter = 0; // counts erode() calls (gates sea-level control)
|
|
std::vector<int> sPrevCount; // per-plate cell count at the previous check
|
|
std::vector<int> sStaleStreak; // consecutive stuck windows per plate
|
|
std::vector<int> sFreePlateIds; // dead plate slots free for reuse
|
|
|
|
// Reusable scratch buffers for step()/erode() so they allocate nothing per tick.
|
|
std::vector<double> sStress, sBelt, sBeltNext, sDelta, sSmoothed, sOldElev, sErode;
|
|
std::vector<uint8_t> sSub, sOver, sColl;
|
|
|
|
// Phase-3 hydrology scratch (derived from elevation each routeFlow(); not saved).
|
|
std::vector<double> sFill, sLakeDepth, sDischarge;
|
|
std::vector<int> sFlowTo, sHydroOrder;
|
|
|
|
// Phase-3 climate scratch (derived each computeClimate(); not saved). sMoist is the
|
|
// 0..1-normalized precipitation the biome classifier reads. sTempSummer/sTempWinter are
|
|
// the obliquity-driven seasonal extremes around the annual mean sTemp (see Seasons).
|
|
std::vector<double> sTemp, sPrecip, sMoist, sTempSummer, sTempWinter;
|
|
std::vector<Vec3> sWind;
|
|
std::vector<int> sUpwind;
|
|
|
|
// Live World scratch (derived each frame in Live World; not saved). sInsolation is the
|
|
// instantaneous solar incidence; sLiveTemp is the temperature for the current day-of-year;
|
|
// sTide is the equilibrium tidal height (m) from the moons + sun.
|
|
std::vector<double> sInsolation, sLiveTemp, sTide;
|
|
std::vector<Vec3> sCurrent; // ocean surface current velocity (tangent; zero on land)
|
|
// Weather (Live World; saved v10). sHasWeather latches once spun up/loaded.
|
|
std::vector<double> sHumidity, sCloud, sRain;
|
|
bool sHasWeather = false;
|
|
// Moving weather systems (saved with weather state). Separate RNG keeps tectonic
|
|
// determinism intact (seeded from cfg.seed in initWeather).
|
|
std::vector<WeatherSystem> sStorms;
|
|
uint32_t sWeatherRng = 1;
|
|
uint32_t sStormNextId = 1; // monotonic id for follow-cam tracking
|
|
// Volcanoes (Live World; saved v15). Separate RNG (seeded from cfg.seed in placeVolcanoes)
|
|
// keeps tectonic determinism intact while lifecycle rolls happen during Live World.
|
|
uint32_t sVolRng = 1;
|
|
// Geography (the atlas; saved v17; active reshuffle salt saved v18). Per-cell feature index
|
|
// arrays (-1 = none) + a separate RNG/salt so naming never perturbs the tectonic stream.
|
|
std::vector<int> sCellLand, sCellWater, sCellRange, sCellRiver;
|
|
uint32_t sGeoRng = 1;
|
|
uint32_t sGeoSalt = 0;
|
|
|
|
// Ecoregions (saved v19). Per-cell ecoregion index (-1 = none) + separate RNG salt.
|
|
std::vector<int> sCellEcoregion;
|
|
uint32_t sEcoRng = 1;
|
|
|
|
// Civilization (saved v20). Per-cell settlement index (-1 = none; rebuilt on load, not saved),
|
|
// a derived habitability field, and a separate RNG so placement never perturbs tectonics.
|
|
std::vector<int> sCellSettlement;
|
|
std::vector<double> sHabitability;
|
|
std::vector<double> sCivCond, sCivDrought; // per-settlement live conditions (derived)
|
|
uint32_t sCivRng = 1;
|
|
// Territory & nations (derived from settlements; not saved). sCellNation: nation index per cell
|
|
// (-1 = wilderness/ocean); sSettleNation: nation index per settlement.
|
|
std::vector<int> sCellNation, sSettleNation;
|
|
// Cultures (civ Step 8: stateful, saved v23). sSettleCulture: culture index per settlement --
|
|
// mutable identity state (seeded at the dawn, changed only by stepCulture()/colony founding);
|
|
// sCellCulture: the derived per-cell view (refreshed by computeCultures(), not saved).
|
|
// sCultureNextId: stable id counter for appended (schism) cultures.
|
|
std::vector<int> sCellCulture, sSettleCulture;
|
|
uint32_t sCultureNextId = 1;
|
|
void seedCultures(); // one-time seeding (dawn / pre-v23 load), PlanetCulture.cpp
|
|
// Per-cell owning settlement index (filled by computeTerritory alongside sCellNation; -1 = none).
|
|
// Reused by the trade wealth field so the heat map matches territory exactly.
|
|
std::vector<int> sCellSettleOwner;
|
|
// Trade & economy (civ Step 7; derived, not saved). Per-settlement prosperity, the trade-link list,
|
|
// and a per-cell wealth field (owning settlement's prosperity). Recomputed each sim year.
|
|
std::vector<double> sProsperity, sCellWealth;
|
|
std::vector<TradeLink> sTradeLinks;
|
|
// Conflict & war (civ Step 5; stateful, saved v21). sSettleAllegiance[s] = the overlord's capital
|
|
// settlement index if conquered, else -1 (independent). Separate war RNG + a stable war-id counter.
|
|
std::vector<int> sSettleAllegiance;
|
|
uint32_t sWarRng = 1, sWarNextId = 1;
|
|
|
|
// Biota: derived density scalars (0..1; recomputed each tick, not saved) and the
|
|
// on-demand discrete population (saved). sHasBiota latches once generated/loaded.
|
|
std::vector<double> sFloraDensity, sFaunaDensity, sFungaDensity;
|
|
std::vector<CellBiota> sBiota;
|
|
bool sHasBiota = false;
|
|
};
|
|
|
|
// Human-editable config file (key = value text). All PlanetConfig input
|
|
// parameters are written/read via one shared field table. Unknown keys ignored.
|
|
// validateConfig returns an empty string if the config is reasonable.
|
|
bool loadConfig(const std::string& path, PlanetConfig& cfg);
|
|
bool saveConfig(const std::string& path, const PlanetConfig& cfg);
|
|
std::string validateConfig(const PlanetConfig& cfg);
|