Files
gamedev-the-steward/docs/FEATURE_SAVE_SCHEMA_AND_MIGRATIONS.md
T
2026-08-23 17:20:41 +02:00

111 lines
5.2 KiB
Markdown

# Feature slice: saves, schema, migrations, and continuation
The local playable world and the regional proof use different persistence
boundaries. Both are primitive-only, versioned, deterministic, and validated
before mutation.
## Local save boundary
`SimulationStateRecord` is currently schema **v16**. It contains the local
simulation seed/clock scope, village/NPC/animal/resource/storage/player/
combatant/faction records, ordered economic events, relationships, knowledge,
legacy opportunities, situations, journal entries, commitments, and bounded
semantic conversation acts. `SimulationManager.create_state_record()` creates
it; `serialize_state()` emits canonical JSON; `restore_state()` preflights and
then atomically restores dependent systems.
Top-level v16 adds non-empty `world_id` and `location_id`. Activity success and
blocked facts carry the same scope. A save recorded under another active adapter
is rejected before clock, economy, NPC, or event state is mutated. This closes
the context-reinterpretation hole where equal local coordinates could be
treated as the same world.
The record stores IDs, primitive values, event IDs, and semantic topic IDs. It
never stores scripts, nodes, `NodePath`s, callables, imported assets, dialogue
prose, or transient HUD/balloon state.
## Migration policy
Nested records migrate at their own version; the top-level schema accepts an
explicit historical range and normalizes old fields before cross-record
validation. Current supported migrations include:
- legacy village/storage and event streams;
- directed relationships and causal knowledge provenance;
- opportunity history and its v2 lifecycle;
- animal routine/definition IDs;
- player citizen needs/health;
- combatants, factions, and enemy definition IDs;
- situations, journal entries, commitments, and conversation acts;
- world/location scope v16;
- explicitly marked legacy unstructured `task_blocked` facts.
Old definition values are not snapshotted. The current build supplies balance
and handler definitions while mutable saved state is preserved. Unknown future
schema versions fail closed until a migration exists.
## Validation invariants
Restore checks include:
- unique IDs and next-ID cursors above restored history;
- valid definition/action/item/storage references;
- actor/target/cost/effect contracts for activity facts;
- animal reservations and feed event agreement;
- relationship causes pointing at valid known events;
- knowledge provenance and one-hop communication rules;
- situation/journal/commitment references and statuses;
- scope consistency for the event log and activity facts;
- no future event tick or forged actor/source/destination fields.
The test suite deliberately removes derived knowledge/opportunity state and
tamper-tests facts, so validation does not rely on a secondary projection to
catch corruption.
## Local file safety
`SimulationSaveManifest` v1 combines the unchanged local v16 record and the
canonical `RegionalSimulationFacade` envelope. It stores an independent
checksum for each authority and cross-validates world, seed, active location,
and processed tick. A rejected manifest cannot partially replace local or
regional state.
`SaveSlotStore` validates the complete manifest before replacement, restricts
slot names and size, preserves a previous file, and can recover a backup after
an interrupted replacement. It also accepts existing raw local v16 files and
bootstraps an idle regional facade at the restored seed and tick. Explicit
non-Jajce adapter scopes continue writing raw local v16 records until a
matching regional facade is implemented. This fallback is permitted only while
the regional facade equals its deterministic idle bootstrap; a local-only save
fails closed rather than discarding live regional state. `F5`/`F9` use the
current quicksave path in `main.tscn`. The player transform and presentation-only
scene state are intentionally not saved.
## Regional persistence boundary
`RegionalChunkedPersistence` and `RegionalChunkedFileStore` are the regional
counterpart. They serialize a `RegionalCaravanService` envelope into global,
location, mobile-group, scheduler, and event-segment chunks, with manifest and
per-file checksums. The file store uses immutable generations, a current
pointer, recovery scanning, and conservative pruning. Partial active-location
loads are metadata-only and cannot silently resurrect a removed location.
This regional chunk codec is not yet the backend for `SaveSlotStore`. The
production facade and combined quicksave manifest are complete; retained raw
events, rollups, chunk generations, and lazy context loading still need one
atomic long-horizon publication contract.
## How to change state safely
1. Add the field to the narrow nested record first.
2. Define `SCHEMA_VERSION`, validation, canonical `to_dictionary()`, and an
explicit migration from the prior version.
3. Update top-level construction/restore/checksum only after the nested record
round-trips.
4. Add malformed/tamper, old-save migration, save/load, and deterministic
continuation tests.
5. Update [SIMULATION_STATE_SCHEMA.md](SIMULATION_STATE_SCHEMA.md) and this
guide with the actual version and compatibility range.
The full field-by-field history remains in [Simulation State Schema](SIMULATION_STATE_SCHEMA.md).