boxdd 0.6 - Rust bindings for Box2D v3

August 8, 2026 ยท View on GitHub

boxdd 0.6 - Rust bindings for Box2D v3

Crates.io Docs Live Examples License

boxdd

boxdd 0.6 is the breaking, soundness-focused binding for the pinned Box2D 3.2.0 development snapshot at commit 56edae79f2949d86142b03450d5d60f63bcf5a6f. It is not a binding for an arbitrary Box2D 3.2 checkout. The source revision, precision, private ABI, snapshot layout, recording format, target, and provider identity are qualified together.

Read the 0.5 to 0.6 migration guide before upgrading.

Crates

  • boxdd-sys: raw FFI, the pinned source tree, generated bindings, and provider identity checks.
  • boxdd: owner-thread Safe Rust APIs for worlds, objects, queries, callbacks, snapshots, recordings, and replay.
  • bevy_boxdd: Bevy 0.19 ECS integration with an explicit local-to-world origin bridge.

0.6 Highlights

  • World is the sole owner of a simulation. Creation returns world-bound IDs for storage; World::body, shape, joint, chain, and query acquire borrow-scoped capabilities for access. Destruction is explicit.
  • The Safe Rust surface has one fallible contract. Operations that can fail return Result; there is no parallel panic/try_* API family.
  • Live BodyId, ShapeId, JointId, ChainId, and ContactId values cannot be detached, rebound, reconstructed from raw Box2D IDs, or serialized. Cross-world, stale, recycled, wrong-kind, and forged identifiers are rejected before native mutation.
  • Absolute coordinates use Position and WorldTransform; local offsets, directions, extents, and rotations use Vec2, Transform, and f32. The double-precision feature changes WorldScalar and the native ABI together.
  • World queries take an explicit absolute Position origin. Standalone collision helpers return LocalManifold; runtime contact manifolds retain local f32 anchors with explicit world-point reconstruction.
  • Foundation::initialize freezes process-global length units and hooks before the first safe native use and returns the explicit root for worlds, scale-aware definitions, worldless native calls, and exclusive replay.
  • Native targets can use Box2D's built-in scheduler through validated WorkerCount values. World and its borrow-scoped capabilities remain !Send and !Sync; worker callbacks receive only thread-safe identifiers and values, never an owning world context. Raw task-system callbacks are outside the Safe Rust layer.
  • Snapshot, RecordingSession, and ReplayPlayer encode distinct ownership models for same-world restore, operation recording, and exclusive process-local replay. Native snapshot and recording bytes are not a Safe Rust persistence format.
  • Every pinned exported C function has an explicit reviewed disposition. Compiler-backed C/Rust probes check ABI compatibility, while Rust tests exercise Safe API behavior and callback boundaries; the inventory does not attempt to infer Rust call graphs.
  • Vendored source is the default provider. System and prebuilt native providers are static-only, exact-manifest adapters; WASM runtime support uses a versioned Emscripten provider.

Quick Start

use boxdd::{BodyBuilder, BodyType, Foundation, Position, ShapeDef, Vec2, WorldBuilder, shapes};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let foundation = Foundation::initialize_default()?;
    let mut world = foundation.create_world(
        WorldBuilder::from(foundation.world_def())
            .gravity(Vec2::new(0.0, -9.8))
            .build()?,
    )?;
    let body_id = world.create_body(
        BodyBuilder::from(foundation.body_def())
            .body_type(BodyType::Dynamic)
            .position(Position::new(0.0, 2.0))
            .build()?,
    )?;
    world.body(body_id)?.create_polygon(
        &ShapeDef::builder().density(1.0).build()?,
        &shapes::box_polygon(0.5, 0.5)?,
    )?;

    let completed = world.step(1.0 / 60.0, 4)?;
    let contact_events = completed.contact_events()?.to_owned()?;
    println!("{} contacts began", contact_events.begin.len());
    Ok(())
}

Keep IDs in application state and acquire a Body, Shape, Joint, or Chain capability only for the duration of an operation. A capability prevents overlapping mutable access to its world; dropping it releases the borrow and never implicitly destroys the object.

Spatial Model

  • WorldScalar is f32 by default and f64 with double-precision.
  • Position and WorldTransform represent absolute world coordinates.
  • Vec2 and Transform always remain local f32 values.
  • Convert an absolute point to a local frame with Position::checked_relative_to. The explicit relative_to_lossy method is available only when lossy narrowing is intentional.
  • Query AABBs, polygons, and cast geometry remain local to the explicit query origin. Ray hit and debug draw positions are absolute Position values.
  • bevy_boxdd::BoxddWorldOrigin maps Bevy-local f32 transforms to absolute Box2D positions and performs origin rebases atomically.

Ownership and Callbacks

World, its borrow-scoped object/query capabilities, snapshots, recording sessions, and replay players are owner-thread objects. A dedicated physics thread plus channels is the supported way to integrate with a multi-threaded or async application.

On native targets, worker-capable callbacks (set_custom_filter, set_pre_solve, friction mixing, and restitution mixing) require Send + Sync + 'static closures and must not call world APIs. Query, dynamic-tree, event-view, and debug-draw callbacks are closure-scoped. Every C-to-Rust callback contains any unwinding panic; the first panic resumes only after native control returns to a Rust-owned boundary.

WASM adapters currently do not prove cross-module Rust function-pointer transport. Callback-backed world, query, dynamic-tree, foundation, replay, and debug-draw APIs are therefore absent at compile time on wasm32; callback-free queries such as closest ray casts and mover casts remain available.

Safe Rust does not expose raw live-object IDs or bind/unbind seams. Use application-owned stable keys for ECS and persistence mappings. Use boxdd-sys directly when an application deliberately accepts the raw FFI contract.

Foundation and Scheduling

Configure global length units before any other safe Box2D call:

use boxdd::{Foundation, FoundationConfig, WorkerCount, WorldBuilder};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let foundation = Foundation::initialize(FoundationConfig::new(1.0))?;
    let workers = WorkerCount::new(4)?;
    let world = foundation.create_world(
        WorldBuilder::from(foundation.world_def())
            .worker_count(workers)
            .build()?,
    )?;
    drop(world);
    Ok(())
}

Initialization is idempotent only for the same configuration. A conflicting configuration is an error. Derive scale-sensitive defaults through foundation.world_def() and foundation.body_def(). Derive a joint base from its active owner with world.joint_base(...) or recording_session.joint_base(...); the owner authenticates both body IDs and preserves the same frozen length-unit contract. Native targets qualify Box2D's built-in scheduler; current WASM adapters accept exactly one worker. WorldDef contains only Safe Rust configuration and cannot install native task or material callback pointers.

Snapshots, Recording, and Replay

  • World::snapshot returns an unforgeable capability for restoring the same world. Successful restore returns a SnapshotRestore mapping. Registrations in the unchanged snapshot/current identity intersection preserve their Safe IDs; destroyed, replaced, or post-snapshot objects are invalidated and remapped as needed. Rejection before the native restore call leaves the world live; a failure after that call makes the world terminal.
  • Snapshot native bytes are deliberately opaque. Safe Rust supports only same-world, same-process restore; durable saves require an application-owned schema that rebuilds a new world.
  • RecordingSession owns the native recording allocation and is the only world access surface while recording. finish validates the writer output and produces an opaque process-local Recording.
  • ReplayPlayer::open(foundation, &recording, config) accepts only that opaque Recording, copies its private stream, acquires exclusive process-global access through the same explicit Foundation root, and exposes closure-scoped, epoch-bound read views. Drop or close restores the previous global state.

The Safe Rust layer does not import or export snapshot or recording bytes. Persist application-level state separately when long-term or cross-build compatibility is required. See boxdd/examples/snapshot_replay.rs for the in-process checkpoint and replay workflow.

Providers and Compatibility

ProviderSelectionContract
Vendored sourcedefaultBuilds the pinned source inventory with matching generated bindings.
Local systemBOXDD_SYS_PROVIDER=systemCaller-supplied static archive, header, bindings, and exact local attestation manifest.
Official prebuiltBOXDD_SYS_PROVIDER=prebuiltExact static manifest plus a signed whole-package provenance statement and Sigstore bundle.
WASM compile-onlyBOXDD_SYS_PROVIDER=wasm-compile-onlyType/build qualification only; no runtime claim.
WASM runtimeRepository xtask provider/Pages entry pointsControlled final link with versioned precision-specific imports; bare BOXDD_SYS_PROVIDER=wasm-provider builds fail closed, and official runtime packages carry signed whole-package provenance.

System and prebuilt adapters never download, extract, cache, discover by name, dynamically link, or fall back to vendored source. Official prebuilt qualification authenticates the canonical provenance statement and exact outer archive before extraction; boxdd-sys then re-verifies the already-local complete member inventory and manifest before linking exact bytes. A provider reporting only b2GetVersion() == 3.2.0 is insufficient. Single and double precision artifacts, manifests, bindings, and dependent crate features cannot be mixed.

The official WASM package is a runtime distribution, not a boxdd-sys build input. Repository-level xtask builds with an activated Emscripten installation. CI installs the fixed supported version, builds the provider, authenticates the complete package before extraction, and qualifies the extracted JavaScript/WASM under Node and Chromium. Building boxdd-sys never discovers, downloads, or executes an Emscripten SDK.

See boxdd-sys/README.md for manifest inputs and docs/platforms/wasm.md for the WASM runtime boundary.

Cargo Features

  • double-precision: use f64 absolute world coordinates and the matching Box2D ABI.
  • serde: serialize safe value and configuration types. It does not serialize a World, live object IDs, snapshots, or recordings.
  • mint, nalgebra, glam: scalar-correct math interop. World-space conversions use WorldScalar; local vector conversions remain f32.
  • bytemuck: Pod/Zeroable for layout-qualified value types.
  • simd-avx2, disable-simd, validate: forward an explicit native provider identity choice.

There is no serialize feature in 0.6. Snapshot, recording, and replay APIs are available through the normal safe crate surface.

Development

git submodule update --init --recursive
cargo fmt --all -- --check
cargo nextest run -p boxdd -p boxdd-sys
cargo nextest run -p boxdd -p boxdd-sys --features boxdd/double-precision
cargo nextest run -p bevy_boxdd
cargo check -p boxdd --examples
cargo check -p boxdd --examples --features double-precision
cargo run -p xtask -- upstream-sync --check
cargo run -p xtask -- api-inventory --check
cargo run -p xtask -- recording-wire-codegen --check

The repository pins Rust 1.95 as MSRV and Rust 1.97 as its development toolchain. Provider, package, sanitizer, Miri, WASM, Pages, and release gates are exposed through xtask and CI.

Examples

  • boxdd/examples/README.md groups the headless core examples by workflow. Start with world_basics, foundation_scheduler, queries, and snapshot_replay.
  • bevy_boxdd/README.md documents the ECS adapter and explicit BoxddWorldOrigin bridge.
  • https://frankorz.com/boxdd/ hosts the generated single-precision Bevy + egui development preview; signed tag-bound WASM release packages are the portable distribution artifacts.

Documentation

Acknowledgments

License

boxdd and boxdd-sys are licensed under MIT OR Apache-2.0. The pinned upstream Box2D source is MIT-licensed.