Charlie KrugThe Build Log

← All posts

The ragdoll never stores a velocity. It stumbles anyway.

Ragdoll Rumble is a two-player browser brawler on a from-scratch physics engine. Here is how a punch sends a ragdoll flailing when nothing in the code holds a velocity.

You hit F, a fist swings out, and the other ragdoll folds backward over the platform edge, one leg still hooked on the lip, arms windmilling on the way down. It reads as a stumble because it moves like weight moving. And yet nowhere in the engine is there a variable called velocity. Every limb only ever knows where it is now and where it was one frame ago.

What it is

Ragdoll Rumble is a two-player brawler that runs entirely in the browser: two people share one keyboard, throw punches, kicks, and lunges, and try to knock each other off the arena. The whole thing is a single static build with no server behind it.

The part I actually cared about is the engine. Most hobby ragdoll games drop in Box2D or Matter.js and wire up some joints, which is a fine way to ship a game but leaves the physics as someone else's math. I wanted to build the solver from primitives, tune floppy limbs by hand until they read as funny instead of glitchy, and understand why ragdolls look the way they do. So there is no physics library here, none, and the interesting behavior falls out of one old trick.

How a punch works with no velocity

A ragdoll is a small graph: point masses for the head, torso, upper and lower arms, upper and lower legs, held together by distance constraints that fix each limb's length. The engine moves those points with Verlet integration, the method Thomas Jakobsen popularized for game ragdolls back in 2001.

The idea is that you never store velocity explicitly. You store each point's current position and its previous position, and the difference between them is the velocity, carried implicitly from frame to frame. To step forward, you move the point by that difference (it keeps going the way it was already going) and then add gravity. That is the whole integrator.

This is what makes a punch cheap and stable. To throw a fist, I do not compute a force or push a velocity onto an accumulator. I just shove the hand point a few units in the punch direction. The next frame, the gap between its new position and its old one is larger, so the implicit velocity is larger, and the solver reads that as momentum. The distance constraints then drag the forearm, the upper arm, and the torso along behind it, several relaxation passes per frame, each pass nudging every constraint back toward its rest length. The recoil, the twist, the tangle with the opponent: all of it is the solver resolving one displaced point downstream. Nobody scripted the stumble.

The bug that blows the whole thing up

The naive version detonates. Two ragdolls collide, you push their overlapping points apart once, and then the constraints yank the limbs back together, and the collision pushes them apart again, and the two corrections fight each other and pump energy into the system until the whole tangle launches off-screen at what looks like the speed of light. Classic over-constrained instability.

The fix that mattered was resolving collisions inside the same relaxation loop as the joints, not once after it. When separation and limb-length are corrected together, pass after pass, the solver converges on a pose that satisfies both at once instead of ping-ponging between them. That one ordering decision is the difference between a fighting game and a fireworks display.

The other quiet load-bearing piece is a fixed-timestep accumulator: the simulation always steps at the same interval regardless of your monitor's refresh rate, so the physics behaves identically on a 60Hz laptop and a 144Hz gaming rig. Position-based dynamics gets unstable fast if the timestep wanders, so that decoupling is not optional. The honest limit: this is Canvas 2D and a hand-tuned solver, not a AAA physics package, so at absurd hit counts you can still catch a limb doing something anatomically upsetting. I decided that was on-brand.

Try it

Grab someone. Player 1 is F / G / H (punch, kick, lunge), Player 2 is / / . / ,. Wait for FIGHT, then knock the other ragdoll off the platform or pin them flat on their back for about a second. First to two rounds wins; any attack starts the rematch. On a phone the controls become on-screen buttons, so you can settle it on the bus.

Ragdoll Rumble is live. Free, in your browser, no signup.

This post is part of the build log: every app my automated factory ships gets written up here, honestly. Browse everything at apps.charliekrug.com. Comments are open below.

Comments

Loading comments…