fix: empty-path re-entrancy, retry_count reset, := to = for Variant

This commit is contained in:
2026-07-03 18:51:13 +02:00
parent c6033b63fb
commit 41bfc8d90a
9 changed files with 2449 additions and 4 deletions
+563
View File
@@ -0,0 +1,563 @@
# The Steward — Build-in-Public Visual Slice Plan
## Purpose
This plan adapts the proposed “Jajce simulation garden” to the repository as it
exists now. It does not assume a blank project.
The immediate visual goal is to replace the flat prototype presentation with a
small, attractive, watchable environment while preserving the working NPC
simulation, player controls, UI, task lifecycle, and navigation behavior.
Before runtime terrain integration, food and wood should migrate from abstract
zones to actual finite world objects according to
[the ResourceNode migration plan](RESOURCE_NODE_MIGRATION.md). This prevents the
new environment from being authored around a zone model already marked for
removal.
The slice should make a viewer understand this promise quickly:
> A cozy Bosnian-inspired world where autonomous people visibly make decisions,
> work, struggle, and create stories.
This is not a plan to build the full city of Jajce. It is a plan to create one
strong visual composition that can host the current simulation and grow with
future systems.
## What already exists
Do not redo these tasks:
- Godot 4.7 project configuration;
- Terrain3D 1.0.2 installed and enabled;
- cross-platform Git configuration;
- working `main.tscn`;
- camera-relative player movement and elevated follow camera;
- three autonomous simulated NPCs;
- hunger, energy, starvation, death, task scoring, and task duration;
- task-change and village-change signals;
- visual NPC navigation and arrival reporting;
- farm, forest, food, guard, study, and rest markers;
- aggregate village UI;
- initial tree assets;
- a baked navigation region for the current flat test area.
Terrain3D's bundled `demo/` directory and most of `addons/terrain_3d/` are
plugin content. They are references, not the game.
## Current architectural constraint
`main.tscn` currently owns several different concerns:
```text
Main
├── current flat World and NavigationRegion3D
├── Player
├── CameraRig
├── SimulationManager
├── WorldViewManager
├── ActiveNPCs
├── TaskZone markers
├── WorldEnvironment
└── UI
```
The player and `WorldViewManager` reference the task markers through exported
`NodePath` values. `NpcVisual` relies on the navigation map to reach those
markers.
Replacing the ground without respecting those references would break behavior
that already works.
## Adjusted scene strategy
Create a reusable environment scene and instance it into the existing runtime.
Keep game orchestration in `main.tscn`.
Target structure:
```text
Main
├── JajceWorld instanced environment
│ ├── TerrainRoot
│ │ └── Terrain3D
│ ├── WaterRoot
│ ├── VillageRoot
│ ├── FoliageRoot
│ ├── NavigationRegion3D
│ ├── WorldObjects
│ │ └── ResourceNodes
│ │ ├── BerryBush instances
│ │ └── Tree instances
│ ├── LegacyTaskMarkers temporary migration fallback
│ │ ├── FarmZone fallback only
│ │ ├── ForestZone fallback only
│ │ ├── FoodZone
│ │ ├── GuardZone
│ │ ├── StudyZone
│ │ └── RestZone
│ ├── DirectionalLight3D
│ └── WorldEnvironment
├── Player
├── CameraRig
├── SimulationManager
├── WorldViewManager
├── ActiveNPCs
└── UI
```
`JajceWorld` should contain geography, world-object presentations, and temporary
legacy interaction locations. It should not own `SimulationManager`, simulated
NPC data, player state, or UI.
Create a small look-development wrapper that instances the same world:
```text
JajceLookdev
├── JajceWorld
└── BeautyCameraRig
```
This provides a cinematic workbench without creating a second environment that
later has to be rebuilt for gameplay.
## Adjusted file structure
Follow the repository's existing feature-oriented layout. Do not create six
empty sub-scenes merely to match a theoretical architecture.
Start with:
```text
world/
└── jajce/
├── JajceWorld.tscn
├── JajceLookdev.tscn
└── beauty_camera.gd
terrain/
└── jajce/
├── data/
├── materials/
├── textures/
└── source/
assets/
└── jajce/
├── buildings/
├── foliage/
├── rocks/
├── props/
├── water/
└── vfx/
```
Extract `JajceWater`, `JajceVillageBlockout`, or other components into their own
scenes only when they have meaningful internal behavior or are reused. Avoid
scaffolding an elaborate directory tree before assets exist.
## Visual target
Build a compressed, game-readable Jajce-inspired valley:
| Inspiration | First-slice interpretation |
| --- | --- |
| Hilltop fortress | One strong blockout silhouette on the ridge |
| Old town slope | Four to six house blocks following two terraces |
| Pliva/Vrbas water | One main river and a suggested secondary channel |
| Waterfall | Hero mesh/VFX feature visible from the main camera |
| Watermills | One working mill silhouette, with room for later expansion |
| Forested hills | Terrain3D foliage clusters and background tree masses |
| Agricultural outskirts | Existing farm and forest jobs integrated into terrain |
| Regional roads | One readable loop connecting world objects and legacy targets |
The fortress, waterfall, roofs, player, and at least two working NPCs should be
readable in one strong composition.
## Scope
### First terrain size
Start around **512 m × 512 m**. The playable and polished area can be smaller
inside it.
Do not begin at 1024 m simply because Terrain3D supports it. Expand only when
the current camera, navigation routes, and composition need more space.
### First material set
Use at most six terrain surfaces:
1. soft grass;
2. dirt path;
3. warm cliff stone;
4. wet riverbank stone;
5. forest floor;
6. compacted village ground.
The material set should establish large readable areas. Avoid high-frequency
photorealistic noise.
### First architecture set
Use:
- one fortress silhouette;
- four to six house blockouts;
- one mill;
- one bridge;
- a few fences, carts, sacks, logs, and work props.
Do not begin with 1220 finished houses. The first shot needs composition, not
an asset catalog.
## Art direction rules
Translate the desired animated-film warmth into an original style:
| Element | Direction |
| --- | --- |
| Terrain | Broad painterly color regions and exaggerated silhouettes |
| Grass | Soft masses and restrained sway, not dense visual noise |
| Trees | Rounded canopies, varied scale, simple readable trunks |
| Rocks | Chunky warm-gray forms with low-frequency detail |
| Buildings | Light plaster/stone, warm roofs, dark timber accents |
| Water | Turquoise/emerald body, strong foam shapes, soft reflection |
| Lighting | Warm sun, cool shadows, mild atmospheric haze |
| Characters | Simple proportions, clear profession colors and props |
| Motion | Wind, water, smoke, leaves, cloth, birds, and purposeful NPC travel |
Cozy presentation does not remove scarcity, death, conflict, or ambition. It
makes the world pleasant to inhabit and the people pleasant to watch.
## Implementation phases
### Phase 0 — Preserve and capture the baseline
Before replacing anything:
- run the current prototype;
- record a short baseline clip or screenshots;
- record the development machine's frame time and obvious scene statistics;
- verify all six task locations can be reached;
- verify starvation, work completion, and death still function;
- note current camera framing and movement feel.
This becomes both a regression reference and excellent before/after material.
### Exit condition
There is a reproducible baseline showing the existing flat prototype and its
working behaviors.
### Systems prerequisite — Migrate food and wood targets
Implement and validate the first ResourceNodes on the current flat map before
moving the playable runtime onto Terrain3D:
- berry bushes for `gather_food`;
- trees for `gather_wood`;
- stable target IDs;
- nearest-available selection;
- single-user reservation;
- extraction after work completion;
- depletion and replanning;
- actual extracted amount applied to the village;
- zone fallback with visible warnings.
Keep the other task zones temporarily. Rest, eating, study, and patrol will
eventually use activity, storage, or workstation targets rather than pretending
to be resource sources.
### Exit condition
Food and wood work without their zones during normal play, resource conservation
holds, and target failure does not leave a permanent reservation.
### Phase 1 — Create the reusable world scaffold
Create `world/jajce/JajceWorld.tscn` with:
- Terrain3D and dedicated terrain data;
- placeholder `WaterRoot`;
- placeholder `VillageRoot`;
- placeholder `FoliageRoot`;
- a navigation region;
- resource-node containers plus temporary fallback markers;
- lighting and environment.
Create `JajceLookdev.tscn` that instances `JajceWorld` and adds a temporary
beauty camera.
Do not migrate `main.tscn` yet. Do not duplicate simulation scripts inside the
look-development scene.
### Exit condition
The same `JajceWorld` instance can open in the lookdev scene and can be instanced
under a temporary runtime test without errors.
### Phase 2 — Greybox the hero composition
Sculpt by hand before attempting GIS/DEM import:
- one dominant ridge;
- descending town terraces;
- main river valley;
- waterfall drop;
- lower pool;
- farm shelf;
- forest work area;
- a walkable road loop connecting world objects and remaining legacy locations.
Block out the fortress, houses, mill, bridge, and waterfall with primitives or
simple meshes.
Place the beauty camera early. Terrain should be judged through the intended
game and video cameras, not only from the editor's free camera.
### Navigation spike
Do an early rough navigation bake now. Do not wait until final art.
The purpose is to detect:
- slopes that are too steep;
- terraces that cannot connect;
- paths too narrow for agents;
- waterfall or river barriers that split required routes;
- terrain collision or navigation incompatibilities.
The final navigation bake still happens after terrain and props stabilize.
### Exit condition
One static frame contains the ridge landmark, roofs, river/waterfall, and
village work area. A test agent can traverse the intended task loop.
### Phase 3 — Establish the beauty baseline
Add only the highest-value presentation:
- six terrain materials;
- warm `WorldEnvironment`;
- directional light and atmospheric haze;
- simple river surface;
- waterfall body, foam, and mist;
- first foliage clusters using Terrain3D instancing where appropriate;
- restrained grass/tree motion;
- chimney smoke;
- slow beauty-camera drift.
Avoid expensive water, dense individual grass nodes, and elaborate weather at
this stage.
### Exit condition
The lookdev scene can produce a compelling 1020 second clip with no gameplay.
The environment remains within the recorded prototype's agreed performance
budget.
### Phase 4 — Integrate the existing game
Once the world composition is stable:
1. Instance `JajceWorld` into `main.tscn`.
2. Disable or remove the old flat world only after the new instance is present.
3. Place or move bushes and trees under
`JajceWorld/WorldObjects/ResourceNodes` while preserving stable IDs.
4. Reconnect remaining player and `WorldViewManager` fallbacks to markers under
`JajceWorld/LegacyTaskMarkers`.
5. Bake navigation for the new walkable area.
6. Test each existing task from multiple spawn positions.
7. Verify task arrival still transitions from traveling to working.
8. Verify task completion still changes village state.
9. Verify depletion and reservations survive the scene migration.
10. Verify player interaction ranges still make sense at the new scale.
11. Verify dead NPC visuals do not block routes or retain reservations.
12. Compare performance and behavior against the Phase 0 baseline.
Do not move simulation ownership into `JajceWorld` to make wiring convenient.
### Exit condition
The current three NPCs and player can perform all existing prototype behaviors
inside the new terrain without regression.
### Phase 5 — Make the simulation readable on video
Add presentation for systems that already exist:
- simple stylized NPC placeholder instead of capsules;
- profession color/prop distinctions;
- compact task/need icon above selected or relevant NPCs;
- name labels when useful;
- an NPC reason inspector for development footage;
- visible food, logs, guard, study, rest, and eating props;
- cinematic/debug UI toggle;
- a scenario reset and fixed seed for repeatable clips.
Do not fake activity that contradicts simulation state. A farmer icon should
represent a real chosen task.
### Exit condition
A viewer can distinguish at least three professions and understand one NPC's
need, destination, and consequence without reading console output.
### Phase 6 — Produce the first public demo sequence
Capture a short sequence with a clear story:
1. establish the valley and waterfall;
2. introduce one named NPC;
3. show the NPC choosing and traveling to work;
4. reveal the reason through a concise overlay;
5. show the work changing a real village resource;
6. end on either recovery or a visible shortage consequence.
The best first character is one of the existing named NPCs—Amina, Tarik, or
Jasmin—rather than an unnamed cinematic extra.
### Exit condition
The clip has an understandable beginning, decision, and consequence. It does not
depend on a voiceover to prove that the simulation is real.
## Build-in-public content pillars
Rotate between four types of updates.
### 1. Transformation
Show visible before/after progress:
- flat test ground to Jajce-inspired valley;
- blockout waterfall to mist and foam;
- capsules to readable professions;
- empty paths to visible work traffic.
### 2. Tiny autonomous stories
Use one named NPC and one question:
- Why did Amina stop guarding?
- Can Tarik gather food before starvation?
- Why is Jasmin resting while the village is short on wood?
- What happens when the food route is blocked?
### 3. System under the beauty
Show a beautiful shot, then reveal the debug reason trace, event, or resource
flow that drives it.
### 4. Learning Godot publicly
Frame honest engineering lessons:
- Terrain3D sculpting and material mistakes;
- navigation problems caused by terrain;
- making NPC simulation deterministic;
- profiling foliage versus agent cost;
- separating simulation data from visual scenes.
## Short-form video template
For TikTok, Shorts, or brief social posts:
```text
02 s Show the outcome or surprising NPC behavior
25 s State one question
515 s Show the cause unfolding
1525 s Reveal the systemic consequence
End Show the next unresolved problem
```
Use captions and strong visual causality. Avoid opening with a long editor tour.
## Initial video backlog
1. **“I replaced my programmer map with a Bosnian-inspired valley.”**
2. **“This villager chose food over their profession—and here is why.”**
3. **“The village can starve without a scripted quest.”**
4. **“Every NPC physically walks to the work they selected.”**
5. **“One blocked path broke the village economy.”**
6. **“Making a Jajce-inspired waterfall in Godot Terrain3D.”**
7. **“The same scene in cinematic mode and simulation-debug mode.”**
8. **“Can three autonomous villagers survive this shortage?”**
Each video should correspond to real repository progress. Do not build isolated
visual tricks solely for a post unless they also support the intended game.
## Performance guardrails
- Record frame time before adding terrain.
- Treat Terrain3D instancing as the default for broad foliage where suitable.
- Avoid physics and navigation obstacles on distant decorative vegetation.
- Keep waterfall mist and smoke particle counts bounded.
- Use simple water before adding screen-space reflection or refraction.
- Test the gameplay camera, not only the beauty camera.
- Profile editor and exported builds separately when export work begins.
- Recheck navigation cost after every meaningful terrain expansion.
- Do not increase NPC population while diagnosing environment cost.
## Source-control guardrails
- Commit Terrain3D data and game-authored terrain assets.
- Continue ignoring `.godot/` and other imported caches.
- Keep Godot `.import` and `.uid` sidecars tracked.
- Do not modify or reformat the third-party Terrain3D addon incidentally.
- Keep lookdev assets under game-owned paths, not inside the addon.
- Make environment, integration, and simulation changes separate commits where
practical.
## Definition of “Jajce Lookdev 01”
The adjusted first milestone is complete when:
- Terrain3D is used by `JajceWorld`; installation itself is already complete.
- One approximately 512 m terrain exists.
- Fortress ridge, town terraces, river valley, and waterfall drop are readable.
- Six or fewer terrain materials establish the major surfaces.
- One fortress, four to six houses, one mill, and one bridge are blocked out.
- River, waterfall foam, mist, warm lighting, and first foliage are present.
- A beauty camera produces a strong 1020 second shot.
- A rough navigation test proves the main route is viable.
- The existing simulation has not yet been duplicated or moved.
## Definition of “Simulation Garden 01”
The follow-up milestone is complete when:
- `JajceWorld` is instanced in `main.tscn`;
- food and wood ResourceNodes work in the terrain;
- remaining legacy task markers are reconnected;
- player movement and all existing interactions work;
- all three current NPCs navigate, work, eat, rest, starve, and die correctly;
- one NPC's decision is readable through in-game presentation;
- cinematic and debug views can show the same real scenario;
- a repeatable public-demo clip shows an autonomous cause and consequence;
- performance and navigation regressions are measured and documented.
## Immediate implementation order
1. Capture the current flat-map baseline.
2. Implement and validate ResourceNodes on the flat map.
3. Migrate food and wood; keep remaining zones as logged fallbacks.
4. Create `JajceWorld.tscn`.
5. Create dedicated Terrain3D data under `terrain/jajce/`.
6. Create `JajceLookdev.tscn` and its beauty camera.
7. Sculpt the ridge, river valley, terraces, and waterfall drop.
8. Place ResourceNodes and only the still-required legacy markers.
9. Block out the fortress, houses, mill, and bridge.
10. Run the early navigation spike.
11. Establish terrain materials, lighting, water, mist, and foliage.
12. Capture “Jajce Lookdev 01.”
13. Instance `JajceWorld` into `main.tscn`.
14. Reconnect fallbacks, rebake navigation, and regression-test the simulation.
15. Add profession/readability presentation.
16. Capture “Simulation Garden 01.”
Do not start with GIS data, a full city, a large asset pack, or more NPC
mechanics. The next proof is a beautiful stage for the systems that already
exist.
+590
View File
@@ -0,0 +1,590 @@
# The Steward — Learning and Reusable-Systems Roadmap
## Purpose
This project is both a playable prototype and an advanced Godot curriculum.
The goal is not merely to accumulate features. It is to understand, implement,
measure, and document systems that can form the foundation of a later
production game.
The roadmap prioritizes:
- learning through complete vertical behaviors;
- separating simulation from presentation;
- repeatable experiments and measurements;
- systems that survive save/load and scale changes;
- visible results suitable for building in public;
- avoiding premature architecture that has not been tested by gameplay.
## How to use this roadmap
Each milestone should produce five outcomes:
1. **Concept learned** — the Godot or simulation topic being studied.
2. **Playable proof** — behavior a player can observe or influence.
3. **Reusable artifact** — a system, pattern, benchmark, or tool.
4. **Exit test** — an objective condition for calling the milestone complete.
5. **Retrospective** — what worked, what failed, and what should change.
Do not advance because files exist. Advance when the exit test passes.
## Three vertical slices
The long-term vision contains three major risks. Validate them separately before
trying to combine the full game.
### A. Simulation slice
A small village runs autonomously, exposes understandable decisions, creates
history, and produces interesting failures without scripted quests.
### B. Action slice
Movement, interaction, melee combat, companions, and small-group commands are
fun in an isolated test environment.
### C. World slice
One village, one route, and part of a town exchange people, goods, information,
and conflict while the player moves between simulation fidelity levels.
The simulation garden is the first slice. A combat sandbox should begin before
the simulation is “finished,” because combat feel is a separate high-risk
problem.
## Continuous learning practices
### Keep a decision record
For meaningful architectural choices, record:
- problem;
- constraints;
- options considered;
- decision;
- consequences;
- conditions that would justify revisiting it.
This can later become a `docs/decisions/` collection. Do not create records for
trivial edits.
### Maintain reproducible scenarios
Important behaviors should have fixed seeds and known initial state:
- normal village day;
- food shortage;
- blocked workplace;
- exhausted worker;
- path failure;
- NPC death;
- relationship conflict;
- migration or faction pressure.
Scenarios make debugging, videos, balancing, and performance comparisons easier.
### Measure before optimizing
Maintain a small benchmark ledger:
| Measurement | Example |
| --- | --- |
| Population | 10, 100, 1,000, 10,000 data-only agents |
| Simulated duration | one day, season, or year |
| Wall-clock runtime | total and per simulated tick |
| Update count | decisions and scheduled events processed |
| Memory | state and history footprint |
| Active visuals | navigation/animation cost |
| Determinism | final-state checksum for a known seed |
### Pair systems with presentation
Every systems milestone should answer: “How can a viewer see this happening?”
Every visual milestone should answer: “Which real state does this represent?”
## Milestone 0 — Baseline and observability
### Learn
- Godot project execution and scene-tree inspection;
- debugger, profiler, monitors, and remote scene tree;
- structured logging and debug overlays;
- establishing behavioral and performance baselines.
### Build
- a simulation pause, single-step, speed control, and reset;
- selectable NPC debug panel;
- current need, task, task state, target, and task score display;
- deterministic scenario seed entry;
- concise event feed separate from verbose console logging;
- baseline performance capture for the existing three-NPC prototype.
### Reusable artifact
A simulation debug console/overlay and scenario runner.
### Exit test
From the running game, a developer can select any NPC and explain:
- what it is doing;
- why it selected that task;
- where it is going;
- when the task should complete;
- what state will change on completion.
The same seed and commands produce the same early simulation result.
## Milestone 1 — Simulation/presentation boundaries
### Learn
- `Node`, `RefCounted`, and `Resource` responsibilities;
- signals versus direct calls;
- dependency direction;
- scene composition;
- typed GDScript and stable identifiers.
### Build
- explicit simulation clock outside frame-dependent rules;
- stable IDs for NPCs and locations;
- serializable state records separated from immutable definitions;
- an adapter interface between simulation and active visual NPCs;
- smaller managers with focused responsibilities;
- removal of direct task-string-to-marker coupling from the core.
### Reusable artifact
A minimal headless-capable simulation kernel with a Godot presentation adapter.
### Exit test
The village can advance without instantiating `NpcVisual` scenes. Loading or
unloading an NPC visual does not change the authoritative NPC state.
## Milestone 2 — Data-driven needs, professions, and actions
### Learn
- custom `Resource` definitions;
- utility AI and consideration curves;
- action preconditions, effects, duration, and interruption;
- dependency injection through definitions;
- debugging decision scores.
### Build
- need definitions;
- profession definitions;
- action definitions for eat, rest, gather food, gather wood, patrol, and study;
- target discovery independent of hard-coded scene paths;
- utility considerations with full reason traces;
- reservations so multiple NPCs do not unrealistically claim one resource or
workstation;
- explicit task failure and replanning behavior.
### Reusable artifact
A generic action-selection system that can evaluate defined actions against
agent and world state.
### Exit test
A new profession and action can be added primarily through definitions and a
small effect implementation. The inspector shows the winning score and rejected
alternatives. NPCs recover from invalid or unavailable targets.
## Milestone 3 — One complete material economy
The staged replacement of abstract task zones begins with
[the ResourceNode migration plan](RESOURCE_NODE_MIGRATION.md).
### Learn
- inventories and item stacks;
- ownership, storage, reservations, and transactions;
- production recipes;
- world interaction points;
- consistency and invariant testing.
### Build
Implement one honest food chain:
```text
source -> gather/harvest -> carry -> store -> retrieve -> prepare/eat
```
Include:
- item definitions;
- personal and building inventories;
- location-based storage;
- physical carrying for active NPCs;
- abstract transfer for distant agents;
- reservation and cancellation;
- spoilage only if it improves the initial loop;
- player participation in the same resource rules.
Add wood only after the food chain is coherent.
### Reusable artifact
Inventory, reservation, transaction, and production primitives.
### Exit test
Every consumed unit of food can be traced to a source and a sequence of actions.
No resource is created or destroyed except through an explicit, inspectable
rule. A shortage is visible before the aggregate UI reports it.
## Milestone 4 — Attractive simulation garden
The detailed environment and public-demo sequence is maintained in
[the build-in-public visual slice plan](BUILD_IN_PUBLIC_PLAN.md).
### Learn
- Terrain3D regions, sculpting, texturing, LOD, and instancing;
- environment lighting, fog, sky, and post-processing;
- modular scene construction;
- navigation generation and validation;
- water and vegetation presentation;
- performance budgets for environment art.
### Build
- a compact Jajce-inspired valley composition;
- ridge landmark, river/waterfall feature, bridge, mill area, farm, forest,
homes, and workplaces;
- warm painterly environment treatment;
- replace capsules with simple stylized, animation-ready characters;
- readable profession props/colors;
- day/night presentation;
- cinematic/debug display toggle;
- stable navigation across all required work routes.
### Reusable artifact
A terrain/world-building pipeline and environment performance checklist.
### Exit test
A short clip communicates the setting, player perspective, NPC professions, and
one production loop without narration or console output. The environment stays
inside the agreed frame-time and memory budget on the development machine.
## Milestone 5 — Time, schedules, and persistence
### Learn
- authoritative game time;
- calendar and scheduled events;
- serialization and schema versioning;
- save migration;
- restoring active scenes from data;
- deterministic random streams.
### Build
- time-of-day and calendar;
- work, meal, sleep, and discretionary schedule blocks;
- simulation event queue;
- versioned save format;
- save/load for people, inventories, tasks, locations, and village state;
- state validation after loading;
- separate random streams where useful.
### Reusable artifact
A versioned world-state persistence layer and simulation scheduler.
### Exit test
Save during travel, work, eating, and sleep; reload each save; then verify that
the simulation resumes coherently. A deterministic headless scenario retains
the same checksum across repeated runs.
## Milestone 6 — Events, memories, and relationships
### Learn
- event sourcing concepts without overcommitting to full event sourcing;
- relationship graphs;
- knowledge versus objective truth;
- memory selection and decay;
- social utility considerations;
- tooling for history inspection.
### Build
- structured world event records;
- actor, target, witness, location, cause, and consequence links;
- relationship dimensions such as familiarity, affection, trust, fear,
obligation, and hostility;
- NPC knowledge of witnessed or communicated events;
- memory importance and retention rules;
- visible social reactions;
- person and settlement history views.
### Reusable artifact
An event/history store and relationship system with query APIs.
### Exit test
Two NPCs exposed to different evidence can hold different beliefs about the
same event. Their future choices visibly differ because of those memories and
relationships.
## Milestone 7 — Rumours and emergent opportunities
### Learn
- information propagation;
- query systems;
- narrative framing from structured facts;
- objective generation;
- consequence-aware quest state;
- natural-language presentation separated from authoritative state.
### Build
- rumour creation and transmission;
- information accuracy, secrecy, and distortion rules;
- detection of unresolved conditions;
- interested-party and capable-helper queries;
- opportunity/quest records that reference real entities and events;
- multiple valid resolution paths;
- consequence application to the source systems;
- concise dialogue/event text templates.
### Reusable artifact
An emergent opportunity generator built over world-state queries.
### Exit test
A shortage, theft, injury, debt, or disappearance creates an opportunity only
when someone knows and cares about it. Resolving it changes the originating
people, resources, relationships, and history without spawning quest-only
duplicates.
## Milestone 8 — Scalable spatial simulation and LOD
### Learn
- spatial partitioning;
- relevance and interest management;
- scheduled/batched updates;
- data layout and allocation profiling;
- active/abstract entity transitions;
- statistical aggregation boundaries.
### Build
- spatial index for people, buildings, and events;
- active, local-abstract, distant-individual, and settlement-aggregate modes;
- promotion/demotion between modes;
- abstract travel and work resolution;
- background settlement updates;
- performance harness for increasing population and history;
- safeguards preserving named people and unresolved events.
### Reusable artifact
A simulation-LOD framework with measured transition invariants.
### Exit test
The benchmark can simulate a target large population faster than real time while
a smaller active population remains fully represented. Moving an NPC between
fidelity levels preserves identity, inventory, task, relationships, and
important history.
Set the target population only after collecting baseline measurements.
## Milestone 9 — Player interaction and social agency
### Learn
- interaction targeting;
- context actions;
- UI state machines;
- dialogue presentation;
- reputation and permission systems;
- accessibility and feedback.
### Build
- inspect people, storage, workplaces, and events;
- talk, trade, help, request, employ, threaten, or report where appropriate;
- priority influence through plausible institutions rather than omniscient
sliders;
- reputation, authority, and social access;
- player participation in professions;
- feedback showing likely and actual consequences.
### Reusable artifact
A context interaction framework connected to simulation actions and authority.
### Exit test
The player can understand and resolve the simulation garden's food crisis in
multiple systemic ways without using a developer menu.
## Milestone 10 — Combat and companion sandbox
Begin this as a focused prototype before Milestones 69 are fully complete.
Integrate it only after its fundamentals feel good.
### Learn
- animation state machines and blending;
- hit detection and hurtboxes;
- root motion versus code-driven motion;
- input buffering;
- camera feedback;
- AI combat behavior;
- group commands and formations;
- combat profiling.
### Build
- isolated combat test scene;
- responsive locomotion, attacks, defense, stagger, and recovery;
- clear targeting without excessive lock-on dependence;
- one companion with understandable autonomous behavior;
- a small command vocabulary;
- morale and retreat prototype;
- simulation consequences for injury, death, witnesses, and reputation.
### Reusable artifact
A combatant state model, animation interface, damage/event model, and companion
command layer.
### Exit test
A short encounter is enjoyable when repeated without progression rewards. One
companion can be directed without constant micromanagement, and combat outcomes
produce valid world events.
## Milestone 11 — Multiple settlements, factions, and mobility
### Learn
- hierarchical simulation;
- settlement economies;
- faction goals and diplomacy;
- trade routes and migration;
- authority and governance;
- large-group command abstraction.
### Build
- one village, one travel route, and one district or edge of the city;
- movement of goods, people, rumours, and threats;
- household and workplace membership;
- faction membership and offices;
- migration and recruitment;
- small-scale leadership progression;
- settlement and faction decision processes.
### Reusable artifact
A regional simulation layer connecting local economies and social structures.
### Exit test
An event in one settlement creates measurable consequences elsewhere through
real movement or communication. The player can gain responsibility through
systemic relationships and actions rather than a fixed promotion quest.
## Milestone 12 — Integrated world slice
### Learn
- production hardening;
- content pipelines;
- onboarding complex systems;
- balancing and telemetry;
- regression testing;
- profiling representative gameplay.
### Build
- the simulation, action, and world slices connected;
- one coherent regional scenario;
- polished onboarding for observation, work, social interaction, and combat;
- save compatibility tests;
- performance budgets and regression scenarios;
- build-in-public demo mode;
- documented extraction boundaries for reusable systems.
### Reusable artifact
A proven architecture and a set of tested subsystems ready to migrate or evolve
into the production game.
### Exit test
The player can begin as an ordinary person, form relationships, participate in
the economy, encounter an emergent problem, fight or negotiate through it, and
leave persistent consequences in another settlement.
## Reuse checkpoints
At the end of each milestone, evaluate the system against this checklist:
- Does it run without the prototype's main scene?
- Are definitions separate from mutable state?
- Is state serializable and versioned where appropriate?
- Can it be tested with a fixed seed?
- Can presentation be replaced without rewriting its rules?
- Does it expose reason/debug information?
- Do measurements justify its complexity?
- Has at least one real gameplay feature exercised the API?
Do not extract a plugin solely because a system might be reusable. Prefer a
clear internal module until multiple real consumers establish a stable API.
## Recommended implementation order from the current repository
The practical next sequence is:
1. Add simulation controls and an NPC reason inspector.
2. Add finite berry bushes and trees through the first `ResourceNode`.
3. Migrate food target selection, reservation, depletion, and actual yield.
4. Introduce an explicit deterministic simulation clock and seed.
5. Replace remaining free-form task strings with action IDs/definitions.
6. Separate action selection, action execution, and visual travel.
7. Migrate wood and give the player the same extraction contract.
8. Build the first location-based food storage/inventory.
9. Make one NPC visibly gather, carry, store, retrieve, and eat food.
10. Build the first attractive Terrain3D simulation-garden layout.
11. Add simple stylized characters and profession readability.
12. Add save/load before the mutable world state becomes substantially larger.
This order strengthens the simulation while regularly producing visible
progress suitable for public development updates.
## Subjects intentionally deferred
These are valuable future learning areas but should not distract from the first
complete slices:
- multiplayer authority and replication;
- procedural terrain generation;
- machine-generated freeform dialogue;
- full historical demography;
- hundreds of item recipes;
- siege-scale combat;
- sophisticated market speculation;
- native GDExtension optimization;
- custom rendering technology.
Revisit them when a completed slice demonstrates a concrete need.
+721
View File
@@ -0,0 +1,721 @@
# The Steward — Project Context
> Agent-facing context for understanding the project quickly.
>
> Snapshot basis: repository state after commit `e30d208`, July 2026. Treat the
> code as the source of truth when this document and the implementation differ.
## Quick orientation
**The Steward** is currently a small Godot prototype for an embodied village
simulation. Its larger purpose is to become a learning laboratory for a
performant, large-scale NPC simulation that can later power a systemic
open-world RPG.
The current village is not intended to be the final game's scale. It is a
controlled environment in which to learn and validate:
- NPC needs and decision-making;
- professions, work, inventories, and economic dependencies;
- actions, consequences, and world events;
- relationships, memories, rumours, and persistent history;
- emergent situations that can be surfaced as quests;
- simulation level of detail for a much larger population;
- readable world presentation and satisfying embodied play.
The desired game is not an omniscient city builder and not primarily a scripted
quest RPG. The player exists physically inside the simulated society.
## North-star vision
> A systemic open-world RPG set around a city inspired by Jajce, Bosnia and
> Herzegovina, and its surrounding villages. Every important NPC participates
> in a simulated society, forms relationships, remembers events, and can affect
> history. The player can remain an ordinary citizen, pursue a profession,
> become a trader, criminal, warrior, or companion leader, command armies, and
> eventually lead a faction.
The world should generate situations. Quests should primarily communicate,
frame, and track those situations rather than manufacture disconnected content.
Example:
1. A harvest fails or a trade route becomes unsafe.
2. Food scarcity raises pressure on households and professions.
3. An NPC steals, migrates, changes work, joins a criminal group, or asks a
relative for help.
4. Witnesses form memories and spread partial information.
5. Relationships, prices, safety, and faction attitudes change.
6. The player learns about the situation through observation, conversation,
rumours, employment, or authority.
7. Any intervention changes the same world state that produced the problem.
The quest is therefore a view onto the simulation, not a parallel scripted
reality.
## Product pillars
### 1. Simulated lives
NPCs should be people rather than production modifiers. Their needs,
professions, possessions, schedules, abilities, ambitions, and circumstances
drive their choices.
### 2. Persistent history
Important actions create structured events. Events can become memories,
knowledge, rumours, relationships, reputations, grudges, obligations, and
political consequences.
### 3. Emergent narrative
Authored content should provide rules, cultures, locations, characters,
archetypes, and exceptional story beats. Ordinary quests and conflicts should
preferably emerge from unresolved world conditions.
### 4. Embodied freedom
The player walks through the same spaces, uses the same resources, and
participates in the same institutions as NPCs. They can play at different
social scales without being forced toward rulership.
### 5. Legible autonomy
Complexity is valuable only when players can understand its consequences. The
game should expose why an NPC chose an action and show resources moving through
the world.
### 6. Small stories inside a large world
The eventual map may be large, but emotional attachment comes from recognizable
people and local consequences. Simulation scale must not turn everyone into an
anonymous number.
### 7. Enjoyable action
Movement and combat should be satisfying independently of the simulation.
Companions and armies should offer expressive, enjoyable control rather than
only statistical resolution.
### 8. A recognizable setting
The core region is inspired by Jajce: dramatic elevation, a fortified urban
center, rivers, a waterfall, wooded slopes, lakes, watermills, roads, farms,
and surrounding villages. This is an inspiration rather than a requirement for
a literal historical reconstruction.
### 9. Cozy presentation with real stakes
The presentation should feel warm, painterly, pastoral, and inviting while the
simulation remains capable of scarcity, conflict, death, political ambition,
and social change.
## Build-in-public goals
Visual appeal and watchability are prototype requirements, not final polish.
Each simulation feature should have a visible expression:
| Simulation state | Visible expression |
| --- | --- |
| Hunger | Looking for food, checking storage, eating, asking, stealing, or weakening |
| Profession | Recognizable tools, clothing, workplace, route, and animation |
| Task choice | Purposeful movement plus optional task/thought icon |
| Production | Items visibly gathered, carried, stored, processed, and consumed |
| Relationship | Greeting, helping, arguing, avoiding, mourning, or celebrating |
| World event | Visible consequences and a concise event/history feed |
| Settlement change | Construction, depletion, prosperity, damage, migration, or recovery |
Development clips should communicate cause and effect without requiring viewers
to read console logs. A cinematic/debug toggle should eventually allow the same
scenario to be shown both beautifully and analytically.
## Visual direction
The target is an original stylized look characterized by:
- painterly natural colors and soft, warm lighting;
- readable, exaggerated terrain and building silhouettes;
- lush vegetation with restrained environmental motion;
- expressive, animation-friendly characters;
- appealing water, mist, smoke, weather, and seasonal variation;
- cozy settlements contrasted with a world capable of hardship;
- a clear elevated third-person view that makes NPC activity readable.
Avoid generic high-fantasy spectacle and avoid treating visual style as a
reason to hide the underlying simulation.
### Initial environment composition
The first public-facing environment should be a compressed simulation garden,
not the complete region:
- a fortress or fortified landmark on an upper ridge;
- a small settlement descending toward water;
- a river, bridge, and waterfall or strong vertical water feature;
- a mill channel and a small cluster of watermills;
- farm terraces and a wooded work area;
- a trade road and distant silhouettes that imply a larger world.
Terrain3D should own broad landforms, ground surfaces, distant landscape, and
large-scale vegetation placement. Roads, river surfaces, waterfalls, cliffs
that require overhangs, bridges, walls, buildings, and other authored landmarks
should use dedicated meshes or appropriate spline/modular tooling.
## Core player loop
The intended loop is:
1. **Observe** people, places, stores, and local problems.
2. **Understand** needs, causes, relationships, and competing priorities.
3. **Influence** people through work, trade, conversation, policy, reputation,
leadership, or force.
4. **Act** personally through movement, interaction, production, exploration,
and combat.
5. **Witness consequences** in the same people and systems.
6. **Adapt** to new opportunities and problems created by those consequences.
The current prototype implements a very early version of observe, autonomous
task choice, physical travel, work completion, resource change, and direct
player assistance. Its abstract task zones are explicitly transitional; the
next world-object migration is documented in
[the ResourceNode plan](RESOURCE_NODE_MIGRATION.md).
## Current technology
- **Engine:** Godot 4.7 project configuration
- **Renderer feature:** Forward Plus
- **Language:** GDScript
- **Main scene:** `res://main.tscn`
- **Terrain:** Terrain3D 1.0.2 is installed and enabled
- **Terrain compatibility floor:** Godot 4.4 according to the bundled extension
- **Version control:** Git
- **Primary branch:** `main`
- **Commit convention:** Conventional Commits
Terrain3D includes binaries, source, editor tooling, examples, and a `demo/`
directory. Most files under `addons/terrain_3d/` and `demo/` are third-party
plugin content, not game architecture.
## Current playable state
### World and player
- `main.tscn` contains a flat approximately 30×30 prototype ground.
- The player is a `CharacterBody3D` represented by placeholder primitive
geometry.
- WASD movement is camera-relative.
- The elevated third-person camera rotates with the mouse and uses smoothed
follow/focus behavior.
- Pressing `E` near a task zone directly contributes food, wood, safety, or
knowledge, or consumes village food.
- `Escape` releases captured mouse input.
### Village simulation
The simulation currently creates three NPCs:
- Amina
- Tarik
- Jasmin
Each receives a random profession from:
- farmer;
- woodcutter;
- guard;
- scholar;
- wanderer.
Each simulated NPC currently stores:
- stable integer ID for the session;
- name and profession;
- hunger and energy;
- strength and intelligence;
- current task and task state;
- task duration and progress;
- simulated position;
- starvation state and duration;
- death state.
The village currently tracks:
- food, initially `20`;
- wood, initially `10`;
- safety, initially `50`;
- knowledge, initially `0`;
- per-resource modifiers;
- per-resource priorities.
### NPC task lifecycle
Task states are:
```text
idle -> traveling -> working -> complete -> idle
```
NPCs currently choose among:
- gather food;
- gather wood;
- patrol;
- study;
- eat;
- rest;
- wander.
Needs override ordinary work. Otherwise a utility-like score combines:
- village priority;
- critical and low resource thresholds;
- profession preference;
- a small random contribution.
The visual NPC walks toward the task's marker. Arrival tells the simulation to
start work. Work progresses on simulation ticks. The village receives the
result only when the task duration completes.
Starvation can reduce productivity and eventually kill an NPC. Dead visuals
stop moving, change appearance, and no longer collide.
### Current task zones
The main scene contains placeholder zones for:
- farm;
- forest;
- guard duty;
- study;
- rest;
- food.
The farm, forest, guard, study, rest, and food locations are represented by
primitive geometry and a few tree assets.
These markers are not the intended long-term work model. Food and wood should
first migrate to finite world `ResourceNode` instances. Storage, rest, study,
and guard behavior should later migrate to target types that match their actual
semantics rather than treating every usable object as a resource source.
### Current UI
A small village panel displays:
- food;
- wood;
- safety;
- knowledge;
- starving NPC count;
- selected village modifiers.
Most deeper reasoning is currently visible only through verbose debug output.
## Runtime architecture
### `simulation/SimNPC.gd`
`SimNPC` is a `RefCounted` simulation model. It owns needs, task selection,
task progression, profession affinity, 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.
### `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;
- creates NPCs;
- coordinates task completion;
- emits village, task, and death signals;
- exposes direct resource-changing methods to the player.
It currently combines clock, orchestration, event publication, population
creation, and some gameplay API responsibilities.
### `world/world_view_manager.gd`
`WorldViewManager` bridges simulation data to visible NPC nodes. It:
- instantiates `NpcVisual` scenes;
- maps task strings to world markers;
- sends targets to visuals;
- reports arrival back to `SimulationManager`;
- applies visual death state.
### `player/npc/NpcVisual.gd`
`NpcVisual` is the active-world representation of an NPC. It currently:
- owns a `NavigationAgent3D`;
- obtains a navigation path;
- moves and rotates toward path points;
- reports arrival;
- applies a simple death presentation.
### `player/player.gd` and `player/camera_rig.gd`
These implement camera-relative character movement, physical interaction
proximity checks, mouse capture, and the elevated follow camera.
### `world/ui/ui.gd`
The UI subscribes to village changes and formats aggregate village state.
## Current runtime flow
```text
SimulationManager tick
|
v
SimNPC updates needs and chooses/advances a task
|
v
npc_task_changed signal
|
v
WorldViewManager selects a task-zone marker
|
v
NpcVisual navigates through the active world
|
v
arrived_at_target signal
|
v
SimulationManager marks the NPC as working
|
v
Later ticks complete work
|
v
SimVillage applies production/consumption
|
v
village_changed signal updates the UI
```
## Repository map
```text
.
├── addons/terrain_3d/ Third-party Terrain3D plugin
├── assets/foliage/ Early tree assets
├── demo/ Terrain3D's bundled demo, not the game
├── docs/ Project context and plans
├── player/
│ ├── camera_rig.gd
│ ├── player.gd
│ ├── PlayerInteraction.gd Currently an empty placeholder
│ └── npc/
│ ├── NpcVisual.gd
│ └── NpcVisual.tscn
├── simulation/
│ ├── SimNPC.gd
│ ├── SimVillage.gd
│ └── SimulationManager.gd
├── world/
│ ├── world_view_manager.gd
│ └── ui/ui.gd
├── main.tscn
└── project.godot
```
## Important limitations and technical debt
These are expected prototype constraints, not necessarily isolated bugs:
- Task names and task-to-zone mappings are duplicated strings.
- Task zones are abstract destinations rather than real usable world objects.
- Mutable simulation state is not serializable through a defined save schema.
- Randomness is not seeded for deterministic replay.
- Simulation time depends on `_process` and a scene-tree node.
- There is no automated test or headless simulation harness.
- Resources are global floating-point counters rather than items in locations
and inventories.
- NPCs do not have homes, schedules, possessions, memories, relationships,
goals, or social knowledge.
- NPC reasoning is logged but not presented through an in-game inspector.
- Active navigation is used as if all agents are local; no simulation LOD exists.
- There is no spatial query/index layer for large populations.
- Simulation, orchestration, and player-facing mutation APIs are concentrated
in `SimulationManager`.
- Path failure and interruption behavior is minimal.
- Terrain3D is enabled but not yet used by the main game scene.
- Combat, companions, factions, politics, trade, rumours, quests, persistence,
and regional travel do not yet exist.
- Placeholder geometry is sufficient for debugging but not for build-in-public
presentation.
## Target simulation architecture
### Separate definitions, state, systems, and presentation
Use four conceptual layers:
1. **Definitions** — immutable data for needs, actions, professions, items,
traits, event types, and locations.
2. **State** — serializable runtime records for NPCs, inventories,
relationships, settlements, factions, and history.
3. **Systems** — clocks, scoring, action resolution, economy, relationships,
events, rumours, and persistence.
4. **Presentation adapters** — Godot nodes that render nearby state, accept
player input, play animation, and provide UI.
Do not make the simulation core depend on `Node3D`, scene paths, animation,
physics frames, or a currently loaded map.
### Stable identity
Every persistent entity should eventually have a stable ID:
- person;
- household;
- item stack or significant item;
- building;
- workplace;
- settlement;
- faction;
- location;
- event.
References between simulation records should use IDs rather than live node
references.
### Fixed and deterministic time
The simulation should advance with an explicit clock and controlled random
source. Given the same starting state, seed, and player commands, a headless
run should be reproducible.
This enables:
- debugging;
- automated scenario tests;
- balancing;
- replaying build-in-public demonstrations;
- comparing performance before and after optimization.
### Scheduled work rather than per-frame thinking
At scale, agents should wake for relevant decisions or scheduled updates rather
than running full reasoning every frame. Examples include:
- need threshold crossed;
- action completed or interrupted;
- new information received;
- path or workplace became unavailable;
- daily schedule boundary;
- relationship or faction event;
- periodic low-frequency maintenance.
### Simulation fidelity levels
The eventual world needs multiple representations:
1. **Active:** full node, physics, navigation, perception, animation, and combat.
2. **Local abstract:** individual position and scheduled actions without
continuous physics.
3. **Distant individual:** data-only people resolving travel and work by time.
4. **Settlement aggregate:** carefully chosen economic or demographic
aggregation for populations that do not currently need individual detail.
Moving between levels must preserve identity and important state. Aggregation
must not erase named relationships or unresolved history.
### Action model
Actions should become data-driven definitions with:
- preconditions;
- candidate targets;
- utility considerations;
- expected duration;
- reservations;
- resource costs;
- effects;
- interruption rules;
- visible presentation hints;
- reason tracing.
The first implementation can remain utility-based. Do not add a complex
planner until current action scoring and sequencing demonstrate a concrete need.
### Events and history
Important outcomes should create structured facts rather than prose-only logs.
A future event record might contain:
```text
event_id
event_type
simulation_time
location_id
actor_ids
target_ids
witness_ids
cause_event_ids
resource_changes
relationship_changes
visibility/secrecy
tags
```
Memories, rumours, reputation, and quests should reference or transform these
facts. Generated text is presentation; structured state remains authoritative.
### Emergent quest principle
A quest candidate should normally require:
- a real unresolved condition;
- an NPC or institution that knows or cares about it;
- a plausible way to communicate it;
- one or more achievable interventions;
- consequences that update the source simulation.
Avoid creating a duplicate quest-only bandit, item, victim, or relationship
when an existing simulated entity can provide the situation.
### Performance principle
Design for scale, but optimize measured bottlenecks:
- keep headless benchmarks;
- record agent count, simulated duration, tick cost, and allocations;
- prefer event-driven/scheduled updates;
- use spatial partitioning for local queries;
- batch homogeneous work when profiling justifies it;
- avoid premature native extensions or data-oriented rewrites before the
behavior model stabilizes.
## Development strategy
The project has two interleaved tracks.
### Living simulation
- deterministic clock and state;
- data-driven needs, professions, and actions;
- inventories and economic flow;
- events, history, relationships, and rumours;
- simulation LOD and performance;
- emergent opportunities and quests.
### Living presentation
- Jajce-inspired terrain composition;
- attractive lighting, water, foliage, and weather;
- readable characters and professions;
- visible work, transport, and consequences;
- interaction UI and simulation inspection;
- combat feel, companions, and command feedback.
Every major systems milestone should produce a shareable visual behavior. Every
visual milestone should support or reveal actual simulation state.
## Immediate milestone: the simulation garden
Before integrating Terrain3D into the playable runtime, migrate food and wood
from abstract zones to finite resource nodes on the current flat test map. This
keeps target-selection and resource-conservation debugging separate from terrain
and navigation changes.
The next coherent visual slice should then aim for:
- one attractive valley section;
- six named villagers;
- three visibly distinct workplaces;
- food and wood as location-based resources;
- visible gathering, carrying, storing, and consuming;
- hunger and energy;
- one understandable shortage crisis;
- direct player assistance and priority influence;
- day/night presentation;
- an in-game reason inspector;
- deterministic replay or scenario reset;
- save/load for the slice;
- a stable 2030 minute session.
Safety and knowledge can remain present, but their deeper production chains
should follow a complete food loop rather than grow in parallel.
## Out of scope until the simulation garden works
- full regional map;
- large city population;
- multiplayer;
- procedural world generation;
- generations and inheritance;
- complete market simulation;
- kingdom-scale diplomacy;
- large battles;
- broad crafting catalog;
- fully generated dialogue;
- multiple large production chains;
- production-quality character customization.
These remain part of the vision, not the next implementation target.
## Agent working guidelines
1. Read this document, [the learning roadmap](LEARNING_ROADMAP.md),
[the ResourceNode migration](RESOURCE_NODE_MIGRATION.md), and
[the build-in-public plan](BUILD_IN_PUBLIC_PLAN.md) before proposing a large
architectural or world-production change.
2. Inspect `git status` and preserve unrelated user changes.
3. Treat `addons/terrain_3d/` and `demo/` as third-party code unless a task
explicitly concerns the plugin.
4. Preserve Godot `.uid` and asset `.import` sidecars. They are tracked project
metadata; `.godot/` is the disposable cache.
5. Keep simulation rules independent of visuals and loaded scenes.
6. Prefer small vertical behavior slices over broad scaffolding.
7. When adding a decision, add a way to inspect why it occurred.
8. When adding persistent state, define how it saves and migrates.
9. When optimizing, include a reproducible benchmark or measurement.
10. Keep new systems data-driven only where it improves reuse or iteration;
avoid abstraction without a demonstrated consumer.
11. Use Conventional Commits.
12. Update these documents when a decision materially changes the vision,
architecture, milestones, or current-state description.
## Definition of “reusable for the future game”
A system is reusable when:
- it has no dependency on this prototype's scene paths or placeholder zones;
- definitions can be supplied as data;
- mutable state is serializable;
- behavior can be exercised headlessly;
- presentation communicates through an adapter or events;
- performance characteristics are measured;
- its public API is documented by real use, not speculative abstraction.
Reusable does not necessarily mean a separate Godot plugin. Extract a library
only after the API has stabilized through use.
## Glossary
- **Active NPC:** A fully represented nearby character with a Godot scene node.
- **Agent:** A simulated decision-making entity, usually an NPC.
- **Event:** A structured fact that something happened in world time.
- **History:** Persisted events and their enduring consequences.
- **Memory:** An NPC's retained interpretation or knowledge of events.
- **Presentation adapter:** Code that translates simulation state into scenes,
animation, audio, UI, and player input.
- **Quest:** A player-facing framing and tracking mechanism for an opportunity
or unresolved condition.
- **Reason trace:** Data explaining the inputs and scores behind a decision.
- **Simulation garden:** A small, attractive, controlled world used to validate
deep systems and make them watchable.
- **Simulation LOD:** Changing computational fidelity based on relevance while
preserving important identity and state.
- **World event:** A consequential state transition that may be witnessed,
remembered, communicated, and acted upon.
+517
View File
@@ -0,0 +1,517 @@
# The Steward — ResourceNode Migration Plan
## Decision
The current task zones are temporary prototype scaffolding.
NPCs and the player should ultimately interact with actual world objects rather
than travel to one abstract marker per task:
```text
Current:
gather_food -> FarmZone -> village food increases
First migration:
gather_food -> choose BerryBush_01 -> travel to its interaction point
-> extract berries -> bush amount decreases
-> village food increases by the extracted amount
```
This migration should happen on the current flat map before the Jajce
environment is integrated. It is easier to debug target selection, reservation,
depletion, and task completion without terrain and navigation changes happening
at the same time.
The old zones remain only as a temporary fallback while actions are migrated.
They are not part of the target architecture.
## Why this is the next systems step
Resource nodes provide the first concrete bridge between:
- abstract simulation decisions;
- spatial target selection;
- active-world navigation;
- finite world state;
- visible cause and effect;
- player and NPC use of the same objects;
- future inventories, ownership, regeneration, and persistence.
They also make build-in-public footage more legible. A viewer can understand a
villager walking to a specific berry bush and depleting it more easily than a
villager entering an invisible “food zone.”
## Scope the first version correctly
The first `ResourceNode` supports only depletable material sources:
- berry bush or crop source -> food;
- tree or wood pile -> wood.
Do not initially use `ResourceNode` for:
- beds or campfires;
- guard posts;
- study locations;
- food storage;
- homes;
- generic workstations.
Those objects share target-selection behavior but not resource-extraction
semantics.
The likely family of world targets is:
```text
WorldActionTarget
├── ResourceNode finite extraction: bush, tree, ore
├── ActivitySite rest, patrol, study, socialize
├── StorageNode deposit and withdraw items
└── Workstation transform inputs into outputs
```
Do not implement this complete inheritance tree now. Build `ResourceNode` from
real requirements, then extract shared target behavior when a second target
type proves what is actually common.
## Fit with the current code
The existing flow is:
```text
SimNPC chooses a task string
-> SimulationManager emits npc_task_changed
-> WorldViewManager maps the task to one Marker3D
-> NpcVisual travels to that marker
-> arrival tells SimulationManager to start work
-> task duration completes
-> SimVillage applies a hard-coded task result
```
The migrated flow should become:
```text
SimNPC chooses an action
-> target resolver finds available matching ResourceNodes
-> one target is reserved for the NPC
-> NPC simulation stores the target's stable ID
-> NpcVisual travels to the target interaction point
-> arrival begins work
-> completion extracts the actual available amount
-> village receives exactly that amount
-> target updates or becomes depleted
-> reservation is released
```
During migration:
```text
gather_food -> prefer matching ResourceNode -> fallback FarmZone
gather_wood -> prefer matching ResourceNode -> fallback ForestZone
other tasks -> existing zones
```
Fallbacks should log clearly so accidental reliance on zones is visible.
## Repository-specific file layout
Keep scripts beside their feature scenes, matching the repository's existing
feature-oriented structure:
```text
world/
└── resource_nodes/
├── ResourceNode.gd
├── ResourceNode.gd.uid
└── ResourceNode.tscn
```
Add instances under the current `main.tscn` while developing:
```text
Main
├── ResourceNodes
│ ├── BerryBush_01
│ ├── BerryBush_02
│ ├── Tree_01
│ └── Tree_02
└── TaskZone temporary fallback
```
When `JajceWorld` is integrated, move or recreate the world-object instances
under:
```text
JajceWorld
└── WorldObjects
└── ResourceNodes
```
Target discovery must not depend on that exact scene path. Resource nodes should
register through a group or a small registry.
## First ResourceNode contract
The first node should answer:
- What is my stable world-object ID?
- Which action can use me?
- Which resource do I yield?
- How much remains?
- How much can one completed action extract?
- Am I enabled and usable?
- Who, if anyone, has reserved me?
- Where should an active character stand?
- Did extraction change or deplete me?
Recommended initial data:
```gdscript
class_name ResourceNode
extends Node3D
signal amount_changed(node_id: StringName, amount_remaining: float)
signal depleted(node_id: StringName)
signal reservation_changed(node_id: StringName, agent_id: int)
@export var node_id: StringName
@export var action_id: StringName = &"gather_food"
@export var resource_id: StringName = &"food"
@export var amount_remaining: float = 10.0
@export var yield_per_action: float = 2.0
@export var enabled: bool = true
@export var can_npcs_use: bool = true
@export var can_player_use: bool = true
@export var hide_visual_when_depleted: bool = false
@onready var interaction_point: Marker3D = $InteractionPoint
```
Use `StringName` IDs as a small migration step away from duplicated free-form
`String` values. Later, action and resource definitions can become custom
`Resource` assets without changing every world node's conceptual contract.
### Avoid duplicated state
Do not store a separately mutable `is_depleted` flag in the first version.
Derive it:
```gdscript
func is_depleted() -> bool:
return amount_remaining <= 0.0
```
If regeneration or non-depleting sources later require more states, introduce
them deliberately.
### Extraction result is authoritative
Extraction must return the amount actually removed:
```gdscript
func extract(requested_amount: float = yield_per_action) -> float:
if not can_extract():
return 0.0
var extracted := minf(requested_amount, amount_remaining)
amount_remaining -= extracted
# Emit signals and update presentation.
return extracted
```
The village receives this returned amount. Do not let both `ResourceNode` and
`SimVillage.apply_npc_task()` independently award the configured yield.
### Stable IDs
`node_id` must be:
- non-empty;
- unique inside the world;
- stable across save/load;
- independent of display name and scene path.
For the first hand-authored nodes, explicit IDs such as
`&"berry_bush_01"` are acceptable. Add startup validation for duplicates.
## Scene structure
The first reusable scene can remain small:
```text
ResourceNode (Node3D)
├── Visual (Node3D)
├── InteractionPoint (Marker3D)
└── DebugLabel (Label3D)
```
Use `Node3D` for `Visual` rather than requiring `MeshInstance3D`; this permits a
mesh, imported model, particles, or multiple visual children later.
The debug label should be controlled by a project/debug setting or inspector
toggle rather than a compile-time constant. It should show:
- node ID;
- resource;
- remaining amount;
- reservation owner;
- depleted/disabled state.
## Reservation rules
Without reservations, several NPCs can choose one nearly depleted bush and all
walk toward it.
The first version needs a single-user reservation:
```text
available -> reserved by agent -> in use -> released
```
A reservation must be released when:
- the task completes;
- the NPC dies;
- the target depletes;
- travel fails;
- the action is interrupted or replaced;
- the target is disabled;
- the NPC or visual is removed.
Reservation is not ownership. Ownership, permission, crime, and social
consequences belong to later systems.
## Target selection
The first resolver may choose the nearest available matching node. It must still
return a reason trace:
```text
candidate BerryBush_01: accepted, distance 8.2
candidate BerryBush_02: rejected, reserved by NPC 2
candidate BerryBush_03: rejected, depleted
selected BerryBush_01
```
Future scoring can include:
- travel cost;
- remaining yield;
- danger;
- tool requirements;
- ownership and permission;
- crowding;
- profession;
- expected completion time;
- knowledge of the target.
Do not implement those before the nearest-available version works.
## Simulation-state boundary
The first `ResourceNode` is a Godot world node and may temporarily own its
remaining amount. That is acceptable for the migration prototype, but it is not
the final persistence boundary.
Preserve these rules now:
- `SimNPC` stores a target ID, not a `Node` reference;
- simulation decisions do not store `NodePath`;
- visual movement receives only a resolved position;
- target extraction is coordinated through one adapter/registry;
- node state has an obvious serialization representation.
Later, authoritative resource state can move into serializable simulation
records while `ResourceNode` becomes its active-world presentation.
## Required changes by existing file
### `simulation/SimNPC.gd`
- add a selected target ID;
- clear it when returning to idle or changing actions;
- do not store `ResourceNode` references;
- preserve current task lifecycle during the first migration.
### `world/world_view_manager.gd`
- query available resource nodes for `gather_food` and `gather_wood`;
- reserve the selected target;
- send its interaction position to `NpcVisual`;
- use the old marker only when no valid node exists;
- distinguish arrival at a specific target from arrival at a generic task zone.
Target discovery can begin here, but should move into a focused resolver or
registry once selection has meaningful rules.
### `simulation/SimulationManager.gd`
- coordinate target completion and reservation release;
- extract from the selected node once;
- give `SimVillage` the actual result;
- handle interruption, death, and failure cleanup;
- prevent old hard-coded production from also firing.
### `simulation/SimVillage.gd`
- add an explicit resource-delta method;
- separate generic resource application from task-name matching;
- retain current action effects for non-migrated tasks.
### `player/player.gd`
- eventually resolve and use the same world targets as NPCs;
- do not add a second player-only harvest path with different rules.
Player use can follow NPC migration rather than block its first test.
## Migration phases
### Phase 1 — ResourceNode scene and registration
- create `ResourceNode.gd` and `ResourceNode.tscn`;
- create a `ResourceNodes` parent in `main.tscn`;
- place two bushes and two trees;
- give every node a stable unique ID;
- show debug state;
- expose group/registry discovery.
**Exit:** nodes register, validate IDs, change amount, deplete, and update debug
presentation without involving NPC logic.
### Phase 2 — Food target selection and reservation
- migrate only `gather_food`;
- find nearest available bush;
- reserve it;
- store its ID on the NPC;
- navigate to its interaction point;
- release on interruption and death;
- retain `FarmZone` fallback with a visible warning.
**Exit:** two NPCs do not select the same single-user bush, and an NPC can
replan when its chosen bush becomes unavailable.
### Phase 3 — Authoritative completion
- extract only after work duration completes;
- return actual extracted amount;
- apply that amount to village food;
- prevent double production;
- deplete and hide/alter the source as configured;
- choose a new source for later tasks.
**Exit:** resource conservation holds:
```text
total bush decrease == total village food increase
```
for the initial gather-food scenario.
### Phase 4 — Wood and player parity
- migrate `gather_wood`;
- configure tree visuals and yields;
- route player interaction through the same extraction contract;
- add clear feedback for unavailable or depleted targets.
**Exit:** food and wood no longer require their abstract zones during normal
play. NPC and player extraction obey the same availability and depletion rules.
### Phase 5 — Jajce world placement
- place bushes and trees as real world objects in `JajceWorld`;
- keep their IDs stable;
- validate interaction points against terrain and navigation;
- use the old task zones only for rest, eat, patrol, and study;
- verify target discovery does not rely on parent scene paths.
**Exit:** the existing food and wood loops work after moving from the flat map
to Terrain3D.
### Phase 6 — Remove the remaining zone model
This is not part of the first ResourceNode implementation.
Introduce the next concrete target types:
- storage for eating and item transfer;
- activity sites for rest, study, and guard work;
- workstations when production recipes begin.
Remove each old zone only when its replacement has:
- target selection;
- active interaction position;
- reservation/capacity;
- completion or ongoing-use behavior;
- failure/interruption handling;
- debug visibility;
- save representation.
**Exit:** `TaskZone` and task-to-marker mapping are deleted, not merely unused.
## Test scenarios
At minimum, exercise:
1. one NPC and one bush;
2. two NPCs and one bush;
3. one NPC and two bushes at different distances;
4. selected bush depletes before arrival;
5. selected bush depletes exactly on completion;
6. NPC dies while traveling;
7. NPC dies while working;
8. navigation fails;
9. node is disabled while reserved;
10. fallback zone is used because no matching node exists;
11. player and NPC contend for the same source;
12. duplicate node IDs are detected.
Use a fixed seed and concise reason tracing where possible.
## Video opportunities
The migration naturally creates strong short-form demonstrations:
- “My NPCs stopped walking to invisible work zones.”
- “Two hungry villagers want the same berry bush.”
- “This bush contains the food—the UI counter no longer creates it.”
- “What happens when the closest resource is already reserved?”
- “A depleted forest changes what every woodcutter decides.”
Show the beautiful result first, then reveal target selection, reservation, and
resource conservation through debug presentation.
## Immediate implementation order
1. Create `world/resource_nodes/ResourceNode.gd`.
2. Create `world/resource_nodes/ResourceNode.tscn`.
3. Add `Main/ResourceNodes`.
4. Place `BerryBush_01`, `BerryBush_02`, `Tree_01`, and `Tree_02`.
5. Add stable IDs and registration validation.
6. Verify extraction and depletion manually.
7. Add target ID to `SimNPC`.
8. Migrate `gather_food` target selection.
9. Add reservation and cleanup.
10. Apply actual extracted yield on completion.
11. Add fixed-seed regression scenarios.
12. Migrate wood.
13. Give the player the same extraction path.
14. Continue with the Jajce world scaffold.
## Definition of done for the first migration
- Food and wood use actual finite world objects.
- NPCs select available targets and reserve them.
- NPC state stores stable target IDs rather than nodes or scene paths.
- Active visuals navigate to explicit interaction points.
- Work duration completes before extraction.
- Village resources increase only by the amount actually extracted.
- Depletion changes availability and presentation.
- Failure, interruption, and death release reservations.
- Old food and forest zones remain only as logged fallbacks.
- The player and NPCs can use the same extraction contract.
- Behavior is inspectable and covered by reproducible scenarios.
+18 -2
View File
@@ -18,7 +18,8 @@ var profession: String = ""
var current_path: PackedVector3Array = [] var current_path: PackedVector3Array = []
var path_index := 0 var path_index := 0
var current_target := Vector3.INF var current_target := Vector3.INF
var arrival_distance := 0.6 var arrival_distance := 1.5
var stuck_counter := 0
var is_dead_visual := false var is_dead_visual := false
var dead_material := StandardMaterial3D.new() var dead_material := StandardMaterial3D.new()
@@ -60,7 +61,8 @@ func set_target_position(pos: Vector3) -> void:
path_index = 0 path_index = 0
if current_path.is_empty(): if current_path.is_empty():
debug_log("Path is empty. Will report arrival/failure fallback.") debug_log("Path is empty, stuck counter will handle fallback.")
nav_agent.target_position = global_position
else: else:
debug_log("New target: %s Path points: %s" % [pos, current_path.size()]) debug_log("New target: %s Path points: %s" % [pos, current_path.size()])
@@ -84,6 +86,10 @@ func _physics_process(delta: float) -> void:
if current_path.is_empty() or path_index >= current_path.size(): if current_path.is_empty() or path_index >= current_path.size():
velocity = Vector3.ZERO velocity = Vector3.ZERO
move_and_slide() move_and_slide()
stuck_counter += 1
if stuck_counter > 90:
debug_log("Stuck with no valid path, reporting arrival")
_report_arrival_once()
return return
var next_pos := current_path[path_index] var next_pos := current_path[path_index]
@@ -95,6 +101,7 @@ func _physics_process(delta: float) -> void:
return return
var direction := to_next.normalized() var direction := to_next.normalized()
var prev_pos := global_position
velocity.x = direction.x * move_speed velocity.x = direction.x * move_speed
velocity.z = direction.z * move_speed velocity.z = direction.z * move_speed
@@ -102,6 +109,15 @@ func _physics_process(delta: float) -> void:
move_and_slide() move_and_slide()
if global_position.distance_squared_to(prev_pos) < 0.001:
stuck_counter += 1
if stuck_counter > 90:
debug_log("Stuck while navigating, reporting arrival")
_report_arrival_once()
return
else:
stuck_counter = 0
var target_angle := atan2(direction.x, direction.z) var target_angle := atan2(direction.x, direction.z)
rotation.y = lerp_angle(rotation.y, target_angle, rotation_speed * delta) rotation.y = lerp_angle(rotation.y, target_angle, rotation_speed * delta)
+2
View File
@@ -13,6 +13,8 @@ albedo_color = Color(1, 0.22352941, 1, 1)
material = SubResource("StandardMaterial3D_d844k") material = SubResource("StandardMaterial3D_d844k")
[node name="NpcVisual" type="CharacterBody3D"] [node name="NpcVisual" type="CharacterBody3D"]
collision_layer = 2
collision_mask = 1
script = ExtResource("1_ixfoq") script = ExtResource("1_ixfoq")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."] [node name="CollisionShape3D" type="CollisionShape3D" parent="."]
+6
View File
@@ -28,6 +28,8 @@ var task_duration := 0.0
var task_progress := 0.0 var task_progress := 0.0
var task_complete := true var task_complete := true
var target_id: StringName = &"" var target_id: StringName = &""
var retry_count := 0
var last_task := ""
func _init( func _init(
_id: int, _id: int,
@@ -227,6 +229,9 @@ func calculate_task_score(
if profession == preferred_profession: if profession == preferred_profession:
score += 2.0 score += 2.0
if task_name == last_task:
score -= 1.5
score += randf_range(0.0, 0.5) score += randf_range(0.0, 0.5)
if DEBUG_LOGS: if DEBUG_LOGS:
@@ -242,6 +247,7 @@ func calculate_task_score(
return score return score
func set_task(task: String, duration: float) -> void: func set_task(task: String, duration: float) -> void:
last_task = current_task
current_task = task current_task = task
task_duration = duration task_duration = duration
task_progress = 0.0 task_progress = 0.0
+12
View File
@@ -98,6 +98,7 @@ func simulate_tick() -> void:
village.apply_npc_task(npc) village.apply_npc_task(npc)
village_was_changed = true village_was_changed = true
npc.retry_count = 0
npc.task_complete = true npc.task_complete = true
npc.task_state = SimNPC.TASK_STATE_IDLE npc.task_state = SimNPC.TASK_STATE_IDLE
@@ -110,6 +111,15 @@ func simulate_tick() -> void:
if DEBUG_LOGS: if DEBUG_LOGS:
print("[SimulationManager] ", npc.npc_name, " gathered food while starving and is going to eat immediately.") print("[SimulationManager] ", npc.npc_name, " gathered food while starving and is going to eat immediately.")
if npc.task_state == SimNPC.TASK_STATE_TRAVELING and npc.target_id == &"":
npc.retry_count += 1
if npc.retry_count >= 5:
if DEBUG_LOGS:
print("[SimulationManager] ", npc.npc_name, " stuck traveling without target for ", npc.retry_count, " attempts, replanning")
npc.task_state = SimNPC.TASK_STATE_IDLE
npc.current_task = ""
npc.retry_count = 0
if DEBUG_LOGS: if DEBUG_LOGS:
print( print(
npc.npc_name, npc.npc_name,
@@ -176,6 +186,8 @@ func notify_npc_arrived(npc_id: int) -> void:
print("[SimulationManager] ", npc.npc_name, " arrived but target ", npc.target_id, " is unavailable, replanning") print("[SimulationManager] ", npc.npc_name, " arrived but target ", npc.target_id, " is unavailable, replanning")
release_npc_reservation(npc.id) release_npc_reservation(npc.id)
npc.task_state = SimNPC.TASK_STATE_IDLE npc.task_state = SimNPC.TASK_STATE_IDLE
npc.current_task = ""
npc.retry_count += 1
return return
npc.start_working() npc.start_working()
+20 -2
View File
@@ -88,10 +88,19 @@ func update_npc_targets() -> void:
visual.set_target_position(resource_pos) visual.set_target_position(resource_pos)
continue continue
var target = get_target_for_task(npc.current_task) var task = npc.current_task
if task == "wander":
var offset = Vector3(randf_range(-6.0, 6.0), 0.0, randf_range(-6.0, 6.0))
visual.set_target_position(visual.global_position + offset)
continue
if task in ["gather_food", "gather_wood"]:
continue
var target = get_target_for_task(task)
if target == null: if target == null:
debug_log("No target for task: %s" % npc.current_task) debug_log("No target for task: %s" % task)
continue continue
visual.set_target_position(target.global_position) visual.set_target_position(target.global_position)
@@ -136,6 +145,7 @@ func _try_get_resource_target(npc: SimNPC, task: String) -> Variant:
return null return null
npc.target_id = node.node_id npc.target_id = node.node_id
npc.retry_count = 0
debug_log("%s reserved %s for %s" % [npc.npc_name, node.node_id, task]) debug_log("%s reserved %s for %s" % [npc.npc_name, node.node_id, task])
return node.interaction_point.global_position return node.interaction_point.global_position
@@ -152,6 +162,14 @@ func _on_npc_task_changed(npc: SimNPC, old_task: String, new_task: String) -> vo
visual.set_target_position(resource_pos) visual.set_target_position(resource_pos)
return return
if new_task == "wander":
var offset := Vector3(randf_range(-6.0, 6.0), 0.0, randf_range(-6.0, 6.0))
visual.set_target_position(visual.global_position + offset)
return
if new_task in ["gather_food", "gather_wood"]:
return
var target := get_target_for_task(new_task) var target := get_target_for_task(new_task)
if target != null: if target != null: