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

9.0 KiB

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 · Epic 5 Slice 1 · backlog E5M1-04
Branch NEO-78-get-world-ability-definitions
Precursor NEO-79IAbilityDefinitionRegistry + DI (Done on main)
Pattern NEO-68GET /game/world/recipe-definitions; NEO-53 — item definitions world GET
Client counterpart None (server-only); optional client enrichment in 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 and E5.M1 freeze box.

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) — backed by IAbilityDefinitionRegistry without duplicating catalog truth.

In scope (from Linear + E5M1-04):

  • 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).
  • Combat resolution, damage application, cooldown commit (NEO-80+).
  • 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):

    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). 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 Related implementation slices and 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.

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. 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.