The pack
physics3d
function physics3d(opts: Physics3dOptions = {}): Pack<{ physics3d: Physics3dApi }>
The physics-3d pack: rigid bodies for a 3D world, over a vendored Box3D core that loads as its own
~1 MB chunk. Requires a 3D world.
Helpers
createRigidWorld3d
async function createRigidWorld3d(options:Physics3dOptions={}):Promise<RigidWorld3d>
A headless rigid world over the same solver as game.physics3d, which you step and dispose yourself. Gravity defaults to −9.81 units/s² (the composed pack uses −20) and substeps to 4. Throws on gravity beyond ±1000 or substeps outside 1 to 32; its step throws on a dt outside 0 to 0.1 s.
Functions
continuous
function continuous(prev: number, next: number): number
next, shifted by whole turns so it is the SHORT way from prev.
The kernel interpolates an object's rotation linearly between fixed steps. A spinning body's yaw crosses from +π to −π every revolution, and a linear blend across that jump runs the whole way back round — one frame of a fast reverse spin, once per turn. Keeping the numbers continuous instead of wrapped costs nothing and makes that blend take the short path by construction.
eulerQuat
function eulerQuat(yaw: number, pitch: number, roll: number): Quat
Euler (the engine's yaw/pitch/roll) → quaternion, in the RENDERER'S OWN composition order.
render-3d's vertex stage computes rotY(−yaw) · rotX(pitch) · rotZ(roll), so the quaternion is
qy · qx · qz with the yaw half-angle negated. Get this order wrong and a body simulates correctly while
drawing at some other orientation — which reads as a physics bug and is not one.
quatConj
function quatConj(q: Quat): Quat
The inverse of a unit quaternion.
quatFromCols
function quatFromCols(cx: V3, cy: V3, cz: V3): Quat
A quaternion from an orthonormal basis, the columns being the frame's world axes.
quatFromTo
function quatFromTo(from: V3, to: V3): Quat
The rotation taking unit from onto to. Handles both poles, where the cross product is useless.
quatFromXTo
function quatFromXTo(axis: V3): Quat
The rotation aiming local +x along axis.
quatFromZTo
function quatFromZTo(axis: V3): Quat
The rotation aiming local +z along axis.
quatMul
function quatMul(a: Quat, b: Quat): Quat
Quaternion product.
quatRotate
function quatRotate(q: Quat, p: V3): V3
Rotate a point by a quaternion.
quatToEuler
function quatToEuler(x: number, y: number, z: number, w: number, out: EulerOut): void
Quaternion → the engine's yaw/pitch/roll, decomposed in the SAME rotY(−yaw)·rotX(pitch)·rotZ(roll) order
the shader applies, so writing these onto an object reproduces the body's exact orientation on screen.
GIMBAL CAVEAT: at pitch = ±90° yaw and roll collapse onto the same axis, so the split between them is arbitrary there and this puts it all in yaw. A single frame's pose is still exact — every decomposition renders the right orientation — but a body tumbling THROUGH the pole can see yaw and roll swap values between frames. That is invisible on screen, because the composed rotation stays continuous; only code reading the raw numbers of a tumbling body would notice.
Classes
Body3d
class Body3d
A body bound to an object.
Dynamic bodies OWN their object's transform: steer them with impulse, force or velocity, never by
writing x/y/z (except teleport). Kinematic bodies are the reverse — move the OBJECT.
readonly target: Body3dTarget— The object this body drives (dynamic) or follows (kinematic).readonly type: 'dynamic' | 'static' | 'kinematic'— Which kind of body it is.onHit: ((other: Body3d, speed: number) => void) | null— Contact above the impact threshold, with the approach speed. Assign at any time.onTouch: ((other: Body3d) => void) | null— Contact began, any speed.onRelease: ((other: Body3d) => void) | null— Contact ended.onEnter: ((other: Body3d) => void) | null— A body entered this trigger.onExit: ((other: Body3d) => void) | null— A body left this trigger._id: Id_sim: Sim_suspended: boolean_dead: booleanconstructor(sim: Sim, id: Id, target: Body3dTarget, type: 'dynamic' | 'static' | 'kinematic')get velocity(): V3— Linear velocity, world units a second. Set the linear velocity, and wake the body — a velocity set on a sleeping body would otherwise do nothing.set velocity(v: V3)— Linear velocity, world units a second. Set the linear velocity, and wake the body — a velocity set on a sleeping body would otherwise do nothing.get spin(): V3— Angular velocity, radians a second per axis. Set the angular velocity, and wake the body.set spin(v: V3)— Angular velocity, radians a second per axis. Set the angular velocity, and wake the body.get mass(): number— The body's mass. Static and kinematic bodies read 0.get awake(): boolean— Is it awake? A sleeping body costs the solver nothing until something touches it. Wake it, or put it to sleep.set awake(v: boolean)— Is it awake? A sleeping body costs the solver nothing until something touches it. Wake it, or put it to sleep.impulse(v: Partial<V3>, at?: V3): void— An instant kick, MASS-SCALED soimpulse({ y: 6 })pops a crate about as high as you would guess whatever it weighs. Passat— a world point — to spin it too: a hit off centre tumbles.force(v: Partial<V3>): void— A continuous push that accumulates until the next step — thrusters, wind. Mass-scaled likeimpulse.torque(v: Partial<V3>): void— A twist about the body's centre, in torque units.teleport(position: V3, rotation?: { yaw?: number; pitch?: number; roll?: number }): void— Move the body and its object instantly, clearing velocity — a respawn.rotationis Euler radians in the engine's own yaw/pitch/roll; omitted means upright. It also SNAPS the object, so the frame that follows does not interpolate the jump: without that, a respawn smears the object across the whole map for one frame.destroy(): void— Take this body out of the world; the object stays where it is. Safe to call twice — the second call does nothing, which matters because the solver RECYCLES body slots and an unguarded double-destroy could kill an unrelated new body.
Character3d
class Character3d extends Body3d
A capsule character controller: a rotation-locked dynamic body with velocity-driven movement. It pushes
crates, rides platforms, climbs shallow steps by capsule shape, and reports grounded for jump gating.
constructor(sim: Sim, id: Id, target: Body3dTarget, height: number)move(dir: { x: number; z: number }, speed: number): void— Steer: set horizontal velocity towarddir(any length) atspeed, keeping vertical motion.get grounded(): boolean— Standing on something, within a small tolerance below the feet?jump(v: number): boolean— Jump at takeoff speedv. Only fires while grounded; returns whether it did.
Constants
IDENTITY
const IDENTITY: Quat
The identity rotation.
Types
BindOptions
interface BindOptions
Options for bind.
type?: 'dynamic' | 'static' | 'kinematic'—'dynamic'(default) — fully simulated.'static'— immovable level geometry.'kinematic'— YOU move the object and it pushes dynamic bodies physically (moving platforms, doors on rails).shape?: 'auto' | 'box' | 'sphere' | 'capsule' | 'hull'— The collision shape:'auto'(default — a box of the object's own size),'sphere','capsule'(upright), or'hull'(a convex wrap of thepointsyou supply — build them withhull-3d'shullOfVerts). Match it to what you can see. A hull is CONVEX: a donut's hull is a solid disc.points?: Float32Array— The'hull'shape's point cloud: flat xyz triples in the object's LOCAL unit space, spanning ±0.5 per axis, which the wrapper scales by sx/sy/sz exactly as it derives box half-extents. Required forshape: 'hull'; missing or degenerate points fall back to the box.friction?: number— Surface grip, 0..1+. Default 0.5.bounce?: number— Bounciness, 0..1. Default 0.rollingResistance?: number— Rolling resistance, 0..~0.1 — rolling bodies slow down. Default 0, which rolls forever.mass?: number— Total mass, dynamic bodies. Default: about 1 per cubic unit.group?: string— Collision group name. Default'default'.hits?: string[]— The group names this body collides with. Default: everything.trigger?: boolean— Sensor mode: overlaps fireonEnter/onExitand apply NO contact forces — pickup zones, pressure plates.onEnter?: (other: Body3d) => void— A body entered this trigger.onExit?: (other: Body3d) => void— A body left this trigger.onHit?: (other: Body3d, speed: number) => void— Contact started, above the solver's impact threshold, with the approach speed — gate impact sounds on it.onTouch?: (other: Body3d) => void— Contact BEGAN at any speed — standing-on bookkeeping, "what am I touching".onRelease?: (other: Body3d) => void— Contact ENDED — the pair separated.fast?: boolean— Continuous collision for fast movers, so a bullet does not tunnel through a thin wall.damping?: number— Linear damping — air drag. Default 0.angularDamping?: number— Angular damping. Default 0.05.gravityScale?: number— Gravity multiplier for this body; 0 floats. Default 1.lockRotation?: boolean— Freeze all rotation — top-down pucks, upright props. Default false.fastSpin?: boolean— Allow very fast rotation. The solver clamps angular speed by default, which rev-limits a WHEEL. Default false.balance?: V3— Centre-of-mass offset in the object's LOCAL space. A car withbalance: { y: -0.4 }carries its weight low like a real one — planted, hard to flip. Default: the geometric centre of the shape.conveyor?: V3— Conveyor belt: the surface drags whatever rests on it at this velocity while the body itself stays put — usually a static belt. The vector is in the shape's LOCAL space, so rotating the object turns the belt with it. Morefrictionis more grip on the cargo.family?: string— Family name: bodies sharing a family NEVER collide with each other while still colliding with the rest of the world. This is the ragdoll knob — limbs must overlap at their joints without fighting. Independent ofgroup/hits.
Body3dTarget
interface Body3dTarget
What bind() accepts: anything with the pose fields a 3D object has. A kernel Handle from
world3d.spawn() satisfies it, and so does a plain object in a headless test.
sx, sy and sz are the FULL sizes along each axis — the engine's primitives are unit-sized, so an
object with sx = 2 really is two units wide. rot is yaw; pitch and roll are the other two, applied
in the renderer's own order.
readonly alive?: boolean— Roster handles expose this; false lets physics release a body whose rendered object was killed.readonly frozen?: boolean— Scene suspension removes this target from simulation until it resumes.x: number— Centre x, world units.y: number— Centre y, world units; y is up.z: number— Centre z, world units.sx: number— Full width along x.sy: number— Full height along y.sz: number— Full depth along z.rot: number— Yaw about y, radians.pitch: number— Pitch about x, radians.roll: number— Roll about z, radians.
Character3dOptions
interface Character3dOptions
Options for character.
radius?: number— Capsule radius. Default 0.4.height?: number— Total capsule height. Default 1.7.friction?: number— Ground friction while standing still. Default 0.1 — low, because movement is velocity-driven.group?: string— Collision group. Default'characters'.
EulerOut
interface EulerOut
What quatToEuler writes into.
yaw: number— Rotation about the vertical axis in radians, with the engine's yaw sign (the renderer appliesrotY(-yaw)).pitch: number— Rotation about the x axis in radians, from -π/2 to π/2.roll: number— Rotation about the z axis in radians; the full turn is applied in the renderer's yaw, pitch, roll composition order.
GrabOptions
interface GrabOptions
Options for grab — hold and drive a body relative to another.
force?: number— Max holding force: how heavy a thing it can carry against gravity and knocks. Default 200.torque?: number— Max holding torque: how firmly it resists spinning away. Default 100.stiffness?: number— Hold stiffness in Hz. Default 5, which is snappy; lower is a laggy tractor beam.damping?: number— Hold damping 0..1. Default 0.8.
HingeOptions
interface HingeOptions
Options for hinge.
at: V3— The world anchor the hinge turns around.axis?: V3— The world hinge axis. Default{ x: 0, y: 1, z: 0 }— a door.limits?: [number, number]— Swing limits in radians[min, max]. Omit for free spin.motor?: { speed: number; torque?: number }— Drive it: radians/second, and a torque budget.
Joint3d
interface Joint3d
A joint. Keep the handle to drive its motor or destroy() it.
motor(speed: number, strength?: number): void— Retune the motor — a hinge in rad/s, a slider in units/s, a wheel in spin rad/s. No-op on springs and welds.steer?(angle: number): void— Aim a wheel joint, radians. No-op on the others.drive?(linear: V3, angular?: V3): void— Grab joints: drive the held body at this relative velocity.destroy(): void— Remove it; the bodies separate.
Physics3dApi
interface Physics3dApi
game.physics3d.
readonly ready: Promise<void>— Resolves once the WASM core has loaded and the world exists.bindand every query throw before it, so a scene that uses physics awaits this first: await game.physics3d.ready; game.physics3d.bind(ground, { type: 'static' });readonly live: boolean— Has the core landed yet?gravity(g: number | V3): void— Set gravity: a number is straight down, or pass a full vector. Wakes every body.bind(target: Body3dTarget, opts?: BindOptions): Body3d— Give an object a body. The shape comes from its own size and the pose from where it is NOW.character(target: Body3dTarget, opts?: Character3dOptions): Character3d— A capsule character controller.bodyOf(target: Body3dTarget): Body3d | null— The body bound to an object, or null.raycast(origin: V3, dir: V3, maxDistance?: number, ignore?: Body3d, opts?: { hits?: string[] }): RayHit3d | null— The nearest hit along a ray.ignoreskips one body — the shooter.raycastAll(origin: V3, dir: V3, maxDistance?: number, opts?: { hits?: string[] }): RayHit3d[]— Every hit along a ray, nearest first.overlap(center: V3, radius: number, opts?: { hits?: string[] }): Body3d[]— Every body overlapping a sphere.overlapBox(center: V3, size: V3, opts?: { hits?: string[] }): Body3d[]— Every body overlapping an axis-aligned box.explosion(at: V3, radius: number, strength: number): void— A radial blast: a mass-scaled outward kick, with lift, on every dynamic body in range.hinge(a: Body3d, b: Body3d, opts: HingeOptions): Joint3d— A hinge — doors, windmills, seesaws.socket(a: Body3d, b: Body3d, opts: SocketOptions): Joint3d— A ball-and-socket — shoulders, hips, chain links.slider(a: Body3d, b: Body3d, opts: SliderOptions): Joint3d— A slider — elevators, pistons, sliding doors.wheel(chassis: Body3d, wheel: Body3d, opts: WheelOptions): Joint3d— A vehicle wheel: suspension, drive and steering in one.spring(a: Body3d, b: Body3d, opts?: SpringOptions): Joint3d— A springy rope — pendulums, cranes, bungees.grab(a: Body3d, b: Body3d, opts?: GrabOptions): Joint3d— A motorised grip — tractor beams, claws, carrying a crate.weld(a: Body3d, b: Body3d, at?: V3): Joint3d— Lock two bodies rigidly together.clear(): void— Remove every body. The objects stay where they are.readonly bodies: number— How many bodies exist.readonly invalidBodies: number— Diagnostic: body handles found stale during a step. It should stay 0.enabled: boolean— Turn the simulation off without tearing it down.
Physics3dOptions
interface Physics3dOptions
Manifest options.
gravity?: number | V3— Gravity: a number is world units/s² straight down, or a full vector. Default −20 (arcade-weighty) for the composed pack;createRigidWorld3ddefaults to −9.81.substeps?: number— Solver substeps per fixed step. Default 4; more for taller stacks.module?: string— Override the core module URL in a development build, on the game's own origin; another origin throws. Rarely needed — the default finds the shipped chunk.
Quat
interface Quat
Box3D's quaternion: a vector part and a scalar.
v: V3— The vector part (x, y, z) of the quaternion, each between -1 and 1 for a unit rotation.s: number— The scalar part (w), between -1 and 1 for a unit rotation; 1 with a zero vector part is no rotation.
RayHit3d
interface RayHit3d
What a ray found.
body: Body3d— The body it struck.point: V3— Where, in world units.normal: V3— The surface normal there.distance: number— How far along the ray, world units.
RigidWorld3d
interface RigidWorld3d
A renderer-independent rigid world for rule packs and physical replay validation.
bind(target:Body3dTarget,options?:BindOptions):Body3d— Bind a plain owned pose to a real rigid body.hinge(a:Body3d,b:Body3d,options:HingeOptions):Joint3d— Pin two bodies around a physical rotation axis.step(dt:number):void— Advance at an explicitly supplied bounded time step.clear():void— Remove every body and its constraints.reset():void— Recreate the solver, clearing bodies, contacts and allocation history for deterministic replay.dispose():void— Release the owned world; repeated disposal is safe.
SliderOptions
interface SliderOptions
Options for slider.
at: V3— The world anchor on the slide axis.axis?: V3— The world slide axis. Default{ x: 0, y: 1, z: 0 }— an elevator.limits?: [number, number]— Travel limits along the axis, world units.motor?: { speed: number; force?: number }— Drive it: units/second, and a force budget.
SocketOptions
interface SocketOptions
Options for socket — a ball-and-socket joint.
at: V3— The world anchor: the ball's centre — a shoulder, a hip, a chain link.axis?: V3— The rest-pose axis the swing cone opens around; point it along the CHILD limb. Default straight down.swing?: number— Max swing away fromaxis, radians. Omit for a free ball joint.twist?: [number, number]— Twist limits aboutaxis, radians. Omit for free twist.stiffness?: number— A soft spring pulling back to the rest pose, in Hz. Adds life to a ragdoll. Default off.
SpringOptions
interface SpringOptions
Options for spring.
length?: number— Rest length. Default: the current distance between the anchors.hertz?: number— Stiffness in Hz — oscillations a second. Default 3.damping?: number— Damping 0..1; 1 is no wobble. Default 0.35.atA?: V3— World anchor on body A. Default A's origin.atB?: V3— World anchor on body B. Default B's origin.
V3
interface V3
A 3D vector, in world units.
x: number— Component along the x axis, in world units.y: number— Component along the vertical y axis, in world units.z: number— Component along the z axis, in world units.
WheelOptions
interface WheelOptions
Options for wheel — suspension, drive and steering in one joint.
at: V3— The world wheel centre at rest.axle?: V3— The world axle (spin) axis. Default{ x: 1, y: 0, z: 0 }— a car facing ±z.hertz?: number— Suspension stiffness in Hz. Default 3.5.damping?: number— Suspension damping 0..1; 1 is no bounce. Default 0.7.travel?: [number, number]— Suspension travel[down, up]around the rest pose. Default[-0.25, 0.15].drive?: boolean | { torque?: number }— This wheel is powered —joint.motor(speed)drives it in rad/s.steer?: boolean | { limit?: number; torque?: number }— This wheel steers —joint.steer(angle)aims it.limitcaps the lock. Default lock 0.65.


