// Headless test for the ecoregion atlas. No display needed. #include "Planet.hpp" #include #include #include #include #include static int failures = 0; static void check(bool cond, const char* what) { std::printf(" [%s] %s\n", cond ? "PASS" : "FAIL", what); if (!cond) ++failures; } static void settle(Planet& p, int maxSteps = 800) { int run = 0; for (int s = 0; s < maxSteps; ++s) { double mc = p.step(); if (mc < 2.0) { if (++run >= 3) break; } else run = 0; } p.computeClimate(); p.classifyBiomes(); p.computeBiotaDensity(); } static bool sameBiota(const std::vector& a, const std::vector& b) { if (a.size() != b.size()) return false; auto eq = [](const std::vector& x, const std::vector& y) { if (x.size() != y.size()) return false; for (size_t k = 0; k < x.size(); ++k) if (x[k].archetype != y[k].archetype || x[k].biome != y[k].biome) return false; return true; }; for (size_t i = 0; i < a.size(); ++i) if (!eq(a[i].flora, b[i].flora) || !eq(a[i].fauna, b[i].fauna) || !eq(a[i].funga, b[i].funga)) return false; return true; } int main() { PlanetConfig cfg; cfg.seed = 9191; cfg.subdivisions = 5; Planet p; p.generate(cfg); settle(p); const int n = (int)p.cells.size(); std::printf("Ecoregions: extraction\n"); p.generateEcoregions(); const auto& E = p.ecoregions(); const auto& ce = p.cellEcoregion(); check(!E.empty(), "generateEcoregions produces regions"); check((int)ce.size() == n, "per-cell ecoregion array sized n"); bool mapped = true, indexOk = true, anchorsOk = true, namesOk = true, domOk = true; std::set names; for (int i = 0; i < n; ++i) { if (p.cells[i].biome != Biome::Ice && ce[i] < 0) mapped = false; if (ce[i] >= (int)E.size()) indexOk = false; } for (const Ecoregion& e : E) { if (e.name.empty() || !names.insert(e.name).second) namesOk = false; if (e.anchorCell < 0 || e.anchorCell >= n || p.cells[e.anchorCell].biome != e.biome || e.biome == Biome::Ice) anchorsOk = false; auto validArch = [](int a) { return a == -1 || (a >= 0 && a < (int)biotaArchetypes().size()); }; if (!validArch(e.dominantFlora) || !validArch(e.dominantFauna) || !validArch(e.dominantFunga)) domOk = false; if (!std::isfinite(e.floraProductivity) || !std::isfinite(e.faunaProductivity) || !std::isfinite(e.fungaProductivity)) domOk = false; } check(mapped, "every non-ice cell maps to an ecoregion"); check(indexOk, "per-cell ecoregion indices are valid"); check(anchorsOk, "ecoregion anchors match their biome"); check(namesOk, "ecoregion names are unique and non-empty"); check(domOk, "dominants/productivity are valid"); std::printf("Ecoregions: determinism\n"); Planet q; q.generate(cfg); settle(q); q.generateEcoregions(); bool same = (q.ecoregions().size() == E.size()) && (q.cellEcoregion() == ce); if (same) for (size_t i = 0; i < E.size(); ++i) { const Ecoregion& a = E[i]; const Ecoregion& b = q.ecoregions()[i]; if (a.name != b.name || a.biome != b.biome || a.anchorCell != b.anchorCell || a.size != b.size || a.dominantFlora != b.dominantFlora || a.dominantFauna != b.dominantFauna || a.dominantFunga != b.dominantFunga) { same = false; break; } } check(same, "same seed produces identical ecoregions"); std::printf("Ecoregions: RNG isolation\n"); { Planet a; a.generate(cfg); settle(a); a.drifting = true; Planet b; b.generate(cfg); settle(b); b.drifting = true; b.generateEcoregions(); double dt = a.cflDtMy(); for (int k = 0; k < 5; ++k) { a.advect(dt); a.step(); a.erode(dt); b.advect(dt); b.step(); b.erode(dt); } bool terrainSame = a.cells.size() == b.cells.size(); for (size_t i = 0; terrainSame && i < a.cells.size(); ++i) if (a.cells[i].elevation != b.cells[i].elevation || a.cells[i].plateId != b.cells[i].plateId) terrainSame = false; check(terrainSame, "ecoregions do not perturb tectonic evolution"); } { Planet a; a.generate(cfg); settle(a); a.generateBiota(); Planet b; b.generate(cfg); settle(b); b.generateEcoregions(); b.generateBiota(); check(a.biotaPopulated() && b.biotaPopulated() && sameBiota(a.biota(), b.biota()), "ecoregions do not perturb biota generation"); } std::printf("Ecoregions: save v19 round-trip\n"); { std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary); p.writeState(ss); Planet r; bool ok = r.readState(ss); check(ok, "readState accepts a v19 stream"); bool match = (r.ecoregions().size() == E.size()) && (r.cellEcoregion() == ce); if (match) for (size_t i = 0; i < E.size(); ++i) if (r.ecoregions()[i].name != E[i].name || r.ecoregions()[i].biome != E[i].biome || r.ecoregions()[i].anchorCell != E[i].anchorCell) { match = false; break; } check(match, "ecoregions round-trip through save"); ss.clear(); ss.seekg(0); Planet old; bool okOld = old.readState(ss, true, true, true, true, true, true, true, true, true, false); check(okOld && old.ecoregions().empty(), "pre-v19 read leaves ecoregions empty"); } std::printf("\n%s (%d failure%s)\n", failures ? "FAILURES" : "ALL ECOREGION CHECKS PASSED", failures, failures == 1 ? "" : "s"); return failures ? 1 : 0; }