# 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`**.
