From 695e07ba9949243d6bae9bdcdbaed63dc6f3c591 Mon Sep 17 00:00:00 2001 From: Jonas Reith Date: Sun, 28 Jun 2026 20:42:14 +0200 Subject: [PATCH] Fix deterministic live weather rewind --- src/render/Viewer.cpp | 4 ++-- src/sim/Planet.hpp | 2 +- src/sim/PlanetTypes.hpp | 2 +- test_weather.cpp | 31 +++++++++++++++++++++++++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/render/Viewer.cpp b/src/render/Viewer.cpp index d1a1b81..9daac11 100644 --- a/src/render/Viewer.cpp +++ b/src/render/Viewer.cpp @@ -407,12 +407,12 @@ void Viewer::liveStepForward() { liveAdvance(liveRate, liveRate); } -// Step everything back: restore the most recent snapshot strictly before the current time (clock + +// Step everything back: restore the newest snapshot at or before the current time (clock + // weather + storms) -- so storms reverse whether they were born while stepping or during a run. If // the history is exhausted, fall back to rewinding the deterministic sky only. void Viewer::liveStepBack() { paused = true; - while (!wxUndo.empty() && wxUndo.back().t >= liveTime - 1e-6) wxUndo.pop_back(); // drop any future frames + while (!wxUndo.empty() && wxUndo.back().t > liveTime + 1e-6) wxUndo.pop_back(); // drop only true future frames if (!wxUndo.empty()) { WxFrame f = wxUndo.back(); wxUndo.pop_back(); liveTime = f.t; diff --git a/src/sim/Planet.hpp b/src/sim/Planet.hpp index f2da3c2..d68341f 100644 --- a/src/sim/Planet.hpp +++ b/src/sim/Planet.hpp @@ -220,7 +220,7 @@ private: // Weather (Live World; saved v10). sHasWeather latches once spun up/loaded. std::vector sHumidity, sCloud, sRain; bool sHasWeather = false; - // Moving weather systems (transient agents; not saved). Separate RNG keeps tectonic + // Moving weather systems (saved with weather state). Separate RNG keeps tectonic // determinism intact (seeded from cfg.seed in initWeather). std::vector sStorms; uint32_t sWeatherRng = 1; diff --git a/src/sim/PlanetTypes.hpp b/src/sim/PlanetTypes.hpp index 7af9ca9..14821f1 100644 --- a/src/sim/PlanetTypes.hpp +++ b/src/sim/PlanetTypes.hpp @@ -28,7 +28,7 @@ enum class PlateType { Oceanic, Continental }; // A moving weather system (Live World): a drifting low-pressure disturbance that travels with the // steering wind and stamps clouds & rain onto the weather fields. Geometry is fixed, so this is a // world-object agent (a point on the sphere, like a moon), not a cell. The intense tropical ones -// (strength past weatherHurricaneStrength) are hurricanes/typhoons. Transient -- not saved. +// (strength past weatherHurricaneStrength) are hurricanes/typhoons. Saved with the weather state. struct WeatherSystem { uint32_t id = 0; // stable id (for the viewer follow-cam; assigned at spawn) Vec3 pos; // unit position on the sphere diff --git a/test_weather.cpp b/test_weather.cpp index 412ed34..72b71c1 100644 --- a/test_weather.cpp +++ b/test_weather.cpp @@ -22,6 +22,24 @@ static void check(bool cond, const char* what) { if (!cond) ++failures; } +static bool sameStorms(const std::vector& a, const std::vector& b) { + if (a.size() != b.size()) return false; + for (size_t i = 0; i < a.size(); ++i) { + if (a[i].id != b[i].id + || a[i].pos.x != b[i].pos.x || a[i].pos.y != b[i].pos.y || a[i].pos.z != b[i].pos.z + || a[i].strength != b[i].strength || a[i].radius != b[i].radius + || a[i].age != b[i].age || a[i].life != b[i].life || a[i].spin != b[i].spin + || a[i].tropical != b[i].tropical) return false; + } + return true; +} + +static bool sameWeatherSnapshot(const WeatherSnapshot& a, const WeatherSnapshot& b) { + return a.humidity == b.humidity && a.cloud == b.cloud && a.rain == b.rain + && sameStorms(a.storms, b.storms) + && a.rng == b.rng && a.nextId == b.nextId; +} + // Run a fixed weather sequence on a planet (returns whether rain ever fell, max cloud + storms). static void runWeather(Planet& p, bool& everRained, double& maxCloud, int& maxStorms) { p.initWeather(); @@ -139,6 +157,19 @@ int main() { check(rt, "captureWeather/restoreWeather round-trips the full weather state"); } + std::printf("Weather: snapshot deterministic replay\n"); + { + Planet wc; wc.generate(cfg); wc.initWeather(); + for (int k = 0; k < 90; ++k) { wc.computeInsolation(0.25, std::fmod(0.2 + 0.01 * k, 1.0)); wc.stepWeather(1.0); } + WeatherSnapshot snap = wc.captureWeather(); + for (int k = 0; k < 180; ++k) { wc.computeInsolation(0.25, std::fmod(0.4 + 0.01 * k, 1.0)); wc.stepWeather(1.0); } + WeatherSnapshot first = wc.captureWeather(); + wc.restoreWeather(snap); + for (int k = 0; k < 180; ++k) { wc.computeInsolation(0.25, std::fmod(0.4 + 0.01 * k, 1.0)); wc.stepWeather(1.0); } + WeatherSnapshot second = wc.captureWeather(); + check(sameWeatherSnapshot(first, second), "restored weather snapshot replays storms and RNG exactly"); + } + std::printf(failures ? "\nSOME WEATHER CHECKS FAILED (%d)\n" : "\nALL WEATHER CHECKS PASSED\n", failures); return failures ? 1 : 0; }