From cbcb4e3d639cc6e8499cdb7ca31b62a562e0bb2a Mon Sep 17 00:00:00 2001 From: VinPropane Date: Tue, 21 Apr 2026 22:21:50 -0400 Subject: [PATCH] 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. --- docs/manual-qa/NEO-24.md | 105 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 docs/manual-qa/NEO-24.md diff --git a/docs/manual-qa/NEO-24.md b/docs/manual-qa/NEO-24.md new file mode 100644 index 0000000..7da6fc7 --- /dev/null +++ b/docs/manual-qa/NEO-24.md @@ -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 1–2 minute session. Benign warnings from other systems are fine; nothing from `TargetSelectionClient` during normal play. + +--- + +When sections 1–10 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).