# 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** - [ ] Accept path returns non-empty resolution payload with deterministic damage. - [ ] Cooldown duration matches ability catalog, not global constant. - [ ] Bruno documents lock → cast → damage → defeat deny 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** - [ ] GET reflects damage applied via cast route without client-side HP math. - [ ] Defeated flag true after lethal cast sequence in Bruno. **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** - [ ] Hook names match [epic_05 Slice 1](../decomposition/epics/epic_05_pve_combat.md#slice-1---combat-rules-mvp) vocabulary. - [ ] **`enemy_defeat`** site fires only on transition to defeated (not every overkill cast). --- ### 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** - [ ] Defeating **`prototype_target_alpha`** grants gig XP once per defeat transition. - [ ] Skill progression snapshot unchanged by combat defeat in tests. - [ ] Documented invariant matches [progression.md](../game-design/progression.md). **Note:** Issue pre-dates this backlog; slug **E5M1-10** maps to existing **[NEO-44](https://linear.app/neon-sprawl/issue/NEO-44)**. --- ### 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** - [ ] Player sees damage/de defeat feedback in HUD after digit-key cast (not Output-only). - [ ] Target HP label matches server snapshot after cast sequence. - [ ] Manual QA exercisable without Bruno. **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** - [ ] Human completes script with server + client; **`prototype_target_alpha`** shows defeated state in HUD. - [ ] Epic 5 Slice 1 AC: combat readable in fixed isometric camera; no silent cast failures. - [ ] Re-read [epic_05 Definition of Done](../decomposition/epics/epic_05_pve_combat.md#definition-of-done) for prototype minimums. **Client counterpart:** this issue (**NEO-86**). --- ## After this backlog - **[E5.M2](../decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md)** Slice 2 — decompose when E5.M1 lands (NPC archetypes + telegraphs + client telegraph UX). - **[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)