The parallel batch machinery from be21d627a only ever existed to chase a
default-on multi-threaded win that never came (washer w8 still +54% with
the hybrid on). At w1 — the only configuration this opt-in flag is for —
parallel_for runs inline, so the serial path gives the identical result
for ~340 fewer lines. Wire up the previously-dead
dynamic_tree_self_pairs/cross_pairs into a serial collect_batch_candidates
(three BVTT self/cross traversals -> canonical (a,b,child) sort -> serial
filter into move_results[0]) and delete BatchWork, BatchCtx, batch_drain_*,
BatchFilterCtx, batch_filter_*, bvtt_step, dynamic_tree_bvtt_drain/expand,
and the batch_frontier/worker_* scratch fields.
Determinism preserved exactly: OFF 0x61E35C31/step314 bit-identical, ON
0xBE99C5F7/step313 identical across workers 1/2/4. The debug SET-equality
oracle and the determinism_broad_phase_hybrid_across_worker_counts test
are unchanged and still pass; zero warnings.
PGO: pgo.sh never trained -bp=1, so an off-path-only profile laid the
hybrid branch out cold and collapsed the win to ~-5%. Add one -b=8 -bp=1
training run (neutral for the default path — counts merge, the OFF branch
stays hot) and retrain.
Corrected README numbers to measured values (hybrid-trained profile,
paired -bp toggle, washer w1): pair-finding stage 7.6k -> 3.6k ms/1000
(-52%); total ~-17% (~19.0k vs ~22.6k), which beats C (20661) and narrows
Rapier's lead from ~23% to ~12% — not the "17.7k / within 5% / -19%"
be21d627a claimed. Still default-off (w8 regresses ~+54%), opt-in
single-threaded accelerator for churn-heavy scenes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Broad-phase-hybrid subsection (why washer loses, the batch design, the
determinism proof, the single-thread win / multi-thread floor tradeoff,
and the default-off rationale). Washer row in the single-thread rapier
table annotated with the opt-in number (~17.7s, beats C, ~5% behind
rapier → box3d ahead-or-even on all nine single-threaded).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the C Box3D column to the nine-scene rapier table, all from one
same-window interleaved single-thread run (2026-07-06). Percentages vs
the box3d Rust column. box3d Rust beats Rapier on 8/9 (junkyard flipped
to a +5% win post-tier-2; only washer lost, Rapier's incremental-BVH
broad phase). vs C: within ~7% geomean, ahead on both pyramid scenes,
worst is junkyard +18%. Replaces the derived † junkyard cell with a
direct measurement. Headline updated 7/9→8/9.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Derived cells marked with a dagger and the derivation stated: cold-window
baseline x the same-binary paired improvement (thermal-drift-immune);
direct cold-window rerun will replace them. vs C: junkyard +17%->+7% w1,
+22%->+12% w8, geomeans +6%/+8%. vs rapier: junkyard -8% -> ~parity,
geomean +34%, washer now rapier's only win.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New middle tier between full manifold recycling and the full SAT, behind
WorldDef.enable_feature_recycling (default true; OFF path bit-identical,
hash 0x61E35C31 verified):
- Case A, separated-witness early-out: a previously-non-touching contact
revalidates only the cached winning axis; still separating beyond the
speculative distance means done in one test. Sound structurally: any
cached axis is a valid separation witness (understates only, which
falls through to the full SAT). Carried junkyard: 24k skips/step,
full SATs 25.4k -> 4.5k per step.
- Case B, touching feature rebuild: re-clips the cached winning feature
under explicit staleness bounds (SATCache::sat_pose at last full SAT,
translation < 4x recycle distance, rotation < ~4.6 deg, forced refresh
every 8 steps); degenerate rebuilds and touching<->separated
transitions fall through same-step.
Probe-driven (junkyard: 89k full SATs/step on 105k pairs, 13k touching;
rapier maintains 3.4x fewer pairs): paired same-binary -fr=0/1 A/Bs show
junkyard -8% in every pairing (collide phase -15%), washer neutral to
-5%, pyramid/rain guards neutral, OFF costs nothing. Same-session
cross-engine junkyard: rapier's -8% lead closes to ~-3%.
pgo.sh now trains BOTH modes (single-mode training starved the remaining
full-SAT path); checked-in profile retrained dual-mode. SATCache pose
serialized in snapshots. feature_recycled_contact_count in Counters.
README: port-extension subsection, soundness argument, updated notes.
Also: rapier bench --probe mode from the workload-probe session.
179/185/179/179 tests green with the tier ON, zero warnings,
determinism suite passes across runs/workers/task systems.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- libs/rapier/crates/bench now mirrors every box3d benchmark scene
(trees100/50/25, junkyard, rain, washer added to large_pyramid/
many_pyramids/joint_grid), same -b indices as the box3d benchmark.
Geometry, densities, filters, spawn cadence and joint counts match;
body/collider/joint counts verified equal on all nine scenes.
- Fix an index-out-of-bounds panic in the vendored rapier simd-stable
constraint grouping (interaction_groups.rs): bodies in a different
island than the interaction (kinematic drivers, dynamics mid
island-merge) indexed the wrong island's conflict masks. Out-of-island
bodies are now exempt from conflict tracking, matching the solver's
existing boundary treatment (bounds-checked gathers, dropped scatters).
Original three scenes reproduce their previous timings after the fix.
- README: full nine-scene single-threaded matrix vs rapier (box3d wins
7/9, +33% geomean; rapier wins the hull-churn scenes junkyard/washer)
with comparability caveats for the extended scenes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Geomeans over the nine real scenes: +7% w=1 / +9% w=8. The scene stays
in the benchmark binaries (upstream suite parity); its fixed-overhead
story is kept in the known-remainder notes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
junkyard now shows its real post-fix +17%; footnote lattice replaced by
one measurement-conditions note. Geomeans +7% w=1 / +13% w=8.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pure full-update pipeline is +38% vs C (both engines, recycling forced
off), diluted to +17% by the at-parity recycle path. Gap is diffuse
(1.3-1.5x per pipeline function); all concentrated hypotheses measured
~zero, including a staging rewrite that halved build_face_a_contact's
instruction count with zero wall-clock effect (the bloat was cold code).
Note: sample attribution unreliable on PGO binaries (hot/cold splits).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Disassembly attribution on current binaries: junkyard's +23% sits
entirely in the NON-SAT narrow phase (2.06x C) — the edge SAT is now
FASTER than C. Cause: LLVM+PGO mega-inlining (third occurrence) —
update_contact compiled to 5.5x C's instruction count, collide_hulls had
no symbol at all. inline(never) on collide_hulls /
compute_convex_manifold / query_face_directions restores C's layout:
junkyard -3.6% paired (retrained profile), washer neutral, others
untouched.
hull_at coverage extended to build_face_a_contact / build_polygon /
clip_segment_to_hull_face / find_incident_face for contract completeness
— measured NEUTRAL beyond the boundary fix, and the feature's earlier
-3.6% is now captured by the safe attribute instead. README documents
the demotion honestly (the safe fix superseded the unsafe one).
Gates: 179/179/185/179 tests (default/unchecked-hulls/dp/nosimd), hash
0x61E35C31 everywhere, zero warnings. PGO profile retrained.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Matrix cells stay default-build (cold-window run); the hull scenes'
opt-in feature gains are annotated as paired deltas rather than absolute
cross-session numbers (thermal windows differ ~10%, mixing them would
misstate both). Algebraic-float-ops evaluation moved to a dedicated
'Evaluated ideas' section at the bottom.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
unchecked-hulls (off by default): elides bounds checks on hull-topology
indexing in the three SAT hot loops via a cfg'd accessor. Safety contract
= hull connectivity invariants validated at construction (hull.rs
is_valid_hull_impl + create_hull asserts), immutable behind Arc; debug
builds always assert, so every test run exercises the contract. Measured
(paired, retrained PGO): junkyard -3.6%, washer -2%, nothing elsewhere —
documented honestly that the checks were NOT most of the hull residue.
Tests 179 green with and without the feature; hash 0x61E35C31 both.
README: evaluation of the newly-stabilized algebraic float ops idea —
incompatible with the determinism contract as a default (compiler-
version/ISA-dependent results break cross-arch equality and cross-build
replay), modest expected upside since hot paths are already hand-
contracted; possible future opt-in, not planned.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A sweep of every C b3ParallelFor/enqueue dispatch against the port found
the finalize-bodies pass (per-body transforms, AABB updates, sleep
accounting, continuous/TOI) and the bullet pass were left serial when
threading was ported — C runs both under b3ParallelFor. rain's w=8 gap
was almost entirely this serial fraction (Amdahl decomposition showed
its parallel portion already at C parity).
FinalizeCtx mirrors the collide pass's pattern: taken arrays + SyncSlice
disjoint per-body access, per-worker task contexts, deterministic merges
(bitset OR, split-candidate max like C), bullet list via atomic cursor
(C's b3AtomicFetchAddInt mirror). No new unsafe primitives. Pre-solve/
custom-filter callbacks force single-worker like collide.
w=8: rain +42% -> +8%, joint_grid +36% -> +11%, large_pyramid and
many_pyramids and trees25 at parity; geomean +11%. Serial geomean +5%
(Rust wins joint_grid/large_pyramid/many_pyramids outright). PGO profile
retrained; README tables + narrative updated. Remaining known
serial-vs-C difference: the split-island enqueue overlap (documented).
Hash 0x61E35C31 bit-identical (runs, workers 1/2/4, external tasks);
179/185/179 tests, zero warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Training set was missing washer and trees50/25 (washer -1.5% with
coverage, large_pyramid unchanged — no dilution). Profile refreshed.
Small-stage main-only fast path (generalizing C's single-block
shortcut): swept cutoffs 32/64/256 at w=8 — only large_world benefited
(-8% of ~11ms); rain regressed at every cutoff (its small-count stages
are mesh-contact stages with heavy per-item cost — serializing them
starves real parallelism) and joint_grid has few fat stages (grid
coloring = 2-4 colors), so its w=8 gap is NOT thin-stage sync. Reverted
per the measurable-win rule; negative result documented in the README.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Disassembly census showed the joint solvers at exact FMA parity with C
but +110 loads/+54 stores per joint: the full 56-byte BodyState get/set
round trip keeps untouched fields live across the ~1000-instruction
solve bodies. StateAccess::set_velocities (same unsafe contract as set,
velocities only, like C's in-place stores) + get_ref field extraction
across all 16 warm-start/solve functions in the 8 joint types.
joint_grid: 817 vs C 801 ms (was -11%). Full fresh matrix in README:
serial geomean +7% vs C with Rust WINNING large_pyramid (-6%) and
many_pyramids (-3%); w=8 geomean +28%. Checked-in PGO profile retrained
for the new code (stale profile cost ~13% on joint scenes).
Same change was measured neutral for contact scatter and correctly
dropped there (state live ~40 instrs vs ~1000) — both verdicts in the
README as a paired case study.
Hash 0x61E35C31 bit-identical everywhere; 179/185/179 tests, zero
warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cargo build --release on anything in the workspace now gets the PGO'd
box3d automatically (-Cprofile-use=libs/box3d/box3d.profdata; verified:
default build runs at the explicit-PGO binary's speed). The profile is
target-independent — x86_64 cross-build with the ARM-trained profile
compiles clean — and degrades gracefully when stale (unmatched functions
keep normal heuristics). Retrain with libs/box3d/pgo.sh.
Fresh four-way interleaved matrix in the README: default box3d is now
faster than or equal to rapier-simd on all three scenes (1118 vs 1451,
1510 vs 1690, 912 vs 914 ms) and faster than non-PGO C on two of three
(C keeps joint_grid 816 vs 912). 179/185/179 tests green with the
config active.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pgo.sh: instrument -> train on the benchmark scenes -> merge -> rebuild.
Paired same-machine runs: large_pyramid 1177 vs 1457 ms (-19%, now 15%
faster than the non-PGO C build), junkyard -14%, many_pyramids -11%.
Determinism hash unchanged under the PGO binary (0x61E35C31 across
runs/workers/task systems) — PGO changes layout/inlining, never
arithmetic. README notes the C-reference fairness caveat.
Also documented as tried-and-dropped (noise-floor in paired A/B, per
the keep-only-measurable-wins rule): cache-line padding of stage-sync
atomics, narrow velocity-only scatter writes, compound child Arc clone
(already eliminated by the earlier scratch fix).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Disassembly-driven (fork agents confirmed the stalls, killed the
bounds-check and recycle-rate hypotheses with instruction-level and
runtime-counter evidence — recycle counts are bit-identical to C):
- Manifolds inline-when-single store: Contact.manifolds Vec<Manifold> ->
enum { None, One(Manifold), Many(Vec) } with deref-as-slice. Convex
contacts keep their manifold inline (the Rust equivalent of C's block-
allocator arena locality — the per-contact heap chase was the main
stall in collide/prepare/store). Contact is #[repr(C)] with manifolds
last so hot header fields stay on the leading cache lines. Public
ContactData API unchanged via Deref; contact_solver.rs needed zero
changes. Pure storage change: determinism hash identical (0x61E35C31).
- #[inline(never)] on update_contact + the four convex stage functions:
C compiles these standalone; LLVM had inlined all of them into one
13.6 KB execute_block paying constant register-spill traffic.
Definitive cold-machine matrix: serial geomean 1.15x -> 1.12x vs C
(many_pyramids 2071 vs 1949 ms, large_pyramid 1501 vs 1392); 8-worker
geomean 1.35x -> 1.30x. vs rapier-simd (adjacent runs): box3d ahead 16%
on large_pyramid and 3% on joint_grid, behind 5% on many_pyramids (was
21%). README grids updated.
179/185/179 tests green, zero warnings, hash unchanged across workers/
arch/task systems.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Re-vendors wide 0.7 + safe_arch, restores the upstream simd-stable
wiring in rapier3d/parry3d manifests and the cfg-simd source, and drops
the added 'stripped build does not support SIMD' guards (upstream's
simd-vs-enhanced-determinism exclusivity guard kept).
Interleaved single-thread retest (min of 4): SIMD buys rapier 1.8-2.2x;
box3d vs rapier-simd is now near parity — large_pyramid 1579 vs 1638 ms,
joint_grid 957 vs 1008 ms (box3d ahead), many_pyramids 2389 vs 1970 ms
(rapier ahead). box3d README grid updated with the honest three-column
table; box3d keeps cross-arch determinism + zero deps at that speed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same scenes/geometry/materials/dt, matched solver budget (4 substeps vs
4 solver iterations), interleaved min-of-4 runs: large_pyramid 2.23x,
many_pyramids 1.82x, joint_grid 1.88x (geomean ~1.97x). Table + fairness
notes at the top of the box3d README (vendored rapier has no SIMD;
enhanced-determinism measured free on these scenes).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- joint prepare: read BodySim through references (was deref-copying
220 bytes twice per joint per step; prepare_joint now at C parity)
- FMA contraction extended to joint solvers (32 sites; hash re-baselined
to 0x61E35C31, still bit-identical across workers/arch/task systems)
- scheduler: workers spin ~tens of us before committing to a kernel
sleep (semaphore try_acquire spin phase; A/B: large_world w=8
24 -> 11.5 ms, joint_grid w=8 1.58x -> 1.49x, other scenes neutral;
intentional deviation from C documented in README)
- tried and reverted: chunks_exact twin-pair edge SAT (won 5% on
junkyard compounds, cost box-box scenes 4-8%; keeps C 1:1 loop shape)
- README: fresh benchmark matrix, second-round notes, stale external
task-hook claim fixed
179/185/179 tests green, zero warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Profiling-driven (sample + disasm comparison vs clang -O3). All changes
safe Rust except one debug_assert-guarded extension of the existing
SyncSlice unsafe contract. Determinism preserved: hash bit-identical
across workers 1/2/4, NEON/SSE2/scalar, internal/external task systems
(new baseline 0x9018E2D8 after approved FMA contraction).
- f32/f64::mul_add contraction in hot scalar math (= C's -ffp-contract=on;
89 sites; wide SIMD ops untouched like C intrinsics). large_pyramid
now at parity with C (1387 vs 1373 ms serial)
- FloatW::get/set: direct lane load/store instead of vector-through-stack
round trip; layout asserted at compile time
- gather_bodies by reference (removes 20-register spill storm)
- per-worker capacity-preserving scratch for convex + mesh collide paths
(C-arena equivalent; mesh path allocated per triangle and serialized
the parallel collide pass on allocator locks)
- update_contact: borrow shapes instead of cloning (deep compound
geometry clones + cross-worker Arc traffic; junkyard w=8 -39%)
- scheduler semaphore: two-level atomic fast path (C uses
dispatch_semaphore_t; old Mutex+Condvar locked every enqueue)
- SyncSlice::get_ref/get_mut unchecked indexing under the existing
unsafe contract, debug_assert-guarded (-6% serial)
- README/PORTING: new numbers, FMA sync conventions, known remainders
179/185/179 tests green (default/double-precision/disable-simd), zero
warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Full op-stream player in recording_replay.rs (b3RecPlayer port):
opcode dispatch for ~150 ops, StateHash verification at every step
marker, query replay with bitwise comparison, keyframe ring with
budget-driven interval doubling, seek/restart/scrub, validate_replay.
tests/test_recording.rs ports test_recording.c (17 tests incl.
record-at-4-workers/replay-at-1-and-4 hash equality). 179/185/179
tests green across default/double-precision/disable-simd, zero
warnings, determinism hash unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port of the C threading design: worker threads with a fixed task ring and
help-while-waiting finish (scheduler.rs), atomic block-claiming parallel_for,
and the solver's stage machinery (per-block syncIndex CAS, sync-bits stage
advancement, mainClaimed race). Parallel narrow phase, broad-phase pairs,
sensors, finalize. Shared access goes through documented disjointness
primitives (sync.rs: SyncPtr/SyncSlice/AtomicIndex); worker_count 1 keeps the
serial path bit-identically. Results are bit-identical at any worker count
(determinism hash 0x7A796F4F asserted at 1/2/4 workers). 8 workers: 3.4-5.7x
over serial on heavy scenes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- contact solver wide ops + V32 now have real SSE2 and NEON paths selected
by target arch; scalar fallback behind the disable-simd feature. All three
paths are bit-identical (cross-arch determinism verified: same ragdoll
hash on NEON, SSE2 under Rosetta, and scalar).
- double-precision feature (C BOX3D_DOUBLE_PRECISION): f64 world positions
with the exact C boundary-function semantics; enables the far-from-origin
test halves (157 tests in DP mode, 151 default).
- world snapshots: recording substrate subset (buffer/writers/geometry
registry/readers) + world_snapshot.c port; bit-identical continuation
after restore, corrupt-image rejection.
- examples/benchmark.rs: all 10 C benchmark scenarios; serial Rust runs
1.05-1.55x slower than C -O2 at one worker (geomean ~1.3x with fat LTO).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Full engine port in libs/box3d: math, geometry, GJK/TOI, hull builder,
dynamic tree, manifolds, constraint graph, solver (serial, scalar SIMD
path), all 8 joint types, sensors, mover, world API. 147 ported C unit
tests green in debug and release. See libs/box3d/README.md for the
upstream revision and sync notes, PORTING.md for conventions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>