20 KiB
E5.M1 — Prototype story backlog (CombatRulesEngine)
Working backlog for Epic 5 — Slice 1 (combat rules MVP). Decomposition and contracts: E5.M1 — CombatRulesEngine.
Labels (Linear): every issue E5.M1; add server or client + Story as listed.
Precursor (do not re-scope): E1.M4 cast funnel — hotbar loadout (NEO-29), cast POST + client digits (NEO-31), target lock + range deny (NEO-28), global cooldown + snapshot (NEO-32), telemetry hook comments (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 targeting (NEO-23–NEO-26) — PrototypeTargetRegistry alpha/beta stubs remain combat dummies until E5.M2. E2.M2 skill XP engine is not the combat payout path — gig XP is 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). 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 |
| E5M1-02 | server | NEO-77 |
| E5M1-03 | server | NEO-79 |
| E5M1-04 | server | NEO-78 |
| E5M1-05 | server | NEO-80 |
| E5M1-06 | server | NEO-81 |
| E5M1-07 | server | NEO-82 |
| E5M1-08 | server | NEO-83 |
| E5M1-09 | server | NEO-84 |
| E5M1-10 | server | NEO-44 |
| E5M1-11 | client | NEO-85 |
| E5M1-12 | client | 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) |
| 11 | E5M1-11 | client | E5M1-07, E5M1-08 |
| 12 | E5M1-12 | client | E5M1-11, E5M1-10 |
Downstream (separate modules): E5.M2 NPC AI + telegraphs; E5.M3 encounters + loot; E1.M4 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 |
| 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 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.jsonwith stableidvalues matching today’s allowlist (prototype_pulse,prototype_guard,prototype_dash,prototype_burst).- Fields:
displayName,baseDamage(non-negative int),cooldownSeconds(positive number), optionalabilityKindenum 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 +
content/README.md.
Out of scope
- Server loader, combat engine, HTTP, Godot.
Acceptance criteria
- PR gate validates ability JSON against schema.
- Exactly four prototype ability ids; duplicate
idfails CI. - Stable id list documented in module doc freeze box.
Landed (NEO-76): content/abilities/prototype_abilities.json, ability-def.schema.json, CI gates in validate_content.py; plan 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.mdsection.
Out of scope
- Injectable registry interface (E5M1-03), HTTP projection, combat resolution.
Acceptance criteria
- Host fails startup on invalid ability JSON (mirror CI rules).
- Tests cover at least one happy path and one malformed catalog rejection.
Landed (NEO-77): fail-fast server load of content/abilities/*_abilities.json — server/NeonSprawl.Server/Game/Combat/ (NEO-77-implementation-plan.md); server README — Ability catalog.
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
PrototypeAbilityRegistrycall sites in hotbar/cast path to registry (keep normalize behavior).
Out of scope
- HTTP GET route (E5M1-04), damage application.
Acceptance criteria
- Unknown ability id fails hotbar/cast validation same as today.
- All four prototype ids resolve with expected
baseDamage/cooldownSeconds.
Landed (NEO-79): IAbilityDefinitionRegistry + DI; hotbar/cast migrated — 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
- GET returns all four prototype abilities with stable field names.
- Bruno happy path documents response shape.
Landed (NEO-78): GET /game/world/ability-definitions — AbilityDefinitionsWorldApi + DTOs in Game/Combat/ (NEO-78-implementation-plan.md); Bruno bruno/neon-sprawl-server/ability-definitions/.
Client counterpart: optional enrichment in NEO-85 — 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) withTryGet,ApplyDamage,TryResetToFullfor prototype targets.- Initialize
prototype_target_alphaandprototype_target_betato 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
- Damaging alpha reduces stored HP; HP cannot go below zero.
- Defeated target remains defeated until explicit reset policy (document prototype: server restart resets).
E5M1-06 — CombatOperations.TryResolve + CombatResult
Goal: Pure server combat resolution: ability + target → damage + remaining HP + defeated.
In scope
CombatOperations.TryResolve(name TBD) usingIAbilityDefinitionRegistry+ICombatEntityHealthStore.- Result type with
damageDealt,targetRemainingHp,targetDefeated, stable denyreasonCodewhen 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
prototype_pulseagainst full HP dummy deals catalog damage deterministically.- Second resolve on defeated target returns structured deny (no silent no-op).
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
AbilityCastResponsev1 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.mdcast section updated; cross-link E1.M4/E5.M1 deny vocabulary.
Out of scope
- Godot presentation — NEO-85.
- 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
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
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_deathcomment 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 vocabulary.
enemy_defeatsite fires only on transition to defeated (not every overkill cast).
E5M1-10 — Gig XP on combat defeat (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-progressioncall on combat defeat. server/README.md; manual QA steps.
Out of scope
- Full nine-gig catalog UI, hub swap, sub-gig XP (gigs.md).
- Godot gig bar — optional in capstone NEO-86.
Acceptance criteria
- Defeating
prototype_target_alphagrants gig XP once per defeat transition. - Skill progression snapshot unchanged by combat defeat in tests.
- Documented invariant matches progression.md.
Note: Issue pre-dates this backlog; slug E5M1-10 maps to existing 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 extendedAbilityCastResponseresolution 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).
docs/manual-qa/NEO-85.md— Godot steps (server + client running).client/README.mdcombat 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.mdsingle-session script: spawn → Tab lock alpha → bind slot 0prototype_pulse→ cast until defeat → verify HUD + optional gig XP visibility if NEO-44 landed.client/README.mdend-to-end combat loop section.- Update 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_alphashows 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 for prototype minimums.
Client counterpart: this issue (NEO-86).
After this backlog
- E5.M2 Slice 2 — decompose when E5.M1 lands (NPC archetypes + telegraphs + client telegraph UX).
- E1.M4 module Ready when cast path returns real
CombatResolutionand client HUD reflects it. - Track delivery in Linear; keep
blockedBysynchronized if scope changes. - For each issue kickoff:
docs/plans/{NEO-XX}-implementation-plan.mdper story-kickoff. - Add
docs/manual-qa/{NEO-XX}.mdfor player-visible client stories when implementation lands.
Decomposition complete checklist
- Every player-visible AC has a
clientLinear issue (NEO-85, NEO-86) - No “optional follow-up” / “future client” without NEO-XX
- Capstone client issue exists for playable Godot verification (NEO-86)
- documentation_and_implementation_alignment.md E5.M1 row updated when work starts
- module_dependency_register.md E5.M1 note updated when issues are created