replay: play back recorded runs via --replay (Phase 3)
Adds view-only playback: re-simulate from the recorded seed + commands and verify the RNG checksums. - ReplayReader (lib): parses a replay file into header + an ordered stream of command/checksum entries. CommandSerializer gains the inverse parseCommand (round-trips every verb; rejects malformed input). - ReplayPlayer (lib): the playback driver. Applies each command at its exact recorded tick and verifies checksums in file order (start() handles tick 0; advanceTo(tick) handles each tick after sim.tick()). Independent of replay-time speed/pause; reports the first desync tick. - CommandManager replay mode: enqueue() becomes a no-op so live input is ignored while the recorded stream drives application. - main.cpp: --replay <file> reads + validates (warns on version/config-hash mismatch), seeds the sim from the header, and threads the replay through MainWindow to GameWorldView. - GameWorldView: drives the player in onFrame (manual speed/pause kept, forward-only), gates the schematic-choices and game-over polls, and draws a "REPLAY" tag plus a passive "Replay ended" / "Desync at tick N" overlay. - computeReplayConfigHash factored out of ReplayRecorder for reuse by main. ReplayPlaybackTest records a scripted run, reads it back, replays it, and asserts no desync + byte-identical final state -- including the periodic-checksum-then-command ordering at a shared tick. Full suite green (350 cases / 3396 assertions); app, tests, and balancing all build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DUsFgd2Ga6pmLz8giS8WUn
This commit is contained in:
@@ -406,23 +406,32 @@ Reshape mutations to flow through one path; behaviour unchanged.
|
||||
- **Exit criteria met:** recorder + serializer + drain-integration tests pass; the format is
|
||||
well-formed and flushed per line. (Live GUI recording is wired but not auto-tested here.)
|
||||
|
||||
### Phase 3 — Playback
|
||||
### Phase 3 — Playback — DONE
|
||||
|
||||
- Implement the **reader/parser** for the format (header + commands + checksums).
|
||||
- Add the `--replay <file>` CLI path in `main`: validate config hash + version (warn on
|
||||
mismatch), construct `Simulation` from seed+config, construct `CommandManager` in **replay
|
||||
mode** (pre-filled, `addCommand` is a no-op).
|
||||
- Replay driver: each frame, drain commands due at the reached tick (same drain path), step
|
||||
ticks, **keep manual speed/pause**, playback only moves forward.
|
||||
- **Gate the two `onFrame` polls** (schematic-choices, game-over) off in replay mode; everything
|
||||
else (recipe/layout dialogs, escape menu) is input-driven and falls away automatically.
|
||||
- **Desync detection:** recompute the RNG checksum at each checkpoint, compare to the file,
|
||||
report "desync at tick N" on mismatch.
|
||||
- **Passive end:** when the command stream is exhausted / recorded game-over is reached, stop
|
||||
with a "replay ended" overlay instead of the restart dialog.
|
||||
- **Files:** new reader in `lib`; `main.cpp`; `CommandManager` (replay mode); `GameWorldView.cpp`
|
||||
(poll gating, end overlay).
|
||||
- **Exit criteria:** a recorded file plays back identically; checksums match throughout.
|
||||
- `ReplayReader` (lib) parses the file into `{ header, entries }`, where each entry is a command
|
||||
(with its tick) or a checksum (with its tick), kept in **file order**. `CommandSerializer`
|
||||
gained the inverse `parseCommand` (round-tripping every verb).
|
||||
- `--replay <file>` CLI path in `main`: reads the file, **warns** on version / config-hash
|
||||
mismatch (proceeds anyway), constructs the `Simulation` from the header seed, and threads the
|
||||
parsed replay through `MainWindow` to `GameWorldView`.
|
||||
- `ReplayPlayer` (lib) is the playback driver. Rather than reproduce frame batching, it applies
|
||||
each command at its **exact recorded tick** and verifies checksums **in file order**:
|
||||
`start()` processes the tick-0 entries, then after every `sim.tick()` `advanceTo(tick)`
|
||||
consumes that tick's entries (periodic checksum first, then command + its checksum — the order
|
||||
the file already has). This makes playback independent of replay-time speed/pause.
|
||||
- `GameWorldView` runs the player in `onFrame` when in replay mode (manual speed/pause kept,
|
||||
forward-only); `CommandManager` is put in **replay mode** so live input is a no-op. The two
|
||||
sim-state polls (schematic-choices, game-over) are **gated off**; dialog/escape paths are
|
||||
input-driven and fall away. A **"REPLAY"** tag plus a passive **"Replay ended"** /
|
||||
**"Desync at tick N"** overlay replaces the restart dialog.
|
||||
- **Files:** new `lib/sim/ReplayReader.{h,cpp}`, `ReplayPlayer.{h,cpp}`; `CommandSerializer`
|
||||
(`parseCommand`); `ReplayRecorder` (shared `computeReplayConfigHash`); `CommandManager`
|
||||
(replay mode); `main.cpp`; `MainWindow.{h,cpp}`; `GameWorldView.{h,cpp}`; new
|
||||
`ReplayPlaybackTest.cpp`.
|
||||
- **Exit criteria met:** the headless `ReplayPlaybackTest` records a scripted run, reads it back,
|
||||
replays it, and asserts **no desync** and a **byte-identical final state checksum** — including
|
||||
the periodic-checksum-then-command ordering at a shared tick. (Live GUI playback is wired but
|
||||
not auto-tested here.)
|
||||
|
||||
### Phase 4 — Closing tests & polish
|
||||
|
||||
|
||||
Reference in New Issue
Block a user