neon-sprawl/docs/plans/NS-24-implementation-plan.md

6.3 KiB

NS-24 — Implementation plan

Story reference

Field Value
Key NS-24
Title Godot client: eliminate CharacterBody3D idle / rest vibration
Jira NS-24
Parent NS-20 — Tech Debt
Related NS-23 (path-follow baseline; idle jitter explicitly deferred here)

Goal, scope, and out-of-scope

Goal: Stable rest pose — no perceptible micro-vibration when horizontal velocity is zero, there is no active walk goal, and the avatar stands on walkable geometry, including prototype floors and NS-19 stepped bumps.

In scope

  • Diagnose and fix (or materially reduce) idle jitter on the prototype CharacterBody3D player driven by player.gd (move_and_slide, floor interaction, NavigationAgent3D coupling as relevant).
  • Preserve NS-23 behavior: walking, arrival at goal, snap_to_server on boot, descend bypass tradeoff, and NS-19 validation UX on the server side (client must not regress those flows).
  • Document non-obvious fixes in client/README.md and/or the player.gd header (per Jira).

Out of scope

  • Full prediction/reconciliation, server-side nav, or redesign of MoveCommand / PositionAuthorityClient contracts.
  • Replacing the capsule with a different locomotion stack unless a minimal inspector tweak is sufficient.

Acceptance criteria checklist

  • At rest (no click-to-move goal, feet on floor), no visible vibration in the default prototype scene for several seconds of observation.
  • Walking, arrival, and snap_to_server still behave correctly; no regression on NS-19 bumps or the NS-23 obstacle / descend-bypass tradeoff documented in client/README.md.
  • If a scene or project setting change is required, it is checked in and called out for designers (README or short comment in scene notes).

Resolution (shipped)

Jolt Physics + physics_interpolation off; QA bumps on separate StaticBody3D bodies (no floor+bump compound internal edges); idle floor_max_angle loosened on rim/straddle; rim settle + random_floor_bump_mesh lip/wall escape; NavigationAgent3D.avoidance_enabled false, floor_block_on_wall true; no global_transform in _process; visual capsule mesh Y lift vs collision to reduce z-fight with bump tops. Shared bump collider fudge: client/scripts/random_floor_bump_collision_constants.gd.

Technical approach

  1. Reproduce and isolate in Godot 4.x editor: Remote Transform on the player (and camera parent if suspected), Visible Collision Shapes, and frame / physics profiler to see whether jitter is position, floor snap, or render-only.
  2. CharacterBody3D / physics — iterate on documented levers: move_and_slide at idle with floor_snap_length = 0; safe_margin; compound Floor collision; physics/3d/physics_engine (Jolt Physics vs Godot Physics — Jolt targets internal-edge / compound-shape jitter); floor_stop_on_slope, motion_mode, up_direction if still needed.
  3. Navigation agent — confirm NavigationAgent3D target updates at rest (set_target_position(global_position) on clear/arrive) do not trigger path churn each frame; adjust only if measurements show agent-driven motion.
  4. Camera / parenting — rule out camera or child node noise if transform root is stable.
  5. Ship the smallest change that clears the AC (code, scene, or project), then update client/README.md to remove or narrow the “Known issue” idle jitter line and describe the chosen pattern.

Shipped note: Idle uses non-zero floor_snap_length (see player.gd) rather than zero snap from the investigation list; straddle/rim logic and bump geometry split were the main levers. Code review: docs/reviews/2026-04-05-NS-24.md.

Files to add

Path Purpose
client/scripts/random_floor_bump_collision_constants.gd Single source for QA bump CylinderShape3D radius fudge vs mesh and RANDOM_FLOOR_BUMP_MESH_GROUP (shared by random_floor_bumps.gd and player.gd lip escape).

Files to modify

Path Rationale
client/scripts/player.gd Idle move_and_slide policy, walking snap, arrival path, bump lip escape (imports random_floor_bump_collision_constants.gd).
client/scenes/main.tscn Player CharacterBody3D / NavigationAgent3D inspector properties, collision layers, or nav region only if diagnosis requires it (AC: designer-visible changes documented).
client/project.godot Physics or project defaults (e.g. physics ticks, 3D defaults) only if required and safe for the whole project.
client/README.md Replace Known issue: idle / rest jitter with the fix summary and any manual verification deltas.

Tests

Action What to cover
Manual (required) Run main scene with server as in NS-16/NS-23 README steps: stand idle 10+ s at spawn and after a move; click-move including stepped bumps (NS-19 props); cold boot snap_to_server; far/vertical rejects still show NS-19 UX; optional multi-click around Obstacle per NS-23 tradeoff.
Automated None — no Godot/GDScript test harness in repo; server xUnit suites unchanged unless an unrelated regression appears (not expected).

Open questions / risks

Resolved for prototype QA: Primary causes were compound internal edges (mitigated by per-bump bodies), physics/render artifacts (interpolation, CharacterBody3D transform desync, z-fight), and rim/wall contacts (mitigated by idle policy + small escapes). Remaining: _snap_capsule_upright() uses full identity basis — when yaw facing is added, preserve Y rotation only (see TODO in player.gd). Moving platforms / uneven nav mesh not validated.

PR / review

Cross-check E1.M1 — InputAndMovementRuntime, docs/plans/NS-23-implementation-plan.md (residual polish pointer), and client/README.md after changes.