Fix load: save weather systems (v11) + clear stale step-back history on load
After loading, storms vanished on forward play/step but reappeared on step-back. Two causes: the moving weather systems weren't saved (transient), so a load started with none; and loadGame didn't clear the wxUndo step-back ring, so stepping back restored STALE snapshots from before the load (which still held the old session's storms) -- hence 'back shows them, forward doesn't'. Save bumped to v11: the weather block now also persists sStorms + sWeatherRng + sStormNextId, so a load resumes the active storms and stepping forward continues them deterministically. Pre-v11 saves load with no active storms (they respawn); pre-v10 still spin weather up live. loadGame now clears wxUndo + followId so a load can't restore stale pre-load weather or follow a gone storm. test_weather.cpp checks the storms round-trip; all five suites pass; GUI build clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f748940004
commit
b19d2a1703
6
BUILD.md
6
BUILD.md
@ -76,9 +76,9 @@ 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 v10: +weather; v9 +moons; v8 +Live
|
planet.save binary snapshot (versioned, currently v11: +weather systems/storms; v10
|
||||||
World clock; v7 +biota): seed + config + full planet state; F5 writes it, F9
|
+weather fields; v9 +moons; v8 +Live World clock; v7 +biota): seed + config +
|
||||||
reloads and resumes deterministically. As of v6
|
full planet state; F5 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
|
||||||
(unknown keys ignored, missing keys default). v6 cannot load pre-v6
|
(unknown keys ignored, missing keys default). v6 cannot load pre-v6
|
||||||
|
|||||||
10
CLAUDE.md
10
CLAUDE.md
@ -93,7 +93,7 @@ the fixed-grid Eulerian model + the climate fields are the groundwork for it.
|
|||||||
oceans, travel with the steering wind (poleward recurve), intensify over warm water, decay over
|
oceans, travel with the steering wind (poleward recurve), intensify over warm water, decay over
|
||||||
land, and **stamp** travelling cloud/rain onto the grid — so the sky visibly evolves. The intense
|
land, and **stamp** travelling cloud/rain onto the grid — so the sky visibly evolves. The intense
|
||||||
tropical ones are **hurricanes/typhoons** (spin by hemisphere, eye + animated spiral marker).
|
tropical ones are **hurricanes/typhoons** (spin by hemisphere, eye + animated spiral marker).
|
||||||
Transient (not saved; respawn from the seed). This makes the weather visibly move (the base field
|
Saved (v11, so a load resumes active storms). This makes the weather visibly move (the base field
|
||||||
alone relaxes to a static pattern).
|
alone relaxes to a static pattern).
|
||||||
|
|
||||||
## Current state
|
## Current state
|
||||||
@ -400,7 +400,7 @@ Working and verified (logic tested headless):
|
|||||||
fields in range, clouds form + rain falls, oceans moister than land, determinism, v10 round-trip.
|
fields in range, clouds form + rain falls, oceans moister than land, determinism, v10 round-trip.
|
||||||
- **Live World — moving weather systems (lows / hurricanes / typhoons):** the base cloud/rain
|
- **Live World — moving weather systems (lows / hurricanes / typhoons):** the base cloud/rain
|
||||||
field relaxes to a *static* pattern under fixed forcing, so `stepWeather` now also runs a
|
field relaxes to a *static* pattern under fixed forcing, so `stepWeather` now also runs a
|
||||||
population of drifting **`WeatherSystem`** agents (PlanetTypes; transient, not saved; separate
|
population of drifting **`WeatherSystem`** agents (PlanetTypes; **saved v11**; separate
|
||||||
`sWeatherRng` seeded from `cfg.seed` → tectonic determinism intact). Each step: **spawn** over
|
`sWeatherRng` seeded from `cfg.seed` → tectonic determinism intact). Each step: **spawn** over
|
||||||
warm tropical ocean (5–25°, SST ≥ `weatherTropicalSST`) or a mid-latitude (30–62°) ocean low
|
warm tropical ocean (5–25°, SST ≥ `weatherTropicalSST`) or a mid-latitude (30–62°) ocean low
|
||||||
(capped at `weatherSystemMax`, prob ∝ `weatherSpawnRate`); **move** along the steering wind
|
(capped at `weatherSystemMax`, prob ∝ `weatherSpawnRate`); **move** along the steering wind
|
||||||
@ -609,10 +609,12 @@ save header is versioned (currently **9**; v2 adds the `[`/`]` drift rate, v3 a
|
|||||||
key=value text block** instead of a raw POD dump, v7 appends the **biota population**
|
key=value text block** instead of a raw POD dump, v7 appends the **biota population**
|
||||||
block — three Organism lists per cell, gated by a flag byte, v8 appends the **Live World**
|
block — three Organism lists per cell, gated by a flag byte, v8 appends the **Live World**
|
||||||
clock — a flag byte + `liveTime`, v9 appends the **moons** block, v10 appends the **weather** block —
|
clock — a flag byte + `liveTime`, v9 appends the **moons** block, v10 appends the **weather** block —
|
||||||
humidity/cloud/rain, flag-gated); newer-than-supported is
|
humidity/cloud/rain, flag-gated, v11 also persists the **weather systems** + RNG so a load resumes
|
||||||
|
active storms); newer-than-supported is
|
||||||
rejected. Older saves (no biota block) load fine with an empty population (press `L`);
|
rejected. Older saves (no biota block) load fine with an empty population (press `L`);
|
||||||
pre-v8 saves load with Live World off; pre-v9 saves synthesize moons from the seed; pre-v10
|
pre-v8 saves load with Live World off; pre-v9 saves synthesize moons from the seed; pre-v10
|
||||||
saves spin weather up live.
|
saves spin weather up live; pre-v11 saves load with no active storms (they respawn). `loadGame`
|
||||||
|
clears the step-back `wxUndo` history so a load can't restore stale pre-load weather.
|
||||||
**As of v6, adding/removing PlanetConfig fields no longer breaks saves** — the saved
|
**As of v6, adding/removing PlanetConfig fields no longer breaks saves** — the saved
|
||||||
config is parsed like `planet.cfg` (unknown keys ignored, missing keys keep defaults),
|
config is parsed like `planet.cfg` (unknown keys ignored, missing keys keep defaults),
|
||||||
written at `precision(17)` so doubles round-trip exactly. (v6 cannot load pre-v6 saves —
|
written at `precision(17)` so doubles round-trip exactly. (v6 cannot load pre-v6 saves —
|
||||||
|
|||||||
@ -201,6 +201,9 @@ recurve (`weatherSystemSpeed`), **intensify** over warm sea / **decay+cull** ove
|
|||||||
**stamp** a Gaussian cloud/rain shield onto the grid — so cloud clusters travel and dissipate
|
**stamp** a Gaussian cloud/rain shield onto the grid — so cloud clusters travel and dissipate
|
||||||
behind them. Tropical systems past `weatherHurricaneStr` are hurricanes/typhoons; rendered as
|
behind them. Tropical systems past `weatherHurricaneStr` are hurricanes/typhoons; rendered as
|
||||||
animated cyclonic spiral markers (eye for cyclones) spinning by hemisphere, in 3D + 2D, under `K`.
|
animated cyclonic spiral markers (eye for cyclones) spinning by hemisphere, in 3D + 2D, under `K`.
|
||||||
|
The systems (+ their RNG/next-id) are **saved (v11)** alongside the humidity/cloud/rain fields, so a
|
||||||
|
load resumes active storms; `loadGame` clears the step-back `wxUndo` ring so a load can't restore a
|
||||||
|
stale pre-load weather state.
|
||||||
|
|
||||||
## Live World viewer controls (follow-cam, 2D zoom, clock stepper)
|
## Live World viewer controls (follow-cam, 2D zoom, clock stepper)
|
||||||
|
|
||||||
|
|||||||
@ -279,7 +279,7 @@ void Viewer::loadGame(const char* path) {
|
|||||||
if (ver >= 8) { is.read(reinterpret_cast<char*>(&lw), sizeof lw);
|
if (ver >= 8) { is.read(reinterpret_cast<char*>(&lw), sizeof lw);
|
||||||
is.read(reinterpret_cast<char*>(&lh), sizeof lh); } // v8: Live World clock
|
is.read(reinterpret_cast<char*>(&lh), sizeof lh); } // v8: Live World clock
|
||||||
if (!is || std::memcmp(magic, "PLSV", 4) != 0 || ver > SAVE_VERSION) { setStatus("Load failed: bad file"); return; }
|
if (!is || std::memcmp(magic, "PLSV", 4) != 0 || ver > SAVE_VERSION) { setStatus("Load failed: bad file"); return; }
|
||||||
if (!planet.readState(is, ver >= 4, ver >= 7, ver >= 9, ver >= 10)) { setStatus("Load failed: corrupt/mismatch"); return; } // v4 biome, v7 biota, v9 moons, v10 weather
|
if (!planet.readState(is, ver >= 4, ver >= 7, ver >= 9, ver >= 10, ver >= 11)) { setStatus("Load failed: corrupt/mismatch"); return; } // v4 biome, v7 biota, v9 moons, v10 weather, v11 storms
|
||||||
cfg = planet.cfg; // adopt the loaded config
|
cfg = planet.cfg; // adopt the loaded config
|
||||||
elapsedMy = em; settled = (st != 0);
|
elapsedMy = em; settled = (st != 0);
|
||||||
planet.drifting = settled; // resume drift boosts iff mid-drift
|
planet.drifting = settled; // resume drift boosts iff mid-drift
|
||||||
@ -290,6 +290,7 @@ void Viewer::loadGame(const char* path) {
|
|||||||
settleRun = settleNeed; // keep the settled latch consistent
|
settleRun = settleNeed; // keep the settled latch consistent
|
||||||
dtMy = settled ? planet.cflDtMy() : 0.0;
|
dtMy = settled ? planet.cflDtMy() : 0.0;
|
||||||
driftAccum = 0.0; formAccum = 0.0;
|
driftAccum = 0.0; formAccum = 0.0;
|
||||||
|
wxUndo.clear(); followId = 0; // drop stale step-back history / follow target
|
||||||
paused = true; selectedCell = -1; subgrids.clear();
|
paused = true; selectedCell = -1; subgrids.clear();
|
||||||
buildBorders(planet, borderR, borders, ridgeBorders);
|
buildBorders(planet, borderR, borders, ridgeBorders);
|
||||||
buildDriftArrows(planet, driftR, driftArrows, plateLabels);
|
buildDriftArrows(planet, driftR, driftArrows, plateLabels);
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
// ViewerInput.cpp (input/picking/keys) and ViewerRender.cpp (drawing).
|
// ViewerInput.cpp (input/picking/keys) and ViewerRender.cpp (drawing).
|
||||||
struct Viewer {
|
struct Viewer {
|
||||||
// ---- Files / save format ------------------------------------------------
|
// ---- Files / save format ------------------------------------------------
|
||||||
static constexpr uint32_t SAVE_VERSION = 10; // v10: +weather; v9: +moons; v8: +Live World clock; v7: +biota; v6: self-describing config; v4: +biome; v3: +phase3
|
static constexpr uint32_t SAVE_VERSION = 11; // v11: +weather systems; v10: +weather fields; v9: +moons; v8: +Live World clock; v7: +biota; v6: self-describing config; v4: +biome; v3: +phase3
|
||||||
const char* CONFIG_PATH = "planet.cfg";
|
const char* CONFIG_PATH = "planet.cfg";
|
||||||
const char* SAVE_PATH = "planet.save";
|
const char* SAVE_PATH = "planet.save";
|
||||||
std::string configPath = "planet.cfg"; // initial config (--config overrides)
|
std::string configPath = "planet.cfg"; // initial config (--config overrides)
|
||||||
|
|||||||
@ -143,7 +143,7 @@ public:
|
|||||||
// synthesize moons from the seed instead. hasWeather: the weather block (save v10+);
|
// synthesize moons from the seed instead. hasWeather: the weather block (save v10+);
|
||||||
// older saves leave weather to spin up on entering Live World.
|
// older saves leave weather to spin up on entering Live World.
|
||||||
bool readState(std::istream& is, bool hasBiome = true, bool hasBiota = true,
|
bool readState(std::istream& is, bool hasBiome = true, bool hasBiota = true,
|
||||||
bool hasMoons = true, bool hasWeather = true);
|
bool hasMoons = true, bool hasWeather = true, bool hasStorms = true);
|
||||||
|
|
||||||
// Helpers for rendering / info.
|
// Helpers for rendering / info.
|
||||||
double cellWidthMeters() const; // approx lateral cell spacing
|
double cellWidthMeters() const; // approx lateral cell spacing
|
||||||
|
|||||||
@ -301,12 +301,18 @@ void Planet::writeState(std::ostream& os) const {
|
|||||||
writeVec(os, cb.flora); writeVec(os, cb.fauna); writeVec(os, cb.funga);
|
writeVec(os, cb.flora); writeVec(os, cb.fauna); writeVec(os, cb.funga);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// v10: Live World weather (humidity/cloud/rain). Flag-gated like biota.
|
// v10: Live World weather (humidity/cloud/rain). Flag-gated like biota. v11 also persists the
|
||||||
|
// moving weather systems + their RNG/next-id, so loading restores active storms (and stepping
|
||||||
|
// forward continues them deterministically) instead of losing them.
|
||||||
uint8_t hasWx = (sHasWeather && sHumidity.size() == cells.size()) ? 1 : 0; writePod(os, hasWx);
|
uint8_t hasWx = (sHasWeather && sHumidity.size() == cells.size()) ? 1 : 0; writePod(os, hasWx);
|
||||||
if (hasWx) { writeVec(os, sHumidity); writeVec(os, sCloud); writeVec(os, sRain); }
|
if (hasWx) {
|
||||||
|
writeVec(os, sHumidity); writeVec(os, sCloud); writeVec(os, sRain);
|
||||||
|
writeVec(os, sStorms); writePod(os, sWeatherRng); writePod(os, sStormNextId); // v11
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Planet::readState(std::istream& is, bool hasBiome, bool hasBiota, bool hasMoons, bool hasWeather) {
|
bool Planet::readState(std::istream& is, bool hasBiome, bool hasBiota, bool hasMoons,
|
||||||
|
bool hasWeather, bool hasStorms) {
|
||||||
// Read the length-prefixed key=value config block (see writeState). A default
|
// Read the length-prefixed key=value config block (see writeState). A default
|
||||||
// PlanetConfig is parsed over, so fields absent from an older save keep their
|
// PlanetConfig is parsed over, so fields absent from an older save keep their
|
||||||
// current defaults. The length guard rejects pre-v6 (raw-POD-config) saves.
|
// current defaults. The length guard rejects pre-v6 (raw-POD-config) saves.
|
||||||
@ -362,6 +368,10 @@ bool Planet::readState(std::istream& is, bool hasBiome, bool hasBiota, bool hasM
|
|||||||
readVec(is, sHumidity); readVec(is, sCloud); readVec(is, sRain);
|
readVec(is, sHumidity); readVec(is, sCloud); readVec(is, sRain);
|
||||||
if (!is || (int)sHumidity.size() != (int)cells.size()) return false;
|
if (!is || (int)sHumidity.size() != (int)cells.size()) return false;
|
||||||
sHasWeather = true;
|
sHasWeather = true;
|
||||||
|
if (hasStorms) { // v11: active weather systems + their RNG
|
||||||
|
readVec(is, sStorms); readPod(is, sWeatherRng); readPod(is, sStormNextId);
|
||||||
|
if (!is) return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
computeBiotaDensity(); // derived density scalars for the colour views
|
computeBiotaDensity(); // derived density scalars for the colour views
|
||||||
|
|||||||
@ -108,16 +108,22 @@ int main() {
|
|||||||
check(ang > 1e-4 && ang < 0.3, "a weather system moves between steps");
|
check(ang > 1e-4 && ang < 0.3, "a weather system moves between steps");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::printf("Weather: save v10\n");
|
std::printf("Weather: save v11 (fields + storms)\n");
|
||||||
std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
|
std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
|
||||||
p.writeState(ss);
|
p.writeState(ss);
|
||||||
Planet q;
|
Planet q;
|
||||||
bool ok = q.readState(ss, true, true, true, true);
|
bool ok = q.readState(ss, true, true, true, true, true);
|
||||||
bool rt = ok && (int)q.cloud().size() == n;
|
bool rt = ok && (int)q.cloud().size() == n;
|
||||||
for (int i = 0; i < n && rt; ++i)
|
for (int i = 0; i < n && rt; ++i)
|
||||||
if (q.humidity()[i] != p.humidity()[i] || q.cloud()[i] != p.cloud()[i] || q.rain()[i] != p.rain()[i])
|
if (q.humidity()[i] != p.humidity()[i] || q.cloud()[i] != p.cloud()[i] || q.rain()[i] != p.rain()[i])
|
||||||
rt = false;
|
rt = false;
|
||||||
check(rt, "save v10 round-trips the weather state");
|
check(rt, "save round-trips the weather fields");
|
||||||
|
bool srt = ((int)q.storms().size() == (int)p.storms().size());
|
||||||
|
for (size_t i = 0; i < q.storms().size() && srt; ++i)
|
||||||
|
if (q.storms()[i].id != p.storms()[i].id
|
||||||
|
|| q.storms()[i].pos.dot(p.storms()[i].pos) < 1.0 - 1e-9
|
||||||
|
|| q.storms()[i].strength != p.storms()[i].strength) srt = false;
|
||||||
|
check(srt, "save v11 round-trips the active weather systems");
|
||||||
|
|
||||||
std::printf("Weather: snapshot round-trip (step-back undo)\n");
|
std::printf("Weather: snapshot round-trip (step-back undo)\n");
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user