9.5 KiB
NS-23 — Implementation plan
Story reference
| Field | Value |
|---|---|
| Key | NS-23 |
| Title | E1.M1: Client path-follow baseline (navigation + visible locomotion) |
| Jira | NS-23 |
| Parent context | NS-10 — E1.M1 InputAndMovementRuntime · NS-1 — Epic 1 · Slice 1 — Movement and position sync |
| Decomposition | E1.M1 — InputAndMovementRuntime |
Delivery: Ship docs/plans/NS-23-implementation-plan.md on the same branch / PR as the NS-23 implementation (plan + code together), matching the NS-19 pattern.
Goal, scope, and out-of-scope
Goal: Deliver visible click-to-move: the avatar follows a path toward the clicked point using a navigation mesh baseline (obstacle-aware), not only steering at a goal or snapping on server ack alone. Aligns with E1.M1: path-follow baseline under server authority.
In scope
- Godot:
NavigationRegion3D(baked mesh) over the prototype walkable floor inmain.tscn, respecting existing static obstacles;NavigationAgent3D(on player or child node) drivingCharacterBody3Dmotion (move_and_slidetoward next path position — follow Godot 4.x recommended pattern). - Pick integration: Reuse
ground_pick.gd(walkablegroup, upward normal); clicked point becomesNavigationAgent3D.target_position(after any needed Y / agent height adjustment consistent with current capsule). - Server authority (unchanged contract): One
MoveCommandper successful pick with the same world target as today (NS-16); server v1 snap + NS-19 validation unchanged.
Out of scope (per Jira)
- Full prediction/reconciliation, MMO-grade netcode, server-side nav mesh validation.
- Replacing
InteractionRequest/ range rules.
Locked — client nav vs server authority (this slice)
| Topic | Decision |
|---|---|
| When to POST | Once per click, immediately with the picked destination (same JSON as today). No waypoint streaming to the server in this story. |
| Server truth | Still snap to target on 200 after validation; GET /position after POST remains the reconciliation read (existing PositionAuthorityClient flow). |
| Straight-line vs path | NS-19 validates displacement from current authoritative position to target (horizontal + vertical limits). It does not check path clearance through geometry; client nav is responsible for visible obstacle avoidance. |
| After successful move | Do not teleport the avatar on the verifying GET when the player is expected to walk: apply the authoritative position as a navigation goal and let the agent complete the path; initial boot sync_from_server remains a hard snap so spawn matches the server. |
| POST 400 | Existing behavior: emit move_rejected, show NS-19 UX; do not start (or clear) a nav path to that target. |
| Distance limits | Default MaxHorizontalStep (e.g. 18 m) remains compatible with single-command long clicks across the prototype; if a future scene needs shorter steps, chain commands or tune limits in a later story. |
Acceptance criteria checklist
- Clicking a valid floor target produces continuous motion along a plausible path around static obstacles in the prototype scene (not sliding through walls).
- Authoritative position still converges with the server (
MoveCommand+GET); no regression on NS-19 rejection UX (reasonCode/ label timeout). - Short note in
server/README.mdand/or client script header: client nav vs server authority for this slice.
Technical approach
-
main.tscn/ navigation- Add
NavigationRegion3Dcovering walkable floor; include obstacleStaticBody3Dgeometry in baking as required by Godot 4 so paths wrap obstacles. - Bake after layout changes; document any editor step in
client/README.mdif bake is not fully reproducible from scene alone.
- Add
-
Player / agent
- Attach
NavigationAgent3D; replace NS-14-style direct steering toward_goalwith path following (velocity towardget_next_path_position()or equivalent,move_and_slide). - Keep
snap_to_serverfor boot and any hard reconcile case you explicitly document (default: boot only).
- Attach
-
Wiring (
main.gd/PositionAuthorityClient)- On
target_chosen: callsubmit_move_targetas today (POST → GET). - Distinguish authoritative position from boot vs after successful move so
main.gdsnaps on boot but sets nav target (no snap) after verify when implementing path-follow. - Optional small API: second signal, enum on existing signal, or
sync_from_server-only snap path — pick the smallest change that stays readable per godot-client-script-organization.
- On
-
Documentation
client/README.md: Update movement section: navmesh path-follow + server authority one-liner.server/README.md: One short subsection or bullet: server validates end target and step; client nav is presentational for obstacle following in this slice.
Resolved — agent shape vs capsule (was risk)
Prototype player collider in main.tscn: CapsuleShape3D_player — radius = 0.4, height = 1.0.
| Setting | Locked choice |
|---|---|
NavigationAgent3D.radius / height |
Match the physics capsule (0.4 / 1.0) so nav clearance matches what move_and_slide actually sweeps. |
| Navmesh bake agent | In NavigationRegion3D bake settings, use the same radius/height (or Godot’s equivalent agent parameters) so baked polygons match runtime pathfinding. |
| Corner clipping vs “stuck” | If the mesh clips into box corners, increase agent radius slightly and rebake (wider corridor on the mesh) before shrinking physics. Do not silently shrink the capsule without an explicit design change. |
| Vertical alignment | Keep path_height_offset (or equivalent) at 0 unless the capsule foot position and floor snap misalign; the pick already supplies floor Y. |
| Arrival | Align target_desired_distance (and related agent tolerances) with existing player ARRIVE_EPS (~0.35 m) so stop distance matches prior steering feel unless QA says otherwise. |
Resolved — navigation sync / frame order (was risk)
| Topic | Locked choice |
|---|---|
| Where motion runs | Path queries (get_next_path_position, velocity toward next point) and move_and_slide run only in Player._physics_process, not in _process or UI thread. |
When target_position is set |
main.gd (or signal handler) may set the agent target in response to target_chosen / authority callbacks; the first consumable segment appears after the navigation map updates. |
| First frame after new target | If NavigationAgent3D.is_navigation_finished() or a zero-length first step appears on the same frame as target_position assignment, defer one tick: await get_tree().physics_frame once after setting target or simply rely on next _physics_process (preferred — avoid await in hot path). Do not poll path from _input without deferral. |
| Map readiness | After adding NavigationRegion3D, ensure the region is baked and enabled before F5; startup sync_from_server snap does not need a path. |
Files to add (expected)
| Path | Purpose |
|---|---|
| None required by name | Prefer extending existing player.gd / scene unless a dedicated player_navigation.gd reduces main.gd bloat. |
Files to modify
| Path | Rationale |
|---|---|
client/scenes/main.tscn |
NavigationRegion3D, bake source meshes, agent node setup. |
client/scripts/player.gd |
Path-follow via NavigationAgent3D; clarify header (NS-14 → NS-23 evolution). |
client/scripts/main.gd |
Boot snap vs post-move nav goal wiring. |
client/scripts/position_authority_client.gd |
If needed: signal shape or phase so main can snap only on boot. |
client/README.md |
Manual check steps for path-follow + server. |
server/README.md |
Client nav vs authority note (AC). |
docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md |
Implementation snapshot when NS-23 ships. |
Tests
| Action | What to cover |
|---|---|
| Manual (required) | Server running; click around obstacles — path goes around; click NS-19 reject targets — same rejection behavior as before; cold start — player matches server position. |
| Automated | None required for this story unless an existing headless hook can assert nav baking (unlikely); prefer manual for Godot nav. |
Open questions / risks
None — agent/bake alignment and physics-frame path consumption are locked in Resolved — agent shape vs capsule and Resolved — navigation sync / frame order above. Reopen only if the scene scale or avatar collider changes materially.
PR / review
Cross-check E1.M1, client README, and server README. Confirm NS-19 props in main.tscn still behave after nav region changes.