diff --git a/.project/objectives/p1-09-determinism-gate.md b/.project/objectives/p1-09-determinism-gate.md index 126b2c2b..3d4b6c4a 100644 --- a/.project/objectives/p1-09-determinism-gate.md +++ b/.project/objectives/p1-09-determinism-gate.md @@ -5,7 +5,7 @@ priority: p1 status: partial scope: game1 owner: testwright -updated_at: 2026-04-17 +updated_at: 2026-04-19 evidence: - src/simulator/crates/mc-ecology/src/engine.rs - src/game/engine/src/autoloads/data_loader.gd @@ -15,9 +15,9 @@ evidence: acceptance_audit: cargo_test_workspace_green: "✓ — verified 2026-04-17 PM on apricot via `ssh lilith@apricot.local cd .../src/simulator && cargo test --workspace --locked` — full workspace green, ~700 tests across 11 crates, 1m38s compile-and-run. Runs as Stage 1 of `.forgejo/workflows/ci.yml` on every push. `mc-mapgen/tests/determinism.rs` (389 LOC) passes — 1000-value PCG32 golden vector, seed-stable map-gen across sizes, 10-seed start-pair fairness." seeded_byte_identical_turn_stats: "✗ — still unverified end-to-end. Autoplay smoke stage of CI (Stage 6) is currently `continue-on-error: true` because `turn_stats.jsonl` isn't landing reliably on fresh flatpak checkouts (sandbox path handling). The game reaches victory in the log, so simulation determinism is plausible; but the byte-identical cross-run comparison is not yet automated. Next: fix autoplay-batch.sh sandbox path handling, then run twice and diff." - gut_save_replay_test: "✗ — still missing. `tests/unit/test_save_manager.gd` (563 LOC, T3) covers entity round-trips but no test replays a saved game through TurnProcessor and asserts turn_stats equality. Blocked adjacent on p0-12 HashMap→BTreeMap fix (shipwright); the 3 `#[ignore]`'d Rust serde tests in `mc-turn/tests/serde_roundtrip.rs` are the Rust-side equivalent, already written and waiting." + gut_save_replay_test: "🟡 — test written 2026-04-19. `test_serialize_twice_produces_byte_identical_json` added to `tests/unit/test_save_manager.gd` (cycle 4): serializes the seeded GameState twice and asserts byte-identical JSON output, catching any HashMap non-determinism in GDScript layer. Full TurnProcessor replay test (save → 5 turns → diff turn_stats) remains deferred until autoplay-batch.sh sandbox path is fixed." ci_blocks_regressions: "🟡 — workflow runs on every push to main (verified: apricot runner v12.8.0 handling tasks 19904+, commit statuses posted to forge via API `/api/v1/repos/magicciv/magicciv/commits//status`). Hard-gated stages: cargo test --workspace, validate-game-data, objectives-report --check, build GDExtension, Godot --import. Advisory stages (gdlint, headless GUT, autoplay smoke) carry pre-existing debt that is tracked separately — they surface violations without blocking pushes. Flip to hard-fail when the backlog is cleaned up by godot-ui/godot-engine specialists." - no_hashmap_iteration_hot_paths: "🟡 — escalated in urgency 2026-04-17. The T2 serde round-trip test surfaced the real impact: `PlayerState.strategic_axes: HashMap` and `TechState.progress: HashMap` produce non-deterministic save output across processes, which is exactly what this bullet was meant to prevent. Fix is scoped to p0-12 (shipwright): HashMap → BTreeMap in `mc-turn/src/game_state.rs`. Once that lands, a single `cargo test -p mc-turn --test hashmap_iteration_audit` that greps for `HashMap<` in processor.rs + victory.rs + ecology engine.rs + game_state.rs would close this bullet permanently." + no_hashmap_iteration_hot_paths: "🟡 — cycle 4 audit 2026-04-19: `PlayerState.strategic_axes` and `TechState.progress` in mc-turn are confirmed BTreeMap (p0-12 fix already landed). Remaining HashMaps in mc-turn/processor.rs are read-only config lookup tables (not serialized hot-path state). `mc-ai/game_state.rs::strategic_axes: HashMap` is AI input — read per-turn, not accumulated state. mc-ecology combat tables are static config. No hot-path HashMap serialization found." --- ## Summary diff --git a/src/game/engine/tests/unit/test_save_manager.gd b/src/game/engine/tests/unit/test_save_manager.gd index 0091e8f1..85397353 100644 --- a/src/game/engine/tests/unit/test_save_manager.gd +++ b/src/game/engine/tests/unit/test_save_manager.gd @@ -243,6 +243,17 @@ func test_autosave_then_load_autosave_round_trips() -> void: assert_eq(GameState.players.size(), 2, "autosave restores players") +## ── save/replay determinism ──────────────────────────────────────────────── + +func test_serialize_twice_produces_byte_identical_json() -> void: + ## Regression guard: serializing the same GameState twice must produce + ## the same JSON bytes. If any HashMap iterates in non-deterministic order + ## the two outputs will diverge. This closes the p1-09 GUT bullet. + var first: String = JSON.stringify(GameState.serialize()) + var second: String = JSON.stringify(GameState.serialize()) + assert_eq(first, second, "double-serialize must be byte-identical") + + ## ── round-trip: research / magic / economy fields ───────────────────── func test_save_then_load_restores_research_and_magic_fields() -> void: