# NEO-28 — Manual QA checklist | Field | Value | |-------|-------| | Key | NEO-28 | | Title | E1M4-03: Combat accept/deny integration + reason-code UX | | Linear | https://linear.app/neon-sprawl/issue/NEO-28/e1m4-03-combat-acceptdeny-integration-reason-code-ux | | Plan | `docs/plans/NEO-28-implementation-plan.md` | ## 1) Server + Bruno - [ ] Start server: `cd server/NeonSprawl.Server && dotnet run`. - [ ] `curl -s http://localhost:5253/health` returns JSON with `status: ok`. - [ ] Happy path (same order as folder **seq**): **`Post hotbar bind slot0 pulse`** → **`Post target select alpha before cast`** → **`Post cast happy`** — cast **`accepted: true`**. - [ ] **`Post cast happy`** alone (no target select in session) — expect **`accepted: false`**, **`reasonCode: invalid_target`** (or run `curl` with loadout + no lock). - [ ] After lock + loadout, **`curl`** cast with **`targetId: prototype_target_beta`** while lock is alpha — **`invalid_target`**. - [ ] After lock + loadout, move player far from alpha (`POST …/move-stream` with a distant position), then cast with alpha — **`out_of_range`**. ## 2) Client — HUD - [ ] With server running, open main scene; hydrate hotbar (NEO-29); bind slot **0** to **`prototype_pulse`** if needed. - [ ] **Without** Tab lock, press **1**: left HUD **`CastFeedbackLabel`** shows **`ability_cast_denied: invalid_target`** (or equivalent); Output may still show **`ability_cast_denied`** warning. - [ ] **Tab** to lock **alpha**; press **1**: **`CastFeedbackLabel`** shows **`Cast: accepted`**; Output still prints **`ability_cast_requested`** when POST queued. - [ ] **WASD** until target HUD shows **`Validity: out_of_range`** (soft lock); press **1**: **`CastFeedbackLabel`** shows **`ability_cast_denied: out_of_range`**.