Harden save/load state validation
This commit is contained in:
parent
99f4a925a1
commit
1090d2d46e
12
BUILD.md
12
BUILD.md
@ -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
|
||||||
|
|||||||
@ -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; }
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
14
test_civ.cpp
14
test_civ.cpp
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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");
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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");
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user