NEO-96: formalize NPC runtime telemetry hook sites

Replace NEO-93 placeholder comments with full telegraph_fired and
npc_state_transition E9.M1 anchors in NpcRuntimeOperations; document
in server README and E5.M2 module alignment.
pull/135/head
VinPropane 2026-05-29 20:48:50 -04:00
parent dedd9a9c18
commit 5e1e37423f
6 changed files with 67 additions and 12 deletions

View File

@ -91,6 +91,8 @@ The **first shipped three-archetype NPC spine** under `content/npc-behaviors/*.j
**Session player combat HP (NEO-95):** **`IPlayerCombatHealthStore`** + **`NpcAttackOperations.TryResolveTelegraphComplete`** in `server/NeonSprawl.Server/Game/Combat/` — telegraph complete applies catalog **`attackDamage`** to aggro holder; **`GET /game/players/{id}/combat-health`** read model for client HUD. Plan: [NEO-95 implementation plan](../../plans/NEO-95-implementation-plan.md); [server README — Session player combat HP (NEO-95)](../../../server/README.md#session-player-combat-hp-neo-95); Bruno `bruno/neon-sprawl-server/combat-health/`.
**NPC runtime telemetry hooks (NEO-96):** comment-only **`telegraph_fired`** + **`npc_state_transition`** hook sites in **`NpcRuntimeOperations.AdvanceAll`** (`TODO(E9.M1)`; no ingest). Plan: [NEO-96 implementation plan](../../plans/NEO-96-implementation-plan.md); [server README — NPC runtime telemetry hooks (NEO-96)](../../../server/README.md#npc-runtime-telemetry-hooks-neo-96).
**NPC behavior definitions HTTP (NEO-90):** **`GET /game/world/npc-behavior-definitions`** — versioned read-only projection (`schemaVersion` **1**, **`npcBehaviors`**) backed by **`INpcBehaviorDefinitionRegistry`**. Plan: [NEO-90 implementation plan](../../plans/NEO-90-implementation-plan.md); [server README — NPC behavior definitions (NEO-90)](../../../server/README.md#npc-behavior-definitions-neo-90); Bruno `bruno/neon-sprawl-server/npc-behavior-definitions/`.
## Risks and telemetry

File diff suppressed because one or more lines are too long

View File

@ -322,8 +322,10 @@ Working backlog for **Epic 5 — Slice 2** ([NPC archetypes and telegraphs](../d
**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.
- [x] Hook names match [epic_05 Slice 2](../decomposition/epics/epic_05_pve_combat.md#slice-2---npc-archetypes-and-telegraphs) vocabulary.
- [x] No runtime behavior change beyond comments.
**Landed ([NEO-96](https://linear.app/neon-sprawl/issue/NEO-96)):** comment-only **`telegraph_fired`** + **`npc_state_transition`** hook sites in **`NpcRuntimeOperations`**; plan [NEO-96-implementation-plan.md](NEO-96-implementation-plan.md).
---

View File

@ -45,9 +45,16 @@
## Acceptance criteria checklist
- [ ] **`TODO(E9.M1)`** markers present at **`telegraph_fired`** and **`npc_state_transition`** hook sites in NPC runtime path.
- [ ] Hook names match [epic_05 Slice 2](../decomposition/epics/epic_05_pve_combat.md#slice-2---npc-archetypes-and-telegraphs) vocabulary (`telegraph_fired`, `npc_state_transition`).
- [ ] No runtime behavior change beyond comments / doc cross-links.
- [x] **`TODO(E9.M1)`** markers present at **`telegraph_fired`** and **`npc_state_transition`** hook sites in NPC runtime path.
- [x] Hook names match [epic_05 Slice 2](../decomposition/epics/epic_05_pve_combat.md#slice-2---npc-archetypes-and-telegraphs) vocabulary (`telegraph_fired`, `npc_state_transition`).
- [x] No runtime behavior change beyond comments / doc cross-links.
## Implementation reconciliation (shipped)
- NEO-93 `(NEO-96)` stub comments replaced with full NEO-84-style hook blocks in **`NpcRuntimeOperations.AdvanceAll`**.
- **`telegraph_fired`** at aggro→telegraph and recover→telegraph entries; **`npc_state_transition`** on every state commit including holder clear → idle.
- **`server/README.md`**, **E5_M2** module doc, **E5M2** backlog, and alignment register updated.
- No new tests (comment-only); existing NPC runtime tests unchanged.
## Technical approach

View File

@ -2,7 +2,11 @@ using NeonSprawl.Server.Game.Combat;
namespace NeonSprawl.Server.Game.Npc;
/// <summary>Deterministic prototype NPC behavior state machine + lazy tick advance (NEO-93).</summary>
/// <summary>
/// Deterministic prototype NPC behavior state machine + lazy tick advance (NEO-93).
/// NEO-96 telemetry hook sites: state transition commits and telegraph starts in <see cref="AdvanceAll"/>
/// (<c>telegraph_fired</c>, <c>npc_state_transition</c>).
/// </summary>
public static class NpcRuntimeOperations
{
/// <summary>Default per-advance simulation cap (seconds) for lazy poll ticks.</summary>
@ -114,6 +118,10 @@ public static class NpcRuntimeOperations
if (snapshot.BehaviorState != NpcBehaviorState.Idle || snapshot.ActiveTelegraph is not null)
{
WriteIdle(runtimeStore, npcInstanceId);
// --- Telemetry hook site (NEO-96): future E9.M1 catalog event `npc_state_transition` ---
// TODO(E9.M1): catalog emit — holder cleared or fixture reset (any → idle).
// Planned payload fields: npcInstanceId, behaviorDefId, fromState, toState (idle),
// phaseStartedUtc, optional aggroHolderPlayerId (previous holder when known).
}
return;
@ -127,7 +135,10 @@ public static class NpcRuntimeOperations
NpcBehaviorState.Aggro,
windowStart,
activeTelegraph: null);
// telemetry: npc_state_transition (NEO-96)
// --- Telemetry hook site (NEO-96): future E9.M1 catalog event `npc_state_transition` ---
// TODO(E9.M1): catalog emit — idle → aggro when aggro holder is set.
// Planned payload fields: npcInstanceId, behaviorDefId, fromState (idle), toState (aggro),
// phaseStartedUtc, aggroHolderPlayerId.
if (!runtimeStore.TryGet(npcInstanceId, out snapshot))
{
return;
@ -139,7 +150,16 @@ public static class NpcRuntimeOperations
{
if (!TryHasAggroHolder(npcInstanceId, threatStore, out aggroHolderPlayerId))
{
if (!runtimeStore.TryGet(npcInstanceId, out snapshot))
{
return;
}
WriteIdle(runtimeStore, npcInstanceId);
// --- Telemetry hook site (NEO-96): future E9.M1 catalog event `npc_state_transition` ---
// TODO(E9.M1): catalog emit — holder cleared mid-cycle (any → idle).
// Planned payload fields: npcInstanceId, behaviorDefId, fromState, toState (idle),
// phaseStartedUtc, optional aggroHolderPlayerId (previous holder).
return;
}
@ -170,7 +190,14 @@ public static class NpcRuntimeOperations
NpcBehaviorState.TelegraphWindup,
cursor,
telegraph);
// telemetry: npc_state_transition (NEO-96)
// --- Telemetry hook site (NEO-96): future E9.M1 catalog event `npc_state_transition` ---
// TODO(E9.M1): catalog emit — aggro → telegraph_windup.
// Planned payload fields: npcInstanceId, behaviorDefId, fromState (aggro),
// toState (telegraph_windup), phaseStartedUtc, aggroHolderPlayerId, telegraphId.
// --- Telemetry hook site (NEO-96): future E9.M1 catalog event `telegraph_fired` ---
// TODO(E9.M1): catalog emit — once per telegraph start (each windup cycle).
// Planned payload fields: npcInstanceId, telegraphId, behaviorDefId, archetypeKind,
// aggroHolderPlayerId, windupStartedUtc, telegraphWindupSeconds.
break;
}
@ -195,7 +222,10 @@ public static class NpcRuntimeOperations
NpcBehaviorState.Recover,
cursor,
activeTelegraph: null);
// telemetry: telegraph_fired / npc_state_transition (NEO-96); logical attack_execute → recover
// --- Telemetry hook site (NEO-96): future E9.M1 catalog event `npc_state_transition` ---
// TODO(E9.M1): catalog emit — telegraph_windup → recover (logical attack_execute instant).
// Planned payload fields: npcInstanceId, behaviorDefId, fromState (telegraph_windup),
// toState (recover), phaseStartedUtc, aggroHolderPlayerId, telegraphId (completed windup).
break;
}
@ -213,7 +243,10 @@ public static class NpcRuntimeOperations
NpcBehaviorState.Recover,
cursor,
activeTelegraph: null);
// telemetry: npc_state_transition (NEO-96)
// --- Telemetry hook site (NEO-96): future E9.M1 catalog event `npc_state_transition` ---
// TODO(E9.M1): catalog emit — attack_execute → recover (defensive visible phase).
// Planned payload fields: npcInstanceId, behaviorDefId, fromState (attack_execute),
// toState (recover), phaseStartedUtc, aggroHolderPlayerId.
break;
case NpcBehaviorState.Recover:
@ -232,7 +265,14 @@ public static class NpcRuntimeOperations
NpcBehaviorState.TelegraphWindup,
cursor,
telegraph);
// telemetry: npc_state_transition (NEO-96)
// --- Telemetry hook site (NEO-96): future E9.M1 catalog event `npc_state_transition` ---
// TODO(E9.M1): catalog emit — recover → telegraph_windup.
// Planned payload fields: npcInstanceId, behaviorDefId, fromState (recover),
// toState (telegraph_windup), phaseStartedUtc, aggroHolderPlayerId, telegraphId.
// --- Telemetry hook site (NEO-96): future E9.M1 catalog event `telegraph_fired` ---
// TODO(E9.M1): catalog emit — once per telegraph start (each windup cycle).
// Planned payload fields: npcInstanceId, telegraphId, behaviorDefId, archetypeKind,
// aggroHolderPlayerId, windupStartedUtc, telegraphWindupSeconds.
break;
}

View File

@ -188,6 +188,10 @@ Per-NPC behavior states live in **`Game/Npc/`** as **`INpcRuntimeStateStore`** +
Plan: [NEO-93 implementation plan](../../docs/plans/NEO-93-implementation-plan.md).
### NPC runtime telemetry hooks (NEO-96)
Comment-only hook sites in **`NpcRuntimeOperations.AdvanceAll`** for future E9.M1 catalog events — **`telegraph_fired`** at each **`telegraph_windup`** entry (aggro→telegraph and recover→telegraph); **`npc_state_transition`** on every committed state change (including holder clear → **`idle`**). **`TODO(E9.M1)`** — no production ingest. Snapshot GET invokes **`AdvanceAll`** before read (no duplicate API-layer hooks). Plan: [NEO-96 implementation plan](../../docs/plans/NEO-96-implementation-plan.md).
## NPC runtime snapshot (NEO-94)
**`GET /game/world/npc-runtime-snapshot`** returns a versioned JSON body (`schemaVersion` **1**, **`serverTimeUtc`**, **`npcInstances`**) backed by **`INpcRuntimeStateStore`** + **`IThreatStateStore`** after **`NpcRuntimeOperations.AdvanceAll`** (lazy tick, **5.0 s** delta cap). Each row includes **`npcInstanceId`**, **`behaviorDefId`**, **`state`** (stable snake_case), **`aggroHolderPlayerId`**, and optional nested **`activeTelegraph`** during **`telegraph_windup`**. All three **`PrototypeNpcRegistry`** instances are always returned in ascending **`npcInstanceId`** order. Plan: [NEO-94 implementation plan](../../docs/plans/NEO-94-implementation-plan.md); Bruno: `bruno/neon-sprawl-server/npc-runtime-snapshot/`.