9.2 KiB
9.2 KiB
NEO-22 — Implementation plan
Story reference
| Field | Value |
|---|---|
| Key | NEO-22 |
| Title | Retire prototype click-to-move; ship WASD locomotion on the client |
| Linear | NEO-22 |
| Parent | E1.M1 InputAndMovementRuntime (or current movement project) |
| Supersedes / deprecates (prototype) | NEO-5, NEO-11 click-to-move surface; NEO-14 seam/jitter mitigations tied to nav + goal steering |
Goal, scope, and out-of-scope
Goal: Replace mouse click → ground pick → MoveCommand → nav path → player.gd goal steering with keyboard WASD (and optional gamepad later) as the primary locomotion input in the Godot prototype. Movement should feel predictable on stepped geometry without the column / nav-ribbon / slide-normal fight that click-to-move accumulated.
In scope
- Input:
project.godotactions (or code) for W/A/S/D; read them in a thin place (main.gdor a smalllocomotion_input.gd) and drive the player’s horizontal intent each physics tick. - Locomotion model:
CharacterBody3D+move_and_slide()with velocity derived from wish direction × speed, camera-relative or world-relative (decide in Decisions); noNavigationAgent3Dpath goal for routine walking unless a later slice reintroduces hybrid “click to mark + auto-run” explicitly. - Remove / disable left-click ground pick as the default move trigger (
ground_pick.gd→main.gd→PositionAuthorityClient.submit_move_target). Either delete the flow for this slice or gate it behind a dev flag documented as off by default. - Server / authority: Resolve how WASD maps to NEO-7
MoveCommandand authoritative position (see Open questions). Minimum acceptable outcomes are spelled in acceptance criteria — pick one path and document it inclient/README.md+ decomposition. - Cleanup: Delete or drastically narrow NEO-11 / NEO-14 / NEO-16 workaround blocks in
player.gd(column seam damp, nav column steering, idle trace scaffolding tied to click goals) only after WASD path is stable; prefer one commit that switches input then a follow-up that deletes dead code. - Docs: Update
client/README.md,docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md, and this plan’s checklist when shipped.
Out of scope
- Full client prediction + server reconciliation for FPS-grade netcode (unless explicitly pulled into this story by product).
- Click-to-interact changes (NEO-12-style interaction) except where click conflicts with removed move binding (document if unchanged).
- Occlusion / camera feature work (NEO-17/20) beyond what’s needed so WASD direction feels correct with the follow camera.
- Production navmesh tooling for NPCs — only player move input changes here.
Acceptance criteria checklist
- WASD moves the avatar on the default
main.tscnfloor with no left-click requirement; speed and direction are stable on flat ground and on stepped QA props without the prior ±MOVE_SPEEDseam oscillation at column/stair corners (subjective + optionaldebug_idle_tracespot-check). - Left-click no longer starts a walk by default (or is clearly dev-only and off in normal play, documented).
- Authoritative position still converges with the server: document whether continuous sync, throttled
MoveCommand, or temporary client-only prototype applies — and add the minimal client/server change so CI + manual steps do not lie. - Cold boot
snap_to_serverstill works; no spawn underground / stale goal regressions. - NEO-10 rejection UX: if clicks are removed, another path still exercises reject payloads in dev (automated test or documented console-only command) or clicks remain only for “set goal” debug, not default locomotion.
- GdUnit (or existing harness) covers at least one pure helper for wish-dir / speed clamp if logic is non-trivial.
Technical approach
- Linear + branch: Issue NEO-22 exists in Backlog; on kickoff set In Progress and branch
neo-22-retire-click-to-move-implement-wasd-locomotion-godot-client(or team naming). - Authority model (blocking): Decide with product/server owner:
- A) Client integrates WASD locally only for prototype; server receives rate-limited position or move deltas (may need new endpoint — likely out of this slice unless already planned), or
- B) Keep discrete
MoveCommandbut drive target from keyboard-sampled “stick to last intent” (awkward), or - C) Server accepts a stream of small moves (largest change — schedule separately).
Document chosen option in README before merging locomotion feel work.
- Input layer: Map WASD to
Vector2in screen/camera space → world XZ tangent toCamera3Dbasis (same pattern as many third-person controllers); normalize; zero when no keys. player.gdrefactor: Replace_has_walk_goal/_auth_walk_goalsteering path with wish velocity from parent or injected each_physics_process; keep gravity, floor snap policy, andsnap_to_serverentry points. StripNavigationAgent3Ddependency for default walk if decision says so (agent node can stay in scene unused until a later hybrid story).- Remove click wiring: Disconnect
target_chosen→submit_move_targetinmain.gdfor default build; optionally keepground_pick.gdfor debug “teleport goal” behindOS.is_debug_build()flag. - Tests + manual: Run README manual path; add unit test for direction math if extracted.
Decisions
| Topic | Choice | Rationale |
|---|---|---|
| Issue id | NEO-22 | Created in Linear 2026-04-16; plan filename matches this key. |
| Camera vs world steering | TBD in kickoff — default recommendation: camera-relative XZ | Matches isometric follow rig; document if world-axis is chosen instead. |
| NavAgent | Default off for routine WASD in this slice | Removes nav-ribbon + column latch class of bugs; re-add only with explicit hybrid design. |
Files to add
| Path | Purpose |
|---|---|
| None required. | Prefer extending existing scripts unless locomotion_input.gd (or similar) is needed to keep main.gd thin per godot-client-script-organization. If extracted, add here and list in “Files to modify” as N/A for that line. |
Files to modify
| Path | Rationale |
|---|---|
client/project.godot |
Declare move_forward / move_back / move_left / move_right (or reuse Godot defaults) bound to WASD. |
client/scripts/main.gd |
Stop wiring click pick to submit_move_target by default; optionally forward WASD intent to player/authority per chosen model. |
client/scripts/ground_pick.gd |
Disable default _input pick or gate behind export/debug; update header comments (NS-16 lineage). |
client/scripts/player.gd |
Replace goal/nav horizontal steering with WASD-driven velocity; delete obsolete seam/nav state once stable. |
client/scripts/position_authority_client.gd |
Align with authority model (throttle, stream, or document client-only prototype). |
client/scenes/main.tscn |
Only if NavigationAgent3D or nodes are removed/changed or input singleton paths change. |
client/README.md |
Replace “click-move” manual checks with WASD + server steps; note deprecation of click-to-move prototype. |
docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md |
Snapshot: click-to-move retired in favor of WASD for prototype; link this plan. |
docs/plans/NEO-11-implementation-plan.md |
Add one-line “superseded for locomotion input by NEO-22” in header or Resolution if policy is to annotate predecessors. |
Tests
| File | Coverage |
|---|---|
client/test/player_test.gd |
Change: remove or replace tests that assumed _has_walk_goal / nav steering; add tests for any extracted wish-direction or speed clamp helper. |
client/test/position_authority_client_test.gd |
Change: align with new submit/sync cadence if PositionAuthorityClient changes. |
| Manual (required) | README path: boot with server, WASD on flat + stepped geometry, snap_to_server, and whichever NEO-10 rejection path remains after click removal. |
Open questions / risks
- Server contract: Today’s
MoveCommandis discrete target-shaped (NEO-7). WASD needs continuous intent — either extend the protocol, rate-limit many small commands, or temporarily diverge prototype authority. This is the main schedule risk; do not merge feel-only client changes without an explicit written decision in README + this plan. - Interaction vs pick: Confirm E / interaction does not rely on the same mouse button in a way that breaks when pick is disabled.
- Gamepad: Out of scope unless trivial (
Input.get_vector); note for follow-up story. - Jira naming: If the team uses NEON-* keys elsewhere, align Linear/Git subject lines per linear-git-naming.