9.7 KiB
NEON-16 — Implementation plan
Story reference
| Field | Value |
|---|---|
| Key | NEON-16 |
| Title | Godot client: eliminate CharacterBody3D idle / rest vibration |
| Jira | NEON-16 |
| Parent | NEON-13 — Tech Debt |
| Related | NEON-8 (path-follow baseline; idle jitter explicitly deferred here) |
Goal, scope, and out-of-scope
Goal: Re-open NEON-16 to restore a stable rest pose after later camera work made the prototype jitter worse: no perceptible idle vibration on flat walkable ground, and no continuous x/z drift while the avatar has no active walk goal.
In scope
- Reproduce the current regression in the prototype scene and separate true player transform drift from camera presentation amplification.
- Diagnose and fix idle jitter in the prototype
CharacterBody3Dplayer driven byplayer.gdand/or the follow rig inisometric_follow_camera.gdwhen evidence shows the camera is making tiny corrections read as larger visible jitter. - Preserve NEON-8 behavior: walking, arrival at goal,
snap_to_serveron boot, descend bypass tradeoff, and NEON-7 validation UX on the server side (client must not regress those flows). - Add or update focused GdUnit coverage for any new pure helper or guard logic introduced by the fix.
- Document non-obvious fixes in
client/README.mdand the living plan if the final solution changes the anti-jitter pattern or camera integration expectations.
Out of scope
- Full prediction/reconciliation, server-side nav, or redesign of MoveCommand / PositionAuthorityClient contracts.
- Replacing the locomotion stack or redesigning the isometric follow camera beyond the smallest anti-jitter hardening needed for this regression.
- Product decisions about enabling camera yaw/orbit; this story only hardens the fixed-yaw prototype behavior.
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 on flat ground, and no measurable-looking x/z drift at idle.
- Walking, arrival, and
snap_to_serverstill behave correctly; no regression on NEON-7 bumps or the NEON-8 obstacle / descend-bypass tradeoff documented inclient/README.md. - If camera-follow hardening is required, it does not introduce new visible lag, framing drift, or occlusion regressions in the current E1.M2 stack.
- 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
Re-opened 2026-04-09 after NEON-25/26/27/30 follow-camera work: the earlier NEON-16 mitigation shipped, but testing now reports worse visible idle jitter even on a flat surface. The current branch treats that as real idle correction noise first, not purely a camera problem.
Implemented on this branch: when the capsule already has stable flat support (floor normal effectively upright and no post-arrival loose-floor window), player.gd now skips the corrective idle move_and_slide() / rim-escape loop entirely and keeps the body parked. The flat-idle classification was broadened so incidental extra contacts on an otherwise flat support do not re-enable the old correction loop. Stable grounded idle also keeps an explicit x/z anchor, clamping away any residual horizontal creep until movement resumes or support is no longer classified as stable. Goal arrival and the descend-bypass check now both measure against the capsule feet height instead of the body origin, which stops same-height floor clicks near obstacles from dropping into the old straight-line wall push. _snap_capsule_upright() also avoids rewriting global_transform when the basis is already identity, reducing needless transform churn during idle.
Technical approach
- Reproduce and isolate in Godot 4.x editor: Remote Transform on the player and camera rig, Visible Collision Shapes, and short idle observation on flat ground first, then random bumps, to determine whether the player root is really moving in x/z.
- CharacterBody3D / physics — inspect the current idle path in
player.gd: idlemove_and_slide(), rim settle, bump proximity escape, and upright snap. Prevent continuous idle correction on stable flat contacts while preserving the original bump / rim escape behavior that NEON-16 added for rougher geometry. - Camera / parenting — if the player still only moves by tiny amounts, harden
isometric_follow_camera.gdso the rig does not magnify sub-threshold target motion. Preferred direction: smooth or deadzone the focus used bydesired_eye_world(),look_at(), and occlusion instead of smoothing only the eye position. - Navigation agent — confirm
NavigationAgent3Drest targets (set_target_position(global_position)on clear/arrive) are not reintroducing path churn that feeds the idle correction loop. - Ship the smallest change that clears the AC, then update
client/README.mdand related docs with the final anti-jitter pattern and verification notes.
Decisions
| Topic | Choice | Rationale |
|---|---|---|
| Regression framing | Treat this as a re-opened NEON-16 fix, not a brand new camera story. | Jira already tracks idle vibration, and the new symptom is the same bug surface with later camera work as a likely amplifier. |
| Debug order | Prove whether player transform drift exists before shipping a camera-only mitigation. | A camera-only band-aid could hide a real CharacterBody3D rest-state bug. |
| Camera hardening scope | Only harden follow behavior if player-side cleanup is insufficient or if raw focus tracking is clearly amplifying tiny motion. | Keeps the fix minimal and avoids reopening broader E1.M2 follow-camera design work. |
| Flat idle strategy | Detect stable flat support and skip corrective idle movement in that state. | The regression report is specifically about persistent idle x/z drift on a flat surface, where the old rim/bump correction loop should not be necessary. |
| Stable support classification | Let a strongly upright floor normal win even when physics reports incidental extra contacts. | Flat supports were still falling back into the correction path when side contacts were reported even though the capsule should have been considered settled. |
| Residual drift containment | Keep a stable-grounded idle x/z anchor and restore it each frame while idle remains stable. | Even if the exact remaining source of horizontal creep is hard to reproduce in headless runs, this guarantees flat idle does not visibly wander once the player has settled. |
| Descend bypass framing | Treat “below the player” using the capsule feet, not the body origin. | Floor picks on the same slab always sit below the capsule origin, which was incorrectly forcing direct steering into obstacle walls and reintroducing jitter after contact. |
| Upright snap writes | Skip the upright rewrite when the basis is already Basis.IDENTITY. |
Avoids touching global_transform every idle frame when there is nothing to correct. |
Files to add
| Path | Purpose |
|---|---|
| None. | This follow-up should fit in existing movement, camera, test, and documentation files. |
Files to modify
| Path | Rationale |
|---|---|
client/scripts/player.gd |
Primary idle-motion diagnosis and fix: idle move_and_slide policy, rim/bump escape guards, and any extracted helper logic used to distinguish stable floor contact from real corrective motion. |
client/test/player_test.gd |
Add regression coverage for any new idle guard/helper logic added to player.gd. |
client/README.md |
Update the documented anti-jitter pattern and manual verification steps once the final fix is known. |
Tests
| Action | What to cover |
|---|---|
| Manual (required) | Run main scene with server as in NEON-4/NEON-8 README steps: stand idle 10+ s at spawn on flat ground, stand idle again after a move, idle on the random green bumps, click-move including stepped bumps (NEON-7 props), cold boot snap_to_server, far/vertical rejects, and NEON-30 occluder click-through sanity. |
| Automated | Updated client/test/player_test.gd with stable-idle plus capsule-feet helper coverage. Headless GdUnit run: 52 tests, 0 errors, 0 failures via Godot 4.6 and res://addons/gdUnit4/bin/GdUnitCmdTool.gd. One-off headless idle probe against main.tscn observed x_range=0 and z_range=0 across a 240-physics-frame idle sample after settle. |
Open questions / risks
- The current regression report says jitter is now worse even on flat ground, so the earlier assumption that the problem was mainly bump/rim contact may no longer be sufficient.
- A player-only fix may still leave visible follow jitter if the camera continues to aim at raw focus each render frame.
- A camera-only fix may hide real
CharacterBody3Ddrift and make future movement regressions harder to see. _snap_capsule_upright()still uses full identity basis — when yaw facing is added, preserve Y rotation only (seeTODOinplayer.gd). Moving platforms / uneven nav mesh not validated.
PR / review
Cross-check E1.M1 — InputAndMovementRuntime, docs/plans/NEON-8-implementation-plan.md (residual polish pointer), and client/README.md after changes.