NEO-24: add manual QA checklist

First instance of the new docs/manual-qa/ convention. Covers boot sync,
Tab happy path, out-of-range soft-lock (both by Tab and by locomotion
per Decision 2), swap-to-beta, Esc clear, denial UI hygiene, input
behavior, move-rejection coexistence, transport failure, and no-
regression checks against NEO-7/9/19/22.
pull/47/head
VinPropane 2026-04-21 22:21:50 -04:00
parent f87ff2c99a
commit cbcb4e3d63
1 changed files with 105 additions and 0 deletions

View File

@ -0,0 +1,105 @@
# NEO-24 — Manual QA checklist
| Field | Value |
|--------|--------|
| **Key** | NEO-24 |
| **Title** | E1.M3: Client tab-target + lock UI synced to server |
| **Linear** | [NEO-24](https://linear.app/neon-sprawl/issue/NEO-24/e1m3-client-tab-target-lock-ui-synced-to-server) |
| **Plan** | [`docs/plans/NEO-24-implementation-plan.md`](../plans/NEO-24-implementation-plan.md) |
| **Branch** | `NEO-24-e1m3-client-tab-target-lock-ui-synced-to-server` |
Pair the client (Godot **F5**) with the server (`cd server/NeonSprawl.Server && dotnet run`). Keep the server console visible so you can see route hits, and the Godot **Output** dock visible for client warnings.
## 0. Setup sanity
- [ ] Server prints a URL around `http://localhost:5253` on boot; `GET /health` returns JSON.
- [ ] In Godot, select `TargetSelectionClient` in the scene tree: **`base_url`** matches the server URL and **`dev_player_id`** matches `Game:DevPlayerId` (default `dev-local-1`).
- [ ] (Optional) Bruno collection `bruno/neon-sprawl-server/targeting/` is configured for the same port — use it to cross-check server state after client actions.
## 1. Boot state
- [ ] On run, the player snaps to spawn (`-5, 0.9, -5`) — existing NEO-7 behavior, unchanged.
- [ ] `UICanvas/TargetLockLabel` (top-left, under `PlayerPositionLabel`) shows:
- `Target: —`
- `Validity: none`
- `Seq: 0`
- [ ] Godot **Output** has no `TargetSelectionClient:` warnings. Server console shows a single `GET /game/players/dev-local-1/target` from the boot sync.
## 2. Tab cycle — happy path
- [ ] Press **Tab**. HUD flips to:
- `Target: prototype_target_alpha`
- `Validity: ok`
- `Seq: 1`
- [ ] Server log shows `POST /game/players/dev-local-1/target/select` with `{"schemaVersion":1,"targetId":"prototype_target_alpha"}`.
- [ ] No `Denied:` line is appended to the HUD (success ⇒ no `reasonCode`).
## 3. Tab to out-of-range target — soft lock preserved
- [ ] With alpha locked, press **Tab** again. The server denies the swap to beta (spawn is ~18 m from `(8, 8)`; beta radius is 4 m).
- [ ] HUD **stays** at `Target: prototype_target_alpha`, `Validity: ok`, `Seq: 1` with **`Denied: out_of_range`** appended. That is the NEO-23 soft-lock rule: a failed swap does not change the persisted lock or its sequence.
- [ ] Server log shows the denial: `POST``200` with `selectionApplied: false`.
- [ ] Press **Tab** several more times from here. Each press cleanly denies; HUD never drifts or shows a `beta` / optimistic lock between responses.
## 4. Soft-lock out-of-range via locomotion (plan Decision 2)
- [ ] Still locked to alpha, **WASD** away from spawn until you are ~8 m horizontally from `(-5, -5)`. Within ~250 ms of the next authority snap, HUD flips to:
- `Target: prototype_target_alpha` (unchanged — soft lock)
- `Validity: out_of_range`
- `Seq: 1` (unchanged — no lock id change)
- [ ] Walk back inside the radius. HUD returns to `Validity: ok` within ~250 ms of the next snap.
- [ ] While walking, Godot Output is **not** spammed with targeting GETs. Server log shows no more than roughly one targeting `GET` per 250 ms during sustained motion.
- [ ] Stand still with no lock changes. Server log shows **no** targeting GETs at idle (no timer polling).
## 5. Swap to beta when in range
- [ ] WASD toward beta at **(8, 8)** until you are within 4 m. Press **Tab**. HUD swaps cleanly:
- `Target: prototype_target_beta`
- `Validity: ok`
- `Seq` increments by 1
- [ ] Press **Tab** once more. Server denies (alpha is far away) — HUD stays at `beta / ok` with `Denied: out_of_range` appended.
## 6. Clear
- [ ] Press **Esc** at any point with a lock held. HUD flips to:
- `Target: —`
- `Validity: none`
- `Seq` increments by 1
- [ ] Any `Denied: …` line from the previous state is gone.
- [ ] Server log shows `POST /target/select` with body `{"schemaVersion":1}` (no `targetId` key).
- [ ] Press **Esc** again with no lock. Server returns the same cleared state; HUD stays `—` / `none` (server may or may not bump sequence — whatever it returns is what the HUD shows).
## 7. Denial UI hygiene
- [ ] Press **Tab** from `—` while far from both targets. If the server denies (possible depending on exact spawn distance), HUD shows `Target: —`, `Validity: none`, `Denied: out_of_range`. The lock id does **not** get "stuck" at something the server refused.
- [ ] After any successful Tab / Esc, the `Denied: …` line goes away on the next response.
## 8. Input behavior
- [ ] **Tab** does **not** make focus jump around UI during play (we intercept in `_input` before UI focus navigation).
- [ ] **Esc** does not quit or minimize the game.
- [ ] Holding **Tab** does not auto-repeat into a firehose of POSTs (pressed action only fires on key press, not echo).
- [ ] **E** (NEO-9 interact) and **WASD** (NEO-22 move) still work independently of the target lock; having a lock does not block interact or movement.
## 9. Coexistence with movement rejection (NEO-19 / move-stream 400)
- [ ] Walk at the orange `MoveRejectPedestal` (~(7.5, 6.5)) while holding a lock. When `move-stream` returns **400** (`vertical_step_exceeded`), `PositionAuthorityClient` runs a boot-style re-sync which emits `authoritative_position_received`.
- [ ] HUD's `MoveRejectLabel` shows the rejection message as before (NEO-19).
- [ ] `TargetSelectionClient` treats that snap like any other: if a lock is held, it fires at most one refresh GET (subject to cooldown). No duplicate requests, no stuck busy state.
## 10. Server-down / transport failure
- [ ] Stop the server. Press **Tab** / **Esc**. Godot **Output** prints `TargetSelectionClient: HTTP failed …` warnings; the client is not wedged — you can press again (busy flag is released on failure).
- [ ] Restart the server and confirm the next **Tab** succeeds; HUD updates from the response.
## 11. No regressions
- [ ] Boot snap still works (NEO-7).
- [ ] WASD locomotion + `move-stream` still works (NEO-22); `PlayerPositionLabel` updates every physics tick.
- [ ] `E` interact with the prototype terminal still prints `allowed=true/false` in Output (NEO-9).
- [ ] Camera zoom / occluder obstacle toggle (Ctrl+Shift+K) still work.
- [ ] No new **red** lines in Godot Output during a typical 12 minute session. Benign warnings from other systems are fine; nothing from `TargetSelectionClient` during normal play.
---
When sections 110 are ticked and 11 shows no regressions, the story is ready for **In Test** / PR. Record any deviations or follow-ups in the **Decisions** or **Open questions** sections of the [implementation plan](../plans/NEO-24-implementation-plan.md).