# NEO-98 — Manual QA checklist | Field | Value | |-------|-------| | Key | NEO-98 | | Title | E5M2-12: Playable NPC telegraph combat capstone (Godot) | | Linear | https://linear.app/neon-sprawl/issue/NEO-98/e5m2-12-playable-npc-telegraph-combat-capstone-godot | | Plan | `docs/plans/NEO-98-implementation-plan.md` | | Branch | `NEO-98-e5m2-12-playable-npc-telegraph-combat-capstone` | ## Preconditions - **Fresh dev player:** stop any running server, then start a new instance so in-memory **`dev-local-1`** NPC HP, aggro/runtime rows, and player combat HP reset. - **No Bruno/curl seeding** for this checklist — hotbar **`prototype_pulse`** bind is automatic (NEO-85 dev bootstrap). - NEO-97 telegraph HUD landed on `main` (PR #136). - **`CombatTargetHpLabel`** uses NEO-85 format **`Target HP: {id} {current}/{max}`** (e.g. **`Target HP: prototype_npc_melee 100/100`**) — match HP substring or full line. ## Archetype reference | Archetype | Instance id | Marker | maxHp | Pulses to defeat | NPC damage | Strike range | Windup | Cooldown | |-----------|-------------|--------|-------|------------------|------------|--------------|--------|----------| | Melee | `prototype_npc_melee` | orange west **`(-3, -3)`** | 100 | **4** | 15 | **2 m** (close) | 1.5 s | 3.0 s | | Ranged | `prototype_npc_ranged` | purple SE **`(3, 3)`** | 80 | **4** | 12 | 10 m | 2.0 s | 4.0 s | | Elite | `prototype_npc_elite` | gold origin **`(0, 0)`** | 200 | **8** | 25 | **4 m** | 2.5 s | 5.0 s | Player casts **`prototype_pulse`** (**25** damage, ~**3 s** cooldown). ## Melee defeat math | Cast # | Expected `CastFeedbackLabel` | `CombatTargetHpLabel` | |--------|------------------------------|------------------------| | 1 | `Cast: 25 dmg → 75 HP` | `75/100` | | 2 | `Cast: 25 dmg → 50 HP` | `50/100` | | 3 | `Cast: 25 dmg → 25 HP` | `25/100` | | 4 | `Cast: 25 dmg → 0 HP — defeated!` | `0/100 (defeated)` | | 5 | `ability_cast_denied: target_defeated` | unchanged defeated | ## Checklist ### Boot 1. Start server: `cd server/NeonSprawl.Server && dotnet run`. 2. Run Godot main scene (**F5**). Confirm slot **0** auto-binds **`prototype_pulse`** after hotbar sync. ### Melee — full telegraph + player HP spine 3. Walk to orange **Melee** marker west of spawn **`(-3, -3)`**. 4. Press **Tab** until **`prototype_npc_melee`** is locked with **`Validity: ok`** — marker **brightens**; **`CombatTargetHpLabel`** shows **`Target HP: prototype_npc_melee 100/100`** (or **`…`** briefly until GET completes). 5. Press **1** (damaging cast): **`CastFeedbackLabel`** shows damage; **`NpcStateLabel`** eventually shows **`NPC state: Melee → aggro`** (~1 Hz poll). 6. **`PlayerCombatHpLabel`** shows **`Player HP: 100/100`** once combat poll starts. 7. Wait **≥ 3 s** from first cast: **`NpcStateLabel`** shows **`telegraph_windup`**; **`TelegraphLabel`** shows countdown (e.g. **`Telegraph: Melee … · ~1.x s (melee)`**) ticking between polls. 8. Wait **≥ 4.5 s** total from first cast (windup completes): stay **within ~2 m** of the melee marker (close combat — tab-lock is 6 m but strike range is **2 m**). **`PlayerCombatHpLabel`** drops to **`85/100`** (melee **15** damage). Back off during windup → telegraph may finish but **no** player damage (whiff). 9. Press **1** three more times (respect ~3 s cooldown) until **`CombatTargetHpLabel`** shows **`0/100 (defeated)`** per defeat table above. 10. Press **1** again: **`ability_cast_denied: target_defeated`**. ### Ranged — defeat + telegraph visible 11. Walk to purple **Ranged** marker south-east **`(3, 3)`**; **Tab** lock **`prototype_npc_ranged`**. 12. Press **1** (first damaging cast) → **`NpcStateLabel`** shows **`aggro`**. 13. Wait **≥ 4 s** from first cast: **`telegraph_windup`** + readable **`TelegraphLabel`** (2.0 s windup after 4.0 s cooldown). 14. Press **1** until defeated (**4** pulses total); verify **`0/80 (defeated)`** on **`CombatTargetHpLabel`** and deny on re-cast. ### Elite — defeat + telegraph visible 15. Walk to gold **Elite** marker at origin **`(0, 0)`**; **Tab** lock **`prototype_npc_elite`**. 16. Press **1** (first damaging cast) → **`NpcStateLabel`** shows **`aggro`**. 17. Wait **≥ 5 s** from first cast: **`telegraph_windup`** + readable **`TelegraphLabel`** (2.5 s windup after 5.0 s cooldown). 18. Press **1** until defeated (**8** pulses total); verify **`0/200 (defeated)`** on **`CombatTargetHpLabel`** and deny on re-cast. ### Regression 19. Press **Esc** to clear lock: **`CombatTargetHpLabel`** shows **`Target HP: — (Tab → elite/melee/ranged near spawn)`** (or equivalent no-lock line). 20. Cast without lock: **`CastFeedbackLabel`** shows **`ability_cast_denied: invalid_target`** (NEO-28). 21. **`CombatTargetHpLabel`** + cooldown HUD still refresh after accepted casts (NEO-85 / NEO-32) — re-lock any NPC and press **1** once to confirm. ## Notes - Capstone baseline prefers **server restart** over mid-session fixture POST. - Optional reset (Development host): `POST /game/__dev/combat-targets-fixture` — see [NEO-97 manual QA](NEO-97.md) / [server README — dev fixture](../../server/README.md#dev-combat-target-fixture-brunomanual-qa). - Component-level telegraph HUD regression (elite incoming-threat row): [NEO-97 manual QA](NEO-97.md). - Slice 1 combat capstone (gig XP): [NEO-86 manual QA](NEO-86.md). ## Acceptance - [ ] Steps 1–21 completable in one session without Bruno/curl. - [ ] Melee telegraph countdown readable **before** player HP drops to **85/100** (steps 7–8). - [ ] All three archetypes show **`defeated`** in **`CombatTargetHpLabel`** (steps 9, 14, 18). - [ ] Aggro on first damaging cast feels deterministic (no silent failures).