NEO-78: Add kickoff implementation plan for ability-definitions GET.
parent
83d1dde4ea
commit
374b4b5a53
|
|
@ -0,0 +1,119 @@
|
|||
# NEO-78 — Implementation plan
|
||||
|
||||
## Story reference
|
||||
|
||||
| Field | Value |
|
||||
|--------|--------|
|
||||
| **Key** | NEO-78 |
|
||||
| **Title** | E5M1-04: GET /game/world/ability-definitions |
|
||||
| **Linear** | https://linear.app/neon-sprawl/issue/NEO-78/e5m1-04-get-gameworldability-definitions |
|
||||
| **Module** | [E5.M1 — CombatRulesEngine](../decomposition/modules/E5_M1_CombatRulesEngine.md) · Epic 5 Slice 1 · backlog **E5M1-04** |
|
||||
| **Branch** | `NEO-78-get-world-ability-definitions` |
|
||||
| **Precursor** | [NEO-79](https://linear.app/neon-sprawl/issue/NEO-79) — `IAbilityDefinitionRegistry` + DI (**Done** on `main`) |
|
||||
| **Pattern** | [NEO-68](https://linear.app/neon-sprawl/issue/NEO-68) — `GET /game/world/recipe-definitions`; [NEO-53](https://linear.app/neon-sprawl/issue/NEO-53) — item definitions world GET |
|
||||
| **Client counterpart** | None (server-only); optional client enrichment in [NEO-85](https://linear.app/neon-sprawl/issue/NEO-85) via this HTTP route |
|
||||
|
||||
## Kickoff clarifications
|
||||
|
||||
| Topic | Question | Agent recommendation | Answer |
|
||||
|--------|----------|----------------------|--------|
|
||||
| **Manual QA doc** | Add `docs/manual-qa/NEO-78.md`? | **Yes** — NEO-68 pattern for world GET HTTP. | **User:** skip — manual QA only when there are client-facing changes; this story is server-only (Bruno + automated tests suffice). |
|
||||
| **Optional `abilityKind`** | Emit when row has no kind? | **Omit property when null** — NEO-68 “loaded fields only”; all four prototype rows have a kind today. | **User:** omit when null. |
|
||||
|
||||
No other blocking decisions — route path, envelope (`schemaVersion` **1**, **`abilities`** array), row fields, registry injection, and frozen four-id order are specified in [E5M1-04](E5M1-prototype-backlog.md#e5m1-04--get-gameworldability-definitions) and [E5.M1 freeze box](../decomposition/modules/E5_M1_CombatRulesEngine.md#prototype-slice-1-freeze-e5m1-01).
|
||||
|
||||
## Goal, scope, and out-of-scope
|
||||
|
||||
**Goal:** Expose a stable, read-only JSON endpoint that returns all four loaded prototype ability definitions for Bruno, tooling, and optional client preview ([NEO-85](https://linear.app/neon-sprawl/issue/NEO-85)) — backed by **`IAbilityDefinitionRegistry`** without duplicating catalog truth.
|
||||
|
||||
**In scope (from Linear + [E5M1-04](E5M1-prototype-backlog.md#e5m1-04--get-gameworldability-definitions)):**
|
||||
|
||||
- **`GET /game/world/ability-definitions`** with versioned envelope (`schemaVersion` **1**, **`abilities`** array).
|
||||
- Row fields: **`id`**, **`displayName`**, **`baseDamage`**, **`cooldownSeconds`**, **`abilityKind`** (omit when null).
|
||||
- Abilities ordered by **`id`** ordinal, matching **`GetDefinitionsInIdOrder()`**.
|
||||
- Bruno folder `bruno/neon-sprawl-server/ability-definitions/`.
|
||||
- `server/README.md` section.
|
||||
- API integration tests (AAA).
|
||||
|
||||
**Out of scope (from Linear + backlog):**
|
||||
|
||||
- Godot HUD / client wiring ([NEO-85](https://linear.app/neon-sprawl/issue/NEO-85)).
|
||||
- Combat resolution, damage application, cooldown commit ([NEO-80+](E5M1-prototype-backlog.md)).
|
||||
- Per-player ability state or loadout mutation.
|
||||
- `docs/manual-qa/NEO-78.md` (server-only; no client-facing changes per kickoff).
|
||||
|
||||
## Acceptance criteria checklist
|
||||
|
||||
- [ ] GET returns all **four** prototype abilities with **`schemaVersion`** **1** and stable field names (`id`, `displayName`, `baseDamage`, `cooldownSeconds`, `abilityKind` when present).
|
||||
- [ ] Bruno happy path documents response shape and runs against dev server.
|
||||
- [ ] Automated API tests (AAA) assert four ids in registry order and spot-check metadata.
|
||||
|
||||
## Technical approach
|
||||
|
||||
1. **Route:** **`GET /game/world/ability-definitions`** — parameterless read; inject **`IAbilityDefinitionRegistry`** only (not **`AbilityDefinitionCatalog`**).
|
||||
|
||||
2. **Response shape:** Top-level **`schemaVersion`** (`AbilityDefinitionsListResponse.CurrentSchemaVersion` = **1**) plus **`abilities`** array. Each row maps from **`AbilityDefRow`**: **`id`**, **`displayName`**, **`baseDamage`**, **`cooldownSeconds`**, **`abilityKind`**. Use **`JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)`** on **`abilityKind`** so absent kinds are omitted from JSON.
|
||||
|
||||
3. **Ordering:** Build list from **`registry.GetDefinitionsInIdOrder()`** (already ordinal by id). Tests assert exact id sequence (frozen four from [E5.M1 freeze box](../decomposition/modules/E5_M1_CombatRulesEngine.md#prototype-slice-1-freeze-e5m1-01)):
|
||||
|
||||
`prototype_burst`, `prototype_dash`, `prototype_guard`, `prototype_pulse`
|
||||
|
||||
(ordinal sort of the four ids — same order as **`AbilityDefinitionRegistryTests`** / catalog keys).
|
||||
|
||||
4. **Implementation:** New **`AbilityDefinitionsWorldApi`** + **`AbilityDefinitionsListDtos.cs`** in `Game/Combat/` (mirror [`RecipeDefinitionsWorldApi`](../../server/NeonSprawl.Server/Game/Crafting/RecipeDefinitionsWorldApi.cs)). Wire **`app.MapAbilityDefinitionsWorldApi()`** from **`Program.cs`** next to other world definition APIs (after **`MapRecipeDefinitionsWorldApi()`**).
|
||||
|
||||
5. **Bruno:** `bruno/neon-sprawl-server/ability-definitions/` with **`Get ability definitions.bru`** + **`folder.bru`**. Tests: 200, JSON, **`schemaVersion` 1**, **`abilities.length === 4`**, ascending id order, **exact frozen-four registry sequence**, spot-check **`prototype_pulse`** (`baseDamage` 25, `cooldownSeconds` 3.0, `abilityKind` `attack`) and **`prototype_burst`** (`baseDamage` 40, `cooldownSeconds` 5.0, `abilityKind` `attack`).
|
||||
|
||||
6. **Docs:** Update **`server/README.md`** ability-catalog section with GET + curl example. Update [E5_M1](E5_M1_CombatRulesEngine.md) **Related implementation slices** and [documentation_and_implementation_alignment.md](documentation_and_implementation_alignment.md) E5.M1 row when landed.
|
||||
|
||||
### Expected prototype row values (from content)
|
||||
|
||||
| `id` | `displayName` | `baseDamage` | `cooldownSeconds` | `abilityKind` |
|
||||
|------|---------------|--------------|-------------------|---------------|
|
||||
| `prototype_pulse` | Prototype Pulse | 25 | 3.0 | `attack` |
|
||||
| `prototype_guard` | Prototype Guard | 0 | 6.0 | `utility` |
|
||||
| `prototype_dash` | Prototype Dash | 0 | 4.0 | `movement` |
|
||||
| `prototype_burst` | Prototype Burst | 40 | 5.0 | `attack` |
|
||||
|
||||
## Files to add
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `server/NeonSprawl.Server/Game/Combat/AbilityDefinitionsWorldApi.cs` | `Map*` extension registering GET route; maps registry → JSON. |
|
||||
| `server/NeonSprawl.Server/Game/Combat/AbilityDefinitionsListDtos.cs` | Versioned response + row DTOs for JSON serialization. |
|
||||
| `server/NeonSprawl.Server.Tests/Game/Combat/AbilityDefinitionsWorldApiTests.cs` | HTTP integration: 200, schema v1, four ids in order, spot-check rows. |
|
||||
| `bruno/neon-sprawl-server/ability-definitions/Get ability definitions.bru` | Manual / Bruno verification against dev server. |
|
||||
| `bruno/neon-sprawl-server/ability-definitions/folder.bru` | Bruno folder metadata (match `recipe-definitions`). |
|
||||
| `docs/plans/NEO-78-implementation-plan.md` | This plan. |
|
||||
|
||||
## Files to modify
|
||||
|
||||
| Path | Rationale |
|
||||
|------|-----------|
|
||||
| `server/NeonSprawl.Server/Program.cs` | Register `MapAbilityDefinitionsWorldApi()` alongside other game world definition APIs. |
|
||||
| `server/README.md` | Document `GET /game/world/ability-definitions`, curl example, links to plan/Bruno. |
|
||||
| `docs/decomposition/modules/E5_M1_CombatRulesEngine.md` | **Related implementation slices** — HTTP read model bullet (NEO-78). |
|
||||
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | E5.M1 row — note NEO-78 HTTP projection when landed. |
|
||||
|
||||
## Tests
|
||||
|
||||
| File | Coverage |
|
||||
|------|----------|
|
||||
| `server/NeonSprawl.Server.Tests/Game/Combat/AbilityDefinitionsWorldApiTests.cs` | **Integration:** `GET /game/world/ability-definitions` via **`InMemoryWebApplicationFactory`**; **`ReadFromJsonAsync`**; assert **`schemaVersion` 1**; **`abilities`** count **4**; ordered **`id`** list equals frozen four in ordinal id order; spot-check **`prototype_pulse`** and **`prototype_burst`** damage/cooldown/kind. **AAA** per [csharp-style](../../.cursor/rules/csharp-style.md). |
|
||||
|
||||
Bruno scripts complement automated tests for manual dev-server verification. No `docs/manual-qa/NEO-78.md` (server-only per kickoff).
|
||||
|
||||
## Open questions / risks
|
||||
|
||||
| Question / risk | Agent recommendation | Status |
|
||||
|-----------------|---------------------|--------|
|
||||
| **Linear blockedBy NEO-79** | Branch from **`main`** where NEO-79 is merged (confirmed at kickoff; branch fast-forwarded). | **adopted** |
|
||||
| **Optional fields later** | When content adds new optional ability-def fields, bump **`schemaVersion`** or extend v1 in a follow-up issue. | **deferred** |
|
||||
| **Client not in this story** | Cross-link **NEO-85** in plan; Bruno is not prototype-complete per [full-stack epic decomposition](../../.cursor/rules/full-stack-epic-decomposition.md). | **adopted** |
|
||||
| **`cooldownSeconds` JSON type** | Emit as JSON **number** (double) matching content schema; tests assert numeric equality. | **adopted** |
|
||||
|
||||
## Decisions (kickoff)
|
||||
|
||||
- **No manual QA doc** — server-only HTTP; Bruno + automated tests (user confirmed).
|
||||
- **`abilityKind` omitted when null** — v1 contract emits loaded fields only (user confirmed).
|
||||
- **Registry-only injection** — HTTP layer depends on **`IAbilityDefinitionRegistry`**, not **`AbilityDefinitionCatalog`**.
|
||||
Loading…
Reference in New Issue