Skip to content

Hard native crash (narrow_phase.rs:1115) when a block in a rope-attached sub-level is changed and its collider is rebaked #950

@OMEGAU371

Description

@OMEGAU371

I'm building a mod on top of Sable's sub-level API and ran into a reproducible hard crash. I've narrowed it down pretty far and I'm fairly sure the failing path is inside Sable's native collider handling rather than my code — reasoning below, and there are already several open issues that look like the same thing reproduced with no custom mod at all.

Env: Sable 1.2.2 · MC 1.21.1 · NeoForge 21.1.x · Create + Create Aeronautics + Create Simulated (rope)

What happens

My mod programmatically changes a block inside a sub-level (an ordinary setBlock-style edit — it goes through RapierPhysicsPipeline.handleBlockChangeRapier3D.changeBlock, which rebakes that sub-level's voxel collider). If that sub-level is joined to another one by a Create Simulated rope — e.g. a hot-air balloon, base sub-level + envelope sub-level tied together by a rope — the next Rapier3D.step() hard-aborts the JVM with a non-unwinding Rust panic:

thread '<unnamed>' panicked at rapier3d/.../src/geometry/narrow_phase.rs:1115:33:
No element at index
thread '<unnamed>' panicked at library/core/src/panicking.rs:225:5:
panic in a function that cannot unwind

Sometimes it surfaces (same cause, different timing) as:

panicked at rapier/src/hooks.rs:30:17: No collider B!
panicked at rapier/src/hooks.rs:240:14: called `Result::unwrap()` on an `Err` value: JavaException

It crosses the JNI boundary as a non-unwinding panic → process::abort(), so it can't be caught or logged from Java.

The isolating fact

This is not a physics/collision problem. Without my mod installed, you can ram that same balloon together as hard as you want and it never crashes — Sable handles the rope plus the collision perfectly fine. The crash only appears when a block in a rope-attached sub-level is changed and its collider gets rebaked. The exact same block change on a sub-level with no rope attached also doesn't crash. So the trigger isn't the rope, and isn't the collision — it's specifically rebaking a sub-level's voxel collider while a rope is keeping that sub-level in a live contact pair.

Minimal repro

  1. Two sub-levels joined by a Create Simulated rope (hot-air balloon base + envelope).
  2. From anything — a mod, a command, a Create drill — change or remove a block in one of the rope-attached sub-levels (anything that reaches Rapier3D.changeBlock / forces the voxel rebake).
  3. Next physics step → narrow_phase.rs:1115 abort. Deterministic, within one or two changes.

What I think is going on

The voxel-collider rebuild in the changeBlock path (and Rapier3D.removeSubLevel) frees/replaces the sub-level's collider but doesn't remove it from Rapier's narrow-phase contact graph — the Coarena that narrow_phase.rs:1115 indexes into. Normally a freed collider would just fall out of contact on the next step, but the rope (setRopeAttachment) holds that sub-level in an active contact pair, so a stale ColliderHandle survives into the next step() and the lookup panics. It looks like the same family as the existing "sub-level removed mid-substep → narrow_phase.rs:1115" reports; the rope just makes it trivially deterministic. I can't see the native source so I can't confirm the precise internal mechanism, but every isolating fact above points at the rebake not purging the contact graph.

Why I don't think it's fixable downstream

Rapier3D.removeSubLevel is the only collider/body removal primitive exposed, and Sable's own SubLevelPhysicsSystem.recoverSubLevel goes through it — so even forcing a full clean rebuild in the safe pre-step window inherits the same gap and still crashes. Nothing on the public API purges the stale narrow-phase contact pair, so I don't think a mod can paper over this; the cleanup has to happen where the collider is freed.

Suggested direction

When a sub-level collider is removed or rebuilt — both in removeSubLevel and in the changeBlock rebake path — also drop the affected collider(s) from the NarrowPhase (and from any rope/joint attachment) before the next step, e.g. collider_set.remove(handle, &mut islands, &mut bodies, &mut joints, true) / narrow_phase.remove_collider(...), so no stale handle survives into narrow_phase.rs:1115. You'll obviously know the native layout better than I can infer it from stack traces.

This already reproduces with no custom mod

These open issues look like exactly this path, and several of them involve no custom mod — just vanilla Create changing a block on a rope-pulley contraption:

#641 and #914 in particular are this bug with zero custom code involved — a Create drill mining/touching a block on a rope-attached contraption rebakes its collider and hits the same crash. Cross-referencing so this can be deduped/linked rather than treated as five separate reports.

Game log

It's a native process::abort(), so the JVM-side latest.log contains nothing beyond the panic lines quoted above. I have full per-run logs and crash reports and can put a specific run on mclo.gs if you want one for a particular trace.

Metadata

Metadata

Assignees

Labels

related: nativesIssues related to native librariestype: crashSomething crashes my game

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions