Skip to content

Shape-cast TOI error scales with target shape extent (stagnation exit returns unrefined lower bound) #429

Description

@vikng-dev

cast_shapes / minkowski_ray_cast returns a time of impact that comes back short by an amount that scales with the target shape's extent. Casting a ball (r = 0.5166) straight down at a cuboid, measured max TOI distance error:

cuboid half-extent max TOI error
5 m 0.246 mm
50 m 3.64 mm
500 m 139.45 mm

The error is one-sided (always short), pose-discontinuous (deterministic per pose, jumping tick to tick), and the witness data (point1/normal1) stays exact even when the TOI is wrong.

Reproduction

Failing test on a branch of my fork (test commit only, no fix — based on current master):

git clone https://github.com/vikng-dev/parry && cd parry
git checkout 89eb49a
cargo test -p parry3d --test lib shape_cast_toi_accuracy
#   FAILS — prints the error table above

Mechanism

At first I suspected the relative convergence tolerance (gjk.rs:775-786), but it never fires for this case — instrumented over the test's 2,400 casts: 0 hits. What actually happens: minkowski_ray_cast advances a lower TOI bound ltoi and tracks an upper bound. With large support coordinates orthogonal to the cast direction, float cancellation keeps the upper bound from decreasing; the "upper bounds inconsistencies" last-chance exit fires (gjk.rs:712-715) and returns the current ltoi as the hit (gjk.rs:721) — still short by the stagnated gap. All 2,278 erroneous TOIs in the instrumented run exited through this path.

Impact

Hit this in a 64 Hz networked tank sim: wheel sphere-casts against a 1000 m ground cuboid came back short by p50 33 mm / p99 134 mm / max 200 mm over 19,828 samples; through a 551 kN/m suspension spring that's 10-40 kN of per-tick force noise — a sustained at-rest hull limit cycle (~12 mm heave) and a standing client/server divergence amplifier. Looks like #414 hit the same exit from a Rapier DynamicShapeCastVehicleController (wheels vs a 10,000-unit ground backstop, TOI ~10× short) before self-closing. #180's knife-edge None returns may live in the same exit structure too.

I have a fix ready — PR follows right after this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions