Phaser AE

Rigid-body 2D physics

Free packv3.0.0-42DPhysics

The engine's own rigid-body solver for 2D games: things that fall, push, stack, bounce and swing. A body is an ordinary game object with a `body` key, and what a platformer or physics puzzle needs, from one-way platforms to conveyor belts and water, is part of its spec.

This is a Free pack. Every phaser.io account can use it in their games.

Released October 14, 2026 · updated October 19, 2026

Rigid-body 2D physics: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

The pack

physics2d

function physics2d(opts: Physics2dOptions = {}): Pack<{ physics: Physics2dApi }, PhysicsTopics, { body: BodyData }, { body?: BodySpec }>

The core on the roster. Bodies are roster objects with a body key; the physics step reads their x/y/rot (so game code may still move them — a teleport is adopted), integrates, and writes x/y/rot back before collision.

Classes

World

class World

The 2D physics world: bodies held in typed arrays (indexed by the number add returns), the contacts and joints between them, and the step that moves them. Units are world units, seconds and radians; y points down.

  • gravityX: number — Gravity along x in units/s²; default 0.
  • gravityY: number — Gravity along y in units/s², positive is down; default 900.
  • substeps: number — Substeps per step. 4 is plenty for games; 8 for tall stacks.
  • contactHertz: number — Contact stiffness. Keep below a quarter of the substep rate (60 at 4 substeps of 60 Hz) or the solver rings.
  • contactDamping: number — Contact damping ratio (dimensionless; 1 is critical damping); default 10.
  • jointHertz: number — Stiffness, in hertz, of the soft constraints that hold joints together; default 60. As for contactHertz, keep it below a quarter of the substep rate.
  • jointDamping: number — Damping ratio (dimensionless) of those joint constraints; default 2.
  • restitutionThreshold: number — Approach speed, in units/s, below which a bounce is absorbed; default 60. A body's own minBounceVelocity overrides it.
  • sleepVelocity: number — Linear speed, in units/s, below which a body counts as still; default 3.
  • sleepAngular: number — Angular speed, in radians/s, below which a body counts as still; default 0.05.
  • sleepTime: number — Seconds a body must stay still before it may sleep (a body only sleeps if everything it touches can too); default 0.5.
  • maxBias: number — Fastest push-out speed, in units/s, that a deep overlap or a joint error may produce; default 400.
  • linearSlop: number — Tolerance, in world units, when generating polygon contacts: edges and edge ends this close count as parallel or touching; default 0.5.
  • cap: number — Slots allocated in each per-body array; it doubles when add needs more.
  • count: number — Slots used so far: the highest body index plus one, freed slots included. Loop to this and skip bodies for which alive is false.
  • x!: Float32Array — Body centre x, in world units. Every per-body array is indexed by the number add returned.
  • y!: Float32Array — Body centre y, in world units.
  • a!: Float32Array — Body angle, in radians.
  • ca!: Float64Array — cos and sin of each body's angle, and the angle they were computed for (trig keeps them current).
  • sa!: Float64Array — Sine of each body's angle; see ca.
  • vx!: Float32Array — Linear velocity x, in units/s.
  • vy!: Float32Array — Linear velocity y, in units/s.
  • av!: Float32Array — Angular velocity, in radians/s.
  • invM!: Float32Array — Inverse mass; 0 for a static or kinematic body, which no force can move.
  • invI!: Float32Array — Inverse rotational inertia; 0 for a static, kinematic or fixed-rotation body.
  • mass!: Float32Array — Mass; 0 for a static or kinematic body.
  • dens!: Float32Array — Each body's own density, as given: what buoyancy is measured against.
  • minBounceVelocity!: Float32Array — Approach speed, in units/s, below which this body absorbs a bounce; negative (the default, -1) defers to restitutionThreshold.
  • rest!: Float32Array — Restitution: 0 is no bounce, 1 a perfect one; default 0.
  • fric!: Float32Array — Friction coefficient; default 0.6.
  • gscale!: Float32Array — Multiplier on gravity for this body; default 1.
  • ldamp!: Float32Array — Linear damping rate per second; default 0.
  • adamp!: Float32Array — Angular damping rate per second; default 0.05.
  • type!: Uint8Array — Body type: STATIC (0), DYNAMIC (1) or KINEMATIC (2).
  • flags!: Uint8Array — Bit flags: 1 fixed rotation, 2 bullet, 4 awake, 8 alive, 16 sensor.
  • sleep!: Float32Array — Seconds the body has been still; it may sleep once this reaches sleepTime.
  • user!: Int32Array — The caller's own number for the body (BodyDef.user), mapping contacts back to it; -1 if none was given.
  • oneWay!: Uint8Array — One-way side: 0 none, 1 up, 2 down, 3 left, 4 right (see OneWay).
  • surf!: Float32Array — Conveyor speed, in units/s, along the body's surface; 0 for none.
  • ax!: Float32Array — Persistent acceleration along x, in units/s² (thrust, per-body wind).
  • ay!: Float32Array — Persistent acceleration along y, in units/s².
  • dragX!: Float32Array — Deceleration toward zero along x, in units/s², applied while ax is 0.
  • dragY!: Float32Array — Deceleration toward zero along y, in units/s², applied while ay is 0.
  • maxVx!: Float32Array — Speed cap along x, in units/s; 0 for no cap.
  • maxVy!: Float32Array — Speed cap along y, in units/s; 0 for no cap.
  • category!: Uint32Array — Collision category bits; default 1.
  • mask!: Uint32Array — Category bits this body accepts; default 0xffffffff. Two bodies collide only if each one's mask accepts the other's category.
  • bounds: Bounds | null — The world's boundary (walls or bouncing edges), or null for none.
  • boundsHits: BoundsHit[] — Edge hits in the last step: body index, edge and approach speed. Cleared at every step.
  • effectors: (EffectorDef & { id: number })[] — The active effectors, each with the id that addEffector gave it.
  • minx!: Float32Array — Left edge of each body's bounding box, in world units (the box of its bounding circle).
  • miny!: Float32Array — Top edge of each body's bounding box, in world units.
  • maxx!: Float32Array — Right edge of each body's bounding box, in world units.
  • maxy!: Float32Array — Bottom edge of each body's bounding box, in world units.
  • bound!: Float32Array — Radius of each body's bounding circle about its centre, in world units.
  • pieces: Piece[][] — Each body's convex pieces (circles and polygons) in body-local coordinates.
  • contacts: Contact[] — Contacts found in the last step, sensor overlaps included, for the caller to read; rebuilt at every step.
  • joints: (Omit<JointDef, 'length'> & { length: number; id: number; alive: boolean; pn: number; pnx: number; pny: number; lowerImpulse: number; upperImpulse: number; motorImpulse: number; angleImpulse: number; springImpulse: number; axisImpulse: number })[] — Joint records, each with the id that addJoint returned. A joint with alive false is dropped at the next step.
  • constructor(capacity = 256)
  • add(d: BodyDef): number — Add a body and return its index, reusing a freed slot if there is one and growing the arrays if not. With no shape the body is a box of width by height (32 by 32 if omitted).
  • remove(i: number): void — Remove body i along with its contacts and joints. Its index is reused by a later add. Does nothing if it is already gone.
  • alive(i: number): boolean — Whether body i exists: it has been added and not removed.
  • isAwake(i: number): boolean — Whether body i is awake rather than asleep.
  • wake(i: number): void — Wake body i and reset its sleep timer; does nothing for a removed body.
  • setType(i: number, t: 'static' | 'dynamic' | 'kinematic'): void — Change body i to 'static', 'dynamic' or 'kinematic' and wake it. A body made dynamic gets back the mass and inertia it was added with (its density's, or its own mass); a static or kinematic one has none.
  • applyImpulse(i: number, ix: number, iy: number, px = this.x[i]!, py = this.y[i]!): void — Give dynamic body i an instant change of momentum (ix, iy) (mass times velocity) at world point (px, py), by default its centre; off-centre impulses spin it. Wakes the body; does nothing for a static or kinematic one.
  • applyForce(i: number, fx: number, fy: number, dt: number): void — Apply force (fx, fy) to body i for dt seconds, as an impulse of force times dt.
  • teleport(i: number, x: number, y: number, angle = this.a[i]!): void — The caller moved a body by hand (the sprite's x/y changed): adopt it.
  • trig(i: number): void — Make ca[i] and sa[i] the cos and sin of body i's angle. They are computed only when the angle has changed since the last call (a signed zero counts as a change), so a turning body pays for them once a substep rather than once per contact. The values are exactly Math.cos and Math.sin of the angle, whoever asks.
  • step(dt: number): void — Advance the world by dt seconds: collision detection once, then substeps solver passes, then restitution, bullet sweeps, bounds, contact reports and sleeping. Does nothing if dt is 0 or less.
  • containsPoint(i: number, px: number, py: number): boolean — Whether world point (px, py) is inside any piece of body i.
  • addEffector(d: EffectorDef): EffectorDef & { id: number } — Add an effector and return the stored object, now carrying its id. It is not a copy: changing its fields takes effect on the next step.
  • removeEffector(id: number): void — Remove the effector with this id; an unknown id is ignored.
  • addJoint(d: JointDef): number — Add a joint and return its id, waking both bodies. Defaults are filled in: a distance joint's length is the present distance between its anchors, the slide axis is normalised, and refAngle is B's present angle relative to A's.
  • removeJoint(id: number): void — Remove a joint. Both ends WAKE: a body held up by a joint that has gone must fall, and a sleeping one would hang in the air.
  • anchorWorld(i: number, lx: number, ly: number): { x: number; y: number } — A body-local point in WORLD units. Public because a joint's ends are drawable: a rope a game can see is a rope a game can draw.
  • raycast(x: number, y: number, dx: number, dy: number, maxDist: number, filter?: (body: number) => boolean): RayHit | null — The nearest body hit by a ray from (x, y) along (dx, dy) (any length) within maxDist world units, or null. fraction in the hit is the distance as a share of maxDist. filter skips the bodies it rejects; sensors are otherwise included.
  • queryAabb(x: number, y: number, w: number, h: number, out: number[] = []): number[] — Bodies whose bounds overlap the rect.
  • contactsOf(i: number, out: Contact[] = []): Contact[] — Contacts touching body i right now.

Types

BodyData

interface BodyData

What h.body presents: the columns plus the verbs. Writable fields are live: the next step adopts them.

  • vx: number — x velocity, units/s. Written by the step; set it to change it.
  • vy: number — y velocity, units/s. Written by the step; set it to change it.
  • av: number — Angular velocity, rad/s.
  • ax: number — Thrust on x, units/s² — live, like velocity.
  • ay: number — Thrust on y, units/s².
  • dragX: number — Deceleration toward zero on x when not thrusting, units/s².
  • dragY: number — Deceleration toward zero on y when not thrusting, units/s².
  • maxVx: number — Speed cap on |vx|, units/s; 0 is none.
  • maxVy: number — Speed cap on |vy|, units/s; 0 is none.
  • readonly mass: number — Mass from density × area or the spec's mass; 0 for static and kinematic bodies.
  • restitution: number — Bounciness 0..1.
  • minBounceVelocity: number — Live per-body bounce cutoff; -1 inherits the solver threshold.
  • friction: number — Surface friction.
  • gravityScale: number — Multiplier on world gravity.
  • density: number — The body's own density — what BUOYANCY measures the fluid's density against. Live. Default 1.
  • surfaceSpeed: number — Conveyor speed along the surface, units/s — live, so a belt can speed up, stop or reverse. 0 is an ordinary surface.
  • readonly type: number — 0 static, 1 dynamic, 2 kinematic. Change it with physics.setType.
  • readonly awake: number — 1 awake, 0 asleep. A sleeping body does not move until touched, or wake().
  • impulse(ix: number, iy: number, atX?: number, atY?: number): void — An instant change of momentum (mass × units/s) at world point (atX, atY); default the centre. Off-centre spins. Wakes the body.
  • force(fx: number, fy: number): void — A force applied for one 60 Hz frame (it is divided by mass and scaled by 1/60, whatever the fixed step). Wakes the body.
  • wake(): void — Wake a sleeping body.
  • readonly grounded: boolean — Standing on something? (a contact with normal pointing up into this body)
  • readonly outline: ReadonlyArray<{ kind: 'circle'; x: number; y: number; r: number } | { kind: 'polygon'; points: ReadonlyArray<readonly [number, number]> }> — The collision shape in body-local units, one entry per convex piece: what a debug overlay draws. A capsule is a box and two circles.
  • readonly blocked: { up: boolean; down: boolean; left: boolean; right: boolean } — Which sides are touching something (not a sensor), this step.
  • readonly touching: ReadonlyArray<{ other: Handle | null; nx: number; ny: number; impulse: number; speed: number; load: number }> — Contacts touching this body right now. load is the sustained force in multiples of this body's own weight (a resting body on the ground reads 1; a crate on a pig reads the mass ratio).

BodySpec

interface BodySpec

What spawn takes under body. Every field is optional; omit shape for a box the size of the sprite.

  • type?: 'static' | 'dynamic' | 'kinematic' — 'dynamic' moves and is pushed; 'static' never moves; 'kinematic' moves by its own velocity and pushes but is not pushed. Default 'dynamic'.
  • shape?: ShapeSpec | 'circle' | 'box' | 'capsule' — 'box' the sprite's size (default), 'circle' of radius min(w, h) / 2, 'capsule' the sprite's size, or a full ShapeSpec in body-local units.
  • density?: number — Mass per unit area; mass = density × shape area. Default 1. Ignored when mass is given.
  • mass?: number — Total mass, overriding density. Default: from density.
  • restitution?: number — Bounciness 0..1; a pair uses the larger of the two. Default 0.
  • minBounceVelocity?: number — Minimum approach speed for bounce, world units/s. Omit to inherit the solver threshold.
  • friction?: number — Surface friction; a pair uses the geometric mean. Default 0.6.
  • gravityScale?: number — Multiplier on world gravity; 0 floats. Default 1.
  • linearDamping?: number — Fraction of linear velocity lost per second. Default 0.
  • angularDamping?: number — Fraction of angular velocity lost per second. Default 0.05.
  • fixedRotation?: boolean — Never rotates (a character). Default false.
  • bullet?: boolean — Swept against statics so a small fast body never passes through a thin wall. Default false.
  • sensor?: boolean — Overlaps and reports, never pushes: triggers, pickups, zones. Default false.
  • category?: number — Unsigned 32-bit collision category bits. Default 1. Both bodies must accept the other category.
  • mask?: number — Unsigned 32-bit accepted category bits. Default 0xffffffff; 0 disables physical contacts, including sensors. Tag overlap rules remain independent.
  • oneWay?: OneWay — A platform others pass through except from this side ('up' = jump up through it, land on it). Default 'none'.
  • surfaceSpeed?: number — Conveyor belt: things touching move at this tangential speed, units/s. Default 0.
  • ax?: number — Persistent acceleration (thrust) on x, units/s². Default 0.
  • ay?: number — Persistent acceleration (thrust) on y, units/s². Default 0.
  • dragX?: number — Deceleration toward zero on x when ax is 0, units/s² (v2's "drag"). Default 0.
  • dragY?: number — Deceleration toward zero on y when ay is 0, units/s². Default 0.
  • maxVx?: number — Speed cap on |vx|, units/s; 0 is no cap. Default 0.
  • maxVy?: number — Speed cap on |vy|, units/s; 0 is no cap. Default 0.
  • vx?: number — Initial x velocity, units/s. Default 0.
  • vy?: number — Initial y velocity, units/s. Default 0.
  • av?: number — Initial angular velocity, rad/s. Default 0.

Bounds

interface Bounds

The world's boundary rectangle in world units: bodies are kept inside it, each edge either a wall that stops them or an edge that bounces them.

  • x: number — Left edge, world units.
  • y: number — Top edge, world units.
  • w: number — Width, world units.
  • h: number — Height, world units.
  • bounce?: { top?: boolean; bottom?: boolean; left?: boolean; right?: boolean } — which edges bounce (reflect velocity); unset edges are walls that stop
  • edges?: { top?: boolean; bottom?: boolean; left?: boolean; right?: boolean } — which edges exist at all; default all

BoundsRecord

interface BoundsRecord

One physics.bounds signal: a body hit an edge of physics.bounds().

  • handle: Handle — The body that hit.
  • edge: 'top' | 'bottom' | 'left' | 'right' — Which edge.
  • speed: number — Speed into the edge, units/s.

ContactRecord

interface ContactRecord

One physics.contact signal: a touching pair this step. Emitted for every pair with a non-zero impulse, and for every sensor overlap.

  • a: Handle — The first body of the pair.
  • b: Handle — The second body of the pair.
  • nx: number — Contact normal x, unit length, pointing from a toward b.
  • ny: number — Contact normal y, unit length, pointing from a toward b.
  • x: number — Contact point x, world units.
  • y: number — Contact point y, world units.
  • impulse: number — Sustained normal impulse this step — a resting body's own weight shows here.
  • speed: number — Approach speed at impact, units/s — use THIS for thuds and damage.
  • sensor: boolean — An overlap with a sensor body: a trigger, never resolved.

CullRecord

interface CullRecord

A body left the cull rect and was killed. id and the last position — the handle is already dead; respawn from these.

  • id: string — The dead object's id.
  • x: number — Last x, world units.
  • y: number — Last y, world units.
  • vx: number — Last x velocity, units/s.
  • vy: number — Last y velocity, units/s.

EffectorDef

type EffectorDef = | { kind: 'area'; x: number; y: number; w: number; h: number; fx: number; fy: number; /** force as acceleration (units/s²) when true, else as force */ accel?: boolean; /** Linear velocity damping rate inside the area, per second. Default 0. */ drag?: number } | { kind: 'point'; x: number; y: number; radius: number; strength: number; falloff?: 'constant' | 'linear' | 'quadratic'; /** Linear velocity damping rate inside the radius, per second. Default 0. */ drag?: number } | { kind: 'buoyancy'; x: number; y: number; w: number; h: number; density: number; drag?: number; flowX?: number; flowY?: number }

A field that acts on bodies each step: an area of force (wind, a current), a point that pulls or pushes (a gravity well, an explosion) within a radius, or a buoyancy pool that floats bodies by their density.

Joint2d

interface Joint2d

A live joint. Every joint verb hands one back, and every field on it is the one the SOLVER reads — write length and the next step uses it.

That is the whole point of returning an object rather than an id. A grapple reels its rope in, a suspension stiffens over rough ground, a winch pays out: all of them are one number changing every frame, and a joint you can only create and destroy forces a game to tear the joint down and build a new one, which loses the solver's accumulated impulses and shows up as a visible jolt.

  • readonly id: number — The joint id, for unjoint.
  • readonly alive: boolean — False once it has been removed, or once either body died.
  • length: number — A distance joint's rest length. Live: REEL a rope in by shrinking it.
  • min: number — A rope's slack range, world units. Equal min and max is a rigid rod.
  • max: number
  • hertz: number — Spring frequency; 0 is rigid. Live.
  • damping: number — Spring damping ratio. Live.
  • readonly taut: boolean — Is the rope PULLING — within half a unit of its limit? A grapple asks this to know a swing is loaded.
  • readonly anchorA: { x: number; y: number } — Where end A is now, world units. Draw the rope between this and anchorB.
  • readonly anchorB: { x: number; y: number } — Where end B is now, world units.
  • motor(speed: number, maxTorque?: number): void — Retune the motor: radians/s for a hinge, units/s for a slider, spin rad/s for a wheel.
  • destroy(): void — Remove it. The bodies separate; the handle goes dead.

OneWay

type OneWay = 'none' | 'up' | 'down' | 'left' | 'right'

Which side of a body other bodies can cross from: 'none' is solid on every side; 'up', 'down', 'left' or 'right' lets bodies pass through except those arriving from that side, so 'up' is a platform you can jump up through and land on.

Physics2dApi

interface Physics2dApi

game.physics. Everything that changes world state is undone when the scene exits.

  • gravity(x: number, y: number): void — Set world gravity, units/s² (default 0, 900). Wakes every body. Scene-scoped.
  • readonly world: World — The raw simulation, for tests and tools.
  • joint(a: Handle, b: Handle, def: Omit<JointDef, 'a' | 'b'>): Joint2d — Connect two bodies with any joint kind; anchors in each body's local frame. Returns the live joint. Throws physics.joint: both objects need a body. Scene-scoped.
  • unjoint(j: Joint2d | number): void — Remove a joint, by handle or by id; unknown ids are ignored.
  • pin(a: Handle, b: Handle, at: { x: number; y: number }, opts?: { limits?: [number, number]; motor?: { speed: number; torque?: number } }): Joint2d — A HINGE at a world point: doors, ragdoll elbows, pinball flippers, powered wheels. motor drives it.
  • rope(a: Handle, b: Handle, opts?: { atA?: { x: number; y: number }; atB?: { x: number; y: number }; length?: number; min?: number; max?: number; hertz?: number; damping?: number }): Joint2d — A ROPE or a rod between two world points: a rigid length, or min/max for slack, or hertz for a spring.
  • slider(a: Handle, b: Handle, at: { x: number; y: number }, opts?: { axis?: { x: number; y: number }; limits?: [number, number]; motor?: { speed: number; force?: number } }): Joint2d — A SLIDER along an axis: elevators, pistons, sliding doors. Holds the angle; limits are world units along the axis.
  • wheel(a: Handle, b: Handle, at: { x: number; y: number }, opts?: { axis?: { x: number; y: number }; hertz?: number; damping?: number; limits?: [number, number]; motor?: { speed: number; torque?: number } }): Joint2d — A WHEEL: it slides along its axis on a SPRING and spins freely — a car's suspension in one joint.
  • weld(a: Handle, b: Handle, at?: { x: number; y: number }): Joint2d — WELD two bodies rigidly: build a compound after the fact, glue a rider to a platform.
  • grab(h: Handle, at: { x: number; y: number }, opts?: { maxForce?: number; hertz?: number; damping?: number }): { target(x: number, y: number): void; release(): void } — GRAB a body and drag it to a world point — the mouse joint. Move target every frame; release() when done. It is deliberately SOFT: a rigid pull would fling a heavy body across the screen the instant the pointer moved, and the force budget is what makes a heavy thing feel heavy to drag.
  • explosion(at: { x: number; y: number }, radius: number, strength: number): void — A radial blast: a mass-independent outward kick on every dynamic body in range, falling off with distance.
  • chain(points: ReadonlyArray<readonly [number, number]> | ReadonlyArray<{ x: number; y: number }>, opts?: { radius?: number; loop?: boolean; friction?: number; restitution?: number; tags?: string[]; layer?: string }): { readonly bodies: ReadonlyArray<Handle>; kill(): void } — CHAIN TERRAIN from a polyline: a hillside, a cave wall, a loop of track. Each segment becomes its OWN static body — a capsule, so consecutive segments overlap at their round ends and a body crossing a join has no notch to catch on. One body per segment rather than one body of many pieces, because a manifold is per PAIR: twenty-five pieces on one body would collapse into a single contact with a single normal, and a ball would slide off the world. Static bodies are free, so this costs nothing and is exactly what the tile collision does with its merged runs.
  • raycast(x: number, y: number, dx: number, dy: number, maxDist: number, filter?: (h: Handle) => boolean): { handle: Handle; x: number; y: number; nx: number; ny: number; dist: number } | null — The nearest body along a ray, within maxDist units, or null. filter drops bodies it returns false for. dist is in units. Emits debug.ray.
  • setType(h: Handle, type: 'static' | 'dynamic' | 'kinematic'): void — Change a body's type. Made dynamic again, it has the mass it was made with (its density's or its spec's mass). An object with no body is ignored.
  • bounds(b: Bounds | null): void — World bounds: walls, or per-edge bounce (a Breakout ball: bounce top/left/right, no bottom edge). null removes them. Scene-scoped.
  • cull(rect: { x: number; y: number; w: number; h: number } | false): void — Change the cull rect for this scene (undone on exit); false disables.
  • effector(e: EffectorDef): EffectorDef & { id: number } — Force fields: 'area' (wind, a conveyor floor), 'point' (a gravity well; negative strength repels), 'buoyancy' (water). Wakes every body. Scene-scoped. The returned object is the LIVE one the step reads, with its id on it: write e.fx, e.density or e.flowX and the next step uses it — a belt whose speed is a knob does not remove and re-add itself to change.
  • removeEffector(id: number): void — Remove a force field by id.
  • readonly bodies: number — How many bodies exist.

Physics2dOptions

interface Physics2dOptions

Manifest options.

  • gravity?: { x: number; y: number } — World gravity, units/s². Default { x: 0, y: 900 }.
  • substeps?: number — Solver substeps per fixed step. Default 4; more for taller stacks.
  • cull?: { x: number; y: number; w: number; h: number } | false — The cull rect: a DYNAMIC body whose centre leaves it is killed and a physics.cull signal says so. A body that rolls off the world otherwise falls forever, its numbers growing until they are not numbers. Default: a box 100,000 units around the origin — an infinity guard. Set a tight one for the level; false disables.

PhysicsTopics

interface PhysicsTopics

The signals this pack emits, by topic.

  • 'physics.contact': ContactRecord — A touching pair, per step (capacity 512).
  • 'physics.bounds': BoundsRecord — A body hit a world bound (capacity 256).
  • 'physics.cull': CullRecord — A body was culled (capacity 64).

ShapeSpec

type ShapeSpec = | { kind: 'circle'; r: number; x?: number; y?: number } | { kind: 'box'; w: number; h: number; x?: number; y?: number; angle?: number } | { kind: 'polygon'; points: ReadonlyArray<readonly [number, number]> | ReadonlyArray<{ x: number; y: number }> } | { kind: 'compound'; shapes: ShapeSpec[] } /** A platformer character: a box with round ends. Glides over tile seams and slope joins. */ | { kind: 'capsule'; w: number; h: number }

Shapes. Everything the solver touches is convex: a circle, or a convex polygon in body-local coordinates with precomputed edge normals. A concave polygon is triangulated (ear clipping) into convex pieces at creation; a box is a 4-gon. Mass and inertia come from the pieces.