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:
@@ -41,6 +41,8 @@
|
||||
#include "VisualsConfig.h"
|
||||
|
||||
struct Command;
|
||||
struct ParsedReplay;
|
||||
class ReplayPlayer;
|
||||
class Simulation;
|
||||
class QPainter;
|
||||
|
||||
@@ -68,7 +70,7 @@ class GameWorldView : public QOpenGLWidget,
|
||||
public:
|
||||
GameWorldView(Simulation* sim, const GameConfig* config,
|
||||
const VisualsConfig* visuals, const std::string& configDir,
|
||||
QWidget* parent = nullptr);
|
||||
const ParsedReplay* replay, QWidget* parent = nullptr);
|
||||
~GameWorldView() override;
|
||||
|
||||
double gameSpeed() const;
|
||||
@@ -121,6 +123,7 @@ private:
|
||||
void drawBeams(QPainter& painter);
|
||||
void drawOverlays(QPainter& painter);
|
||||
void drawScreenSpace(QPainter& painter);
|
||||
void drawReplayOverlay(QPainter& painter);
|
||||
|
||||
float tilePx() const;
|
||||
float viewportWidthTiles() const;
|
||||
@@ -178,6 +181,9 @@ private:
|
||||
CommandManager m_commandManager;
|
||||
// A Reset command was enqueued; reset the view after the next drain applies it.
|
||||
bool m_viewResetPending = false;
|
||||
// Non-null => view-only playback: ticks are driven by the recorded stream and
|
||||
// live input is ignored (the CommandManager is in replay mode).
|
||||
std::unique_ptr<ReplayPlayer> m_replayPlayer;
|
||||
|
||||
TickDriver m_tickDriver;
|
||||
QElapsedTimer m_frameTimer;
|
||||
|
||||
Reference in New Issue
Block a user