neon-sprawl/docs/decomposition/modules/E5_M1_CombatRulesEngine.md

108 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# E5.M1 — CombatRulesEngine
## Summary
| Field | Value |
|--------|--------|
| **Module ID** | E5.M1 |
| **Epic** | [Epic 5 — PvE Combat](../epics/epic_05_pve_combat.md) |
| **Stage target** | Prototype |
| **Status** | In Progress — Slice 1 backlog [E5M1-prototype-backlog.md](../../plans/E5M1-prototype-backlog.md): E5M1-01 [NEO-76](https://linear.app/neon-sprawl/issue/NEO-76) ability catalog + CI landed; E5M1-02 [NEO-77](https://linear.app/neon-sprawl/issue/NEO-77) server load landed; E5M1-03 [NEO-79](https://linear.app/neon-sprawl/issue/NEO-79) registry + hotbar/cast migration landed; E5M1-04 [NEO-78](https://linear.app/neon-sprawl/issue/NEO-78) ability-definitions GET landed; E5M1-05 [NEO-80](https://linear.app/neon-sprawl/issue/NEO-80) combat entity health store landed; E5M1-06 [NEO-81](https://linear.app/neon-sprawl/issue/NEO-81) combat operations + `CombatResult` landed; E5M1-07 [NEO-82](https://linear.app/neon-sprawl/issue/NEO-82) cast POST + wire `combatResolution` landed; E5M1-08 [NEO-83](https://linear.app/neon-sprawl/issue/NEO-83) combat-targets GET landed; E5M1-09+ pending (see [dependency register](module_dependency_register.md)) |
| **Linear** | Label **`E5.M1`** · [E5M1-prototype-backlog.md](../../plans/E5M1-prototype-backlog.md) **E5M1-01** [NEO-76](https://linear.app/neon-sprawl/issue/NEO-76) → **E5M1-12** [NEO-86](https://linear.app/neon-sprawl/issue/NEO-86); gig XP **E5M1-10** [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44) |
## Purpose
**Authority:** [Client vs server](client_server_authority.md#e5m1-combatrulesengine) — server validates intents and computes all outcomes; never trust client damage/healing/crit/death.
Core tab-target combat resolution: valid actions against a locked target, hit resolution, cooldown and resource timing, and threat state as needed for prototype encounters. Presents readable outcomes in fixed isometric view and feeds XP awards through [E2.M2](E2_M2_XpAwardAndLevelEngine.md).
**PvP:** Single engine for PvE and PvP; player-target hostility **gates** on [E6.M1](E6_M1_PvPEligibilityAndFlagState.md). See [PvP and the combat engine](pvp_combat_integration.md).
## Responsibilities
- Combat state machine for tab-target flow (attacks, abilities per prototype scope).
- Validate and apply `CombatAction`; produce `CombatResolution` for UI and logs (include **PvP deny reasons** when player targets are blocked; see [pvp_combat_integration.md](pvp_combat_integration.md)).
- Integrate with targeting/interaction from **E1.M3** (target lock, valid target checks).
- Award combat XP via E2.M2 integration where specified in slices.
## Key contracts
| Contract | Role |
|----------|------|
| `CombatAction` | Attack, ability, or other combat intent from client/input scaffold. |
| `CombatResolution` | Hits, misses, damage, denies with reason codes (no silent failures). |
| `ThreatState` | Aggro/threat for NPC and encounter coordination (as scoped). |
## Module dependencies
- **E1.M3** — [InteractionAndTargetingLayer](E1_M3_InteractionAndTargetingLayer.md): `TargetState`, selection, and alignment with **E1.M4** ([AbilityInputScaffold](E1_M4_AbilityInputScaffold.md)) for `AbilityCastRequest` wiring.
- **E2.M2** — [XpAwardAndLevelEngine](E2_M2_XpAwardAndLevelEngine.md): combat XP grants.
## Dependents (by design)
- **E5.M2** — NpcAiAndBehaviorProfiles.
- **E5.M3** — EncounterAndRewardTables.
- **E1.M4** — AbilityInputScaffold sends `AbilityCastRequest` to combat.
- **E7.M1** — QuestStateMachine for combat-related quest steps.
- **E2.M2** — Invoked as integration caller for XP (not a structural child).
## Related implementation slices
See Epic 5 **Slice 1 — Combat rules MVP**: target lock, basic attacks, 46 abilities, cooldowns/resources; telemetry `encounter_start`, `ability_used`, `player_death`, `enemy_defeat`.
**E5M1-04 (NEO-78):** **`GET /game/world/ability-definitions`** — `AbilityDefinitionsWorldApi` + DTOs in `Game/Combat/` ([NEO-78 plan](../../plans/NEO-78-implementation-plan.md)); [server README — Ability definitions (NEO-78)](../../../server/README.md#ability-definitions-neo-78); Bruno `bruno/neon-sprawl-server/ability-definitions/`.
**E5M1-05 (NEO-80):** **`ICombatEntityHealthStore`** + **`InMemoryCombatEntityHealthStore`** — lazy HP init for **`PrototypeTargetRegistry`** dummies at **`PrototypeCombatConstants.MaxPrototypeTargetHp` (100)** ([NEO-80 plan](../../plans/NEO-80-implementation-plan.md)); [server README — Combat entity health (NEO-80)](../../../server/README.md#combat-entity-health-neo-80).
**E5M1-06 (NEO-81):** **`CombatOperations.TryResolve`** + **`CombatResult`** + **`CombatReasonCodes`** — deterministic catalog damage, defeated pre-check deny (`target_defeated`), unknown ability/target validation ([NEO-81 plan](../../plans/NEO-81-implementation-plan.md)); [server README — Combat engine (NEO-81)](../../../server/README.md#combat-engine-neo-81).
**E5M1-07 (NEO-82):** **`AbilityCastApi`** → **`CombatOperations.TryResolve`** — nested wire **`combatResolution`** on accept, per-ability catalog **`cooldownSeconds`**, **`target_defeated`** cast deny without cooldown commit ([NEO-82 plan](../../plans/NEO-82-implementation-plan.md)); [server README — Ability cast (NEO-82)](../../../server/README.md#ability-cast-neo-31-neo-82); Bruno `bruno/neon-sprawl-server/ability-cast/`.
**E5M1-08 (NEO-83):** **`GET /game/world/combat-targets`** — `CombatTargetsWorldApi` + DTOs in `Game/Combat/`; authoritative HP snapshot from **`ICombatEntityHealthStore`** ([NEO-83 plan](../../plans/NEO-83-implementation-plan.md)); [server README — Combat targets snapshot (NEO-83)](../../../server/README.md#combat-targets-snapshot-neo-83); Bruno `bruno/neon-sprawl-server/combat-targets/`.
## Linear backlog (decomposed)
Full story tables, kickoff defaults, and **`blockedBy` graph:** [E5M1-prototype-backlog.md](../../plans/E5M1-prototype-backlog.md).
| Slug | Linear |
|------|--------|
| E5M1-01 | [NEO-76](https://linear.app/neon-sprawl/issue/NEO-76) |
| E5M1-02 | [NEO-77](https://linear.app/neon-sprawl/issue/NEO-77) |
| E5M1-03 | [NEO-79](https://linear.app/neon-sprawl/issue/NEO-79) |
| E5M1-04 | [NEO-78](https://linear.app/neon-sprawl/issue/NEO-78) |
| E5M1-05 | [NEO-80](https://linear.app/neon-sprawl/issue/NEO-80) |
| E5M1-06 | [NEO-81](https://linear.app/neon-sprawl/issue/NEO-81) |
| E5M1-07 | [NEO-82](https://linear.app/neon-sprawl/issue/NEO-82) |
| E5M1-08 | [NEO-83](https://linear.app/neon-sprawl/issue/NEO-83) |
| E5M1-09 | [NEO-84](https://linear.app/neon-sprawl/issue/NEO-84) |
| E5M1-10 | [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44) |
| E5M1-11 | [NEO-85](https://linear.app/neon-sprawl/issue/NEO-85) |
| E5M1-12 | [NEO-86](https://linear.app/neon-sprawl/issue/NEO-86) |
## Prototype Slice 1 freeze (E5M1-01)
The **first shipped four-ability combat spine** under `content/abilities/*.json` is **frozen** for hotbar bindings, cast validation, and combat resolution until a deliberate migration or follow-up issue expands the roster.
| `AbilityDef.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` |
**Rules:** Do **not** rename these `id` values—change **`displayName`** only, or add a new `id` in a follow-up issue. CI ([`scripts/validate_content.py`](../../../scripts/validate_content.py)) and server startup ([NEO-77](../../plans/NEO-77-implementation-plan.md)) enforce **exactly** these four ids and duplicate-`id` rejection across all ability JSON files. Relaxing or changing that gate belongs in a new Linear issue once the catalog intentionally grows. **`prototype_pulse`** at 25 damage × 4 casts defeats a 100 HP prototype dummy (E5M1 backlog default). **`IAbilityDefinitionRegistry`** + hotbar/cast migration **landed** ([NEO-79](../../plans/NEO-79-implementation-plan.md), E5M1-03).
## Prototype notes (NEO-28)
Epic 1 **Slice 3** cast accept/deny on **`POST /game/players/{id}/ability-cast`** remains the E1.M4 gate funnel: hotbar/loadout validation ([E1.M4](E1_M4_AbilityInputScaffold.md)), **`targetId`** must match the persisted combat lock, resolve in the prototype target registry, and sit within horizontal lock radius against authoritative **`PositionState`**. Deny codes **`invalid_target`** and **`out_of_range`** reuse the same strings as E1.M3 **`TargetState.validity`**. **NEO-82 (E5M1-07):** after gates pass, **`CombatOperations.TryResolve`** runs; accept returns nested wire **`combatResolution`** (`damageDealt`, `targetRemainingHp`, `targetDefeated`, …) and commits per-ability catalog cooldown; **`target_defeated`** JSON deny on re-hit at zero HP.
## Risks and telemetry
- Sluggish or opaque tab-target: prototype readability gate; instrument time-in-combat and deny reasons.
- Desync logging via E9.M1 taxonomy when available.
## Source anchors
- Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Core Epic Map, Epic 5.
- [Module dependency register](module_dependency_register.md)