neon-sprawl/docs/plans/E5M2-prototype-backlog.md

20 KiB
Raw Blame History

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-76NEO-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-23NEO-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 112 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.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 + 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.

Landed (NEO-87): content/npc-behaviors/prototype_npc_behaviors.json, npc-behavior-def.schema.json, CI gates in validate_content.py; plan NEO-87-implementation-plan.md.


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.

Landed (NEO-88): fail-fast server load of content/npc-behaviors/*_npc_behaviors.json at startup — server/NeonSprawl.Server/Game/Npc/; plan NEO-88-implementation-plan.md.


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.

Landed (NEO-89): INpcBehaviorDefinitionRegistry + NpcBehaviorDefinitionRegistry + PrototypeNpcBehaviorRegistry in server/NeonSprawl.Server/Game/Npc/; plan NEO-89-implementation-plan.md.


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.

Landed (NEO-90): GET /game/world/npc-behavior-definitionsNpcBehaviorDefinitionsWorldApi + DTOs in Game/Npc/; plan NEO-90-implementation-plan.md; Bruno bruno/neon-sprawl-server/npc-behavior-definitions/.


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 — 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: idleaggrotelegraph_windupattack_executerecover 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 — 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 — 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 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).
  • docs/manual-qa/NEO-97.mdGodot 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

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 blockedBy synchronized if scope changes.
  • For each issue kickoff: docs/plans/{NEO-XX}-implementation-plan.md per story-kickoff.
  • Add docs/manual-qa/{NEO-XX}.md for player-visible client stories when implementation lands.

Decomposition complete checklist