114 lines
8.8 KiB
Markdown
114 lines
8.8 KiB
Markdown
# NEO-68 — Implementation plan
|
|
|
|
## Story reference
|
|
|
|
| Field | Value |
|
|
|--------|--------|
|
|
| **Key** | NEO-68 |
|
|
| **Title** | E3.M2: GET world recipe-definitions + Bruno |
|
|
| **Linear** | https://linear.app/neon-sprawl/issue/NEO-68/e3m2-get-world-recipe-definitions-bruno |
|
|
| **Module** | [E3.M2 — RefinementAndRecipeExecution](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md) · Epic 3 Slice 3 · backlog **E3M2-04** |
|
|
| **Branch** | `NEO-68-get-world-recipe-definitions-bruno` |
|
|
| **Precursor** | [NEO-67](https://linear.app/neon-sprawl/issue/NEO-67) — `IRecipeDefinitionRegistry` + DI (**Done** on `main`) |
|
|
| **Pattern** | [NEO-53](https://linear.app/neon-sprawl/issue/NEO-53) — `GET /game/world/item-definitions` |
|
|
| **Client counterpart** | [NEO-74](https://linear.app/neon-sprawl/issue/NEO-74) — E3S5-03 craft UI + recipe list (blocked by this issue; server-only in this story) |
|
|
| **Blocks** | [NEO-74](https://linear.app/neon-sprawl/issue/NEO-74), [NEO-73](https://linear.app/neon-sprawl/issue/NEO-73) (Linear relations) |
|
|
|
|
## Kickoff clarifications
|
|
|
|
| Topic | Question | Agent recommendation | Answer |
|
|
|--------|----------|----------------------|--------|
|
|
| **JSON row fields** | Expose required six + I/O only vs optional nullables? | **Required six + `inputs`/`outputs` only** — prototype rows omit `requiredSkillLevel` / `stationTag`; mirror NEO-53 “loaded fields only”. | **User:** required six + I/O only. |
|
|
| **Response envelope** | Array property name? | **`recipes`** (with `schemaVersion` **1**) — parallel to NEO-36 `skills` / NEO-53 `items`; matches content vocabulary. | **Adopted** — no objection; follows precedent. |
|
|
| **Route path** | `/game/world/…` vs `/game/content/…`? | **`GET /game/world/recipe-definitions`** — E3M2 backlog + parallel to item/skill defs. | **Adopted** — backlog. |
|
|
| **Manual QA doc** | Add `docs/manual-qa/NEO-68.md`? | **Yes** — player-visible HTTP; NEO-53 pattern. | **User:** yes. |
|
|
|
|
## Goal, scope, and out-of-scope
|
|
|
|
**Goal:** Expose a stable, read-only JSON endpoint that returns all eight loaded prototype recipe definitions for Bruno, manual QA, client preview ([NEO-74](https://linear.app/neon-sprawl/issue/NEO-74)), and tooling — backed by **`IRecipeDefinitionRegistry`** without duplicating catalog truth.
|
|
|
|
**In scope (from Linear + [E3M2-04](E3M2-prototype-backlog.md#e3m2-04--get-world-recipe-definitions)):**
|
|
|
|
- **`GET /game/world/recipe-definitions`** with versioned envelope (`schemaVersion` **1**, **`recipes`** array).
|
|
- Row fields: **`id`**, **`displayName`**, **`recipeKind`**, **`requiredSkillId`**, **`inputs`**, **`outputs`** (I/O rows: **`itemId`**, **`quantity`**; camelCase JSON).
|
|
- Recipes ordered by **`id`** ordinal, matching **`GetDefinitionsInIdOrder()`**.
|
|
- Bruno folder `bruno/neon-sprawl-server/recipe-definitions/`.
|
|
- `docs/manual-qa/NEO-68.md`.
|
|
- `server/README.md` section.
|
|
- API integration tests (AAA).
|
|
|
|
**Out of scope (from Linear + backlog):**
|
|
|
|
- Per-player craft history or cooldown state.
|
|
- Optional schema fields (`requiredSkillLevel`, `stationTag`) until content ships them.
|
|
- Craft execution ([NEO-69](https://linear.app/neon-sprawl/issue/NEO-69)).
|
|
- Godot client ([NEO-74](https://linear.app/neon-sprawl/issue/NEO-74)).
|
|
|
|
## Acceptance criteria checklist
|
|
|
|
- [x] GET returns all **eight** prototype recipes with **`schemaVersion`** **1** and input/output summary fields needed for QA.
|
|
- [x] Bruno happy path documented and runnable against dev server.
|
|
- [x] Automated API tests (AAA) assert eight ids, id order, and spot-check metadata + I/O rows.
|
|
|
|
## Technical approach
|
|
|
|
1. **Route:** **`GET /game/world/recipe-definitions`** — parameterless read; inject **`IRecipeDefinitionRegistry`** only (not `RecipeDefinitionCatalog`).
|
|
|
|
2. **Response shape:** Top-level **`schemaVersion`** (`RecipeDefinitionsListResponse.CurrentSchemaVersion` = **1**) plus **`recipes`** array. Each row maps from **`RecipeDefRow`**: **`id`**, **`displayName`**, **`recipeKind`**, **`requiredSkillId`**, **`inputs`**, **`outputs`**. Do **not** emit optional null fields on this contract.
|
|
|
|
3. **I/O projection:** Map **`RecipeIoRow`** → JSON with **`itemId`**, **`quantity`** (same keys as content schema).
|
|
|
|
4. **Ordering:** Build list from **`registry.GetDefinitionsInIdOrder()`** (already ordinal by id). Tests assert exact id sequence (frozen eight from [E3.M2 snapshot](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md#prototype-slice-3-freeze-e3m2-01)):
|
|
|
|
`make_armor_quick`, `make_contract_token`, `make_field_stim_batch`, `make_field_stim_mk0`, `make_prototype_armor`, `make_survey_drone_kit`, `refine_scrap_efficient`, `refine_scrap_standard`.
|
|
|
|
5. **Implementation:** New **`RecipeDefinitionsWorldApi`** + **`RecipeDefinitionsListDtos.cs`** in `Game/Crafting/` (mirror [`ItemDefinitionsWorldApi`](../../server/NeonSprawl.Server/Game/Items/ItemDefinitionsWorldApi.cs)). Wire **`app.MapRecipeDefinitionsWorldApi()`** from **`Program.cs`** next to **`MapItemDefinitionsWorldApi()`**.
|
|
|
|
6. **Bruno:** `bruno/neon-sprawl-server/recipe-definitions/` with **`Get recipe definitions.bru`** + **`folder.bru`**. Tests: 200, JSON, **`schemaVersion` 1**, **`recipes.length === 8`**, ascending id order, **exact frozen-eight registry sequence** (mirror NEO-53 item-definitions Bruno), spot-check **`refine_scrap_standard`** (`recipeKind` `process`, inputs/outputs) and **`make_field_stim_mk0`** (`recipeKind` `make`, multi-input).
|
|
|
|
7. **Docs:** `docs/manual-qa/NEO-68.md` (mirror NEO-53). Update **`server/README.md`** recipe section with GET + curl. Update [E3_M2](E3_M2_RefinementAndRecipeExecution.md) **Related implementation slices** and [documentation_and_implementation_alignment.md](documentation_and_implementation_alignment.md) E3.M2 row when landed.
|
|
|
|
## Files to add
|
|
|
|
| Path | Purpose |
|
|
|------|---------|
|
|
| `server/NeonSprawl.Server/Game/Crafting/RecipeDefinitionsWorldApi.cs` | `Map*` extension registering GET route; maps registry → JSON. |
|
|
| `server/NeonSprawl.Server/Game/Crafting/RecipeDefinitionsListDtos.cs` | Versioned response + row + I/O DTOs for JSON serialization. |
|
|
| `server/NeonSprawl.Server.Tests/Game/Crafting/RecipeDefinitionsWorldApiTests.cs` | HTTP integration: 200, schema v1, eight ids in order, spot-check rows + I/O. |
|
|
| `bruno/neon-sprawl-server/recipe-definitions/Get recipe definitions.bru` | Manual / Bruno verification against dev server. |
|
|
| `bruno/neon-sprawl-server/recipe-definitions/folder.bru` | Bruno folder metadata (match `item-definitions`). |
|
|
| `docs/manual-qa/NEO-68.md` | Checklist: start server, curl GET, confirm eight ids + spot-check I/O. |
|
|
| `docs/plans/NEO-68-implementation-plan.md` | This plan. |
|
|
|
|
## Files to modify
|
|
|
|
| Path | Rationale |
|
|
|------|-----------|
|
|
| `server/NeonSprawl.Server/Program.cs` | Register `MapRecipeDefinitionsWorldApi()` alongside other game world definition APIs. |
|
|
| `server/README.md` | Document `GET /game/world/recipe-definitions`, curl example, links to plan/manual QA/Bruno. |
|
|
| `docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md` | **Related implementation slices** — HTTP read model bullet (NEO-68). |
|
|
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | E3.M2 row — note NEO-68 HTTP projection when landed. |
|
|
|
|
## Tests
|
|
|
|
| Test file | What it covers |
|
|
|-----------|----------------|
|
|
| `server/NeonSprawl.Server.Tests/Game/Crafting/RecipeDefinitionsWorldApiTests.cs` | **Integration:** `GET /game/world/recipe-definitions` via **`InMemoryWebApplicationFactory`**; **`ReadFromJsonAsync`**; assert **`schemaVersion` 1**; **`recipes`** count **8**; ordered **`id`** list equals **`FrozenEightInIdOrder`** (shared constant referenced by manual QA / Bruno); spot-check **`refine_scrap_standard`** and **`make_field_stim_mk0`**. **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-67** | Branch from **`main`** where NEO-67 is merged (confirmed at kickoff). | **adopted** |
|
|
| **Optional fields later** | When content adds `requiredSkillLevel` / `stationTag`, bump **`schemaVersion`** or extend v1 with documented null semantics in a follow-up issue. | **deferred** |
|
|
| **Client not in this story** | Cross-link **NEO-74** in plan; Bruno + manual QA are not prototype-complete per [full-stack epic decomposition](../../.cursor/rules/full-stack-epic-decomposition.md). | **adopted** |
|
|
|
|
## Decisions (kickoff)
|
|
|
|
- **Required six + I/O only** — no optional null fields on v1 contract (user confirmed).
|
|
- **`recipes` array key** — consistent with entity naming in sibling world-definition endpoints.
|
|
- **`docs/manual-qa/NEO-68.md` included** (user confirmed).
|
|
- **Registry-only injection** — HTTP layer depends on **`IRecipeDefinitionRegistry`**, not **`RecipeDefinitionCatalog`**.
|