191 lines
11 KiB
Markdown
191 lines
11 KiB
Markdown
# The Steward — Economic Event Stream
|
||
|
||
## Current contract
|
||
|
||
Every successful food or wood movement creates an `EconomicEventRecord` using
|
||
the applicable event type:
|
||
|
||
```text
|
||
resource_extracted
|
||
storage_deposited
|
||
storage_withdrawn
|
||
item_consumed
|
||
animal_fed
|
||
```
|
||
|
||
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. `animal_fed` records one food moving
|
||
from `village_pantry` to a stable animal ID at the animal's authoritative
|
||
position. Actor `-1` identifies a player-triggered extraction or feeding 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 inventory and storage transactions and requests event
|
||
records only after state changes succeed. `AnimalCareSystem` composes the
|
||
pantry withdrawal with animal hunger relief and requests `animal_fed` only
|
||
after both succeed. `SimulationManager` remains the scene-tree 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 v11 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
|
||
v1–v7 migrate to an empty opportunity list, while world schemas v8–v10
|
||
preserve and normalize opportunity history. World v9 adds an empty animal list
|
||
during migration; world v10 preserves and upgrades its animal records. 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.
|
||
|
||
Animal-feed facts receive an additional cross-record check: actor is either an
|
||
existing NPC or the player sentinel, source is the real pantry, destination is
|
||
an existing animal, item and definition cost are exactly one food, event tick
|
||
is not in the future, and the animal's `last_fed_tick` agrees with its latest
|
||
feed fact.
|
||
|
||
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:
|
||
|
||
```text
|
||
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.
|