neon-sprawl/docs/manual-qa/NEO-24.md

110 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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`
- `prototype_target_alpha: 0.00 m / 8.0 (in)` (spawn `(-5,-5)` is exactly on alpha's XZ anchor)
- `prototype_target_beta: 18.38 m / 4.0 (out)`
- [ ] **Visible anchors** are rendered in-world: an **orange** mast + flat ring at `(-5, 0.5, -5)` (alpha, 8 m radius) and a **violet** mast + ring at `(8, 0, 8)` (beta, 4 m radius). Both rings lie flat on the floor; the player capsule should sit inside the alpha ring at spawn.
- [ ] 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 `move-stream` 200 (each successful POST emits `authoritative_ack` → throttled target `GET`), 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 `move-stream` ack.
- [ ] 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, even though `move-stream` POSTs fire at ~20 Hz.
- [ ] Stand still with no lock changes. Server log shows **no** targeting GETs at idle (no `move-stream` is sent while idle, so there is no ack to trigger a refresh).
- [ ] **Distance lines match the ring**: while moving, the `prototype_target_alpha: <d> m / 8.0 (in|out)` line flips `in`/`out` as you cross the visible orange ring edge. Any time the HUD shows `Validity: ok` but the distance line reads `(out)`, the server position snapshot is lagging the visible capsule — the next `authoritative_ack` refresh should reconcile within 250 ms.
## 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 both `authoritative_position_received` (for the snap) and `authoritative_ack` (for cooldown-throttled consumers).
- [ ] HUD's `MoveRejectLabel` shows the rejection message as before (NEO-19).
- [ ] `TargetSelectionClient` treats that ack 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).