11 KiB
NEO-11 — Implementation plan
Story reference
| Field | Value |
|---|---|
| Key | NEO-11 |
| Title | E1.M1: Client path-follow baseline (navigation + visible locomotion) |
| Linear | NEO-11 |
| Parent context | E1.M1 InputAndMovementRuntime · Epic 1 · Slice 1 — Movement and position sync |
| Decomposition | E1.M1 — InputAndMovementRuntime |
Delivery: Ship docs/plans/NEO-11-implementation-plan.md on the same branch / PR as the NEO-11 implementation (plan + code together), matching the NEO-10 pattern.
Goal, scope, and out-of-scope
Goal: Deliver visible click-to-move: the avatar moves toward the verified server target using NavigationAgent3D + a baked mesh where applicable, instead of teleporting on the verifying GET. Aligns with E1.M1: path-follow baseline under server authority.
In scope
- Godot:
NavigationRegion3D(baked mesh) over the prototype walkable floor inmain.tscn(obstacle geometry included in bake for future use);NavigationAgent3Don the player drivingCharacterBody3Dmotion (move_and_slidetoward next path position when the client is following the mesh — Godot 4.x 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 (NEO-7); server v1 snap + NEO-10 validation unchanged.
Out of scope (per Linear)
- 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 | NEO-10 validates displacement from current authoritative position to target (horizontal + vertical limits). It does not check path clearance through geometry. Client may follow nav waypoints when the descend bypass is inactive; automatic obstacle detours are not required for this slice (see Locked — prototype movement tradeoff). |
| 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 NEO-10 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. |
Locked — prototype movement tradeoff
| Topic | Decision |
|---|---|
| Uneven surfaces vs obstacles | The client uses a descend bypass in player.gd: when the authoritative goal Y is below the body origin (typical for floor picks vs mid-capsule), steering goes straight in xz toward the goal instead of following nav waypoints first. This keeps stepped bumps smooth; it does not guarantee routing around tall static obstacles on a single click. |
| Player expectation | Multi-click (or chained moves) is acceptable to navigate around geometry the client would otherwise bee-line into. No automatic obstacle navigation is required for NEO-11 closure. |
| Residual polish | Visible idle jitter at rest is tracked as tech debt — NEO-14 (not a blocker for this story). |
Acceptance criteria checklist
- Clicking a valid floor target produces continuous motion toward the verified target without sliding through geometry in the common case; navmesh waypoints are used when the descend bypass does not apply. Obstacle detours on one click are not required (tradeoff above).
- Authoritative position still converges with the server (
MoveCommand+GET); no regression on NEO-10 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 NEO-5-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: Movement section: nav agent + server authority, and the uneven-surface vs obstacle tradeoff (multi-click around obstacles).server/README.md: Server validates end target and step; client nav is presentational only (no server navmesh); obstacle detours not guaranteed.
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 (NEO-5 → NEO-11 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 NEO-11 ships. |
Tests
| Action | What to cover |
|---|---|
| Manual (required) | Server running; click-move toward targets (including stepped bumps); NEO-10 reject targets — same rejection behavior as before; cold start — player matches server position. Optional: confirm multi-click can route around the prototype Obstacle when a single click bee-lines. |
| Automated | None required for this story unless an existing headless hook can assert nav baking (unlikely); prefer manual for Godot nav. |
Open questions / risks
Prototype tradeoff is explicit under Locked — prototype movement tradeoff (bumps vs single-click obstacle routing). Agent/bake alignment and physics-frame path consumption remain locked in Resolved — agent shape vs capsule and Resolved — navigation sync / frame order. Reopen if scene scale or avatar collider changes materially.
PR / review
Cross-check E1.M1, client README, and server README. Confirm NEO-10 props in main.tscn still behave after nav region changes.