19 KiB
E5.M2 — Prototype story backlog (NpcAiAndBehaviorProfiles)
Working backlog for Epic 5 — Slice 2 (NPC archetypes and telegraphs). Decomposition and contracts: E5.M2 — NpcAiAndBehaviorProfiles.
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).
Labels (Linear): every issue E5.M2; add server or client + Story as listed.
Precursor (do not re-scope): E5.M1 combat spine — ability catalog (NEO-76–NEO-79), cast + CombatResolution (NEO-82), combat-targets GET (NEO-83), client HUD + capstone (NEO-85, NEO-86). ThreatState / aggro was stub-only in Slice 1 — this module owns it.
Upstream (must be landed): E5.M1 Ready; E1.M3 targeting + tab cycle (NEO-23–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 |
| E5M2-02 | server | NEO-88 |
| E5M2-03 | server | NEO-89 |
| E5M2-04 | server | NEO-90 |
| E5M2-05 | server | NEO-91 |
| E5M2-06 | server | NEO-92 |
| E5M2-07 | server | NEO-93 |
| E5M2-08 | server | NEO-94 |
| E5M2-09 | server | NEO-95 |
| E5M2-10 | server | NEO-96 |
| E5M2-11 | client | NEO-97 |
| E5M2-12 | client | 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 encounters + loot; E4.M2 spawn ecology; E1.M4 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) |
| 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 |
| World placement | Static PrototypeNpcRegistry (successor to target registry anchors) until E4.M2 |
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.jsonwith stableidvalues:prototype_melee_pressure,prototype_ranged_control,prototype_elite_mini_boss.- Fields:
displayName,archetypeKindenum (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 +
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
idfails 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.mdsection.
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/GetDefinitionsInIdOrderparity 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 inGame/Npc/.- Integration tests (AAA); Bruno folder
bruno/neon-sprawl-server/npc-behavior-definitions/. server/README.mdroute section.
Out of scope
- Runtime state, telegraphs, Godot.
Acceptance criteria
- GET returns all three defs in ascending
idorder withschemaVersion1. - 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/ICombatEntityHealthStoregates 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 — 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/TryClearOnLeashusing playerPositionState+ registry anchors.- Hook from
CombatOperations.TryResolveon 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
attackCooldownSecondselapses from last attack. - Windup duration matches
telegraphWindupSecondsfrom 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-NPCstate,aggroHolderPlayerId, activeTelegraphEvent(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.mdsection.
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 — 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.TryResolveTelegraphCompleteinvoked from tick advance.- Extend snapshot DTO (or add
GET /game/players/{id}/combat-health) withcurrentHp/maxHpfor client HUD. - Integration tests: telegraph complete → player HP decreases deterministically.
- Reserved
player_deathhook 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 — 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.mdpointer; module doc cross-link.
Out of scope
- Production ingest, Godot.
Acceptance criteria
- Hook names match epic_05 Slice 2 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— pollGET /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,PlayerCombatHpLabelunderUICanvas. - Update
prototype_target_constants.gd/ tab cycle for three NPC ids (server parity). - GdUnit tests with HTTP doubles (testing-expectations.md).
docs/manual-qa/NEO-97.md— Godot steps (server + client running).client/README.mdNPC / 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.mdsingle-session script covering three archetypes.client/README.mdend-to-end NPC combat loop section.- Update documentation_and_implementation_alignment.md E5.M2 row when complete.
Out of scope
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 for prototype minimums.
Client counterpart: this issue (NEO-98).
After this backlog
- E5.M3 Slice 3 — decompose when E5.M2 lands (encounters + loot routing).
- Track delivery in Linear; keep
blockedBysynchronized if scope changes. - For each issue kickoff:
docs/plans/{NEO-XX}-implementation-plan.mdper story-kickoff. - Add
docs/manual-qa/{NEO-XX}.mdfor player-visible client stories when implementation lands.
Decomposition complete checklist
- Every player-visible AC has a
clientLinear issue (NEO-97, NEO-98) - No “optional follow-up” / “future client” without NEO-XX
- Capstone client issue exists for playable Godot verification (NEO-98)
- documentation_and_implementation_alignment.md updated
- module_dependency_register.md E5.M2 note updated when issues are created