# NEO-60 — Implementation plan ## Story reference | Field | Value | |--------|--------| | **Key** | NEO-60 | | **Title** | E3.M1: GET world resource-node-definitions + Bruno | | **Linear** | https://linear.app/neon-sprawl/issue/NEO-60/e3m1-get-world-resource-node-definitions-bruno | | **Module** | [E3.M1 — ResourceNodeAndGatherLoop](../decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md) · Epic 3 Slice 2 · backlog **E3M1-04** | | **Branch** | `NEO-60-e3m1-get-world-resource-node-definitions-bruno` | | **Precursor** | [NEO-59](https://linear.app/neon-sprawl/issue/NEO-59) — `IResourceNodeDefinitionRegistry` + DI (**Done** on `main`) | | **Pattern** | [NEO-53](https://linear.app/neon-sprawl/issue/NEO-53) — `GET /game/world/item-definitions` | ## Kickoff clarifications | Topic | Question | Agent recommendation | Answer | |--------|----------|----------------------|--------| | **Yield projection** | Nested `yield` object vs flat `itemId`/`quantity` on the row? | **Nested `yield: { itemId, quantity }`** — mirrors catalog split (node def + yield table) and matches NEO-59 registry shape; room for multi-yield later. | **User:** nested yield object. | | **Row id JSON name** | `id` vs `nodeDefId`? | **`id`** — same HTTP convention as item/skill definition endpoints (NEO-53 / NEO-36). | **User:** `id`. | | **Manual QA doc** | Add `docs/manual-qa/NEO-60.md`? | **Yes** — user-visible HTTP surface; mirror NEO-53. | **User:** yes. | ## Goal, scope, and out-of-scope **Goal:** Expose a stable, read-only JSON endpoint that returns all loaded prototype resource-node definitions (with yield summary) for dev tooling, Bruno, manual QA, and future client/codegen, backed by **`IResourceNodeDefinitionRegistry`** without duplicating catalog truth outside the server. **In scope (from Linear + [E3M1-04](E3M1-prototype-backlog.md#e3m1-04--get-world-resource-node-definitions)):** - **`GET /game/world/resource-node-definitions`** with versioned envelope (`schemaVersion` **1**, **`nodes`** array). - Each row: **`id`**, **`displayName`**, **`gatherLens`**, **`maxGathers`**, nested **`yield`** with **`itemId`** and **`quantity`** (camelCase JSON). - Nodes ordered by **`id`** ordinal, matching **`GetDefinitionsInIdOrder()`**. - Bruno folder `bruno/neon-sprawl-server/resource-node-definitions/`. - `docs/manual-qa/NEO-60.md`. - `server/README.md` section. - API integration tests (AAA). **Out of scope (from Linear + backlog):** - Per-instance remaining capacity / depletion state ([NEO-61](https://linear.app/neon-sprawl/issue/NEO-61)). - Gather engine, inventory mutation, interact wiring ([NEO-62+](E3M1-prototype-backlog.md)). - Client HUD. ## Acceptance criteria checklist - [x] GET returns all **four** prototype defs with **`schemaVersion`** **1** and yield summary fields needed for QA. - [x] Bruno happy path documented and runnable against dev server. - [x] Automated API tests (AAA) assert four ids in order and spot-check node + yield metadata. ## Technical approach 1. **Route:** **`GET /game/world/resource-node-definitions`** — parameterless read; inject **`IResourceNodeDefinitionRegistry`** only (not `ResourceNodeCatalog`). 2. **Response shape:** Top-level **`schemaVersion`** (`ResourceNodeDefinitionsListResponse.CurrentSchemaVersion` = **1**) plus **`nodes`** array. Each row maps from **`ResourceNodeDefRow`** + joined **`ResourceYieldRow`** via **`TryGetYield`**: **`id`** ← `NodeDefId`, **`displayName`**, **`gatherLens`**, **`maxGathers`**, **`yield`** ← `{ itemId, quantity }`. Prototype catalog guarantees one yield per node at load time; if **`TryGetYield`** fails for an enumerated def, treat as programmer error (should not occur post-NEO-58 validation). 3. **Ordering:** Build list from **`registry.GetDefinitionsInIdOrder()`**. Tests assert exact id sequence (ordinal): `prototype_bio_mat_gamma`, `prototype_resource_node_alpha`, `prototype_subsurface_vein_beta`, `prototype_urban_bulk_delta`. 4. **Implementation:** New **`ResourceNodeDefinitionsWorldApi`** + **`ResourceNodeDefinitionsListDtos.cs`** in `Game/Gathering/` (mirror [`ItemDefinitionsWorldApi`](../../server/NeonSprawl.Server/Game/Items/ItemDefinitionsWorldApi.cs)). Wire **`app.MapResourceNodeDefinitionsWorldApi()`** from **`Program.cs`** next to **`MapItemDefinitionsWorldApi()`**. 5. **Bruno:** `bruno/neon-sprawl-server/resource-node-definitions/` with **`Get resource node definitions.bru`** + **`folder.bru`**. Tests: 200, JSON, **`schemaVersion` 1**, **`nodes.length === 4`**, ascending id order, frozen four ids in registry order, spot-check **`prototype_resource_node_alpha`** (`gatherLens` `consumer_salvage`, `maxGathers` 10, `yield.itemId` `scrap_metal_bulk`, `yield.quantity` 1) and **`prototype_urban_bulk_delta`** (`yield.quantity` 5). 6. **Docs:** `docs/manual-qa/NEO-60.md` (mirror NEO-53). Update **`server/README.md`** resource-node section with GET + curl. Update [E3_M1](E3_M1_ResourceNodeAndGatherLoop.md) **Related implementation slices** and [documentation_and_implementation_alignment.md](documentation_and_implementation_alignment.md) E3.M1 row when landed. Flip E3M1-04 acceptance checkboxes in [E3M1-prototype-backlog.md](E3M1-prototype-backlog.md). ## Files to add | Path | Purpose | |------|---------| | `server/NeonSprawl.Server/Game/Gathering/ResourceNodeDefinitionsWorldApi.cs` | `Map*` extension registering GET route; maps registry → JSON (node + nested yield). | | `server/NeonSprawl.Server/Game/Gathering/ResourceNodeDefinitionsListDtos.cs` | Versioned response + row/yield DTOs for JSON serialization. | | `server/NeonSprawl.Server.Tests/Game/Gathering/ResourceNodeDefinitionsWorldApiTests.cs` | HTTP integration: 200, schema v1, four ids in order, spot-check rows + yields. | | `bruno/neon-sprawl-server/resource-node-definitions/Get resource node definitions.bru` | Manual / Bruno verification against dev server. | | `bruno/neon-sprawl-server/resource-node-definitions/folder.bru` | Bruno folder metadata (match `item-definitions`). | | `docs/manual-qa/NEO-60.md` | Checklist: start server, curl GET, confirm four ids + spot-check yield. | ## Files to modify | Path | Rationale | |------|-----------| | `server/NeonSprawl.Server/Program.cs` | Register `MapResourceNodeDefinitionsWorldApi()` alongside other game world APIs. | | `server/README.md` | Document `GET /game/world/resource-node-definitions`, curl example, links to plan/manual QA/Bruno. | | `docs/decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md` | **Related implementation slices** — HTTP read model bullet (NEO-60). | | `docs/decomposition/modules/documentation_and_implementation_alignment.md` | E3.M1 row — note NEO-60 HTTP projection when landed. | | `docs/plans/E3M1-prototype-backlog.md` | E3M1-04 acceptance checkboxes when landed. | ## Tests | Test file | What it covers | |-----------|----------------| | `server/NeonSprawl.Server.Tests/Game/Gathering/ResourceNodeDefinitionsWorldApiTests.cs` | **Integration:** `GET /game/world/resource-node-definitions` via **`InMemoryWebApplicationFactory`**; **`ReadFromJsonAsync`**; assert **`schemaVersion` 1**; **`nodes`** count **4**; ordered **`id`** list equals frozen four (ordinal); spot-check **`prototype_resource_node_alpha`** (`displayName` “Prototype Salvage Heap”, `gatherLens` `consumer_salvage`, `maxGathers` 10, `yield.itemId` `scrap_metal_bulk`, `yield.quantity` 1) and **`prototype_urban_bulk_delta`** (`gatherLens` `urban_bulk`, `yield.quantity` 5). **AAA** per [csharp-style](../../.cursor/rules/csharp-style.md). | Bruno scripts complement automated tests for manual dev-server verification. ## Open questions / risks | Question / risk | Agent recommendation | Status | |-----------------|----------------------|--------| | **Linear blockedBy NEO-59** | Branch from **`main`** where NEO-59 is merged (confirmed at kickoff). | **adopted** | | **Missing yield at map time** | Rely on NEO-58 load parity (every node has exactly one yield row); no silent omission in HTTP mapper. | **adopted** | | **Instance depletion in response** | Omit remaining gathers — instance state is NEO-61; this endpoint is definition catalog only. | **adopted** | None blocking. ## Decisions (kickoff) - **Nested `yield` object** — `{ itemId, quantity }` per row; joined from registry’s separate TryGet methods. - **Row `id` JSON name** — consistent with item/skill world definition endpoints. - **`nodes` top-level array** — parallel to `items` / `skills` naming on sibling routes. - **Manual QA doc included** — `docs/manual-qa/NEO-60.md`.