Physics overview
Meep ships a rigid-body physics engine in the box - pure JavaScript, built for massive worlds, fully debuggable, and bit-exact across V8 runtimes.
Meep comes with a rigid-body physics engine built in. Not a binding to a WebAssembly blob, not a third-party library you wire up yourself - an engine, written in the same JavaScript as the rest of Meep, that you turn on by registering two systems.
Built in, not bolted on
Most web engines don’t ship physics. Babylon and PlayCanvas both tell you to bring your own - Rapier, Cannon, Ammo, Havok - and give you an integration layer at best. That means a second runtime to load, a WASM heap that lives outside your debugger, and a simulation whose source you can’t step into.
Meep’s physics is part of the engine. A body is three components on one entity, and the simulation is two systems:
import { PhysicsSystem } from "@woosh/meep-engine/src/engine/physics/ecs/PhysicsSystem.js";
import { ColliderObserverSystem } from "@woosh/meep-engine/src/engine/physics/ecs/ColliderObserverSystem.js";
const physics = new PhysicsSystem();
em.addSystem(physics); // the solver
em.addSystem(new ColliderObserverSystem(physics)); // attaches colliders to bodies
Both lines are required - PhysicsSystem says so in its own contract. Its dependency tuple is (RigidBody, Transform64); it is ColliderObserverSystem that watches for (Collider, Transform64) and attaches each collider to a body, so a PhysicsSystem registered on its own leaves you with bodies that have nothing to collide against.
The observer resolves the body from the collider’s own entity when that entity also carries a RigidBody, and from its ParentEntity when it does not - which is how compound bodies are assembled from child collider entities. A collider that resolves to neither is an orphan and is silently skipped, so add the body before its colliders.
From here, any entity carrying Transform64 + RigidBody + Collider falls under gravity, collides, and comes to rest - see Rigid bodies and Colliders & shapes.
Optimized for massive worlds
The simulation cost tracks the active set, not the size of the world:
- Two spatial indexes - one for static geometry, one for moving bodies. Level geometry is added once and never rebuilt; only moving bodies pay a per-frame cost.
- Solving in groups. Bodies that are touching are solved together as a group, and separate groups are solved independently. Static and kinematic bodies anchor a group without enlarging it.
- Sleeping in bulk. A pile that comes to rest sleeps as a unit and drops out of the active set entirely. A hundred-block stack wakes in a single step when something hits it - no frame-by-frame ripple up the stack.
A scene can hold an enormous static world and thousands of resting bodies while the per-frame budget stays proportional to what’s actually moving.
Fully debuggable - it’s all JavaScript
Because the solver is plain JS, it lives in the same world as your gameplay code. Set a breakpoint inside the collision code. Step through a contact being resolved. Read a body’s velocity in the console mid-frame. There is no opaque WASM heap and no separate runtime - the same browser DevTools you use for the rest of your page reach all the way down.
In development, thousands of runtime assertions guard the physics invariants and fail loudly at the source of the problem. In production, the strip plugin removes them for native-speed runtime.
Drawing what the solver sees
Physics ships no debug-draw API of its own. Anything you want to see - a contact normal, a shape cast’s swept volume, a broadphase box - you draw yourself through the engine’s immediate-mode gizmo surface, which is renderer-neutral, plus the system that renders one frame of it:
import { Gizmo } from "@woosh/meep-engine/src/engine/graphics/render/gizmo/Gizmo.js";
import { DebugDrawSystem } from "@woosh/meep-engine/src/engine/graphics3/DebugDrawSystem.js";
em.addSystem(new DebugDrawSystem(engine.graphics));
// …then, from anywhere during the frame:
Gizmo.color = [1, 0.2, 0.1, 1]; // RGBA, at least 4 elements
Gizmo.draw_wire_sphere(hit.position, 0.1); // centre, radius
Gizmo.draw_line(hit.position, contactEnd); // from, to
Gizmo is a singleton. color is a property, not a method, and every draw_* call takes the colour that is set when it runs - which is what lets one pass carry a frame’s worth of differently coloured shapes. The rest of the surface is clear(), draw_line, draw_solid_cube, draw_wire_cube, draw_solid_sphere and draw_wire_sphere; positions and sizes are indexable triples, so a Vector3 works directly.
The recording is immediate-mode: a frame’s recording does not survive into the next, and DebugDrawSystem clears it once the frame that consumed it has been submitted. Shapes are blended and depth-tested but never depth-writing, so they are occluded by the scene and occlude nothing, including each other; two overlapping solid shapes composite in the order they were recorded rather than by depth. line_width is a property of the system (default 2), in CSS pixels, not of a draw. See Render extensions for how the pass slots into the frame.
Deterministic and bit-exact across V8
The simulation advances on a fixed timestep and uses no wall-clock, no hardware randomness, and no platform math beyond IEEE-754 doubles. Given the same inputs in the same order, it produces the same result, bit for bit, on every V8 runtime - Chrome, Edge, Node, Deno, Electron.
That’s the foundation for:
- Lockstep multiplayer - replicate inputs, not state. Every client simulates the same world.
- Replays - record the input stream, replay the exact match.
- Server reconciliation - the server re-runs the client’s steps and gets the identical answer.
- Reproducible tests - a physics regression is a diff, not a flake.
See Determinism for the guarantees and the rules that preserve them.
Where to go next
- Rigid bodies -
RigidBody, body kinds, mass, forces, sleeping. - Colliders & shapes - shapes, materials, layers, sensors, contact events.
- Spatial queries - raycasts, shape casts, and overlap tests against the live world.
- Joints - connect bodies with ball-sockets, hinges, sliders, and welds.
- Constraints - per-DOF limits, springs, and motors on a joint.
- Determinism - the fixed-step contract and what bit-exact buys you.
- Raining rigid bodies and Jointed chains - live demos.
- The source:
src/engine/physics/ecs/- start withPhysicsSystem.js.