# 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** | Ready — 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 [NEO-84](https://linear.app/neon-sprawl/issue/NEO-84) combat telemetry hook sites landed; E5M1-10 [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44) gig XP on defeat landed; E5M1-11 [NEO-85](https://linear.app/neon-sprawl/issue/NEO-85) client combat HUD landed; E5M1-12 [NEO-86](https://linear.app/neon-sprawl/issue/NEO-86) playable combat capstone landed — Epic 5 Slice 1 client complete (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. **Combat defeat** awards **gig XP** via **`IPlayerGigProgressionStore`** ([NEO-44](../../plans/NEO-44-implementation-plan.md)) — **not** E2.M2 skill XP ([progression.md](../../game-design/progression.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 **gig XP** on combat defeat via dedicated gig progression store ([NEO-44](../../plans/NEO-44-implementation-plan.md)); do **not** route default encounter clears through E2.M2 skill XP.

## 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): **skill** XP for gather/craft/mission callers only; combat → gig XP stays on E5.M1 (NEO-44).

## 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** — Skill XP integration caller for gather/craft/mission paths (not structural child; not combat gig XP).

## Related implementation slices

See Epic 5 **Slice 1 — Combat rules MVP**: target lock, basic attacks, 4–6 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/`.

**E5M1-09 (NEO-84):** comment-only Slice 1 telemetry hook sites in **`CombatOperations.TryResolve`** — **`ability_used`**, **`enemy_defeat`**; reserved **`encounter_start`** / **`player_death`** ([NEO-84 plan](../../plans/NEO-84-implementation-plan.md)); [server README — Combat telemetry hooks (NEO-84)](../../../server/README.md#combat-telemetry-hooks-neo-84).

**E5M1-10 (NEO-44):** **`CombatDefeatGigXpGrant`** on cast accept when **`targetDefeated`** — **25** gig XP to prototype main gig **`breach`** via **`IPlayerGigProgressionStore`** (not E2.M2 skill XP); **`GET /game/players/{id}/gig-progression`** ([NEO-44 plan](../../plans/NEO-44-implementation-plan.md)); [server README — Gig progression snapshot (NEO-44)](../../../server/README.md#gig-progression-snapshot-neo-44); Bruno `bruno/neon-sprawl-server/gig-progression/`.

**E5M1-11 (NEO-85):** client combat HUD — **`ability_cast_client.gd`** parses nested **`combatResolution`** on accept; **`combat_targets_client.gd`** + **`CombatTargetHpLabel`**; **`CastFeedbackLabel`** damage/defeat copy; event-driven **`GET /game/world/combat-targets`** refresh on cast accept and lock change ([NEO-85 plan](../../plans/NEO-85-implementation-plan.md), [`NEO-85` manual QA](../../manual-qa/NEO-85.md)); [client README — Combat feedback + target HP HUD (NEO-85)](../../../client/README.md#combat-feedback--target-hp-hud-neo-85).

**E5M1-12 (NEO-86):** playable combat capstone — **`gig_progression_client.gd`** + **`GigXpLabel`**; defeat-triggered gig GET refresh; capstone manual QA [`NEO-86`](../../manual-qa/NEO-86.md) ([NEO-86 plan](../../plans/NEO-86-implementation-plan.md)); [client README — End-to-end combat loop (NEO-86)](../../../client/README.md#end-to-end-combat-loop-neo-86). Epic 5 Slice 1 client capstone complete.

## 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)
