godot-save-load-format-mismatch
I produced ~/SOLUTION.md — a self-contained fix document with a title, root-cause analysis, the exact code changes, and a verification section.
The production autosave wrote an unloadable flat format because TimeManager invoked the legacy GameState.save() writer instead of the canonical SaveManager.save_game(). SaveManager.load_game() validates for the nested document and fails with Missing game_state field. A secondary defect left the derived owned_territories/player_territories ownership index empty after load.
The fix has three parts:
1. Route both autosave and quick-save through SaveManager.save_game() (never GameState.save() directly).
2. Rebuild the derived territory ownership index inside load_game() (and the owner-coordinate map) after deserialization — since ownership is derived, not stored.
3. Optional back-compatible migration so legacy flat autosaves already written in production load correctly instead of hard-failing.
Verification is gated on the real save path (as the commit lesson demands): an integration test that triggers a real on-disk autosave and then reloads it through SaveManager.load_game(), plus a matching negative/repro test, a manual smoke procedure, and a diff audit to confirm no stray GameState.save() call sites remain.
I could not execute the fix against the actual project: the escalation-doctrine / Godot source tree is not present anywhere on this machine (I searched the filesystem for .gd files, SaveManager*, autosave/doctrine directories — all empty), and GitHub code search requires auth. So the solution is specified as exact, ready-to-apply GDScript changes against the architecture you described, with a verification procedure to run once the repo is mounted. The changes are deliberately anchored to the files and call sites named in the problem (TimeManager, SaveManager.load_game()/save_game(), GameState.save()), so they slot directly into your tree.
# Evidence - Problem class: godot-save-load-format-mismatch - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-21T17:45:25.470Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Godot 4.3 game: production autosave wrote a legacy flat JSON format (14 top-level scalars, no game_state key, no world data) because TimeManager called GameState.save_game() while SaveManager.load_game() validates a nested format and fails with Critical validation errors: Missing game_state field. Fix: route autosave/quick-save through SaveManager.save_game() which serializes game_state/territories/factions/economy/research; also rebuild derived territory ownership index after load (owned_territories/player_territories were left empty).", "environment": "production", "language": "gdscript", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "godot-save-load-format-mismatch", "provider": "openrouter", "solved_at": "2026-08-21T17:45:25.471Z", "version": "godot 4.3"}I produced ~/SOLUTION.md — a self-contained fix document with a title, root-cause analysis, the exact code changes, and a verification section.
The production autosave wrote an unloadable flat format because TimeManager invoked the legacy GameState.save() writer instead of the canonical SaveManager.save_game(). SaveManager.load_game() validates for the nested document and fails with Missing game_state field. A secondary defect left the derived owned_territories/player_territories ownership index empty after load.
The fix has three parts:
1. Route both autosave and quick-save through SaveManager.save_game() (never GameState.save() directly).
2. Rebuild the derived territory ownership index inside load_game() (and the owner-coordinate map) after deserialization — since ownership is derived, not stored.
3. Optional back-compatible migration so legacy flat autosaves already written in production load correctly instead of hard-failing.
Verification is gated on the real save path (as the commit lesson demands): an integration test that triggers a real on-disk autosave and then reloads it through SaveManager.load_game(), plus a matching negative/repro test, a manual smoke procedure, and a diff audit to confirm no stray GameState.save() call sites remain.
I could not execute the fix against the actual project: the escalation-doctrine / Godot source tree is not present anywhere on this machine (I searched the filesystem for .gd files, SaveManager*, autosave/doctrine directories — all empty), and GitHub code search requires auth. So the solution is specified as exact, ready-to-apply GDScript changes against the architecture you described, with a verification procedure to run once the repo is mounted. The changes are deliberately anchored to the files and call sites named in the problem (TimeManager, SaveManager.load_game()/save_game(), GameState.save()), so they slot directly into your tree.
# Evidence - Problem class: godot-save-load-format-mismatch - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-21T17:45:25.470Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Godot 4.3 game: production autosave wrote a legacy flat JSON format (14 top-level scalars, no game_state key, no world data) because TimeManager called GameState.save_game() while SaveManager.load_game() validates a nested format and fails with Critical validation errors: Missing game_state field. Fix: route autosave/quick-save through SaveManager.save_game() which serializes game_state/territories/factions/economy/research; also rebuild derived territory ownership index after load (owned_territories/player_territories were left empty).", "environment": "production", "language": "gdscript", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "godot-save-load-format-mismatch", "provider": "openrouter", "solved_at": "2026-08-21T17:45:25.471Z", "version": "godot 4.3"}