# The Steward — Simulation State Schema ## Current contract `SimulationStateRecord` is the versioned JSON boundary for the current simulation. The current world schema is v14 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; - scene-independent animal identity, position, hunger, feeding history, reservation, availability, current routine site, active travel destination, and next routine tick; - 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, including first-acquisition method, acquisition tick, and historical communicator provenance; - opportunity records with stable type/status, interested NPC, trigger event, target storage/resource/amount, exact later resolution event identity, or a deterministic invalidation reason and close tick; - a player record holding the player-citizen's hunger, energy, and carried inventory. The player's world position remains presentation-owned and is intentionally not serialized; - combatant records (health, weapon, faction, position, hostility) for NPC defenders and standalone raiders/wolves; - faction records (food, warriors, aggression, morale, stance, war plan) for the village and the hill tribe. The top-level identity is: ```json { "schema": "the_steward.simulation", "schema_version": 14 } ``` 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, communicated provenance, and deterministic retention boundaries, and rejection of unsupported schemas. Active, resolved, and invalidated opportunity round-tripping, contradictory target state, invalid resource sources, and cross-record event references are covered too. 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. NPC inventory IDs and amounts are canonicalized to strings and floats; empty or colliding normalized IDs, non-numeric values, and negative or non-finite amounts are rejected. Parsed storage values are likewise canonicalized so save/restore continuation retains byte-stable checksums. Current storage parsing rejects empty item IDs, negative or non-finite amounts, negative or non-finite capacity, and contents above capacity. Runtime deposit and withdrawal requests also reject empty item IDs and non-finite amounts without mutating authoritative stock. When the authored pantry or woodpile is present, current world parsing also requires its authoritative food or wood amount to match the duplicated village summary; contradictory saves are rejected instead of silently changing checksum during restore. World-v1 migration expands a storage's inferred capacity when needed so valid legacy stock above the current default is preserved rather than discarded. The compound NPC `feed_animal` route needs no additional saved phase. An in-progress record already contains the exact animal `target_id`, matching animal reservation, carried inventory, task state, and active travel destination. Insufficient carried food plus a pantry destination is the pickup leg; the definition cost in inventory plus the animal destination is the delivery leg. Restore emits only the saved travel request and never repeats pickup. A compatible pre-delivery v11 feed record may instead have empty inventory and the claimed animal as its destination; arrival redirects that same claim through the current pantry waypoint before any withdrawal. If that older record is already working, its first resumed tick performs the same reroute and carries its saved work progress back to the goat. Successful pickup changes the saved destination to the exact animal before publishing synchronous inventory, event, or village signals, so signal-time saves cannot encode carried food with a stale pantry destination. 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 v1–v2 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. SimulationStateRecord v6 / KnownEventStateRecord v2 adds immutable first-acquisition provenance: `performed`, `witnessed`, `communicated`, or `legacy`. Communicated records also retain the stable NPC ID of the direct speaker. New direct facts are created only at event-record time from the actor and nearby NPC positions; historical events are never re-evaluated against current positions. A co-located shared-activity encounter can add one communicated reference to the original fact without appending or replaying the event. World schema v5 migrates actor-owned knowledge to `performed`; non-actor knowledge becomes `legacy` because v5 may contain either a proximity witness or an older implied relationship fact. Parsing never invents that missing provenance. At runtime, communication requires a distinct source who currently knows the event through `performed` or `witnessed` acquisition. Persisted records validate that direct-method snapshot even after the source forgets. This enforces the current one-hop boundary and makes validation independent of array ordering. SimulationStateRecord v7 / KnownEventStateRecord v3 adds `acquired_tick` and a snapshot of the direct speaker's acquisition method. Direct facts use the objective event tick; newly communicated facts use the conversation tick. The speaker snapshot keeps historical provenance valid even if the speaker later forgets their own copy, while runtime communication still requires a currently known performed/witnessed fact and remains one-hop. World schema v6 migrates direct acquisition to the objective event tick. Communicated and ambiguous legacy facts receive a fresh retention lease at the saved simulation tick because their exact acquisition time was not previously stored. The v6 source record supplies the persisted direct-method snapshot. Importance is intentionally derived rather than duplicated: a fact is `lasting` while it is the current trust cause for one of that knower's directed relationships; every other fact is `recent`. Current causal facts are protected so relationship state remains explainable. Each NPC keeps at most three recent facts, and recent facts at least one simulated day old are removed at the next deterministic daily-sized review. The objective event stream is never pruned by this rule. SimulationStateRecord v8 adds the top-level `opportunities` array and `simulation.next_opportunity_id`. The first nested `OpportunityStateRecord` schema stores `opportunity_id`, `opportunity_type`, `status`, `created_tick`, `trigger_event_id`, `interested_npc_id`, `target_id`, `resource_id`, `target_amount`, `resolution_event_id`, and `resolved_tick`. World schemas v1–v7 migrate explicitly to an empty opportunity list with next ID zero. SimulationStateRecord v9 preserves world-v8 opportunity history and upgrades the nested record to `OpportunityStateRecord` v2. The nested schema adds `closed_tick` and `invalidation_reason`, accepts `supply_missing_wood`, and adds the stable `invalidated` status. Nested opportunity v1 records normalize their resolved tick into the close tick and remain valid pantry history. Economic event schema v3 adds normalized `action_id` and `required_amount` fields; nested v1/v2 events receive empty/zero defaults, while a new `task_blocked` event requires its stable action, source storage, resource, and positive requirement contract. SimulationStateRecord v10 adds the top-level `animals` array and the first `AnimalStateRecord` schema. Each animal record stores its stable animal and species IDs, display name, authoritative world position, hunger, `last_fed_tick`, reservation owner, and enabled/player/NPC feeding flags. World schema v9 preserves opportunity history while migrating to an empty animal list. SimulationStateRecord v11 preserves world-v10 animals and upgrades nested `AnimalStateRecord` v1 to v2. The nested schema adds `routine_site_id`, `travel_target_site_id`, exact `travel_target_position`, `has_travel_target`, and `next_routine_tick`. Migrated animals receive an idle, immediately due routine without inventing a destination; loaded-world selection establishes the first real target later. SimulationStateRecord v12 lets `RelationshipStateRecord` reference the player sentinel (`PLAYER_ACTOR_ID`) as a subject, so a villager's directed trust can point at the player. World v11 migration adds nothing but accepts the relaxed validation; existing NPC-only relationship history is preserved. Player-triggered food supply is recorded as a pantry `storage_deposited` from the player inventory (or, for legacy v11 saves, as a direct harvest into the pantry). SimulationStateRecord v13 adds the top-level `player` record (`PlayerStateRecord` v1) with the player-citizen's hunger, energy, and carried inventory. World schemas v12 and earlier migrate to a fresh default player record (hunger 40, energy 100, empty inventory). Player needs advance on the deterministic tick with exact binary fractions so continuation checksums stay stable. Resource records also migrate to v4, adding `max_amount` and `regrow_rate`; legacy v3 resources keep their current amount as the cap and gain no regrowth. SimulationStateRecord v14 adds the top-level `combatants` and `factions` arrays. `CombatantStateRecord` v1 tracks health, equipped weapon, faction, hostile flag, and position for NPC defenders, tribe raiders, and wolves. `FactionStateRecord` v1 tracks food, warriors, aggression, morale, stance, and the tribe's war plan with its confidence. World schemas v13 and earlier migrate to empty combatant lists plus default village/tribe factions; the manager registers living NPC combatants on restore. Combatant positions are quantized to a binary-exact grid and faction food to a binary-exact step so that arbitrary world floats round-trip through JSON without breaking deterministic continuation checksums. Current-schema parsing requires unique animal IDs that do not collide with resource or storage IDs. An animal reservation must belong to an existing NPC whose active feed task targets that exact animal. Every `animal_fed` event must name an existing animal as its destination, food as its item, the exact feed action cost, and a valid NPC actor or the player sentinel. New NPC events name the matching `npc__inventory`; player events name the pantry. Direct pantry-sourced NPC facts remain accepted as legacy-compatible v11 history, while runtime emits matching-inventory sources for new NPC deliveries. The latest matching feed event tick must agree with the animal's `last_fed_tick`. The bounded opportunity family accepts at most one open record globally. Opportunity, trigger-event, and resolution-event IDs are unique and the next ID must remain above restored history. Referenced NPCs, storage, resources, and events must exist. A `restock_empty_pantry` trigger is a positive NPC food withdrawal from `village_pantry` to that actor's matching inventory; an open record additionally requires the pantry to remain below its one-food target and its interested NPC to have acquired that fact no later than creation. A `supply_missing_wood` trigger is the performer's known patrol or study `task_blocked` fact against `village_woodpile` with a one-wood requirement; an open record additionally requires a living interested performer and the woodpile below target. Resolution references an exact later matching NPC inventory-to-storage deposit or player `resource_extracted` event from an existing player-usable matching resource into the target storage, with matching resolved and closed ticks. Resolved history does not require the storage still to contain the resource because later use is valid. Invalidation has no resolution event and requires a stable close tick and is currently valid only for `supply_missing_wood`. `interested_died` must agree with the restored NPC's death state; the runtime creates `evidence_stale` at the one-day unresolved boundary. While a need is open, its interested villager's trigger evidence is treated as lasting during deterministic memory maintenance. Resolution or invalidation releases that fact back to the normal bounded-retention rules; neither deletes objective event or opportunity history. `OpportunityHelperResult` and `OpportunityPlayerResponseResult` are deliberately absent from this schema. The helper result is re-derived from the open opportunity plus current NPC knowledge, directed relationship, inventory, action-definition, storage-capacity, and finite-resource records. The player result additionally requires no capable helper and derives its ordinary gather action, real destination, and source count from current enabled player-usable finite resources. Save/restore regressions require the same applicable route result and an unchanged checksum before and after querying. When an idle derived helper chooses the reported supply action, only the existing NPC task, task-state, target, travel, and reservation fields serialize. The helper result remains absent: restore retains any in-progress ordinary task and independently re-derives current capability from restored facts. Active-need communication adds no schema fields. It persists through the existing `KnownEventStateRecord` event, listener, source NPC, direct source method, and acquisition-tick fields. Communication and player-response HUD animations are transient and are cleared rather than replayed after restore. ## 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. Current-schema parsing also canonicalizes numeric, boolean, and ID variants so a freshly registered finite source retains a byte-stable checksum after a JSON round trip. ## Animal authority `AnimalCareSystem` owns `AnimalStateRecord` instances independently of the scene tree and advances hunger and due routine selection in stable-ID order. `AnimalNode` binds a loaded presentation and interaction point to that authority; the simulation position and selected destination remain canonical while the node is loaded or absent. Animals are not `ResourceNode` instances and are not inserted into the resource discovery grid. The first bounded slice uses a deterministic linear loaded-animal query. NPC and player feeding resolve through the same exact animal mutation but pay from their real current source. Player care withdraws directly from `village_pantry`. NPC care reserves the animal, performs an exact pantry-to-inventory pickup at the authored storage waypoint, carries that unit to the same claim, and consumes inventory only at delivery. Failed or partial pickup changes neither inventory nor animal history. The first routine consumers alternate Dunja and Zora between stable-ID private shelters and one shared pasture without drawing RNG. `AnimalRoutineSite` declares optional loaded-world resident ownership: an empty resident ID is shared species context, while a named resident makes a shelter private. `ActiveWorldAdapter` filters those loaded markers for each stable animal; site ownership is not persistent state and does not require a schema bump. The selected site ID and exact position do enter state, and each `AnimalNode` follows a real `NavigationServer3D` path while reporting factual position changes. A mid-route save restores the same position and target; unloading a scene node pauses presentation only, and reloading it resumes from those facts. Hunger cancels travel before care targeting. Multiple animal records remain independently authoritative. A schema-v11 save may contain concurrent feed reservations only when each reserving NPC targets that exact animal in a valid travel or work state. Restoring Dunja and Zora preserves both claim pairings and the checksum. The same existing NPC fields preserve either pantry or goat travel plus carried food, so a post-pickup restore continues without another withdrawal or RNG draw. Completing one feed releases and mutates only its destination record. The persistent hungry/content presentation derives from authoritative hunger. The short feed response is transient and is reset rather than serialized or replayed after restore. ## 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 v1–v10 layouts; - player inventory or player relationship records; - broader relationship dimensions, line-of-sight/hearing evidence, continuous or personalized memory decay, reinforcement, false beliefs, or multi-hop rumours; - generalized opportunity/quest definitions, acceptance, rewards, assignment, dialogue, or player-facing quest-log state; - persistence for the player transform or presentation-only scene state. Those features should build on this boundary rather than inventing parallel serialization paths.