NEO-93: Reconcile plan, backlog, and docs for runtime state machine.
Co-authored-by: Cursor <cursoragent@cursor.com>pull/131/head
parent
48813998d7
commit
ecffba9e4b
|
|
@ -7,7 +7,7 @@
|
|||
| **Module ID** | E5.M2 |
|
||||
| **Epic** | [Epic 5 — PvE Combat](../epics/epic_05_pve_combat.md) |
|
||||
| **Stage target** | Prototype |
|
||||
| **Status** | In Progress — Slice 2 backlog [E5M2-prototype-backlog.md](../../plans/E5M2-prototype-backlog.md): **E5M2-01** [NEO-87](https://linear.app/neon-sprawl/issue/NEO-87) catalog + CI landed · **E5M2-02** [NEO-88](https://linear.app/neon-sprawl/issue/NEO-88) server load landed · **E5M2-03** [NEO-89](https://linear.app/neon-sprawl/issue/NEO-89) registry + DI landed · **E5M2-04** [NEO-90](https://linear.app/neon-sprawl/issue/NEO-90) behavior-definitions GET landed · **E5M2-05** [NEO-91](https://linear.app/neon-sprawl/issue/NEO-91) NPC instance registry + combat-target migration landed · **E5M2-06** [NEO-92](https://linear.app/neon-sprawl/issue/NEO-92) aggro rule + threat store landed → **E5M2-12** [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98) |
|
||||
| **Status** | In Progress — Slice 2 backlog [E5M2-prototype-backlog.md](../../plans/E5M2-prototype-backlog.md): **E5M2-01** [NEO-87](https://linear.app/neon-sprawl/issue/NEO-87) catalog + CI landed · **E5M2-02** [NEO-88](https://linear.app/neon-sprawl/issue/NEO-88) server load landed · **E5M2-03** [NEO-89](https://linear.app/neon-sprawl/issue/NEO-89) registry + DI landed · **E5M2-04** [NEO-90](https://linear.app/neon-sprawl/issue/NEO-90) behavior-definitions GET landed · **E5M2-05** [NEO-91](https://linear.app/neon-sprawl/issue/NEO-91) NPC instance registry + combat-target migration landed · **E5M2-06** [NEO-92](https://linear.app/neon-sprawl/issue/NEO-92) aggro rule + threat store landed · **E5M2-07** [NEO-93](https://linear.app/neon-sprawl/issue/NEO-93) NPC runtime state machine landed → **E5M2-12** [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98) |
|
||||
| **Linear** | Label **`E5.M2`** · [E5M2-prototype-backlog.md](../../plans/E5M2-prototype-backlog.md) **E5M2-01** [NEO-87](https://linear.app/neon-sprawl/issue/NEO-87) · **E5M2-02** [NEO-88](https://linear.app/neon-sprawl/issue/NEO-88) · **E5M2-03** [NEO-89](https://linear.app/neon-sprawl/issue/NEO-89) · **E5M2-04** [NEO-90](https://linear.app/neon-sprawl/issue/NEO-90) · **E5M2-05** [NEO-91](https://linear.app/neon-sprawl/issue/NEO-91) → **E5M2-12** [NEO-98](https://linear.app/neon-sprawl/issue/NEO-98) |
|
||||
|
||||
## Purpose
|
||||
|
|
@ -85,6 +85,8 @@ The **first shipped three-archetype NPC spine** under `content/npc-behaviors/*.j
|
|||
|
||||
**Threat / aggro store (NEO-92):** **`IThreatStateStore`** + **`AggroOperations`** in `server/NeonSprawl.Server/Game/Npc/` — first damaging cast acquires holder; leash break clears holder (move + cast hooks). HTTP projection in [NEO-94](https://linear.app/neon-sprawl/issue/NEO-94). Plan: [NEO-92 implementation plan](../../plans/NEO-92-implementation-plan.md).
|
||||
|
||||
**NPC runtime state machine (NEO-93):** **`INpcRuntimeStateStore`** + **`NpcRuntimeOperations.AdvanceAll`** in `server/NeonSprawl.Server/Game/Npc/` — lazy tick advance for **`idle`** → **`aggro`** → **`telegraph_windup`** → **`attack_execute`** → **`recover`**; holder sync from **`IThreatStateStore`**; HTTP projection in [NEO-94](https://linear.app/neon-sprawl/issue/NEO-94). Plan: [NEO-93 implementation plan](../../plans/NEO-93-implementation-plan.md).
|
||||
|
||||
**NPC behavior definitions HTTP (NEO-90):** **`GET /game/world/npc-behavior-definitions`** — versioned read-only projection (`schemaVersion` **1**, **`npcBehaviors`**) backed by **`INpcBehaviorDefinitionRegistry`**. Plan: [NEO-90 implementation plan](../../plans/NEO-90-implementation-plan.md); [server README — NPC behavior definitions (NEO-90)](../../../server/README.md#npc-behavior-definitions-neo-90); Bruno `bruno/neon-sprawl-server/npc-behavior-definitions/`.
|
||||
|
||||
## Risks and telemetry
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -241,9 +241,11 @@ Working backlog for **Epic 5 — Slice 2** ([NPC archetypes and telegraphs](../d
|
|||
|
||||
**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.
|
||||
- [x] Aggro'd NPC enters telegraph after `attackCooldownSeconds` elapses from last attack.
|
||||
- [x] Windup duration matches `telegraphWindupSeconds` from behavior def.
|
||||
- [x] Idle NPC with no aggro holder does not telegraph.
|
||||
|
||||
**Landed ([NEO-93](https://linear.app/neon-sprawl/issue/NEO-93)):** **`INpcRuntimeStateStore`** + **`NpcRuntimeOperations.AdvanceAll`** — catalog-driven state machine, lazy tick with delta cap; plan [NEO-93-implementation-plan.md](NEO-93-implementation-plan.md).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -50,10 +50,18 @@
|
|||
|
||||
## Acceptance criteria checklist
|
||||
|
||||
- [ ] Aggro'd NPC enters telegraph after `attackCooldownSeconds` elapses from last attack (or from aggro entry on first cycle).
|
||||
- [ ] Windup duration matches `telegraphWindupSeconds` from behavior def.
|
||||
- [ ] Idle NPC with no aggro holder does not telegraph.
|
||||
- [ ] Full transition chain **`idle` → `aggro` → `telegraph_windup` → `attack_execute` → `recover`** is reachable with holder set (deterministic injected clock).
|
||||
- [x] Aggro'd NPC enters telegraph after `attackCooldownSeconds` elapses from last attack (or from aggro entry on first cycle).
|
||||
- [x] Windup duration matches `telegraphWindupSeconds` from behavior def.
|
||||
- [x] Idle NPC with no aggro holder does not telegraph.
|
||||
- [x] Full transition chain **`idle` → `aggro` → `telegraph_windup` → `attack_execute` → `recover`** is reachable with holder set (deterministic injected clock).
|
||||
|
||||
## Implementation reconciliation (shipped)
|
||||
|
||||
- **`NpcBehaviorState`**, **`INpcRuntimeStateStore`**, **`InMemoryNpcRuntimeStateStore`**, and **`NpcRuntimeOperations.AdvanceAll`** landed in `Game/Npc/`.
|
||||
- Holder-only idle→aggro sync from **`IThreatStateStore`**; no proximity auto-aggro.
|
||||
- **`attack_execute`** chains instantly to **`recover`** within the same advance step (no player damage — NEO-95).
|
||||
- Dev **`combat-targets-fixture`** resets runtime rows alongside HP + threat.
|
||||
- **17** NEO-93-focused tests green (`NpcRuntimeOperationsTests`, `InMemoryNpcRuntimeStateStoreTests`, fixture extension).
|
||||
|
||||
## Technical approach
|
||||
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ Plan: [NEO-80 implementation plan](../../docs/plans/NEO-80-implementation-plan.m
|
|||
curl -sS -i "http://localhost:5253/game/world/combat-targets"
|
||||
```
|
||||
|
||||
**Dev combat-target fixture (Bruno/manual QA):** When `Game:EnableCombatTargetFixtureApi` is **true** (default in **Development** via `appsettings.Development.json`, **Testing**, or CI Bruno step) or the host environment is **Development** / **Testing**, **`POST /game/__dev/combat-targets-fixture`** with `schemaVersion` **1** resets all **`PrototypeNpcRegistry`** instances to catalog full HP via **`TryResetToFull`** and clears all prototype aggro holders (**NEO-92**). **404** when disabled. Bruno: `scripts/combat-targets-reset-helper.js`.
|
||||
**Dev combat-target fixture (Bruno/manual QA):** When `Game:EnableCombatTargetFixtureApi` is **true** (default in **Development** via `appsettings.Development.json`, **Testing**, or CI Bruno step) or the host environment is **Development** / **Testing**, **`POST /game/__dev/combat-targets-fixture`** with `schemaVersion` **1** resets all **`PrototypeNpcRegistry`** instances to catalog full HP via **`TryResetToFull`**, clears all prototype aggro holders (**NEO-92**), and resets NPC runtime behavior rows to **`idle`** (**NEO-93**). **404** when disabled. Bruno: `scripts/combat-targets-reset-helper.js`.
|
||||
|
||||
## Threat / aggro state (NEO-92)
|
||||
|
||||
|
|
@ -167,6 +167,27 @@ Per-NPC aggro holders live in **`Game/Npc/`** as **`IThreatStateStore`** + **`In
|
|||
|
||||
Plan: [NEO-92 implementation plan](../../docs/plans/NEO-92-implementation-plan.md).
|
||||
|
||||
## NPC runtime behavior state (NEO-93)
|
||||
|
||||
Per-NPC behavior states live in **`Game/Npc/`** as **`INpcRuntimeStateStore`** + **`InMemoryNpcRuntimeStateStore`**, keyed by **`PrototypeNpcRegistry`** instance id. **`NpcRuntimeOperations.AdvanceAll`** drives lazy tick advance (monotonic clock, per-call delta cap **5.0 s**) using catalog **`attackCooldownSeconds`** and **`telegraphWindupSeconds`**.
|
||||
|
||||
| State | Meaning |
|
||||
|-------|---------|
|
||||
| **`idle`** | No aggro holder; no telegraph. |
|
||||
| **`aggro`** | Holder set; waiting **`attackCooldownSeconds`** before windup. |
|
||||
| **`telegraph_windup`** | Active telegraph; **`ActiveTelegraphSnapshot`** on the runtime row. |
|
||||
| **`attack_execute`** | Instant stub in NEO-93 (player damage lands in [NEO-95](https://linear.app/neon-sprawl/issue/NEO-95)). |
|
||||
| **`recover`** | Post-attack cooldown wait before next windup. |
|
||||
|
||||
| Rule | Behavior |
|
||||
|------|----------|
|
||||
| **Holder sync** | Empty holder → **`idle`**; holder while **`idle`** → **`aggro`**. No proximity auto-aggro (first-hit holder from NEO-92 only). |
|
||||
| **Lazy advance** | **`AdvanceAll`** simulates up to **5.0 s** per call; [NEO-94](https://linear.app/neon-sprawl/issue/NEO-94) wires this to **`GET /game/world/npc-runtime-snapshot`**. |
|
||||
| **Fixture reset** | Dev combat-target fixture clears runtime rows alongside HP + threat. |
|
||||
| **HTTP read** | Not exposed yet — snapshot GET lands in [NEO-94](https://linear.app/neon-sprawl/issue/NEO-94). |
|
||||
|
||||
Plan: [NEO-93 implementation plan](../../docs/plans/NEO-93-implementation-plan.md).
|
||||
|
||||
## Combat engine (NEO-81)
|
||||
|
||||
**`CombatOperations.TryResolve`** in **`Game/Combat/`** resolves server-internal **`CombatResult`**: ability lookup via **`IAbilityDefinitionRegistry`**, target HP read via **`ICombatEntityHealthStore`**, defeated pre-check (no damage on re-hit), then catalog **`baseDamage`** application. Zero-damage abilities (**`prototype_guard`**, **`prototype_dash`**) succeed without mutating HP. **`AbilityCastApi`** (NEO-82) invokes **`TryResolve`** after E1.M4 gates and returns nested wire **`combatResolution`** on accept.
|
||||
|
|
|
|||
Loading…
Reference in New Issue