111 lines
5.2 KiB
Markdown
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).
|