How Game Physics Engines Actually Work: Constraint Solvers, Continuous Collision Detection, and the Math Behind Stacking a Thousand Blocks

Our earlier piece on physics-based games covered the genre from the outside — its history, its sub-types, what makes one good. This piece is the technical complement. If you have ever wondered what is actually happening inside a physics engine when you stack a tower of blocks — what math is running, what compromises are being made, why stacking ten blocks is easy and a hundred is hard — this is the long explanation.

The aim here is not to be a tutorial. We are not going to teach you to write a physics engine from scratch. But by the end of this piece, when you load up a browser physics game and start placing objects, you should have a clear mental model of what is running underneath and why certain limits exist. That model will make you a better player, and if you are a developer, it will help you pick the right engine and tune it well.

The problem: rigid body dynamics in real time

A physics engine's core job is to simulate rigid body dynamics: the motion of solid objects under forces, and the collisions between them. "Rigid" means the objects do not deform — a box stays the same shape no matter how hard it is hit. Real-world objects deform under load, but simulating that (soft-body dynamics) is orders of magnitude more expensive, so games almost universally start with rigid bodies and add deformation only where specifically needed.

Each rigid body in the simulation has, at minimum: a position, an orientation, a linear velocity, and an angular velocity. More complete engines track mass, inertia tensor, linear and angular damping, friction coefficients, restitution (bounciness), and a handful of collision shape parameters. On each tick of the simulation, the engine must advance every body according to the forces acting on it, detect any collisions that occurred during the step, resolve those collisions so objects do not interpenetrate, satisfy any joint constraints (hinges, sliders, ropes) the bodies participate in, and produce a new world state.

This needs to happen in less than 16 milliseconds to maintain 60 FPS. If you have 500 objects in contact with each other, that is a hard budget.

Step 1: Integration

The first piece of the puzzle is numerical integration: given a body's current velocity and the forces on it, advance its position by one time step.

The simplest approach is explicit Euler integration. Given a body at position p with velocity v, and forces F acting on it for time dt, the new state is:

  • v_new = v + (F/m) * dt
  • p_new = p + v_new * dt

This works, but it has a well-known problem: for stiff systems (like a rope under tension), Euler integration is unstable. Small timestep errors compound over time and the simulation explodes — objects launch themselves at impossible velocities or oscillate wildly. Most physics engines use semi-implicit Euler (also called symplectic Euler) instead, which uses the new velocity to advance position rather than the old one. This tiny change — one line of code — dramatically improves stability for oscillating systems.

For higher accuracy, engines can use Runge-Kutta 4 (RK4) or similar multi-step methods. These compute intermediate velocities and average them, which is more accurate but 4x more expensive. Most game engines stick with semi-implicit Euler because the tiny accuracy loss is invisible at game scales and the performance win is big.

Step 2: Collision detection (broadphase)

Once bodies have moved, we need to find which ones are touching. Naively checking every body against every other body is O(n²), which does not scale: 500 bodies would require 124,750 collision checks per frame.

The trick is broadphase partitioning. Before doing any detailed geometric collision tests, the engine splits the world into regions, tracks which regions each body occupies, and only tests pairs of bodies that share a region. Typical data structures:

Uniform grid. The world is divided into equally-sized cells. Each body is registered in the cells it overlaps. For each cell, test every pair of bodies inside. Fast, simple, and great for small uniform worlds. Bad for worlds with a big size disparity between objects.

Sweep and prune (SAP). Sort bodies along each axis by their bounding-box extents; pairs whose bounding boxes overlap on all axes become collision candidates. Efficient for worlds where most objects are roughly static — the sort is nearly linear if things haven't moved much. Box2D uses this.

Bounding volume hierarchy (BVH). A tree of bounding boxes where each internal node contains its children. Query by descending the tree and pruning branches whose bounding boxes do not overlap with the query shape. Better for heterogeneous worlds with many static objects and a few dynamic ones; used by Bullet (Ammo.js), Rapier.

Quadtree (2D) / Octree (3D). Adaptive space partitioning that subdivides regions with more bodies into smaller cells. Common in puzzle games where density varies widely across the play area.

Broadphase produces a list of potentially-colliding pairs. Most of them will not actually be colliding, but the list is much shorter than n², which is the whole point.

Step 3: Collision detection (narrowphase)

For each candidate pair from the broadphase, the engine must do a precise geometric test: do these shapes actually overlap, and if so, where and how?

For simple shape pairs (sphere-sphere, box-box, sphere-box) there are closed-form analytical tests. For general convex polygon collisions, the standard algorithm is GJK (Gilbert-Johnson-Keerthi), which iteratively refines a simplex on the Minkowski difference of the two shapes to detect overlap. Paired with EPA (Expanding Polytope Algorithm) for depth and contact point calculation, GJK+EPA covers almost every convex shape pair you will encounter in a game.

For concave shapes, the engine usually decomposes them into convex pieces in an offline preprocessing step and treats each piece separately. This is why physics engines often have "convex hull" or "triangle mesh" shape types; the former is a compromise (less accurate but fast), the latter is the full version (more accurate but expensive).

The output of narrowphase is a contact manifold: a list of contact points, each with a position, a normal (direction the bodies should be pushed apart), and a penetration depth.

Step 4: Continuous collision detection

Here is a classic bug in naive physics engines: a fast-moving bullet passes through a thin wall because at frame N the bullet was on one side, at frame N+1 it was on the other side, and the collision was never detected because neither frame captured the moment of overlap. This is called tunnelling.

The fix is continuous collision detection (CCD): rather than testing only the current positions, test the swept volumes of the objects over the time step. If the swept volumes overlap, compute the time-of-impact (TOI) and advance both objects only up to that moment, resolve the collision, then integrate the remaining time.

CCD is much more expensive than discrete collision detection, and most engines apply it only to bodies marked "bullet-like" — fast-moving objects that are likely to tunnel. The rest get the cheaper discrete test. This is why shooter physics engines let you flag projectiles as CCD-enabled; you do not pay the cost unless you need it.

For browser physics games with lots of stacked objects, CCD usually isn't needed — stacked blocks move slowly. For games with fast projectiles or high-velocity impacts, it is critical.

Step 5: Constraint solving

After collisions are detected, the engine must resolve them so objects do not interpenetrate. This is where it gets interesting.

A collision is actually a type of constraint: "these two bodies should not overlap." Joints are also constraints: "these two bodies must be connected at this point." Friction is a constraint: "the relative tangential velocity at this contact should be zero or bounded." Every physical interaction in the simulation can be expressed as a constraint.

The job of the constraint solver is to find a set of impulses (instantaneous velocity changes) to apply to each body such that all the constraints are satisfied — or, when they cannot all be satisfied simultaneously, to find the closest feasible solution.

This is a hard math problem in general. Modern game physics engines solve it with iterative methods, most commonly the Projected Gauss-Seidel (PGS) solver. The algorithm:

  1. For each constraint, independently compute the impulse needed to satisfy it.
  2. Apply the impulse (clamping it to valid ranges — e.g., contact impulses can only push, not pull).
  3. Repeat the loop over all constraints several times (typically 8-20 iterations per frame).
  4. Each iteration pulls the system closer to a consistent solution.

PGS is not exact — more iterations get closer but never fully there — and the limits of PGS are visible in any physics game. Stacks of more than ~10 blocks get wobbly because the solver can't fully propagate the ground-reaction force through a long contact chain in the iteration budget. Fast-spinning ropes oscillate because the constraint forces don't converge fast enough.

Alternative solvers exist. Sequential impulse (a variant of PGS used by Box2D) applies constraints one at a time and converges faster for contact-heavy scenes. Direct solvers (like LCP-based methods) are exact but much slower and don't scale past ~100 constraints. Featherstone's algorithm is a specialised direct solver for articulated bodies (like skeletons) that trades generality for speed.

Step 6: Joint constraints

Joints connect two bodies with a specific relationship. The main types in game physics engines:

  • Revolute / hinge joint: bodies can rotate about a shared axis. Used for doors, wheels, ragdoll elbows.
  • Prismatic / slider joint: bodies can translate along a shared axis but not rotate. Used for pistons, sliding doors.
  • Distance joint: the distance between two points is fixed (or within a range). Used for ropes, chains.
  • Weld joint: bodies are rigidly attached, no relative motion allowed. Used for breakable connections.
  • Pulley joint: two distance constraints sharing a total length budget. Used for classic block-and-tackle puzzles.

Each joint is a constraint that the solver handles alongside collision constraints. Well-designed joints feel right; poorly-tuned ones feel jittery or rubbery. The difference is usually in the solver iteration count and the specific constraint formulation.

Step 7: Sleeping and islands

A physics engine with 1000 objects would be expensive to simulate every frame — except that in most scenes, most of those objects are not moving. They are resting on the ground, stacked, or out of contact with anything dynamic.

The optimisation is sleeping. A body that has been nearly motionless for some number of frames is marked as "sleeping" and skipped by the integrator and solver until something wakes it up (a collision, a force, an awake neighbour moves). A stable stack of 100 blocks costs almost nothing to simulate; the solver only wakes up the blocks involved when a dynamic object hits the pile.

A related idea is islands. The engine groups bodies that are transitively connected by contacts or constraints into "islands" and solves each island independently. A scene with 10 disjoint piles of blocks can be solved as 10 small systems rather than one big one, which is significantly faster due to the quadratic-ish cost structure of the solver.

Both optimisations are what make 500-body scenes feasible in a browser tab. Without them, you'd be lucky to run 50.

Why Box2D is the standard

Erin Catto released Box2D in 2007. It is a free, open-source, 2D rigid-body physics library in C++, and it has been ported to almost every language that runs in a browser — box2d.js, planck.js, and several others are direct descendants. The reason it became the browser physics standard is worth understanding.

Box2D gets several things right simultaneously:

  • Sequential impulse solver — the specific variant Catto pioneered is unusually stable for stacking and constraint chains, which are exactly the scenarios casual games care about.
  • Simple API — creating a body, attaching a shape, connecting joints, stepping the world takes a dozen lines of code. The API maps cleanly onto what a game wants to do.
  • Predictable performance — you can look at a scene and roughly predict how much CPU it will cost. That predictability makes it possible to ship games that run on low-end hardware.
  • Excellent documentation and community — Catto's own book, plus a decade of community tutorials, made Box2D approachable for beginners.

Angry Birds used Box2D. Limbo used Box2D. Crayon Physics Deluxe used Box2D. Hundreds of browser games you may have played were running Box2D underneath. When JavaScript ports appeared in the 2010s, they carried the same quality forward, which is why modern browser physics games still feel remarkably consistent across developers — they are mostly running the same core library.

Modern alternatives — Matter.js (JavaScript-native, 2014), Planck.js (Box2D JS port), Cannon.js (3D), Ammo.js (Bullet port, 3D), and Rapier (Rust/WebAssembly, 2020) — all borrow heavily from Box2D's design choices even when they reimplement them differently.

The limits you can feel

Every physics engine has characteristic failure modes that an observant player can spot in any game. Knowing them makes you a sharper critic of physics games:

Stack wobble at 10+ blocks is the iterative solver not fully propagating ground-reaction force. Fix: more solver iterations (slower) or a better solver (rare).

Rope stretch under load is the distance constraint not being enforced hard enough. Fix: a stiffer solver or a specialised rope model.

Objects jittering when resting is the sleeping heuristic misfiring — the body should sleep but doesn't because it gets micro-bumped by the solver. Fix: tuning the sleep threshold.

Fast objects passing through walls is tunnelling — you need CCD on that object.

Stacks slowly settling over time (even when you're not touching them) is position correction not fully converging. Aesthetic rather than functional, but distinctive.

Joints feeling rubbery is solver iterations insufficient for the joint stiffness. Increase iterations or use implicit integration.

WebGPU and the near future

The biggest change on the horizon for browser physics is WebGPU, which ships across all major browsers in 2024-2025 and offers 5-20x the compute of WebGL for parallel workloads. Physics simulation is embarrassingly parallel — many constraints can be solved independently — and engines are starting to offload the solver to the GPU.

Rapier is already experimenting with GPU solvers. Within the next two years, browser physics games with 1000+ dynamic bodies will be routine; 10,000+ will be possible. The ceiling that has held back browser physics games for a decade is about to rise dramatically.

The second shift is neural physics — using trained networks to approximate expensive simulation steps (especially for soft bodies, cloth, and fluid). This is mostly AAA tech right now but is starting to appear in browser research demos. Expect it to be practical for production games within 3-5 years.

What to play if you want to feel the engine

If this piece has you curious, the games in our Physics tag (385 titles) are the best way to play a physics engine in action. Specific recommendations:

  • Super Stacker 3 — play it, then deliberately try to stack 15+ blocks and watch the wobble kick in at the iterative-solver limit described above.
  • Ragdoll Physics — a ragdoll is a set of rigid bodies connected by constrained joints; you can see the solver's behaviour in every flop.
  • Save the Miner — the lines you draw become rigid bodies the solver has to integrate alongside everything else.
  • Mad Truck Challenge Special — a vehicle is a compound rigid body with wheels (revolute joints), suspension (prismatic joints with springs), and ground contacts. Notice how the engine handles the combination.

For the broader genre context and more recommendations, see our physics games deep dive. For the format-history side, our Flash-to-HTML5 history piece covers the migration that made modern browser physics possible.

Physics engines are one of those pieces of infrastructure that is everywhere once you know to look for it. The algorithms are eighty years of applied math compressed into a library that fits in a few hundred kilobytes of WebAssembly. Every time you stack a block and it stays put, that is a constraint solver doing its job. Every time it wobbles and falls, that is the same solver telling you the truth about what your stack is actually doing.