6.4 KiB
Review — NEO-24
Date: 2026-04-21
Scope: NEO-24-e1m3-client-tab-target-lock-ui-synced-to-server (origin/main...HEAD)
Base: origin/main (merge-base 37f44a494400f4fdd369077c52dfc012df73a5d5)
Follow-up: blocking issue + suggestions below are done (strikethrough + Done.). See the NEO-24 plan Decision 3 for the signal change.
Verdict
Request changes — Addressed. Blocking issue fixed; integration test added; doc-alignment row updated.
Summary
This branch adds the client-side target selection node, HUD label, input bindings, tests, and story docs for NEO-24. The overall direction matches the E1.M3 plan well: selection intent is POSTed to the server, the HUD paints only server-acknowledged target state, and denial handling is intentionally authoritative.
The main risk is that the advertised movement-driven soft-lock refresh is not actually wired to normal locomotion. The new unit suite passes, but it only proves TargetSelectionClient reacts correctly when its refresh hook is called directly; it does not cover the real signal behavior of PositionAuthorityClient, so the branch can merge with a broken end-to-end acceptance criterion.
Documentation checked
docs/plans/NEO-24-implementation-plan.md— conflicts. Decision 2 and the acceptance criteria require movement-triggered refresh while a lock is held, but the current wiring only refreshes on boot sync / rejection resync, not on ordinary movement.docs/manual-qa/NEO-24.md— conflicts. Section 4 expects walking out of range to flip HUD validity within ~250 ms during normal locomotion; that flow is not reachable with the current signal source.docs/decomposition/modules/E1_M3_InteractionAndTargetingLayer.md— matches for server-authoritative target state and denial reconciliation.docs/decomposition/modules/client_server_authority.md— matches for client-as-intent / server-as-truth behavior.docs/decomposition/modules/module_dependency_register.md— matches.E1.M3remainsIn Progress.docs/decomposition/modules/documentation_and_implementation_alignment.md— partially matches. TheE1.M3tracking row still says client tab-target is open; if/when this story lands, that snapshot should be updated to reflect NEO-24 progress.
Blocking issues
-
The movement-driven refresh path described by the plan, README, and manual QA is not reachable during normal locomotion.Done. Added a separatemain.gdconnectsTargetSelectionClient.on_authoritative_position_snap()toPositionAuthorityClient.authoritative_position_received, butposition_authority_client.gdonly emits that signal from_emit_position_from_response()duringsync_from_server()and the move-rejection recovery path. Successfulmove-streamPOSTs explicitly do not emit the signal, so walking around with a held lock never triggers the throttledGET /targetrefresh that NEO-24 depends on. As a result, the HUD can stay stuck atValidity: okafter the player walks out of range, which breaks the plan's Decision 2, the acceptance criterion for soft-lock refresh, and manual QA section 4.authoritative_ack(world)signal onPositionAuthorityClientthat fires on bothBOOT_GET200 and successfulmove-stream200 (extracted a shared_parse_world_from_responsehelper).authoritative_position_receivedkeeps its current snap-only semantics so rubber-band suppression is untouched.TargetSelectionClient.on_authoritative_ack(world)replaceson_authoritative_position_snap; cooldown + lock gate unchanged.main.gdconnects to the new signal. Seeclient/scripts/position_authority_client.gd,target_selection_client.gd,main.gd, and plan Decision 3.Relevant behavior in
client/scripts/position_authority_client.gd:match _phase: Phase.BOOT_GET: _busy = false if response_code == 200: _emit_position_from_response(text, true) Phase.STREAM_POST: ... if response_code == 200: # Do not emit `authoritative_position_received` here. passMeanwhile
client/scripts/target_selection_client.gdonly refreshes from that signal:func on_authoritative_position_snap(_world: Vector3, apply_as_snap: bool) -> void: if not apply_as_snap: return if not _has_lock(): return ... request_sync_from_server()The fix needs to either emit a suitable movement/reconciliation signal during normal locomotion or hook the target refresh to another authoritative movement event that actually occurs while moving.
Suggestions
Add an integration-level test for the real movement/targeting wiring. The currentDone. Addedclient/test/target_selection_client_test.gdsuite callson_authoritative_position_snap()directly, which is why it misses the broken connection toPositionAuthorityClient. A focused test aroundmain.gdwiring orPositionAuthorityClientsignal behavior would prevent this regression.client/test/target_refresh_on_locomotion_test.gd: wires a realPositionAuthorityClientandTargetSelectionClientwith mock transports and the sameauthoritative_ack→on_authoritative_ackconnectionmain.gduses. Verifies (a) amove-stream200 with a held lock produces exactly one throttledGET /targetand the cached state flips toout_of_range, and (b) the same path is a no-op when no lock is held. Also addedtest_sync_boot_get_200_emits_authoritative_ackandtest_move_stream_post_200_emits_authoritative_acktoposition_authority_client_test.gdso renames on either side will break the per-script suites too.UpdateDone. E1.M3 row now reflects NEO-24 landed (HUD +docs/decomposition/modules/documentation_and_implementation_alignment.mdif this story lands. ItsE1.M3snapshot still says client tab-target is open, which will be stale after merge.authoritative_ackrefresh) with links to the plan and manual QA file.
Nits
None.
Verification
- Ran
godot --headless --import --path . --quit-after 10fromclient/. - Ran
godot --headless --path . -s res://addons/gdUnit4/bin/GdUnitCmdTool.gd --ignoreHeadlessMode -a res://test/target_selection_client_test.gdfromclient/— suite passed. - I did not run the full manual client/server checklist, but
docs/manual-qa/NEO-24.mdsection 4 is the critical end-to-end check to rerun after fixing the movement refresh trigger.