# E3.M1 — Prototype story backlog (ResourceNodeAndGatherLoop) Working backlog for **Epic 3 — Slice 2** ([gather nodes and outputs](../decomposition/epics/epic_03_crafting_economy.md#epic-3-slice-2)). Decomposition and contracts: [E3.M1 — ResourceNodeAndGatherLoop](../decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md). **Labels (Linear):** every issue **`E3.M1`**; add **`server`** / **`Story`** as listed per issue. **Precursor (do not re-scope):** [NEO-41](https://linear.app/neon-sprawl/issue/NEO-41) grants **`salvage`** skill XP on successful **`POST …/interact`** when interactable **`kind`** is **`resource_node`** — **no** `GatherResult`, yields, inventory, or depletion yet. Slice 2 **migrates** XP into the gather engine and adds item grants + node capacity. **Upstream (must be landed):** [E3.M3](../decomposition/modules/E3_M3_ItemizationAndInventorySchema.md) Slice 1 — frozen item catalog, **`IPlayerInventoryStore`**, inventory operations ([NEO-50](https://linear.app/neon-sprawl/issue/NEO-50)–[NEO-56](https://linear.app/neon-sprawl/issue/NEO-56)). **E1.M3** interact + range ([NEO-9](https://linear.app/neon-sprawl/issue/NEO-9), [NEO-25](https://linear.app/neon-sprawl/issue/NEO-25)). **E2.M2** grant path ([NEO-38](https://linear.app/neon-sprawl/issue/NEO-38)). **Prototype gather spine (frozen in E3M1-01):** **four** `ResourceNodeDef` rows — one per [gather lens](../game-design/gathering.md#node-lenses-design-facing) — each with a fixed **`ResourceYieldTable`** entry awarding **`scrap_metal_bulk`** (primary material from [E3.M3 freeze](../decomposition/modules/E3_M3_ItemizationAndInventorySchema.md#prototype-slice-1-freeze-neo-50)). World placement stays on **`PrototypeInteractableRegistry`** until zone graph ([E4.M1](../decomposition/modules/E4_M1_ZoneGraphAndTravelRules.md)) ships. **Linear issues (created):** attach `docs/plans/NEO-*-implementation-plan.md` on the same branch as implementation work. | Slug | Linear | |------|--------| | E3M1-01 | [NEO-57](https://linear.app/neon-sprawl/issue/NEO-57) | | E3M1-02 | [NEO-58](https://linear.app/neon-sprawl/issue/NEO-58) | | E3M1-03 | [NEO-59](https://linear.app/neon-sprawl/issue/NEO-59) | | E3M1-04 | [NEO-60](https://linear.app/neon-sprawl/issue/NEO-60) | | E3M1-05 | [NEO-61](https://linear.app/neon-sprawl/issue/NEO-61) | | E3M1-06 | [NEO-62](https://linear.app/neon-sprawl/issue/NEO-62) | | E3M1-07 | [NEO-63](https://linear.app/neon-sprawl/issue/NEO-63) | | E3M1-08 | [NEO-64](https://linear.app/neon-sprawl/issue/NEO-64) | **Dependency graph in Linear:** E3M1-02 blocked by E3M1-01. E3M1-03 blocked by E3M1-02. E3M1-04 blocked by E3M1-03. E3M1-05 blocked by E3M1-03. E3M1-06 blocked by E3M1-05 and E3M1-03 (inventory from E3.M3). E3M1-07 blocked by E3M1-06. E3M1-08 blocked by E3M1-06 (may parallel E3M1-07). --- ## Story order (recommended) | Order | Slug | Depends on | |-------|------|------------| | 1 | **E3M1-01** | E3.M3 item catalog (NEO-50) | | 2 | **E3M1-02** | E3M1-01 | | 3 | **E3M1-03** | E3M1-02 | | 4 | **E3M1-04** | E3M1-03 | | 5 | **E3M1-05** | E3M1-03 | | 6 | **E3M1-06** | E3M1-05, E3.M3 inventory store (NEO-54+) | | 7 | **E3M1-07** | E3M1-06 | | 8 | **E3M1-08** | E3M1-06 | **Downstream (separate modules):** [E3.M2](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md) recipes consume gathered materials; [NEO-42](https://linear.app/neon-sprawl/issue/NEO-42) refine XP hook runs after craft success; [E4.M2](../decomposition/modules/E4_M2_SpawnEcologyController.md) owns respawn/ecology pacing later. --- ## Kickoff decisions (decomposition defaults) | Topic | Decision | Rationale | |-------|----------|-----------| | Node count | **4** frozen defs (epic allows 4–6) | One per gather lens; enough variety without bloating prototype registry | | Gather wire surface | Keep **`POST …/interact`** ([E1.M3](../decomposition/modules/E1_M3_InteractionAndTargetingLayer.md)) | No separate gather HTTP in prototype; E3.M1 owns resolution **after** interact eligibility | | Yields | **Fixed** quantities per gather (no RNG) | Deterministic tests; probability tables are pre-production | | Depleted node | **`Allowed: false`** + **`reasonCode: node_depleted`** | Prevents “free” taps; clearer than silent no-op | | XP | **Migrate** NEO-41 inline grant into gather engine on successful yield | Single call site for gather completion side effects | | Skill per node | All prototype nodes award **`salvage`** + **`activity`** (10 XP) | Matches NEO-41; per-lens skill mapping is content follow-up | --- ### E3M1-01 — Prototype ResourceNodeDef + yield tables + schemas + CI **Goal:** Lock content shape and CI validation for **four** frozen resource nodes and their **fixed** yield tables before server load. **In scope** - `content/schemas/resource-node-def.schema.json` and `content/schemas/resource-yield-table.schema.json` (or a single catalog schema if simpler — pick one in the plan PR and document). - `content/resource-nodes/prototype_resource_nodes.json` with stable **`nodeDefId`** values aligned to interactable ids (see module doc freeze table). - `content/resource-nodes/prototype_resource_yields.json` (or embedded yields — plan decides) referencing only **`scrap_metal_bulk`** from the E3.M3 freeze. - `scripts/validate_content.py`: schema validation, duplicate ids, exact four-node allowlist, **`gatherLens`** coverage, cross-check that every yield **`itemId`** exists in `content/items/`. - Designer note in [E3_M1](../decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md) + `content/README.md`. **Out of scope** - Server loader, depletion store, inventory grants, HTTP. **Acceptance criteria** - [ ] PR gate validates resource-node JSON against schema. - [ ] Exactly four prototype node ids; duplicate `nodeDefId` fails CI. - [ ] Every yield references a valid frozen item id. - [ ] Stable id list documented in module doc freeze box. --- ### E3M1-02 — Server resource-node catalog load (fail-fast) **Goal:** Disk → host: startup load of `content/resource-nodes/*.json` with CI-parity validation. **In scope** - Loader + catalog types under `server/NeonSprawl.Server/Game/Gathering/` (path TBD in plan). - Fail-fast on malformed files, duplicate ids, unknown yield item ids (cross-check **`IItemDefinitionRegistry`** at startup). - Unit tests (AAA) for loader happy path and failure modes. **Out of scope** - Per-instance depletion, HTTP, interact wiring. **Acceptance criteria** - [ ] Server refuses boot when resource-node catalog invalid (mirror E3.M3 / E2.M1 catalog behavior). - [ ] Catalog resolves `ResourceNodeDef` and yield row by `nodeDefId`. --- ### E3M1-03 — Resource node definition registry + DI **Goal:** Injectable `IResourceNodeDefinitionRegistry` consumed by gather engine and interact integration. **In scope** - `ResourceNodeDefinitionRegistry` implementation + DI registration. - Unit tests (AAA): lookup, unknown id, yield metadata, capacity fields. **Out of scope** - HTTP, persistence of depletion state. **Acceptance criteria** - [ ] Host resolves registry from DI; unknown `nodeDefId` distinguishable from valid ids in tests. --- ### E3M1-04 — GET world resource-node-definitions **Goal:** Versioned read-only catalog for Bruno, client preview, and tooling — mirror [NEO-53](https://linear.app/neon-sprawl/issue/NEO-53) item-definitions pattern. **In scope** - `GET /game/world/resource-node-definitions` + DTOs + API tests (AAA). - Bruno folder `bruno/neon-sprawl-server/resource-node-definitions/`. - `server/README.md` section. **Out of scope** - Per-instance remaining capacity (instance state is E3M1-05). **Acceptance criteria** - [ ] GET returns all four prototype defs with schema version field and yield summary fields needed for QA. - [ ] Bruno happy path documented. --- ### E3M1-05 — World node instance depletion store **Goal:** Server-authoritative **remaining gathers** per node instance (`interactableId`), with structured deny when depleted. **In scope** - `IResourceNodeInstanceStore` (or equivalent) keyed by **`interactableId`** (matches [NEO-25](https://linear.app/neon-sprawl/issue/NEO-25) descriptor ids). - Initialize capacity from `ResourceNodeDef.maxGathers` (or equivalent field name frozen in E3M1-01). - Decrement on successful gather commit; **`node_depleted`** when zero. - Persistence: in-memory + Postgres + migration (mirror NEO-54 / NEO-8 policy). - Unit + integration tests (AAA). **Out of scope** - Item grants, skill XP, HTTP beyond store tests. **Acceptance criteria** - [ ] First gather on a fresh node succeeds; repeated gathers until capacity returns **`node_depleted`** without going negative. - [ ] Postgres + in-memory parity tests. --- ### E3M1-06 — GatherResult engine (yields + inventory + skill XP) **Goal:** Single server operation that resolves **`GatherResult`**: validate node + capacity, grant items via **`PlayerInventoryOperations`**, award **`salvage`** XP via **`SkillProgressionGrantOperations`**, commit depletion. **In scope** - `GatherOperations` / `GatherResult` types in `server/NeonSprawl.Server/Game/Gathering/`. - Stable deny reason codes: `node_depleted`, `inventory_full`, `unknown_node`, etc. - Unit tests (AAA) with stub inventory + progression stores. - Document **`GatherResult`** shape in module doc (server-internal until promoted to wire). **Out of scope** - `InteractionApi` wiring (E3M1-07). - Client HUD. - Regen / respawn timers ([E4.M2](../decomposition/modules/E4_M2_SpawnEcologyController.md)). **Acceptance criteria** - [ ] Successful gather adds configured **`scrap_metal_bulk`** quantity to player bag. - [ ] Full bag returns stable `reasonCode` without partial silent loss. - [ ] Skill XP grant uses same rules as NEO-38 / NEO-41 (`salvage`, `activity`, 10 XP). - [ ] Depletion store decrements atomically with successful grant. --- ### E3M1-07 — Interact path integration + registry expansion + Bruno **Goal:** Replace NEO-41 inline XP block with gather engine; register **four** prototype resource nodes in **`PrototypeInteractableRegistry`**; extend Bruno/manual QA for gather → inventory. **In scope** - `InteractionApi`: on `resource_node` kind, call gather engine; map engine deny → `InteractionResponse` with **`Allowed: false`** + `reasonCode` (including **`node_depleted`**). - Remove duplicate XP grant path once gather engine covers it. - Add three new interactable anchors (positions TBD in plan; keep **`prototype_resource_node_alpha`** anchor). - Bruno: move near node → interact → GET inventory shows **`scrap_metal_bulk`**; depleted node deny. - `server/README.md`; integration tests (AAA). **Out of scope** - Godot gather HUD (optional follow-up). - New gather-specific HTTP route. **Acceptance criteria** - [ ] In-range interact on fresh node increases inventory quantity and **`salvage`** XP. - [ ] Depleted node returns **`node_depleted`** without inventory or XP change. - [ ] Terminal interact unchanged (no gather side effects). - [ ] Bruno documents happy path + depletion deny for at least one node. --- ### E3M1-08 — `resource_gathered` / `gather_node_depleted` telemetry hook sites **Goal:** Comment-only hooks on gather success and depletion for future E9.M1 catalog events. **In scope** - Hook placement in gather engine (and interact deny branch if applicable). - `TODO(E9.M1)` comments; no production logging. - README + module doc pointer. **Out of scope** - Telemetry ingest, dashboards. **Acceptance criteria** - [ ] Hook sites documented in code and `server/README.md`. - [ ] Matches Epic 3 Slice 2 telemetry vocabulary in [epic_03](../decomposition/epics/epic_03_crafting_economy.md#epic-3-slice-2). --- ## After this backlog - Decompose **[E3.M2](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md)** Slice 3 (recipes + craft HTTP) once E3M1-06+ land and inventory holds gathered materials. - Track delivery in Linear; keep `blockedBy` links synchronized if scope changes. - For each issue kickoff, add `docs/plans/{NEO-XX}-implementation-plan.md` per [story-kickoff](../../.cursor/rules/story-kickoff.md). - Add `docs/manual-qa/{NEO-XX}.md` when interact + inventory surfaces are testable (E3M1-07+). ## Related docs - [E3_M1_ResourceNodeAndGatherLoop.md](../decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md) - [E3M3-prototype-backlog.md](E3M3-prototype-backlog.md) - [epic_03_crafting_economy.md](../decomposition/epics/epic_03_crafting_economy.md) - [gathering.md](../game-design/gathering.md) - [NEO-41 implementation plan](NEO-41-implementation-plan.md)