Skip to content

Fix determinism & entity-loss bugs in engine state reconstruction - #110

Merged
thegalah merged 1 commit into
masterfrom
fix/engine-state-reconstruction-determinism
Jul 25, 2026
Merged

Fix determinism & entity-loss bugs in engine state reconstruction#110
thegalah merged 1 commit into
masterfrom
fix/engine-state-reconstruction-determinism

Conversation

@thegalah

Copy link
Copy Markdown
Contributor

The state-reconstruction path (backing evaluateNextState, the forward-model API used for training/lookahead) carried several correctness bugs that made reconstructed ticks non-reproducible and dropped entity fields on reload.

  • Deterministic PRNG seed on state reload: replace Math.floor(Math.random() * Math.random() * (10 ^ 6)) — which used the unseeded global RNG and where 10 ^ 6 is XOR (=12), not 1e6 — with a deterministic FNV-1a hash over tick + layout. Seed is derived from state alone (NOT config.PrngSeed, which getConfig() randomises per call), so identical (state, actions) inputs reproduce identical output.
  • Powerups no longer lose created/expires on reload: add createdOverride to FreezePowerupEntity/BlastPowerupEntity and thread entity.created through reconstructEntity.
  • FreezePowerup now expires off FreezePowerupDurationTicks, not BlastPowerupDurationTicks.
  • Bombs reconstruct with their serialised blast_diameter instead of an arbitrary owner unit's current diameter.

Tests: add a determinism check (forced spawn with multiple empty cells so location and type depend on the PRNG) and powerup/bomb round-trip assertions.

Known limitation: guarantees same input -> same output, but does not reproduce the original game's exact PRNG stream position (would require serialising PRNG state into IGameState — a wire-schema change, out of scope).

The state-reconstruction path (backing evaluateNextState, the forward-model
API used for training/lookahead) carried several correctness bugs that made
reconstructed ticks non-reproducible and dropped entity fields on reload.

- Deterministic PRNG seed on state reload: replace
  `Math.floor(Math.random() * Math.random() * (10 ^ 6))` — which used the
  unseeded global RNG and where `10 ^ 6` is XOR (=12), not 1e6 — with a
  deterministic FNV-1a hash over tick + layout. Seed is derived from state
  alone (NOT config.PrngSeed, which getConfig() randomises per call), so
  identical (state, actions) inputs reproduce identical output.
- Powerups no longer lose created/expires on reload: add createdOverride to
  FreezePowerupEntity/BlastPowerupEntity and thread entity.created through
  reconstructEntity.
- FreezePowerup now expires off FreezePowerupDurationTicks, not
  BlastPowerupDurationTicks.
- Bombs reconstruct with their serialised blast_diameter instead of an
  arbitrary owner unit's current diameter.

Tests: add a determinism check (forced spawn with multiple empty cells so
location and type depend on the PRNG) and powerup/bomb round-trip assertions.

Known limitation: guarantees same input -> same output, but does not
reproduce the original game's exact PRNG stream position (would require
serialising PRNG state into IGameState — a wire-schema change, out of scope).
@thegalah
thegalah merged commit 02ff438 into master Jul 25, 2026
1 check passed
@thegalah
thegalah deleted the fix/engine-state-reconstruction-determinism branch July 25, 2026 15:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant