From ec3c0ee4fedbeb047442c74a8b151adfc0aed146 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sun, 5 Apr 2026 00:09:01 -0400 Subject: [PATCH] NS-23: Add implementation plan and E1.M1 snapshot for path-follow kickoff --- .../modules/E1_M1_InputAndMovementRuntime.md | 1 + docs/plans/NS-23-implementation-plan.md | 98 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 docs/plans/NS-23-implementation-plan.md diff --git a/docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md b/docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md index 5fd2062..1286d74 100644 --- a/docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md +++ b/docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md @@ -34,6 +34,7 @@ Contract readiness is tracked in the [module dependency register](module_depende ## Implementation snapshot - **Done (prototype):** Server-side authoritative **`PositionState`** read + **`MoveCommand`** apply (HTTP JSON v1, snap-to-target, `sequence` increments); **NS-19** server-side step + optional district bounds validation with **`reasonCode`** rejections ([NS-19](../../plans/NS-19-implementation-plan.md), `MoveCommandValidation`, [server README — Move command](../../../server/README.md#move-command-ns-16-ns-19)); default **in-memory** store; optional **PostgreSQL** persistence when `ConnectionStrings:NeonSprawl` is set ([NS-17](../../plans/NS-17-implementation-plan.md)); Godot client submits move and snaps to server after GET ([NS-15](../../plans/NS-15-implementation-plan.md), [NS-16](../../plans/NS-16-implementation-plan.md), `server/NeonSprawl.Server/Game/PositionState/`, `client/scripts/`). **`InteractionRequest`** + server-side horizontal range check ([NS-18](../../plans/NS-18-implementation-plan.md); `Game/Interaction/`, `Game/World/HorizontalReach.cs`, [server README — Interaction](../../../server/README.md#interaction-ns-18)). See [server README — Position persistence](../../../server/README.md#position-persistence-ns-17). +- **In progress:** **NS-23** — client **`NavigationRegion3D` / `NavigationAgent3D`** path-follow baseline while keeping **`MoveCommand`** / **`GET`** authority ([NS-23](../../plans/NS-23-implementation-plan.md)). - **Not yet:** Prediction/reconciliation, full Epic 1 Slice 1 movement loop and telemetry. - **Alignment:** [Documentation and implementation alignment](documentation_and_implementation_alignment.md). diff --git a/docs/plans/NS-23-implementation-plan.md b/docs/plans/NS-23-implementation-plan.md new file mode 100644 index 0000000..ce159be --- /dev/null +++ b/docs/plans/NS-23-implementation-plan.md @@ -0,0 +1,98 @@ +# NS-23 — Implementation plan + +## Story reference + +| Field | Value | +|--------|--------| +| **Key** | NS-23 | +| **Title** | E1.M1: Client path-follow baseline (navigation + visible locomotion) | +| **Jira** | [NS-23](https://neon-sprawl.atlassian.net/browse/NS-23) | +| **Parent context** | [NS-10 — E1.M1 InputAndMovementRuntime](https://neon-sprawl.atlassian.net/browse/NS-10) · [NS-1 — Epic 1](https://neon-sprawl.atlassian.net/browse/NS-1) · Slice 1 — Movement and position sync | +| **Decomposition** | [E1.M1 — InputAndMovementRuntime](../decomposition/modules/E1_M1_InputAndMovementRuntime.md) | + +**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 in `main.tscn`, respecting existing static obstacles; **`NavigationAgent3D`** (on player or child node) driving **`CharacterBody3D`** motion (`move_and_slide` toward next path position — follow Godot 4.x recommended pattern). +- **Pick integration:** Reuse **`ground_pick.gd`** (`walkable` group, upward normal); clicked point becomes **`NavigationAgent3D.target_position`** (after any needed Y / agent height adjustment consistent with current capsule). +- **Server authority (unchanged contract):** One **`MoveCommand`** per successful pick with the **same world target** as today ([NS-16](NS-16-implementation-plan.md)); server **v1 snap** + **[NS-19](NS-19-implementation-plan.md)** 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.md`** and/or **client script header**: client nav vs server authority for this slice. + +## Technical approach + +1. **`main.tscn` / navigation** + - Add **`NavigationRegion3D`** covering walkable floor; include obstacle **`StaticBody3D`** geometry in baking as required by Godot 4 so paths wrap obstacles. + - Bake after layout changes; document any **editor step** in `client/README.md` if bake is not fully reproducible from scene alone. + +2. **Player / agent** + - Attach **`NavigationAgent3D`**; replace NS-14-style **direct steering toward `_goal`** with **path following** (velocity toward `get_next_path_position()` or equivalent, `move_and_slide`). + - Keep **`snap_to_server`** for **boot** and any **hard reconcile** case you explicitly document (default: boot only). + +3. **Wiring (`main.gd` / `PositionAuthorityClient`)** + - On **`target_chosen`**: call **`submit_move_target`** as today (POST → GET). + - Distinguish **authoritative position from boot** vs **after successful move** so **`main.gd`** snaps 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](../../.cursor/rules/godot-client-script-organization.md). + +4. **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. + +## 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 + +- **Agent radius / height** vs capsule: mis-tuning causes clipping or “stuck”; match **`CollisionShape3D`** to agent parameters. +- **Frame ordering:** `NavigationServer` sync — follow Godot docs (`await get_tree().physics_frame` if required before first path query). + +## PR / review + +Cross-check [E1.M1](../decomposition/modules/E1_M1_InputAndMovementRuntime.md), [client README](../../client/README.md), and [server README](../../server/README.md). Confirm NS-19 props in `main.tscn` still behave after nav region changes.