neon-sprawl/docs/plans/NEO-60-implementation-plan.md

110 lines
8.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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 registrys 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`.