602 lines
22 KiB
Markdown
602 lines
22 KiB
Markdown
# The Steward — ResourceNode Migration Plan
|
|
|
|
## Progress Overview
|
|
|
|
| Phase | Description | Status |
|
|
|-------|-------------|--------|
|
|
| **1** | ResourceNode scene + registration | ✅ Complete |
|
|
| **2** | Food target selection + reservation | ✅ Complete |
|
|
| **3** | Authoritative extraction on completion | ✅ Complete |
|
|
| **4a** | Wood target selection (NPC) | ✅ Complete |
|
|
| **4b** | Player parity (extraction contract) | ✅ Complete |
|
|
| **5** | Jajce world placement | 🔶 Scaffold/navigation proof complete |
|
|
| **6** | Replace remaining activity markers | 🔶 Resource zones removed; activity targets pending |
|
|
|
|
### Key divergences from the original plan
|
|
|
|
- **Zone fallback removed** (Phase 3): `gather_food`/`gather_wood` no longer fall back to `FarmZone`/`ForestZone` markers when no resource node is available. The original plan called for a logged fallback; instead, the NPC goes idle (via `navigation_failed` → wander) to guarantee resource conservation. The obsolete farm and forest scene nodes have now also been deleted. The remaining markers are temporary targets for eating, rest, study, and patrol—not resource fallbacks.
|
|
- **`navigation_failed` signal** (discovered during Phase 4): Added a separate signal pathway to distinguish genuine arrival from navigation failure. The original plan treated all "arrivals" uniformly.
|
|
- **Stale-path prevention** (discovered during Phase 4): Added `path_request_id` + `path_pending` to reject async path results that arrive after a new target was set or the NPC died.
|
|
- **Duplicate node_id detection** (discovered during Phase 4): Added startup validation that disables nodes with duplicate IDs and pushes an error.
|
|
- **Simulation-owned resource authority** (architecture gate): Resource amount,
|
|
reservation, and enabled state now live in `ResourceStateRecord`; ResourceNode
|
|
binds by stable ID and can unload without deleting authoritative state.
|
|
|
|
### Bugs found and fixed
|
|
|
|
| # | Bug | Phase found | Fix |
|
|
|---|-----|-------------|-----|
|
|
| 1 | Navigation failure: empty path caused NPC to idle permanently | Phase 3 stump | `_report_arrival_once()` on empty path (later replaced by `navigation_failed` signal) |
|
|
| 2 | Unavailable-target deadlock: replan didn't trigger `npc_task_changed` | Phase 3 | Set `current_task = ""` so tick detects task change and emits signal |
|
|
| 3 | Zone fallback broke conservation (fixed during Phase 4 removal) | Phase 3 | Removed zone fallback for gather tasks; NPC stays idle if no node available |
|
|
| 4 | NPC collision blocking: NPCs physically blocked each other at shared destinations | Phase 3 | `collision_layer=2`, `collision_mask=1` on NpcVisual; `arrival_distance` 0.6→1.5 (later reverted to 0.6 after navigation_failed signal) |
|
|
| 5 | Repetitive task selection: NPC chose same task every tick | Phase 3 | `last_task` tracking with -1.5 score penalty when same task repeats |
|
|
| 6 | Wander didn't wander: wandered to rest marker instead of random offset | Phase 3 | Random ±6 unit offset from current position |
|
|
| 7 | `:=` infers from Variant: warnings treated as errors from `odig` | Phase 2-3 | Use `=` instead of `:=` for calls returning `Variant` |
|
|
| 8 | `Variant as StringName`: invalid cast | Phase 2-3 | Check for `null` on Dictionary `.get()` instead of casting |
|
|
| 9 | Empty-path re-entrancy: immediate arrival report caused re-entrant signal chain | Phase 4 | Defer to `stuck_counter`/`stuck_time` in `_physics_process` instead |
|
|
| 10 | Stale async path: `await physics_frame` result used after target or death changed | Phase 4 | `path_request_id`/`path_pending` guard |
|
|
| 11 | Stolen extraction: NPC extracted after reservation was lost | Phase 4 | Guard extraction with the authoritative resource record's reservation |
|
|
| 12 | Depleted target retained an NPC reservation after player harvesting | Phase 4 | Authoritative extraction now releases its reservation on depletion |
|
|
|
|
## 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 resource zones have been removed. Temporary activity markers remain
|
|
only for actions whose real target type does not exist yet. They are not
|
|
fallbacks and 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
|
|
|
|
Before this migration, the flow was:
|
|
|
|
```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 is:
|
|
|
|
```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
|
|
```
|
|
|
|
Current target behavior:
|
|
|
|
```text
|
|
gather_food -> matching ResourceNode -> recover/wander when unavailable
|
|
gather_wood -> matching ResourceNode -> recover/wander when unavailable
|
|
other tasks -> existing zones
|
|
```
|
|
|
|
Food and wood zone fallback was deliberately removed to preserve resource
|
|
conservation.
|
|
|
|
## 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
|
|
└── ActivityMarkers temporary eat/rest/study/patrol targets
|
|
```
|
|
|
|
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 initial_amount: float = 10.0
|
|
@export var yield_per_action: float = 2.0
|
|
@export var initial_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
|
|
var state: ResourceStateRecord
|
|
```
|
|
|
|
Action IDs now use canonical `SimulationIds` values and resolve through
|
|
validated `ActionDefinition` resources. ResourceNode's exported amount and
|
|
enabled values are initialization defaults only; its bound
|
|
`ResourceStateRecord` owns live mutable state.
|
|
|
|
### Avoid duplicated state
|
|
|
|
Do not store a separately mutable `is_depleted` flag in the first version.
|
|
Derive it:
|
|
|
|
```gdscript
|
|
func is_depleted() -> bool:
|
|
return state.is_depleted() if state != null else initial_amount <= 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 *[complete]*
|
|
|
|
- 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 *[complete]*
|
|
|
|
- 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~~ *(removed in Phase 4,
|
|
see divergences above)*
|
|
|
|
**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 *[complete]*
|
|
|
|
- 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 *[complete]*
|
|
|
|
**Wood migration** ✅ complete:
|
|
- `gather_wood` resolves loaded presentation candidates through
|
|
`ActionTargetResolver` and `ActiveWorldAdapter`
|
|
- Tree_01 (15 wood, yield 3.0) and Tree_02 (12 wood, yield 3.0) placed
|
|
- Extraction follows same path as food (reserve → travel → work → extract → village resource delta)
|
|
- Zone fallback removed; NPC goes idle if no tree available
|
|
|
|
**Player parity** ✅ complete:
|
|
- Player interaction resolves the nearest player-usable resource node by its
|
|
interaction point.
|
|
- `SimulationManager.harvest_resource_node()` uses the same authoritative
|
|
`ResourceStateRecord.extract()` contract and applies exactly the returned
|
|
amount.
|
|
- Player harvesting can contend with an NPC reservation; depletion releases
|
|
that reservation so the NPC can recover cleanly.
|
|
- Depleted and disabled targets provide explicit feedback.
|
|
- Food and forest zone bindings were removed from the player.
|
|
- `tests/resource_node_player_parity_test.gd` verifies food conservation, wood
|
|
conservation, depletion, and player/NPC contention.
|
|
|
|
**Exit:** food and wood no longer require their abstract zones during normal
|
|
play. NPC and player extraction obey the same availability, depletion, and
|
|
authoritative-yield rules.
|
|
|
|
### Phase 5 — Jajce world placement
|
|
|
|
This phase is a bounded handoff to
|
|
[the build-in-public visual plan](BUILD_IN_PUBLIC_PLAN.md), not permission for
|
|
open-ended environment polish. Complete the scaffold, placement, and navigation
|
|
proof, then pass the learning roadmap's architecture gate before the beauty
|
|
baseline.
|
|
|
|
- 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.
|
|
|
|
**Progress:** `556d0fd` adds the standalone Jajce scaffold, four stable-ID
|
|
resources, dedicated Terrain3D seed data, remaining activity markers, and 24
|
|
headless navigation-route checks. Runtime `main.tscn` migration is intentionally
|
|
deferred until after the architecture gate, so this phase is not yet complete.
|
|
|
|
**Exit:** the existing food and wood loops work after moving from the flat map
|
|
to Terrain3D.
|
|
|
|
### Phase 6 — Replace the remaining activity-marker 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.
|
|
|
|
**Progress:** the `TaskZone` container and obsolete `FarmZone`/`ForestZone`
|
|
nodes have been deleted from `main.tscn`. The surviving nodes live under
|
|
`ActivityMarkers` to make their transitional role explicit. Task-to-marker
|
|
mapping still exists for eating, rest, study, and patrol, so this phase is not
|
|
complete.
|
|
|
|
The former `FoodZone` is now `PantryMarker` backed by simulation-owned
|
|
`StorageStateRecord` state and explicit deposit/withdraw transactions. A
|
|
dedicated authored StorageNode presentation is still pending.
|
|
|
|
**Exit:** temporary activity markers 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. no matching node causes recovery without zone production; ✅ implemented
|
|
11. player and NPC contend for the same source; ✅ automated
|
|
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.
|
|
|
|
## Implementation status
|
|
|
|
| # | Task | Status | Commits |
|
|
|---|------|--------|---------|
|
|
| 1 | Create `ResourceNode.gd` and `ResourceNode.tscn` | ✅ Done | `9cd38a1` |
|
|
| 2 | Add `Main/ResourceNodes` | ✅ Done | `9cd38a1` |
|
|
| 3 | Place BerryBush_01, BerryBush_02, Tree_01, Tree_02 | ✅ Done | `9cd38a1` |
|
|
| 4 | Add stable IDs and registration validation | ✅ Done | `9cd38a1`, `1837bc0` |
|
|
| 5 | Verify extraction and depletion manually | ✅ Done | `9cd38a1` |
|
|
| 6 | Add target ID to `SimNPC` | ✅ Done | `9cd38a1` |
|
|
| 7 | Migrate `gather_food` target selection | ✅ Done | `9cd38a1`, `c6033b6` |
|
|
| 8 | Add reservation and cleanup | ✅ Done | `9cd38a1`, `b8752f8` |
|
|
| 9 | Apply actual extracted yield on completion | ✅ Done | `c6033b6` |
|
|
| 10 | Add navigation failure handling (replaces regression scenarios) | ✅ Done | `b8752f8` |
|
|
| 11 | Migrate wood | ✅ Done | `c6033b6`, `8049c52` |
|
|
| 12 | Give the player the same extraction path | ✅ Done | `326df51` |
|
|
| 13 | Continue with the Jajce world scaffold | 🔶 Placement proof done; runtime migration pending | `556d0fd` |
|
|
|
|
## Definition of done for the first migration
|
|
|
|
| Criterion | Status |
|
|
|-----------|--------|
|
|
| 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 zone fallbacks are no longer used by NPCs | ✅ (fallback removed by design) |
|
|
| The player and NPCs can use the same extraction contract | ✅ |
|
|
| Behavior is inspectable and covered by reproducible scenarios | ✅ (debug logging, node overlay, headless player-parity test) |
|