Replay: deterministic record & playback (#4)

Add deterministic record/playback for a run.

Recording captures `(seed, config hash, ordered tick-tagged commands)` and re-simulates on playback — no state snapshots. `DotaFactory.exe --replay <file>` re-plays a recorded run view-only with manual speed/pause.

Reviewed-on: #4
Co-authored-by: Malte Langkabel <malte.langkabel@gmail.com>
Co-committed-by: Malte Langkabel <malte.langkabel@gmail.com>
This commit was merged in pull request #4.
This commit is contained in:
2026-07-01 19:20:08 +00:00
committed by mlangkabel
parent cf68ac2862
commit d74ba5bfad
40 changed files with 3385 additions and 129 deletions

View File

@@ -0,0 +1,55 @@
#pragma once
#include <memory>
#include <vector>
struct Command;
class ReplayRecorder;
class Simulation;
// Ordered queue that funnels every player command into the single
// Simulation::apply chokepoint (see docs/replay_design.md). This is deliberately
// NOT the EventManager pub/sub bus: sim mutations must apply in a strict,
// tick-pinned, recordable order to a single recipient.
//
// Live play pushes commands via enqueue(); they are applied at the next drain(),
// which runs once per frame before the tick batch. Draining before the tick
// (even at 0x game speed) lets a paused player see placed construction sites
// immediately while staying deterministic — replay applies each command at its
// recorded tick regardless of frame cadence.
class CommandManager
{
public:
explicit CommandManager(Simulation& simulation);
// Defined out-of-line so the unique_ptr<ReplayRecorder> member can be
// destroyed where ReplayRecorder is a complete type.
~CommandManager();
// Append a command for application at the next drain (FIFO order).
void enqueue(std::shared_ptr<const Command> command);
// Apply all queued commands in FIFO order through Simulation::apply, then
// clear the queue. If a recorder is attached, each applied command is recorded
// (a Reset rolls the recorder to a new file).
void drain();
bool hasPending() const;
// Attach a recorder and start recording the current run. Ownership is taken.
// Passing nullptr detaches/stops recording.
void setRecorder(std::unique_ptr<ReplayRecorder> recorder);
// In replay mode, enqueue() is a no-op: the queue is driven by the recorded
// stream (ReplayPlayer), so stray live input produces nothing.
void setReplayMode(bool replayMode);
// Record a periodic RNG checksum if the current tick is on the checksum
// cadence. Call once per simulated tick (from the tick loop).
void recordTickCheckpoint();
private:
Simulation& m_simulation;
std::vector<std::shared_ptr<const Command>> m_queue;
std::unique_ptr<ReplayRecorder> m_recorder;
bool m_replayMode = false;
};