Skip to content

fix: join the deferred BVH pass before a zero-length step returns - #1067

Open
haowang1013 wants to merge 1 commit into
dimforge:masterfrom
haowang1013:fix/zero-dt-step-joins-deferred-bvh
Open

haowang1013 wants to merge 1 commit into
dimforge:masterfrom
haowang1013:fix/zero-dt-step-joins-deferred-bvh

Conversation

@haowang1013

Copy link
Copy Markdown

Summary

Since #1012, a step with dt <= 0.0 returns right after collision detection. When the broad-phase update in that step defers its optimization pass, take_deferred_optimize moves the tree out of the broad-phase (mem::replace(&mut self.tree, Bvh::new())). A full step always puts it back: its last action, update_moved_collider_aabbs, calls join_deferred_bvh_optimize. The zero-length early return did not. Until the next full step, the broad-phase holds an empty tree, so every scene query misses every collider, including the ones that did not move.

This PR calls join_deferred_bvh_optimize before that early return.

Why this matters

A zero-length step is the usual way to refresh scene queries between steps. For example, an engine teleports a body, then runs a zero-length step so that raycasts see the new pose. In @dimforge/rapier3d-compat 0.21.0, queries made right after such a step can return nothing. 0.20.0 (Rapier 0.35) does not have the early return and is not affected.

The pass is deferred when either:

  • the update itself changes at least 1/16 of the leaves, or
  • frame_index % 8 == 0 and the in-place leaf updates since the last pass reach 1/64 of the leaves.

The second condition can fire in a zero-length step that has no user changes. So running more zero-length steps does not reliably work around the problem from the outside: whichever step lands on that frame leaves the tree out.

Tests

New crates/rapier3d/tests/zero_dt_step_scene_queries.rs:

  • zero_length_step_after_a_teleport_keeps_every_collider_queryable: teleports a fixed, kinematic or dynamic body, runs one zero-length step, then checks the moved collider at its new pose, the old pose and an untouched collider.
  • consecutive_zero_length_steps_keep_every_collider_queryable: 640 colliders and 1 to 3 consecutive zero-length steps across all eight frame alignments, so that one step lands on the periodic optimizer.

Both tests fail on master and pass with this change.

Validation

  • cargo test -p rapier3d --release (445 passed, 0 failed)
  • cargo test -p rapier3d --release --features parallel --test zero_dt_step_scene_queries --test single_worker_deferred_bvh (passed)
  • cargo fmt -- --check (passed)
  • cargo clippy -p rapier3d --tests --release with and without --features parallel, RUSTFLAGS="-D warnings" (passed)

I did not rebuild the TypeScript bindings. I reproduced the problem against the published @dimforge/rapier3d-compat 0.21.0 and confirmed that 0.20.0 does not show it.

This is unrelated to #1008 / #1018, which are about the parallel join deadlocking. This bug also shows up in single-threaded builds.

🤖 Generated with Claude Code

A step with `dt <= 0.0` returned right after collision detection. When that
broad-phase update deferred its optimization pass, `take_deferred_optimize` had
moved the tree out of the broad-phase, and nothing put it back before the next
full step. Until then every scene query missed every collider, including the
ones that did not move.

Join the deferred pass before the early return, as the end of a full step does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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.

1 participant