diff --git a/docs/plans/NEO-36-implementation-plan.md b/docs/plans/NEO-36-implementation-plan.md new file mode 100644 index 0000000..804c95b --- /dev/null +++ b/docs/plans/NEO-36-implementation-plan.md @@ -0,0 +1,81 @@ +# NEO-36 — Implementation plan + +## Story reference + +| Field | Value | +|--------|--------| +| **Key** | NEO-36 | +| **Title** | E2.M1: Read-only skill catalog HTTP projection + Bruno | +| **Linear** | https://linear.app/neon-sprawl/issue/NEO-36/e2m1-read-only-skill-catalog-http-projection-bruno | +| **Module** | [E2.M1 — SkillDefinitionRegistry](../decomposition/modules/E2_M1_SkillDefinitionRegistry.md) | + +## Kickoff clarifications + +No questions were put to the user during kickoff. + +**Reason:** The issue specifies the response fields (`id`, `displayName`, `category`, `allowedXpSourceKinds`), excludes mutations and client polish, and points at “match server routing style.” The repo already exposes a read-only world projection at `GET /game/world/interactables` (`InteractablesWorldApi`) with a versioned JSON envelope—this story can mirror that pattern. **`ISkillDefinitionRegistry`** and the frozen prototype trio (`salvage`, `refine`, `intrusion`) are present on `main` from **NEO-33–NEO-35** (`prototype_skills.json`, `SkillDefinitionRegistryTests`). Linear may still list **blockedBy NEO-35**; the implementation branch is based on **`main`** where that work is already merged. + +**If you want a different public path** than the default in Technical approach (below), say so before implementation. + +## Goal, scope, and out-of-scope + +**Goal:** Expose a stable, read-only JSON endpoint that returns all loaded prototype skill definitions for dev tooling, Bruno, and manual QA, backed by **`ISkillDefinitionRegistry`** without duplicating catalog truth outside the server. + +**In scope (from Linear):** + +- One GET route consistent with existing APIs (under `/game/`, same style as other read-only projections). +- JSON rows including **`id`**, **`displayName`**, **`category`**, **`allowedXpSourceKinds`** per skill. +- Bruno request(s) under `bruno/neon-sprawl-server/`, mirroring existing collections (e.g. `interaction/Get interactables list.bru`). +- `docs/manual-qa/NEO-36.md` checklist (endpoint is user-visible for QA per repo rules). + +**Out of scope (from Linear):** + +- Mutations or authoring APIs. +- Client UX / debug panel (optional and not required). + +## Acceptance criteria checklist + +- [ ] Bruno (and/or automated test) can call the endpoint against a running dev server and observe the **three frozen ids** (`salvage`, `refine`, `intrusion`). +- [ ] `docs/manual-qa/NEO-36.md` exists with a short checklist for hitting the endpoint. + +## Technical approach + +1. **Route:** Add **`GET /game/world/skill-definitions`** (parallel to **`/game/world/interactables`**) as a parameterless read. If “world” reads odd for catalog-only data, acceptable alternative is **`GET /game/content/skill-definitions`**—pick one in implementation and keep Bruno/tests aligned; default **`/game/world/skill-definitions`** for consistency with the other static registry projection. + +2. **Response shape:** Follow the interactables pattern: top-level **`schemaVersion`** (const `1` for first ship of this contract) plus an array (e.g. **`skills`**) of objects with **`id`**, **`displayName`**, **`category`**, **`allowedXpSourceKinds`**. Order skills by **`id`** ordinal to match **`ISkillDefinitionRegistry.GetDefinitionsInIdOrder()`**. + +3. **Implementation:** New static **`SkillDefinitionsWorldApi`** (or **`SkillCatalogApi`**) in `Game/Skills/` that injects **`ISkillDefinitionRegistry`**, maps rows to DTOs (either anonymous projection types or small sealed DTO types in a `*Dtos.cs` file next to the API), returns **`Results.Json`**. Wire **`app.MapSkillDefinitionsWorldApi()`** (name TBD) from **`Program.cs`** next to other `Map*Api` calls. + +4. **Bruno:** New folder `bruno/neon-sprawl-server/skill-definitions/` with **`Get skill definitions.bru`** (and optional **`folder.bru`** if the collection uses per-folder metadata like `ability-cast`). Reuse **`{{baseUrl}}`** from environments; tests assert 200, JSON, schema version, array length ≥ 3, and presence of the three frozen ids (mirror style of **`Get interactables list.bru`**). + +5. **Automated tests:** Add **`SkillDefinitionsWorldApiTests`** (or equivalent) using **`InMemoryWebApplicationFactory`**, **`HttpClient.GetAsync`**, **`ReadFromJsonAsync`** on the response DTO—same structure as **`InteractablesWorldApiTests`**, asserting the trio and ordering. + +## Files to add + +| Path | Purpose | +|------|---------| +| `server/NeonSprawl.Server/Game/Skills/SkillDefinitionsWorldApi.cs` (or chosen name) | `Map*` extension registering GET route; maps registry → JSON. | +| `server/NeonSprawl.Server/Game/Skills/SkillDefinitionsListDtos.cs` (or inline records in API file if kept tiny) | Versioned response + row DTOs for JSON serialization. | +| `server/NeonSprawl.Server.Tests/Game/Skills/SkillDefinitionsWorldApiTests.cs` | HTTP integration: 200, schema, three ids, id order. | +| `bruno/neon-sprawl-server/skill-definitions/Get skill definitions.bru` | Manual / Bruno verification against dev server. | +| `bruno/neon-sprawl-server/skill-definitions/folder.bru` | Optional; match Bruno folder conventions used elsewhere. | +| `docs/manual-qa/NEO-36.md` | Checklist: start server, call GET, confirm trio (and note Bruno path). | + +## Files to modify + +| Path | Rationale | +|------|-----------| +| `server/NeonSprawl.Server/Program.cs` | Register the new `Map*Api()` alongside existing game APIs. | + +## Tests + +| Test file | What it covers | +|-----------|------------------| +| `server/NeonSprawl.Server.Tests/Game/Skills/SkillDefinitionsWorldApiTests.cs` | **Integration:** `GET` new route via **`InMemoryWebApplicationFactory`**; **`ReadFromJsonAsync`**; assert **`schemaVersion`**, ordered **`id`** list equals **`intrusion`**, **`refine`**, **`salvage`** (ordinal sort); spot-check one row’s **`allowedXpSourceKinds`**. **AAA** comments per [csharp-style](../../.cursor/rules/csharp-style.md#unit-and-integration-tests-arrange-act-assert). | + +Bruno scripts are manual verification; they complement but do not replace the automated test above. + +## Open questions / risks + +- **Linear relation:** Issue may still show **blockedBy NEO-35**; code on **`main`** already includes the registry. If **`main`** lags a fork, rebase onto **`main`** after NEO-35 merge. +- **Path naming:** If product prefers **`/content/`** over **`/world/`**, swap in one place (route + Bruno + tests) before shipping.