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:
53
src/lib/sim/ReplayRecorder.h
Normal file
53
src/lib/sim/ReplayRecorder.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include "Tick.h"
|
||||
|
||||
struct Command;
|
||||
|
||||
// Writes a replay file as the game runs (see docs/replay_design.md). The format
|
||||
// is line-oriented append-friendly text: a small keyed header, then one line per
|
||||
// command (tick-tagged) interleaved with RNG-state checksum lines for desync
|
||||
// detection. Each line is flushed so a crash mid-run still leaves a valid partial
|
||||
// file.
|
||||
//
|
||||
// One file = one run between Reset boundaries; startNewRun() closes the current
|
||||
// file and opens a fresh one.
|
||||
class ReplayRecorder
|
||||
{
|
||||
public:
|
||||
// configDir: hashed (its *.toml files) into the header for config-mismatch
|
||||
// detection on playback. outputDir: where .replay files are written.
|
||||
ReplayRecorder(std::string configDir, std::string outputDir);
|
||||
~ReplayRecorder();
|
||||
|
||||
ReplayRecorder(const ReplayRecorder&) = delete;
|
||||
ReplayRecorder& operator=(const ReplayRecorder&) = delete;
|
||||
|
||||
// Close any current file, then open a fresh one (named <timestamp>_<seed>),
|
||||
// write the header, and record an initial tick-0 checksum. A run boundary.
|
||||
void startNewRun(unsigned int seed, std::uint64_t initialRngFingerprint);
|
||||
|
||||
// Append one command line tagged with the tick it was applied at, followed by
|
||||
// the post-apply RNG checksum.
|
||||
void recordCommand(Tick tick, const Command& command, std::uint64_t rngFingerprint);
|
||||
|
||||
// Append a periodic RNG checksum line.
|
||||
void recordChecksum(Tick tick, std::uint64_t rngFingerprint);
|
||||
|
||||
void close();
|
||||
bool isOpen() const;
|
||||
const std::string& currentFilePath() const;
|
||||
|
||||
private:
|
||||
// 64-bit hash over the *.toml files in m_configDir, as a 16-char hex string.
|
||||
std::string computeConfigHash() const;
|
||||
|
||||
std::string m_configDir;
|
||||
std::string m_outputDir;
|
||||
std::string m_filePath;
|
||||
std::ofstream m_stream;
|
||||
};
|
||||
Reference in New Issue
Block a user