On macOS (wgpu/Metal), the rbd multibody solver's contact and PD-joint sweeps have no effect: bodies free-fall through the ground, and more solver iterations make the blow-up worse (gravity integrates per TGS iteration while constraints contribute nothing).
Root cause is not in nexus — it's a naga 29 MSL backend bug: the writer renders a loop's continuing block at the top of the next while iteration and re-evaluates the break_if condition there, after the continuing statements have advanced the loop variables the condition reads. rust-gpu while loops (conditional backedge computed in the loop body) therefore exit one body-execution early on Metal. In solve_contact_constraints_par, the per-lane while i < ndofs { …; i += LANES } reductions have exactly one iteration per lane — so they execute zero times, J·v evaluates to 0, and every impulse stays 0. CUDA and Vulkan (spirv-passthrough) are unaffected, which makes it look like a nexus solver bug when it isn't.
Fix is upstream: gfx-rs/wgpu#9815 (fixes the long-open gfx-rs/wgpu#4558). Until it ships in a wgpu release, a vendored-naga [patch.crates-io] with the condition-snapshot fix restores correct Metal behavior — verified against a CUDA golden reference of the same SPIR-V (step-0 contact impulse matches to 4 decimals; an RL training run over the engine converges identically on macOS and CUDA after the patch).
Happy to share the diagnosis trail (deterministic-seed intermediate diffing down to an in-kernel lane-liveness probe) or the vendored patch. (Originally filed on the old repo as dimforge/nexus-rustgpu#3 before noticing development moved here.)
On macOS (wgpu/Metal), the rbd multibody solver's contact and PD-joint sweeps have no effect: bodies free-fall through the ground, and more solver iterations make the blow-up worse (gravity integrates per TGS iteration while constraints contribute nothing).
Root cause is not in nexus — it's a naga 29 MSL backend bug: the writer renders a loop's
continuingblock at the top of the nextwhileiteration and re-evaluates thebreak_ifcondition there, after the continuing statements have advanced the loop variables the condition reads. rust-gpuwhileloops (conditional backedge computed in the loop body) therefore exit one body-execution early on Metal. Insolve_contact_constraints_par, the per-lanewhile i < ndofs { …; i += LANES }reductions have exactly one iteration per lane — so they execute zero times, J·v evaluates to 0, and every impulse stays 0. CUDA and Vulkan (spirv-passthrough) are unaffected, which makes it look like a nexus solver bug when it isn't.Fix is upstream: gfx-rs/wgpu#9815 (fixes the long-open gfx-rs/wgpu#4558). Until it ships in a wgpu release, a vendored-naga
[patch.crates-io]with the condition-snapshot fix restores correct Metal behavior — verified against a CUDA golden reference of the same SPIR-V (step-0 contact impulse matches to 4 decimals; an RL training run over the engine converges identically on macOS and CUDA after the patch).Happy to share the diagnosis trail (deterministic-seed intermediate diffing down to an in-kernel lane-liveness probe) or the vendored patch. (Originally filed on the old repo as dimforge/nexus-rustgpu#3 before noticing development moved here.)