Skip to content

fix: character controller drops its move on a flat floor's rounding error - #1058

Open
VirtualMaestro wants to merge 1 commit into
dimforge:masterfrom
VirtualMaestro:fix-cc-slip-rounding
Open

VirtualMaestro wants to merge 1 commit into
dimforge:masterfrom
VirtualMaestro:fix-cc-slip-rounding

Conversation

@VirtualMaestro

Copy link
Copy Markdown

Problem

A character moving over a flat floor sometimes loses all or part of its horizontal motion for one step, and drifts
along the other horizontal axis (#809: "occasionally sticks", x drift while walking along z). The same thing happens
to a character carried on a moving collider: on some steps the carry is dropped.

Cause

The shape cast returns the floor normal one ulp short of unit length: (0, 0.99999994, 0), or with a ~5e-9 tilt
(±4.8e-9, 0.99999994, ±4.8e-9).

  1. decompose_hit takes the horizontal tangent direction as normal1 × up. For a normal parallel to up that is
    zero, so the whole tangent lands in vertical_tangent. With the 5e-9 tilt it points along an arbitrary diagonal,
    so about half of the move lands there. (In 2D the horizontal tangent is always zero.)
  2. Because the normal is short, vertical_tangent keeps a downward part of about -1.9e-9.
  3. handle_slopes reads up · vertical_tangent < 0 as slipping. slipping_intent is false, so the non-slip-slope
    branch keeps horizontal_tangent + normal_part and drops vertical_tangent, i.e. the horizontal move.

Trace of the reproduction from #809 (second comment), step 24 at v0.36.0:

remaining (0, -0.0167, -0.3), hit toi 0, normal1 (-4.77e-9, 0.99999994, 4.77e-9)
horizontal_tangent (-0.15, 0, -0.15), vertical_tangent (0.15, -1.86e-9, -0.15)
slipping_intent false, slipping true -> non-slip-slope branch drops vertical_tangent
applied (-0.15, 0, -0.15): half the z move lost, 0.15 m of x drift

Fix

slipping now needs a downward part above 1e-5 of the tangent's length:

let slipping =
    self.up.dot(decomp.vertical_tangent) < -1.0e-5 * decomp.vertical_tangent.length();

The bound it introduces: a slope within 1e-5 rad (0.0006°) of horizontal no longer counts as slipping, so a character
standing on it slides by under 1e-5 of its vertical move per step. Nothing else changes.

Tests

  • New character_controller_flat_ground_keeps_horizontal_motion: a port of the 3D kinematic character jumping/jittering on flat surface #809 reproduction (capsule walking
    along z at 9 m/s on a body-less box floor, dt 1/30, 200 steps). It asserts the z move of every step within 1e-3 of
    the desired one and |x| < 1e-2. On master without the fix it fails at step 69 (0.24 instead of 0.3); with the fix
    the worst z error is 1.6e-4 and the largest |x| 1.25e-3.
  • character_controller_ground_detection passes, and its reach improves from 933.8 / 974.3 to 998.2 / 999.1 (no snap
    / snap; ideal 1000). The comment there blames "accumulated numerical error" for the shortfall; most of it was this.
  • cargo test -p rapier3d: 444 passed, 0 failed (lib 82, integration and doc tests).
    cargo test -p rapier2d: 216 passed, 0 failed (lib 64).
    cargo fmt -- --check clean; cargo clippy -p rapier3d --tests with -D warnings clean.
    (Windows, stable-x86_64-pc-windows-gnu.)

Note for review

In character_controller_ground_detection, the fix ends the balls lower than before (final y 0.30 / 0.04 instead
of 0.95 / 0.99; they start half inside the floor). Started at rest on the floor (y 1.01) instead, the no-snap ball
sinks to y -0.5 and the test's grounded assert fails at step 6043. The sink is not in the controller. It comes
from the shape cast against the test's 2002 m wide cuboid: in f32, parry's GJK ray cast stalls on a simplex face
inside the CSO, about 1400 m from the origin, and then reports None or a time of impact up to 9 cm past the
surface. A ball resting at the target distance then moves the full -0.1 into the floor on step 0. With a half-space
floor at the same height, the test shows no sink with or without this fix (final y 1.0101, reach 999.3 / 1000.5
of the ideal 999.4 / 1000.6). Without the fix, the false slip dropped the move on many steps and applied only the
upward nudge, which lifted the balls back out. I can open a separate parry issue with the traces.

Not checked: the y bouncing under 100x gravity in the first report of #809, and the 2D reproduction in its third
comment.

Fixes #809

🤖 Generated with Claude Code

…rror

A floor normal one ulp short of unit length, e.g. (0, 0.99999994, 0), left
a ~1e-9 downward part in `vertical_tangent`. `handle_slopes` read it as a
slip and the non-slip-slope branch dropped `vertical_tangent`, which on a
flat floor holds all or part of the horizontal move. A slip now needs a
downward part above 1e-5 of the tangent's length.

Fixes dimforge#809.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3D kinematic character jumping/jittering on flat surface

1 participant