Files
gamedev-the-steward/docs/SIMULATION_STATE_SCHEMA.md
T
2026-07-11 11:27:40 +02:00

157 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# The Steward — Simulation State Schema
## Current contract
`SimulationStateRecord` is the versioned JSON boundary for the current
simulation. The current world schema is v5 and captures:
- simulation seed, tick interval, tick count, clock remainder, and elapsed
clock ticks;
- every NPC's identity, needs, attributes, task lifecycle, target, position,
starvation state, and decision RNG stream;
- village resource counters;
- controlled per-NPC wander RNG streams;
- scene-independent resource state plus the definition facts needed while its
ResourceNode is unloaded;
- pantry contents, carried NPC inventory, the ordered economic event stream,
and its next stable event ID;
- directed relationship records with familiarity, trust, and the stable event
ID that last changed trust;
- per-NPC known-event records that reference objective event history without
copying it.
The top-level identity is:
```json
{
"schema": "the_steward.simulation",
"schema_version": 5
}
```
Unknown schema versions and structurally incomplete records are rejected. Add
an explicit migration function before accepting a future version; never
silently reinterpret old data as the newest layout.
NPC action and profession references are serialized as stable strings and
restored as `StringName`. Records referencing unknown executable actions or
professions are rejected against the validated definition registry. See
[the simulation definitions](SIMULATION_DEFINITIONS.md).
## Deterministic continuation
RNG seeds and internal states are encoded as decimal strings. They are signed
64-bit values and cannot safely pass through every JSON number implementation
without precision loss. Converting them to ordinary JSON numbers caused a
restored simulation to retain the same visible state while silently changing
its future random sequence.
`tests/simulation_state_serialization_test.gd` protects this contract by:
1. running a fixed-seed scenario for 48 ticks without interruption;
2. running the same scenario for 24 ticks;
3. serializing and restoring into a fresh manager;
4. running the remaining 24 ticks;
5. requiring the complete final-state checksums to match.
It also verifies clock remainder, resource amount/reservation/enabled
round-tripping, presentation unload/rebind, directed relationship/cause
round-tripping, divergent known-event state, and rejection of unsupported
schemas.
NPCStateRecord v2 adds the resolved travel destination and whether it is
active. Nested v1 NPC records migrate explicitly with no invented active
destination. This allows a reloaded visual to resume travel without changing
target selection or deterministic RNG state.
NPCStateRecord v3 adds carried inventory. SimulationStateRecord v2 adds
StorageStateRecord entries; world-schema v1 migrates legacy village food and
wood into the stable `village_pantry` and `village_woodpile` records. Parsed
storage values are canonicalized so save/restore continuation retains
byte-stable checksums.
SimulationStateRecord v3 adds ordered `EconomicEventRecord` entries and
`next_event_id`. World schemas v1 and v2 migrate explicitly to an empty event
stream. Event records use stable source/destination/item IDs, reject invalid or
duplicate IDs, and preserve deterministic ordering across save/restore. See
[the economic event stream](ECONOMIC_EVENTS.md).
EconomicEventRecord v2 adds the authoritative world position captured when the
fact is recorded. Nested v1 events migrate with a zero-vector fallback; old
saves already encode their stable location IDs, and migrated knowledge is not
recomputed spatially.
SimulationStateRecord v4 moves social authority into top-level directed
`RelationshipStateRecord` entries. Each record contains `observer_id`,
`subject_id`, familiarity, trust, and `last_trust_cause_event_id` (`-1` when no
event has changed trust). Parsing requires unique ordered pairs, existing NPC
IDs, bounded relationship values, and a real event for every non-empty cause
ID.
The relationship system serializes records in stable observer/subject order so
they participate in deterministic checksums.
World schema v3 migrates its NPC-local familiarity pairs into directed
relationships with neutral trust. Nested NPC schema v4 removes that obsolete
duplicate field; v3 NPC records remain accepted only through the explicit
migration. World schemas v1v2 also derive the graph from any legacy
familiarity data (or initialize it empty) after their storage/event migrations.
SimulationStateRecord v5 adds top-level `KnownEventStateRecord` entries keyed
by `knower_id` and `event_id`. The objective `EconomicEventRecord` remains the
single completed fact; knowledge records only state which NPC knows it. Parsing
requires unique NPC/event pairs and valid references to both an existing NPC
and an event in the same record. A relationship's causal event must also be
known by that relationship's observer and, for the current trust contract, be
a food deposit performed by the relationship subject.
World schema v4 migrates relationship causes into known-event records. The old
schema used village-wide awareness, so this preserves the implied known fact
without falsely inventing spatial witness provenance. New v5 facts are created
only at event-record time from the actor and nearby NPC positions; historical
events are never re-evaluated against current positions.
## Resource authority
`SimulationManager` owns `ResourceStateRecord` instances independently of the
scene tree. Amount, reservation, enabled state, action/resource identity,
yield, usage permissions, and discovery metadata remain available and
serializable while no ResourceNode is loaded.
ResourceNode binds to a record by stable ID and provides presentation,
interaction transforms, and active-world discoverability. Unloading a node does
not delete its record. Loading another node with the same stable ID rebinds to
the existing record and cannot overwrite its amount or reservation with scene
defaults.
ResourceStateRecord v3 adds `safety_risk`, `comfort_distance`, and
`discovery_priority` so unloaded resources keep the same target-selection
meaning after save/restore. Nested v1 and v2 resource records are migrated
explicitly.
## Local quicksave boundary
`SaveSlotStore` writes the existing versioned JSON record to
`user://saves/quicksave.json`. It validates the serialized record before
replacement, limits file size, restricts slot names, preserves the previous
file during replacement, and can recover that backup if replacement is
interrupted. Loading parses and validates the complete record before mutating
the simulation.
In `main.tscn`, F5 saves and F9 loads this slot. Restoration rebuilds active NPC
visuals from authoritative state.
## Deliberately out of scope
This phase does not yet provide:
- a save-slot menu, metadata, thumbnails, autosaves, or multiple profiles;
- migrations from any historical world schema other than the explicitly
supported v1v4 layouts;
- player inventory or player relationship records;
- broader relationship dimensions, line-of-sight/hearing evidence,
acquisition provenance, memory decay, or communication;
- persistence for the player transform or presentation-only scene state.
Those features should build on this boundary rather than inventing parallel
serialization paths.