use crate::math::{Fix128, QuatFix, Vec3Fix};
use crate::solver::{PhysicsWorld, RigidBody, SolverConfig};
#[repr(C)]
pub struct AliceVec3 {
pub x: f64,
pub y: f64,
pub z: f64,
}
#[repr(C)]
pub struct AliceQuat {
pub x: f64,
pub y: f64,
pub z: f64,
pub w: f64,
}
#[repr(C)]
pub struct AlicePhysicsConfig {
pub substeps: u32,
pub iterations: u32,
pub gravity_x: f64,
pub gravity_y: f64,
pub gravity_z: f64,
pub damping: f64,
}
#[repr(C)]
pub struct AliceBodyInfo {
pub position: AliceVec3,
pub velocity: AliceVec3,
pub angular_velocity: AliceVec3,
pub rotation: AliceQuat,
pub inv_mass: f64,
pub is_static: u8,
pub is_sensor: u8,
}
impl AliceVec3 {
fn to_vec3fix(&self) -> Vec3Fix {
Vec3Fix::new(
Fix128::from_f64(self.x),
Fix128::from_f64(self.y),
Fix128::from_f64(self.z),
)
}
fn from_vec3fix(v: Vec3Fix) -> Self {
Self {
x: v.x.to_f64(),
y: v.y.to_f64(),
z: v.z.to_f64(),
}
}
}
impl AliceQuat {
fn from_quatfix(q: QuatFix) -> Self {
Self {
x: q.x.to_f64(),
y: q.y.to_f64(),
z: q.z.to_f64(),
w: q.w.to_f64(),
}
}
}
#[no_mangle]
pub extern "C" fn alice_physics_world_create() -> *mut PhysicsWorld {
let world = PhysicsWorld::new(SolverConfig::default());
Box::into_raw(Box::new(world))
}
#[no_mangle]
pub extern "C" fn alice_physics_world_create_with_config(
config: AlicePhysicsConfig,
) -> *mut PhysicsWorld {
let solver_config = SolverConfig {
substeps: config.substeps as usize,
iterations: config.iterations as usize,
gravity: Vec3Fix::new(
Fix128::from_f64(config.gravity_x),
Fix128::from_f64(config.gravity_y),
Fix128::from_f64(config.gravity_z),
),
damping: Fix128::from_f64(config.damping),
..Default::default()
};
let world = PhysicsWorld::new(solver_config);
Box::into_raw(Box::new(world))
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_world_destroy(world: *mut PhysicsWorld) {
if !world.is_null() {
drop(Box::from_raw(world));
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_world_step(world: *mut PhysicsWorld, dt: f64) -> u8 {
let w = match world.as_mut() {
Some(w) => w as *mut PhysicsWorld,
None => return 0,
};
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
(*w).step(Fix128::from_f64(dt));
}));
result.is_ok() as u8
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_world_step_n(
world: *mut PhysicsWorld,
dt: f64,
steps: u32,
) -> u8 {
let w = match world.as_mut() {
Some(w) => w as *mut PhysicsWorld,
None => return 0,
};
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let dt_fix = Fix128::from_f64(dt);
for _ in 0..steps {
(*w).step(dt_fix);
}
}));
result.is_ok() as u8
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_world_body_count(world: *const PhysicsWorld) -> u32 {
match world.as_ref() {
Some(w) => w.bodies.len() as u32,
None => 0,
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_world_get_positions_batch(
world: *const PhysicsWorld,
out: *mut f64,
out_capacity: u32,
) -> u8 {
let w = match world.as_ref() {
Some(w) => w,
None => return 0,
};
if out.is_null() {
return 0;
}
let n = w.bodies.len();
if (out_capacity as usize) < n * 3 {
return 0;
}
let buf = std::slice::from_raw_parts_mut(out, n * 3);
for (i, body) in w.bodies.iter().enumerate() {
buf[i * 3] = body.position.x.to_f64();
buf[i * 3 + 1] = body.position.y.to_f64();
buf[i * 3 + 2] = body.position.z.to_f64();
}
1
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_world_get_velocities_batch(
world: *const PhysicsWorld,
out: *mut f64,
out_capacity: u32,
) -> u8 {
let w = match world.as_ref() {
Some(w) => w,
None => return 0,
};
if out.is_null() {
return 0;
}
let n = w.bodies.len();
if (out_capacity as usize) < n * 3 {
return 0;
}
let buf = std::slice::from_raw_parts_mut(out, n * 3);
for (i, body) in w.bodies.iter().enumerate() {
buf[i * 3] = body.velocity.x.to_f64();
buf[i * 3 + 1] = body.velocity.y.to_f64();
buf[i * 3 + 2] = body.velocity.z.to_f64();
}
1
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_world_set_velocities_batch(
world: *mut PhysicsWorld,
data: *const f64,
count: u32,
) -> u8 {
let w = match world.as_mut() {
Some(w) => w,
None => return 0,
};
if data.is_null() {
return 0;
}
let n = w.bodies.len();
if (count as usize) < n * 3 {
return 0;
}
let buf = std::slice::from_raw_parts(data, n * 3);
for (i, body) in w.bodies.iter_mut().enumerate() {
body.velocity = Vec3Fix::new(
Fix128::from_f64(buf[i * 3]),
Fix128::from_f64(buf[i * 3 + 1]),
Fix128::from_f64(buf[i * 3 + 2]),
);
}
1
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_body_apply_impulses_batch(
world: *mut PhysicsWorld,
data: *const f64,
count: u32,
) -> u8 {
let w = match world.as_mut() {
Some(w) => w,
None => return 0,
};
if data.is_null() || count % 4 != 0 {
return 0;
}
let buf = std::slice::from_raw_parts(data, count as usize);
let n_bodies = w.bodies.len();
for chunk in buf.chunks_exact(4) {
let body_id = chunk[0] as usize;
if body_id >= n_bodies {
continue;
}
let impulse = Vec3Fix::new(
Fix128::from_f64(chunk[1]),
Fix128::from_f64(chunk[2]),
Fix128::from_f64(chunk[3]),
);
w.bodies[body_id].apply_impulse(impulse);
}
1
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_body_add_dynamic(
world: *mut PhysicsWorld,
position: AliceVec3,
mass: f64,
) -> u32 {
match world.as_mut() {
Some(w) => {
let body = RigidBody::new_dynamic(position.to_vec3fix(), Fix128::from_f64(mass));
w.add_body(body) as u32
}
None => u32::MAX,
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_body_add_static(
world: *mut PhysicsWorld,
position: AliceVec3,
) -> u32 {
match world.as_mut() {
Some(w) => {
let body = RigidBody::new_static(position.to_vec3fix());
w.add_body(body) as u32
}
None => u32::MAX,
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_body_add_sensor(
world: *mut PhysicsWorld,
position: AliceVec3,
) -> u32 {
match world.as_mut() {
Some(w) => {
let body = RigidBody::new_sensor(position.to_vec3fix());
w.add_body(body) as u32
}
None => u32::MAX,
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_body_get_info(
world: *const PhysicsWorld,
body_id: u32,
out: *mut AliceBodyInfo,
) -> u8 {
let (w, o) = match (world.as_ref(), out.as_mut()) {
(Some(w), Some(o)) => (w, o),
_ => return 0,
};
match w.bodies.get(body_id as usize) {
Some(b) => {
o.position = AliceVec3::from_vec3fix(b.position);
o.velocity = AliceVec3::from_vec3fix(b.velocity);
o.angular_velocity = AliceVec3::from_vec3fix(b.angular_velocity);
o.rotation = AliceQuat::from_quatfix(b.rotation);
o.inv_mass = b.inv_mass.to_f64();
o.is_static = b.is_static() as u8;
o.is_sensor = b.is_sensor as u8;
1
}
None => 0,
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_body_get_position(
world: *const PhysicsWorld,
body_id: u32,
out: *mut AliceVec3,
) -> u8 {
let (w, o) = match (world.as_ref(), out.as_mut()) {
(Some(w), Some(o)) => (w, o),
_ => return 0,
};
match w.bodies.get(body_id as usize) {
Some(b) => {
*o = AliceVec3::from_vec3fix(b.position);
1
}
None => 0,
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AliceFix128Raw {
pub hi: i64,
pub lo: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AliceVec3Fix128Raw {
pub x: AliceFix128Raw,
pub y: AliceFix128Raw,
pub z: AliceFix128Raw,
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_body_get_position_fix128_raw(
world: *const PhysicsWorld,
body_id: u32,
out: *mut AliceVec3Fix128Raw,
) -> u8 {
let (w, o) = match (world.as_ref(), out.as_mut()) {
(Some(w), Some(o)) => (w, o),
_ => return 0,
};
match w.bodies.get(body_id as usize) {
Some(b) => {
*o = AliceVec3Fix128Raw {
x: AliceFix128Raw {
hi: b.position.x.hi,
lo: b.position.x.lo,
},
y: AliceFix128Raw {
hi: b.position.y.hi,
lo: b.position.y.lo,
},
z: AliceFix128Raw {
hi: b.position.z.hi,
lo: b.position.z.lo,
},
};
1
}
None => 0,
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_body_set_position(
world: *mut PhysicsWorld,
body_id: u32,
position: AliceVec3,
) -> u8 {
match world.as_mut() {
Some(w) => match w.bodies.get_mut(body_id as usize) {
Some(b) => {
b.position = position.to_vec3fix();
1
}
None => 0,
},
None => 0,
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_body_get_velocity(
world: *const PhysicsWorld,
body_id: u32,
out: *mut AliceVec3,
) -> u8 {
let (w, o) = match (world.as_ref(), out.as_mut()) {
(Some(w), Some(o)) => (w, o),
_ => return 0,
};
match w.bodies.get(body_id as usize) {
Some(b) => {
*o = AliceVec3::from_vec3fix(b.velocity);
1
}
None => 0,
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_body_set_velocity(
world: *mut PhysicsWorld,
body_id: u32,
velocity: AliceVec3,
) -> u8 {
match world.as_mut() {
Some(w) => match w.bodies.get_mut(body_id as usize) {
Some(b) => {
b.velocity = velocity.to_vec3fix();
1
}
None => 0,
},
None => 0,
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_body_get_rotation(
world: *const PhysicsWorld,
body_id: u32,
out: *mut AliceQuat,
) -> u8 {
let (w, o) = match (world.as_ref(), out.as_mut()) {
(Some(w), Some(o)) => (w, o),
_ => return 0,
};
match w.bodies.get(body_id as usize) {
Some(b) => {
*o = AliceQuat::from_quatfix(b.rotation);
1
}
None => 0,
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_body_set_restitution(
world: *mut PhysicsWorld,
body_id: u32,
restitution: f64,
) -> u8 {
match world.as_mut() {
Some(w) => match w.bodies.get_mut(body_id as usize) {
Some(b) => {
b.restitution = Fix128::from_f64(restitution);
1
}
None => 0,
},
None => 0,
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_body_set_friction(
world: *mut PhysicsWorld,
body_id: u32,
friction: f64,
) -> u8 {
match world.as_mut() {
Some(w) => match w.bodies.get_mut(body_id as usize) {
Some(b) => {
b.friction = Fix128::from_f64(friction);
1
}
None => 0,
},
None => 0,
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_body_apply_impulse(
world: *mut PhysicsWorld,
body_id: u32,
impulse: AliceVec3,
) -> u8 {
match world.as_mut() {
Some(w) => match w.bodies.get_mut(body_id as usize) {
Some(b) => {
b.apply_impulse(impulse.to_vec3fix());
1
}
None => 0,
},
None => 0,
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_body_apply_impulse_at(
world: *mut PhysicsWorld,
body_id: u32,
impulse: AliceVec3,
point: AliceVec3,
) -> u8 {
match world.as_mut() {
Some(w) => match w.bodies.get_mut(body_id as usize) {
Some(b) => {
b.apply_impulse_at(impulse.to_vec3fix(), point.to_vec3fix());
1
}
None => 0,
},
None => 0,
}
}
#[no_mangle]
pub extern "C" fn alice_physics_config_default() -> AlicePhysicsConfig {
let cfg = SolverConfig::default();
AlicePhysicsConfig {
substeps: cfg.substeps as u32,
iterations: cfg.iterations as u32,
gravity_x: cfg.gravity.x.to_f64(),
gravity_y: cfg.gravity.y.to_f64(),
gravity_z: cfg.gravity.z.to_f64(),
damping: cfg.damping.to_f64(),
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_world_set_gravity(
world: *mut PhysicsWorld,
x: f64,
y: f64,
z: f64,
) {
if let Some(w) = world.as_mut() {
w.config.gravity = Vec3Fix::new(
Fix128::from_f64(x),
Fix128::from_f64(y),
Fix128::from_f64(z),
);
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_world_set_substeps(world: *mut PhysicsWorld, substeps: u32) {
if let Some(w) = world.as_mut() {
w.config.substeps = substeps as usize;
}
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_state_serialize(
world: *const PhysicsWorld,
out_len: *mut u32,
) -> *mut u8 {
let w = match world.as_ref() {
Some(w) => w,
None => {
if let Some(len) = out_len.as_mut() {
*len = 0;
}
return std::ptr::null_mut();
}
};
let state = w.serialize_state();
let len = state.len();
if let Some(out) = out_len.as_mut() {
*out = len as u32;
}
let boxed = state.into_boxed_slice();
Box::into_raw(boxed) as *mut u8
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_state_deserialize(
world: *mut PhysicsWorld,
data: *const u8,
len: u32,
) -> u8 {
let w = match world.as_mut() {
Some(w) => w,
None => return 0,
};
if data.is_null() || len == 0 {
return 0;
}
let slice = std::slice::from_raw_parts(data, len as usize);
w.deserialize_state(slice) as u8
}
#[no_mangle]
pub unsafe extern "C" fn alice_physics_state_free(data: *mut u8, len: u32) {
if !data.is_null() && len > 0 {
let slice = std::slice::from_raw_parts_mut(data, len as usize);
drop(Box::from_raw(slice as *mut [u8]));
}
}
#[no_mangle]
pub extern "C" fn alice_physics_version() -> *const std::os::raw::c_char {
const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");
VERSION.as_ptr().cast()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_string_matches_cargo_pkg_version() {
let s = unsafe { std::ffi::CStr::from_ptr(alice_physics_version()) };
assert_eq!(s.to_str().unwrap(), env!("CARGO_PKG_VERSION"));
}
#[test]
fn test_vec3_conversion_roundtrip() {
let original = Vec3Fix::new(
Fix128::from_f64(1.5),
Fix128::from_f64(-2.25),
Fix128::from_f64(3.75),
);
let c = AliceVec3::from_vec3fix(original);
let back = c.to_vec3fix();
assert_eq!(original.x.hi, back.x.hi);
assert_eq!(original.y.hi, back.y.hi);
assert_eq!(original.z.hi, back.z.hi);
}
#[test]
fn test_world_create_destroy() {
unsafe {
let world = alice_physics_world_create();
assert!(!world.is_null());
assert_eq!(alice_physics_world_body_count(world), 0);
alice_physics_world_destroy(world);
}
}
#[test]
fn test_world_destroy_null() {
unsafe {
alice_physics_world_destroy(std::ptr::null_mut());
}
}
#[test]
fn test_world_create_with_config() {
unsafe {
let config = alice_physics_config_default();
let world = alice_physics_world_create_with_config(config);
assert!(!world.is_null());
alice_physics_world_destroy(world);
}
}
#[test]
fn test_body_add_and_get() {
unsafe {
let world = alice_physics_world_create();
let pos = AliceVec3 {
x: 1.0,
y: 2.0,
z: 3.0,
};
let id = alice_physics_body_add_dynamic(world, pos, 1.0);
assert_eq!(id, 0);
assert_eq!(alice_physics_world_body_count(world), 1);
let mut out = AliceVec3 {
x: 0.0,
y: 0.0,
z: 0.0,
};
let ok = alice_physics_body_get_position(world, id, &mut out);
assert_eq!(ok, 1);
assert!((out.x - 1.0).abs() < 1e-10);
assert!((out.y - 2.0).abs() < 1e-10);
assert!((out.z - 3.0).abs() < 1e-10);
alice_physics_world_destroy(world);
}
}
#[test]
fn test_body_get_invalid_id() {
unsafe {
let world = alice_physics_world_create();
let mut out = AliceVec3 {
x: 0.0,
y: 0.0,
z: 0.0,
};
let ok = alice_physics_body_get_position(world, 999, &mut out);
assert_eq!(ok, 0);
alice_physics_world_destroy(world);
}
}
#[test]
fn test_null_world_safety() {
unsafe {
let null: *mut PhysicsWorld = std::ptr::null_mut();
assert_eq!(alice_physics_world_body_count(null), 0);
assert_eq!(alice_physics_world_step(null, 0.016), 0);
assert_eq!(alice_physics_world_step_n(null, 0.016, 10), 0);
let pos = AliceVec3 {
x: 0.0,
y: 0.0,
z: 0.0,
};
assert_eq!(alice_physics_body_add_dynamic(null, pos, 1.0), u32::MAX);
}
}
#[test]
fn test_step_and_gravity() {
unsafe {
let world = alice_physics_world_create();
let pos = AliceVec3 {
x: 0.0,
y: 10.0,
z: 0.0,
};
let id = alice_physics_body_add_dynamic(world, pos, 1.0);
for _ in 0..60 {
alice_physics_world_step(world, 1.0 / 60.0);
}
let mut out = AliceVec3 {
x: 0.0,
y: 0.0,
z: 0.0,
};
alice_physics_body_get_position(world, id, &mut out);
assert!(out.y < 10.0, "Body should fall under gravity, y={}", out.y);
alice_physics_world_destroy(world);
}
}
#[test]
fn test_batch_positions() {
unsafe {
let world = alice_physics_world_create();
let p1 = AliceVec3 {
x: 1.0,
y: 2.0,
z: 3.0,
};
let p2 = AliceVec3 {
x: 4.0,
y: 5.0,
z: 6.0,
};
alice_physics_body_add_static(world, p1);
alice_physics_body_add_static(world, p2);
let mut buf = [0.0f64; 6];
let ok = alice_physics_world_get_positions_batch(world, buf.as_mut_ptr(), 6);
assert_eq!(ok, 1);
assert!((buf[0] - 1.0).abs() < 1e-10);
assert!((buf[1] - 2.0).abs() < 1e-10);
assert!((buf[2] - 3.0).abs() < 1e-10);
assert!((buf[3] - 4.0).abs() < 1e-10);
assert!((buf[4] - 5.0).abs() < 1e-10);
assert!((buf[5] - 6.0).abs() < 1e-10);
let fail = alice_physics_world_get_positions_batch(world, buf.as_mut_ptr(), 3);
assert_eq!(fail, 0);
alice_physics_world_destroy(world);
}
}
#[test]
fn test_state_serialization_ffi() {
unsafe {
let world = alice_physics_world_create();
let pos = AliceVec3 {
x: 0.0,
y: 10.0,
z: 0.0,
};
alice_physics_body_add_dynamic(world, pos, 1.0);
let mut len: u32 = 0;
let data = alice_physics_state_serialize(world, &mut len);
assert!(!data.is_null());
assert!(len > 0);
let ok = alice_physics_state_deserialize(world, data, len);
assert_eq!(ok, 1);
alice_physics_state_free(data, len);
alice_physics_world_destroy(world);
}
}
#[test]
fn test_impulse_application() {
unsafe {
let world = alice_physics_world_create();
alice_physics_world_set_gravity(world, 0.0, 0.0, 0.0);
let pos = AliceVec3 {
x: 0.0,
y: 0.0,
z: 0.0,
};
let id = alice_physics_body_add_dynamic(world, pos, 1.0);
let impulse = AliceVec3 {
x: 10.0,
y: 0.0,
z: 0.0,
};
alice_physics_body_apply_impulse(world, id, impulse);
alice_physics_world_step(world, 1.0 / 60.0);
let mut out = AliceVec3 {
x: 0.0,
y: 0.0,
z: 0.0,
};
alice_physics_body_get_position(world, id, &mut out);
assert!(out.x > 0.0, "Impulse should move body right, x={}", out.x);
alice_physics_world_destroy(world);
}
}
#[test]
fn test_body_info() {
unsafe {
let world = alice_physics_world_create();
let pos = AliceVec3 {
x: 1.0,
y: 2.0,
z: 3.0,
};
let id = alice_physics_body_add_sensor(world, pos);
let mut info = std::mem::zeroed::<AliceBodyInfo>();
let ok = alice_physics_body_get_info(world, id, &mut info);
assert_eq!(ok, 1);
assert_eq!(info.is_sensor, 1);
assert!((info.position.x - 1.0).abs() < 1e-10);
alice_physics_world_destroy(world);
}
}
#[test]
fn ffi_contract_gravity_fall_deterministic() {
unsafe {
let world = alice_physics_world_create();
let pos = AliceVec3 {
x: 0.0,
y: 10.0,
z: 0.0,
};
let mass = 1.0_f64;
let id = alice_physics_body_add_dynamic(world, pos, mass);
for _ in 0..60 {
let _ = alice_physics_world_step(world, 1.0 / 60.0);
}
let mut info = std::mem::zeroed::<AliceBodyInfo>();
let ok = alice_physics_body_get_info(world, id, &mut info);
assert_eq!(ok, 1);
assert!(
info.position.y < 10.0,
"body must fall under gravity, got y={}",
info.position.y
);
assert!(
info.position.y > -20.0,
"final y out of expected range, got y={}",
info.position.y
);
assert!(
info.position.x.abs() < 1e-9,
"no horizontal drift expected, got x={}",
info.position.x
);
assert!(
info.position.z.abs() < 1e-9,
"no horizontal drift expected, got z={}",
info.position.z
);
let mut raw = std::mem::zeroed::<AliceVec3Fix128Raw>();
let ok_raw = alice_physics_body_get_position_fix128_raw(world, id, &mut raw);
assert_eq!(ok_raw, 1, "raw fix128 getter must succeed");
let world2 = alice_physics_world_create();
let pos2 = AliceVec3 {
x: 0.0,
y: 10.0,
z: 0.0,
};
let id2 = alice_physics_body_add_dynamic(world2, pos2, mass);
for _ in 0..60 {
let _ = alice_physics_world_step(world2, 1.0 / 60.0);
}
let mut raw2 = std::mem::zeroed::<AliceVec3Fix128Raw>();
let ok_raw2 = alice_physics_body_get_position_fix128_raw(world2, id2, &mut raw2);
assert_eq!(
ok_raw2, 1,
"raw fix128 getter must succeed on the replay world"
);
alice_physics_world_destroy(world2);
assert_eq!(raw.x.hi, raw2.x.hi, "byte-for-byte determinism (x.hi)");
assert_eq!(raw.x.lo, raw2.x.lo, "byte-for-byte determinism (x.lo)");
assert_eq!(raw.y.hi, raw2.y.hi, "byte-for-byte determinism (y.hi)");
assert_eq!(raw.y.lo, raw2.y.lo, "byte-for-byte determinism (y.lo)");
assert_eq!(raw.z.hi, raw2.z.hi, "byte-for-byte determinism (z.hi)");
assert_eq!(raw.z.lo, raw2.z.lo, "byte-for-byte determinism (z.lo)");
const GOLDEN: [(i64, u64); 3] = [(0, 0), (8, 6_631_112_686_738_674_129), (0, 0)];
assert_eq!(
[
(raw.x.hi, raw.x.lo),
(raw.y.hi, raw.y.lo),
(raw.z.hi, raw.z.lo)
],
GOLDEN,
"FFI free-fall golden raw Fix128 pair drifted"
);
alice_physics_world_destroy(world);
}
}
}