7.6 KiB
7.6 KiB
NEO-24 — Manual QA checklist
| Field | Value |
|---|---|
| Key | NEO-24 |
| Title | E1.M3: Client tab-target + lock UI synced to server |
| Linear | NEO-24 |
| Plan | docs/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:5253on boot;GET /healthreturns JSON. - In Godot, select
TargetSelectionClientin the scene tree:base_urlmatches the server URL anddev_player_idmatchesGame:DevPlayerId(defaultdev-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, underPlayerPositionLabel) shows:Target: —Validity: noneSeq: 0prototype_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 singleGET /game/players/dev-local-1/targetfrom the boot sync.
2. Tab cycle — happy path
- Press Tab. HUD flips to:
Target: prototype_target_alphaValidity: okSeq: 1
- Server log shows
POST /game/players/dev-local-1/target/selectwith{"schemaVersion":1,"targetId":"prototype_target_alpha"}. - No
Denied:line is appended to the HUD (success ⇒ noreasonCode).
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: 1withDenied: out_of_rangeappended. 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→200withselectionApplied: 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 nextmove-stream200 (each successful POST emitsauthoritative_ack→ throttled targetGET), HUD flips to:Target: prototype_target_alpha(unchanged — soft lock)Validity: out_of_rangeSeq: 1(unchanged — no lock id change)
- Walk back inside the radius. HUD returns to
Validity: okwithin ~250 ms of the nextmove-streamack. - While walking, Godot Output is not spammed with targeting GETs. Server log shows no more than roughly one targeting
GETper 250 ms during sustained motion, even thoughmove-streamPOSTs fire at ~20 Hz. - Stand still with no lock changes. Server log shows no targeting GETs at idle (no
move-streamis 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 flipsin/outas you cross the visible orange ring edge. Any time the HUD showsValidity: okbut the distance line reads(out), the server position snapshot is lagging the visible capsule — the nextauthoritative_ackrefresh 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_betaValidity: okSeqincrements by 1
- Press Tab once more. Server denies (alpha is far away) — HUD stays at
beta / okwithDenied: out_of_rangeappended.
6. Clear
- Press Esc at any point with a lock held. HUD flips to:
Target: —Validity: noneSeqincrements by 1
- Any
Denied: …line from the previous state is gone. - Server log shows
POST /target/selectwith body{"schemaVersion":1}(notargetIdkey). - 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 showsTarget: —,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
_inputbefore 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. Whenmove-streamreturns 400 (vertical_step_exceeded),PositionAuthorityClientruns a boot-style re-sync which emits bothauthoritative_position_received(for the snap) andauthoritative_ack(for cooldown-throttled consumers). - HUD's
MoveRejectLabelshows the rejection message as before (NEO-19). TargetSelectionClienttreats 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-streamstill works (NEO-22);PlayerPositionLabelupdates every physics tick. Einteract with the prototype terminal still printsallowed=true/falsein 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
TargetSelectionClientduring 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.