186 lines
8.6 KiB
Markdown
186 lines
8.6 KiB
Markdown
# The Steward — Action System Architecture
|
|
|
|
## Current flow
|
|
|
|
```text
|
|
SimulationManager tick
|
|
-> ActionExecutionSystem advances needs and working progress
|
|
-> SimulationPopulationView refreshes transient living/starving indexes
|
|
-> VillageOpportunitySystem re-derives the current capable helper
|
|
-> ActionSelectionSystem chooses an action when the NPC is idle
|
|
-> SimulationManager stores the selected action
|
|
-> npc_target_requested
|
|
-> WorldViewManager supplies the active visual's current position
|
|
-> ActionTargetResolver consumes simulation state + ActiveWorldAdapter facts
|
|
-> SimulationManager stores/reserves the target
|
|
-> npc_travel_requested
|
|
-> WorldViewManager commands NpcVisual travel
|
|
-> arrival/navigation failure returns to SimulationManager
|
|
```
|
|
|
|
## Responsibilities
|
|
|
|
### ActionSelectionSystem
|
|
|
|
- reads NPC and village state;
|
|
- queries `RelationshipSystem` after personal survival/schedule overrides, so a
|
|
trusted starving acquaintance can redirect ordinary work toward food
|
|
gathering while the pantry is low;
|
|
- consumes the manager's current `SimulationPopulationView` for that social
|
|
query instead of rebuilding an all-NPC lookup for each idle decision;
|
|
- accepts the current ephemeral `OpportunityHelperResult` and, only for its
|
|
matching NPC, selects the reported ordinary gather/deposit action after
|
|
urgent self-care and sleep/meal schedule branches;
|
|
- evaluates current utility scores;
|
|
- consumes the NPC's deterministic decision RNG;
|
|
- returns an action ID, optional urgent-duration override, branch reason,
|
|
compared utility scores, and definition-backed rejection reasons;
|
|
- does not mutate targets, navigation, or visuals.
|
|
|
|
### ActionExecutionSystem
|
|
|
|
- advances hunger, energy, starvation, death, and working progress;
|
|
- marks an action complete when its duration elapses;
|
|
- does not select the next action or resolve a target.
|
|
|
|
### ActionTargetResolver
|
|
|
|
- reads target metadata from ActionDefinition;
|
|
- consumes active-world facts through ActiveWorldAdapter;
|
|
- expands resource queries through nearby grid ranges and prunes farther cells
|
|
only when authoritative risk/comfort/priority bounds prove they cannot win;
|
|
- preserves the former registration-order tie behavior and keeps a linear
|
|
compatibility path for isolated adapters;
|
|
- checks simulation-owned ResourceStateRecord availability;
|
|
- reserves and returns stable resource target IDs;
|
|
- skips full-capacity activity sites based on authoritative NPC target claims;
|
|
- returns typed activity-site, storage, or wander positions without owning
|
|
presentation.
|
|
|
|
### ActiveWorldAdapter
|
|
|
|
- owns the disposable `LoadedResourceSpatialIndex` of loaded stable-ID resource
|
|
interaction positions;
|
|
- refreshes the index as ResourceNodes enter, leave, rebind authoritative
|
|
state, or move;
|
|
- exposes loaded storage and activity-site interaction positions;
|
|
- exposes activity-site capacity as an active-world fact;
|
|
- contains active-world query facts, not persistent mutable authority;
|
|
- does not choose actions or reserve resources.
|
|
|
|
### WorldViewManager
|
|
|
|
- spawns and tracks NpcVisual instances;
|
|
- supplies a visual's current position when resolution is requested;
|
|
- applies travel commands;
|
|
- reports arrival, navigation failure, and visual death state;
|
|
- does not map actions, select targets, write target IDs, or mutate resources.
|
|
|
|
### SimulationManager
|
|
|
|
SimulationManager remains the orchestrator and event boundary. It owns
|
|
simulation records, invokes the focused systems, stores selected actions and
|
|
targets, and translates presentation callbacks into simulation transitions.
|
|
It also owns one disposable `SimulationPopulationView`, rebuilt at tick start
|
|
and refreshed after each NPC advances so interleaved decision semantics remain
|
|
unchanged.
|
|
The manager publishes the latest `ActionSelectionResult` for presentation; the
|
|
UI does not recompute decisions. Each idle selection re-derives the capable
|
|
helper instead of consulting persisted assignment state. At completion it
|
|
atomically pays any definition-backed stored-resource cost before applying the
|
|
action effect. A late shortfall suppresses the effect and records a
|
|
`task_blocked` fact. The manager also captures actor/nearby knowledge before
|
|
forwarding newly recorded events into `RelationshipSystem`. When an NPC arrives
|
|
beside a worker at the same non-storage activity site, the manager may transfer
|
|
one direct known fact and applies its consequence only to that newly informed
|
|
listener. An open opportunity's exact trigger receives bounded priority in
|
|
that conversation before normal lasting/recent ranking. It exposes
|
|
knowledge, provenance, relationship, and cause queries without moving social
|
|
authority into UI. After consequences are known, it protects current causal
|
|
facts, enforces the recent-memory cap, and runs age review from authoritative
|
|
simulation ticks.
|
|
|
|
### VillageEconomy
|
|
|
|
- owns storage and NPC-inventory transfer operations;
|
|
- keeps `village.food` and `village.wood` synchronized as aggregate views;
|
|
- validates and pays definition-backed completion costs;
|
|
- emits completed transaction facts without owning their history.
|
|
|
|
### SimulationEventLog
|
|
|
|
- owns ordered economic and narrative event identity;
|
|
- answers recent-history, actor-history, and consumption-rate queries;
|
|
- restores persisted history without performing or replaying transactions.
|
|
|
|
### EventKnowledgeSystem
|
|
|
|
- stores stable `(knower_id, event_id)` references separately from objective
|
|
history;
|
|
- initially observes successful food-deposit events for the actor and living
|
|
NPCs within a fixed proximity radius;
|
|
- preserves first acquisition as performed, witnessed, communicated, or
|
|
legacy, with a stable source NPC ID for direct communication;
|
|
- permits one performed/witnessed fact to cross one co-located social hop, but
|
|
does not relay communicated or ambiguous legacy facts;
|
|
- tries the active opportunity trigger first when the speaker directly knows
|
|
it, then orders remaining communication by lasting relationship relevance
|
|
and acquisition recency;
|
|
- retains current relationship causes and at most three other recent facts per
|
|
NPC, expiring day-old recent facts at deterministic review boundaries;
|
|
- captures evidence only when the event happens, never from current positions
|
|
during restore;
|
|
- answers deterministic known-event and knower queries without formatting
|
|
prose or choosing actions.
|
|
|
|
### RelationshipSystem
|
|
|
|
- owns directed familiarity/trust records and stable relationship queries;
|
|
- applies a bounded trust gain when a hungry familiar NPC knows about a
|
|
contributor's successful food-deposit event;
|
|
- stores the exact event ID as the trust cause and ignores replayed or older
|
|
events;
|
|
- selects trusted starving subjects deterministically for action selection;
|
|
- uses a supplied per-tick population view when available, with a compatibility
|
|
fallback for isolated callers;
|
|
- does not choose actions, perform transactions, or format presentation text.
|
|
|
|
These collaborators are `RefCounted` rule services, not additional scene-tree
|
|
managers. Further decomposition should follow measured pressure and a proven
|
|
gameplay consumer.
|
|
|
|
## Player context-interaction contract
|
|
|
|
`Player.get_interaction_context()` derives one ephemeral
|
|
`PlayerInteractionResult` in the same priority order used by `E`: hungry
|
|
animal, usable finite resource, guard site, study site, then pantry. The result
|
|
contains the exact stable target ID, action, display name, prompt copy,
|
|
availability, and loaded target reference. `try_interact()` executes that
|
|
single resolved result rather than maintaining a second target map.
|
|
|
|
`PlayerInteractionHud` formats the result and transient outcome feedback. It
|
|
does not select targets, mutate simulation state, or enter the save record.
|
|
State restore clears active feedback and re-derives the prompt from current
|
|
simulation and loaded-world facts. The HUD is separate from development
|
|
overlays so the embodied action remains legible in cinematic mode.
|
|
|
|
## Active-position contract
|
|
|
|
NpcVisual emits active position changes after successful movement.
|
|
WorldViewManager forwards those facts through
|
|
`SimulationManager.synchronize_npc_position()`. Arrival, navigation failure,
|
|
and visual unload also perform a final synchronization.
|
|
|
|
SimNPC stores both authoritative position and the resolved travel destination.
|
|
The destination is versioned simulation state, so reloading a visual resumes
|
|
the same route request without rerolling wander, resolving another resource, or
|
|
changing a reservation.
|
|
|
|
`tests/npc_visual_lifecycle_test.gd` proves that visual unload/reload preserves
|
|
action, target ID, travel destination, resource reservation, position, and
|
|
state checksum.
|
|
|
|
Unloaded NPCs do not yet resolve abstract travel time; they remain in their
|
|
current task state until an active visual or a future simulation-LOD travel
|
|
system advances them.
|