Skip to content

Size the box-box edge-axis tie-break to float32 rounding - #1597

Closed
jsw7460 wants to merge 1 commit into
google-deepmind:mainfrom
jsw7460:box-box-edge-axis-float32-tie
Closed

Size the box-box edge-axis tie-break to float32 rounding#1597
jsw7460 wants to merge 1 commit into
google-deepmind:mainfrom
jsw7460:box-box-edge-axis-float32-tie

Conversation

@jsw7460

@jsw7460 jsw7460 commented Aug 20, 2026

Copy link
Copy Markdown

Fix box_box ghost contacts on level resting surfaces under FP32

When two boxes rest flat against each other, four of the nine edge-cross separating axes are parallel to the face normal, creating an exact tie in separation depth. In FP32, the legacy relative margin 1.0 - 1e-12 rounds to 1.0, allowing FP32 cancellation noise in cross-product evaluation to erroneously pick an edge axis over the face axis. The subsequent edge-edge routine degenerates on face contacts and generates phantom contact points hundreds of millimeters deep.

This patch introduces an absolute edge_slack proportional to the sum of the box half-extents (1e-6 * sum(sizes)), ensuring face axes are stably preferred on near-ties regardless of near-zero resting separation. Adds a regression test covering the degenerate resting pose.

Simply increasing the relative margin does not work: the separations are sums of box extents times rotation entries, so their FP32 error is absolute (~5e-8 m for supports of order 0.4 m) against a resting separation of ~1e-4 m — a relative error near 5e-4. Both 1e-6 and 1e-4 leave every failing pose failing.

(Note: Mujoco's C has since rewritten this collider on main using Sutherland-Hodgman face clipping and precision-aware constants split on mjUSESINGLE. Porting that is likely the better long-term direction)

box_box picks the separating axis of least penetration, and the winner selects between two different manifold routines: a face axis is clipped into several points, an edge axis becomes the closest point between two segments. Edge axes are held to a margin so that faces win near-ties, since the face manifold is the better one for the solver.

That margin is relative, `separation * (1.0 - 1e-12)`, inherited from MuJoCo's C where mjtNum is double and 1e-12 is a few thousand ULPs. Here everything is float32, `1.0 - 1e-12` rounds to exactly 1.0, and the test reduces to `c3 < separation`.

It matters more than a missing tie-break usually would, because the tie is not incidental. When both boxes are level -- a bar resting on a table -- four of the nine edge-cross axes come out exactly parallel to the face normal. They are the same direction expressed two ways, so their separations agree to the last bit and rounding decides the winner. Over 2000 resting poses of a 120x6x30 mm bar on a 1.2x0.8 m table, overlapping by 0.1 mm, 92 of them hand the contact to the edge-edge branch, which then reports penetrations around 0.9 m at points outside both boxes. MuJoCo reports four contacts at -1e-4 m for every one of those poses. In a simulation the solver undoing that penetration throws the bar off the table at over 10 m/s and the scene goes non-finite shortly after.

This is not an artifact of one machine's arithmetic: the pose in the test reports -0.9563501 m on both x86_64 with CUDA and arm64 on the CPU backend, agreeing to every digit printed.

Restoring a relative margin does not fix it. The separations are sums of box extents times rotation entries, so their float32 error is absolute and proportional to the extents -- around 5e-8 m for supports of order 0.4 m. Against a resting separation of 1e-4 m that is a relative error near 5e-4, so 1e-6 and 1e-4 both leave all 92 poses failing. This scales the slack by the box extents instead, which takes the same 2000 poses to zero.

The test calls box_box directly with the geom poses kinematics produces for one of those configurations. Going through a model would need nativeccd disabled to reach this collider, and that leaves box-box on the analytic path for later tests in the same process.
@thowell
thowell requested a review from kbayes August 20, 2026 16:48
@thowell

thowell commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

@jsw7460 thank you for contributing to mujoco warp!

@kbayes please take a look. thanks!

@thowell

thowell commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

@jsw7460 thank you for contributing to mujoco warp! closing this issue in favor a complete refactor #1614

@thowell thowell closed this Sep 11, 2026
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.

2 participants