# E5.M1 — Prototype story backlog (CombatRulesEngine)

Working backlog for **Epic 5 — Slice 1** ([combat rules MVP](../decomposition/epics/epic_05_pve_combat.md#slice-1---combat-rules-mvp)). Decomposition and contracts: [E5.M1 — CombatRulesEngine](../decomposition/modules/E5_M1_CombatRulesEngine.md).

**Labels (Linear):** every issue **`E5.M1`**; add **`server`** or **`client`** + **`Story`** as listed.

**Precursor (do not re-scope):** [E1.M4](../decomposition/modules/E1_M4_AbilityInputScaffold.md) cast funnel — hotbar loadout ([NEO-29](https://linear.app/neon-sprawl/issue/NEO-29)), cast POST + client digits ([NEO-31](https://linear.app/neon-sprawl/issue/NEO-31)), target lock + range deny ([NEO-28](https://linear.app/neon-sprawl/issue/NEO-28)), global cooldown + snapshot ([NEO-32](https://linear.app/neon-sprawl/issue/NEO-32)), telemetry hook comments ([NEO-30](https://linear.app/neon-sprawl/issue/NEO-30)). **`POST …/ability-cast`** today returns **`accepted: true`** with **no** damage, **no** per-ability cooldown from data, and **no** **`CombatResolution`** payload — that gap is **E5.M1**.

**Upstream (must be landed):** [E1.M3](../decomposition/modules/E1_M3_InteractionAndTargetingLayer.md) targeting ([NEO-23](https://linear.app/neon-sprawl/issue/NEO-23)–[NEO-26](https://linear.app/neon-sprawl/issue/NEO-26)) — **`PrototypeTargetRegistry`** alpha/beta stubs remain combat dummies until [E5.M2](../decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md). [E2.M2](../decomposition/modules/E2_M2_XpAwardAndLevelEngine.md) skill XP engine is **not** the combat payout path — gig XP is [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44).

**Prototype combat spine (frozen in E5M1-01):** **four** `AbilityDef` rows — ids **`prototype_pulse`**, **`prototype_guard`**, **`prototype_dash`**, **`prototype_burst`** (same allowlist as today’s hardcoded [`PrototypeAbilityRegistry`](../../server/NeonSprawl.Server/Game/AbilityInput/PrototypeAbilityRegistry.cs)). **Two** combat dummies — **`prototype_target_alpha`** / **`prototype_target_beta`** with shared max HP. Player-as-target hostility and **E6.M1** PvP gates stay **stub deny** until Epic 6; prototype targets are NPC stubs only.

**Linear issues (created):** attach `docs/plans/NEO-*-implementation-plan.md` on the same branch as implementation work.

| Slug | Layer | Linear |
|------|-------|--------|
| E5M1-01 | server | [NEO-76](https://linear.app/neon-sprawl/issue/NEO-76) |
| E5M1-02 | server | [NEO-77](https://linear.app/neon-sprawl/issue/NEO-77) |
| E5M1-03 | server | [NEO-79](https://linear.app/neon-sprawl/issue/NEO-79) |
| E5M1-04 | server | [NEO-78](https://linear.app/neon-sprawl/issue/NEO-78) |
| E5M1-05 | server | [NEO-80](https://linear.app/neon-sprawl/issue/NEO-80) |
| E5M1-06 | server | [NEO-81](https://linear.app/neon-sprawl/issue/NEO-81) |
| E5M1-07 | server | [NEO-82](https://linear.app/neon-sprawl/issue/NEO-82) |
| E5M1-08 | server | [NEO-83](https://linear.app/neon-sprawl/issue/NEO-83) |
| E5M1-09 | server | [NEO-84](https://linear.app/neon-sprawl/issue/NEO-84) |
| E5M1-10 | server | [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44) |
| E5M1-11 | client | [NEO-85](https://linear.app/neon-sprawl/issue/NEO-85) |
| E5M1-12 | client | [NEO-86](https://linear.app/neon-sprawl/issue/NEO-86) |

**Dependency graph in Linear:** E5M1-02 blocked by E5M1-01. E5M1-03 blocked by E5M1-02. E5M1-04 blocked by E5M1-03. E5M1-05 blocked by E5M1-03. E5M1-06 blocked by E5M1-05 and E5M1-03. E5M1-07 blocked by E5M1-06. E5M1-08 blocked by E5M1-06 (may parallel E5M1-07). E5M1-09 blocked by E5M1-07. E5M1-10 (NEO-44) blocked by E5M1-07. E5M1-11 blocked by E5M1-07 and E5M1-08. E5M1-12 blocked by E5M1-11 and E5M1-10.

**Board order:** estimates **1–12** matching slug order (E5M1-01 = 1 … E5M1-12 = 12). On the Epic 5 project board, sort by **Estimate** (ascending).

---

## Story order (recommended)

| Order | Slug | Layer | Depends on |
|-------|------|-------|------------|
| 1 | **E5M1-01** | server | Existing four prototype ability ids (hardcoded allowlist) |
| 2 | **E5M1-02** | server | E5M1-01 |
| 3 | **E5M1-03** | server | E5M1-02 |
| 4 | **E5M1-04** | server | E5M1-03 |
| 5 | **E5M1-05** | server | E5M1-03, `PrototypeTargetRegistry` ids |
| 6 | **E5M1-06** | server | E5M1-05, E5M1-03 |
| 7 | **E5M1-07** | server | E5M1-06 |
| 8 | **E5M1-08** | server | E5M1-06 |
| 9 | **E5M1-09** | server | E5M1-07 |
| 10 | **E5M1-10** | server | E5M1-07 ([NEO-44](https://linear.app/neon-sprawl/issue/NEO-44)) |
| 11 | **E5M1-11** | client | E5M1-07, E5M1-08 |
| 12 | **E5M1-12** | client | E5M1-11, E5M1-10 |

**Downstream (separate modules):** [E5.M2](../decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md) NPC AI + telegraphs; [E5.M3](../decomposition/modules/E5_M3_EncounterAndRewardTables.md) encounters + loot; [E1.M4](../decomposition/modules/E1_M4_AbilityInputScaffold.md) closes when **`CombatResolution`** lands; per-ability cooldown UI may refresh from extended cast responses or existing snapshot route.

---

## Kickoff decisions (decomposition defaults)

| Topic | Decision | Rationale |
|-------|----------|-----------|
| Ability count | **4** frozen defs | Matches existing hotbar allowlist; epic allows 4–6 |
| Combat targets | **`prototype_target_alpha`** / **`beta`** only | Reuse E1.M3 registry until E5.M2 spawns real NPCs |
| Max HP | **100** per dummy (constant in content or combat constants) | Enough casts to observe defeat without tuning TTK yet |
| Hit resolution | **Deterministic** damage from `AbilityDef.baseDamage`; no miss/crit RNG | Readable prototype tests; RNG is pre-production |
| Cooldown | **Per-ability** `cooldownSeconds` from catalog on successful **resolve** | Replaces global `AbilityPrototypeCooldown` when E5M1-07 lands |
| Player HP / death | **Out of scope** Slice 1 | `player_death` telemetry gets a **reserved** hook site only |
| `ThreatState` | **Stub** / comment-only in Slice 1 | Full threat/aggro is E5.M2 |
| PvP | **Stub** deny path documented; no player targets in prototype registry | [pvp_combat_integration.md](../decomposition/modules/pvp_combat_integration.md) |
| Cast wire | **Extend** `POST …/ability-cast` **`AbilityCastResponse` v1** | Avoid parallel combat POST in prototype; E1.M4 client keeps one route |
| Target HP read | **`GET /game/world/combat-targets`** snapshot | Client HUD without trusting local damage math |
| Gig XP | **[NEO-44](https://linear.app/neon-sprawl/issue/NEO-44)** on **`targetDefeated`** | Combat → **gig** XP only; no E2.M2 skill grant |

---

### E5M1-01 — Prototype AbilityDef catalog + schemas + CI

**Goal:** Lock content shape and CI validation for **four** frozen combat abilities before server load.

**In scope**

- `content/schemas/ability-def.schema.json`.
- `content/abilities/prototype_abilities.json` with stable **`id`** values matching today’s allowlist (`prototype_pulse`, `prototype_guard`, `prototype_dash`, `prototype_burst`).
- Fields: **`displayName`**, **`baseDamage`** (non-negative int), **`cooldownSeconds`** (positive number), optional **`abilityKind`** enum for future UX (`attack` / `utility` / `movement`).
- `scripts/validate_content.py`: schema validation, duplicate ids, exact four-id allowlist, non-empty display names.
- Designer note in [E5_M1](../decomposition/modules/E5_M1_CombatRulesEngine.md) + `content/README.md`.

**Out of scope**

- Server loader, combat engine, HTTP, Godot.

**Acceptance criteria**

- [x] PR gate validates ability JSON against schema.
- [x] Exactly four prototype ability ids; duplicate `id` fails CI.
- [x] Stable id list documented in module doc freeze box.

**Landed ([NEO-76](https://linear.app/neon-sprawl/issue/NEO-76)):** [`content/abilities/prototype_abilities.json`](../../content/abilities/prototype_abilities.json), [`ability-def.schema.json`](../../content/schemas/ability-def.schema.json), CI gates in [`validate_content.py`](../../scripts/validate_content.py); plan [NEO-76-implementation-plan.md](NEO-76-implementation-plan.md).

---

### E5M1-02 — Server ability catalog load (fail-fast)

**Goal:** Disk → host: startup load of `content/abilities/*.json` with CI-parity validation.

**In scope**

- Loader + catalog types under `server/NeonSprawl.Server/Game/Combat/` (path finalized in plan).
- Fail-fast on malformed files, duplicate ids, allowlist mismatch with CI.
- Unit tests (AAA) for loader happy path and failure modes.
- `server/README.md` section.

**Out of scope**

- Injectable registry interface (E5M1-03), HTTP projection, combat resolution.

**Acceptance criteria**

- [x] Host fails startup on invalid ability JSON (mirror CI rules).
- [x] Tests cover at least one happy path and one malformed catalog rejection.

**Landed ([NEO-77](https://linear.app/neon-sprawl/issue/NEO-77)):** fail-fast server load of `content/abilities/*_abilities.json` — `server/NeonSprawl.Server/Game/Combat/` ([NEO-77-implementation-plan.md](NEO-77-implementation-plan.md)); [server README — Ability catalog](../../server/README.md#ability-catalog-contentabilities-neo-77).

---

### E5M1-03 — `IAbilityDefinitionRegistry` + DI

**Goal:** Injectable lookup for combat and hotbar validation; replace direct static allowlist reads over time.

**In scope**

- `IAbilityDefinitionRegistry` + implementation backed by startup catalog.
- DI registration in `Program.cs`.
- Unit tests (AAA) for known-id lookup and unknown-id miss.
- Migrate **`PrototypeAbilityRegistry`** call sites in hotbar/cast path to registry (keep normalize behavior).

**Out of scope**

- HTTP GET route (E5M1-04), damage application.

**Acceptance criteria**

- [x] Unknown ability id fails hotbar/cast validation same as today.
- [x] All four prototype ids resolve with expected **`baseDamage`** / **`cooldownSeconds`**.

**Landed ([NEO-79](https://linear.app/neon-sprawl/issue/NEO-79)):** [`IAbilityDefinitionRegistry`](../../server/NeonSprawl.Server/Game/Combat/IAbilityDefinitionRegistry.cs) + DI; hotbar/cast migrated — [NEO-79-implementation-plan.md](NEO-79-implementation-plan.md).

---

### E5M1-04 — `GET /game/world/ability-definitions`

**Goal:** Versioned read-only projection of frozen ability defs for client/tooling.

**In scope**

- `GET /game/world/ability-definitions` → list DTO (`schemaVersion`, `abilities[]` with id, displayName, baseDamage, cooldownSeconds, abilityKind).
- API + integration tests (AAA); Bruno folder `bruno/neon-sprawl-server/ability-definitions/`.
- `server/README.md`; no manual QA (server-only per NEO-78 kickoff).

**Out of scope**

- Godot HUD — client uses this optionally in E5M1-11; not required for Bruno proof.

**Acceptance criteria**

- [x] GET returns all four prototype abilities with stable field names.
- [x] Bruno happy path documents response shape.

**Landed ([NEO-78](https://linear.app/neon-sprawl/issue/NEO-78)):** **`GET /game/world/ability-definitions`** — `AbilityDefinitionsWorldApi` + DTOs in `Game/Combat/` ([NEO-78-implementation-plan.md](NEO-78-implementation-plan.md)); Bruno `bruno/neon-sprawl-server/ability-definitions/`.

**Client counterpart:** optional enrichment in [NEO-85](#e5m1-11--client-combat-feedback--target-hp-hud) — not a separate issue.

---

### E5M1-05 — Combat entity health store + prototype dummy init

**Goal:** Server-owned HP for combat dummies keyed by target id.

**In scope**

- `ICombatEntityHealthStore` (or equivalent) with **`TryGet`**, **`ApplyDamage`**, **`TryResetToFull`** for prototype targets.
- Initialize **`prototype_target_alpha`** and **`prototype_target_beta`** to catalog max HP at first access (in-memory; not persisted in Slice 1).
- Unit tests (AAA) for damage, floor at zero, defeated flag.

**Out of scope**

- Player HP, persistence migration, NPC spawn (E5.M2).

**Acceptance criteria**

- [x] Damaging alpha reduces stored HP; HP cannot go below zero.
- [x] Defeated target remains defeated until explicit reset policy (document prototype: server restart resets).

**Landed ([NEO-80](https://linear.app/neon-sprawl/issue/NEO-80)):** **`ICombatEntityHealthStore`** + **`InMemoryCombatEntityHealthStore`** — lazy init for **`prototype_target_alpha`** / **`prototype_target_beta`** at **`PrototypeCombatConstants.MaxPrototypeTargetHp` (100)** ([NEO-80-implementation-plan.md](NEO-80-implementation-plan.md)); [server README — Combat entity health (NEO-80)](../../server/README.md#combat-entity-health-neo-80).

---

### E5M1-06 — `CombatOperations.TryResolve` + `CombatResult`

**Goal:** Pure server combat resolution: ability + target → damage + remaining HP + defeated.

**In scope**

- `CombatOperations.TryResolve` (name TBD) using **`IAbilityDefinitionRegistry`** + **`ICombatEntityHealthStore`**.
- Result type with **`damageDealt`**, **`targetRemainingHp`**, **`targetDefeated`**, stable deny **`reasonCode`** when target already defeated or unknown.
- Unit tests (AAA) for happy path, zero-damage utility abilities, defeated-target deny.

**Out of scope**

- HTTP wiring (E5M1-07), gig XP (NEO-44), telemetry hooks.

**Acceptance criteria**

- [x] **`prototype_pulse`** against full HP dummy deals catalog damage deterministically.
- [x] Second resolve on defeated target returns structured deny (no silent no-op).

**Landed ([NEO-81](https://linear.app/neon-sprawl/issue/NEO-81)):** **`CombatOperations.TryResolve`** + **`CombatResult`** + **`CombatReasonCodes`** — deterministic catalog damage, defeated pre-check deny, unknown ability/target validation ([NEO-81-implementation-plan.md](NEO-81-implementation-plan.md)); [server README — Combat engine (NEO-81)](../../server/README.md#combat-engine-neo-81).

---

### E5M1-07 — Wire `ability-cast` into combat engine + extend cast response

**Goal:** Successful casts after E1.M4 gates invoke **`CombatOperations`** and return **`CombatResolution`** fields on the existing cast POST.

**In scope**

- After NEO-28/32 gates pass, call combat engine; commit per-ability cooldown from ability def (replace global constant).
- Extend **`AbilityCastResponse` v1** with optional resolution block on accept: e.g. **`damageDealt`**, **`targetRemainingHp`**, **`targetDefeated`**, **`targetId`**, **`abilityId`** (exact names in plan).
- JSON deny **`target_defeated`** (or aligned code) when firing at defeated dummy.
- Integration tests + Bruno: lock target → cast → response shows damage; repeated casts until defeated then deny.
- `server/README.md` cast section updated; cross-link E1.M4/E5.M1 deny vocabulary.

**Out of scope**

- Godot presentation — **[NEO-85](#e5m1-11--client-combat-feedback--target-hp-hud)**.
- Rich **`ThreatState`**, player damage, PvP eligibility (stub only).

**Acceptance criteria**

- [x] Accept path returns non-empty resolution payload with deterministic damage.
- [x] Cooldown duration matches ability catalog, not global constant.
- [x] Bruno documents lock → cast → damage → defeat deny spine.

**Landed ([NEO-82](https://linear.app/neon-sprawl/issue/NEO-82)):** cast POST invokes **`CombatOperations.TryResolve`**; nested **`combatResolution`** on accept; per-ability catalog cooldown; **`target_defeated`** deny ([NEO-82-implementation-plan.md](NEO-82-implementation-plan.md)); Bruno defeat spine.

**Client counterpart:** [NEO-85](https://linear.app/neon-sprawl/issue/NEO-85)

---

### E5M1-08 — `GET /game/world/combat-targets` snapshot

**Goal:** Read-only HP snapshot for prototype combat dummies for client HUD and manual QA.

**In scope**

- `GET /game/world/combat-targets` → versioned list (`targetId`, `maxHp`, `currentHp`, `defeated`).
- Seeds store entries for alpha/beta on first read (same init policy as E5M1-05).
- API tests (AAA); Bruno folder; README section.

**Out of scope**

- Godot wiring (E5M1-11), NPC movement.

**Acceptance criteria**

- [x] GET reflects damage applied via cast route without client-side HP math.
- [x] Defeated flag true after lethal cast sequence in Bruno.

**Landed ([NEO-83](https://linear.app/neon-sprawl/issue/NEO-83)):** **`GET /game/world/combat-targets`** — `CombatTargetsWorldApi` + DTOs; cast→GET damage + defeat spine ([NEO-83-implementation-plan.md](NEO-83-implementation-plan.md)); Bruno `bruno/neon-sprawl-server/combat-targets/`.

**Client counterpart:** [NEO-85](https://linear.app/neon-sprawl/issue/NEO-85)

---

### E5M1-09 — Slice 1 combat telemetry hook sites

**Goal:** Comment-only hooks for Epic 5 Slice 1 product telemetry names; no E9.M1 ingest.

**In scope**

- Hook placement in combat resolve / cast accept path for **`ability_used`**, **`enemy_defeat`**.
- Reserved **`encounter_start`** / **`player_death`** comment sites (encounters and player HP deferred).
- `TODO(E9.M1)` markers; README + module doc pointers.

**Out of scope**

- Production logging, dashboards.

**Acceptance criteria**

- [x] Hook names match [epic_05 Slice 1](../decomposition/epics/epic_05_pve_combat.md#slice-1---combat-rules-mvp) vocabulary.
- [x] **`enemy_defeat`** site fires only on transition to defeated (not every overkill cast).

**Landed ([NEO-84](https://linear.app/neon-sprawl/issue/NEO-84)):** comment-only **`ability_used`** / **`enemy_defeat`** hook sites in **`CombatOperations.TryResolve`**; reserved **`encounter_start`** / **`player_death`** ([NEO-84-implementation-plan.md](NEO-84-implementation-plan.md)); [server README — Combat telemetry hooks](../../../server/README.md#combat-telemetry-hooks-neo-84).

---

### E5M1-10 — Gig XP on combat defeat ([NEO-44](https://linear.app/neon-sprawl/issue/NEO-44))

**Goal:** On prototype target defeat, award **gig XP** to the attacker's **main gig** — **not** E2.M2 skill XP.

**In scope**

- Minimal gig progression store + grant helper (prototype main gig only — id TBD in kickoff plan, e.g. frozen **`breach`**).
- Hook from combat defeat path in E5M1-07 engine.
- **`GET`** (or extend existing snapshot) for gig progression visibility — scope in implementation plan.
- Tests proving no **`POST …/skill-progression`** call on combat defeat.
- `server/README.md`; manual QA steps.

**Out of scope**

- Full nine-gig catalog UI, hub swap, sub-gig XP ([gigs.md](../game-design/gigs.md)).
- Godot gig bar — optional in capstone [NEO-86](#e5m1-12--playable-tab-target-combat-capstone-godot).

**Acceptance criteria**

- [x] Defeating **`prototype_target_alpha`** grants gig XP once per defeat transition.
- [x] Skill progression snapshot unchanged by combat defeat in tests.
- [x] Documented invariant matches [progression.md](../game-design/progression.md).

**Landed ([NEO-44](https://linear.app/neon-sprawl/issue/NEO-44)):** **`IPlayerGigProgressionStore`** + **`CombatDefeatGigXpGrant`** on cast accept when **`targetDefeated`**; **`GET …/gig-progression`**; main gig **`breach`**, **25** XP per defeat; plan [NEO-44-implementation-plan.md](NEO-44-implementation-plan.md); manual QA [`NEO-44`](../manual-qa/NEO-44.md).

---

### E5M1-11 — Client combat feedback + target HP HUD

**Goal:** Godot displays authoritative cast outcomes and dummy HP without local combat math.

**In scope**

- Extend **`ability_cast_client.gd`** (or sibling) to parse extended **`AbilityCastResponse`** resolution fields.
- Update **`CastFeedbackLabel`** (or dedicated combat line) with damage / defeat messaging on accept.
- **`combat_targets_client.gd`** + HUD label for locked target HP (poll or refresh after successful cast + on target lock change).
- GdUnit tests with HTTP doubles ([testing-expectations.md](../../.cursor/rules/testing-expectations.md)).
- `docs/manual-qa/NEO-85.md` — Godot steps (server + client running).
- `client/README.md` combat HUD section.

**Out of scope**

- Final combat VFX, floating damage numbers art pass, player HP bar.

**Acceptance criteria**

- [x] Player sees damage/defeat feedback in HUD after digit-key cast (not Output-only).
- [x] Target HP label matches server snapshot after cast sequence.
- [x] Manual QA exercisable without Bruno.

**Landed ([NEO-85](https://linear.app/neon-sprawl/issue/NEO-85)):** **`ability_cast_client.gd`** parses **`combatResolution`**; **`combat_targets_client.gd`** + **`CombatTargetHpLabel`**; event-driven GET refresh; plan [NEO-85-implementation-plan.md](NEO-85-implementation-plan.md); manual QA [`NEO-85`](../manual-qa/NEO-85.md).

**Client counterpart:** this issue (**NEO-85**).

---

### E5M1-12 — Playable tab-target combat capstone (Godot)

**Goal:** Prove Epic 5 Slice 1 acceptance **in Godot**: tab-target lock, cast abilities, readable deny reasons, defeat a prototype dummy — without Bruno.

**In scope**

- `docs/manual-qa/NEO-86.md` single-session script: spawn → Tab lock alpha → bind slot 0 **`prototype_pulse`** → cast until defeat → verify HUD + optional gig XP visibility if NEO-44 landed.
- `client/README.md` end-to-end combat loop section.
- Update [documentation_and_implementation_alignment.md](../decomposition/modules/documentation_and_implementation_alignment.md) E5.M1 row when complete.

**Out of scope**

- NPC AI (E5.M2), encounters/loot (E5.M3), group scaling.

**Acceptance criteria**

- [x] Human completes script with server + client; **`prototype_target_alpha`** shows defeated state in HUD.
- [x] Epic 5 Slice 1 AC: combat readable in fixed isometric camera; no silent cast failures.
- [x] Re-read [epic_05 Definition of Done](../decomposition/epics/epic_05_pve_combat.md#definition-of-done) for prototype minimums.

**Landed ([NEO-86](https://linear.app/neon-sprawl/issue/NEO-86)):** **`gig_progression_client.gd`** + **`GigXpLabel`**; defeat-triggered gig GET refresh; capstone manual QA [`NEO-86`](../manual-qa/NEO-86.md); plan [NEO-86-implementation-plan.md](NEO-86-implementation-plan.md); `client/README.md` end-to-end combat loop section. Epic 5 Slice 1 client capstone complete.

**Client counterpart:** this issue (**NEO-86**).

---

## After this backlog

- **[E5.M2](../decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md)** Slice 2 — decomposed in [E5M2-prototype-backlog.md](E5M2-prototype-backlog.md) (**NEO-87** → **NEO-98**).
- **[E1.M4](../decomposition/modules/E1_M4_AbilityInputScaffold.md)** module **Ready** when cast path returns real **`CombatResolution`** and client HUD reflects it.
- Track delivery in Linear; keep `blockedBy` synchronized if scope changes.
- For each issue kickoff: `docs/plans/{NEO-XX}-implementation-plan.md` per [story-kickoff](../../.cursor/rules/story-kickoff.md).
- Add `docs/manual-qa/{NEO-XX}.md` for player-visible **client** stories when implementation lands.

## Decomposition complete checklist

- [x] Every player-visible AC has a **`client`** Linear issue (**NEO-85**, **NEO-86**)
- [x] No “optional follow-up” / “future client” without NEO-XX
- [x] Capstone client issue exists for playable Godot verification (**NEO-86**)
- [x] [documentation_and_implementation_alignment.md](../decomposition/modules/documentation_and_implementation_alignment.md) E5.M1 row updated when work starts
- [x] [module_dependency_register.md](../decomposition/modules/module_dependency_register.md) E5.M1 note updated when issues are created

## Related docs

- [E5_M1_CombatRulesEngine.md](../decomposition/modules/E5_M1_CombatRulesEngine.md)
- [E1_M4_AbilityInputScaffold.md](../decomposition/modules/E1_M4_AbilityInputScaffold.md)
- [epic_05_pve_combat.md](../decomposition/epics/epic_05_pve_combat.md)
- [abilities.md](../game-design/abilities.md)
- [gigs.md](../game-design/gigs.md)
- [pvp_combat_integration.md](../decomposition/modules/pvp_combat_integration.md)
- [client_server_authority.md](../decomposition/modules/client_server_authority.md)
