Your hand is capped at 18 m/s, which is roughly what a human arm does. The tip of this whip goes through 640. Nothing in the code pushes it. The only thing that happens between those two numbers is that the whip gets thinner.
The one asymmetry
A whip is two facts fighting each other. It cannot stretch — 2 m of it is 2 m of it, always. And its mass per unit length falls off geometrically from butt to tip, so the far end has almost nothing in it.
Throw a loop into a chain like that and the loop has to keep going. But the further it travels the less mass is left in front of it to carry the energy, and a fixed budget of energy carried by a shrinking mass means one thing:
½ m v² roughly constant ⇒ v ∝ 1 / √m
That is the whole mechanic. Everything below is measured out of the shipped module at its shipped settings — 48 segments, 96 sub-steps, 2 m, 220 g, hand capped at 18 m/s.
Watching it happen
Here is one throw at a 400:1 taper, sampled as the loop runs out. v·√m is the
invariant the argument above predicts:
| loop is at | still moving | at | v·√m |
|---|---|---|---|
| 106 cm | 9.2 g | 26 m/s | 2.45 |
| 131 cm | 4.0 g | 47 m/s | 2.97 |
| 153 cm | 1.8 g | 74 m/s | 3.17 |
| 172 cm | 0.8 g | 112 m/s | 3.16 |
| 180 cm | 0.5 g | 149 m/s | 3.30 |
The mass drops by 18× and the speed comes up 5.7×, while the product wanders by about 30% and does not trend the way either of its factors does. That is close to conservation and honestly not exactly it — the loop is still drawing on the whip behind it the whole way down, so it does slightly better than √. Then the last 20 cm arrive, the popper has 0.02 g in it, and the number goes vertical: 149 → 641 m/s in the final stretch of the run.
The demo puts those two columns side by side as the only gauges on the screen — tip speed climbing and grams still moving falling — because watching one without the other is the version of this you already believe.
The crack is not in your arm
Same cast, same 18 m/s hand, same 2 m of cord. Only the mass distribution changes:
| butt : tip | peak tip speed | best cast, snapped early | |
|---|---|---|---|
| 1 : 1 (a rope) | 71 m/s | 0.21 Mach | 0.48 Mach |
| 16 : 1 | 186 m/s | 0.54 Mach | 1.08 Mach |
| 64 : 1 | 323 m/s | 0.94 Mach | 1.81 Mach |
| 128 : 1 | 438 m/s | 1.28 Mach | 2.22 Mach |
| 256 : 1 | 559 m/s | 1.63 Mach | 2.68 Mach |
| 400 : 1 (shipped) | 641 m/s | 1.87 Mach | 3.03 Mach |
| 1024 : 1 | 845 m/s | 2.46 Mach | 3.87 Mach |
Fitting the whole range gives peak ∝ ratio^0.38, against the 0.5 that pure
energy conservation in the loop would ask for — damping and the mass that never
joins the loop eat the difference. The [ and ] keys walk that table live.
Put it on 1:1 and you have a skipping rope: you can flail it as hard as the arm
cap allows and the cards just sit there, because 163 m/s is not a crack, it is
a slap.
At 35.6× amplification the tapered version is not doing something your arm could do if you tried harder. It is doing something your arm cannot do at all.
What the taper actually buys you in the game
Not just a bigger number — a wider window. The tip is only above Mach 1 for a few milliseconds, and everything it sweeps in that window breaks:
| taper | supersonic for | arc covered while up there |
|---|---|---|
| 128 : 1 | 0.79 ms | 32 cm |
| 256 : 1 | 3.06 ms | 126 cm |
| 400 : 1 | 4.00 ms | 167 cm |
| 1024 : 1 | 6.23 ms | 302 cm |
So the taper is really a difficulty slider on how forgiving your aim has to be. At 128:1 you get a 32 cm licence and you had better have put it somewhere; at 400:1 you carve a metre and a half of arc through the board and take everything standing on it.
Four milliseconds is the honest figure and it is worth sitting with. The demo runs at 0.15× so a cast takes about a second and a half of your time; the whip thinks the whole thing lasted 200 ms, and the part you were aiming lasted four of them.
You do not get to choose the range
This is the bit that makes it a game rather than a physics toy. Across 18 casts spanning sweeps from 30 to 85 cm and reversals from 50 ms to 220 ms — a 3× range of peak speed, 390 to 1111 m/s — the crack landed:
121 to 145 cm from the hand. Mean 137 cm. That is 69% of the whip, every time.
You are not ranging a projectile. The whip has one radius and it is going to use it. What you actually control is:
- where the hand is when the loop arrives, which slides that circle around the world — a 40 cm sweep tops the arc out at x = 1.41 m, a 90 cm sweep at x = 2.05 m;
- the bearing along it, which is the direction you snapped;
- the speed, set by how early you reverse — snap at 60 ms and you get 1039 m/s, wait until 220 ms and you get 423.
So the dashed ring in the demo is not a targeting aid, it is a statement of fact: the crack lands on that circle. Move the circle. The ten cards are laid out across the reach range on purpose — a short cast takes the three nearest, a long one cannot reach back for them, and the board goes down in five casts on a ladder of sweeps, never in one.
Controls
- Drag — your hand follows the pointer, speed-limited to 18 m/s. Sweep forward, then snap back. This is the real control and it will produce 2–3 Mach once you find the reversal.
- Space — hold and release for a canned cast; the hold sets the sweep, and so the reach. This is the exact cast every number above was measured with.
[]taper ·creach ring ·rreset the board.
Reuse
src/whip.mjs is a framework-free ES module that draws nothing:
createWhip({ length, segments, massRatio, segRatio, totalMass, damping, handleMax, substeps, iterations })→ a whip.setHandle(x, y, dt)moves the butt under the speed cap;step(dt)advances one frame of whip time.- Read
x[],y[],vx[],vy[]for the chain,radius[](1 at the butt) for how thick to draw it, andmassAhead[i]for the grams still in front of nodei— that array is the whole explanatory gauge. fastestNode()is, in practice, where the loop is.arcTo(i)gives its distance along the whip.- After each
step(),tipTrackholds one entry per sub-step — 96 swept segments with speeds. Hit-test against those, never against frame positions. peakTip,crackAt(position, hand position and reach),supersonicTimeandsupersonicArcaccumulate untilresetPeak().sweepHitsCircle(x0, y0, x1, y1, cx, cy, r)is the segment-sweep test the demo uses.MACH1is 343.
Cost is about 1.1 ms per step() in Node at the shipped settings, and the demo
holds 61 fps in Chromium through a cast.
Gotchas
-
Sub-step, do not iterate. Sub-steps buy convergence here in a way solver iterations do not, and the peak speed is frankly hostage to them: 364 m/s at 8 sub-steps, 641 at the shipped 96, 696 at 256, still climbing. The absolute speed is a discretisation artefact and I would not defend it to three digits. What the piece actually claims is stable, and I checked rather than assumed — over a 16× refinement, 24 up to 384 sub-steps:
sub-steps exponent reach uniform cord 24 0.341 74% 65 m/s 96 0.368 69% 70 m/s 384 0.382 67% 73 m/s The peak nearly doubles across that range; the exponent moves 12%, the reach moves seven points, and the rope stays a rope. 96 was chosen to fit a 60 Hz budget, not because it had converged.
-
Hit-test the swept segment. At 640 m/s the tip moves 1.28 m in a single 2 ms sim step and 72 cm between two adjacent samples of the trace. Test the instantaneous position and the tip teleports straight through a card without touching it.
-
Interpolate the handle across the sub-steps. Snapping the butt to its new position at the top of the frame fires a spurious shock down the chain every frame the hand moves, and the whip develops a permanent case of the jitters.
-
Sweep the constraint solver forwards, then back. One forward Gauss-Seidel pass from butt to tip carries the hand’s motion the whole length of the chain, which is the direction the information genuinely travels. Solving in a arbitrary order needs many times the iterations for the same result. Chain length holds to 2.0000 m through a 3 Mach crack.
-
Draw the thin end thicker than it is. At 400:1 the popper is a twentieth the radius of the butt, which is a third of a pixel. The renderer uses
radius^0.55, which is a lie, and the alternative is a whip whose business end is invisible. -
The whip after a crack does not settle for a long time — it is still thrashing above 30 m/s a second later. The demo ends a cast on a hard 0.55 s cap rather than waiting for quiet, but not while the pointer is down, because that is a person mid-cast rather than a whip that has finished.
-
demo/bundles its own copy of the module (self-contained by contract). If you touchsrc/, re-copy it intodemo/.

