Files
gamedev-the-steward/docs/ECONOMIC_EVENTS.md
T
2026-07-16 12:13:54 +02:00

9.9 KiB
Raw Blame History

The Steward — Economic Event Stream

Current contract

Every successful food or wood movement creates an EconomicEventRecord using the applicable event type:

resource_extracted
storage_deposited
storage_withdrawn
item_consumed

Each record contains a monotonically increasing event ID, event type, simulation tick, actor ID, source ID, destination ID, item ID, and transferred amount, plus the authoritative world position captured when the fact is recorded. NPC events preserve their authoritative interaction target (or NPC position when no target applies); player extraction/depletion events preserve the ResourceNode interaction position. Actor -1 identifies a player-triggered extraction until persistent player identity is introduced.

Events are immutable facts about completed transfers. They do not perform the transaction and are not replayed to reconstruct current state. Resource, inventory, and storage records remain authoritative.

SimulationEventLog owns ordered event identity, append/restore behavior, and history/rate queries, including exact lookup through get_by_id(). VillageEconomy performs transactions and requests event records only after state changes succeed; SimulationManager remains the public signal boundary used by presentation.

An action whose definition-backed completion cost becomes unavailable records a zero-amount task_blocked narrative fact with stable action, source storage, resource, and required-amount fields plus the readable shortfall reason. This makes late contention queryable and inspectable without pretending that a transfer occurred.

Persistence and determinism

SimulationStateRecord schema v9 stores the ordered event stream, next_event_id, directed relationships that may reference an exact event, and per-NPC known-event references with first-acquisition provenance, plus opportunity records that reference exact trigger/resolution events. Schema v1 and v2 saves migrate to an empty stream beginning at ID zero; world schemas v1v7 migrate to an empty opportunity list, while world schema v8 preserves and normalizes its pantry opportunity history. Parsing rejects duplicate event IDs, invalid or duplicate knowledge/opportunity references, impossible communicator sources, relationship causes the observer does not know, and next IDs that could collide with restored history.

Positive food deposits, positive NPC food withdrawals from village_pantry into that actor's matching inventory, and definition-backed patrol/study task_blocked facts caused by missing wood at village_woodpile are currently knowable. The actor and living NPCs within the bounded witness radius receive a KnownEventStateRecord at record time. Witness distance uses the event's captured position, never the actor's later location. A successful food deposit can raise a hungry familiar witness's directed trust; the relationship stores that same deposit event ID rather than copied prose, so the inspector can resolve and display the real completed fact. This is evidence-gated use of the event stream, not general event sourcing.

Knowledge now records whether an NPC performed, witnessed, heard, or inherited a fact from a legacy save. When an NPC arrives beside an already-working NPC at the same non-storage activity site, the worker may communicate one newest direct fact within 2.5 metres. The listener references the same immutable event and retains the speaker ID; no transaction or event is replayed. Only performed/witnessed facts can cross this one social hop, so this is not yet a rumour cascade. Relationship appraisal uses the listener's state when the fact is acquired, not a reconstruction of their needs when the old event occurred.

Known facts are now bounded without deleting objective history. A fact is lasting while it remains that NPC's current trust cause; other facts are recent. An NPC keeps at most three recent facts, and a deterministic review once per simulated-day-sized interval removes recent facts at least one day old. Current causes remain so Relationship and Because never point at forgotten evidence. Conversations prefer lasting direct facts, then the most recently acquired direct fact. Communicated provenance snapshots how the speaker knew the event, so a listener's memory stays valid after the speaker forgets.

VillageOpportunitySystem is another read-only consumer of immutable facts. A known positive NPC pantry withdrawal, combined with the pantry currently being empty and a living critically hungry knower, can open one restock_empty_pantry record. A known missing-wood task_blocked fact can open one supply_missing_wood record for its living performer. At most one need is open globally. The system does not perform a transaction, assign an NPC, or append a quest-only event. A need resolves only after its target storage actually reaches the one-unit target through either:

  • a later matching positive storage_deposited event from a real NPC inventory; or
  • a later player resource_extracted event from an existing player-usable matching ResourceNode directly into the target storage.

The exact trigger and resolution IDs remain in opportunity history. The active trigger memory is protected until closure. A wood need is invalidated if its interested performer dies or if it is still unresolved one simulated day after opening; invalidation emits no event and stores the close tick and stable reason. Resolution or invalidation returns the trigger to normal bounded retention.

When a performer or direct witness meets another villager at an eligible shared activity, the active opportunity's trigger is attempted before normal lasting/recent communication ranking. EventKnowledgeSystem still requires performed/witnessed source provenance and records only the existing event ID, speaker ID, source method, listener, and acquisition tick. A newer unrelated fact cannot displace the active report, while communicated listeners still cannot relay it onward.

For an open need, the same system can derive one capable helper without assigning work. A candidate must know the exact trigger and have a familiar directed relationship with at least 0.6 trust toward the interested villager. Enough carried inventory produces the matching deposit route; otherwise a matching enabled, NPC-usable, sufficiently stocked and available finite ResourceNode state produces the gather route. Ranking is deterministic and prefers ready inventory, trust, profession fit, familiarity, then stable NPC ID. The returned opportunity/trigger/helper/action/resource, inventory source or available finite-source count, and reason are an ephemeral query result. It does not select an exact gather target; that remains with ActiveWorldAdapter and ActionTargetResolver. No event, reservation, RNG draw, task mutation, or serialized assignment is created.

At an idle NPC decision boundary, SimulationManager performs that query again and gives the ephemeral result to ActionSelectionSystem. Personal survival, low-energy rest, mourning, and sleep/meal schedule branches retain precedence; otherwise only the named helper selects the reported ordinary gather/deposit action. From there, existing target resolution chooses and reserves an exact finite resource or storage target. The current NPC task may serialize normally while in progress, but no opportunity-to-helper assignment or acceptance state exists in the save.

The food-loop regression verifies this chain:

bush -> NPC inventory -> village pantry -> NPC inventory -> consumed

It checks event order, stable IDs, exact quantities, conservation, and save/restore continuity.

Presentation

npc_inventory_changed is a transient presentation signal. The active NpcVisual shows a small food sack whenever its authoritative inventory contains food. A newly loaded visual derives the same state directly from the NPC record, so unloading presentation does not lose the fact. The selected-NPC inspector shows up to four lasting-first retained facts with acquisition provenance, separately from three objective events the NPC personally performed. A real trust-changing consequence emits one transient amber blossom above its observer. The cue is presentation-only, remains visible in cinematic mode, and is neither saved nor replayed after visual rebuild. The village summary also shows one compact active need with its real target progress and interested villager; only that villager's inspector retains its open/closed detail and names the exact supplier for a resolution. The empty-bowl world cue remains specific to the pantry shortage rather than representing a wood need as hunger. The active summary additionally names the currently derived helper, supply action, trust fact, and inventory or finite-source route, or states that no informed trusted villager is currently able to supply it. One separate cardless player HUD gives transient warm-amber feedback for the authoritative need opening, exact named report, newly informed helper decision, and real resolution. It stays available when development overlays are hidden, but clears on restore and never becomes objective or save state.

Deliberate limits

The objective stream is currently kept in full for the small simulation garden; only per-NPC known references are bounded. Before large populations or long-running worlds, add measured archival/summary rules and query indexes. Proximity is the only current direct witness rule, and communication is intentionally one-hop and tied to shared activity arrival. Line of sight, hearing, personalized reinforcement/decay, multi-hop rumours, secrecy, false beliefs, and multi-event causal graphs belong in later event/history slices. They should extend this record family without making prose authoritative or recomputing old evidence from current positions. The current opportunity family is likewise a bounded two-type projection with one read-only helper query and one ordinary action-selection consumer, not a generic quest, reward, acceptance, dialogue, helper assignment, or capable- helper framework.