Files
dota_factory/src/lib/sim/ReplayRecorder.h
Malte Langkabel 26e108f3e1 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
2026-06-30 21:06:59 +02:00

55 lines
1.9 KiB
C++

#pragma once
#include <cstdint>
#include <fstream>
#include <string>
#include "Tick.h"
struct Command;
// 64-bit hash (16-char hex) over the *.toml files in configDir. Stored in the
// replay header and recomputed on playback to detect a config mismatch.
std::string computeReplayConfigHash(const std::string& configDir);
// 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:
std::string m_configDir;
std::string m_outputDir;
std::string m_filePath;
std::ofstream m_stream;
};