replay: add determinism foundation and double-run verification (Phase 0)
Introduces the state-checksum machinery the replay feature rests on, and a test that proves the simulation is deterministic for a given seed/binary. - StateChecksum: FNV-1a Hasher (bit-pattern float hashing, -0 normalization, length-tagged strings) plus a portable mt19937 state fingerprint. - BeltSystem/BuildingSystem: appendChecksum(Hasher&) folding transport and building/site/occupancy state in deterministic (sorted/insertion) order. - Simulation: rngFingerprint() (cheap, for the future file checksum) and computeStateChecksum() (full state: RNG, scalars, wave/schematic/unlock state, subsystems, and ECS position/health/facing/body/scrap/identity). - DeterminismTest: Hasher unit tests, RNG-fingerprint tests, and a double-run test asserting identical per-tick full-state checksums from one seed; plus a different-seed divergence guard. Exit criteria met: double-run determinism test passes; full suite green (334 cases / 3346 assertions). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DUsFgd2Ga6pmLz8giS8WUn
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
|
||||
class AiSystem;
|
||||
class BuildingSystem;
|
||||
class Hasher;
|
||||
class CombatSystem;
|
||||
class DynamicBodySystem;
|
||||
class MovementIntentSystem;
|
||||
@@ -88,6 +89,16 @@ public:
|
||||
bool isRecipeUnlocked(const std::string& recipeId) const;
|
||||
bool isItemUnlocked(const std::string& itemId) const;
|
||||
|
||||
// -- 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;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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);
|
||||
@@ -149,6 +160,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