Fix defensive bounds checks found in review: plate indexing, war/diplomacy load, map zoom reset

- PlanetDrift.cpp: guard cells[i].plateId before indexing plates[] in advect(),
  matching the bounds-checking convention used elsewhere in plate lifecycle code.
- Panels.cpp: same guard in cellInfo() for the cell-info panel's plate lookup.
- PlanetIO.cpp: range-check War.attacker/.defender and DiploTie.a/.b against
  settlements.size() on load, matching the validation already done for
  sSettleAllegiance.
- Viewer.cpp: reset mapZoom/mapPanX/mapPanY in regenWorld() so a zoomed 2D map
  doesn't strand the view on reseed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jonas 2026-07-02 19:03:52 +02:00
parent cdf4319b31
commit 99f4a925a1
4 changed files with 13 additions and 3 deletions

View File

@ -34,7 +34,8 @@ static int drawWrapped(const std::string& text, int x, int y, int font, Color co
static std::vector<std::string> cellInfo(const Planet& p, int i, double elev, double age) { static std::vector<std::string> cellInfo(const Planet& p, int i, double elev, double age) {
const Cell& c = p.cells[i]; const Cell& c = p.cells[i];
double lon, lat; dirToLonLat(c.unit, lon, lat); double lon, lat; dirToLonLat(c.unit, lon, lat);
const Plate& pl = p.plates[c.plateId]; static const Plate kUnknownPlate{}; // defensive: a cell should always own a valid plate
const Plate& pl = (c.plateId >= 0 && c.plateId < (int)p.plates.size()) ? p.plates[c.plateId] : kUnknownPlate;
const int n = (int)p.cells.size(); const int n = (int)p.cells.size();
auto sized = [&](const std::vector<double>& v) { return (int)v.size() == n; }; auto sized = [&](const std::vector<double>& v) { return (int)v.size() == n; };
std::vector<std::string> L; std::vector<std::string> L;

View File

@ -284,6 +284,7 @@ void Viewer::regenWorld() { // after generate(): geometry change
buildDriftArrows(planet, driftR, driftArrows, plateLabels); buildDriftArrows(planet, driftR, driftArrows, plateLabels);
buildMap2D(planet, mapRect, map2D); buildMap2D(planet, mapRect, map2D);
selectedCell = -1; subgrids.clear(); selectedCell = -1; subgrids.clear();
mapZoom = 1.0; mapPanX = 0.0; mapPanY = 0.0; // drop any 2D-map zoom/pan from the old world
settled = false; settleRun = 0; formAccum = 0.0; stepCount = 0; paused = false; settled = false; settleRun = 0; formAccum = 0.0; stepCount = 0; paused = false;
liveWorld = false; followId = 0; wxUndo.clear(); events.clear(); nextEventId = 1; // reseed/regen drops back to World Creation liveWorld = false; followId = 0; wxUndo.clear(); events.clear(); nextEventId = 1; // reseed/regen drops back to World Creation
liveInfoTab = 0; eventRowRects.clear(); eventRowIndices.clear(); liveInfoTab = 0; eventRowRects.clear(); eventRowIndices.clear();

View File

@ -34,8 +34,12 @@ void Planet::advect(double dtMy) {
std::vector<Vec3> vel(n); std::vector<Vec3> vel(n);
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
oP[i] = cells[i].plateId; oE[i] = cells[i].elevation; oA[i] = cells[i].geoAge; oO[i] = cells[i].oceanic; oP[i] = cells[i].plateId; oE[i] = cells[i].elevation; oA[i] = cells[i].geoAge; oO[i] = cells[i].oceanic;
if (cells[i].plateId >= 0 && cells[i].plateId < (int)plates.size()) {
const Plate& p = plates[cells[i].plateId]; const Plate& p = plates[cells[i].plateId];
vel[i] = (p.driftAxis * p.angSpeed).cross(cells[i].unit) * R; vel[i] = (p.driftAxis * p.angSpeed).cross(cells[i].unit) * R;
} else {
vel[i] = Vec3(); // defensive: a cell should always own a valid plate
}
} }
for (int c = 0; c < n; ++c) { for (int c = 0; c < n; ++c) {

View File

@ -756,6 +756,8 @@ bool Planet::readState(std::istream& is, bool hasBiome, bool hasBiota, bool hasM
readPod(is, w.id); readPod(is, w.attacker); readPod(is, w.defender); readPod(is, w.id); readPod(is, w.attacker); readPod(is, w.defender);
readPod(is, w.startYear); readPod(is, w.warscore); readPod(is, w.battles); readPod(is, w.startYear); readPod(is, w.warscore); readPod(is, w.battles);
if (!is || !std::isfinite(w.warscore)) return false; if (!is || !std::isfinite(w.warscore)) return false;
if (w.attacker < 0 || w.attacker >= (int)settlements.size()
|| w.defender < 0 || w.defender >= (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;
@ -771,6 +773,8 @@ bool Planet::readState(std::istream& is, bool hasBiome, bool hasBiota, bool hasM
readPod(is, t.truceUntil); uint8_t k = 0; readPod(is, k); readPod(is, t.truceUntil); uint8_t k = 0; readPod(is, k);
t.kind = (k <= (uint8_t)DiploKind::Rival) ? (DiploKind)k : DiploKind::Neutral; t.kind = (k <= (uint8_t)DiploKind::Rival) ? (DiploKind)k : DiploKind::Neutral;
if (!is || !std::isfinite(t.attitude)) return false; if (!is || !std::isfinite(t.attitude)) return false;
if (t.a < 0 || t.a >= (int)settlements.size()
|| t.b < 0 || t.b >= (int)settlements.size()) return false;
} }
} }
computeBiotaDensity(); // derived density scalars for the colour views computeBiotaDensity(); // derived density scalars for the colour views