Fix: step-back now reverses run-born storms (record undo history continuously)
The step-back undo history was cleared on every continuous-run frame, so weather systems (storms/hurricanes) created during a normal run had no recorded past. Stepping back then only rewound the deterministic sky and left the storm frozen at its current spot, resuming motion only on a forward step. liveAdvance() now records a snapshot of the pre-advance weather state at ~one-step cadence on ANY forward advance (continuous run or manual '.'), not just manual steps -- the interval scales with liveRate, so it's ~one snapshot per real second at any clock rate, in a bounded ring. liveStepBack() searches the ring for the most recent snapshot before the current time and restores it (clock + humidity/cloud/rain + storms + RNG), so storms reverse regardless of when they were born. The clear-on-run was removed; entering Live World still resets the ring. 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
8efb1e343b
commit
f748940004
10
CLAUDE.md
10
CLAUDE.md
@ -425,10 +425,12 @@ Working and verified (logic tested headless):
|
|||||||
dtWeather)`; **`.`** steps forward and **`,`** back by `liveRate` hours (both auto-pause, like a
|
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
|
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/
|
snapshots the full state — `Planet::captureWeather()`/`restoreWeather()` (humidity/cloud/rain/
|
||||||
storms/RNG) into a bounded `wxUndo` history — and **`,` restores the previous snapshot**, so the
|
storms/RNG) into a bounded `wxUndo` ring — and **`,` restores the previous snapshot**, so the
|
||||||
step really reverses *everything* (clouds, rain, storms) plus the deterministic sky. A continuous
|
step really reverses *everything* (clouds, rain, **moving storms**) plus the deterministic sky.
|
||||||
run (unpause) clears the undo history (so `,` then falls back to a sky-only rewind). `S` in Live
|
`liveAdvance` records a snapshot at ~one-step cadence on **any** forward advance (continuous run or
|
||||||
World aliases the forward step.
|
manual step), so storms born during a continuous run also rewind (an earlier version cleared the
|
||||||
|
history on run, which left run-born storms frozen on step-back); `,` searches the ring by time, the
|
||||||
|
ring drops oldest past `wxUndoMax`. `S` in Live World aliases the forward step.
|
||||||
- Mouse hover (in either view) shows per-cell info. Clicking a tile opens a
|
- 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
|
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
|
flat hoverable grid of subtiles (neighbor-owned subtiles dimmed). A high-res
|
||||||
|
|||||||
@ -223,9 +223,11 @@ Three viewer-only controls over the Live World sim:
|
|||||||
overlay). `.`/`,` step ±`liveRate` hours and **auto-pause** (frame-step). Weather is integrated
|
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
|
and not analytically reversible, so a forward step snapshots the full weather state
|
||||||
(`Planet::captureWeather`/`restoreWeather` — humidity/cloud/rain/storms/RNG) into a bounded
|
(`Planet::captureWeather`/`restoreWeather` — humidity/cloud/rain/storms/RNG) into a bounded
|
||||||
`wxUndo` ring; **`,` restores the previous snapshot**, reversing clouds/rain/storms exactly as well
|
`wxUndo` ring; **`,` restores the most recent snapshot before now**, reversing clouds/rain/storms
|
||||||
as the deterministic sky. A continuous run clears `wxUndo` (then `,` rewinds the sky only). The
|
exactly as well as the deterministic sky. `liveAdvance` records a snapshot at ~one-step cadence on
|
||||||
snapshot also reseeds the storm RNG so re-stepping forward replays deterministically.
|
*any* forward advance — continuous run or manual step — so storms born during a run also rewind
|
||||||
|
(the ring is bounded, ~one snapshot per real second since the interval scales with `liveRate`). The
|
||||||
|
restored snapshot includes the storm RNG, so re-stepping forward replays deterministically.
|
||||||
|
|
||||||
## Headless testing
|
## Headless testing
|
||||||
|
|
||||||
|
|||||||
@ -304,8 +304,7 @@ void Viewer::stepSim() {
|
|||||||
if (liveWorld) {
|
if (liveWorld) {
|
||||||
// --- Live World: advance the slow clock; geology is frozen --------
|
// --- Live World: advance the slow clock; geology is frozen --------
|
||||||
double dtH = (!paused) ? liveRate * GetFrameTime() : 0.0; // simulated hours this frame
|
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); // liveAdvance records step-back snapshots itself
|
||||||
liveAdvance(dtH, dtH);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!paused && !settled) {
|
if (!paused && !settled) {
|
||||||
@ -344,6 +343,17 @@ void Viewer::stepSim() {
|
|||||||
// integrated, non-reversible path, so it advances by dtWeather (0 = hold, used for a backward
|
// integrated, non-reversible path, so it advances by dtWeather (0 = hold, used for a backward
|
||||||
// step which still rewinds the deterministic sky: day/night, tides, seasons, moon phases).
|
// step which still rewinds the deterministic sky: day/night, tides, seasons, moon phases).
|
||||||
void Viewer::liveAdvance(double dtClock, double dtWeather) {
|
void Viewer::liveAdvance(double dtClock, double dtWeather) {
|
||||||
|
// Record a step-back snapshot of the PRE-advance state at ~one-step cadence, on ANY forward
|
||||||
|
// advance (continuous run or manual step) -- so stepping back reverses weather + storms whether
|
||||||
|
// they were born while stepping or during a continuous run. interval ~ liveRate means roughly
|
||||||
|
// one snapshot per real second regardless of the clock rate (bounded ring, drops oldest).
|
||||||
|
if (dtClock > 0.0) {
|
||||||
|
double interval = std::max(1e-6, liveRate);
|
||||||
|
if (wxUndo.empty() || liveTime - wxUndo.back().t >= interval - 1e-9) {
|
||||||
|
if ((int)wxUndo.size() >= wxUndoMax) wxUndo.erase(wxUndo.begin());
|
||||||
|
wxUndo.push_back(WxFrame{ liveTime, planet.captureWeather() });
|
||||||
|
}
|
||||||
|
}
|
||||||
liveTime = std::max(0.0, liveTime + dtClock);
|
liveTime = std::max(0.0, liveTime + dtClock);
|
||||||
double days = liveTime / planet.cfg.dayLengthHours;
|
double days = liveTime / planet.cfg.dayLengthHours;
|
||||||
double dayOfYear01 = days / planet.cfg.yearLengthDays; dayOfYear01 -= std::floor(dayOfYear01);
|
double dayOfYear01 = days / planet.cfg.yearLengthDays; dayOfYear01 -= std::floor(dayOfYear01);
|
||||||
@ -364,19 +374,19 @@ void Viewer::liveAdvance(double dtClock, double dtWeather) {
|
|||||||
rebuildLiveOverlay();
|
rebuildLiveOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step the live clock forward one rate-unit. Auto-pauses (like a video frame-step) and snapshots
|
// Step the live clock forward one rate-unit. Auto-pauses (like a video frame-step); liveAdvance
|
||||||
// the pre-step state so the backward step can restore weather + storms exactly.
|
// records the pre-step snapshot so the backward step can restore weather + storms exactly.
|
||||||
void Viewer::liveStepForward() {
|
void Viewer::liveStepForward() {
|
||||||
paused = true;
|
paused = true;
|
||||||
if ((int)wxUndo.size() >= wxUndoMax) wxUndo.erase(wxUndo.begin());
|
|
||||||
wxUndo.push_back(WxFrame{ liveTime, planet.captureWeather() });
|
|
||||||
liveAdvance(liveRate, liveRate);
|
liveAdvance(liveRate, liveRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step everything back one frame: restore the last snapshot (clock + weather + storms) if we have
|
// Step everything back: restore the most recent snapshot strictly before the current time (clock +
|
||||||
// one; otherwise fall back to rewinding the deterministic sky only (day/night, tides, seasons).
|
// 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() {
|
void Viewer::liveStepBack() {
|
||||||
paused = true;
|
paused = true;
|
||||||
|
while (!wxUndo.empty() && wxUndo.back().t >= liveTime - 1e-6) wxUndo.pop_back(); // drop any 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;
|
||||||
|
|||||||
@ -172,7 +172,7 @@ void Viewer::handleInput() {
|
|||||||
if (IsKeyPressed(KEY_W) && settled) { // enter / leave Live World (slow real-time clock)
|
if (IsKeyPressed(KEY_W) && settled) { // enter / leave Live World (slow real-time clock)
|
||||||
liveWorld = !liveWorld;
|
liveWorld = !liveWorld;
|
||||||
if (liveWorld) {
|
if (liveWorld) {
|
||||||
phase3Prompt = false; paused = false;
|
phase3Prompt = false; paused = false; wxUndo.clear();
|
||||||
refreshView(); // fresh base colours; overlay builds in stepSim
|
refreshView(); // fresh base colours; overlay builds in stepSim
|
||||||
setStatus("Live World started");
|
setStatus("Live World started");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user