extends Node ## Save/load — version field, file IO, save-between-ticks contract. ## ## Saves only happen between sim ticks (Sim owns the loop). JobRunner mid-toil ## state must round-trip from day one — see docs/architecture.md "Save format". ## ## Phase 3: pawn states (including mid-walk + JobRunner) round-trip via ## Pawn.to_dict() / Pawn.from_dict() — gated by has_method() so the pre-Phase-3 ## Pawn (which lacked the methods) didn't break here. ## Phase 16 expands to tilemap data, storyteller state, items, furniture, etc. const SAVE_VERSION: int = 1 const SAVE_PATH: String = "user://save_slot.json" func write_save() -> bool: var pawn_dicts: Array = [] for p in World.pawns: if p.has_method("to_dict"): pawn_dicts.append(p.to_dict()) var payload := { "version": SAVE_VERSION, "sim_tick": Sim.tick, "game_state": GameState.save_dict(), "pawns": pawn_dicts, } var f := FileAccess.open(SAVE_PATH, FileAccess.WRITE) if f == null: push_error("SaveSystem.write_save: cannot open %s" % SAVE_PATH) return false f.store_string(JSON.stringify(payload)) Audit.log("save", "wrote %d pawns at tick %d" % [pawn_dicts.size(), Sim.tick]) return true func read_save() -> Dictionary: if not FileAccess.file_exists(SAVE_PATH): return {} var f := FileAccess.open(SAVE_PATH, FileAccess.READ) if f == null: return {} var raw := f.get_as_text() var parsed: Variant = JSON.parse_string(raw) if typeof(parsed) != TYPE_DICTIONARY: push_error("SaveSystem.read_save: corrupt save") return {} if int(parsed.get("version", 0)) != SAVE_VERSION: push_warning( "SaveSystem.read_save: version mismatch (%s vs %s)" % [parsed.get("version", "?"), SAVE_VERSION] ) return parsed ## Apply a previously-loaded save payload onto live world state. ## Pawn dicts are zipped against World.pawns by index — Phase 3 simplicity; ## Phase 16 will introduce stable pawn IDs. func apply_save(payload: Dictionary) -> void: if payload.has("sim_tick"): Sim.tick = int(payload["sim_tick"]) if payload.has("game_state"): GameState.apply_dict(payload["game_state"]) var pawn_dicts: Array = payload.get("pawns", []) var n: int = min(pawn_dicts.size(), World.pawns.size()) for i in n: var p = World.pawns[i] if p.has_method("from_dict"): p.from_dict(pawn_dicts[i]) Audit.log("save", "applied %d pawns at tick %d" % [n, Sim.tick])