refactor: clarify simulation ownership

This commit is contained in:
Rijad Zuzo
2026-07-10 11:02:11 +02:00
parent 0f7b12080e
commit ce8f71082b
29 changed files with 793 additions and 587 deletions
+17 -2
View File
@@ -68,8 +68,23 @@ It also publishes the latest `ActionSelectionResult` for presentation; the UI
does not recompute decisions. 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.
Further decomposition should follow measured pressure rather than splitting it
into managers for their own sake.
### 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.
These collaborators are `RefCounted` rule services, not additional scene-tree
managers. Further decomposition should follow measured pressure and a proven
gameplay consumer.
## Active-position contract
+91
View File
@@ -0,0 +1,91 @@
# The Steward — Architecture Overview
This project is organized around gameplay ownership, not scene-tree location.
Serializable simulation records are authoritative; loaded Godot nodes present
that state and contribute active-world facts such as positions and navigation
results.
## Runtime flow
```text
SimulationClock
-> SimulationManager orchestrates one deterministic tick
-> ActionExecutionSystem advances needs and work
-> ActionSelectionSystem chooses an action
-> ActionTargetResolver resolves a stable target ID
-> VillageEconomy performs inventory/storage transactions
-> SimulationEventLog records completed facts
-> WorldViewManager presents travel and NPC state
-> ActiveWorldAdapter supplies loaded-world positions/capacity
-> NpcVisual performs local navigation and animation
```
`SimulationManager` is the scene-tree façade for the simulation. It owns the
tick lifecycle, authoritative NPC/village/resource records, reservations, and
the signals consumed by presentation. Focused collaborators own rules that
would otherwise obscure that lifecycle:
- `simulation/actions/` owns selection, progress, and target resolution;
- `simulation/economy/VillageEconomy.gd` owns storage/inventory transactions
and keeps village resource summaries synchronized;
- `simulation/events/SimulationEventLog.gd` owns ordered event identity,
history queries, and rate calculations;
- `simulation/persistence/` owns save-slot file safety;
- `simulation/state/` owns versioned serialized record contracts;
- `simulation/definitions/` owns stable IDs and immutable action/profession
definitions.
The manager deliberately remains a façade instead of being split into a
collection of scene-tree manager nodes. A new collaborator is justified when
one cohesive rule set has several real consumers or makes the tick lifecycle
hard to read.
## Folder ownership
| Path | Responsibility |
| --- | --- |
| `simulation/` | Headless-capable orchestration and core models |
| `simulation/actions/` | Action decisions, execution, and target queries |
| `simulation/economy/` | Authoritative inventory and storage transactions |
| `simulation/events/` | Immutable event history and derived event queries |
| `simulation/state/` | Versioned, serializable mutable records |
| `simulation/definitions/` | Stable IDs and immutable gameplay definitions |
| `simulation/persistence/` | Validated local save-file storage |
| `world/` | Loaded-world interaction geometry and presentation adapters |
| `world/resource_nodes/` | Finite resource presentation bound by stable ID |
| `world/storage/` | Storage interaction geometry, never stored quantities |
| `world/activity/` | Rest/study/patrol interaction sites and capacity facts |
| `player/` | Player input, camera, and active NPC presentation |
| `tests/` | Deterministic headless gameplay scenarios |
Top-level core model scripts keep their stable paths because Godot's global
class cache records `class_name` locations. Moving them solely for cosmetic
nesting can break editor and headless startup for existing workspaces without
improving ownership.
## Dependency rules
- Simulation code must run without `main.tscn` or loaded world nodes.
- Persistent references are stable IDs, never `Node`, `NodePath`, or scene
ownership.
- Presentation may report facts and submit commands; it does not choose NPC
actions or own resource, storage, inventory, event, or reservation state.
- Resource changes go through `ResourceStateRecord`, NPC inventory, and
`VillageEconomy`; `village.food` and `village.wood` are synchronized views.
- New mutable features define serialization and deterministic continuation at
the same time as their first gameplay use.
- Prefer one tested vertical behavior over a generic framework with no proven
consumers.
## Where new code goes
Put a rule beside the state it governs. A relationship consequence belongs in
a focused simulation system plus serialized relationship records; its icon or
animation belongs in presentation. Add a world node only when the behavior
needs loaded-world geometry. Add a stable ID or definition when content must be
referenced across saves, scenes, or unloaded simulation.
The architectural decision and detailed contracts live in
[ADR 0001](decisions/0001-simulation-authority-boundary.md),
[the action system architecture](ACTION_SYSTEM_ARCHITECTURE.md), and
[the simulation state schema](SIMULATION_STATE_SCHEMA.md).
+5
View File
@@ -20,6 +20,11 @@ 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. `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 the action and shortfall
reason. This makes late contention inspectable without pretending that a
+6 -2
View File
@@ -20,8 +20,10 @@ world only when consumed.
- `StorageStateRecord` owns pantry and woodpile contents and capacity.
- `SimNPC.inventory` owns carried item amounts.
- `SimulationManager` performs deposit, withdrawal, and consumption
transactions.
- `VillageEconomy` performs deposit, withdrawal, consumption, and
definition-backed completion-cost transactions.
- `SimulationManager` coordinates action lifecycle and exposes the transaction
results to presentation.
- `village.food` and `village.wood` are synchronized aggregate views used by
the existing UI, priorities, and utility scoring. They are not second
mutation paths.
@@ -45,6 +47,8 @@ Transactions apply the amount actually available:
- extraction cannot exceed the source;
- deposit cannot exceed storage capacity;
- player extraction is limited to storage capacity until player inventory
exists, so overflow remains at the source;
- withdrawal cannot exceed pantry contents;
- eating succeeds only when the NPC carries one food.
+8 -3
View File
@@ -29,9 +29,8 @@ Each milestone should produce five outcomes:
Do not advance because files exist. Advance when the exit test passes.
The current implementation has completed ResourceNode migration through player
parity plus the minimal Jajce scaffold and navigation proof. The architecture
gate below is now active and must pass before beauty production or broader
simulation features. See
parity, the Jajce scaffold/navigation proof, and the mandatory architecture
gate below. See
[ADR 0001](decisions/0001-simulation-authority-boundary.md).
## Mandatory architecture gate
@@ -660,6 +659,12 @@ The practical next sequence is:
Recently completed:
- Simulation responsibility cleanup: storage/inventory transactions now live
in `VillageEconomy`, ordered history and rate queries live in
`SimulationEventLog`, and `SimulationManager` exposes a shorter tick
lifecycle while remaining the scene-tree façade. Dead prototype APIs and
unused scene artifacts were removed, with the ownership map documented in
`ARCHITECTURE_OVERVIEW.md`.
- `F10` cinematic/debug presentation toggle that hides development UI, world
labels, and NPC name/profession labels without changing simulation state;
- `F12` repeatable simulation-garden demo reset by reloading the current scene
+30 -19
View File
@@ -375,40 +375,46 @@ glyphs.
### `simulation/SimNPC.gd`
`SimNPC` is a `RefCounted` simulation model. It owns needs, task selection,
task progression, profession affinity, starvation, and death.
task state, profession affinity, carried inventory, starvation, and death.
This separation from the visual node is an important architectural seed and
should be preserved.
### `simulation/SimVillage.gd`
`SimVillage` is a `RefCounted` aggregate for shared resources, modifiers,
priorities, and applying completed NPC work.
`SimVillage` is a `RefCounted` aggregate for synchronized village resource
views, modifiers, and priorities.
### `simulation/SimulationManager.gd`
`SimulationManager` is currently a scene-tree `Node` that:
- owns the village and NPC array;
- advances a tick approximately every 1.2 seconds;
- advances deterministic ticks through `SimulationClock`;
- creates NPCs;
- coordinates task completion;
- coordinates selection, travel, reservations, and task completion;
- emits village, task, and death signals;
- exposes direct resource-changing methods to the player.
- exposes the bounded player/simulation command API.
It currently combines clock, orchestration, event publication, population
creation, and some gameplay API responsibilities.
Focused `RefCounted` collaborators keep rule ownership visible:
- action systems own selection, execution progress, and target resolution;
- `VillageEconomy` owns storage/inventory transactions and synchronized
village resource views;
- `SimulationEventLog` owns deterministic event history and queries.
`SimulationManager` remains the scene-tree façade and signal boundary rather
than duplicating these responsibilities across additional manager nodes.
### `world/world_view_manager.gd`
`WorldViewManager` bridges simulation data to visible NPC nodes. It:
- instantiates `NpcVisual` scenes;
- resolves resource nodes, random wander targets, and remaining activity
markers;
- writes the selected ResourceNode ID onto the NPC as a transitional behavior;
- sends targets to visuals;
- supplies active visual positions for simulation-owned target resolution;
- sends resolved travel destinations to visuals;
- reports arrival and navigation failure back to `SimulationManager`;
- synchronizes successful visual movement into authoritative NPC position;
- applies visual death state.
### `player/npc/NpcVisual.gd`
@@ -456,7 +462,9 @@ NpcVisual navigates through the active world
| Later ticks complete work
| |
| v
| ResourceStateRecord.extract() -> village.apply_resource_delta()
| ResourceStateRecord.extract() -> NPC inventory
| -> VillageEconomy transfers inventory/storage as actions complete
| -> SimulationEventLog appends completed facts
| |
| v
| village_changed signal updates the UI
@@ -488,6 +496,9 @@ NpcVisual navigates through the active world
│ ├── SimulationManager.gd
│ ├── actions/ Selection, execution, and target resolution
│ ├── definitions/ Stable IDs and custom definition resources
│ ├── economy/ Inventory and storage transactions
│ ├── events/ Ordered event history and queries
│ ├── persistence/ Validated local save-slot storage
│ └── state/ Versioned simulation-state records
├── tests/
│ ├── action_system_boundaries_test.gd
@@ -538,9 +549,9 @@ These are expected prototype constraints, not necessarily isolated bugs:
- Automated coverage includes deterministic same-seed and save/restore
continuation checks, player-parity/resource-contention, flat-map, and Jajce
scaffold scenarios; broader gameplay coverage is still missing.
- Resources are global floating-point counters rather than items in locations
and inventories, except food, which now moves through sources, NPC inventory,
and the village pantry.
- Food and wood now move through finite sources, NPC inventory, and typed
village storage. Other village metrics remain aggregate values rather than
located items.
- NPCs do not have homes, schedules, possessions, memories, relationships,
goals, or social knowledge.
- The reason inspector exposes current decisions, but deeper historical traces
@@ -549,9 +560,9 @@ These are expected prototype constraints, not necessarily isolated bugs:
- Unloaded traveling NPCs preserve their state but do not yet advance through
abstract travel time.
- There is no spatial query/index layer for large populations.
- SimulationManager still orchestrates multiple systems and player-facing
mutation APIs, but selection, execution, target resolution, and active-world
queries now have focused collaborators.
- SimulationManager still coordinates the tick lifecycle and bounded
player-facing commands, while action rules, active-world queries, economic
transactions, and event history have focused collaborators.
- Path failure and interruption emit a `navigation_failed` signal and send the NPC to wander; this is functional but not yet polished.
- The old greybox navigation source has been replaced by a project-owned
Terrain3D-derived navigation resource. The current bake is still a first
+7 -5
View File
@@ -6,22 +6,24 @@ sources of truth.
1. [`PROJECT_CONTEXT.md`](PROJECT_CONTEXT.md) is the canonical description of
the vision, current implementation, target architecture, and active
constraints.
2. [`LEARNING_ROADMAP.md`](LEARNING_ROADMAP.md) owns milestone order,
2. [`ARCHITECTURE_OVERVIEW.md`](ARCHITECTURE_OVERVIEW.md) is the concise map of
runtime ownership, folder responsibilities, and dependency rules.
3. [`LEARNING_ROADMAP.md`](LEARNING_ROADMAP.md) owns milestone order,
architecture gates, reusable-system exit tests, and intentionally deferred
work.
3. [`BUILD_IN_PUBLIC_PLAN.md`](BUILD_IN_PUBLIC_PLAN.md) owns the scoped Jajce
4. [`BUILD_IN_PUBLIC_PLAN.md`](BUILD_IN_PUBLIC_PLAN.md) owns the scoped Jajce
visual slice. It must respect the architecture gates in the learning
roadmap.
4. [`RESOURCE_NODE_MIGRATION.md`](RESOURCE_NODE_MIGRATION.md) is a focused
5. [`RESOURCE_NODE_MIGRATION.md`](RESOURCE_NODE_MIGRATION.md) is a focused
migration plan. Phases 16 are complete; follow-up work should expand
resource discovery without reintroducing abstract resource zones.
5. [`ACTION_SYSTEM_ARCHITECTURE.md`](ACTION_SYSTEM_ARCHITECTURE.md),
6. [`ACTION_SYSTEM_ARCHITECTURE.md`](ACTION_SYSTEM_ARCHITECTURE.md),
[`ECONOMIC_EVENTS.md`](ECONOMIC_EVENTS.md),
[`FOOD_STORAGE_ARCHITECTURE.md`](FOOD_STORAGE_ARCHITECTURE.md),
[`SIMULATION_DEFINITIONS.md`](SIMULATION_DEFINITIONS.md), and
[`SIMULATION_STATE_SCHEMA.md`](SIMULATION_STATE_SCHEMA.md) document the
current data contracts.
6. [`decisions/`](decisions/) contains durable architectural decisions,
7. [`decisions/`](decisions/) contains durable architectural decisions,
including consequences and revisit conditions.
When documents disagree:
+4 -3
View File
@@ -60,9 +60,10 @@ 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 into
the stable `village_pantry` record. Parsed storage values are canonicalized so
save/restore continuation retains byte-stable checksums.
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
@@ -54,7 +54,7 @@ random streams, and no dependency on a loaded gameplay scene.
5. ✅ Split action selection, execution, and presentation travel.
6. ✅ Synchronize active position and prove visual unload/reload
invariance.
7. Add save-slot persistence before schedules or relationships
7. Add save-slot persistence before schedules or relationships
substantially expand mutable state.
Schema v1 and its deterministic continuation contract are documented in