# E5.M2 — NpcAiAndBehaviorProfiles ## Summary | Field | Value | |--------|--------| | **Module ID** | E5.M2 | | **Epic** | [Epic 5 — PvE Combat](../epics/epic_05_pve_combat.md) | | **Stage target** | Prototype | | **Status** | In Progress — Slice 2 backlog [E5M2-prototype-backlog.md](../../plans/E5M2-prototype-backlog.md): **E5M2-01** [NEO-87](https://linear.app/neon-sprawl/issue/NEO-87) catalog + CI landed · **E5M2-02** [NEO-88](https://linear.app/neon-sprawl/issue/NEO-88) server load landed · **E5M2-03** [NEO-89](https://linear.app/neon-sprawl/issue/NEO-89) registry + DI landed · **E5M2-04** [NEO-90](https://linear.app/neon-sprawl/issue/NEO-90) behavior-definitions GET landed · **E5M2-05** [NEO-91](https://linear.app/neon-sprawl/issue/NEO-91) NPC instance registry + combat-target migration landed · **E5M2-06** [NEO-92](https://linear.app/neon-sprawl/issue/NEO-92) aggro rule + threat store landed · **E5M2-07** [NEO-93](https://linear.app/neon-sprawl/issue/NEO-93) NPC runtime state machine landed · **E5M2-08** [NEO-94](https://linear.app/neon-sprawl/issue/NEO-94) runtime snapshot GET landed · **E5M2-09** [NEO-95](https://linear.app/neon-sprawl/issue/NEO-95) NPC attack resolve + player combat HP landed · **E5M2-10** [NEO-96](https://linear.app/neon-sprawl/issue/NEO-96) NPC runtime telemetry hooks landed → **E5M2-12** [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98) | | **Linear** | Label **`E5.M2`** · [E5M2-prototype-backlog.md](../../plans/E5M2-prototype-backlog.md) **E5M2-01** [NEO-87](https://linear.app/neon-sprawl/issue/NEO-87) · **E5M2-02** [NEO-88](https://linear.app/neon-sprawl/issue/NEO-88) · **E5M2-03** [NEO-89](https://linear.app/neon-sprawl/issue/NEO-89) · **E5M2-04** [NEO-90](https://linear.app/neon-sprawl/issue/NEO-90) · **E5M2-05** [NEO-91](https://linear.app/neon-sprawl/issue/NEO-91) → **E5M2-12** [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98) | ## Purpose **Authority:** [Client vs server](client_server_authority.md) — server owns NPC behavior state, aggro holders, telegraph timing, and NPC→player damage; client displays telegraphs and state from polled snapshots only. Enemy archetype behavior loops, aggro logic, and telegraph scheduling on top of [E5.M1 — CombatRulesEngine](E5_M1_CombatRulesEngine.md). Replaces passive **`prototype_target_alpha` / `beta`** dummies with **three** fight-back NPC instances for the vision prototype minimum ([three archetypes](../../../neon_sprawl_vision.plan.md#prototype-content-minimums)). ## Responsibilities - Run `NpcBehaviorDef`; emit `TelegraphEvent`; apply `AggroRule` / **`ThreatState`** integration with combat state. - Lazy NPC tick advance on **`GET /game/world/npc-runtime-snapshot`** poll (prototype). - Session **`IPlayerCombatHealthStore`** for incoming NPC damage (no Postgres in Slice 2). ## Key contracts | Contract | Role | |----------|------| | `NpcBehaviorDef` | Archetype scripts or state machines (content JSON + schema). | | `TelegraphEvent` | Telegraph timing for UI and dodge windows. | | `AggroRule` | Target selection and leash behavior. | | `ThreatState` | Per-NPC aggro holder (`playerId` or empty). | ## Module dependencies - **E5.M1** — CombatRulesEngine (player → NPC damage, combat-target HP, cast funnel). ## Dependents (by design) - **E4.M2** — SpawnEcologyController. - **E5.M3** — EncounterAndRewardTables. ## Related implementation slices Epic 5 **Slice 2 — NPC archetypes and telegraphs**: three archetypes (melee pressure, ranged control, elite mini-boss); telemetry `telegraph_fired`, `npc_state_transition`. **HTTP read model (NEO-90):** **`GET /game/world/npc-behavior-definitions`** — `NpcBehaviorDefinitionsWorldApi` + DTOs in `Game/Npc/`; Bruno `bruno/neon-sprawl-server/npc-behavior-definitions/`. ## Linear backlog (decomposed) Full story tables, kickoff defaults, and **`blockedBy` graph:** [E5M2-prototype-backlog.md](../../plans/E5M2-prototype-backlog.md). | Slug | Linear | |------|--------| | E5M2-01 | [NEO-87](https://linear.app/neon-sprawl/issue/NEO-87) | | E5M2-02 | [NEO-88](https://linear.app/neon-sprawl/issue/NEO-88) | | E5M2-03 | [NEO-89](https://linear.app/neon-sprawl/issue/NEO-89) | | E5M2-04 | [NEO-90](https://linear.app/neon-sprawl/issue/NEO-90) | | E5M2-05 | [NEO-91](https://linear.app/neon-sprawl/issue/NEO-91) | | E5M2-06 | [NEO-92](https://linear.app/neon-sprawl/issue/NEO-92) | | E5M2-07 | [NEO-93](https://linear.app/neon-sprawl/issue/NEO-93) | | E5M2-08 | [NEO-94](https://linear.app/neon-sprawl/issue/NEO-94) | | E5M2-09 | [NEO-95](https://linear.app/neon-sprawl/issue/NEO-95) | | E5M2-10 | [NEO-96](https://linear.app/neon-sprawl/issue/NEO-96) | | E5M2-11 | [NEO-97](https://linear.app/neon-sprawl/issue/NEO-97) | | E5M2-12 | [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98) | ## Prototype Slice 2 freeze (E5M2-01) The **first shipped three-archetype NPC spine** under `content/npc-behaviors/*.json` is **frozen** for behavior binding, aggro tuning, and telegraph timings until a deliberate migration issue expands the roster. | `NpcBehaviorDef.id` | `displayName` | `archetypeKind` | `maxHp` | `aggroRadius` | `leashRadius` | `telegraphWindupSeconds` | `attackDamage` | `attackCooldownSeconds` | |---------------------|---------------|-----------------|---------|---------------|---------------|--------------------------|----------------|-------------------------| | **`prototype_melee_pressure`** | Melee Pressure | `melee_pressure` | 100 | 8.0 | 16.0 | 1.5 | 15 | 3.0 | | **`prototype_ranged_control`** | Ranged Control | `ranged_control` | 80 | 10.0 | 20.0 | 2.0 | 12 | 4.0 | | **`prototype_elite_mini_boss`** | Elite Mini-Boss | `elite_mini_boss` | 200 | 8.0 | 18.0 | 2.5 | 25 | 5.0 | **NPC instance ids (E5M2-05):** **`prototype_npc_melee`**, **`prototype_npc_ranged`**, **`prototype_npc_elite`** — each binds one behavior def + world anchor (replaces **`prototype_target_alpha` / `beta`**). **Rules:** Do **not** rename frozen behavior **`id`** values without a migration issue. CI ([`scripts/validate_content.py`](../../../scripts/validate_content.py)) and server startup ([NEO-88](../../plans/NEO-88-implementation-plan.md)) enforce **exactly** these three behavior ids, duplicate-`id` rejection, and **`leashRadius` > `aggroRadius`** per row. Relaxing or changing that gate belongs in a new Linear issue once the catalog intentionally grows. Catalog + CI plan: [NEO-87 implementation plan](../../plans/NEO-87-implementation-plan.md). **`INpcBehaviorDefinitionRegistry`** + DI **landed** ([NEO-89](../../plans/NEO-89-implementation-plan.md), E5M2-03) — game code and HTTP read models should inject the registry, not **`NpcBehaviorDefinitionCatalog`**. Read-only HTTP projection: **`GET /game/world/npc-behavior-definitions`** ([NEO-90](../../plans/NEO-90-implementation-plan.md), E5M2-04). **Server load (NEO-88):** On host startup, `server/NeonSprawl.Server/Game/Npc/` loads `content/npc-behaviors/*_npc_behaviors.json` with the same validation gates as CI and **refuses to listen** when the catalog is invalid. Config and discovery: [server README — NPC behavior catalog](../../../server/README.md#npc-behavior-catalog-contentnpc-behaviors-neo-88). Plan: [NEO-88 implementation plan](../../plans/NEO-88-implementation-plan.md). **NPC behavior definition registry (NEO-89):** **`INpcBehaviorDefinitionRegistry`** in `server/NeonSprawl.Server/Game/Npc/` wraps the startup-loaded catalog for read-only lookup by stable behavior `id` (`TryGetDefinition`), trim/lowercase validation (`TryNormalizeKnown`), and ordered enumeration (`GetDefinitionsInIdOrder`). Stable prototype id constants: **`PrototypeNpcBehaviorRegistry`**. **NEO-91+** (instance registry, runtime tick) should inject the interface rather than `NpcBehaviorDefinitionCatalog`. Plan: [NEO-89 implementation plan](../../plans/NEO-89-implementation-plan.md). **NPC instance registry (NEO-91):** **`PrototypeNpcRegistry`** in `server/NeonSprawl.Server/Game/Npc/` — instance ids **`prototype_npc_melee`**, **`prototype_npc_ranged`**, **`prototype_npc_elite`** with behavior bindings + world anchors; **`ICombatEntityHealthStore`** uses catalog **`maxHp`** per instance. Plan: [NEO-91 implementation plan](../../plans/NEO-91-implementation-plan.md). Godot **`prototype_target_constants.gd`** migration **landed in NEO-91**; [NEO-97](https://linear.app/neon-sprawl/issue/NEO-97) adds HUD poll clients only. **Threat / aggro store (NEO-92):** **`IThreatStateStore`** + **`AggroOperations`** in `server/NeonSprawl.Server/Game/Npc/` — first damaging cast acquires holder; leash break clears holder (move + cast hooks). HTTP projection: **`aggroHolderPlayerId`** on **`GET /game/world/npc-runtime-snapshot`** ([NEO-94](../../plans/NEO-94-implementation-plan.md)). Plan: [NEO-92 implementation plan](../../plans/NEO-92-implementation-plan.md). **NPC runtime state machine (NEO-93):** **`INpcRuntimeStateStore`** + **`NpcRuntimeOperations.AdvanceAll`** in `server/NeonSprawl.Server/Game/Npc/` — lazy tick advance for **`idle`** → **`aggro`** → **`telegraph_windup`** → **`attack_execute`** → **`recover`**; holder sync from **`IThreatStateStore`**. HTTP projection: **`GET /game/world/npc-runtime-snapshot`** ([NEO-94](../../plans/NEO-94-implementation-plan.md)). Plan: [NEO-93 implementation plan](../../plans/NEO-93-implementation-plan.md). **NPC runtime snapshot HTTP (NEO-94):** **`GET /game/world/npc-runtime-snapshot`** — versioned read-only projection (`schemaVersion` **1**, **`npcInstances`**, optional nested **`activeTelegraph`**) with lazy **`AdvanceAll`** on poll. Plan: [NEO-94 implementation plan](../../plans/NEO-94-implementation-plan.md); [server README — NPC runtime snapshot (NEO-94)](../../../server/README.md#npc-runtime-snapshot-neo-94); Bruno `bruno/neon-sprawl-server/npc-runtime-snapshot/`. **Session player combat HP (NEO-95):** **`IPlayerCombatHealthStore`** + **`NpcAttackOperations.TryResolveTelegraphComplete`** in `server/NeonSprawl.Server/Game/Combat/` — telegraph complete applies catalog **`attackDamage`** to aggro holder; **`GET /game/players/{id}/combat-health`** read model for client HUD. Plan: [NEO-95 implementation plan](../../plans/NEO-95-implementation-plan.md); [server README — Session player combat HP (NEO-95)](../../../server/README.md#session-player-combat-hp-neo-95); Bruno `bruno/neon-sprawl-server/combat-health/`. **NPC runtime telemetry hooks (NEO-96):** comment-only **`telegraph_fired`** + **`npc_state_transition`** hook sites in **`NpcRuntimeOperations.AdvanceAll`** (`TODO(E9.M1)`; no ingest). Plan: [NEO-96 implementation plan](../../plans/NEO-96-implementation-plan.md); [server README — NPC runtime telemetry hooks (NEO-96)](../../../server/README.md#npc-runtime-telemetry-hooks-neo-96). **Client telegraph HUD (NEO-97):** Godot poll of **`npc-runtime-snapshot`** + **`combat-health`**; **`TelegraphLabel`**, **`NpcStateLabel`**, **`PlayerCombatHpLabel`**; combat-active ~1 Hz poll gate. Plan: [NEO-97 implementation plan](../../plans/NEO-97-implementation-plan.md); [client README — NPC runtime + telegraph HUD (NEO-97)](../../../client/README.md#npc-runtime--telegraph-hud-neo-97); manual QA [NEO-97](../../manual-qa/NEO-97.md). **NPC behavior definitions HTTP (NEO-90):** **`GET /game/world/npc-behavior-definitions`** — versioned read-only projection (`schemaVersion` **1**, **`npcBehaviors`**) backed by **`INpcBehaviorDefinitionRegistry`**. Plan: [NEO-90 implementation plan](../../plans/NEO-90-implementation-plan.md); [server README — NPC behavior definitions (NEO-90)](../../../server/README.md#npc-behavior-definitions-neo-90); Bruno `bruno/neon-sprawl-server/npc-behavior-definitions/`. ## Risks and telemetry - Telegraph desync: single **`npc-runtime-snapshot`** poll surface; client display-only windup tick-down between polls (reconciled on each GET via **`npc_runtime_hud_state.gd`**). - Aggro confusion: deterministic first-hit + leash rules; instrument `npc_state_transition` when E9.M1 lands. ## Source anchors - Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Epic 5, prototype enemy minimums. - [Module dependency register](module_dependency_register.md)