#![cfg(all(feature = "enhanced-determinism", feature = "serde-serialize"))]
use rapier3d::prelude::*;
const GOLDEN: u64 = 0x0c46_183e_4fbf_cfbb;
struct Fnv(u64);
impl Fnv {
fn new() -> Self {
Self(0xcbf29ce484222325)
}
fn eat_bytes(&mut self, bytes: &[u8]) {
for b in bytes {
self.0 ^= *b as u64;
self.0 = self.0.wrapping_mul(0x100000001b3);
}
}
fn eat(&mut self, f: Real) {
self.eat_bytes(&f.to_bits().to_le_bytes());
}
}
fn checksum(world: &PhysicsWorld) -> u64 {
let mut h = Fnv::new();
h.eat_bytes(&bincode::serialize(&world.broad_phase).expect("broad-phase serialization"));
h.eat_bytes(&bincode::serialize(&world.narrow_phase).expect("narrow-phase serialization"));
let mut handles: Vec<_> = world.bodies.iter().map(|(handle, _)| handle).collect();
handles.sort_by_key(|h| h.into_raw_parts().0);
for handle in handles {
let rb = &world.bodies[handle];
for c in rb.translation().to_array() {
h.eat(c);
}
for c in rb.rotation().to_array() {
h.eat(c);
}
for c in rb.linvel().to_array() {
h.eat(c);
}
for c in rb.angvel().to_array() {
h.eat(c);
}
}
h.0
}
fn spawn_cluster(world: &mut PhysicsWorld, seed: usize, height: Real) {
for i in 0..12 {
let a = (seed * 7 + i * 13) as Real * 0.011;
let b = (seed * 11 + i * 5) as Real * 0.017;
world.insert(
RigidBodyBuilder::dynamic()
.translation(Vector::new(
(i as Real % 4.0) * 1.1 - 2.2 + a,
height + (i / 4) as Real * 1.1,
b % 3.0 - 1.5,
))
.linvel(Vector::new(a % 1.5 - 0.75, 0.0, b % 1.5 - 0.75)),
ColliderBuilder::cuboid(0.5, 0.5, 0.5),
);
}
}
fn run() -> u64 {
let mut world = PhysicsWorld::new();
world.insert(
RigidBodyBuilder::fixed().translation(Vector::new(0.0, -0.5, 0.0)),
ColliderBuilder::cuboid(30.0, 0.5, 30.0),
);
for i in 0..14 {
for j in 0..2 {
for k in 0..14 {
let jitter = (i as Real * 0.013 + k as Real * 0.017) % 0.05;
world.insert(
RigidBodyBuilder::dynamic().translation(Vector::new(
i as Real * 1.05 - 7.0 + jitter,
j as Real * 1.05 + 0.55,
k as Real * 1.05 - 7.0 - jitter,
)),
ColliderBuilder::cuboid(0.5, 0.5, 0.5),
);
}
}
}
for _ in 0..220 {
world.step();
}
for round in 0..10 {
spawn_cluster(&mut world, round, 6.0);
for _ in 0..40 {
world.step();
}
}
checksum(&world)
}
#[test]
fn parallel_and_sequential_builds_agree() {
let hash = run();
assert_eq!(
hash, GOLDEN,
"\nbroad/narrow-phase checksum drifted: got {hash:#018x}, expected {GOLDEN:#018x}.\n\
This build's work distribution changed what it computes. The `parallel` feature \
must only decide *who* runs a chunk — never how work is split, in what order \
results are merged, or which algorithm runs. If the change was intentional, \
re-mint GOLDEN and say why in the commit.\n"
);
}