Skip to main content

alice_physics/
lib.rs

1//! ALICE-Physics: Deterministic 128-bit Fixed-Point Physics Engine
2//!
3//! A high-precision physics engine designed for deterministic simulation
4//! across different platforms and hardware. Uses 128-bit fixed-point arithmetic
5//! to ensure bit-exact results regardless of CPU or compiler.
6//!
7//! # Features
8//!
9//! - **Deterministic**: the Fix128 core (rigid bodies, constraints, joints,
10//!   BVH, CCD) is bit-exact across all platforms — pure integer arithmetic.
11//!   <!-- claim-test: test_multi_step_bit_exact -->
12//!   Grid / engineering modules on `f32` / `f64` are bit-exact via `det_math`
13//!   (correctly-rounded software transcendental functions); see README
14//!   "Determinism" for the per-module table.
15//!   <!-- claim-test: determinism_freefall -->
16//! - **128-bit Fixed-Point**: I64F64 format with CORDIC trigonometry
17//! - **XPBD Solver**: Extended Position Based Dynamics for stable constraints
18//! - **GJK/EPA**: Robust collision detection for convex shapes
19//! - **Linear BVH**: Morton code-based spatial acceleration (used for broad-phase and raycast)
20//! - **Rollback Support**: State serialization for netcode
21//!
22//! # Feature Flags
23//!
24//! <!-- readme-sync: features -->
25//! | Feature | Description |
26//! |---------|-------------|
27//! | `std` (default) | Standard library support. Disable for `no_std` (requires `alloc`). |
28//! | `simd` | SSE2 acceleration for `Vec3Fix` math ops (`dot`, `add`, `sub`). Results remain bit-exact. Solver hot paths use scalar arithmetic to guarantee cross-platform determinism. |
29//! | `parallel` | Parallel constraint solving via Rayon with graph-colored batches. |
30//! | `neural` | Deterministic neural controller integration (ALICE-ML). |
31//! | `python` | `PyO3` + `NumPy` zero-copy Python bindings. |
32//! | `replay` | Replay recording/playback via ALICE-DB. |
33//! | `ffi` | C FFI for Unity/UE5/game engine integration. |
34//! | `wasm` | WebAssembly bindings via wasm-bindgen. |
35//! | `analytics` | ALICE-Analytics simulation profiling (`DDSketch`, `HyperLogLog`). |
36//! | `gpu-solver-bridge` | `GpuSolverBridge` trait for external GPU solver backends. No cost when off. |
37//! | `privacy-crypto` | `privacy` re-exports `alice-crypto`'s differential-privacy primitives and adds `KeyedRappor`. Off by default; `alice-crypto` becomes a public dependency. |
38//!
39//! # Example
40//!
41//! ```rust
42//! use alice_physics::{PhysicsWorld, PhysicsConfig, RigidBody, Fix128, Vec3Fix};
43//!
44//! // Create physics world
45//! let config = PhysicsConfig::default();
46//! let mut world = PhysicsWorld::new(config);
47//!
48//! // Add a dynamic body
49//! let body = RigidBody::new_dynamic(
50//!     Vec3Fix::from_int(0, 10, 0),  // position
51//!     Fix128::ONE,                   // mass = 1
52//! );
53//! let body_id = world.add_body(body);
54//!
55//! // Step simulation (60 frames at 1/60 second)
56//! let dt = Fix128::from_ratio(1, 60);
57//! for _ in 0..60 {
58//!     world.step(dt);
59//! }
60//!
61//! // Body should have fallen under gravity
62//! let pos = world.bodies[body_id].position;
63//! assert!(pos.y < Fix128::from_int(10), "Body fell under gravity");
64//! ```
65//!
66//! # Modules
67//!
68//! ## Core
69//! - [`math`]: Fixed-point math primitives (Fix128, `Vec3Fix`, `QuatFix`, `Mat3Fix`, CORDIC)
70//! - [`collider`]: Collision shapes (Sphere, Capsule, `ConvexHull`, AABB) and GJK/EPA detection
71//! - [`solver`]: XPBD physics solver, rigid body dynamics, constraint batching
72//! - [`bvh`]: Linear BVH for broad-phase collision detection (Morton codes, stackless traversal)
73//! - [`error`]: Unified physics error type (`PhysicsError`)
74//!
75//! ## Collision Shapes
76//! - [`box_collider`]: Oriented Bounding Box (OBB) with GJK support
77//! - [`compound`]: Multi-shape compound collider with local transforms
78//! - [`cone`]: Cone collider with GJK support (apex +Y, base -Y)
79//! - [`convex_mesh_builder`]: Incremental convex hull builder from point sets
80//! - [`cylinder`]: Cylinder collider with GJK support
81//! - [`ellipsoid`]: Ellipsoid collider with three independent semi-axes
82//! - [`plane_collider`]: Infinite plane collider (sphere/AABB intersection)
83//! - [`sdf_collider`]: SDF-based collision shapes (distance field surfaces)
84//! - [`spherical_terrain`]: Sphere-world ground as a radial height field, and central gravity
85//! - [`torus`]: Torus collider with major/minor radii
86//! - [`wedge`]: Wedge (triangular prism) collider with 6 vertices
87//!
88//! ## Spatial Acceleration
89//! - [`dynamic_bvh`]: Incremental AABB tree with O(log n) insert/remove/update
90//! - [`spatial`]: Hash grid for neighbor queries (shared across fluid, cloth)
91//! - [`contact_cache`]: Persistent contact manifold cache with `HashMap` O(1) lookup
92//!
93//! ## Constraints & Dynamics
94//! - [`joint`]: Joint constraints (Ball, Hinge, Fixed, Slider, Spring, D6, `ConeTwist`)
95//! - [`joint_extra`]: Advanced joints (Pulley, Gear, Weld, Rack-and-Pinion, Mouse)
96//! - [`motor`]: PD controllers and joint motors
97//! - [`articulation`]: Articulated bodies (ragdolls, robotic arms, Featherstone)
98//! - [`force`]: Custom force fields (wind, gravity, buoyancy, vortex, explosion, magnetic)
99//! - [`sleeping`]: Sleep/wake and island management (Union-Find)
100//! - [`filter`]: Collision filtering with layer/mask bitmasks (see [`filter::layers`])
101//! - [`material`]: Material pair table (friction, restitution, combine rules)
102//!
103//! ## Queries
104//! - [`raycast`]: Ray casting against spheres, AABBs, capsules, planes
105//! - [`shape_raycast`]: World ray queries against the geometry bodies collide as (shapes, compounds, static colliders, SDF)
106//! - [`sensors`]: Simulated lidar, contact sensor and IMU with deterministic Gaussian noise
107//! - [`query`]: Shape cast (sphere, capsule) and overlap queries (sphere, AABB)
108//! - [`ccd`]: Continuous collision detection (TOI, conservative advancement, speculative)
109//!
110//! ## Soft Body & Simulation
111//! - [`rope`]: XPBD distance chain rope and cable simulation
112//! - [`cloth`]: XPBD triangle mesh cloth with self-collision
113//! - [`fluid`]: Position-Based Fluids (PBF) with spatial hash grid
114//! - [`deformable`]: FEM-XPBD deformable body simulation
115//! - [`vehicle`]: Vehicle physics (wheel, suspension, engine, steering)
116//! - [`vehicle_dynamics`]: Per-wheel vehicle dynamics (tyre models, wheel spin, brakes / ABS, road surfaces and weather, powertrain, scenario metrics and lossless replay)
117//! - [`character`]: Kinematic capsule-based character controller (move-and-slide)
118//! - [`world_character`]: `PhysicsWorld::move_character`, move-and-slide of a character against the world's collided geometry
119//! - [`trimesh`]: Triangle mesh collision with BVH acceleration (Moller-Trumbore)
120//! - [`heightfield`]: Height field terrain collision (bilinear interpolation)
121//! - [`cloth_fluid`]: Cloth-fluid coupling (drag, buoyancy, boundary repulsion)
122//! - [`rope_attach`]: Rope attachment to rigid bodies with compliance and break force
123//! - [`soft_body_cut`]: Plane-based cutting of deformable bodies and cloth
124//! - [`particle`]: General-purpose particle system with emitters and force fields
125//!
126//! ## 2D Physics
127//! - [`physics2d`]: Complete 2D engine (SAT collision, XPBD solver, joints)
128//!
129//! ## Mesh & I/O Utilities
130//! - [`mass_properties`]: Mass and inertia tensor computation for standard shapes
131//! - [`collision_mesh_gen`]: Marching cubes SDF-to-mesh with simplification
132//! - [`scene_io`]: Binary (.aphys) and JSON scene serialization (`std`)
133//! - [`multi_world`]: Multiple independent physics worlds with body transfer
134//!
135//! ## Visualization
136//! - [`heatmap`]: Stress/temperature heatmap with viridis colormap
137//! - [`flow_viz`]: Fluid flow arrows and streamline generation
138//! - [`contact_viz`]: Contact force arrows and friction cone visualization
139//!
140//! ## SDF Integration
141//! - [`sdf_manifold`]: Multi-point contact manifold from SDF surfaces
142//! - [`sdf_ccd`]: Sphere tracing continuous collision detection for SDF
143//! - [`sdf_force`]: SDF-driven force fields (attract, repel, contain, flow)
144//! - [`sdf_destruction`]: Real-time CSG boolean destruction (`std`)
145//! - [`sdf_adaptive`]: Adaptive SDF evaluation with distance-based LOD (`std`)
146//! - [`convex_decompose`]: Convex decomposition from SDF voxel grid (`std`)
147//! - [`gpu_sdf`]: GPU compute shader interface for batch SDF evaluation (`std`)
148//!
149//! ## SDF Simulation Modifiers (`std`)
150//! - [`sim_field`]: 3D scalar/vector fields with trilinear interpolation and diffusion
151//! - [`sim_modifier`]: Physics modifier chain for SDF surfaces
152//! - [`thermal`]: Heat diffusion, melt, thermal expansion, freeze
153//! - [`pressure`]: Contact-force deformation (crush, bulge, dent)
154//! - [`erosion`]: Wind, water, chemical, ablation erosion
155//! - [`fracture`]: Stress-driven crack propagation with CSG subtraction
156//! - [`phase_change`]: Solid/liquid/gas transitions driven by temperature
157//! - [`coupled_field`]: Deterministic `Fix128` channel for sharing one field between physics
158//!
159//! ## Game Systems
160//! - [`animation_blend`]: Ragdoll animation blending with SLERP
161//! - [`audio_physics`]: Physics-based audio parameter generation
162//! - [`netcode`]: Deterministic simulation with frame input, checksum, rollback
163//! - [`fluid_netcode`]: Deterministic fluid netcode with delta compression (`std`)
164//! - [`interpolation`]: Substep interpolation with SLERP quaternion blending
165//! - [`debug_render`]: Wireframe visualization API (bodies, contacts, joints, BVH)
166//! - [`profiling`]: Per-stage timer and per-frame statistics
167//!
168//! ## Analytics & Privacy (`std`)
169//! - [`sketch`]: Probabilistic sketches (`HyperLogLog`, `DDSketch`, Count-Min)
170//! - [`anomaly`]: Streaming anomaly detection (MAD, EWMA, Z-score)
171//! - [`privacy`]: Local differential privacy (Laplace, RAPPOR)
172//! - [`pipeline`]: Lock-free metric aggregation pipeline
173//!
174//! ## Utility
175//! - [`rng`]: Deterministic pseudo-random number generator (PCG-XSH-RR)
176//! - [`event`]: Contact and trigger event tracking
177//!
178//! ## Feature-Gated
179//! - `neural`: Deterministic neural controller (ALICE-ML, `--features neural`)
180//! - `ffi`: C FFI for Unity/UE5 game engines (`--features ffi`)
181//! - `replay`: Replay recording/playback (`--features replay`)
182//! - `db_bridge`: Physics state persistence bridge (`--features replay`)
183//! - `analytics_bridge`: Simulation profiling bridge (`--features analytics`)
184//!
185//! # Determinism
186//!
187//! This engine guarantees bit-exact results by:
188//!
189//! 1. Using 128-bit fixed-point instead of IEEE 754 floating-point
190//! 2. CORDIC algorithm for trigonometry (no FPU instructions)
191//! 3. Fixed iteration counts in all algorithms
192//! 4. Deterministic sorting (stable sort with explicit comparators)
193//!
194//! This makes it suitable for:
195//! - Lockstep multiplayer games
196//! - Rollback netcode
197//! - Replay systems
198//! - Distributed physics simulation
199
200// TGS solver family (`solver_tgs*`) modules opt out of missing_docs
201// locally with `#![allow(missing_docs)]` while their per-item `///`
202// docs are being backfilled (Turn E follow-up). The rest of the crate
203// is now under `deny` — verified 0 warnings via
204// `cargo doc --no-deps --all-features` at v0.14.0-preview.3 landing,
205// escalated from `warn` on 2026-09-13 (v1.0 roadmap Item D).
206#![deny(missing_docs)]
207// Pedantic: suppress lints inherent to physics/math code.
208// - Cast lints: Fixed-point ↔ float conversions and index casts are pervasive.
209// - Naming: coordinate variables (px, py, pz), vertex indices (v0, v1, v2).
210// - must_use: pure functions returning computed values are the norm; annotating
211//   every constructor and accessor adds noise without improving safety.
212// - doc_markdown: technical identifiers in docs are context-obvious.
213// - unreadable_literal: hash constants and bit patterns are more readable without separators.
214// - large_stack_arrays: physics lookup tables and inline data are intentional.
215#![allow(
216    clippy::cast_possible_truncation,
217    clippy::cast_precision_loss,
218    clippy::cast_sign_loss,
219    clippy::cast_lossless,
220    clippy::cast_possible_wrap,
221    clippy::similar_names,
222    clippy::many_single_char_names,
223    clippy::module_name_repetitions,
224    clippy::unreadable_literal,
225    clippy::large_stack_arrays,
226    clippy::doc_markdown
227)]
228#![cfg_attr(not(feature = "std"), no_std)]
229
230#[cfg(all(feature = "wasm", feature = "ffi"))]
231compile_error!("Features `wasm` and `ffi` are mutually exclusive. Enable only one.");
232
233#[cfg(all(feature = "wasm", not(feature = "std")))]
234compile_error!("Feature `wasm` requires `std`.");
235
236#[cfg(not(feature = "std"))]
237extern crate alloc;
238
239// Test builds only: lets a module compile an integration test file (which
240// names the crate `alice_physics`) as part of the lib tests, so its lines count
241// in the `--lib` coverage.
242#[cfg(test)]
243extern crate self as alice_physics;
244
245pub mod acoustic_wave;
246pub mod aeroelasticity;
247#[cfg(feature = "analytics")]
248pub mod analytics_bridge;
249pub mod animation_blend;
250pub mod anisotropic;
251pub mod anisotropic_friction;
252#[cfg(feature = "std")]
253pub mod anomaly;
254pub mod articulation;
255pub mod atmosphere;
256pub mod audio_physics;
257pub mod beam_stress;
258pub mod bimaterial;
259pub mod box_collider;
260#[cfg(feature = "std")]
261pub mod bridging;
262pub mod buckling;
263pub mod buoyancy_zone;
264pub mod bvh;
265pub mod ccd;
266#[cfg(feature = "std")]
267pub mod cfd_solver;
268pub mod character;
269pub mod character_state;
270pub mod cloth;
271pub mod cloth_fluid;
272pub mod collider;
273pub mod collision_mesh_gen;
274pub mod compound;
275pub mod compressible;
276pub mod cone;
277pub mod contact_cache;
278pub mod contact_viz;
279#[cfg(feature = "std")]
280pub mod convex_decompose;
281pub mod convex_mesh_builder;
282pub mod coupled_field;
283pub mod coupled_iteration;
284pub mod coupling_medium;
285pub mod creep_longterm;
286pub mod crowd_force;
287pub mod cubic_elastic_fem;
288pub mod cylinder;
289pub mod damping_rayleigh;
290#[cfg(feature = "replay")]
291pub mod db_bridge;
292pub mod debug_render;
293pub mod deformable;
294/// Deterministic `f32` / `f64` transcendentals (cross-platform bit-exact).
295///
296/// Since 1.3.0 the implementation lives in the [`alice_det_math`] crate;
297/// every function here is an `#[inline]` delegation to it, so
298/// `alice_physics::det_math::sin` and `alice_det_math::sin` are the same law
299/// — ALICE-SDF's evaluator uses the same crate, which is what makes the
300/// `SdfField` boundary bit-exact across the two. Available without `std`
301/// (software `sqrt`; same bits).
302///
303/// The functions are delegations rather than a `pub use` because
304/// `cargo-semver-checks` cannot see items re-exported from another crate and
305/// would report the 1.2.0 API as removed.
306pub mod det_math {
307    pub use alice_det_math::{acos64, asin64, round, round64, sin_cos, sqrt, sqrt64};
308
309    macro_rules! delegate {
310        ($(fn $name:ident($($arg:ident: $ty:ty),*) -> $ret:ty;)*) => {
311            $(
312                #[doc = concat!("Delegates to [`alice_det_math::", stringify!($name), "`].")]
313                #[inline]
314                #[must_use]
315                pub fn $name($($arg: $ty),*) -> $ret {
316                    alice_det_math::$name($($arg),*)
317                }
318            )*
319        };
320    }
321
322    delegate! {
323        fn sin(x: f32) -> f32;
324        fn cos(x: f32) -> f32;
325        fn exp(x: f32) -> f32;
326        fn ln(x: f32) -> f32;
327        fn powf(x: f32, y: f32) -> f32;
328        fn powi(x: f32, n: i32) -> f32;
329        fn cbrt(x: f32) -> f32;
330        fn hypot(x: f32, y: f32) -> f32;
331        fn atan(x: f32) -> f32;
332        fn atan2(y: f32, x: f32) -> f32;
333        fn asin(x: f32) -> f32;
334        fn acos(x: f32) -> f32;
335        fn tan(x: f32) -> f32;
336        fn tanh(x: f32) -> f32;
337        fn atan64(x: f64) -> f64;
338        fn atan2_64(y: f64, x: f64) -> f64;
339        fn exp64(x: f64) -> f64;
340        fn ln64(x: f64) -> f64;
341        fn powf64(x: f64, y: f64) -> f64;
342    }
343
344    // max / min with a defined result on every target and profile
345    //
346    // `f32::max` / `f32::min` (and `f64`) leave the sign of an equal pair of
347    // zeros unspecified, and the targets differ: aarch64 returns +0 from max and
348    // -0 from min, x86_64 returns the argument (`0.0f32.max(-0.0)` is -0 there),
349    // and x86_64's `f64::min(0.0, -0.0)` is -0 unoptimized and +0 optimized The
350    // functions below fix that case: a NaN operand yields the other operand (as
351    // std does), an equal pair of zeros yields +0 from max and -0 from min (IEEE
352    // 754-2019 maximumNumber / minimumNumber, which is what aarch64 already
353    // returns), and every other pair is ordered by value Production code uses
354    // these; the lib clippy pass forbids `f32::max` / `f32::min` / `f64::max` /
355    // `f64::min` (`.github/clippy-lib/clippy.toml`)
356
357    /// `max` with a defined zero sign: NaN yields the other operand, `max(-0.0,
358    /// 0.0)` and `max(0.0, -0.0)` are `+0.0`
359    #[inline]
360    #[must_use]
361    pub(crate) fn max(a: f32, b: f32) -> f32 {
362        if a.is_nan() || b > a {
363            b
364        } else if b.is_nan() || a > b || !a.is_sign_negative() {
365            a
366        } else {
367            b
368        }
369    }
370
371    /// `min` with a defined zero sign: NaN yields the other operand, `min(-0.0,
372    /// 0.0)` and `min(0.0, -0.0)` are `-0.0`
373    #[inline]
374    #[must_use]
375    pub(crate) fn min(a: f32, b: f32) -> f32 {
376        if a.is_nan() || b < a {
377            b
378        } else if b.is_nan() || a < b || a.is_sign_negative() {
379            a
380        } else {
381            b
382        }
383    }
384
385    /// [`max`] for `f64`
386    #[inline]
387    #[must_use]
388    pub(crate) fn max64(a: f64, b: f64) -> f64 {
389        if a.is_nan() || b > a {
390            b
391        } else if b.is_nan() || a > b || !a.is_sign_negative() {
392            a
393        } else {
394            b
395        }
396    }
397
398    /// [`min`] for `f64`
399    #[inline]
400    #[must_use]
401    pub(crate) fn min64(a: f64, b: f64) -> f64 {
402        if a.is_nan() || b < a {
403            b
404        } else if b.is_nan() || a < b || a.is_sign_negative() {
405            a
406        } else {
407            b
408        }
409    }
410
411    #[cfg(test)]
412    mod max_min_tests {
413        use super::{max, max64, min, min64};
414
415        // the expected value on {-inf, -1, -0, +0, +1, +inf, NaN}^2, written out
416        // by rule rather than computed with the std functions under test
417        fn expected_max(a: f64, b: f64) -> f64 {
418            match (a.is_nan(), b.is_nan()) {
419                (true, _) => b,
420                (_, true) => a,
421                _ if a > b => a,
422                _ if b > a => b,
423                // equal: only the zero pair can differ in bits
424                _ => {
425                    if a != 0.0 {
426                        a
427                    } else if a.is_sign_negative() && b.is_sign_negative() {
428                        -0.0
429                    } else {
430                        0.0
431                    }
432                }
433            }
434        }
435
436        fn expected_min(a: f64, b: f64) -> f64 {
437            match (a.is_nan(), b.is_nan()) {
438                (true, _) => b,
439                (_, true) => a,
440                _ if a < b => a,
441                _ if b < a => b,
442                _ => {
443                    if a == 0.0 && (a.is_sign_negative() || b.is_sign_negative()) {
444                        -0.0
445                    } else {
446                        a
447                    }
448                }
449            }
450        }
451
452        const LATTICE: [f64; 7] = [
453            f64::NEG_INFINITY,
454            -1.0,
455            -0.0,
456            0.0,
457            1.0,
458            f64::INFINITY,
459            f64::NAN,
460        ];
461
462        fn same(x: f64, y: f64) -> bool {
463            (x.is_nan() && y.is_nan()) || x.to_bits() == y.to_bits()
464        }
465
466        #[test]
467        fn max_and_min_follow_the_rule_on_the_signed_zero_lattice() {
468            let mut compared = 0;
469            for &a in &LATTICE {
470                for &b in &LATTICE {
471                    // black_box keeps the comparisons at run time, so the
472                    // profile under test compiles the branches itself
473                    let (x, y) = (core::hint::black_box(a), core::hint::black_box(b));
474                    let (xf, yf) = (x as f32, y as f32);
475                    assert!(
476                        same(max64(x, y), expected_max(a, b)),
477                        "max64({a:?}, {b:?}) = {:?}",
478                        max64(x, y)
479                    );
480                    assert!(
481                        same(min64(x, y), expected_min(a, b)),
482                        "min64({a:?}, {b:?}) = {:?}",
483                        min64(x, y)
484                    );
485                    assert!(
486                        same(f64::from(max(xf, yf)), expected_max(a, b)),
487                        "max({a:?}, {b:?}) = {:?}",
488                        max(xf, yf)
489                    );
490                    assert!(
491                        same(f64::from(min(xf, yf)), expected_min(a, b)),
492                        "min({a:?}, {b:?}) = {:?}",
493                        min(xf, yf)
494                    );
495                    compared += 4;
496                }
497            }
498            assert_eq!(compared, 4 * 49);
499        }
500
501        #[test]
502        fn the_zero_pair_has_the_documented_sign_in_both_orders() {
503            let (p, n) = (
504                core::hint::black_box(0.0f32),
505                core::hint::black_box(-0.0f32),
506            );
507            assert_eq!(max(p, n).to_bits(), 0x0000_0000);
508            assert_eq!(max(n, p).to_bits(), 0x0000_0000);
509            assert_eq!(min(p, n).to_bits(), 0x8000_0000);
510            assert_eq!(min(n, p).to_bits(), 0x8000_0000);
511            let (p, n) = (
512                core::hint::black_box(0.0f64),
513                core::hint::black_box(-0.0f64),
514            );
515            assert_eq!(max64(p, n).to_bits(), 0);
516            assert_eq!(max64(n, p).to_bits(), 0);
517            assert_eq!(min64(p, n).to_bits(), 0x8000_0000_0000_0000);
518            assert_eq!(min64(n, p).to_bits(), 0x8000_0000_0000_0000);
519        }
520    }
521}
522#[cfg(any(feature = "ffi", feature = "wasm", feature = "python"))]
523mod binding_api;
524pub mod dynamic_bvh;
525#[cfg(feature = "std")]
526pub mod dynamic_fem;
527pub mod electromagnetic;
528pub mod ellipsoid;
529#[cfg(feature = "std")]
530pub mod erosion;
531pub mod error;
532pub mod euler_fv;
533pub mod eulerian_grid;
534pub mod event;
535pub mod fatigue;
536#[cfg(feature = "ffi")]
537pub mod ffi;
538pub mod filament_db;
539
540pub(crate) mod body_collider;
541pub mod fillet_stress;
542pub mod filter;
543#[cfg(feature = "std")]
544pub mod flow_viz;
545pub mod fluid;
546#[cfg(feature = "std")]
547pub mod fluid_netcode;
548pub mod force;
549#[cfg(feature = "std")]
550pub mod fracture;
551#[cfg(feature = "std")]
552pub mod fsi_advanced;
553#[cfg(feature = "std")]
554pub mod gpu_sdf;
555pub mod heatmap;
556pub mod heightfield;
557pub mod hyperelastic;
558#[cfg(feature = "std")]
559pub mod ik_physics_bridge;
560pub mod interface_capture;
561pub mod interpolation;
562pub mod joint;
563pub mod joint_extra;
564pub mod kepler;
565pub mod kinematic_loop;
566#[cfg(feature = "std")]
567pub mod laminate;
568pub mod laminate_failure;
569// Content identifier of the rules a world is configured with.
570pub mod law_id;
571pub mod layer_adhesion;
572pub mod lift_drag;
573/// Small-strain linear elastic FEM on tetrahedra (P1), producing a Cauchy
574/// stress tensor per element from an [`sdf_fem_mesh::SdfTetMesh`].
575///
576/// `std` only, because its input is: [`sdf_fem_mesh`] deduplicates vertices
577/// through a `HashMap`, which `alloc` does not provide, so it carries the same
578/// gate. Adding `alloc` imports here would not help — the module cannot be
579/// built at all without the type it consumes.
580#[cfg(feature = "std")]
581pub mod linear_elastic_fem;
582pub mod linear_solver;
583pub mod mass_properties;
584pub mod material;
585pub mod math;
586pub mod math_util;
587pub mod maxwell_fdtd;
588pub mod metric;
589pub mod modal;
590pub mod molecular_dynamics;
591pub mod motor;
592pub mod multi_world;
593pub mod multiphase;
594pub mod nbody;
595pub mod netcode;
596#[cfg(feature = "std")]
597pub mod netcode_prediction;
598#[cfg(feature = "neural")]
599pub mod neural;
600pub mod non_newtonian;
601pub mod pair_potential;
602pub mod particle;
603#[cfg(feature = "std")]
604pub mod phase_change;
605#[cfg(feature = "std")]
606pub mod piezoelectric;
607#[cfg(feature = "std")]
608pub mod pipeline;
609pub mod plane_collider;
610pub mod plastic;
611#[cfg(feature = "std")]
612pub mod pressure;
613pub mod prestressed;
614pub mod print_orientation;
615#[cfg(feature = "std")]
616pub mod print_pipeline_solver;
617#[cfg(feature = "std")]
618pub mod privacy;
619pub mod profiling;
620#[cfg(feature = "python")]
621mod python;
622pub mod quadratic_elastic_fem;
623pub mod query;
624pub mod ragdoll;
625pub mod raycast;
626#[cfg(feature = "replay")]
627pub mod replay;
628pub mod rng;
629#[cfg(feature = "std")]
630pub mod rolling_contact;
631pub mod rope;
632pub mod rope_attach;
633pub mod rotor;
634#[cfg(feature = "std")]
635pub mod scene_io;
636#[cfg(feature = "std")]
637pub mod sdf_adaptive;
638pub mod sdf_ccd;
639pub mod sdf_character;
640pub mod sdf_collider;
641#[cfg(feature = "std")]
642pub mod sdf_destruction;
643#[cfg(feature = "std")]
644pub mod sdf_fem_mesh;
645pub mod sdf_force;
646pub mod sdf_manifold;
647#[cfg(feature = "std")]
648pub mod sdf_sph;
649pub mod sdf_wind_field;
650// Content hash of the stepping semantics; no `cfg`: the same value in every build.
651pub mod semantics;
652pub mod sensors;
653pub mod shape;
654pub mod shape_raycast;
655#[cfg(feature = "std")]
656pub mod sim_field;
657#[cfg(feature = "std")]
658pub mod sim_modifier;
659pub mod static_collider;
660pub mod structural_solver;
661#[cfg(feature = "std")]
662pub mod support_volume;
663pub mod surface_tension_csf;
664#[cfg(feature = "std")]
665pub mod thin_wall;
666pub mod vibration_wall;
667pub mod warp_risk;
668pub mod wave_ship;
669pub mod wind_zone;
670pub mod world_character;
671pub mod world_participant;
672pub mod world_shape_query;
673
674pub mod physics2d;
675
676#[cfg(feature = "gpu-solver-bridge")]
677pub mod gpu_bridge;
678#[cfg(feature = "std")]
679pub mod sketch;
680pub mod sleeping;
681pub mod smoke_fire;
682pub mod soft_body_cut;
683pub mod solver;
684// Gyroscopic term `ω × Iω` by symplectic splitting, shared by XPBD and TGS.
685pub(crate) mod gyroscopic;
686// solver_tgs* family: pub(crate) since v0.14.0-preview.8 (v1.0 Item B
687// Option C — TGS extension mechanism with zero downstream adoption is
688// hidden from the public surface; re-expose via semver-minor bump if
689// concrete demand emerges).
690#[cfg(feature = "std")]
691pub(crate) mod det_hash;
692#[cfg(feature = "std")]
693pub(crate) mod solver_tgs;
694#[cfg(feature = "std")]
695pub(crate) mod solver_tgs_hooks_6dof_oriented;
696#[cfg(feature = "std")]
697pub(crate) mod solver_tgs_hooks_6dof_oriented_scoped;
698// Adapter from `solver::RigidBody` / `ContactConstraint` to the solver_tgs*
699// family's oriented body/contact types — the production entry point for
700// `SolverBackend::Tgs` (`PhysicsWorld::step_tgs`). std-gated for the same
701// reason as the rest of `solver_tgs*` (the family uses `std::collections::HashMap`).
702#[cfg(feature = "std")]
703pub(crate) mod solver_tgs_backend;
704pub mod spatial;
705pub mod spherical_terrain;
706#[cfg(feature = "std")]
707pub mod thermal;
708pub mod thermal_stress;
709pub mod torus;
710#[cfg(feature = "std")]
711pub mod transient_thermal;
712pub mod trimesh;
713pub mod turbulence;
714pub mod vehicle;
715pub mod vehicle_dynamics;
716#[cfg(feature = "wasm")]
717mod wasm;
718pub mod wedge;
719
720// Re-export commonly used types
721pub use animation_blend::{AnimationBlender, AnimationClip, BlendMode, SkeletonPose};
722#[cfg(feature = "std")]
723pub use anomaly::{
724    AnomalyCallback, AnomalyEvent, CompositeDetector, EwmaDetector, MadDetector, StreamingMedian,
725    ZScoreDetector,
726};
727pub use articulation::{ArticulatedBody, FeatherstoneSolver};
728pub use audio_physics::{AudioConfig, AudioEvent, AudioGenerator, AudioMaterial};
729pub use box_collider::OrientedBox;
730pub use bvh::{BvhNode, BvhPrimitive, LinearBvh};
731pub use ccd::{
732    aabb_plane_toi, capsule_plane_toi, speculative_contact, sphere_capsule_toi, CcdConfig,
733};
734pub use character::{CharacterConfig, CharacterController, MoveResult, PushImpulse};
735pub use cloth::{Cloth, ClothConfig};
736pub use cloth_fluid::{
737    apply_cloth_boundary_to_fluid, apply_fluid_forces_to_cloth, ClothFluidCoupling,
738};
739pub use collider::{Capsule, CollisionResult, ConvexHull, ScaledShape, Sphere, Support, AABB};
740pub use collision_mesh_gen::{
741    compute_mesh_aabb, generate_collision_mesh, simplify_collision_mesh, CollisionMesh,
742    CollisionMeshConfig,
743};
744#[cfg(feature = "std")]
745pub use compound::{CompoundShape, ShapeRef};
746pub use cone::Cone;
747pub use contact_cache::{BodyPairKey, ContactCache, ContactManifold};
748pub use contact_viz::{
749    generate_contact_arrows, generate_friction_cones, ContactArrow, FrictionCone,
750};
751#[cfg(feature = "std")]
752pub use convex_decompose::{DecomposeConfig, DecompositionResult};
753pub use convex_mesh_builder::{build_convex_hull, compute_centroid};
754pub use cylinder::Cylinder;
755pub use debug_render::{debug_draw_world, DebugColor, DebugDrawData, DebugDrawFlags};
756pub use deformable::{DeformableBody, DeformableConfig};
757pub use dynamic_bvh::DynamicAabbTree;
758pub use ellipsoid::Ellipsoid;
759#[cfg(feature = "std")]
760pub use erosion::{ErosionConfig, ErosionModifier, ErosionType};
761pub use error::PhysicsError;
762pub use event::{ContactEvent, ContactEventType, EventCollector, JointBreakEvent, TriggerEvent};
763/// Re-export predefined collision layer constants for convenience.
764pub use filter::layers;
765pub use filter::CollisionFilter;
766#[cfg(feature = "std")]
767pub use flow_viz::{
768    generate_flow_arrows, generate_streamlines, FlowArrow, FlowVizConfig, Streamlines,
769};
770pub use fluid::{Fluid, FluidConfig};
771#[cfg(feature = "std")]
772pub use fluid_netcode::{FluidDelta, FluidSnapshot};
773pub use force::{apply_force_fields, ForceField, ForceFieldInstance};
774#[cfg(feature = "std")]
775pub use fracture::{Crack, FractureConfig, FractureModifier};
776#[cfg(feature = "std")]
777pub use gpu_sdf::{
778    batch_size, GpuDispatchConfig, GpuSdfBatch, GpuSdfInstancedBatch, GpuSdfMultiDispatch,
779    GpuSdfQuery, GpuSdfResult,
780};
781pub use heatmap::{
782    generate_stress_heatmap, generate_temperature_heatmap, heatmap_to_rgba, Heatmap, HeatmapConfig,
783    SliceAxis,
784};
785pub use heightfield::HeightField;
786pub use interpolation::{BodySnapshot, InterpolationState, WorldSnapshot};
787#[allow(deprecated)] // re-exported for compatibility until 3.0
788pub use joint::{
789    solve_joints, solve_joints_breakable, solve_joints_breaking_on_force,
790    solve_joints_with_reaction_forces,
791};
792pub use joint::{
793    BallJoint, ConeTwistJoint, D6Joint, D6Motion, FixedJoint, HingeJoint, Joint, JointType,
794    SliderJoint, SpringJoint,
795};
796pub use joint_extra::{
797    solve_extra_joints, ExtraJoint, GearJoint, MouseJoint, PulleyJoint, RackAndPinionJoint,
798    WeldJoint,
799};
800pub use mass_properties::{
801    box_mass_properties, capsule_mass_properties, convex_hull_mass_properties,
802    cylinder_mass_properties, sphere_mass_properties, translate_inertia, MassProperties,
803};
804pub use material::{CombineRule, CombinedMaterial, MaterialId, MaterialTable, PhysicsMaterial};
805pub use math::{simd_width, Fix128, Mat3Fix, QuatFix, Vec3Fix, SIMD_WIDTH};
806pub use motor::{JointMotor, MotorMode, PdController};
807pub use multi_world::{MultiWorld, Portal};
808pub use netcode::{
809    DeterministicSimulation, FrameInput, InputApplicator, NetcodeConfig, SimulationChecksum,
810    SimulationSnapshot,
811};
812pub use particle::{LandingEvent, LandingTarget, Particle, ParticleEmitter, ParticleSystem};
813#[cfg(feature = "std")]
814pub use phase_change::{Phase, PhaseChangeConfig, PhaseChangeModifier};
815pub use physics2d::{
816    BodyType2D, Contact2D, Joint2D, PhysicsConfig2D, PhysicsWorld2D, RigidBody2D, Shape2D, Vec2Fix,
817};
818#[cfg(feature = "std")]
819pub use pipeline::{
820    MetricEvent, MetricPipeline, MetricRegistry, MetricSnapshot, MetricType, RingBuffer,
821};
822pub use plane_collider::PlaneCollider;
823#[cfg(feature = "std")]
824pub use pressure::{PressureConfig, PressureModifier};
825#[cfg(feature = "privacy-crypto")]
826pub use privacy::KeyedRappor;
827#[cfg(feature = "std")]
828#[allow(deprecated)] // kept for existing callers, who see the deprecation at their use
829pub use privacy::{
830    LaplaceNoise, PrivacyBudget, PrivateAggregator, RandomizedResponse, Rappor, XorShift64,
831};
832pub use profiling::{PhysicsProfiler, ProfileEntry, StepStats};
833pub use query::{
834    batch_raycast, batch_sphere_cast, capsule_cast, overlap_aabb, overlap_aabb_bvh, overlap_sphere,
835    overlap_sphere_bvh, sphere_cast, BatchRayQuery, OverlapResult, ShapeCastHit,
836};
837pub use raycast::{
838    raycast_all_aabbs, raycast_all_spheres, raycast_any_aabbs, raycast_any_spheres, Ray, RayHit,
839};
840pub use rng::DeterministicRng;
841pub use rope::{Rope, RopeConfig};
842pub use rope_attach::{solve_rope_attachments, RopeAttachment};
843#[cfg(feature = "std")]
844pub use scene_io::{
845    load_scene, load_scene_json, save_scene, save_scene_json, PhysicsScene, SerializedBody,
846    SerializedJoint,
847};
848#[cfg(feature = "std")]
849pub use sdf_adaptive::{AdaptiveConfig, AdaptiveSdfEvaluator};
850pub use sdf_ccd::SdfCcdConfig;
851pub use sdf_collider::{ClosureSdf, SdfCollider, SdfContact, SdfField};
852#[cfg(feature = "std")]
853pub use sdf_destruction::{DestructibleSdf, DestructionShape};
854pub use sdf_force::{SdfForceField, SdfForceType};
855pub use sdf_manifold::ManifoldConfig;
856#[cfg(feature = "std")]
857pub use sdf_manifold::SdfManifold;
858pub use semantics::{PHYSICS_SEMANTICS_ID, PHYSICS_SEMANTICS_PINS};
859pub use shape::{Shape, ShapeError};
860#[cfg(feature = "std")]
861pub use sim_field::{ScalarField3D, VectorField3D};
862#[cfg(feature = "std")]
863pub use sim_modifier::{ModifiedSdf, PhysicsModifier, SingleModifiedSdf};
864#[cfg(feature = "std")]
865pub use sketch::{CountMinSketch, DDSketch, FnvHasher, HeavyHitters, HyperLogLog, Mergeable};
866pub use sleeping::{Island, IslandManager, SleepConfig, SleepData, SleepState};
867pub use soft_body_cut::{cut_cloth, cut_deformable, CutPlane, CutResult};
868#[cfg(feature = "std")]
869pub use solver::ContactModifier;
870#[cfg(feature = "std")]
871pub use solver::TgsCacheStats;
872pub use solver::{
873    BodyType, ContactConstraint, DistanceConstraint, LawCheck, PhysicsConfig, PhysicsWorld,
874    RigidBody, SolverBackend, StageWork, WorldCcdConfig, WorldSnapshotError,
875};
876pub use spatial::SpatialGrid;
877#[cfg(feature = "std")]
878pub use thermal::{HeatSource, ThermalConfig, ThermalModifier};
879pub use torus::Torus;
880pub use trimesh::{TriMesh, Triangle};
881pub use vehicle::{Vehicle, VehicleConfig};
882pub use wedge::Wedge;
883
884/// Prelude module for convenient imports
885pub mod prelude {
886    pub use crate::animation_blend::{AnimationBlender, AnimationClip, BlendMode, SkeletonPose};
887    #[cfg(feature = "std")]
888    pub use crate::anomaly::{
889        AnomalyCallback, AnomalyEvent, CompositeDetector, EwmaDetector, MadDetector,
890        StreamingMedian, ZScoreDetector,
891    };
892    pub use crate::articulation::{ArticulatedBody, FeatherstoneSolver};
893    pub use crate::audio_physics::{AudioConfig, AudioEvent, AudioGenerator, AudioMaterial};
894    pub use crate::box_collider::OrientedBox;
895    pub use crate::bvh::{BvhNode, BvhPrimitive, LinearBvh};
896    pub use crate::ccd::{speculative_contact, CcdConfig};
897    pub use crate::character::{CharacterConfig, CharacterController, MoveResult, PushImpulse};
898    pub use crate::cloth::{Cloth, ClothConfig};
899    pub use crate::cloth_fluid::{
900        apply_cloth_boundary_to_fluid, apply_fluid_forces_to_cloth, ClothFluidCoupling,
901    };
902    pub use crate::collider::{
903        Capsule, CollisionResult, ConvexHull, ScaledShape, Sphere, Support, AABB,
904    };
905    pub use crate::collision_mesh_gen::{
906        compute_mesh_aabb, generate_collision_mesh, simplify_collision_mesh, CollisionMesh,
907        CollisionMeshConfig,
908    };
909    #[cfg(feature = "std")]
910    pub use crate::compound::{CompoundShape, ShapeRef};
911    pub use crate::cone::Cone;
912    pub use crate::contact_cache::{BodyPairKey, ContactCache, ContactManifold};
913    pub use crate::contact_viz::{
914        generate_contact_arrows, generate_friction_cones, ContactArrow, FrictionCone,
915    };
916    #[cfg(feature = "std")]
917    pub use crate::convex_decompose::{DecomposeConfig, DecompositionResult};
918    pub use crate::convex_mesh_builder::{build_convex_hull, compute_centroid};
919    pub use crate::cylinder::Cylinder;
920    pub use crate::debug_render::{debug_draw_world, DebugColor, DebugDrawData, DebugDrawFlags};
921    pub use crate::deformable::{DeformableBody, DeformableConfig};
922    pub use crate::dynamic_bvh::DynamicAabbTree;
923    pub use crate::ellipsoid::Ellipsoid;
924    #[cfg(feature = "std")]
925    pub use crate::erosion::{ErosionConfig, ErosionModifier, ErosionType};
926    pub use crate::error::PhysicsError;
927    pub use crate::event::{
928        ContactEvent, ContactEventType, EventCollector, JointBreakEvent, TriggerEvent,
929    };
930    pub use crate::filter::layers;
931    pub use crate::filter::CollisionFilter;
932    #[cfg(feature = "std")]
933    pub use crate::flow_viz::{
934        generate_flow_arrows, generate_streamlines, FlowArrow, FlowVizConfig, Streamlines,
935    };
936    pub use crate::fluid::{Fluid, FluidConfig};
937    #[cfg(feature = "std")]
938    pub use crate::fluid_netcode::{FluidDelta, FluidSnapshot};
939    pub use crate::force::{apply_force_fields, ForceField, ForceFieldInstance};
940    #[cfg(feature = "std")]
941    pub use crate::fracture::{Crack, FractureConfig, FractureModifier};
942    #[cfg(feature = "std")]
943    pub use crate::gpu_sdf::{
944        batch_size, GpuDispatchConfig, GpuSdfBatch, GpuSdfInstancedBatch, GpuSdfMultiDispatch,
945        GpuSdfQuery, GpuSdfResult,
946    };
947    pub use crate::heatmap::{
948        generate_stress_heatmap, generate_temperature_heatmap, heatmap_to_rgba, Heatmap,
949        HeatmapConfig, SliceAxis,
950    };
951    pub use crate::heightfield::HeightField;
952    pub use crate::interpolation::{BodySnapshot, InterpolationState, WorldSnapshot};
953    #[allow(deprecated)] // re-exported for compatibility until 3.0
954    pub use crate::joint::{
955        solve_joints, solve_joints_breakable, solve_joints_breaking_on_force,
956        solve_joints_with_reaction_forces,
957    };
958    pub use crate::joint::{
959        BallJoint, ConeTwistJoint, D6Joint, D6Motion, FixedJoint, HingeJoint, Joint, JointType,
960        SliderJoint, SpringJoint,
961    };
962    pub use crate::joint_extra::{
963        solve_extra_joints, ExtraJoint, GearJoint, MouseJoint, PulleyJoint, RackAndPinionJoint,
964        WeldJoint,
965    };
966    pub use crate::mass_properties::{
967        box_mass_properties, capsule_mass_properties, convex_hull_mass_properties,
968        cylinder_mass_properties, sphere_mass_properties, translate_inertia, MassProperties,
969    };
970    pub use crate::material::{
971        CombineRule, CombinedMaterial, MaterialId, MaterialTable, PhysicsMaterial,
972    };
973    pub use crate::math::{simd_width, Fix128, Mat3Fix, QuatFix, Vec3Fix, SIMD_WIDTH};
974    pub use crate::motor::{JointMotor, MotorMode, PdController};
975    pub use crate::multi_world::{MultiWorld, Portal};
976    pub use crate::netcode::{
977        DeterministicSimulation, FrameInput, InputApplicator, NetcodeConfig, SimulationChecksum,
978        SimulationSnapshot,
979    };
980    pub use crate::particle::{
981        LandingEvent, LandingTarget, Particle, ParticleEmitter, ParticleSystem,
982    };
983    #[cfg(feature = "std")]
984    pub use crate::phase_change::{Phase, PhaseChangeConfig, PhaseChangeModifier};
985    #[cfg(feature = "std")]
986    pub use crate::pipeline::{
987        MetricEvent, MetricPipeline, MetricRegistry, MetricSnapshot, MetricType, RingBuffer,
988    };
989    pub use crate::plane_collider::PlaneCollider;
990    #[cfg(feature = "std")]
991    pub use crate::pressure::{PressureConfig, PressureModifier};
992    #[cfg(feature = "privacy-crypto")]
993    pub use crate::privacy::KeyedRappor;
994    #[cfg(feature = "std")]
995    #[allow(deprecated)] // kept for existing callers, who see the deprecation at their use
996    pub use crate::privacy::{
997        LaplaceNoise, PrivacyBudget, PrivateAggregator, RandomizedResponse, Rappor, XorShift64,
998    };
999    pub use crate::profiling::{PhysicsProfiler, ProfileEntry, StepStats};
1000    pub use crate::query::{
1001        batch_raycast, batch_sphere_cast, capsule_cast, overlap_aabb, overlap_aabb_bvh,
1002        overlap_sphere, overlap_sphere_bvh, sphere_cast, BatchRayQuery, OverlapResult,
1003        ShapeCastHit,
1004    };
1005    pub use crate::raycast::{
1006        raycast_all_aabbs, raycast_all_spheres, raycast_any_aabbs, raycast_any_spheres, Ray, RayHit,
1007    };
1008    pub use crate::rng::DeterministicRng;
1009    pub use crate::rope::{Rope, RopeConfig};
1010    pub use crate::rope_attach::{solve_rope_attachments, RopeAttachment};
1011    #[cfg(feature = "std")]
1012    pub use crate::scene_io::{
1013        load_scene, load_scene_json, save_scene, save_scene_json, PhysicsScene, SerializedBody,
1014        SerializedJoint,
1015    };
1016    #[cfg(feature = "std")]
1017    pub use crate::sdf_adaptive::{AdaptiveConfig, AdaptiveSdfEvaluator};
1018    pub use crate::sdf_ccd::SdfCcdConfig;
1019    pub use crate::sdf_collider::{ClosureSdf, SdfCollider, SdfContact, SdfField};
1020    #[cfg(feature = "std")]
1021    pub use crate::sdf_destruction::{DestructibleSdf, DestructionShape};
1022    pub use crate::sdf_force::{SdfForceField, SdfForceType};
1023    pub use crate::sdf_manifold::ManifoldConfig;
1024    #[cfg(feature = "std")]
1025    pub use crate::sdf_manifold::SdfManifold;
1026    #[cfg(feature = "std")]
1027    pub use crate::sim_field::{ScalarField3D, VectorField3D};
1028    #[cfg(feature = "std")]
1029    pub use crate::sim_modifier::{ModifiedSdf, PhysicsModifier, SingleModifiedSdf};
1030    #[cfg(feature = "std")]
1031    pub use crate::sketch::{
1032        CountMinSketch, DDSketch, FnvHasher, HeavyHitters, HyperLogLog, Mergeable,
1033    };
1034    pub use crate::sleeping::{Island, IslandManager, SleepConfig, SleepData, SleepState};
1035    pub use crate::soft_body_cut::{cut_cloth, cut_deformable, CutPlane, CutResult};
1036    #[cfg(feature = "std")]
1037    pub use crate::solver::ContactModifier;
1038    #[cfg(feature = "std")]
1039    pub use crate::solver::TgsCacheStats;
1040    pub use crate::solver::{
1041        BodyType, ContactConstraint, DistanceConstraint, LawCheck, PhysicsConfig, PhysicsWorld,
1042        RigidBody, SolverBackend, StageWork, WorldCcdConfig, WorldSnapshotError,
1043    };
1044    pub use crate::spatial::SpatialGrid;
1045    #[cfg(feature = "std")]
1046    pub use crate::thermal::{HeatSource, ThermalConfig, ThermalModifier};
1047    pub use crate::torus::Torus;
1048    pub use crate::trimesh::{TriMesh, Triangle};
1049    pub use crate::vehicle::{Vehicle, VehicleConfig};
1050    pub use crate::wedge::Wedge;
1051}
1052
1053#[cfg(all(test, feature = "std"))]
1054mod compile_smoke_tests {
1055    //! Verify that core types from the prelude and key modules are accessible.
1056    //! These tests catch accidental breakage of public re-exports.
1057
1058    use super::*;
1059
1060    #[test]
1061    fn test_prelude_types_accessible() {
1062        let _ = Fix128::ZERO;
1063        let _ = Vec3Fix::ZERO;
1064        let _ = QuatFix::IDENTITY;
1065        let _ = Mat3Fix::IDENTITY;
1066        let _ = PhysicsConfig::default();
1067        let _ = CollisionFilter::default();
1068        let _ = SleepConfig::default();
1069    }
1070
1071    #[test]
1072    fn test_error_type_accessible() {
1073        let e = PhysicsError::InvalidBodyIndex { index: 0, count: 0 };
1074        let _ = format!("{e}");
1075    }
1076
1077    #[test]
1078    fn test_spatial_grid_accessible() {
1079        let mut grid = SpatialGrid::new(Fix128::ONE, 16);
1080        grid.insert(0, Vec3Fix::ZERO);
1081        grid.build();
1082        let mut neighbors = Vec::new();
1083        grid.query_neighbors_into(Vec3Fix::ZERO, Fix128::ONE, &mut neighbors);
1084        assert!(neighbors.contains(&0));
1085    }
1086
1087    #[test]
1088    fn test_wasm_ffi_mutual_exclusion() {
1089        // The compile_error! macros in lib.rs guarantee that `wasm` + `ffi`
1090        // cannot be enabled together. This test documents the invariant.
1091        // If this crate compiles at all, the exclusion is enforced.
1092        // The compile_error! macros above enforce this at compile time.
1093        // If this test runs, the invariant holds.
1094    }
1095}
1096
1097#[cfg(all(test, feature = "std"))]
1098mod tests {
1099    use super::*;
1100
1101    #[test]
1102    fn test_basic_simulation() {
1103        // Create world with default config
1104        let config = PhysicsConfig::default();
1105        let mut world = PhysicsWorld::new(config);
1106
1107        // Add a falling body
1108        let mut body = RigidBody::new_dynamic(Vec3Fix::from_int(0, 100, 0), Fix128::ONE);
1109        body.restitution = Fix128::from_ratio(5, 10); // 0.5 bounce
1110
1111        let body_id = world.add_body(body);
1112
1113        // Add ground (static body)
1114        let ground = RigidBody::new_static(Vec3Fix::ZERO);
1115        world.add_body(ground);
1116
1117        // Step simulation
1118        let dt = Fix128::from_ratio(1, 60);
1119        for _ in 0..60 {
1120            world.step(dt);
1121        }
1122
1123        // Body should have fallen
1124        let pos = world.bodies[body_id].position;
1125        assert!(pos.y < Fix128::from_int(100), "Body should have fallen");
1126    }
1127
1128    #[test]
1129    fn test_determinism() {
1130        // Run simulation twice with same initial conditions
1131        fn run_simulation() -> Vec3Fix {
1132            let config = PhysicsConfig::default();
1133            let mut world = PhysicsWorld::new(config);
1134
1135            let body = RigidBody::new_dynamic(
1136                Vec3Fix::from_int(5, 50, 3),
1137                Fix128::from_ratio(3, 2), // mass = 1.5
1138            );
1139            world.add_body(body);
1140
1141            let dt = Fix128::from_ratio(1, 60);
1142            for _ in 0..120 {
1143                world.step(dt);
1144            }
1145
1146            world.bodies[0].position
1147        }
1148
1149        let result1 = run_simulation();
1150        let result2 = run_simulation();
1151
1152        // Results must be bit-exact
1153        assert_eq!(result1.x.hi, result2.x.hi);
1154        assert_eq!(result1.x.lo, result2.x.lo);
1155        assert_eq!(result1.y.hi, result2.y.hi);
1156        assert_eq!(result1.y.lo, result2.y.lo);
1157        assert_eq!(result1.z.hi, result2.z.hi);
1158        assert_eq!(result1.z.lo, result2.z.lo);
1159    }
1160
1161    #[test]
1162    fn test_state_serialization() {
1163        let config = PhysicsConfig::default();
1164        let mut world = PhysicsWorld::new(config);
1165
1166        // Add body and simulate
1167        let body = RigidBody::new_dynamic(Vec3Fix::from_int(10, 20, 30), Fix128::ONE);
1168        world.add_body(body);
1169
1170        let dt = Fix128::from_ratio(1, 60);
1171        for _ in 0..30 {
1172            world.step(dt);
1173        }
1174
1175        // Serialize state
1176        let state = world.serialize_state();
1177
1178        // Continue simulation
1179        for _ in 0..30 {
1180            world.step(dt);
1181        }
1182        let pos_after = world.bodies[0].position;
1183
1184        // Restore state
1185        world.deserialize_state(&state);
1186
1187        // Continue simulation from restored state
1188        for _ in 0..30 {
1189            world.step(dt);
1190        }
1191        let pos_restored = world.bodies[0].position;
1192
1193        // Results must be identical
1194        assert_eq!(pos_after.x.hi, pos_restored.x.hi);
1195        assert_eq!(pos_after.y.hi, pos_restored.y.hi);
1196        assert_eq!(pos_after.z.hi, pos_restored.z.hi);
1197    }
1198
1199    #[test]
1200    fn test_distance_constraint() {
1201        let config = PhysicsConfig::default();
1202        let mut world = PhysicsWorld::new(config);
1203
1204        // Two bodies connected by distance constraint
1205        let body_a = RigidBody::new_dynamic(Vec3Fix::from_int(0, 10, 0), Fix128::ONE);
1206        let body_b = RigidBody::new_dynamic(Vec3Fix::from_int(5, 10, 0), Fix128::ONE);
1207        let id_a = world.add_body(body_a);
1208        let id_b = world.add_body(body_b);
1209
1210        // Target distance = 5 units
1211        let constraint = DistanceConstraint {
1212            body_a: id_a,
1213            body_b: id_b,
1214            local_anchor_a: Vec3Fix::ZERO,
1215            local_anchor_b: Vec3Fix::ZERO,
1216            target_distance: Fix128::from_int(5),
1217            compliance: Fix128::ZERO,
1218            cached_lambda: Fix128::ZERO,
1219        };
1220        world.add_distance_constraint(constraint);
1221
1222        // Simulate at 120 Hz for stricter convergence
1223        let dt = Fix128::from_ratio(1, 120);
1224        for _ in 0..120 {
1225            world.step(dt);
1226        }
1227
1228        // Distance should remain approximately 5
1229        let pos_a = world.bodies[id_a].position;
1230        let pos_b = world.bodies[id_b].position;
1231        let dist = (pos_b - pos_a).length();
1232
1233        let target = Fix128::from_int(5);
1234        let error = if dist > target {
1235            dist - target
1236        } else {
1237            target - dist
1238        };
1239
1240        // Allow at most 2% error (target=5, tolerance=0.1) under gravity
1241        let tolerance = Fix128::from_ratio(1, 10);
1242        assert!(
1243            error < tolerance,
1244            "Distance constraint violated: error = {error:?}, tolerance = {tolerance:?}"
1245        );
1246    }
1247}