Live World stepper: step weather & storms back too (undo history)
Backward stepping previously only rewound the deterministic sky (day/night, tides, seasons, moons) and held the weather, because the cloud/rain/storm state is integrated forward and not analytically reversible. Now a forward step snapshots the full weather state via new Planet::captureWeather()/restoreWeather() (humidity/cloud/rain/storms + the storm RNG/next-id) into a bounded wxUndo ring, and the backward step restores the previous snapshot -- so '.' then ',' reverses EVERYTHING, including clouds, rain and moving storms. Both steps auto-pause (video frame-step feel). A continuous run (unpause) clears the undo history, after which ',' falls back to the sky-only rewind. Restoring also reseeds the storm RNG so re-stepping forward replays deterministically. test_weather.cpp adds a capture/restore round-trip check; all five suites pass; GUI build clean. Docs updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
47fee0b25f
commit
8efb1e343b
3
BUILD.md
3
BUILD.md
@ -53,7 +53,8 @@ the full ~2.8x speedup; the default uses all cores for no extra gain:
|
||||
O toggle ocean-current arrows (warm = poleward/red, cold = equatorward/blue)
|
||||
K toggle weather clouds/rain cover (Live World)
|
||||
Y follow-cam: cycle the 3D camera through active storms (Live World; off after last)
|
||||
. / , step the live clock forward / back by one rate-unit (back rewinds sky only)
|
||||
. / , step the live clock forward / back by one rate-unit (auto-pauses; back also
|
||||
rewinds weather + storms via an undo history)
|
||||
wheel over the 2D map: zoom toward cursor (1-8x); drag pans when zoomed
|
||||
SPACE pause while forming / re-evolve once settled (or the on-screen button)
|
||||
[ / ] drift speed (My/s) -- in Live World: live clock rate (hours/s, hour->month)
|
||||
|
||||
13
CLAUDE.md
13
CLAUDE.md
@ -422,9 +422,13 @@ Working and verified (logic tested headless):
|
||||
(`drawMapTris` now derives y from the rect, not the fixed `m.pos`). Mouse-wheel over the map zooms
|
||||
toward the cursor (1–8×); drag pans when zoomed, else rotates `mapLon`; 2D picking inverts the same
|
||||
rect. (3) **Clock stepper**: the `stepSim` live body is factored into `Viewer::liveAdvance(dtClock,
|
||||
dtWeather)`; **`.`** steps forward and **`,`** back by `liveRate` hours — backward rewinds the
|
||||
deterministic sky (day/night, tides, seasons, moon phases) but holds weather (`dtWeather=0`, not
|
||||
reversible). `S` in Live World aliases the forward step (no longer runs a stray tectonic tick).
|
||||
dtWeather)`; **`.`** steps forward and **`,`** back by `liveRate` hours (both auto-pause, like a
|
||||
video frame-step). Weather is an integrated path (not analytically reversible), so a forward step
|
||||
snapshots the full state — `Planet::captureWeather()`/`restoreWeather()` (humidity/cloud/rain/
|
||||
storms/RNG) into a bounded `wxUndo` history — and **`,` restores the previous snapshot**, so the
|
||||
step really reverses *everything* (clouds, rain, storms) plus the deterministic sky. A continuous
|
||||
run (unpause) clears the undo history (so `,` then falls back to a sky-only rewind). `S` in Live
|
||||
World aliases the forward step.
|
||||
- Mouse hover (in either view) shows per-cell info. Clicking a tile opens a
|
||||
right-side detail panel: tile info header + the tile's subgrid drawn as a
|
||||
flat hoverable grid of subtiles (neighbor-owned subtiles dimmed). A high-res
|
||||
@ -564,7 +568,8 @@ all in 3D + 2D) · `N` day/night terminator (Live World) · `T` tide-coloured co
|
||||
`SPACE` or on-screen button pause ·
|
||||
`[`/`]` drift speed (My/sec) — in **Live World** the live-clock rate (hours/sec, hour→month) ·
|
||||
`S` single tick (in **Live World** steps the clock forward) · `.`/`,` step the live clock
|
||||
forward/back by one rate-unit (`,` rewinds the sky only — weather can't reverse) ·
|
||||
forward/back by one rate-unit (auto-pauses; `,` steps **everything** back incl. weather/storms via
|
||||
an undo history) ·
|
||||
`Y` cycle the 3D camera to **follow a storm** (off after the last) · mouse-wheel **over the 2D map**
|
||||
zooms toward the cursor (drag pans when zoomed) · `F` fast-forward Phase-1 forming to settled ·
|
||||
`H` toggle Phase 3 (hydrology) · `L` generate biota population (flora/fauna/funga,
|
||||
|
||||
@ -220,9 +220,12 @@ Three viewer-only controls over the Live World sim:
|
||||
drag pans when zoomed, else keeps the `mapLon` longitude rotation.
|
||||
- **Clock stepper**: the `stepSim` Live-World body is factored into `Viewer::liveAdvance(dtClock,
|
||||
dtWeather)` (clamps `liveTime≥0`, recomputes insolation/season/tides/moons, `stepWeather`,
|
||||
overlay). `.`/`,` step ±`liveRate` hours; backward passes `dtWeather=0` because weather is an
|
||||
integrated, non-reversible path (the deterministic sky — day/night, tides, seasons, moon phases —
|
||||
still rewinds fine).
|
||||
overlay). `.`/`,` step ±`liveRate` hours and **auto-pause** (frame-step). Weather is integrated
|
||||
and not analytically reversible, so a forward step snapshots the full weather state
|
||||
(`Planet::captureWeather`/`restoreWeather` — humidity/cloud/rain/storms/RNG) into a bounded
|
||||
`wxUndo` ring; **`,` restores the previous snapshot**, reversing clouds/rain/storms exactly as well
|
||||
as the deterministic sky. A continuous run clears `wxUndo` (then `,` rewinds the sky only). The
|
||||
snapshot also reseeds the storm RNG so re-stepping forward replays deterministically.
|
||||
|
||||
## Headless testing
|
||||
|
||||
|
||||
@ -227,7 +227,7 @@ void Viewer::regenWorld() { // after generate(): geometry change
|
||||
buildMap2D(planet, mapRect, map2D);
|
||||
selectedCell = -1; subgrids.clear();
|
||||
settled = false; settleRun = 0; formAccum = 0.0; stepCount = 0; paused = false;
|
||||
liveWorld = false; followId = 0; // reseed/regen drops back to World Creation
|
||||
liveWorld = false; followId = 0; wxUndo.clear(); // reseed/regen drops back to World Creation
|
||||
planet.drifting = false; // Phase 1: original forming behavior
|
||||
phase3 = false; phase3Prompt = false; phase3PromptAt = planet.cfg.phase3AfterMy;
|
||||
rivers.clear(); bigRivers.clear();
|
||||
@ -304,6 +304,7 @@ void Viewer::stepSim() {
|
||||
if (liveWorld) {
|
||||
// --- Live World: advance the slow clock; geology is frozen --------
|
||||
double dtH = (!paused) ? liveRate * GetFrameTime() : 0.0; // simulated hours this frame
|
||||
if (dtH > 0.0) wxUndo.clear(); // a continuous run invalidates the manual step-back history
|
||||
liveAdvance(dtH, dtH);
|
||||
return;
|
||||
}
|
||||
@ -363,6 +364,29 @@ void Viewer::liveAdvance(double dtClock, double dtWeather) {
|
||||
rebuildLiveOverlay();
|
||||
}
|
||||
|
||||
// Step the live clock forward one rate-unit. Auto-pauses (like a video frame-step) and snapshots
|
||||
// the pre-step state so the backward step can restore weather + storms exactly.
|
||||
void Viewer::liveStepForward() {
|
||||
paused = true;
|
||||
if ((int)wxUndo.size() >= wxUndoMax) wxUndo.erase(wxUndo.begin());
|
||||
wxUndo.push_back(WxFrame{ liveTime, planet.captureWeather() });
|
||||
liveAdvance(liveRate, liveRate);
|
||||
}
|
||||
|
||||
// Step everything back one frame: restore the last snapshot (clock + weather + storms) if we have
|
||||
// one; otherwise fall back to rewinding the deterministic sky only (day/night, tides, seasons).
|
||||
void Viewer::liveStepBack() {
|
||||
paused = true;
|
||||
if (!wxUndo.empty()) {
|
||||
WxFrame f = wxUndo.back(); wxUndo.pop_back();
|
||||
liveTime = f.t;
|
||||
planet.restoreWeather(f.w);
|
||||
liveAdvance(0.0, 0.0); // recompute the sky/overlay at the restored time (weather held)
|
||||
} else {
|
||||
liveAdvance(-liveRate, 0.0); // no history: deterministic sky rewinds, weather holds
|
||||
}
|
||||
}
|
||||
|
||||
// The 2D map's projection rect after zoom/pan: mapRect scaled about its centre by mapZoom and
|
||||
// shifted by the screen-space pan. The scissor + frame stay the real mapRect, so it clips cleanly.
|
||||
Rectangle Viewer::mapViewRect() const {
|
||||
|
||||
@ -79,6 +79,11 @@ struct Viewer {
|
||||
bool liveWorld = false, dayNightOn = true;
|
||||
double liveTime = 0.0; // hours since the live clock started
|
||||
double liveRate = 1.0; // sim hours advanced per real second (ramps hour->month)
|
||||
// Step-back undo history: each forward step snapshots the clock + full weather state so a
|
||||
// backward step restores everything (weather is an integrated path, not analytically reversible).
|
||||
struct WxFrame { double t; WeatherSnapshot w; };
|
||||
std::vector<WxFrame> wxUndo;
|
||||
static constexpr int wxUndoMax = 180;
|
||||
std::vector<float> illum; // per-cell day/night brightness (1 = day, floor = night)
|
||||
std::vector<Color> shadedColors;// vcolors + snow/ice tint + day/night dim (live overlay)
|
||||
Vector3 sunDir{0.0f, 0.0f, 1.0f}; // model-space sub-solar direction (for the 3D sun marker)
|
||||
@ -138,6 +143,8 @@ struct Viewer {
|
||||
void loadGame(const char* path);
|
||||
void stepSim(); // advance forming / drift+hydrology this frame
|
||||
void liveAdvance(double dtClock, double dtWeather); // advance the Live World clock + fields
|
||||
void liveStepForward(); // step the clock forward one rate-unit (snapshots for undo)
|
||||
void liveStepBack(); // step everything back one frame (restores weather/storms)
|
||||
Rectangle mapViewRect() const; // 2D map projection rect after zoom/pan (scissor stays mapRect)
|
||||
|
||||
// ---- Input (ViewerInput.cpp) --------------------------------------------
|
||||
|
||||
@ -176,7 +176,7 @@ void Viewer::handleInput() {
|
||||
refreshView(); // fresh base colours; overlay builds in stepSim
|
||||
setStatus("Live World started");
|
||||
} else {
|
||||
paused = true; followId = 0; refreshView(); // back to World Creation (drift), paused
|
||||
paused = true; followId = 0; wxUndo.clear(); refreshView(); // back to World Creation, paused
|
||||
setStatus("Live World stopped");
|
||||
}
|
||||
}
|
||||
@ -204,13 +204,14 @@ void Viewer::handleInput() {
|
||||
if (IsKeyPressed(KEY_C)) { selectedCell = -1; subgrids.clear(); }
|
||||
if (IsKeyPressed(KEY_R)) { cfg.seed = (uint32_t)(GetTime() * 100000) | 1; regen(); }
|
||||
if (IsKeyPressed(KEY_S)) { // one step
|
||||
if (liveWorld) liveAdvance(liveRate, liveRate); // Live World: step the clock forward
|
||||
if (liveWorld) liveStepForward(); // Live World: step the clock forward
|
||||
else { stepOnce(); refreshView(); } // forming/drift: one tectonic tick
|
||||
}
|
||||
// Live World clock stepper: step by one rate-unit (liveRate hours). Backward rewinds the
|
||||
// deterministic sky (day/night, tides, seasons, moon phases); weather holds (can't reverse).
|
||||
if (IsKeyPressed(KEY_PERIOD) && liveWorld) { liveAdvance(liveRate, liveRate); setStatus("Step forward"); }
|
||||
if (IsKeyPressed(KEY_COMMA) && liveWorld) { liveAdvance(-liveRate, 0.0); setStatus("Step back (sky only)"); }
|
||||
// Live World clock stepper: step by one rate-unit (liveRate hours). Forward integrates weather;
|
||||
// backward restores the snapshot from the last forward step -> everything (incl. weather +
|
||||
// storms) steps back, within the current paused stepping session.
|
||||
if (IsKeyPressed(KEY_PERIOD) && liveWorld) { liveStepForward(); setStatus("Step forward"); }
|
||||
if (IsKeyPressed(KEY_COMMA) && liveWorld) { liveStepBack(); setStatus(wxUndo.empty() ? "Step back (sky only)" : "Step back"); }
|
||||
if (IsKeyPressed(KEY_F)) { // fast-forward to settled
|
||||
if (!settled) {
|
||||
while (!settled) stepOnce();
|
||||
|
||||
@ -100,6 +100,10 @@ public:
|
||||
const std::vector<double>& cloud() const { return sCloud; }
|
||||
const std::vector<double>& rain() const { return sRain; }
|
||||
const std::vector<WeatherSystem>& storms() const { return sStorms; }
|
||||
// Snapshot / restore the full weather state (humidity/cloud/rain/storms/RNG) for the viewer's
|
||||
// step-back undo history -- weather is an integrated path, so backward stepping restores a frame.
|
||||
WeatherSnapshot captureWeather() const;
|
||||
void restoreWeather(const WeatherSnapshot& s);
|
||||
|
||||
// Phase 3 (biomes): classify every cell into a Biome from elevation + the climate
|
||||
// fields (temperature + normalized precipitation). Derived + written back into
|
||||
|
||||
@ -64,6 +64,14 @@ struct Moon {
|
||||
double dispRadius = 0.10; // display sphere radius (visual size)
|
||||
};
|
||||
|
||||
// A full snapshot of the (integrated, non-analytic) weather state, for the viewer's step-back
|
||||
// undo history -- weather can't be reversed in closed form, so we restore a saved frame instead.
|
||||
struct WeatherSnapshot {
|
||||
std::vector<double> humidity, cloud, rain;
|
||||
std::vector<WeatherSystem> storms;
|
||||
uint32_t rng = 0, nextId = 0;
|
||||
};
|
||||
|
||||
struct Plate {
|
||||
int id = 0;
|
||||
PlateType type = PlateType::Oceanic; // initial crust type seeded onto cells
|
||||
|
||||
@ -26,6 +26,19 @@ void Planet::initWeather() {
|
||||
sHasWeather = true;
|
||||
}
|
||||
|
||||
WeatherSnapshot Planet::captureWeather() const {
|
||||
WeatherSnapshot s;
|
||||
s.humidity = sHumidity; s.cloud = sCloud; s.rain = sRain;
|
||||
s.storms = sStorms; s.rng = sWeatherRng; s.nextId = sStormNextId;
|
||||
return s;
|
||||
}
|
||||
|
||||
void Planet::restoreWeather(const WeatherSnapshot& s) {
|
||||
sHumidity = s.humidity; sCloud = s.cloud; sRain = s.rain;
|
||||
sStorms = s.storms; sWeatherRng = s.rng; sStormNextId = s.nextId;
|
||||
sHasWeather = !sHumidity.empty();
|
||||
}
|
||||
|
||||
void Planet::stepWeather(double dtHours) {
|
||||
const int n = (int)cells.size();
|
||||
if (!sHasWeather || (int)sHumidity.size() != n || (int)sCloud.size() != n || (int)sRain.size() != n)
|
||||
|
||||
@ -119,6 +119,20 @@ int main() {
|
||||
rt = false;
|
||||
check(rt, "save v10 round-trips the weather state");
|
||||
|
||||
std::printf("Weather: snapshot round-trip (step-back undo)\n");
|
||||
{
|
||||
Planet wc; wc.generate(cfg); wc.initWeather();
|
||||
for (int k = 0; k < 60; ++k) { wc.computeInsolation(0.25, std::fmod(0.3 + 0.01 * k, 1.0)); wc.stepWeather(1.0); }
|
||||
WeatherSnapshot snap = wc.captureWeather();
|
||||
int s0 = (int)wc.storms().size();
|
||||
for (int k = 0; k < 30; ++k) { wc.computeInsolation(0.25, std::fmod(0.9 + 0.01 * k, 1.0)); wc.stepWeather(1.0); }
|
||||
wc.restoreWeather(snap); // step back to the saved frame
|
||||
bool rt = ((int)wc.storms().size() == s0);
|
||||
for (int i = 0; i < n && rt; ++i)
|
||||
if (wc.cloud()[i] != snap.cloud[i] || wc.humidity()[i] != snap.humidity[i] || wc.rain()[i] != snap.rain[i]) rt = false;
|
||||
check(rt, "captureWeather/restoreWeather round-trips the full weather state");
|
||||
}
|
||||
|
||||
std::printf(failures ? "\nSOME WEATHER CHECKS FAILED (%d)\n" : "\nALL WEATHER CHECKS PASSED\n", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user