FERRETGame engine over Babylon.js
Ferret

@ferret/physics

Layers, triggers and raycasts; the rigid-body kernels (light and Havok); the capability host with scoped configuration; budgets and tiers.

95 exports · source in packages/physics/src

Classes

Collider class

class Collider extends Component<ColliderParams>

Anything on a layer that can be overlapped: triggers, interactables, characters. Static level geometry uses markStatic instead.

shape: ShapeKind
layerBit: number
maskBits: number
physics: PhysicsServiceSet by the physics service when the component is attached.
onAttach()
onDetach()
get isTrigger()
center(out = new BABYLON.Vector3()): BABYLON.Vector3

packages/physics/src/physics.ts:27

CollisionLayers class

class CollisionLayers

UE-style object channels: every collider is on one layer and tests against a mask of layers.

constructor(names: string[] = [])
define(name: string): number
bit(name: string): number
mask(...names: string[]): numberA mask from layer names; '*' = all layers.
names(): string[]
namesOf(mask: number): string[]

packages/physics/src/layers.ts:2

Governor class

class Governor

Watches the world step and gives up detail in the order of rules when it stays over budget, then takes it back when there is room again. It acts on the numbers the capabilities already read and on switching capabilities off; the events say what happened (physics:degrade { level, rule, meanMs, budgetMs }), so a game can tell the player or log it.

readonly budget: TierBudget
readonly rules: DegradeRule[]
level: number
constructor(private host: PhysicsHost, readonly opts: GovernorOptions = {})
observe(ms: number)Called with the time of each step.

packages/physics/src/budget.ts:57

HavokKernel class

class HavokKernel implements PhysicsKernel

The rigid-body kernel on Havok's own API (no Babylon scene needed, so it runs headless and in tests): bodies of any shape that move, turn with their full inertia, stack and rest; joints that report their force and break; swept collision. The game loads the WASM (await HavokPhysics()) and hands the module in, so a game that does not want rigid bodies does not carry it.

What Havok does not do the same way as the light kernel: contacts cannot be changed before they are solved (caps.contactHooks is false: friction and restitution come from the materials, combined by the library's rules; a pair table is not used), and there is no rolling resistance (a capability adds it as a torque).

readonly name: "havok"
readonly caps: KernelCaps
gravity: Vec3
readonly materials: MaterialLibrary
substeps: number
readonly bodies: Body[]
readonly constraints: Constraint[]
readonly world: HP_WorldId
constructor(readonly hk: HavokPhysicsWithBindings, o: HavokKernelOptions = {})
dispose()Free the Havok world and everything in it.
addBody(def: BodyDef): Body
removeBody(b: Body)
setMassProperties(b: Body, mass: number, centreOfMass: Vec3, inertia: Vec3)Mass, centre of mass (body space) and principal inertia set from outside (a vessel whose tanks fill); where the kernel can.
setSurface(b: Body, sf: { friction: number; kineticFriction: number; restitution: number })A body's own friction and restitution, over its material's: how a kernel without contact hooks takes a capability's dials.
centreOf(b: Body): Vec3World position of the body's centre of mass.
applyForce(b: Body, f: Vec3, at?: Vec3)
applyImpulse(b: Body, j: Vec3, at?: Vec3)
applyTorque(b: Body, t: Vec3)
setVelocity(b: Body, v: Vec3)
setPosition(b: Body, p: Vec3)
sleep(b: Body)
wake(b: Body)
addForceField(fn: (b: Body, h: number) => Vec3 | FieldForce | null)A force that depends on where a body is (gravity, drag, buoyancy): evaluated at the position of every sub-step, so a fast or orbiting body gets the force it feels where it is, not where it was at the start of the step. Returns newtons, or null for none.
beforeSolve(_cb: (c: Contact) => void)Havok solves contacts inside its step: a hook is accepted and never called (caps.contactHooks is false).
onContact(cb: (e: ContactEvent) => void)Called after the step with what each contact did.
addConstraint(def: ConstraintDef): Constraint
removeConstraint(c: Constraint)
onConstraintBreak(cb: (c: Constraint) => void)Called after the step for each constraint whose force passed its limit; the constraint is already removed.
step(dt: number): void
rayCast(origin: Vec3, dir: Vec3, maxDistance: number): KernelRayHit | null

packages/physics/src/havok-kernel.ts:36

LightKernel class

class LightKernel implements PhysicsKernel

A small deterministic rigid-body kernel: spheres that move, roll, slide and bounce against static planes, boxes and spheres and against each other. No stacking of boxes and no joints; for projectiles, balls, debris, rolling and sliding tests, and as the reference the capabilities are checked against. The same PhysicsKernel surface as a heavier backend, so a capability does not know.

readonly name: "light"
readonly caps: KernelCaps
gravity: Vec3
readonly materials: MaterialLibrary
substeps: number
maxSubsteps: number
iterations: number
restitutionThreshold: number
readonly bodies: Body[]
readonly constraints: Constraint[]
constructor(o: LightKernelOptions = {})
addBody(def: BodyDef): Body
removeBody(b: Body)
addConstraint(def: ConstraintDef): Constraint
removeConstraint(c: Constraint)
onConstraintBreak(cb: (c: Constraint) => void)Called after the step for each constraint whose force passed its limit; the constraint is already removed.
applyForce(b: Body, f: Vec3, at?: Vec3)
applyImpulse(b: Body, j: Vec3, at?: Vec3)
applyTorque(b: Body, t: Vec3)
setVelocity(b: Body, v: Vec3)
setPosition(b: Body, p: Vec3)
sleep(b: Body)
wake(b: Body)
addForceField(fn: (b: Body, h: number) => Vec3 | FieldForce | null)A force that depends on where a body is (gravity, drag, buoyancy): evaluated at the position of every sub-step, so a fast or orbiting body gets the force it feels where it is, not where it was at the start of the step. Returns newtons, or null for none.
beforeSolve(cb: (c: Contact) => void)Called for every contact before it is solved, so a capability can change friction or restitution.
onContact(cb: (e: ContactEvent) => void)Called after the step with what each contact did.
step(dt: number): void
rayCast(origin: Vec3, dir: Vec3, maxDistance: number): KernelRayHit | null

packages/physics/src/light-kernel.ts:29

MaterialLibrary class

class MaterialLibrary
combine: CombineRules
add(name: string, m: PhysicsMaterial): this
get(name: string | undefined): PhysicsMaterial | undefined
has(name: string)
names()
load(table: Record<string, PhysicsMaterial>): thisLoad {name: material}; throws with every problem listed when the table is invalid.
setPair(a: string, b: string, values: PhysicsMaterial): thisThe values for one pair of materials that override the combine rule (ice on rubber is not the mean of the two).
property(a: string | undefined, b: string | undefined, key: string): number | undefinedOne property of a pair: the pair table first, then the combine rule; a value only one side has is used as it is.
contact(a?: string, b?: string)What a contact between two materials uses.

packages/physics/src/materials.ts:57

PhysicsHost class

class PhysicsHost implements Service

The home of the capabilities: the kernel, the materials, the registry and the fixed order of the step. A game registers the capabilities it wants and switches them on (in code, in the manifest, or by command); nothing realistic runs otherwise.

readonly name: "physics.host"
readonly kernel: PhysicsKernel
readonly materials: MaterialLibrary
readonly bodies: Set<RigidBody>
readonly fields: PhysicsFieldsFields that other code (a character, a vessel) may read when the capability behind them is on; each is set and cleared by its capability.
readonly capabilities: Map<string, Capability>
world: World
zoneAt(p: Vec3): string | nullWhich zone a point is in (none when the world has no ZoneService).
lastStepMs: numberSeconds the last step's physics took (ms), for the budget view.
constructor(o: PhysicsHostOptions = {})
init(world: World)
dispose()
register(cap: Capability): this
config(name: string): ScopedConfig
isEnabled(name: string)
enabled(): string[]
enable(name: string, values?: Record<string, unknown>): this
disable(name: string): this
configure(c: PhysicsConfig, scope: 'world' | 'level' = 'level')Apply a physics block (from a level manifest or a game): materials, pair rules, combine rules, then the capabilities with their values.
readonly fractures: Map<string, LevelFracture>Pre-fractured bodies of the level, by the name of the intact body's node (read by the strength capability when that body breaks).
governor: Governor | nullGives up detail when the step runs over its tier's budget (see Governor); none until a game sets one.
beginLevel(c: PhysicsConfig): string[]A level's physics block: applied now, undone by endLevel (the capabilities it switched on go off, its scoped values go). Constraints and fractures name nodes, so the level's bodies must exist already. Returns what could not be linked.
endLevel()
fixedUpdate(dt: number)Called before the components' fixed update, at the fixed rate.

packages/physics/src/host.ts:210

PhysicsService class

class PhysicsService implements Service

Collision layers, trigger volumes with enter/exit events, raycasts and overlap queries. Static level geometry is Babylon meshes with checkCollisions; characters move against it (see

readonly name: "physics"
readonly layers: CollisionLayers
readonly colliders: Set<Collider>
constructor(opts: PhysicsOptions = {})
init(world: World)
markStatic(root: BABYLON.TransformNode | BABYLON.AbstractMesh, layer = 'World')Make a node's meshes static world geometry: blocking for characters and hit by raycasts.
raycast(origin: BABYLON.Vector3, dir: BABYLON.Vector3, maxDistance: number, opts: { mask?: number; ignore?: (m: BABYLON.AbstractMesh) => boolean } = {}): RayHit | nullFirst hit of a ray against static geometry (and optionally entity colliders' meshes) on the masked layers.
overlapSphere(center: BABYLON.Vector3, radius: number, mask = -1): Collider[]Colliders whose volume overlaps a sphere.
dropColliderPairs(c: Collider)
fixedUpdate()Called before the components' fixed update, at the fixed rate.
occupants(trigger: Collider): Entity[]Who is inside a trigger right now.
dispose()

packages/physics/src/physics.ts:58

RigidBody class

class RigidBody extends Component<RigidBodyParams>

A node that the physics kernel moves: the node's position and rotation follow the body after every step.

host: PhysicsHost
body: Body
onAttach()
onDetach()
get velocity(): Vec3

packages/physics/src/host.ts:103

ScopedConfig class

class ScopedConfig

A capability's values at four scopes; the narrowest that has the key wins: entity, zone, level, world, then the defaults.

constructor(private host: PhysicsHost, readonly defaults: Record<string, unknown> = {})
set(scope: 'world' | 'level', key: string, value: unknown): void
set(scope: 'zone' | 'entity', key: string, value: unknown, id: string): void
set(scope: Scope, key: string, value: unknown, id?: string)
assign(scope: 'world' | 'level', values: Record<string, unknown>)
clearLevel()
where(key: string, ctx: ScopeContext = {}): Scope | 'default' | undefinedWhich layer a value comes from for this context: the narrowest scope that has the key, or default / undefined when none has it.
get<T = number>(key: string, ctx: ScopeContext = {}): T

packages/physics/src/host.ts:42

Functions

add function

function add: (a: Vec3, b: Vec3) => Vec3

packages/physics/src/vec.ts:7

addTo function

function addTo: (a: Vec3, b: Vec3, s?: number) => void

packages/physics/src/vec.ts:15

boundingRadius function

function boundingRadius(s: Shape): number

Radius of the sphere around the body's origin that holds the shape.

packages/physics/src/shapes.ts:103

bounds function

function bounds(s: Shape): [Vec3, Vec3]

Axis-aligned bounds in body space.

packages/physics/src/shapes.ts:36

combineValues function

function combineValues(rule: CombineRule, a: number, b: number): number

packages/physics/src/materials.ts:47

contactScope function

function contactScope(a: Body, b: Body, point: Vec3): ScopeContext

Where to look up a capability's scoped values for a contact: the dynamic body's entity (else the other one), and the contact point (so a zone applies).

packages/physics/src/host.ts:434

copy function

function copy: (a: Vec3) => Vec3

packages/physics/src/vec.ts:16

cross function

function cross: (a: Vec3, b: Vec3) => Vec3

packages/physics/src/vec.ts:11

defaultRules function

function defaultRules: (t: TierBudget) => DegradeRule[]

packages/physics/src/budget.ts:34

dot function

function dot: (a: Vec3, b: Vec3) => number

packages/physics/src/vec.ts:10

entityOf function

function entityOf(b: Body): Entity | undefined

The entity a body belongs to (the RigidBody entity), when it has one.

packages/physics/src/host.ts:428

len function

function len: (a: Vec3) => number

packages/physics/src/vec.ts:12

len2 function

function len2: (a: Vec3) => number

packages/physics/src/vec.ts:13

localSdf function

function localSdf(s: Shape): (x: number, y: number, z: number) => number

Geometry of the body shapes in body space, for capabilities that need more than a radius: a signed distance (negative inside), the volume, the bounding box, and a lattice of sample points that stand for the volume (buoyancy, drag, heat, a liquid's walls).

packages/physics/src/shapes.ts:8

lowestBelow function

function lowestBelow(s: Shape, q: [number, number, number, number]): number

Height of the shape's lowest point below the body's origin, the body turned by q (the radius, for a sphere), m.

packages/physics/src/shapes.ts:145

norm function

function norm: (a: Vec3) => Vec3

packages/physics/src/vec.ts:14

overlapsSphere function

function overlapsSphere(c: Collider, center: BABYLON.Vector3, radius: number): boolean

Is a world-space sphere inside the collider's volume (box: oriented; sphere; capsule)?

packages/physics/src/physics.ts:160

overlapsTrigger function

function overlapsTrigger(t: Collider, o: Collider): boolean

Does collider o overlap the trigger volume t? A capsule is tested as a swept sphere along its axis (several samples).

packages/physics/src/physics.ts:174

projectedArea function

function projectedArea(s: Shape, q: [number, number, number, number], dir: Vec3): number

Area the shape shows to a direction (world dir, the body turned by q), m²: exact for a sphere and a box, from the volume otherwise.

packages/physics/src/shapes.ts:110

qFromAxisAngle function

function qFromAxisAngle(axis: Vec3, angle: number): Quat

packages/physics/src/vec.ts:25

qIdentity function

function qIdentity: () => Quat

packages/physics/src/vec.ts:18

qIntegrate function

function qIntegrate(q: Quat, w: Vec3, dt: number): Quat

Integrate an orientation by an angular velocity over dt (exact for constant w).

packages/physics/src/vec.ts:38

qInverse function

function qInverse: (q: Quat) => Quat

packages/physics/src/vec.ts:24

qMul function

function qMul(a: Quat, b: Quat): Quat

packages/physics/src/vec.ts:29

qRotate function

function qRotate(q: Quat, v: Vec3): Vec3

packages/physics/src/vec.ts:19

sampleShape function

function sampleShape(s: Shape, n = 4): ShapeSamples

n cells along each axis of the bounds, the ones whose centre is inside; each carries an equal share of the volume.

packages/physics/src/shapes.ts:97

scale function

function scale: (a: Vec3, s: number) => Vec3

packages/physics/src/vec.ts:9

scopedSurface function

function scopedSurface(host: PhysicsHost, b: Body): { friction: number; kineticFriction: number; restitution: number }

A body's friction and restitution where it is now: its material's, times the friction capability's scale and the impact capability's scale at its scope when they are on. What a kernel without contact hooks is given through setSurface.

packages/physics/src/host.ts:443

seededRng function

function seededRng(seed: number): () => number

A seeded generator (mulberry32): the same seed gives the same sequence on every machine.

packages/physics/src/vec.ts:48

shapeVolume function

function shapeVolume(s: Shape): number

Exact volume where the shape has a formula; otherwise from a fine lattice.

packages/physics/src/shapes.ts:77

splitShape function

function splitShape(s: Shape, n: number): Piece[]

Cut a shape into about n box pieces (body space): the bounds cut along their longest edges until there are n cells, the cells whose centre lies outside the shape dropped and their volume shared among the rest, so the pieces hold the whole volume. A sphere is cut the same way.

packages/physics/src/shapes.ts:126

sub function

function sub: (a: Vec3, b: Vec3) => Vec3

packages/physics/src/vec.ts:8

syncSurfaces function

function syncSurfaces(host: PhysicsHost, seen: Map<Body, string>)

Give every body whose scoped surface changed its new surface (kernels without contact hooks); seen keeps what each one has.

packages/physics/src/host.ts:451

v3 function

function v3: (x?: number, y?: number, z?: number) => Vec3

packages/physics/src/vec.ts:6

validateMaterials function

function validateMaterials(table: unknown, base = 'materials'): Issue[]

Check a materials table: numbers only, inside sane ranges. Returns the problems (empty when fine).

packages/physics/src/materials.ts:33

validatePhysicsConfig function

function validatePhysicsConfig(c: unknown, known: string[] = []): Issue[]

packages/physics/src/host.ts:159

volumeBelow function

function volumeBelow(s: Shape, p: Vec3, q: [number, number, number, number], y: number): number

Volume of the shape below a horizontal surface at height y, the body at p turned by q: by its sample cells (exact for a sphere).

packages/physics/src/shapes.ts:156

Interfaces

Body interface

interface Body
readonly id: number
readonly kind: BodyKind
readonly shape: Shape
mass: number
invMass: number
invInertia: number
inertia?: Vec3principal moments of inertia, kg·m², where the kernel knows them (a box, a hull); a sphere's three are equal
position: Vec3
orientation: Quat
velocity: Vec3
angularVelocity: Vec3
force: Vec3
torque: Vec3
material?: string
linearDamping: number
angularDamping: number
gravityScale: number
sleeping: boolean
userData?: unknown
removed: boolean
exempt?: string[]capabilities that leave this body alone (a vessel computes its own weight and buoyancy: ['gravity', 'media'])

packages/physics/src/kernel.ts:39

BodyDef interface

interface BodyDef
shape: Shape
kind?: BodyKind
mass?: number
position?: Vec3
orientation?: Quat
velocity?: Vec3
angularVelocity?: Vec3
material?: string
linearDamping?: number
angularDamping?: number
gravityScale?: number
lockRotation?: booleanNo spin: the sphere slides like a block (a crate, a puck) instead of rolling.
noCollide?: booleanTouches nothing: other bodies pass through it (a pivot pin that only anchors joints). Joints still hold it. A rigid-body kernel only.
userData?: unknown

packages/physics/src/kernel.ts:20

Capability interface

interface Capability
readonly name: string
readonly phase: Phase
readonly order?: numberPosition among capabilities of the same phase (lower first); ties go by name, never by registration.
readonly cost?: stringWhat it costs, in words, for the inspector and the budget notes.
readonly defaults?: Record<string, unknown>Values at every scope unless overridden.
readonly needsBodies?: booleanNeeds rigid bodies from the kernel; it refuses to switch on when the kernel has none.
readonly requires?: string[]Names of capabilities that must be on first.
load?(host: PhysicsHost, value: unknown[], scope: 'world' | 'level'): voidA list in the level's physics block under this capability's name (actuators): handed over when the block is applied.
loadBindings?(host: PhysicsHost, value: unknown[]): voidThe level's physics.bindings list (actuators): what drives its commands from input, interactables, zones and events. Dropped with the level.
unloadBindings?(): void
enable?(host: PhysicsHost): void
disable?(host: PhysicsHost): void
step?(dt: number, host: PhysicsHost, contacts: readonly ContactEvent[]): voidPhase 'field': add forces for this step. Phases 'strength' and 'stretch': act on what the step did.

packages/physics/src/host.ts:13

ColliderParams interface

interface ColliderParams
shape?: ShapeKind
size?: [number, number, number]box: full size (x, y, z)
radius?: numbersphere / capsule radius
height?: numbercapsule total height (feet to head)
offset?: [number, number, number]centre offset from the entity's origin
layer?: string
mask?: string[]layers this collider overlaps with (triggers) or collides with
trigger?: booleana trigger fires enter/exit events instead of blocking

packages/physics/src/physics.ts:6

CombineRules interface

interface CombineRules
friction: CombineRule
restitution: CombineRule
rollingFriction: CombineRule
[property: string]: CombineRule

packages/physics/src/materials.ts:20

Constraint interface

interface Constraint
readonly id: number
type: ConstraintType
readonly a: Body
readonly b: Body
length: number
stiffness: number
damping: number
maxForce: number
userData?: unknown
force: numberforce over the last step, N: positive in tension (it pulls the two bodies together), negative in compression
stretch: numberpresent distance less the length, m
broken: boolean
anchor?: Vec3
axis?: Vec3

packages/physics/src/kernel.ts:160

ConstraintDef interface

interface ConstraintDef
type: ConstraintType
a: Body
b: Body
length?: numberthe distance (rod), the longest distance (rope) or the rest length (spring); the present distance when missing
stiffness?: numberN/m (spring)
damping?: numberN·s/m (spring)
maxForce?: numberthe constraint breaks when the force on it (N) passes this; never when missing
anchor?: Vec3ball, hinge, fixed: the shared point in world space (the middle between the bodies when missing)
axis?: Vec3hinge: the axis in world space (up when missing)
limits?: [number, number]hinge: the angle from the pose it was made in, radians, from the lower to the upper limit; free when missing
userData?: unknown

packages/physics/src/kernel.ts:140

Contact interface

interface Contact

One contact between two bodies during a step. normal points from b toward a.

a: Body
b: Body
point: Vec3
normal: Vec3
depth: number
friction: numberMutable before the solve: a hook may change what this contact uses.
kineticFriction: number
rollingFriction: number
pivotFriction: number
restitution: number
restitutionThreshold: numberSpeed below which the bounce is a rest.
readonly closing: numberClosing speed along the normal when the contact is made (m/s, positive = approaching); read-only for hooks.
anisotropy?: { axis: Vec3; across: number }Friction that differs along a direction (skates, a rail): the coefficient across axis is across times the one along it.
sticky?: booleanA capability's request that the two bodies leave this contact as one (a perfectly inelastic hit).

packages/physics/src/kernel.ts:66

ContactEvent interface

interface ContactEvent

What happened at one contact over a step, delivered after it.

a: Body
b: Body
point: Vec3
normal: Vec3
impulse: numberTotal normal impulse over the step (N·s).
speed: numberLargest closing speed along the normal during the step (m/s).
slide: numberSliding speed along the surface at the largest impact (m/s).
restitution: numberThe restitution the contact was solved with, and whether a capability asked the bodies to stick.
sticky: boolean

packages/physics/src/kernel.ts:89

DegradeRule interface

interface DegradeRule

One step down: values to set at world scope (capability.key = value; a number may be a share of the tier's own: of) and capabilities to switch off.

name: string
set?: { capability: string; key: string; value: number }[]
disable?: string[]

packages/physics/src/budget.ts:32

FieldForce interface

interface FieldForce

What a force field may return: a force, a torque, or both.

force?: Vec3
torque?: Vec3

packages/physics/src/kernel.ts:108

GovernorOptions interface

interface GovernorOptions
tier?: Tier
stepMs?: numberoverride the tier's step budget, ms
rules?: DegradeRule[]
window?: numbersteps averaged before a decision (default 30)
down?: numberwindows over budget in a row before stepping down (default 2) and windows well under it before stepping back up (default 4)
up?: number

packages/physics/src/budget.ts:40

HavokKernelOptions interface

interface HavokKernelOptions
materials?: MaterialLibrary
gravity?: Vec3
substeps?: numberHavok steps per world step; force fields are evaluated before each (1 = once a step)

packages/physics/src/havok-kernel.ts:6

Issue interface

interface Issue
path: string
message: string

packages/physics/src/materials.ts:30

KernelCaps interface

interface KernelCaps
friction: booleanStatic and kinetic friction, restitution with a threshold and combine rules from materials.
restitutionCombine: boolean
rollingFriction: boolean
contactImpulses: boolean
constraints: boolean
breakableConstraints: boolean
ccd: 'none' | 'substep' | 'native''substep' = small steps scaled by speed; 'native' = swept collision.
shapes: string[]
contactHooks?: booleanbeforeSolve can change a contact before it is solved; false: hooks see nothing and per-pair changes go through materials
rigidBodies?: booleanbodies of any shape move, rotate with a full inertia tensor and stack
joints?: booleanball, hinge and fixed constraints

packages/physics/src/kernel.ts:110

KernelRayHit interface

interface KernelRayHit
body: Body
point: Vec3
normal: Vec3
distance: number

packages/physics/src/kernel.ts:105

LevelConstraint interface

interface LevelConstraint
id?: string
type: 'rod' | 'rope' | 'spring' | 'ball' | 'hinge' | 'fixed'
a: string
b: string
length?: number
stiffness?: number
damping?: number
maxForce?: number
anchor?: Vec3
axis?: Vec3
limits?: [number, number]

packages/physics/src/host.ts:154

LevelFracture interface

interface LevelFracture
node: string
pieces: string[]
material?: string
toughness?: number

packages/physics/src/host.ts:155

LightKernelOptions interface

interface LightKernelOptions
materials?: MaterialLibrary
gravity?: Vec3
substeps?: numberSmallest number of sub-steps per step; fast bodies get more (up to maxSubsteps) so they do not tunnel.
maxSubsteps?: number
iterations?: number
restitutionThreshold?: numberClosing speed below which a bounce is a rest (m/s).

packages/physics/src/light-kernel.ts:5

MediumSample interface

interface MediumSample
name: string
density: number
viscosity: number
flow: Vec3

packages/physics/src/host.ts:136

PhysicsConfig interface

interface PhysicsConfig

What a physics block of a level or a game may hold.

capabilities?: string[]
materials?: Record<string, PhysicsMaterial>
pairs?: Array<{ a: string; b: string; values: PhysicsMaterial }>
combine?: Record<string, CombineRule>
gravity?: { vector?: Vec3 }
constraints?: LevelConstraint[]links between two bodies of the level, by the names of their nodes (a Physics Constraint of Unreal where the kernel has an equivalent)
fractured?: LevelFracture[]bodies authored in pieces: when the intact one breaks, its pieces (nodes of the level) take over
[capability: string]: unknown

packages/physics/src/host.ts:142

PhysicsFields interface

interface PhysicsFields

Read access to what capabilities provide: gravity at a point (m/s², a vector) and, later, the medium.

gravity?: (p: Vec3, ctx?: ScopeContext) => Vec3
medium?: (p: Vec3, ctx?: ScopeContext) => MediumSampleThe medium at a point (air by default): density kg/m³, dynamic viscosity Pa·s, the flow velocity m/s (wind, current).
column?: (p: Vec3, height: number, ctx?: ScopeContext) => { fraction: number; medium: MediumSample | null }The part of a vertical segment of height standing at p that is inside a dense medium (water, mud), and that medium.

packages/physics/src/host.ts:129

PhysicsHostOptions interface

interface PhysicsHostOptions
kernel?: PhysicsKernel
materials?: MaterialLibrary
capabilities?: Capability[]

packages/physics/src/host.ts:139

PhysicsKernel interface

interface PhysicsKernel
readonly name: string
readonly caps: KernelCaps
gravity: Vec3
readonly bodies: readonly Body[]
readonly constraints: readonly Constraint[]
step(dt: number): void
addBody(def: BodyDef): Body
removeBody(b: Body): void
addConstraint(def: ConstraintDef): Constraint
removeConstraint(c: Constraint): void
onConstraintBreak(cb: (c: Constraint) => void): () => voidCalled after the step for each constraint whose force passed its limit; the constraint is already removed.
applyForce(b: Body, f: Vec3, at?: Vec3): void
applyImpulse(b: Body, j: Vec3, at?: Vec3): void
applyTorque(b: Body, t: Vec3): void
setVelocity(b: Body, v: Vec3): void
setPosition(b: Body, p: Vec3): void
sleep(b: Body): void
wake(b: Body): void
addForceField(fn: (b: Body, h: number) => Vec3 | FieldForce | null): () => voidA force that depends on where a body is (gravity, drag, buoyancy): evaluated at the position of every sub-step, so a fast or orbiting body gets the force it feels where it is, not where it was at the start of the step. Returns newtons, or null for none.
beforeSolve(cb: (c: Contact) => void): () => voidCalled for every contact before it is solved, so a capability can change friction or restitution.
onContact(cb: (e: ContactEvent) => void): () => voidCalled after the step with what each contact did.
rayCast(origin: Vec3, dir: Vec3, maxDistance: number): KernelRayHit | null
setMassProperties?(b: Body, mass: number, centreOfMass: Vec3, inertia: Vec3): voidMass, centre of mass (body space) and principal inertia set from outside (a vessel whose tanks fill); where the kernel can.
setSurface?(b: Body, s: { friction: number; kineticFriction: number; restitution: number }): voidA body's own friction and restitution, over its material's: how a kernel without contact hooks takes a capability's dials.

packages/physics/src/kernel.ts:179

PhysicsMaterial interface

interface PhysicsMaterial

Physical materials as data. A value that is not given means "this capability does not apply to it", not a default: a body with no strength cannot break. The few values a contact cannot do without (friction, restitution) have library defaults, stated here.

density?: number
friction?: number
kineticFriction?: number
rollingFriction?: number
pivotFriction?: number
restitution?: number
youngModulus?: number
yieldStrength?: number
tensileStrength?: number
toughness?: number
[property: string]: number | undefined

packages/physics/src/materials.ts:5

PhysicsOptions interface

interface PhysicsOptions
layers?: string[]

packages/physics/src/physics.ts:51

Piece interface

interface Piece
shape: Shape
position: Vec3
volume: number

packages/physics/src/shapes.ts:121

RayHit interface

interface RayHit
point: BABYLON.Vector3
normal: BABYLON.Vector3
distance: number
mesh: BABYLON.AbstractMesh
entity?: Entity

packages/physics/src/physics.ts:24

RigidBodyParams interface

interface RigidBodyParams
shape?: 'sphere' | 'box' | 'plane' | 'capsule' | 'cylinder'
radius?: number
height?: numbercapsule and cylinder: the length along y (a capsule's between the centres of its caps)
geometry?: Shapeany shape the kernel has (a hull, a compound, a mesh): wins over shape
size?: [number, number, number]box: full size, like Collider
normal?: Vec3
offset?: number
mass?: numberkg; taken from the material density and the shape when missing
material?: string
kind?: BodyKind
linearDamping?: number
angularDamping?: number
gravityScale?: number
velocity?: Vec3
angularVelocity?: Vec3
lockRotation?: boolean
rotate?: booleanTurn the node with the body (a rolling ball); off for a body whose node is a model that should stay upright.

packages/physics/src/host.ts:81

ScopeContext interface

interface ScopeContext

Where a value is asked from: an entity, or just a place (a zone covers it).

entity?: Entity | string
position?: Vec3

packages/physics/src/host.ts:38

ShapeSamples interface

interface ShapeSamples
points: Vec3[]cell centres inside the shape, body space
cell: Vec3the cell's edges along x, y, z, m
volume: numberthe volume each point stands for, m³ (the shape's volume over the count, so the sum is exact where the volume is known)

packages/physics/src/shapes.ts:67

TierBudget interface

interface TierBudget
stepMs: numberthe share of a frame the whole physics step may take, milliseconds
maxPieces: numberpieces alive at once (the strength capability's maxPieces)
fluidParticles: numberparticles of all liquids, and of all cloths: for the game to give the capabilities that take such a budget
clothParticles: number
flowSources: numberflow sources the level's field takes (FlowField.maxSources) and the cells of flow boxes solved per physics step, all boxes together
flowBoxCells: number

packages/physics/src/budget.ts:12

TriggerEvent interface

interface TriggerEvent
trigger: Entity
other: Entity

packages/physics/src/physics.ts:23

Types

BodyKind type

type BodyKind = 'static' | 'dynamic' | 'kinematic'

packages/physics/src/kernel.ts:18

CombineRule type

type CombineRule = 'mean' | 'geometric' | 'min' | 'max' | 'multiply'

packages/physics/src/materials.ts:19

ConstraintType type

type ConstraintType = 'rod' | 'rope' | 'spring' | 'ball' | 'hinge' | 'fixed'

A link between two bodies' centres. - rod: a rigid distance (pushes and pulls) - rope: a maximum distance (pulls only; slack when closer) - spring: Hooke's law with damping (implicit, so stable at any stiffness) - ball: the two bodies share a point and turn freely about it (a shoulder, a hook) - hinge: they share a point and turn about one axis only, within optional limits (a door, a wheel) - fixed: they are welded in the pose they have (a bracket that can break) The last three need a kernel with caps.joints (rigid bodies).

packages/physics/src/kernel.ts:139

Phase type

type Phase = 'field' | 'strength' | 'stretch'

The fixed order of the step: fields add forces, the kernel integrates and resolves contacts, strength reads them, stretch integrates soft things.

packages/physics/src/host.ts:10

Quat type

type Quat = [number, number, number, number]

packages/physics/src/vec.ts:4

Scope type

type Scope = 'world' | 'level' | 'zone' | 'entity'

packages/physics/src/host.ts:39

Shape type

type Shape = | { type: 'sphere'; radius: number } | { type: 'box'; half: Vec3 } // static or kinematic only in the light kernel | { type: 'plane'; normal: Vec3; offset: number } // static only: dot(normal, p) = offset // the shapes below need a rigid-body kernel (Havok); the light kernel refuses them | { type: 'capsule'; radius: number; halfHeight: number } // along the body's y, halfHeight to the centre of each cap | { type: 'cylinder'; radius: number; halfHeight: number } // along the body's y | { type: 'convex'; points: Vec3[] } // the hull of the points, in body space | { type: 'mesh'; positions: number[]; indices: number[] } // triangles in body space; static or kinematic only | { type: 'compound'; children: { shape: Shape; position?: Vec3; orientation?: Quat }[] }

The part of the dynamics every capability is written against. Havok, the light kernel of this package, or a game's own backend implements it; a capability asks caps what is native and does the rest in a force or contact hook.

packages/physics/src/kernel.ts:7

ShapeKind type

type ShapeKind = 'box' | 'sphere' | 'capsule'

packages/physics/src/physics.ts:4

Tier type

type Tier = 'desktop' | 'mobile' | 'console' | 'low'

What a hardware tier can afford, and what to give up when the world step runs over its share of the frame. The numbers in TIERS come from tools/bench (2026-11-02), run on the dev machine in Node, Chromium and WebKit: a full step share of 6 ms holds about 200 resting pieces, or about 170 to 290 liquid particles, or about 230 cloth particles when each takes 40 / 30 / 30 % of it. The console, mobile and low tiers are scaled down from the same table; they are not measured on those machines: run tools/bench there and replace them. flowBoxCells comes from the flow box's measured 0.9 microseconds per cell per step (A13) at 30 % of the step share: 1.8 ms / 0.9 us = 2000 cells stepped every physics step on the desktop (a box stepped every n-th step may have n times as many).

packages/physics/src/budget.ts:11

Vec3 type

type Vec3 = [number, number, number]

Plain-number vectors for the physics code: no allocation-heavy classes, no engine dependency, and the same arithmetic everywhere so a replay gives the same bits. A vector is [x, y, z]; a quaternion is [x, y, z, w].

packages/physics/src/vec.ts:3

Constants

DEFAULT_COMBINE const

const DEFAULT_COMBINE: CombineRules

packages/physics/src/materials.ts:22

DEFAULT_CONTACT const

const DEFAULT_CONTACT: { friction: number; restitution: number; rollingFriction: number; pivotFriction: number; }

packages/physics/src/materials.ts:23

DEFAULT_LAYERS const

const DEFAULT_LAYERS: string[]

The layers a game gets unless it defines its own (the common UE object channels).

packages/physics/src/layers.ts:35

EXAMPLE_MATERIALS const

const EXAMPLE_MATERIALS: Record<string, PhysicsMaterial>

A few common materials, as examples and for tests. A game defines its own.

packages/physics/src/materials.ts:106

TIERS const

const TIERS: Record<Tier, TierBudget>

packages/physics/src/budget.ts:24