Merge pull request #119 from ViPro-Technologies/NEO-84-combat-telemetry-hooks
NEO-84: Slice 1 combat telemetry hook sitespull/120/head
commit
b65bff2a36
|
|
@ -7,7 +7,7 @@
|
|||
| **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-10 [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44) gig XP on defeat landed; E5M1-09+ pending (see [dependency register](module_dependency_register.md)) |
|
||||
| **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 [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+ 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
|
||||
|
|
@ -60,6 +60,8 @@ See Epic 5 **Slice 1 — Combat rules MVP**: target lock, basic attacks, 4–6 a
|
|||
|
||||
**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/`.
|
||||
|
||||
## Linear backlog (decomposed)
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ Rows appear when work starts; default for unlisted modules is **Planned** / not
|
|||
| E3.M1 | Ready | **NEO-41 landed (prototype, superseded on interact by NEO-63):** inline XP on **`resource_node`** interact. **NEO-57–NEO-61** — content, registry, HTTP defs, depletion store (see prior alignment). **NEO-62 landed:** **`GatherOperations`** + **`GatherResult`**. **NEO-63 landed:** **`POST …/interact`** **`resource_node`** → gather engine; **four** **`PrototypeInteractableRegistry`** anchors; Bruno gather → inventory + depletion deny ([NEO-63](../../plans/NEO-63-implementation-plan.md), [`NEO-63` manual QA](../../manual-qa/NEO-63.md)); [server README — Gather via interact (NEO-63)](../../../server/README.md#gather-via-interact-neo-63). **NEO-64 landed:** comment-only **`resource_gathered`** / **`gather_node_depleted`** hook sites in **`GatherOperations.TryGather`** ([NEO-64](../../plans/NEO-64-implementation-plan.md), [`NEO-64` manual QA](../../manual-qa/NEO-64.md)); Epic 3 Slice 2 backlog **E3M1-01**–**E3M1-08** complete. **NEO-73 landed:** client gather feedback — **`skill_progression_client.gd`**, **`prototype_interactable_picker.gd`** (nearest in-range **R**), **`GatherFeedbackLabel`** + **`SkillProgressionLabel`**, auto inventory/skill refresh after successful gather ([NEO-73](../../plans/NEO-73-implementation-plan.md), [`NEO-73` manual QA](../../manual-qa/NEO-73.md)); `client/README.md` gather section. **NEO-75 landed:** capstone gather→refine→make loop in Godot — [`NEO-75` manual QA](../../manual-qa/NEO-75.md), `client/README.md` end-to-end section; Epic 3 Slice 5 client complete. | [NEO-41](../../plans/NEO-41-implementation-plan.md) … [NEO-64](../../plans/NEO-64-implementation-plan.md), [NEO-73](../../plans/NEO-73-implementation-plan.md), [NEO-75](../../plans/NEO-75-implementation-plan.md), [E3S5-client-prototype-backlog](../../plans/E3S5-client-prototype-backlog.md), [E3_M1](E3_M1_ResourceNodeAndGatherLoop.md); `server/NeonSprawl.Server/Game/Gathering/`, `Game/Interaction/`, `client/scripts/skill_progression_client.gd`, `client/scripts/prototype_interactable_picker.gd` |
|
||||
| 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 | In Progress | **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/`. **Backlog decomposed:** Epic 5 Slice 1 — [E5M1-prototype-backlog.md](../../plans/E5M1-prototype-backlog.md) **E5M1-09** [NEO-84](https://linear.app/neon-sprawl/issue/NEO-84) through **E5M1-12** [NEO-86](https://linear.app/neon-sprawl/issue/NEO-86). **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), [E1M4-prototype-backlog.md](../../plans/E1M4-prototype-backlog.md) |
|
||||
| E5.M1 | In Progress | **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). **Backlog decomposed:** Epic 5 Slice 1 — [E5M1-prototype-backlog.md](../../plans/E5M1-prototype-backlog.md) **E5M1-11** [NEO-85](https://linear.app/neon-sprawl/issue/NEO-85) through **E5M1-12** [NEO-86](https://linear.app/neon-sprawl/issue/NEO-86). **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), [E1M4-prototype-backlog.md](../../plans/E1M4-prototype-backlog.md) |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ Fleshed-out scope, contracts, and integration notes live in **per-module documen
|
|||
|
||||
**E2.M1 note:** Epic 2 Slice 1 backlog in Linear ([Epic 2 — Classless Skill and Progression Framework](https://linear.app/neon-sprawl/project/epic-2-classless-skill-and-progression-framework-5200f84bf8b6)): [NEO-33](https://linear.app/neon-sprawl/issue/NEO-33) → [NEO-34](https://linear.app/neon-sprawl/issue/NEO-34) → [NEO-35](https://linear.app/neon-sprawl/issue/NEO-35) → [NEO-36](https://linear.app/neon-sprawl/issue/NEO-36); label **`E2.M1`**. See [E2_M1_SkillDefinitionRegistry.md](E2_M1_SkillDefinitionRegistry.md). **NEO-33** (content + CI + docs) moves the register row to **In Progress**; **NEO-34+** cover server registry and HTTP. Update [documentation and implementation alignment](documentation_and_implementation_alignment.md) when slices land.
|
||||
|
||||
**E2.M2 note:** Epic 2 Slice 2 — [NEO-37](https://linear.app/neon-sprawl/issue/NEO-37) (progression snapshot **GET**) and [NEO-38](https://linear.app/neon-sprawl/issue/NEO-38) (grant **POST**, persistence, level-up payloads); label **`E2.M2`**. Module doc [E2_M2_XpAwardAndLevelEngine.md](E2_M2_XpAwardAndLevelEngine.md). Slice 3 integration: [NEO-41](https://linear.app/neon-sprawl/issue/NEO-41)–[NEO-43](https://linear.app/neon-sprawl/issue/NEO-43) landed; **NEO-44** (gig XP, Epic 5) remains backlog. Keep aligned with [documentation and implementation alignment](documentation_and_implementation_alignment.md).
|
||||
**E2.M2 note:** Epic 2 Slice 2 — [NEO-37](https://linear.app/neon-sprawl/issue/NEO-37) (progression snapshot **GET**) and [NEO-38](https://linear.app/neon-sprawl/issue/NEO-38) (grant **POST**, persistence, level-up payloads); label **`E2.M2`**. Module doc [E2_M2_XpAwardAndLevelEngine.md](E2_M2_XpAwardAndLevelEngine.md). Slice 3 integration: [NEO-41](https://linear.app/neon-sprawl/issue/NEO-41)–[NEO-43](https://linear.app/neon-sprawl/issue/NEO-43) landed. **NEO-44** (gig XP on combat defeat) landed under **E5.M1** — combat → gig XP only, not E2.M2 skill grants ([NEO-44 plan](../../plans/NEO-44-implementation-plan.md)). Keep aligned with [documentation and implementation alignment](documentation_and_implementation_alignment.md).
|
||||
|
||||
**E2.M3 note:** Epic 2 Slice 4 — [NEO-45](https://linear.app/neon-sprawl/issue/NEO-45) → [NEO-49](https://linear.app/neon-sprawl/issue/NEO-49); label **`E2.M3`**. **NEO-45 landed:** prototype **`salvage`** mastery catalog + schema + CI. **NEO-46 landed:** fail-fast server mastery catalog load. **NEO-47 landed:** `PerkUnlockEngine`, `IPlayerPerkStateStore` + **`V004`**, level-up hook in skill XP grants. **NEO-49 landed:** comment-only **`perk_unlock`** telemetry hook in `PerkUnlockEngine.TryUnlockPerks`. **NEO-48 landed:** **`GET`/`POST /game/players/{id}/perk-state`** + Bruno `perk-state/`. Flagship track; see [E2_M3_MasteryAndPerkUnlocks.md](E2_M3_MasteryAndPerkUnlocks.md), [E2M3-pre-production-backlog.md](../../plans/E2M3-pre-production-backlog.md), and [documentation_and_implementation_alignment.md](documentation_and_implementation_alignment.md) E2.M3 row.
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ Fleshed-out scope, contracts, and integration notes live in **per-module documen
|
|||
| 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/`.
|
||||
**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). Pending client: **E5M1-11** [NEO-85](https://linear.app/neon-sprawl/issue/NEO-85) → **E5M1-12** [NEO-86](https://linear.app/neon-sprawl/issue/NEO-86).
|
||||
|
||||
### Epic 6 — PvP Security
|
||||
|
||||
|
|
|
|||
|
|
@ -287,8 +287,10 @@ Working backlog for **Epic 5 — Slice 1** ([combat rules MVP](../decomposition/
|
|||
|
||||
**Acceptance criteria**
|
||||
|
||||
- [ ] Hook names match [epic_05 Slice 1](../decomposition/epics/epic_05_pve_combat.md#slice-1---combat-rules-mvp) vocabulary.
|
||||
- [ ] **`enemy_defeat`** site fires only on transition to defeated (not every overkill cast).
|
||||
- [x] Hook names match [epic_05 Slice 1](../decomposition/epics/epic_05_pve_combat.md#slice-1---combat-rules-mvp) vocabulary.
|
||||
- [x] **`enemy_defeat`** site fires only on transition to defeated (not every overkill cast).
|
||||
|
||||
**Landed ([NEO-84](https://linear.app/neon-sprawl/issue/NEO-84)):** comment-only **`ability_used`** / **`enemy_defeat`** hook sites in **`CombatOperations.TryResolve`**; reserved **`encounter_start`** / **`player_death`** ([NEO-84-implementation-plan.md](NEO-84-implementation-plan.md)); [server README — Combat telemetry hooks](../../../server/README.md#combat-telemetry-hooks-neo-84).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
# NEO-84 — Implementation plan
|
||||
|
||||
## Story reference
|
||||
|
||||
| Field | Value |
|
||||
|--------|--------|
|
||||
| **Key** | NEO-84 |
|
||||
| **Title** | E5M1-09: Slice 1 combat telemetry hook sites |
|
||||
| **Linear** | https://linear.app/neon-sprawl/issue/NEO-84/e5m1-09-slice-1-combat-telemetry-hook-sites |
|
||||
| **Module** | [E5.M1 — CombatRulesEngine](../decomposition/modules/E5_M1_CombatRulesEngine.md) · Epic 5 Slice 1 · backlog **E5M1-09** |
|
||||
| **Branch** | `NEO-84-combat-telemetry-hooks` |
|
||||
| **Blocked by** | [NEO-82](https://linear.app/neon-sprawl/issue/NEO-82) — cast POST + `combatResolution` (**Done** on `main`); [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44) gig XP on defeat (**Done** on `main`) |
|
||||
| **Pattern** | [NEO-71](https://linear.app/neon-sprawl/issue/NEO-71) — engine-only comment hooks in `*Operations`; [NEO-30](https://linear.app/neon-sprawl/issue/NEO-30) — separate E1.M4 funnel names in `AbilityCastApi` (keep, do not duplicate E5 names there) |
|
||||
| **Client counterpart** | None — comment-only server story; combat HUD is [NEO-85](https://linear.app/neon-sprawl/issue/NEO-85) |
|
||||
|
||||
## Kickoff clarifications
|
||||
|
||||
| Topic | Question | Agent recommendation | Answer |
|
||||
|--------|----------|----------------------|--------|
|
||||
| **Manual QA doc** | Add `docs/manual-qa/NEO-84.md`? | **Skip** — server-only; comments-only; no API/response change (NEO-71 / NEO-83 pattern). | **User:** skip. |
|
||||
| **Hook anchor** | Engine only vs engine + `AbilityCastApi`? | **`CombatOperations.TryResolve` only** — HTTP delegates to engine; NEO-30 stays for `ability_cast_*` funnel events (NEO-71/64/56 precedent). | **User:** engine only. |
|
||||
| **`ability_used` scope** | Zero-damage successful resolves (`prototype_guard`, `prototype_dash`)? | **Yes** — every successful `TryResolve` return (ability still consumed in combat path). | **Adopted** (backlog “cast accept path” + readable prototype semantics). |
|
||||
| **Runtime behavior** | Comments-only vs dev `ILogger`? | **Comments-only** + `TODO(E9.M1)` (NEO-40/71 precedent). | **Adopted** (repo precedent). |
|
||||
| **Reserved sites** | Where for `encounter_start` / `player_death`? | **Class-level** reserved hook block on `CombatOperations` — encounters (E5.M3) and player HP deferred per [E5M1 kickoff defaults](E5M1-prototype-backlog.md#kickoff-decisions-decomposition-defaults). | **Adopted** (backlog default). |
|
||||
|
||||
## Goal, scope, and out-of-scope
|
||||
|
||||
**Goal:** Document and place **comment-only hook sites** on the server-authoritative combat resolve path for Epic 5 Slice 1 product telemetry names **`ability_used`** and **`enemy_defeat`**, plus **reserved** sites for **`encounter_start`** and **`player_death`**. No E9.M1 ingest.
|
||||
|
||||
**In scope (from Linear + [E5M1-09](E5M1-prototype-backlog.md#e5m1-09--slice-1-combat-telemetry-hook-sites)):**
|
||||
|
||||
- Hook placement in **`CombatOperations.TryResolve`** for **`ability_used`** (every successful resolve) and **`enemy_defeat`** (defeat transition only).
|
||||
- Reserved **`encounter_start`** / **`player_death`** comment sites (no executable path in Slice 1).
|
||||
- **`TODO(E9.M1)`** markers; **`server/README.md`** combat telemetry subsection; [E5_M1 module](../decomposition/modules/E5_M1_CombatRulesEngine.md) landed line.
|
||||
- E5M1 backlog + alignment register updates when implementation completes.
|
||||
|
||||
**Out of scope (from Linear + backlog):**
|
||||
|
||||
- Production logging, dashboards, metrics (**E9.M1**).
|
||||
- `ILogger` / dev-only log lines.
|
||||
- Duplicate hook comments in **`AbilityCastApi`** (NEO-30 funnel hooks remain; E5 combat vocabulary lives in engine only).
|
||||
- Godot client — **[NEO-85](https://linear.app/neon-sprawl/issue/NEO-85)**.
|
||||
- **`docs/manual-qa/NEO-84.md`** (kickoff decision).
|
||||
|
||||
## Acceptance criteria checklist
|
||||
|
||||
- [x] Hook names match [epic_05 Slice 1](../decomposition/epics/epic_05_pve_combat.md#slice-1---combat-rules-mvp) vocabulary (`ability_used`, `enemy_defeat`, reserved `encounter_start`, `player_death`).
|
||||
- [x] **`enemy_defeat`** site documents emit only on transition to defeated (not re-hit / overkill — enforced by `target_defeated` deny before damage).
|
||||
|
||||
## Technical approach
|
||||
|
||||
1. **Authoritative surface:** **`CombatOperations.TryResolve`** — all combat damage and defeat transitions funnel here; **`AbilityCastApi`** invokes after E1.M4 gates and commits cooldown / gig XP without duplicating E5 hook comments.
|
||||
|
||||
2. **Class summary + reserved sites (NEO-84):** Extend **`CombatOperations`** XML summary with NEO-84 telemetry anchor. Add a **reserved hook sites** comment block (no `TODO` emit) documenting:
|
||||
- **`encounter_start`** — future E5.M3 encounter spawn / engage path (not wired in Slice 1).
|
||||
- **`player_death`** — future player HP / death resolution (out of scope Slice 1 per backlog).
|
||||
|
||||
3. **Hook site A — `ability_used`:** On **every successful** return path (zero-damage branch and post-`TryApplyDamage` success):
|
||||
- Comment block names future E9.M1 event **`ability_used`**.
|
||||
- **`TODO(E9.M1): catalog emit`** (NEO-71 style).
|
||||
- Planned payload fields: `abilityId`, `targetId`, `damageDealt`, `targetRemainingHp`, `targetDefeated`; note **`playerId`** is not in engine scope — E9.M1 ingest at **`AbilityCastApi`** accept layer should correlate route `id` with this event (document in README, do not add API duplicate hook).
|
||||
|
||||
4. **Hook site B — `enemy_defeat`:** Immediately after **`TryApplyDamage`** when **`after.Defeated`** is **`true`** (lethal hit transition only):
|
||||
- Comment block names **`enemy_defeat`**.
|
||||
- **`TODO(E9.M1): catalog emit`** — once per target defeat transition.
|
||||
- Planned payload fields: `targetId`, `abilityId` (killing blow), `damageDealt`, final HP **0**; correlate `playerId` at cast layer as above.
|
||||
- **AC guarantee:** Re-hit on defeated targets returns **`target_defeated`** deny **before** damage — hook never reached on overkill.
|
||||
|
||||
5. **NEO-30 coexistence:** Keep existing **`ability_cast_requested`** / **`ability_cast_denied`** comments in **`AbilityCastApi`** (E1.M4 funnel). **`ability_used`** is the Epic 5 combat-outcome catalog name at resolve success — document both in README under separate subsections.
|
||||
|
||||
6. **NEO-44 / gig XP:** No new hooks in **`CombatDefeatGigXpGrant`** — defeat side effects stay separate from telemetry vocabulary; **`enemy_defeat`** anchor remains on the HP transition in **`TryResolve`**.
|
||||
|
||||
7. **Docs (minimal):**
|
||||
- **`server/README.md`:** new **NEO-84 combat telemetry hooks** subsection under combat engine (hook table + “no production ingest”).
|
||||
- **`CombatResult.cs`:** one-line summary cross-reference to NEO-84 hook anchor in **`CombatOperations`** (optional wording tweak).
|
||||
- **`E5_M1_CombatRulesEngine.md`:** E5M1-09 landed line with README link.
|
||||
- **`documentation_and_implementation_alignment.md`** + **`E5M1-prototype-backlog.md`:** E5M1-09 checkboxes / landed note when complete.
|
||||
|
||||
## Files to add
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `docs/plans/NEO-84-implementation-plan.md` | This plan. |
|
||||
|
||||
## Files to modify
|
||||
|
||||
| Path | Rationale |
|
||||
|------|-----------|
|
||||
| `server/NeonSprawl.Server/Game/Combat/CombatOperations.cs` | **NEO-84:** reserved-site class comment; **`ability_used`** on both success paths; **`enemy_defeat`** on `after.Defeated` transition only. |
|
||||
| `server/NeonSprawl.Server/Game/Combat/CombatResult.cs` | Cross-reference NEO-84 hook anchor in type summary (wording tweak if needed). |
|
||||
| `server/README.md` | Document combat telemetry hook placement (NEO-84); distinguish from NEO-30 cast funnel hooks. |
|
||||
| `docs/decomposition/modules/E5_M1_CombatRulesEngine.md` | E5M1-09 telemetry hooks landed line + README link. |
|
||||
| `docs/plans/E5M1-prototype-backlog.md` | E5M1-09 acceptance checkboxes + landed note when complete. |
|
||||
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | E5.M1 row — E5M1-09 complete when landed. |
|
||||
|
||||
## Tests
|
||||
|
||||
| Path | Change |
|
||||
|------|--------|
|
||||
| **None required** | Comments-only markers; existing **`CombatOperationsTests`** and **`AbilityCastApiTests`** cover resolve/defeat behavior unchanged. |
|
||||
|
||||
**Manual verification:** Code review that comment blocks cite **`ability_used`** / **`enemy_defeat`** / reserved names, include **`TODO(E9.M1)`**, and that **`enemy_defeat`** sits only on the post-damage defeat branch. Optional Bruno cast + combat-targets regression (no request/response change).
|
||||
|
||||
## Open questions / risks
|
||||
|
||||
**None.** Precursors NEO-82 and NEO-44 are complete; production logging risk avoided by **comments-only** kickoff decisions.
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
# Code review — NEO-84 combat telemetry hook sites
|
||||
|
||||
**Date:** 2026-05-25
|
||||
**Scope:** Branch `NEO-84-combat-telemetry-hooks` · commits `fb2bfe5`–`667a1a9` vs `origin/main`
|
||||
**Base:** `origin/main`
|
||||
|
||||
## Verdict
|
||||
|
||||
**Approve with nits**
|
||||
|
||||
## Summary
|
||||
|
||||
NEO-84 delivers **E5M1-09**: comment-only telemetry hook sites in **`CombatOperations.TryResolve`** for Epic 5 Slice 1 catalog names **`ability_used`** (every successful resolve, including zero-damage abilities) and **`enemy_defeat`** (lethal defeat transition only), plus class-level reserved comments for **`encounter_start`** and **`player_death`**. All active sites carry **`TODO(E9.M1): catalog emit`** with planned payload fields; no runtime behavior, API shape, or test changes. **`server/README.md`**, **`CombatResult.cs`**, **`E5_M1_CombatRulesEngine.md`**, **`E5M1-prototype-backlog.md`**, and **`documentation_and_implementation_alignment.md`** were updated in the implementation commit. Implementation matches the kickoff plan and acceptance checklist. Residual risk is negligible — comments-only server slice; client combat HUD remains **NEO-85** out of scope.
|
||||
|
||||
## Documentation checked
|
||||
|
||||
| Document | Result |
|
||||
|----------|--------|
|
||||
| [`docs/plans/NEO-84-implementation-plan.md`](../plans/NEO-84-implementation-plan.md) | **Matches** — kickoff decisions (engine-only hooks, zero-damage `ability_used`, comments-only, skip manual QA), scope, hook placement table, and acceptance checklist align with code. |
|
||||
| [`docs/plans/E5M1-prototype-backlog.md`](../plans/E5M1-prototype-backlog.md) · **E5M1-09** | **Matches** — AC checked; landed note cites **`TryResolve`** and README anchor. |
|
||||
| [`docs/decomposition/modules/E5_M1_CombatRulesEngine.md`](../decomposition/modules/E5_M1_CombatRulesEngine.md) | **Matches** — Status and **Related implementation slices** entry for E5M1-09 NEO-84. |
|
||||
| [`docs/decomposition/modules/documentation_and_implementation_alignment.md`](../decomposition/modules/documentation_and_implementation_alignment.md) | **Matches** — E5.M1 row cites NEO-84 landed + README link. |
|
||||
| [`docs/decomposition/modules/module_dependency_register.md`](../decomposition/modules/module_dependency_register.md) | **Matches** — E5.M1 note includes NEO-44 and NEO-84 landed (addressed post-review). |
|
||||
| [`docs/decomposition/epics/epic_05_pve_combat.md`](../decomposition/epics/epic_05_pve_combat.md) · Slice 1 telemetry vocabulary | **Matches** — hook names align with epic list. |
|
||||
| [`docs/decomposition/modules/client_server_authority.md`](../decomposition/modules/client_server_authority.md) | **Matches** — server-authoritative resolve path; no client telemetry. |
|
||||
| [`server/README.md`](../../server/README.md) | **Matches** — combat telemetry hooks subsection; distinguishes NEO-30 cast funnel from E5 combat-outcome names. |
|
||||
| Full-stack epic decomposition | **Matches** — plan documents no client counterpart; NEO-85 owns HUD. |
|
||||
| Manual QA | **N/A** — kickoff skip (NEO-71/NEO-83 pattern). |
|
||||
|
||||
## Blocking issues
|
||||
|
||||
None.
|
||||
|
||||
## Suggestions
|
||||
|
||||
1. ~~**Stale `module_dependency_register` E5.M1 note** — The long **E5.M1 note** paragraph still ends at **NEO-83 landed** and omits **NEO-44** (gig XP) and **NEO-84** (telemetry hooks) even though **`E5_M1_CombatRulesEngine.md`** and the alignment register were updated. Append landed one-liners (mirror alignment row) so dependency-register readers stay consistent.~~ **Done.** E5.M1 note appended NEO-44 + NEO-84 landed; E2.M2 note corrected NEO-44 status.
|
||||
|
||||
2. ~~**Damage-path `ability_used` payload comment parity** — The zero-damage branch documents that **`playerId`** is out of engine scope and should be correlated at **`AbilityCastApi`** accept; the post-damage **`ability_used`** block lists payload fields but omits that note. Copy the one-line **`playerId`** correlation hint for E9.M1 wiring consistency.~~ **Done.** Post-damage `ability_used` block now includes the `playerId` correlation line.
|
||||
|
||||
## Nits
|
||||
|
||||
- Nit: **`enemy_defeat`** uses an empty `if (after.Defeated) { …comments… }` block — intentional hook anchoring (same pattern as **`gather_node_depleted`** in **`GatherOperations`**); fine as-is.
|
||||
|
||||
- Nit: Class-level reserved-site comments sit immediately above the **`TryResolve`** XML summary rather than in a dedicated region after all methods — readable and matches “class-level reserved block” kickoff decision; optional reorder only if a style guide emerges.
|
||||
|
||||
- ~~Nit: **`E2.M2 note`** in **`module_dependency_register`** still says **NEO-44 remains backlog** — pre-existing staleness unrelated to this diff; fix when touching register for suggestion 1.~~ **Done.** Addressed with suggestion 1 register update.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cd /home/don/neon-sprawl/server
|
||||
dotnet test NeonSprawl.Server.Tests/NeonSprawl.Server.Tests.csproj \
|
||||
--filter "FullyQualifiedName~CombatOperations"
|
||||
dotnet test NeonSprawl.Server.Tests/NeonSprawl.Server.Tests.csproj \
|
||||
--filter "FullyQualifiedName~AbilityCastApiTests"
|
||||
```
|
||||
|
||||
**CombatOperationsTests:** 10/10 passed (local run).
|
||||
**AbilityCastApiTests:** 21/21 passed (local run).
|
||||
|
||||
**Manual (optional):** Confirm hook comment blocks in `server/NeonSprawl.Server/Game/Combat/CombatOperations.cs` cite **`ability_used`** / **`enemy_defeat`** / reserved names, include **`TODO(E9.M1)`**, and that **`enemy_defeat`** sits only after **`TryApplyDamage`** when **`after.Defeated`**. Optional Bruno `ability-cast/` + `combat-targets/` regression — no request/response change expected.
|
||||
|
|
@ -3,9 +3,13 @@ namespace NeonSprawl.Server.Game.Combat;
|
|||
/// <summary>
|
||||
/// Orchestrates deterministic combat resolution: ability catalog damage + prototype target HP (NEO-81).
|
||||
/// Cast HTTP wiring: <see cref="AbilityInput.AbilityCastApi"/> (NEO-82).
|
||||
/// NEO-84 telemetry hook sites: <see cref="TryResolve"/> (<c>ability_used</c>, <c>enemy_defeat</c>).
|
||||
/// </summary>
|
||||
public static class CombatOperations
|
||||
{
|
||||
// --- Reserved telemetry hook sites (NEO-84): no emit until future modules ---
|
||||
// encounter_start — future E5.M3 encounter spawn/engage path (not wired in Slice 1).
|
||||
// player_death — future player HP / death resolution (player HP out of scope in Slice 1).
|
||||
/// <summary>
|
||||
/// Resolves one ability against one prototype combat target using catalog damage and the health store.
|
||||
/// Defeated targets deny before damage application; zero-damage abilities succeed without mutating HP.
|
||||
|
|
@ -34,6 +38,10 @@ public static class CombatOperations
|
|||
|
||||
if (definition.BaseDamage == 0)
|
||||
{
|
||||
// --- Telemetry hook site (NEO-84): future E9.M1 catalog event `ability_used` ---
|
||||
// TODO(E9.M1): catalog emit — on every successful resolve (including zero-damage abilities).
|
||||
// Planned payload fields: abilityId, targetId, damageDealt (0), targetRemainingHp, targetDefeated.
|
||||
// playerId is not in engine scope; correlate route id at AbilityCastApi accept (NEO-30 funnel).
|
||||
return new CombatResult(
|
||||
Success: true,
|
||||
ReasonCode: null,
|
||||
|
|
@ -47,6 +55,17 @@ public static class CombatOperations
|
|||
return Deny(CombatReasonCodes.UnknownTarget);
|
||||
}
|
||||
|
||||
if (after.Defeated)
|
||||
{
|
||||
// --- Telemetry hook site (NEO-84): future E9.M1 catalog event `enemy_defeat` ---
|
||||
// TODO(E9.M1): catalog emit — once per target defeat transition (lethal hit only; re-hit denied earlier).
|
||||
// Planned payload fields: targetId, abilityId (killing blow), damageDealt, final HP 0; correlate playerId at cast layer.
|
||||
}
|
||||
|
||||
// --- Telemetry hook site (NEO-84): future E9.M1 catalog event `ability_used` ---
|
||||
// TODO(E9.M1): catalog emit — on every successful resolve after damage application.
|
||||
// Planned payload fields: abilityId, targetId, damageDealt, targetRemainingHp, targetDefeated.
|
||||
// playerId is not in engine scope; correlate route id at AbilityCastApi accept (NEO-30 funnel).
|
||||
return new CombatResult(
|
||||
Success: true,
|
||||
ReasonCode: null,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ namespace NeonSprawl.Server.Game.Combat;
|
|||
|
||||
/// <summary>
|
||||
/// Server-internal combat resolution envelope (NEO-81); promoted to wire JSON via cast response extension (NEO-82).
|
||||
/// NEO-84 telemetry hook sites live in <see cref="CombatOperations.TryResolve"/>.
|
||||
/// </summary>
|
||||
/// <param name="DamageDealt">Catalog damage applied on success; zero on deny.</param>
|
||||
/// <param name="TargetRemainingHp">Authoritative HP after success; optional on deny (e.g. zero on <c>target_defeated</c>).</param>
|
||||
|
|
|
|||
|
|
@ -145,6 +145,16 @@ curl -sS -i "http://localhost:5253/game/world/combat-targets"
|
|||
|
||||
Plan: [NEO-81 implementation plan](../../docs/plans/NEO-81-implementation-plan.md); cast wiring: [NEO-82](https://linear.app/neon-sprawl/issue/NEO-82).
|
||||
|
||||
### Combat telemetry hooks (NEO-84)
|
||||
|
||||
Comment-only hook sites in **`CombatOperations.TryResolve`** for future E9.M1 catalog events ([Epic 5 Slice 1](../../docs/decomposition/epics/epic_05_pve_combat.md#slice-1---combat-rules-mvp)). **`TODO(E9.M1)`** — no production ingest. HTTP **`POST …/ability-cast`** calls the engine only (no duplicate API-layer hooks for these names).
|
||||
|
||||
- **`ability_used`** — on every successful resolve (zero-damage abilities included).
|
||||
- **`enemy_defeat`** — only when lethal damage drives **`after.Defeated`** to **`true`** (re-hit on defeated targets denies at **`target_defeated`** before damage).
|
||||
- **Reserved (comments only):** **`encounter_start`** (E5.M3 encounters), **`player_death`** (player HP deferred).
|
||||
|
||||
E1.M4 cast funnel events **`ability_cast_requested`** / **`ability_cast_denied`** remain in **`AbilityCastApi`** ([NEO-30](#ability-cast-neo-31-neo-82)). E9.M1 ingest should correlate route **`playerId`** with engine payload fields at accept. Plan: [NEO-84 implementation plan](../../docs/plans/NEO-84-implementation-plan.md).
|
||||
|
||||
## Recipe definitions (NEO-68)
|
||||
|
||||
**`GET /game/world/recipe-definitions`** returns a versioned JSON body (`schemaVersion` **1**, **`recipes`**) backed by **`IRecipeDefinitionRegistry`** — the same prototype rows loaded at startup (no second source of truth). Each row includes **`id`**, **`displayName`**, **`recipeKind`**, **`requiredSkillId`**, and nested **`inputs`** / **`outputs`** (`itemId`, `quantity`). Plan: [NEO-68 implementation plan](../../docs/plans/NEO-68-implementation-plan.md); manual QA: [`docs/manual-qa/NEO-68.md`](../../docs/manual-qa/NEO-68.md); Bruno: `bruno/neon-sprawl-server/recipe-definitions/`.
|
||||
|
|
|
|||
Loading…
Reference in New Issue