replay: record every run to a replay file (Phase 2)
Hooks a recorder into the command chokepoint so each run is written to disk as it plays, ready for playback in Phase 3. - ReplayRecorder (lib): line-oriented append-friendly file — keyed header (version, build, seed, config_hash, timestamp), then '---', then tick-tagged command lines interleaved with "# checksum <tick> <hex>" RNG fingerprints. Each line is flushed so a crash leaves a valid partial file. Config hash is a 64-bit FNV over the config dir's *.toml files; build tag is __DATE__/__TIME__. - CommandSerializer (lib): per-command text (length-prefixed variable parts; ship layouts and splitter filters serialized inline). Reset is a file boundary, never a stream entry. - CommandManager owns an optional ReplayRecorder: drain() records each applied command + a post-apply checksum; a drained Reset rolls to a new file; recordTickCheckpoint() (called per tick from onFrame) writes a checksum every 30 ticks. - Random seed generated in main and on restart (std::random_device); Simulation retains it via getSeed() for the header. - GameWorldView attaches the recorder at construction (first file + tick-0 checksum); replays land in <data>/replays named <timestamp>_<seed>.replay. ReplayRecorderTest covers serialization, file well-formedness, file rolling, and the CommandManager drain->record integration. Full suite green (346 cases / 3377 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:
@@ -382,20 +382,29 @@ Reshape mutations to flow through one path; behaviour unchanged.
|
||||
still passes; `[command]` equivalence tests pass; no UI call site mutates the sim directly
|
||||
(verified by grep — convention, not compile-enforced).
|
||||
|
||||
### Phase 2 — Recording
|
||||
### Phase 2 — Recording — DONE
|
||||
|
||||
- Implement the **line-oriented append writer**: header (seed, config hash, build/version,
|
||||
timestamp) + one line per command + checksum lines.
|
||||
- Generate a **random seed outside the sim** (in `main`/reset), write to header.
|
||||
- Compute the **config hash** over the loaded config.
|
||||
- Hook the **recorder at the apply chokepoint** in `CommandManager::drain()`: append each command
|
||||
(tick-tagged), append an **RNG checksum after every command** and **every 30 ticks**.
|
||||
- **Lifecycle:** open a new file on `Simulation` construction and on each `reset()` (restart =
|
||||
boundary); store in `data/`, named by timestamp+seed; retain everything.
|
||||
- **Files:** new replay writer in `lib`; `main.cpp` (seed), config hash helper;
|
||||
`CommandManager`/`Simulation` for the tick checksum hook.
|
||||
- **Exit criteria:** every run produces a well-formed, growing replay file; a crash mid-run still
|
||||
leaves a valid partial file.
|
||||
- `ReplayRecorder` (lib) writes the **line-oriented append file**: header (`version`, `build`,
|
||||
`seed`, `config_hash`, `timestamp`) then `---`, then one tick-tagged line per command
|
||||
interleaved with `# checksum <tick> <hex>` lines. Each line is flushed, so a crash mid-run
|
||||
leaves a valid partial file. `CommandSerializer` produces the per-command text (length-prefixed
|
||||
variable parts; `ShipLayoutConfig`/filters serialized inline). The build tag is
|
||||
`__DATE__ " " __TIME__`; the config hash is a 64-bit FNV over the `*.toml` files in the config
|
||||
dir (re-hashed on playback to detect mismatch).
|
||||
- **Random seed** generated in `main` (and on each restart in `MainWindow`) via
|
||||
`std::random_device`; `Simulation` retains it (`getSeed()`) for the header.
|
||||
- **Recorder hooked at the chokepoint:** `CommandManager` owns an optional `ReplayRecorder`;
|
||||
`drain()` records each applied command (tick-tagged) + a post-apply RNG checksum, and
|
||||
`recordTickCheckpoint()` (called per tick from the `onFrame` loop) writes a checksum every 30
|
||||
ticks. A drained `Reset` rolls the recorder to a new file (restart = boundary).
|
||||
- **Lifecycle:** `GameWorldView` attaches the recorder at construction (opens the first file with
|
||||
the initial seed + a tick-0 checksum); files live in `<data>/replays`, named
|
||||
`<timestamp>_<seed>.replay`; everything is retained.
|
||||
- **Files:** new `lib/sim/ReplayRecorder.{h,cpp}`, `CommandSerializer.{h,cpp}`; `Simulation`
|
||||
(`getSeed`); `CommandManager` (recorder + tick checkpoint); `main.cpp` (seed);
|
||||
`MainWindow.cpp` / `GameWorldView.{h,cpp}` (wiring); new `ReplayRecorderTest.cpp`.
|
||||
- **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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user