# NEO-31 — Manual QA checklist

| Field | Value |
|-------|-------|
| Key | NEO-31 |
| Title | E1M4-02: Input to AbilityCastRequest path |
| Linear | https://linear.app/neon-sprawl/issue/NEO-31/e1m4-02-input-to-abilitycastrequest-path |
| Plan | `docs/plans/NEO-31-implementation-plan.md` |
| Branch | `NEO-31-e1m4-02-input-to-abilitycastrequest-path` |

## 1) Server + Bruno

- [ ] Start server: `cd server/NeonSprawl.Server && dotnet run`.
- [ ] `curl -s http://localhost:5253/health` returns JSON with `status: ok`.
- [ ] Run Bruno collection (or at least): `ability-cast/Post hotbar bind slot0 pulse` (seq **20**), **`ability-cast/Post target select alpha before cast`** (seq **21**), then **`ability-cast/Post cast happy`** (seq **22**) — expect cast `accepted: true`.
- [ ] `ability-cast/Post cast bad schema` → HTTP **400**.
- [ ] `ability-cast/Post cast missing player` → HTTP **404**.
- [ ] **`ability-cast/Post cast loadout mismatch`** (seq **25**): run **`ability-cast/Post hotbar bind slot0 pulse`** in the same session first so slot 0 is `prototype_pulse`; then expect `accepted: false` and `reasonCode == "loadout_mismatch"`.

## 2) Client — bound slot

- [ ] With server running, open main scene; confirm hotbar hydrated (NEO-29).
- [ ] Bind slot **0** to `prototype_pulse` (Bruno `hotbar-loadout/Post loadout bind` or equivalent `curl`) if not already.
- [ ] Press **1** (InputMap `hotbar_slot_1`): Godot Output shows a line starting with **`ability_cast_requested`** including `slot=0`, `ability_id=prototype_pulse`, and `targetId=` matching current HUD lock (`null` / `—` when cleared, or the locked id after Tab).
- [ ] Server log or network trace shows **one** `POST …/ability-cast` per key press (no spam while key held; Godot `is_action_pressed` fires once per press).

## 3) Client — unbound slot

- [ ] Ensure slot **2** has no ability (default empty).
- [ ] Press **3** (`hotbar_slot_3`): **no** cast POST; Output shows **`Hotbar cast ignored: slot 2 empty_slot`** (or equivalent `push_warning` text).

## 4) Deny UX

- [ ] Force a server deny (e.g. cast wrong ability for bound slot via `curl`): response `accepted: false`; if exercised through the client, Output includes **`ability_cast_denied`** in a warning.
