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:
@@ -24,6 +24,8 @@
|
||||
|
||||
class AiSystem;
|
||||
class BuildingSystem;
|
||||
struct Command;
|
||||
class Hasher;
|
||||
class CombatSystem;
|
||||
class DynamicBodySystem;
|
||||
class MovementIntentSystem;
|
||||
@@ -50,6 +52,12 @@ public:
|
||||
// Advances the simulation by one tick. Tick order per architecture.md §Tick Order.
|
||||
void tick();
|
||||
|
||||
// The single command chokepoint: applies one player command by dispatching
|
||||
// to the underlying mutators. Every sim mutation during play must flow
|
||||
// through here so it can be recorded and replayed (see docs/replay_design.md
|
||||
// and CommandManager). Reached via CommandManager::drain.
|
||||
void apply(const Command& command);
|
||||
|
||||
// Returns all fire events accumulated since the last drain, clearing the
|
||||
// internal queue. Call once per rendered frame (REQ-SHP-FIRING-BEAM).
|
||||
std::vector<BeamFiredEvent> drainBeamFiredEvents();
|
||||
@@ -60,12 +68,9 @@ public:
|
||||
// Returns true if there are pending schematic choices waiting for player input.
|
||||
bool hasSchematicChoicesPending() const;
|
||||
|
||||
// Applies the player's chosen schematic from the pending choices.
|
||||
// choiceIndex must be in [0, pendingChoices.size()).
|
||||
// Clears the pending choices after application.
|
||||
void applySchematicChoice(int choiceIndex);
|
||||
|
||||
Tick currentTick() const;
|
||||
// The seed this run was (re)initialized with; written to the replay header.
|
||||
unsigned int getSeed() const;
|
||||
int buildingBlocksStock() const;
|
||||
bool isGameOver() const;
|
||||
double threatLevel() const;
|
||||
@@ -88,16 +93,21 @@ public:
|
||||
bool isRecipeUnlocked(const std::string& recipeId) const;
|
||||
bool isItemUnlocked(const std::string& itemId) const;
|
||||
|
||||
// Checks affordability, deducts building blocks, and places the building.
|
||||
// Returns the new entity id, or kInvalidBuildingId if blocks are insufficient.
|
||||
BuildingId tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);
|
||||
// -- Determinism (see docs/replay_design.md) -----------------------------
|
||||
// 64-bit fingerprint of the RNG stream state. Cheap; written to the replay
|
||||
// file periodically + after each command for desync detection.
|
||||
unsigned long long rngFingerprint() const;
|
||||
|
||||
// Demolishes the building with the given id and refunds building blocks.
|
||||
void demolish(BuildingId id);
|
||||
// 64-bit fingerprint of the full simulation state (RNG, scalars, buildings,
|
||||
// belts, and ECS component state). Used by the double-run determinism test;
|
||||
// a superset of rngFingerprint().
|
||||
unsigned long long computeStateChecksum() const;
|
||||
|
||||
BuildingSystem& buildings();
|
||||
// Const subsystem accessors (queries only). The mutable counterparts are
|
||||
// private and reachable only through Simulation::apply (the command
|
||||
// chokepoint) or, in tests, SimulationTestAccess — so production code cannot
|
||||
// mutate the factory outside the recorded command path (docs/replay_design.md).
|
||||
const BuildingSystem& buildings() const;
|
||||
BeltSystem& belts();
|
||||
const BeltSystem& belts() const;
|
||||
ShipSystem& ships();
|
||||
const ShipSystem& ships() const;
|
||||
@@ -107,6 +117,29 @@ public:
|
||||
const EntityAdmin& admin() const;
|
||||
|
||||
private:
|
||||
// Grants tests access to the private player-action mutators below without
|
||||
// opening them to production code (see src/test/SimulationTestAccess.h).
|
||||
friend struct SimulationTestAccess;
|
||||
|
||||
// -- Player-action mutators (command chokepoint only) --------------------
|
||||
// Reached during play exclusively via apply(); never called by UI/app code.
|
||||
|
||||
// Checks affordability, deducts building blocks, and places the building.
|
||||
// Returns the new entity id, or kInvalidBuildingId if blocks are insufficient.
|
||||
BuildingId tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);
|
||||
|
||||
// Demolishes the building with the given id and refunds building blocks.
|
||||
void demolish(BuildingId id);
|
||||
|
||||
// Applies the player's chosen schematic from the pending choices.
|
||||
// choiceIndex must be in [0, pendingChoices.size()).
|
||||
// Clears the pending choices after application.
|
||||
void applySchematicChoice(int choiceIndex);
|
||||
|
||||
// Mutable subsystem accessors; same chokepoint rule as the mutators above.
|
||||
BuildingSystem& buildingsMutable();
|
||||
BeltSystem& beltsMutable();
|
||||
|
||||
void handleEvent(std::shared_ptr<const TracePrintRequestedEvent> event) override;
|
||||
|
||||
BuildingId allocateBuildingId(); // Strictly increasing; never returns kInvalidBuildingId.
|
||||
@@ -126,6 +159,7 @@ private:
|
||||
|
||||
GameConfig m_config;
|
||||
std::mt19937 m_rng;
|
||||
unsigned int m_seed;
|
||||
|
||||
Tick m_currentTick;
|
||||
Tick m_nextDepartureTick;
|
||||
@@ -149,6 +183,11 @@ private:
|
||||
std::map<std::string, SchematicState> m_schematicLevels;
|
||||
std::map<std::string, SchematicState> m_moduleSchematicLevels;
|
||||
|
||||
// Determinism helpers — fold sub-state into the hasher in deterministic order.
|
||||
static void appendSchematicMap(Hasher& hasher,
|
||||
const std::map<std::string, SchematicState>& levels);
|
||||
static void appendStringSet(Hasher& hasher, const std::set<std::string>& ids);
|
||||
|
||||
// Explicitly unlocked assembler recipe schematics (REQ-LOCK-EXPLICIT).
|
||||
std::set<std::string> m_unlockedRecipeSchematicIds;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user