Fix deterministic live weather rewind

This commit is contained in:
Jonas Reith 2026-06-28 20:42:14 +02:00
parent 1358e81426
commit 695e07ba99
4 changed files with 35 additions and 4 deletions

View File

@ -407,12 +407,12 @@ void Viewer::liveStepForward() {
liveAdvance(liveRate, liveRate); 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 // 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. // the history is exhausted, fall back to rewinding the deterministic sky only.
void Viewer::liveStepBack() { void Viewer::liveStepBack() {
paused = true; 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()) { if (!wxUndo.empty()) {
WxFrame f = wxUndo.back(); wxUndo.pop_back(); WxFrame f = wxUndo.back(); wxUndo.pop_back();
liveTime = f.t; liveTime = f.t;

View File

@ -220,7 +220,7 @@ private:
// Weather (Live World; saved v10). sHasWeather latches once spun up/loaded. // Weather (Live World; saved v10). sHasWeather latches once spun up/loaded.
std::vector<double> sHumidity, sCloud, sRain; std::vector<double> sHumidity, sCloud, sRain;
bool sHasWeather = false; 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). // determinism intact (seeded from cfg.seed in initWeather).
std::vector<WeatherSystem> sStorms; std::vector<WeatherSystem> sStorms;
uint32_t sWeatherRng = 1; uint32_t sWeatherRng = 1;

View File

@ -28,7 +28,7 @@ enum class PlateType { Oceanic, Continental };
// A moving weather system (Live World): a drifting low-pressure disturbance that travels with the // 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 // 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 // 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 { struct WeatherSystem {
uint32_t id = 0; // stable id (for the viewer follow-cam; assigned at spawn) uint32_t id = 0; // stable id (for the viewer follow-cam; assigned at spawn)
Vec3 pos; // unit position on the sphere Vec3 pos; // unit position on the sphere

View File

@ -22,6 +22,24 @@ static void check(bool cond, const char* what) {
if (!cond) ++failures; if (!cond) ++failures;
} }
static bool sameStorms(const std::vector<WeatherSystem>& a, const std::vector<WeatherSystem>& 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). // 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) { static void runWeather(Planet& p, bool& everRained, double& maxCloud, int& maxStorms) {
p.initWeather(); p.initWeather();
@ -139,6 +157,19 @@ int main() {
check(rt, "captureWeather/restoreWeather round-trips the full weather state"); 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); std::printf(failures ? "\nSOME WEATHER CHECKS FAILED (%d)\n" : "\nALL WEATHER CHECKS PASSED\n", failures);
return failures ? 1 : 0; return failures ? 1 : 0;
} }