Harden save/load state validation

This commit is contained in:
Jonas Reith 2026-07-01 20:32:02 +02:00
parent 99f4a925a1
commit 1090d2d46e
20 changed files with 270 additions and 96 deletions

View File

@ -84,11 +84,13 @@ CLI flags (applied before the first load/generate):
planet.cfg human-editable key=value config of every PlanetConfig parameter; planet.cfg human-editable key=value config of every PlanetConfig parameter;
auto-created on first run, reload live with F2. Range-checked on auto-created on first run, reload live with F2. Range-checked on
load; an invalid file reverts to safe defaults (not overwritten). load; an invalid file reverts to safe defaults (not overwritten).
planet.save binary snapshot (versioned, currently v17: +geography/atlas; v16 planet.save binary snapshot (versioned, currently v22: +diplomacy; v21
+event log; v15 +stateful +conflict/wars; v20 +settlements; v19 +ecoregions; v18
volcanoes; v13 +Live World clock rate; v12 +step-back history +geography salt; v17 +geography/atlas; v16 +event log; v15
(~40 frames, so a load can rewind storms); v11 +weather systems/storms; +stateful volcanoes; v13 +Live World clock rate; v12
v10 +weather fields; v9 +moons; v8 +Live World clock; v7 +biota): seed + config + full planet state; F5 +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 writes it, F9 reloads and resumes deterministically. As of v6
the config is stored as a self-describing key=value block (like the config is stored as a self-describing key=value block (like
planet.cfg), so adding/removing config fields no longer breaks saves planet.cfg), so adding/removing config fields no longer breaks saves

View File

@ -5,6 +5,8 @@
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <fstream> #include <fstream>
#include <set>
#include <utility>
namespace { namespace {
const char* weatherEventName(const WeatherSystem& ws, const Planet& p) { const char* weatherEventName(const WeatherSystem& ws, const Planet& p) {
@ -17,6 +19,42 @@ namespace {
int eventSeverityForWeather(const WeatherSystem& ws, const Planet& p) { int eventSeverityForWeather(const WeatherSystem& ws, const Planet& p) {
return (ws.tropical && ws.strength >= p.cfg.weatherHurricaneStr) ? 2 : (ws.tropical ? 1 : 0); return (ws.tropical && ws.strength >= p.cfg.weatherHurricaneStr) ? 2 : (ws.tropical ? 1 : 0);
} }
bool validFrameWar(const War& w, int settlementCount) {
return w.attacker >= 0 && w.attacker < settlementCount
&& w.defender >= 0 && w.defender < settlementCount
&& w.attacker != w.defender
&& w.battles >= 0
&& std::isfinite(w.warscore);
}
bool sanitizeFrameDiplomacy(std::vector<DiploTie>& ties, int settlementCount) {
std::set<std::pair<int, int>> seen;
for (DiploTie& t : ties) {
if (t.a < 0 || t.a >= settlementCount || t.b < 0 || t.b >= settlementCount || t.a == t.b)
return false;
if (t.a > t.b) std::swap(t.a, t.b);
if (!seen.insert({t.a, t.b}).second) return false;
if (!std::isfinite(t.attitude)) return false;
t.attitude = std::clamp(t.attitude, -1.0, 1.0);
if ((uint8_t)t.kind > (uint8_t)DiploKind::Rival) t.kind = DiploKind::Neutral;
}
return true;
}
bool validateFrameCivState(WeatherSnapshot& w, size_t currentSettlements) {
if (w.settlementPop.size() > currentSettlements) return false;
for (double p : w.settlementPop) if (!std::isfinite(p)) return false;
const int settlementCount = (int)w.settlementPop.size();
if (!w.settlementAllegiance.empty()) {
if ((int)w.settlementAllegiance.size() != settlementCount) return false;
for (int a : w.settlementAllegiance)
if (a < -1 || a >= settlementCount) return false;
}
std::set<std::pair<int, int>> warsSeen;
for (const War& war : w.wars) {
if (!validFrameWar(war, settlementCount)) return false;
if (!warsSeen.insert(std::minmax(war.attacker, war.defender)).second) return false;
}
return sanitizeFrameDiplomacy(w.diplomacy, settlementCount);
}
} }
bool Viewer::init(int argc, char** argv) { bool Viewer::init(int argc, char** argv) {
@ -166,21 +204,21 @@ void Viewer::recolor() {
? habitabilityColor(hab[i]) : Color{30, 42, 64, 255}; // ocean/ice: dim blue ? habitabilityColor(hab[i]) : Color{30, 42, 64, 255}; // ocean/ice: dim blue
break; break;
case ColorMode::Territory: { case ColorMode::Territory: {
int ni = (i < (int)cnat.size()) ? cnat[i] : -1; int ni = (i < cnat.size()) ? cnat[i] : -1;
if (ni >= 0) vcolors[i] = nationColor(ni); // owned: realm tint if (ni >= 0) vcolors[i] = nationColor(ni); // owned: realm tint
else vcolors[i] = (planet.cells[i].elevation > planet.cfg.seaLevel) // wilderness land vs sea else vcolors[i] = (planet.cells[i].elevation > planet.cfg.seaLevel) // wilderness land vs sea
? Color{60, 64, 58, 255} : Color{26, 34, 52, 255}; ? Color{60, 64, 58, 255} : Color{26, 34, 52, 255};
break; break;
} }
case ColorMode::Culture: { case ColorMode::Culture: {
int ci = (i < (int)ccult.size()) ? ccult[i] : -1; int ci = (i < ccult.size()) ? ccult[i] : -1;
if (ci >= 0) vcolors[i] = cultureColor(ci); // owned: culture tint if (ci >= 0) vcolors[i] = cultureColor(ci); // owned: culture tint
else vcolors[i] = (planet.cells[i].elevation > planet.cfg.seaLevel) // uncultured land vs sea else vcolors[i] = (planet.cells[i].elevation > planet.cfg.seaLevel) // uncultured land vs sea
? Color{60, 64, 58, 255} : Color{26, 34, 52, 255}; ? Color{60, 64, 58, 255} : Color{26, 34, 52, 255};
break; break;
} }
case ColorMode::Wealth: { case ColorMode::Wealth: {
double w = (i < (int)cwealth.size()) ? cwealth[i] : 0.0; double w = (i < cwealth.size()) ? cwealth[i] : 0.0;
if (w > 0.0) vcolors[i] = wealthColor(w / wealthMax); // owned land: trade prosperity if (w > 0.0) vcolors[i] = wealthColor(w / wealthMax); // owned land: trade prosperity
else vcolors[i] = (planet.cells[i].elevation > planet.cfg.seaLevel) // wilderness land vs sea else vcolors[i] = (planet.cells[i].elevation > planet.cfg.seaLevel) // wilderness land vs sea
? Color{40, 42, 46, 255} : Color{22, 30, 44, 255}; ? Color{40, 42, 46, 255} : Color{22, 30, 44, 255};
@ -428,7 +466,9 @@ void Viewer::detectLiveEvents(const std::vector<WeatherSystem>& beforeStorms,
// -> realm foundings, tier rises (to a kingdom/empire), and collapses (capital lost/absorbed). // -> realm foundings, tier rises (to a kingdom/empire), and collapses (capital lost/absorbed).
void Viewer::detectNationEvents(const std::vector<Nation>& before) { void Viewer::detectNationEvents(const std::vector<Nation>& before) {
auto byCapital = [](const std::vector<Nation>& v, int cap) -> const Nation* { auto byCapital = [](const std::vector<Nation>& v, int cap) -> const Nation* {
for (const Nation& nn : v) if (nn.capital == cap) return &nn; return nullptr; for (const Nation& nn : v)
if (nn.capital == cap) return &nn;
return nullptr;
}; };
for (const Nation& nat : planet.nationList()) { for (const Nation& nat : planet.nationList()) {
if (nat.capital < 0 || nat.capital >= (int)planet.settlements.size()) continue; if (nat.capital < 0 || nat.capital >= (int)planet.settlements.size()) continue;
@ -643,6 +683,7 @@ void Viewer::loadGame(const char* path) {
|| !std::isfinite(v.activity) || !std::isfinite(v.baseElev) || !std::isfinite(v.activity) || !std::isfinite(v.baseElev)
|| !std::isfinite(v.built) || !std::isfinite(v.timer) || !std::isfinite(v.built) || !std::isfinite(v.timer)
|| !std::isfinite(v.ashTimer) || !std::isfinite(v.ashCarry)) historyOk = false; || !std::isfinite(v.ashTimer) || !std::isfinite(v.ashCarry)) historyOk = false;
if (!validateFrameCivState(f.w, planet.settlements.size())) historyOk = false;
if (historyOk) wxUndo.push_back(std::move(f)); if (historyOk) wxUndo.push_back(std::move(f));
} }
if (!historyOk) { wxUndo.clear(); skippedHistory = true; } if (!historyOk) { wxUndo.clear(); skippedHistory = true; }

View File

@ -288,6 +288,11 @@ private:
uint32_t rnd(); uint32_t rnd();
double rndf(); // [0,1) double rndf(); // [0,1)
void buildGeometry(); // build icosphere + per-cell unit/neighbors 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);
void clearDerivedState(); // clear geometry-dependent scratch/derived fields void clearDerivedState(); // clear geometry-dependent scratch/derived fields
void assignPlates(); void assignPlates();
void seedInitialRelief(); void seedInitialRelief();

View File

@ -217,7 +217,7 @@ CivUpdate Planet::stepCivilization(double dtHours, double liveTime) {
double siteQ = std::clamp(0.45 + cfg.civSiteVariety * ((coast ? 1.2 : 0.0) + riverQ), 0.3, 6.0); double siteQ = std::clamp(0.45 + cfg.civSiteVariety * ((coast ? 1.2 : 0.0) + riverQ), 0.3, 6.0);
double cond = harvest * droughtFactor * coldFactor * floodFactor * ashFactor; double cond = harvest * droughtFactor * coldFactor * floodFactor * ashFactor;
double prosp = (k < sProsperity.size()) ? sProsperity[k] : 0.0; // civ Step 7: trade wealth boosts capacity double prosp = ((size_t)k < sProsperity.size()) ? sProsperity[(size_t)k] : 0.0; // civ Step 7: trade wealth boosts capacity
double K = cfg.civMaxPopulation * hab * siteQ * cond * (1.0 + cfg.tradeProsperityWeight * prosp); double K = cfg.civMaxPopulation * hab * siteQ * cond * (1.0 + cfg.tradeProsperityWeight * prosp);
sCivCond[k] = cond; sCivDrought[k] = drought; sCivCond[k] = cond; sCivDrought[k] = drought;

View File

@ -277,7 +277,9 @@ std::string Planet::nameNewLand(int cell) {
uint32_t seed = geoSeed + (uint32_t)(cell * 2654435761u) uint32_t seed = geoSeed + (uint32_t)(cell * 2654435761u)
+ (uint32_t)geoFeatures.size() * 40503u; + (uint32_t)geoFeatures.size() * 40503u;
auto inUse = [&](const std::string& nm) { auto inUse = [&](const std::string& nm) {
for (const GeoFeature& f : geoFeatures) if (f.name == nm) return true; return false; for (const GeoFeature& f : geoFeatures)
if (f.name == nm) return true;
return false;
}; };
std::string nm = namegen::makeName(seed, bank); std::string nm = namegen::makeName(seed, bank);
for (int g = 0; inUse(nm) && g < 128; ++g) nm = namegen::makeName(seed += 0x9E3779B9u, bank); for (int g = 0; inUse(nm) && g < 128; ++g) nm = namegen::makeName(seed += 0x9E3779B9u, bank);

View File

@ -8,6 +8,8 @@
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <limits> #include <limits>
#include <set>
#include <utility>
// --- Config file (text) + save/load (binary) -------------------------------- // --- Config file (text) + save/load (binary) --------------------------------
@ -368,6 +370,16 @@ std::string validateConfig(const PlanetConfig& cfg) {
bad.push_back("volcanoDormantMinYears > volcanoDormantMaxYears"); bad.push_back("volcanoDormantMinYears > volcanoDormantMaxYears");
if (cfg.volcanoAshMinYears > cfg.volcanoAshMaxYears) if (cfg.volcanoAshMinYears > cfg.volcanoAshMaxYears)
bad.push_back("volcanoAshMinYears > volcanoAshMaxYears"); bad.push_back("volcanoAshMinYears > volcanoAshMaxYears");
if (!(cfg.biomeIceTemp < cfg.biomeTundraTemp && cfg.biomeTundraTemp < cfg.biomeTaigaTemp))
bad.push_back("biome temperature thresholds must satisfy Ice < Tundra < Taiga");
if (cfg.biomeHillsElev >= cfg.biomeMountainElev)
bad.push_back("biomeHillsElev >= biomeMountainElev");
if (cfg.civAbandonPop >= cfg.civTownPop || cfg.civTownPop >= cfg.civCityPop)
bad.push_back("civilization population thresholds must satisfy Abandon < Town < City");
if (cfg.civSeedPopulation <= cfg.civAbandonPop)
bad.push_back("civSeedPopulation <= civAbandonPop");
if (!(cfg.diploRivalThreshold < cfg.diploNonAggThreshold && cfg.diploNonAggThreshold < cfg.diploAllyThreshold))
bad.push_back("diplomacy thresholds must satisfy Rival < NonAggression < Alliance");
if (bad.empty()) return {}; if (bad.empty()) return {};
std::string msg = "Bad config:"; std::string msg = "Bad config:";
@ -412,6 +424,34 @@ namespace {
bool validBiomeByte(uint8_t b) { bool validBiomeByte(uint8_t b) {
return b <= (uint8_t)Biome::Mountains; return b <= (uint8_t)Biome::Mountains;
} }
bool validWarRecord(const War& w, int settlementCount) {
return w.attacker >= 0 && w.attacker < settlementCount
&& w.defender >= 0 && w.defender < settlementCount
&& w.attacker != w.defender
&& w.battles >= 0
&& std::isfinite(w.warscore);
}
bool validateWars(std::vector<War>& wars, int settlementCount) {
std::set<std::pair<int, int>> seen;
for (const War& w : wars) {
if (!validWarRecord(w, settlementCount)) return false;
auto key = std::minmax(w.attacker, w.defender);
if (!seen.insert(key).second) return false;
}
return true;
}
bool sanitizeDiplo(std::vector<DiploTie>& ties, int settlementCount) {
std::set<std::pair<int, int>> seen;
for (DiploTie& t : ties) {
if (t.a < 0 || t.a >= settlementCount || t.b < 0 || t.b >= settlementCount || t.a == t.b)
return false;
if (t.a > t.b) std::swap(t.a, t.b);
if (!seen.insert({t.a, t.b}).second) return false;
if (!std::isfinite(t.attitude)) return false;
t.attitude = std::clamp(t.attitude, -1.0, 1.0);
}
return true;
}
} }
// Full simulation state. Geometry (unit/neighbors) is NOT stored -- it is rebuilt // Full simulation state. Geometry (unit/neighbors) is NOT stored -- it is rebuilt
@ -519,6 +559,20 @@ bool Planet::readState(std::istream& is, bool hasBiome, bool hasBiota, bool hasM
bool hasWeather, bool hasStorms, bool hasVolcanoes, bool hasStatefulVolcanoes, bool hasWeather, bool hasStorms, bool hasVolcanoes, bool hasStatefulVolcanoes,
bool hasGeography, bool hasGeoSalt, bool hasEcoregions, bool hasSettlements, bool hasGeography, bool hasGeoSalt, bool hasEcoregions, bool hasSettlements,
bool hasConflict, bool hasDiplo) { bool hasConflict, bool hasDiplo) {
Planet tmp;
tmp.drifting = drifting; // readState never serialized this flag; preserve old caller-visible behavior.
if (!tmp.readStateImpl(is, hasBiome, hasBiota, hasMoons, hasWeather, hasStorms, hasVolcanoes,
hasStatefulVolcanoes, hasGeography, hasGeoSalt, hasEcoregions,
hasSettlements, hasConflict, hasDiplo))
return false;
*this = std::move(tmp);
return true;
}
bool Planet::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) {
// Save blocks are append-only by version. If a caller asks for an older prefix, // Save blocks are append-only by version. If a caller asks for an older prefix,
// later blocks cannot exist in that stream even if the default arguments say otherwise. // later blocks cannot exist in that stream even if the default arguments say otherwise.
if (!hasBiota) { hasMoons = false; hasWeather = false; hasStorms = false; hasVolcanoes = false; } if (!hasBiota) { hasMoons = false; hasWeather = false; hasStorms = false; hasVolcanoes = false; }
@ -759,6 +813,7 @@ bool Planet::readState(std::istream& is, bool hasBiome, bool hasBiota, bool hasM
if (w.attacker < 0 || w.attacker >= (int)settlements.size() if (w.attacker < 0 || w.attacker >= (int)settlements.size()
|| w.defender < 0 || w.defender >= (int)settlements.size()) return false; || w.defender < 0 || w.defender >= (int)settlements.size()) return false;
} }
if (!validateWars(wars, (int)settlements.size())) return false;
readPod(is, sWarRng); readPod(is, sWarNextId); readPod(is, sWarRng); readPod(is, sWarNextId);
if (!sWarRng) sWarRng = cfg.seed ? (cfg.seed ^ 0x5A7B0A11u) : 0x5A7B0A11u; if (!sWarRng) sWarRng = cfg.seed ? (cfg.seed ^ 0x5A7B0A11u) : 0x5A7B0A11u;
if (!sWarNextId) sWarNextId = 1; if (!sWarNextId) sWarNextId = 1;
@ -776,6 +831,7 @@ bool Planet::readState(std::istream& is, bool hasBiome, bool hasBiota, bool hasM
if (t.a < 0 || t.a >= (int)settlements.size() if (t.a < 0 || t.a >= (int)settlements.size()
|| t.b < 0 || t.b >= (int)settlements.size()) return false; || t.b < 0 || t.b >= (int)settlements.size()) return false;
} }
if (!sanitizeDiplo(diplomacy, (int)settlements.size())) return false;
} }
computeBiotaDensity(); // derived density scalars for the colour views computeBiotaDensity(); // derived density scalars for the colour views
return (bool)is; return (bool)is;

View File

@ -1,11 +1,11 @@
// Headless logic test for the Biota stage (flora / fauna / funga). No display. // Headless logic test for the Biota stage (flora / fauna / funga). No display.
// //
// g++ -std=c++17 -O2 -Isrc/sim test_biota.cpp src/sim/IcoSphere.cpp \ // g++ -std=c++17 -O2 -Isrc/sim test_biota.cpp src/sim/IcoSphere.cpp
// src/sim/Planet.cpp src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp \ // src/sim/Planet.cpp src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp
// src/sim/PlanetErosion.cpp src/sim/PlanetHydrology.cpp \ // src/sim/PlanetErosion.cpp src/sim/PlanetHydrology.cpp
// src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp \ // src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp
// src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp \ // src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp
// src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp \ // src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp
// src/sim/PlanetIO.cpp -o /tmp/tb && /tmp/tb // src/sim/PlanetIO.cpp -o /tmp/tb && /tmp/tb
// //
// Verifies: density ranges, zero life under ice + marine flora/fauna present in the // Verifies: density ranges, zero life under ice + marine flora/fauna present in the
@ -95,8 +95,12 @@ int main() {
const auto& B = p.biota(); const auto& B = p.biota();
bool slotsOk = true, pointsOk = true, carnGated = true, iceEmpty = true, oceanFungaEmpty = true; bool slotsOk = true, pointsOk = true, carnGated = true, iceEmpty = true, oceanFungaEmpty = true;
bool anyMarineOrg = false; bool anyMarineOrg = false;
auto cost = [&](const std::vector<Organism>& v) { int s = 0; const auto& AR = biotaArchetypes(); auto cost = [&](const std::vector<Organism>& v) {
for (auto& o : v) s += pointCost(AR[o.archetype].size); return s; }; int s = 0;
const auto& AR = biotaArchetypes();
for (auto& o : v) s += pointCost(AR[o.archetype].size);
return s;
};
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
const CellBiota& cb = B[i]; const CellBiota& cb = B[i];
if (p.cells[i].biome == Biome::Ice) { // ice (land or frozen sea): empty if (p.cells[i].biome == Biome::Ice) { // ice (land or frozen sea): empty

View File

@ -1,12 +1,12 @@
// Headless test for civilization Step 2 (habitability + settlements). No display needed. // Headless test for civilization Step 2 (habitability + settlements). No display needed.
// //
// g++ -std=c++17 -O2 -Isrc/sim test_civ.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp \ // g++ -std=c++17 -O2 -Isrc/sim test_civ.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp
// src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp src/sim/PlanetErosion.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/PlanetHydrology.cpp src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp
// src/sim/PlanetLive.cpp src/sim/PlanetOcean.cpp src/sim/PlanetWeather.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/PlanetVolcano.cpp src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp
// src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp src/sim/NameGen.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/PlanetGeography.cpp src/sim/PlanetEcoregions.cpp src/sim/PlanetCiv.cpp
// src/sim/PlanetIO.cpp -o /tmp/tc && /tmp/tc // src/sim/PlanetIO.cpp -o /tmp/tc && /tmp/tc
// //
// Verifies: habitability range/zeros; placement spacing/cap/land + unique names; food-driven growth // Verifies: habitability range/zeros; placement spacing/cap/land + unique names; food-driven growth

View File

@ -1,7 +1,7 @@
// Headless test for civilization colonization (kingdoms found new settlements + islands). No display. // Headless test for civilization colonization (kingdoms found new settlements + islands). No display.
// //
// g++ -std=c++17 -O2 -Isrc/sim test_colony.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp \ // g++ -std=c++17 -O2 -Isrc/sim test_colony.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp
// ... (the same source list as test_civ) ... src/sim/PlanetTrade.cpp src/sim/PlanetIO.cpp \ // ... (the same source list as test_civ) ... src/sim/PlanetTrade.cpp src/sim/PlanetIO.cpp
// -o /tmp/tcol && /tmp/tcol // -o /tmp/tcol && /tmp/tcol
// //
// Verifies: colonies are founded (the settlement count grows); a colony belongs to its founder's realm // Verifies: colonies are founded (the settlement count grows); a colony belongs to its founder's realm

View File

@ -1,13 +1,13 @@
// Headless test for civilization Step 5 (conflict, war & shifting borders). No display needed. // Headless test for civilization Step 5 (conflict, war & shifting borders). No display needed.
// //
// g++ -std=c++17 -O2 -Isrc/sim test_conflict.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp \ // g++ -std=c++17 -O2 -Isrc/sim test_conflict.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp
// src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp src/sim/PlanetErosion.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/PlanetHydrology.cpp src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp
// src/sim/PlanetLive.cpp src/sim/PlanetOcean.cpp src/sim/PlanetWeather.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/PlanetVolcano.cpp src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp
// src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp src/sim/NameGen.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/PlanetGeography.cpp src/sim/PlanetEcoregions.cpp src/sim/PlanetCiv.cpp
// src/sim/PlanetNation.cpp src/sim/PlanetCulture.cpp src/sim/PlanetConflict.cpp \ // src/sim/PlanetNation.cpp src/sim/PlanetCulture.cpp src/sim/PlanetConflict.cpp
// src/sim/PlanetIO.cpp -o /tmp/twar && /tmp/twar // src/sim/PlanetIO.cpp -o /tmp/twar && /tmp/twar
// //
// Verifies: wars start between neighbouring realms; conquest sets allegiance so computeTerritory moves // Verifies: wars start between neighbouring realms; conquest sets allegiance so computeTerritory moves
@ -146,6 +146,30 @@ int main() {
r.computeTerritory(); r.computeTerritory();
check(r.cellNation() == p2.cellNation(), "territory recomputed after load matches (allegiance honoured)"); check(r.cellNation() == p2.cellNation(), "territory recomputed after load matches (allegiance honoured)");
} }
{
Planet bad = p2;
bad.wars.clear();
War w; w.attacker = 0; w.defender = (int)bad.settlements.size() + 10;
bad.wars.push_back(w);
std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
bad.writeState(ss);
Planet r;
check(!r.readState(ss, true, true, true, true, true, true, true, true, true, true, true, true),
"readState rejects active wars with invalid settlement endpoints");
}
if (p2.settlements.size() >= 2) {
Planet bad = p2;
bad.wars.clear();
War a; a.attacker = 0; a.defender = 1;
War b; b.attacker = 1; b.defender = 0;
bad.wars.push_back(a);
bad.wars.push_back(b);
std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
bad.writeState(ss);
Planet r;
check(!r.readState(ss, true, true, true, true, true, true, true, true, true, true, true, true),
"readState rejects duplicate active wars for the same realm pair");
}
std::printf("Conflict: step-back snapshot round-trip\n"); std::printf("Conflict: step-back snapshot round-trip\n");
{ {

View File

@ -1,12 +1,12 @@
// Headless test for civilization Step 4 (cultures, beliefs & governments). No display needed. // Headless test for civilization Step 4 (cultures, beliefs & governments). No display needed.
// //
// g++ -std=c++17 -O2 -Isrc/sim test_culture.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp \ // g++ -std=c++17 -O2 -Isrc/sim test_culture.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp
// src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp src/sim/PlanetErosion.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/PlanetHydrology.cpp src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp
// src/sim/PlanetLive.cpp src/sim/PlanetOcean.cpp src/sim/PlanetWeather.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/PlanetVolcano.cpp src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp
// src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp src/sim/NameGen.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/PlanetGeography.cpp src/sim/PlanetEcoregions.cpp src/sim/PlanetCiv.cpp
// src/sim/PlanetNation.cpp src/sim/PlanetCulture.cpp src/sim/PlanetIO.cpp -o /tmp/tc && /tmp/tc // src/sim/PlanetNation.cpp src/sim/PlanetCulture.cpp src/sim/PlanetIO.cpp -o /tmp/tc && /tmp/tc
// //
// Verifies: one culture per inhabited continent; every living settlement has a culture; valid // Verifies: one culture per inhabited continent; every living settlement has a culture; valid

View File

@ -1,13 +1,13 @@
// Headless test for civilization Step 6 (diplomacy, alliances & coalitions). No display needed. // Headless test for civilization Step 6 (diplomacy, alliances & coalitions). No display needed.
// //
// g++ -std=c++17 -O2 -Isrc/sim test_diplomacy.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp \ // g++ -std=c++17 -O2 -Isrc/sim test_diplomacy.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp
// src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp src/sim/PlanetErosion.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/PlanetHydrology.cpp src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp
// src/sim/PlanetLive.cpp src/sim/PlanetOcean.cpp src/sim/PlanetWeather.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/PlanetVolcano.cpp src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp
// src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp src/sim/NameGen.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/PlanetGeography.cpp src/sim/PlanetEcoregions.cpp src/sim/PlanetCiv.cpp
// src/sim/PlanetNation.cpp src/sim/PlanetCulture.cpp src/sim/PlanetConflict.cpp \ // src/sim/PlanetNation.cpp src/sim/PlanetCulture.cpp src/sim/PlanetConflict.cpp
// src/sim/PlanetIO.cpp -o /tmp/tdip && /tmp/tdip // src/sim/PlanetIO.cpp -o /tmp/tdip && /tmp/tdip
// //
// Verifies: alliances + rivalries form; allies never war on each other; coalitions form (allies join a // Verifies: alliances + rivalries form; allies never war on each other; coalitions form (allies join a
@ -127,6 +127,32 @@ int main() {
check(ok, "readState accepts the v22 stream"); check(ok, "readState accepts the v22 stream");
check(tiesEqual(r.diploList(), p.diploList()), "diplomacy survives save/load"); check(tiesEqual(r.diploList(), p.diploList()), "diplomacy survives save/load");
} }
{
Planet bad = p;
bad.diplomacy.clear();
DiploTie t; t.a = 0; t.b = (int)bad.settlements.size() + 10;
bad.diplomacy.push_back(t);
std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
bad.writeState(ss);
Planet r;
check(!r.readState(ss, true, true, true, true, true, true, true, true, true, true, true, true, true),
"readState rejects diplomacy ties with invalid settlement endpoints");
}
if (p.settlements.size() >= 2) {
Planet odd = p;
odd.diplomacy.clear();
DiploTie t; t.a = 1; t.b = 0; t.attitude = 99.0; t.kind = (DiploKind)255;
odd.diplomacy.push_back(t);
std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
odd.writeState(ss);
Planet r;
bool ok = r.readState(ss, true, true, true, true, true, true, true, true, true, true, true, true, true);
bool sanitized = ok && r.diploList().size() == 1
&& r.diploList()[0].a == 0 && r.diploList()[0].b == 1
&& std::fabs(r.diploList()[0].attitude - 1.0) < 1e-12
&& r.diploList()[0].kind == DiploKind::Neutral;
check(sanitized, "readState canonicalizes and clamps salvageable diplomacy ties");
}
std::printf("Diplomacy: step-back snapshot round-trip\n"); std::printf("Diplomacy: step-back snapshot round-trip\n");
{ {

View File

@ -1,11 +1,11 @@
// Headless test for the geography / atlas stage (named feature extraction + naming). No display. // Headless test for the geography / atlas stage (named feature extraction + naming). No display.
// //
// g++ -std=c++17 -O2 -Isrc/sim test_geography.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp \ // g++ -std=c++17 -O2 -Isrc/sim test_geography.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp
// src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp src/sim/PlanetErosion.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/PlanetHydrology.cpp src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp
// src/sim/PlanetLive.cpp src/sim/PlanetOcean.cpp src/sim/PlanetWeather.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/PlanetVolcano.cpp src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp
// src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp src/sim/NameGen.cpp \ // src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp src/sim/NameGen.cpp
// src/sim/PlanetGeography.cpp src/sim/PlanetIO.cpp -o /tmp/tg && /tmp/tg // src/sim/PlanetGeography.cpp src/sim/PlanetIO.cpp -o /tmp/tg && /tmp/tg
// //
// Verifies: extraction (continents/oceans/ranges/rivers/lakes), per-cell membership consistency, // Verifies: extraction (continents/oceans/ranges/rivers/lakes), per-cell membership consistency,

View File

@ -1,11 +1,11 @@
// Headless test for the Live World stage (insolation + live seasonal temperature). // Headless test for the Live World stage (insolation + live seasonal temperature).
// No display / raylib needed. // No display / raylib needed.
// //
// g++ -std=c++17 -O2 -Isrc/sim test_live.cpp src/sim/IcoSphere.cpp \ // g++ -std=c++17 -O2 -Isrc/sim test_live.cpp src/sim/IcoSphere.cpp
// src/sim/Planet.cpp src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp \ // src/sim/Planet.cpp src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp
// src/sim/PlanetErosion.cpp src/sim/PlanetHydrology.cpp \ // src/sim/PlanetErosion.cpp src/sim/PlanetHydrology.cpp
// src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp src/sim/PlanetLive.cpp \ // src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp src/sim/PlanetLive.cpp
// src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp src/sim/PlanetFaunaGen.cpp \ // src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp src/sim/PlanetFaunaGen.cpp
// src/sim/PlanetFungiGen.cpp src/sim/PlanetIO.cpp -o /tmp/tl && /tmp/tl // src/sim/PlanetFungiGen.cpp src/sim/PlanetIO.cpp -o /tmp/tl && /tmp/tl
// //
// Verifies: insolation range; the sub-solar hemisphere is lit and the night side dark; // Verifies: insolation range; the sub-solar hemisphere is lit and the night side dark;

View File

@ -1,8 +1,8 @@
// Headless logic test for Phase 1 tectonics. No display / raylib needed. // Headless logic test for Phase 1 tectonics. No display / raylib needed.
// //
// g++ -std=c++17 -O2 -Isrc/sim test_logic.cpp src/sim/IcoSphere.cpp \ // 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/Planet.cpp src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp
// src/sim/PlanetErosion.cpp src/sim/PlanetHydrology.cpp src/sim/PlanetIO.cpp \ // src/sim/PlanetErosion.cpp src/sim/PlanetHydrology.cpp src/sim/PlanetIO.cpp
// -o /tmp/t && /tmp/t // -o /tmp/t && /tmp/t
// //
// Verifies the invariants documented in CLAUDE.md so Planet::step() and the // Verifies the invariants documented in CLAUDE.md so Planet::step() and the
@ -234,6 +234,20 @@ int main() {
finishManualState(badPlate); finishManualState(badPlate);
Planet rp; Planet rp;
check(!rp.readState(badPlate), "readState() rejects invalid cell plate ids"); check(!rp.readState(badPlate), "readState() rejects invalid cell plate ids");
Planet victim; victim.generate(cfg);
const size_t beforeCells = victim.cells.size();
const int beforeSubdiv = victim.cfg.subdivisions;
const double beforeElev = victim.cells.empty() ? 0.0 : victim.cells[0].elevation;
std::stringstream badAfterBuild(std::ios::in | std::ios::out | std::ios::binary);
writeManualStatePrefix(badAfterBuild, "subdivisions = 0\nplateCount = 1\n", 77);
finishManualState(badAfterBuild);
bool rejected = !victim.readState(badAfterBuild);
bool unchanged = victim.cells.size() == beforeCells
&& victim.cfg.subdivisions == beforeSubdiv
&& !victim.cells.empty()
&& victim.cells[0].elevation == beforeElev;
check(rejected && unchanged, "failed readState() leaves the existing planet unchanged");
} }
std::printf("\n%s (%d failure%s)\n", std::printf("\n%s (%d failure%s)\n",

View File

@ -1,12 +1,12 @@
// Headless test for civilization Step 3 (territory & nations / realms). No display needed. // Headless test for civilization Step 3 (territory & nations / realms). No display needed.
// //
// g++ -std=c++17 -O2 -Isrc/sim test_nation.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp \ // g++ -std=c++17 -O2 -Isrc/sim test_nation.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp
// src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp src/sim/PlanetErosion.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/PlanetHydrology.cpp src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp
// src/sim/PlanetLive.cpp src/sim/PlanetOcean.cpp src/sim/PlanetWeather.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/PlanetVolcano.cpp src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp
// src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp src/sim/NameGen.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/PlanetGeography.cpp src/sim/PlanetEcoregions.cpp src/sim/PlanetCiv.cpp
// src/sim/PlanetNation.cpp src/sim/PlanetIO.cpp -o /tmp/tn && /tmp/tn // src/sim/PlanetNation.cpp src/sim/PlanetIO.cpp -o /tmp/tn && /tmp/tn
// //
// Verifies: territory ownership + wilderness; bigger cities own more; realm grouping (kingdom vs // Verifies: territory ownership + wilderness; bigger cities own more; realm grouping (kingdom vs

View File

@ -1,11 +1,11 @@
// Headless test for the Live World ocean/sky stage (moons + tides). No display needed. // Headless test for the Live World ocean/sky stage (moons + tides). No display needed.
// //
// g++ -std=c++17 -O2 -Isrc/sim test_ocean.cpp src/sim/IcoSphere.cpp \ // g++ -std=c++17 -O2 -Isrc/sim test_ocean.cpp src/sim/IcoSphere.cpp
// src/sim/Planet.cpp src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp \ // src/sim/Planet.cpp src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp
// src/sim/PlanetErosion.cpp src/sim/PlanetHydrology.cpp \ // src/sim/PlanetErosion.cpp src/sim/PlanetHydrology.cpp
// src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp src/sim/PlanetLive.cpp \ // src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp src/sim/PlanetLive.cpp
// src/sim/PlanetOcean.cpp src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp \ // src/sim/PlanetOcean.cpp src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp
// src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp src/sim/PlanetIO.cpp \ // src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp src/sim/PlanetIO.cpp
// -o /tmp/to && /tmp/to // -o /tmp/to && /tmp/to
// //
// Verifies: 1-3 moons generated deterministically; moon/sun directions are unit vectors that // Verifies: 1-3 moons generated deterministically; moon/sun directions are unit vectors that

View File

@ -1,13 +1,13 @@
// Headless test for civilization Step 7 (trade & economy). No display needed. // Headless test for civilization Step 7 (trade & economy). No display needed.
// //
// g++ -std=c++17 -O2 -Isrc/sim test_trade.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp \ // g++ -std=c++17 -O2 -Isrc/sim test_trade.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp
// src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp src/sim/PlanetErosion.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/PlanetHydrology.cpp src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp
// src/sim/PlanetLive.cpp src/sim/PlanetOcean.cpp src/sim/PlanetWeather.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/PlanetVolcano.cpp src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp
// src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp src/sim/NameGen.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/PlanetGeography.cpp src/sim/PlanetEcoregions.cpp src/sim/PlanetCiv.cpp
// src/sim/PlanetNation.cpp src/sim/PlanetCulture.cpp src/sim/PlanetConflict.cpp \ // src/sim/PlanetNation.cpp src/sim/PlanetCulture.cpp src/sim/PlanetConflict.cpp
// src/sim/PlanetTrade.cpp src/sim/PlanetIO.cpp -o /tmp/ttr && /tmp/ttr // src/sim/PlanetTrade.cpp src/sim/PlanetIO.cpp -o /tmp/ttr && /tmp/ttr
// //
// Verifies: trade links form (coastal pairs over a longer sea range); hubs are richer than isolated // Verifies: trade links form (coastal pairs over a longer sea range); hubs are richer than isolated

View File

@ -1,11 +1,11 @@
// Headless test for Live World volcanoes (stateful growth, dormancy, explosions, ash, // Headless test for Live World volcanoes (stateful growth, dormancy, explosions, ash,
// rewind snapshots and save/load). No display needed. // rewind snapshots and save/load). No display needed.
// //
// g++ -std=c++17 -O2 -Isrc/sim test_volcano.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp \ // g++ -std=c++17 -O2 -Isrc/sim test_volcano.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp
// src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp src/sim/PlanetErosion.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/PlanetHydrology.cpp src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp
// src/sim/PlanetLive.cpp src/sim/PlanetOcean.cpp src/sim/PlanetWeather.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/PlanetVolcano.cpp src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp
// src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp src/sim/PlanetIO.cpp -o /tmp/tv && /tmp/tv // src/sim/PlanetFaunaGen.cpp src/sim/PlanetFungiGen.cpp src/sim/PlanetIO.cpp -o /tmp/tv && /tmp/tv
#include "Planet.hpp" #include "Planet.hpp"

View File

@ -1,10 +1,10 @@
// Headless test for the Live World weather cycle (humidity / cloud / rain). No display needed. // Headless test for the Live World weather cycle (humidity / cloud / rain). No display needed.
// //
// g++ -std=c++17 -O2 -Isrc/sim test_weather.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp \ // g++ -std=c++17 -O2 -Isrc/sim test_weather.cpp src/sim/IcoSphere.cpp src/sim/Planet.cpp
// src/sim/PlanetTectonics.cpp src/sim/PlanetDrift.cpp src/sim/PlanetErosion.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/PlanetHydrology.cpp src/sim/PlanetBiomes.cpp src/sim/PlanetClimate.cpp
// src/sim/PlanetLive.cpp src/sim/PlanetOcean.cpp src/sim/PlanetWeather.cpp \ // src/sim/PlanetLive.cpp src/sim/PlanetOcean.cpp src/sim/PlanetWeather.cpp
// src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp src/sim/PlanetFaunaGen.cpp \ // src/sim/PlanetBiota.cpp src/sim/PlanetFloraGen.cpp src/sim/PlanetFaunaGen.cpp
// src/sim/PlanetFungiGen.cpp src/sim/PlanetIO.cpp -o /tmp/tw && /tmp/tw // src/sim/PlanetFungiGen.cpp src/sim/PlanetIO.cpp -o /tmp/tw && /tmp/tw
// //
// Verifies: fields stay in range; oceans (the evaporation source) end up moister than land; // Verifies: fields stay in range; oceans (the evaporation source) end up moister than land;