diff --git a/docs/decomposition/epics/epic_05_pve_combat.md b/docs/decomposition/epics/epic_05_pve_combat.md index 1cb4300..5249680 100644 --- a/docs/decomposition/epics/epic_05_pve_combat.md +++ b/docs/decomposition/epics/epic_05_pve_combat.md @@ -64,6 +64,8 @@ Deliver tab-target combat resolution, NPC behaviors and telegraphs, encounter te - Telegraph timing matches UI feedback; aggro rules deterministic. - Telemetry hooks: `telegraph_fired`, `npc_state_transition`. +**Linear backlog:** [E5M2-prototype-backlog.md](../../plans/E5M2-prototype-backlog.md) — [NEO-87](https://linear.app/neon-sprawl/issue/NEO-87) → [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98); client capstone **E5M2-12** [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98). Verify in **Godot**, not Bruno-only. + ### Slice 3 - Encounters and loot routing - Scope: E5.M3 wired to E3.M3 and E7.M2 for rewards and quest progress. diff --git a/docs/decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md b/docs/decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md index ba4aae3..b7a31bf 100644 --- a/docs/decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md +++ b/docs/decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md @@ -7,27 +7,33 @@ | **Module ID** | E5.M2 | | **Epic** | [Epic 5 — PvE Combat](../epics/epic_05_pve_combat.md) | | **Stage target** | Prototype | -| **Status** | Planned (see [dependency register](module_dependency_register.md)) | +| **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) → **E5M2-12** [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98) (decomposed; implementation not started) | +| **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-12** [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98) | ## Purpose -Enemy archetype behavior loops, aggro logic, and telegraph scheduling on top of [E5.M1 — CombatRulesEngine](E5_M1_CombatRulesEngine.md). +**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` / threat integration with combat state. +- 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. | +| `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. +- **E5.M1** — CombatRulesEngine (player → NPC damage, combat-target HP, cast funnel). ## Dependents (by design) @@ -36,9 +42,47 @@ Enemy archetype behavior loops, aggro logic, and telegraph scheduling on top of ## Related implementation slices -Epic 5 **Slice 2** — three archetypes; `telegraph_fired`, `npc_state_transition`. +Epic 5 **Slice 2 — NPC archetypes and telegraphs**: three archetypes (melee pressure, ranged control, elite mini-boss); telemetry `telegraph_fired`, `npc_state_transition`. + +## 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` | `telegraphWindupSeconds` | `attackDamage` | +|---------------------|---------------|-----------------|---------|--------------------------|----------------| +| **`prototype_melee_pressure`** | Melee Pressure | `melee_pressure` | 100 | 1.5 | 15 | +| **`prototype_ranged_control`** | Ranged Control | `ranged_control` | 80 | 2.0 | 12 | +| **`prototype_elite_mini_boss`** | Elite Mini-Boss | `elite_mini_boss` | 200 | 2.5 | 25 | + +**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 enforces **exactly** these three behavior ids. Tune combat feel via numeric fields or new ids in a follow-up issue. + +## Risks and telemetry + +- Telegraph desync: single **`npc-runtime-snapshot`** poll surface; no client windup math. +- 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. +- Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Epic 5, prototype enemy minimums. - [Module dependency register](module_dependency_register.md) diff --git a/docs/decomposition/modules/documentation_and_implementation_alignment.md b/docs/decomposition/modules/documentation_and_implementation_alignment.md index da30688..87ac9e0 100644 --- a/docs/decomposition/modules/documentation_and_implementation_alignment.md +++ b/docs/decomposition/modules/documentation_and_implementation_alignment.md @@ -57,6 +57,7 @@ Rows appear when work starts; default for unlisted modules is **Planned** / not | E3.M3 | In Progress | **NEO-50 landed:** frozen prototype six-item catalog in [`content/items/prototype_items.json`](../../../content/items/prototype_items.json); [`item-def.schema.json`](../../../content/schemas/item-def.schema.json); PR gate + [`validate_content.py`](../../../scripts/validate_content.py). **NEO-51 landed:** fail-fast server load of `content/items/*_items.json` at startup — `server/NeonSprawl.Server/Game/Items/` ([NEO-51](../../plans/NEO-51-implementation-plan.md)); [server README — Item catalog](../../../server/README.md#item-catalog-contentitems-neo-51). **NEO-52 landed:** injectable **`IItemDefinitionRegistry`** + lookup tests ([NEO-52](../../plans/NEO-52-implementation-plan.md)). **NEO-53 landed:** **`GET /game/world/item-definitions`** — `ItemDefinitionsWorldApi` + DTOs in `Game/Items/` ([NEO-53](../../plans/NEO-53-implementation-plan.md), [`NEO-53` manual QA](../../manual-qa/NEO-53.md)); [server README — Item definitions (NEO-53)](../../../server/README.md#item-definitions-neo-53); Bruno `bruno/neon-sprawl-server/item-definitions/`. **NEO-54 landed:** **`IPlayerInventoryStore`** + **`PlayerInventoryOperations`** — 24 bag + 1 equipment slots, stack rules, `V005` migration ([NEO-54](../../plans/NEO-54-implementation-plan.md)). **NEO-55 landed:** **`GET`/`POST /game/players/{id}/inventory`** — `PlayerInventoryApi` + DTOs in `Game/Items/` ([NEO-55](../../plans/NEO-55-implementation-plan.md)); [server README — Player inventory (NEO-54 store, NEO-55 HTTP)](../../../server/README.md#player-inventory-neo-54-store-neo-55-http); Bruno `bruno/neon-sprawl-server/inventory/`. **NEO-56 landed:** comment-only **`item_created`** / **`inventory_transfer_denied`** telemetry hook sites in [`PlayerInventoryOperations`](../../../server/NeonSprawl.Server/Game/Items/PlayerInventoryOperations.cs) ([NEO-56](../../plans/NEO-56-implementation-plan.md), [`NEO-56` manual QA](../../manual-qa/NEO-56.md)); no E9.M1 ingest. **NEO-72 landed:** client inventory HUD — **`inventory_client.gd`**, **`item_definitions_client.gd`**, **`InventoryLabel`**, boot hydrate + **`I`** refresh ([NEO-72](../../plans/NEO-72-implementation-plan.md), [`NEO-72` manual QA](../../manual-qa/NEO-72.md)); `client/README.md` inventory HUD section. **NEO-75 landed:** **`prototype_economy_hud_section.gd`** collapse toggle + capstone manual QA ([NEO-75](../../plans/NEO-75-implementation-plan.md), [`NEO-75` manual QA](../../manual-qa/NEO-75.md)); Epic 3 Slice 5 client complete. | [NEO-50](../../plans/NEO-50-implementation-plan.md), [NEO-51](../../plans/NEO-51-implementation-plan.md), [NEO-52](../../plans/NEO-52-implementation-plan.md), [NEO-53](../../plans/NEO-53-implementation-plan.md), [NEO-54](../../plans/NEO-54-implementation-plan.md), [NEO-55](../../plans/NEO-55-implementation-plan.md), [NEO-56](../../plans/NEO-56-implementation-plan.md), [NEO-72](../../plans/NEO-72-implementation-plan.md), [NEO-75](../../plans/NEO-75-implementation-plan.md), [E3S5-client-prototype-backlog](../../plans/E3S5-client-prototype-backlog.md), [E3M3-prototype-backlog](../../plans/E3M3-prototype-backlog.md), [E3_M3](E3_M3_ItemizationAndInventorySchema.md) | | E3.M2 | Ready | **NEO-65 landed:** frozen prototype eight-recipe catalog in [`content/recipes/prototype_recipes.json`](../../../content/recipes/prototype_recipes.json); [`recipe-def.schema.json`](../../../content/schemas/recipe-def.schema.json) + [`recipe-io-row.schema.json`](../../../content/schemas/recipe-io-row.schema.json); PR gate + [`validate_content.py`](../../../scripts/validate_content.py) Slice 3 gates ([NEO-65](../../plans/NEO-65-implementation-plan.md)). **NEO-66 landed:** fail-fast server load of `content/recipes/*_recipes.json` at startup — `server/NeonSprawl.Server/Game/Crafting/` ([NEO-66](../../plans/NEO-66-implementation-plan.md)); [server README — Recipe catalog](../../../server/README.md#recipe-catalog-contentrecipes-neo-66). **NEO-67 landed:** injectable **`IRecipeDefinitionRegistry`** + lookup tests ([NEO-67](../../plans/NEO-67-implementation-plan.md)). **NEO-68 landed:** **`GET /game/world/recipe-definitions`** — `RecipeDefinitionsWorldApi` + DTOs in `Game/Crafting/` ([NEO-68](../../plans/NEO-68-implementation-plan.md), [`NEO-68` manual QA](../../manual-qa/NEO-68.md)); [server README — Recipe definitions (NEO-68)](../../../server/README.md#recipe-definitions-neo-68); Bruno `bruno/neon-sprawl-server/recipe-definitions/`. **NEO-69 landed:** **`CraftOperations.TryCraft`** + **`CraftResult`** — pre-flight inputs/outputs, inventory mutation, refine XP ([NEO-69](../../plans/NEO-69-implementation-plan.md)); [server README — Craft engine (NEO-69)](../../../server/README.md#craft-engine-neo-69-and-craft-http-neo-70). **NEO-70 landed:** **`POST /game/players/{id}/craft`** — `PlayerCraftApi` + wire **`CraftResponse`**; Bruno gather→refine→make spine ([NEO-70](../../plans/NEO-70-implementation-plan.md)); Bruno `bruno/neon-sprawl-server/craft/`. **NEO-71 landed:** comment-only **`item_crafted`** / **`craft_failed`** telemetry hook sites in **`CraftOperations.TryCraft`** ([NEO-71](../../plans/NEO-71-implementation-plan.md)); [server README — Craft telemetry hooks (NEO-71)](../../../server/README.md#craft-telemetry-hooks-neo-71). **NEO-42 landed (prep):** **`RefineActivitySkillXpGrant`** / **`RefineSkillXpConstants`** — wired from craft engine success path. Epic 3 Slice 3 server backlog **E3M2-01**–**E3M2-07** complete. Upstream: E3.M1 **Ready**, E3.M3 inventory landed. **NEO-74 landed:** client craft UI — **`recipe_definitions_client.gd`**, **`craft_client.gd`**, **`craft_recipe_panel.gd`**, **`CraftFeedbackLabel`**, **refine** skill row ([NEO-74](../../plans/NEO-74-implementation-plan.md), [`NEO-74` manual QA](../../manual-qa/NEO-74.md)); `client/README.md` craft section. **NEO-75 landed:** capstone playable gather→refine→make loop — [`NEO-75` manual QA](../../manual-qa/NEO-75.md), **`prototype_economy_hud_section.gd`**; Epic 3 Slice 5 client complete. | [NEO-42](../../plans/NEO-42-implementation-plan.md), [NEO-65](../../plans/NEO-65-implementation-plan.md), [NEO-66](../../plans/NEO-66-implementation-plan.md), [NEO-67](../../plans/NEO-67-implementation-plan.md), [NEO-68](../../plans/NEO-68-implementation-plan.md), [NEO-69](../../plans/NEO-69-implementation-plan.md), [NEO-70](../../plans/NEO-70-implementation-plan.md), [NEO-71](../../plans/NEO-71-implementation-plan.md), [NEO-74](../../plans/NEO-74-implementation-plan.md), [NEO-75](../../plans/NEO-75-implementation-plan.md), [E3M2-prototype-backlog](../../plans/E3M2-prototype-backlog.md), [E3S5-client-prototype-backlog](../../plans/E3S5-client-prototype-backlog.md), [E3_M2](E3_M2_RefinementAndRecipeExecution.md) | | E5.M1 | Ready | **NEO-76 landed:** frozen prototype four-ability catalog in [`content/abilities/prototype_abilities.json`](../../../content/abilities/prototype_abilities.json); [`ability-def.schema.json`](../../../content/schemas/ability-def.schema.json); PR gate + [`validate_content.py`](../../../scripts/validate_content.py) E5M1 four-id gate ([NEO-76](../../plans/NEO-76-implementation-plan.md)). **NEO-77 landed:** fail-fast server load of `content/abilities/*_abilities.json` at startup — `server/NeonSprawl.Server/Game/Combat/` ([NEO-77](../../plans/NEO-77-implementation-plan.md)); [server README — Ability catalog](../../../server/README.md#ability-catalog-contentabilities-neo-77). **NEO-79 landed:** injectable **`IAbilityDefinitionRegistry`** + DI; hotbar/cast use **`TryNormalizeKnown`** ([NEO-79](../../plans/NEO-79-implementation-plan.md)); Bruno `bruno/neon-sprawl-server/hotbar-loadout/` + `ability-cast/` unknown-ability deny smokes. **NEO-78 landed:** **`GET /game/world/ability-definitions`** — `AbilityDefinitionsWorldApi` + DTOs in `Game/Combat/` ([NEO-78](../../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/`. **NEO-80 landed:** **`ICombatEntityHealthStore`** / **`InMemoryCombatEntityHealthStore`** — lazy prototype dummy HP at **`PrototypeCombatConstants.MaxPrototypeTargetHp` (100)**; DI via **`AddCombatEntityHealthStore()`** ([NEO-80](../../plans/NEO-80-implementation-plan.md)); [server README — Combat entity health (NEO-80)](../../../server/README.md#combat-entity-health-neo-80). **NEO-81 landed:** **`CombatOperations.TryResolve`** + **`CombatResult`** + **`CombatReasonCodes`** — defeated pre-check deny, catalog damage ([NEO-81](../../plans/NEO-81-implementation-plan.md)); [server README — Combat engine (NEO-81)](../../../server/README.md#combat-engine-neo-81). **NEO-82 landed:** **`AbilityCastApi`** → **`CombatOperations.TryResolve`**; nested wire **`combatResolution`** on accept; per-ability catalog cooldown; **`target_defeated`** cast deny ([NEO-82](../../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/` defeat spine. **NEO-83 landed:** **`GET /game/world/combat-targets`** — `CombatTargetsWorldApi` + DTOs; authoritative HP snapshot from **`ICombatEntityHealthStore`** ([NEO-83](../../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/`. **NEO-44 landed:** **`CombatDefeatGigXpGrant`** on cast **`targetDefeated`** — **25** gig XP to **`breach`** via **`IPlayerGigProgressionStore`** (V007); **`GET …/gig-progression`** ([NEO-44](../../plans/NEO-44-implementation-plan.md), [`NEO-44` manual QA](../../manual-qa/NEO-44.md)); [server README — Gig progression (NEO-44)](../../../server/README.md#gig-progression-snapshot-neo-44); Bruno `bruno/neon-sprawl-server/gig-progression/`. **NEO-84 landed:** comment-only **`ability_used`** / **`enemy_defeat`** telemetry hook sites in **`CombatOperations.TryResolve`**; reserved **`encounter_start`** / **`player_death`** ([NEO-84](../../plans/NEO-84-implementation-plan.md)); [server README — Combat telemetry hooks (NEO-84)](../../../server/README.md#combat-telemetry-hooks-neo-84). **NEO-85 landed:** client combat HUD — **`ability_cast_client.gd`** parses **`combatResolution`**; **`combat_targets_client.gd`**, **`CastFeedbackLabel`** resolution copy, **`CombatTargetHpLabel`**; event-driven GET refresh ([NEO-85](../../plans/NEO-85-implementation-plan.md), [`NEO-85` manual QA](../../manual-qa/NEO-85.md)); `client/README.md` combat HUD section. **NEO-86 landed:** playable combat capstone — **`gig_progression_client.gd`**, **`GigXpLabel`**, defeat-triggered gig GET refresh; capstone manual QA [`NEO-86`](../../manual-qa/NEO-86.md); `client/README.md` end-to-end combat loop section ([NEO-86](../../plans/NEO-86-implementation-plan.md)). **Epic 5 Slice 1 client capstone complete.** **Backlog decomposed:** Epic 5 Slice 1 — **E5M1-01**–**E5M1-12** landed. **Precursor landed:** E1.M4 cast funnel (NEO-28/31/32). | [E5M1-prototype-backlog.md](../../plans/E5M1-prototype-backlog.md), [E5_M1](E5_M1_CombatRulesEngine.md), [NEO-77](../../plans/NEO-77-implementation-plan.md), [NEO-79](../../plans/NEO-79-implementation-plan.md), [NEO-78](../../plans/NEO-78-implementation-plan.md), [NEO-80](../../plans/NEO-80-implementation-plan.md), [NEO-81](../../plans/NEO-81-implementation-plan.md), [NEO-82](../../plans/NEO-82-implementation-plan.md), [NEO-83](../../plans/NEO-83-implementation-plan.md), [NEO-44](../../plans/NEO-44-implementation-plan.md), [NEO-84](../../plans/NEO-84-implementation-plan.md), [NEO-85](../../plans/NEO-85-implementation-plan.md), [NEO-86](../../plans/NEO-86-implementation-plan.md), [E1M4-prototype-backlog.md](../../plans/E1M4-prototype-backlog.md) | +| E5.M2 | In Progress | **Backlog decomposed:** Epic 5 Slice 2 — [E5M2-prototype-backlog.md](../../plans/E5M2-prototype-backlog.md) **E5M2-01** [NEO-87](https://linear.app/neon-sprawl/issue/NEO-87) → **E5M2-12** [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98); client capstone **NEO-98**. Three archetypes (`prototype_melee_pressure`, `prototype_ranged_control`, `prototype_elite_mini_boss`); replaces alpha/beta dummies with **`prototype_npc_melee` / `ranged` / `elite`**. Owns **`ThreatState`**, **`TelegraphEvent`**, **`GET …/npc-runtime-snapshot`**, session **`IPlayerCombatHealthStore`**. Upstream **E5.M1 Ready**. No implementation landed yet. | [E5M2-prototype-backlog.md](../../plans/E5M2-prototype-backlog.md), [E5_M2](E5_M2_NpcAiAndBehaviorProfiles.md), label **`E5.M2`** on NEO-87–NEO-98 | --- diff --git a/docs/decomposition/modules/module_dependency_register.md b/docs/decomposition/modules/module_dependency_register.md index ac76785..4aedf87 100644 --- a/docs/decomposition/modules/module_dependency_register.md +++ b/docs/decomposition/modules/module_dependency_register.md @@ -71,12 +71,14 @@ Fleshed-out scope, contracts, and integration notes live in **per-module documen | Module ID | Module Name | Depends On | Contract Needed | Phase Required | Status | |---|---|---|---|---|---| -| E5.M1 | CombatRulesEngine | E1.M3, E2.M2 | CombatAction, CombatResolution, ThreatState | Prototype | In Progress | -| E5.M2 | NpcAiAndBehaviorProfiles | E5.M1 | NpcBehaviorDef, TelegraphEvent, AggroRule | Prototype | Planned | +| E5.M1 | CombatRulesEngine | E1.M3, E2.M2 | CombatAction, CombatResolution, ThreatState | Prototype | Ready | +| E5.M2 | NpcAiAndBehaviorProfiles | E5.M1 | NpcBehaviorDef, TelegraphEvent, AggroRule | Prototype | In Progress | | E5.M3 | EncounterAndRewardTables | E5.M1, E3.M3, E7.M2 | EncounterDef, RewardTable, EncounterCompleteEvent | Prototype | Planned | | E5.M4 | GroupCombatScaling | E5.M3, E8.M1 | ScalingProfile, PartySizeModifier, CombatDifficultyBand | Pre-production | Planned | -**E5.M1 note:** Epic 5 **Slice 1** backlog in Linear ([Epic 5 — PvE Combat and Encounter Content](https://linear.app/neon-sprawl/project/epic-5-pve-combat-and-encounter-content-cf3c94c3-5346-40e0-8aaf-281e846f2846)): [NEO-76](https://linear.app/neon-sprawl/issue/NEO-76) → [NEO-86](https://linear.app/neon-sprawl/issue/NEO-86); label **`E5.M1`**. Gig XP on defeat: **E5M1-10** [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44). See [E5M1-prototype-backlog.md](../../plans/E5M1-prototype-backlog.md), [E5_M1_CombatRulesEngine.md](E5_M1_CombatRulesEngine.md). Builds on E1.M4 cast funnel (NEO-28/31/32); extends **`POST …/ability-cast`** with **`CombatResolution`**. **NEO-76 landed:** frozen four-ability catalog + CI ([NEO-76 plan](../../plans/NEO-76-implementation-plan.md)). **NEO-77 landed:** fail-fast server load of `content/abilities/*_abilities.json` ([NEO-77 plan](../../plans/NEO-77-implementation-plan.md)). **NEO-79 landed:** injectable **`IAbilityDefinitionRegistry`** + DI; hotbar/cast use **`TryNormalizeKnown`** ([NEO-79 plan](../../plans/NEO-79-implementation-plan.md)). **NEO-78 landed:** **`GET /game/world/ability-definitions`** — `AbilityDefinitionsWorldApi` + DTOs in `Game/Combat/` ([NEO-78 plan](../../plans/NEO-78-implementation-plan.md)); Bruno `bruno/neon-sprawl-server/ability-definitions/`. **NEO-80 landed:** **`ICombatEntityHealthStore`** / **`InMemoryCombatEntityHealthStore`** — lazy prototype dummy HP at **`PrototypeCombatConstants.MaxPrototypeTargetHp` (100)**; DI via **`AddCombatEntityHealthStore()`** ([NEO-80 plan](../../plans/NEO-80-implementation-plan.md)); [server README — Combat entity health (NEO-80)](../../../server/README.md#combat-entity-health-neo-80). **NEO-81 landed:** **`CombatOperations.TryResolve`** + **`CombatResult`** + **`CombatReasonCodes`** — defeated pre-check deny vocabulary (`target_defeated`, `unknown_ability`, `unknown_target`) ([NEO-81 plan](../../plans/NEO-81-implementation-plan.md)); [server README — Combat engine (NEO-81)](../../../server/README.md#combat-engine-neo-81). **NEO-82 landed:** **`AbilityCastApi`** invokes **`CombatOperations.TryResolve`** after E1.M4 gates; nested wire **`combatResolution`** on accept; per-ability catalog **`cooldownSeconds`** on successful resolve; **`target_defeated`** JSON cast deny without cooldown ([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/` defeat spine. **NEO-83 landed:** **`GET /game/world/combat-targets`** — `CombatTargetsWorldApi` + DTOs; authoritative HP snapshot from **`ICombatEntityHealthStore`** for client HUD ([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/`. **NEO-44 landed:** **`CombatDefeatGigXpGrant`** on cast **`targetDefeated`** — **25** gig XP to **`breach`** via **`IPlayerGigProgressionStore`** (V007); **`GET …/gig-progression`** ([NEO-44 plan](../../plans/NEO-44-implementation-plan.md)); [server README — Gig progression (NEO-44)](../../../server/README.md#gig-progression-snapshot-neo-44). **NEO-84 landed:** comment-only **`ability_used`** / **`enemy_defeat`** telemetry hook sites in **`CombatOperations.TryResolve`**; 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). **NEO-85 landed:** client combat HUD — **`combat_targets_client.gd`**, **`CombatTargetHpLabel`**, **`CastFeedbackLabel`** resolution copy; event-driven **`GET /game/world/combat-targets`** refresh ([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). **NEO-86 landed:** 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.** +**E5.M1 note:** Epic 5 **Slice 1** backlog in Linear ([Epic 5 — PvE Combat and Encounter Content](https://linear.app/neon-sprawl/project/epic-5-pve-combat-and-encounter-content-cf3c94c3-5346-40e0-8aaf-281e846f2846)): [NEO-76](https://linear.app/neon-sprawl/issue/NEO-76) → [NEO-86](https://linear.app/neon-sprawl/issue/NEO-86); label **`E5.M1`**. Gig XP on defeat: **E5M1-10** [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44). See [E5M1-prototype-backlog.md](../../plans/E5M1-prototype-backlog.md), [E5_M1_CombatRulesEngine.md](E5_M1_CombatRulesEngine.md). Builds on E1.M4 cast funnel (NEO-28/31/32); extends **`POST …/ability-cast`** with **`CombatResolution`**. **NEO-76 landed:** frozen four-ability catalog + CI ([NEO-76 plan](../../plans/NEO-76-implementation-plan.md)). **NEO-77 landed:** fail-fast server load of `content/abilities/*_abilities.json` ([NEO-77 plan](../../plans/NEO-77-implementation-plan.md)). **NEO-79 landed:** injectable **`IAbilityDefinitionRegistry`** + DI; hotbar/cast use **`TryNormalizeKnown`** ([NEO-79 plan](../../plans/NEO-79-implementation-plan.md)). **NEO-78 landed:** **`GET /game/world/ability-definitions`** — `AbilityDefinitionsWorldApi` + DTOs in `Game/Combat/` ([NEO-78 plan](../../plans/NEO-78-implementation-plan.md)); Bruno `bruno/neon-sprawl-server/ability-definitions/`. **NEO-80 landed:** **`ICombatEntityHealthStore`** / **`InMemoryCombatEntityHealthStore`** — lazy prototype dummy HP at **`PrototypeCombatConstants.MaxPrototypeTargetHp` (100)**; DI via **`AddCombatEntityHealthStore()`** ([NEO-80 plan](../../plans/NEO-80-implementation-plan.md)); [server README — Combat entity health (NEO-80)](../../../server/README.md#combat-entity-health-neo-80). **NEO-81 landed:** **`CombatOperations.TryResolve`** + **`CombatResult`** + **`CombatReasonCodes`** — defeated pre-check deny vocabulary (`target_defeated`, `unknown_ability`, `unknown_target`) ([NEO-81 plan](../../plans/NEO-81-implementation-plan.md)); [server README — Combat engine (NEO-81)](../../../server/README.md#combat-engine-neo-81). **NEO-82 landed:** **`AbilityCastApi`** invokes **`CombatOperations.TryResolve`** after E1.M4 gates; nested wire **`combatResolution`** on accept; per-ability catalog **`cooldownSeconds`** on successful resolve; **`target_defeated`** JSON cast deny without cooldown ([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/` defeat spine. **NEO-83 landed:** **`GET /game/world/combat-targets`** — `CombatTargetsWorldApi` + DTOs; authoritative HP snapshot from **`ICombatEntityHealthStore`** for client HUD ([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/`. **NEO-44 landed:** **`CombatDefeatGigXpGrant`** on cast **`targetDefeated`** — **25** gig XP to **`breach`** via **`IPlayerGigProgressionStore`** (V007); **`GET …/gig-progression`** ([NEO-44 plan](../../plans/NEO-44-implementation-plan.md)); [server README — Gig progression (NEO-44)](../../../server/README.md#gig-progression-snapshot-neo-44). **NEO-84 landed:** comment-only **`ability_used`** / **`enemy_defeat`** telemetry hook sites in **`CombatOperations.TryResolve`**; 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). **NEO-85 landed:** client combat HUD — **`combat_targets_client.gd`**, **`CombatTargetHpLabel`**, **`CastFeedbackLabel`** resolution copy; event-driven **`GET /game/world/combat-targets`** refresh ([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). **NEO-86 landed:** 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 complete — register row Ready.** + +**E5.M2 note:** Epic 5 **Slice 2** backlog in Linear ([Epic 5 — PvE Combat and Encounter Content](https://linear.app/neon-sprawl/project/epic-5-pve-combat-and-encounter-content-cf3c94c3-5346-40e0-8aaf-281e846f2846)): [NEO-87](https://linear.app/neon-sprawl/issue/NEO-87) → [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98); label **`E5.M2`**. Client capstone **E5M2-12** [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98). See [E5M2-prototype-backlog.md](../../plans/E5M2-prototype-backlog.md), [E5_M2_NpcAiAndBehaviorProfiles.md](E5_M2_NpcAiAndBehaviorProfiles.md). Upstream **E5.M1 Ready**. Replaces **`prototype_target_alpha` / `beta`** with three NPC archetype instances; owns **`ThreatState`**, **`TelegraphEvent`**, session **`IPlayerCombatHealthStore`**. **Backlog decomposed** — implementation not started. ### Epic 6 — PvP Security diff --git a/docs/plans/E5M1-prototype-backlog.md b/docs/plans/E5M1-prototype-backlog.md index 09e0c0d..08dd787 100644 --- a/docs/plans/E5M1-prototype-backlog.md +++ b/docs/plans/E5M1-prototype-backlog.md @@ -378,7 +378,7 @@ Working backlog for **Epic 5 — Slice 1** ([combat rules MVP](../decomposition/ ## After this backlog -- **[E5.M2](../decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md)** Slice 2 — decompose when E5.M1 lands (NPC archetypes + telegraphs + client telegraph UX). +- **[E5.M2](../decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md)** Slice 2 — decomposed in [E5M2-prototype-backlog.md](E5M2-prototype-backlog.md) (**NEO-87** → **NEO-98**). - **[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). diff --git a/docs/plans/E5M2-prototype-backlog.md b/docs/plans/E5M2-prototype-backlog.md new file mode 100644 index 0000000..aaaa8a6 --- /dev/null +++ b/docs/plans/E5M2-prototype-backlog.md @@ -0,0 +1,385 @@ +# E5.M2 — Prototype story backlog (NpcAiAndBehaviorProfiles) + +Working backlog for **Epic 5 — Slice 2** ([NPC archetypes and telegraphs](../decomposition/epics/epic_05_pve_combat.md#slice-2---npc-archetypes-and-telegraphs)). Decomposition and contracts: [E5.M2 — NpcAiAndBehaviorProfiles](../decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md). + +**Full-stack policy:** Epics deliver **playable game features**. Every **player-visible** story has a **`client`** Linear issue created in **this same decomposition pass** — not an undocumented follow-up. Reference: [E5M1 (paired server+client)](../plans/E5M1-prototype-backlog.md). + +**Labels (Linear):** every issue **`E5.M2`**; add **`server`** or **`client`** + **`Story`** as listed. + +**Precursor (do not re-scope):** [E5.M1](../decomposition/modules/E5_M1_CombatRulesEngine.md) combat spine — ability catalog ([NEO-76](https://linear.app/neon-sprawl/issue/NEO-76)–[NEO-79](https://linear.app/neon-sprawl/issue/NEO-79)), cast + **`CombatResolution`** ([NEO-82](https://linear.app/neon-sprawl/issue/NEO-82)), combat-targets GET ([NEO-83](https://linear.app/neon-sprawl/issue/NEO-83)), client HUD + capstone ([NEO-85](https://linear.app/neon-sprawl/issue/NEO-85), [NEO-86](https://linear.app/neon-sprawl/issue/NEO-86)). **`ThreatState`** / aggro was **stub-only** in Slice 1 — this module owns it. + +**Upstream (must be landed):** [E5.M1](../decomposition/modules/E5_M1_CombatRulesEngine.md) **Ready**; [E1.M3](../decomposition/modules/E1_M3_InteractionAndTargetingLayer.md) targeting + tab cycle ([NEO-23](https://linear.app/neon-sprawl/issue/NEO-23)–[NEO-26](https://linear.app/neon-sprawl/issue/NEO-26)). + +**Prototype NPC spine (frozen in E5M2-01):** **three** `NpcBehaviorDef` rows — ids **`prototype_melee_pressure`**, **`prototype_ranged_control`**, **`prototype_elite_mini_boss`** — one per vision archetype (melee pressure, ranged control, elite mini-boss). **Three** combat NPC **instance** ids replace E1.M3/E5.M1 dummies: **`prototype_npc_melee`**, **`prototype_npc_ranged`**, **`prototype_npc_elite`** (each binds a behavior def + world anchor). + +**Linear issues (created):** attach `docs/plans/NEO-*-implementation-plan.md` on the story branch when work starts. + +| Slug | Layer | Linear | +|------|-------|--------| +| E5M2-01 | server | [NEO-87](https://linear.app/neon-sprawl/issue/NEO-87) | +| E5M2-02 | server | [NEO-88](https://linear.app/neon-sprawl/issue/NEO-88) | +| E5M2-03 | server | [NEO-89](https://linear.app/neon-sprawl/issue/NEO-89) | +| E5M2-04 | server | [NEO-90](https://linear.app/neon-sprawl/issue/NEO-90) | +| E5M2-05 | server | [NEO-91](https://linear.app/neon-sprawl/issue/NEO-91) | +| E5M2-06 | server | [NEO-92](https://linear.app/neon-sprawl/issue/NEO-92) | +| E5M2-07 | server | [NEO-93](https://linear.app/neon-sprawl/issue/NEO-93) | +| E5M2-08 | server | [NEO-94](https://linear.app/neon-sprawl/issue/NEO-94) | +| E5M2-09 | server | [NEO-95](https://linear.app/neon-sprawl/issue/NEO-95) | +| E5M2-10 | server | [NEO-96](https://linear.app/neon-sprawl/issue/NEO-96) | +| E5M2-11 | client | [NEO-97](https://linear.app/neon-sprawl/issue/NEO-97) | +| E5M2-12 | client | [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98) | + +**Dependency graph in Linear:** E5M2-02 blocked by E5M2-01. E5M2-03 blocked by E5M2-02. E5M2-04 blocked by E5M2-03. E5M2-05 blocked by E5M2-03. E5M2-06 blocked by E5M2-05. E5M2-07 blocked by E5M2-06 and E5M2-03. E5M2-08 blocked by E5M2-07. E5M2-09 blocked by E5M2-08. E5M2-10 blocked by E5M2-07 (may parallel E5M2-09). E5M2-11 blocked by E5M2-08 and E5M2-09. E5M2-12 blocked by E5M2-11. + +**Board order:** estimates **1–12** matching slug order (E5M2-01 = 1 … E5M2-12 = 12). On the Epic 5 project board, sort by **Estimate** (ascending). + +--- + +## Story order (recommended) + +| Order | Slug | Layer | Depends on | +|-------|------|-------|------------| +| 1 | **E5M2-01** | server | E5.M1 Ready | +| 2 | **E5M2-02** | server | E5M2-01 | +| 3 | **E5M2-03** | server | E5M2-02 | +| 4 | **E5M2-04** | server | E5M2-03 | +| 5 | **E5M2-05** | server | E5M2-03 | +| 6 | **E5M2-06** | server | E5M2-05 | +| 7 | **E5M2-07** | server | E5M2-06, E5M2-03 | +| 8 | **E5M2-08** | server | E5M2-07 | +| 9 | **E5M2-09** | server | E5M2-08 | +| 10 | **E5M2-10** | server | E5M2-07 | +| 11 | **E5M2-11** | client | E5M2-08, E5M2-09 | +| 12 | **E5M2-12** | client | E5M2-11 | + +**Downstream (separate modules):** [E5.M3](../decomposition/modules/E5_M3_EncounterAndRewardTables.md) encounters + loot; [E4.M2](../decomposition/modules/E4_M2_SpawnEcologyController.md) spawn ecology; [E1.M4](../decomposition/modules/E1_M4_AbilityInputScaffold.md) may consume telegraph deny hints later. + +--- + +## Kickoff decisions (decomposition defaults) + +| Topic | Decision | Rationale | +|-------|----------|-----------| +| Archetype count | **3** frozen behavior defs | Vision prototype minimum; epic Slice 2 | +| Dummy migration | **Replace** `prototype_target_alpha` / `beta` with **3 NPC instance ids** | Vision asks for 3 enemy archetypes, not passive dummies + NPCs | +| NPC instance ids | **`prototype_npc_melee`**, **`prototype_npc_ranged`**, **`prototype_npc_elite`** | Stable prefix; each references a `behaviorDefId` | +| Aggro trigger | **First damaging cast** on NPC + **proximity re-aggro** inside `aggroRadius` | Deterministic; no random target swaps | +| Leash | **Return idle** when holder player leaves `leashRadius` | Simple anti-kite prototype rule | +| ThreatState | **In-memory** per-NPC aggro holder (`playerId` or empty) | E5.M1 stub → real store here | +| Player HP | **Session in-memory** **`IPlayerCombatHealthStore`** (100 default); **no Postgres** | NPC pressure readable in Godot without E5.M1 persistence scope | +| NPC tick | **Lazy advance** on **`GET …/npc-runtime-snapshot`** poll (client ~1 Hz in combat pocket) | Mirrors cooldown snapshot poll ([NEO-32](https://linear.app/neon-sprawl/issue/NEO-32)) | +| Telegraph wire | **`GET /game/world/npc-runtime-snapshot`** v1 — states + active **`TelegraphEvent`** rows | Single poll surface; no client-side windup math | +| NPC attacks | **Deterministic** damage from `NpcBehaviorDef.attackDamage` after telegraph resolves | Matches E5.M1 no-RNG combat | +| PvP | NPCs **never** acquire player targets blocked by E6 stub | [pvp_combat_integration.md](../decomposition/modules/pvp_combat_integration.md) | +| World placement | Static **`PrototypeNpcRegistry`** (successor to target registry anchors) until [E4.M2](../decomposition/modules/E4_M2_SpawnEcologyController.md) | Same static-registry pattern as E5.M1 dummies | +| HUD fidelity | Prototype Labels / debug panels (not final art) | NEO-85 / NEO-97 precedent | + +--- + +### E5M2-01 — Prototype NpcBehaviorDef catalog + schemas + CI + +**Goal:** Lock content shape and CI validation for **three** frozen NPC behavior archetypes before server load. + +**In scope** + +- `content/schemas/npc-behavior-def.schema.json`. +- `content/npc-behaviors/prototype_npc_behaviors.json` with stable **`id`** values: **`prototype_melee_pressure`**, **`prototype_ranged_control`**, **`prototype_elite_mini_boss`**. +- Fields: **`displayName`**, **`archetypeKind`** enum (`melee_pressure` / `ranged_control` / `elite_mini_boss`), **`maxHp`**, **`aggroRadius`**, **`leashRadius`**, **`telegraphWindupSeconds`**, **`attackDamage`**, **`attackCooldownSeconds`**. +- `scripts/validate_content.py`: schema validation, duplicate ids, exact three-id allowlist, positive numeric guards. +- Designer note in [E5_M2](../decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md) + `content/README.md`. + +**Out of scope** + +- Server loader, runtime engine, HTTP, Godot. + +**Acceptance criteria** + +- [ ] PR gate validates NPC behavior JSON against schema. +- [ ] Exactly three prototype behavior ids; duplicate `id` fails CI. +- [ ] Stable id list documented in module doc freeze box. + +--- + +### E5M2-02 — Server NPC behavior catalog load (fail-fast) + +**Goal:** Disk → host: startup load of `content/npc-behaviors/*.json` with CI-parity validation. + +**In scope** + +- Loader + catalog types under `server/NeonSprawl.Server/Game/Npc/` (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 (E5M2-03), HTTP projection, runtime tick. + +**Acceptance criteria** + +- [ ] Host fails startup on invalid NPC behavior JSON (mirror CI rules). +- [ ] Tests cover at least one happy path and one malformed catalog rejection. + +--- + +### E5M2-03 — Injectable `INpcBehaviorDefinitionRegistry` + DI + +**Goal:** Replace direct catalog access with injectable registry; wire into host DI. + +**In scope** + +- `INpcBehaviorDefinitionRegistry` + implementation backed by E5M2-02 catalog. +- `TryNormalizeKnown` / `GetDefinitionsInIdOrder` parity with ability registry patterns. +- DI extension method; unit tests (AAA). + +**Out of scope** + +- HTTP routes, NPC runtime, client. + +**Acceptance criteria** + +- [ ] Registry resolves all three frozen behavior ids. +- [ ] Unknown id normalization fails closed. + +--- + +### E5M2-04 — GET `/game/world/npc-behavior-definitions` + +**Goal:** Read-only world projection of behavior defs for client labels and Bruno smokes. + +**In scope** + +- `NpcBehaviorDefinitionsWorldApi` + versioned DTOs in `Game/Npc/`. +- Integration tests (AAA); Bruno folder `bruno/neon-sprawl-server/npc-behavior-definitions/`. +- `server/README.md` route section. + +**Out of scope** + +- Runtime state, telegraphs, Godot. + +**Acceptance criteria** + +- [ ] GET returns all three defs in ascending `id` order with `schemaVersion` **1**. +- [ ] Bruno happy GET passes in CI Bruno step. + +--- + +### E5M2-05 — Prototype NPC instance registry + combat-target migration + +**Goal:** Replace passive alpha/beta dummies with **three** NPC combat instances wired into targeting + HP stores. + +**In scope** + +- `PrototypeNpcRegistry` (or extend targeting registry with NPC metadata): instance id, `behaviorDefId`, world anchor, shared lock radius convention. +- Migrate **`PrototypeTargetRegistry`** / **`ICombatEntityHealthStore`** gates from alpha/beta → three NPC ids; max HP from behavior def. +- Update Bruno combat-targets / ability-cast / target-select smokes; server tests. +- Document breaking id change + client constant migration requirement in plan. + +**Out of scope** + +- Behavior tick, aggro, telegraphs (E5M2-06+). +- Godot markers — **client counterpart [NEO-97](#)** updates markers in parallel story after server ids land (blockedBy E5M2-08 for HUD; markers can land in E5M2-11). + +**Acceptance criteria** + +- [ ] Cast + combat-targets GET operate on three NPC ids only (alpha/beta removed). +- [ ] Each instance references a valid frozen `behaviorDefId`. +- [ ] Bruno defeat spine passes for at least one archetype. + +**Client counterpart:** [NEO-97](https://linear.app/neon-sprawl/issue/NEO-97) — archetype markers + telegraph HUD (after runtime snapshot lands). + +--- + +### E5M2-06 — AggroRule engine + `IThreatStateStore` + +**Goal:** Deterministic aggro: NPC acquires holder on first damaging player cast; clears on leash break. + +**In scope** + +- `IThreatStateStore` + in-memory implementation keyed by NPC instance id. +- `AggroOperations.TryAcquire` / `TryClearOnLeash` using player **`PositionState`** + registry anchors. +- Hook from **`CombatOperations.TryResolve`** on successful NPC damage (player → NPC). +- Unit + integration tests (AAA). + +**Out of scope** + +- NPC attack loop, telegraphs, client HUD. + +**Acceptance criteria** + +- [ ] First damaging cast on NPC sets aggro holder to casting player id. +- [ ] Holder clears when player leaves `leashRadius` (deterministic distance check). +- [ ] Re-aggro inside radius after clear follows same first-hit rule. + +--- + +### E5M2-07 — NPC behavior state machine + lazy tick advance + +**Goal:** Server-owned NPC states: **`idle`** → **`aggro`** → **`telegraph_windup`** → **`attack_execute`** → **`recover`** with catalog-driven timings. + +**In scope** + +- `NpcRuntimeOperations.AdvanceAll` (or per-id) called from snapshot GET handler with monotonic clock delta cap. +- State transitions emit internal events for telemetry hooks (E5M2-10). +- Unit tests with injected clock for deterministic transitions. + +**Out of scope** + +- HTTP DTO projection (E5M2-08), player damage (E5M2-09), Godot. + +**Acceptance criteria** + +- [ ] Aggro'd NPC enters telegraph after `attackCooldownSeconds` elapses from last attack. +- [ ] Windup duration matches `telegraphWindupSeconds` from behavior def. +- [ ] Idle NPC with no aggro holder does not telegraph. + +--- + +### E5M2-08 — TelegraphEvent snapshot GET + wire DTOs + +**Goal:** **`GET /game/world/npc-runtime-snapshot`** returns authoritative NPC runtime rows + active telegraphs for client poll. + +**In scope** + +- `NpcRuntimeSnapshotWorldApi` + DTOs: per-NPC **`state`**, **`aggroHolderPlayerId`**, active **`TelegraphEvent`** (`telegraphId`, `npcInstanceId`, `windupRemainingSeconds`, `archetypeKind`, …). +- Handler invokes E5M2-07 lazy tick before read. +- Integration tests (AAA); Bruno `bruno/neon-sprawl-server/npc-runtime-snapshot/`. +- `server/README.md` section. + +**Out of scope** + +- Godot poll client (E5M2-11). +- Player damage application (E5M2-09). + +**Acceptance criteria** + +- [ ] Snapshot reflects state transition within one poll generation after aggro. +- [ ] Active telegraph row appears during windup with decreasing remaining time. +- [ ] Bruno smoke: lock NPC → damage → poll until telegraph row present. + +**Client counterpart:** [NEO-97](https://linear.app/neon-sprawl/issue/NEO-97) — telegraph label + NPC state HUD driven by this GET. + +--- + +### E5M2-09 — NPC attack resolve + session player combat HP + +**Goal:** When telegraph completes, apply **`attackDamage`** to aggro holder via **`IPlayerCombatHealthStore`**; expose player HP on snapshot or dedicated GET. + +**In scope** + +- `IPlayerCombatHealthStore` + in-memory implementation (session-only, lazy init 100 HP). +- `NpcAttackOperations.TryResolveTelegraphComplete` invoked from tick advance. +- Extend snapshot DTO (or add **`GET /game/players/{id}/combat-health`**) with **`currentHp`** / **`maxHp`** for client HUD. +- Integration tests: telegraph complete → player HP decreases deterministically. +- Reserved **`player_death`** hook site when HP hits 0 (comment-only, no respawn loop). + +**Out of scope** + +- Postgres persistence, healing, player defensive abilities. +- Godot HUD — **client counterpart [NEO-97](#)**. + +**Acceptance criteria** + +- [ ] Elite archetype telegraph → attack deals catalog damage to holder. +- [ ] Player HP read model matches store after NPC attack. +- [ ] No client-side damage math required for verification (Bruno or integration tests). + +**Client counterpart:** [NEO-97](https://linear.app/neon-sprawl/issue/NEO-97) — player HP + incoming telegraph/attack feedback labels. + +--- + +### E5M2-10 — Slice 2 NPC telemetry hook sites + +**Goal:** Comment-only **`telegraph_fired`** and **`npc_state_transition`** hook sites in NPC runtime path. + +**In scope** + +- TODO(E9.M1) markers at telegraph start and on state transition commit. +- `server/README.md` pointer; module doc cross-link. + +**Out of scope** + +- Production ingest, Godot. + +**Acceptance criteria** + +- [ ] Hook names match [epic_05 Slice 2](../decomposition/epics/epic_05_pve_combat.md#slice-2---npc-archetypes-and-telegraphs) vocabulary. +- [ ] No runtime behavior change beyond comments. + +--- + +### E5M2-11 — Client telegraph HUD + NPC archetype markers + +**Goal:** Godot shows **server-owned** telegraph timing, NPC state, and archetype markers for all three instances. + +**In scope** + +- `client/scripts/npc_runtime_client.gd` — poll **`GET /game/world/npc-runtime-snapshot`** (~1 Hz when combat HUD visible). +- `client/scripts/npc_archetype_markers.gd` — replace alpha/beta dummy markers with three archetype capsules + labels. +- HUD labels: **`TelegraphLabel`**, **`NpcStateLabel`**, **`PlayerCombatHpLabel`** under `UICanvas`. +- Update `prototype_target_constants.gd` / tab cycle for three NPC ids (server parity). +- GdUnit tests with HTTP doubles ([testing-expectations.md](../../.cursor/rules/testing-expectations.md)). +- `docs/manual-qa/NEO-97.md` — **Godot** steps (server + client running). +- `client/README.md` NPC / telegraph section. + +**Out of scope** + +- Final VFX art; dodge mechanics (prototype shows telegraph only). + +**Acceptance criteria** + +- [ ] Player-visible telegraph countdown matches server snapshot within one poll frame. +- [ ] Tab cycle visits three NPC instances in documented order. +- [ ] Manual QA checklist exercisable without Bruno. + +**Client counterpart:** this issue (**NEO-97**). + +--- + +### E5M2-12 — Playable NPC telegraph combat capstone (Godot) + +**Goal:** Prove Epic 5 Slice 2 acceptance **in Godot**: aggro a melee NPC, observe telegraph, take damage, defeat all three archetypes — without Bruno. + +**In scope** + +- `docs/manual-qa/NEO-98.md` single-session script covering three archetypes. +- `client/README.md` end-to-end NPC combat loop section. +- Update [documentation_and_implementation_alignment.md](../decomposition/modules/documentation_and_implementation_alignment.md) E5.M2 row when complete. + +**Out of scope** + +- Encounters/loot ([E5.M3](../decomposition/modules/E5_M3_EncounterAndRewardTables.md)), spawn ecology ([E4.M2](../decomposition/modules/E4_M2_SpawnEcologyController.md)). + +**Acceptance criteria** + +- [ ] Human completes script with server + client; telegraph timing readable before damage. +- [ ] Epic 5 Slice 2 AC: telegraph timing matches UI feedback; aggro rules feel deterministic in manual QA. +- [ ] 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-98**). + +--- + +## After this backlog + +- **[E5.M3](../decomposition/modules/E5_M3_EncounterAndRewardTables.md)** Slice 3 — decompose when E5.M2 lands (encounters + loot routing). +- 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-97**, **NEO-98**) +- [x] No “optional follow-up” / “future client” without NEO-XX +- [x] Capstone client issue exists for playable Godot verification (**NEO-98**) +- [x] [documentation_and_implementation_alignment.md](../decomposition/modules/documentation_and_implementation_alignment.md) updated +- [x] [module_dependency_register.md](../decomposition/modules/module_dependency_register.md) E5.M2 note updated when issues are created + +## Related docs + +- [E5_M2_NpcAiAndBehaviorProfiles.md](../decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md) +- [E5_M1_CombatRulesEngine.md](../decomposition/modules/E5_M1_CombatRulesEngine.md) +- [epic_05_pve_combat.md](../decomposition/epics/epic_05_pve_combat.md) +- [abilities.md](../game-design/abilities.md) +- [pvp_combat_integration.md](../decomposition/modules/pvp_combat_integration.md) +- [client_server_authority.md](../decomposition/modules/client_server_authority.md)