You are up in the cab of a container crane. Two controls: the trolley runs left and right along the boom, the hoist pays rope out and takes it back in. That is the whole input surface. There is no “stop the load” button, because there is no such thing.
The load hanging forty feet under the trolley is a pendulum, and it is the only opponent in the game. Every metre per second you put into the trolley goes somewhere, and where it goes is into the swing. Start moving and the box lags behind you; stop moving and it comes past you. Do that twice in a row without thinking about the timing and you are standing over the cell you want with a nine-metre box scything through it at walking pace, unable to put it down.
Six boxes, bottom tier left to right and then the tier above it. The clock runs from your first input. A box only seats when it touches down slow and square; land it hard or crooked and it seats scuffed, +3s. Drag it through a stack you already built and that is a strike, +4s. Nothing is scripted — a landing is clean because the load was actually slow when it touched.
The technique
Everything in the game falls out of one fact: a pendulum’s period depends only on its length. At the rope lengths you work at, a full swing is about four seconds, and four seconds is long enough to plan inside.
- To stop a swing, chase it. Accelerate the trolley in the direction the load is already moving and you take energy out. Accelerate against it and you double it. The dial in the corner tells you which one you are about to do.
- To move without starting one, split the push: accelerate, wait half a period, accelerate again. The second push arrives exactly out of phase with the swing the first one made, and the two cancel. This is textbook input shaping and it works here because the physics is the real thing.
- Paying out rope calms the angle. A pendulum being lengthened slowly keeps its energy but spreads it over a longer arm — the amplitude falls off as L^(-3/4). Lowering early is a real tactic. Hoisting up while swinging does the reverse, which is why a box snatched off the quay in a hurry comes up angrier than it went down.
- You can land on a swing. At the end of the arc the load is momentarily stationary. Time the descent so it touches down there and the swing does not matter at all. It is the hardest thing in the game and the best-feeling.
The two instruments
The game has almost no HUD, but it has two drawings that are doing real work:
The envelope. The dashed outlines ahead of and behind the load are where the
load will be at the ends of its arc, computed from the linearised amplitude
√(θ² + ω²L/g). It shifts from teal to red as the arc grows. An operator in a
real cab can see this by looking; a player at a screen cannot, so it is drawn.
The dotted line hanging from the trolley is the rest position — plumb in still
air, leaning downwind when there is any.
The phase dial, bottom left: swing angle across, angular rate up, both normalised. A still load is a dot in the middle. A swinging one orbits, once per period, and the radius of the orbit is the amplitude. What makes it an instrument rather than a readout is that trolley acceleration drags the orbit’s centre sideways — so the entire technique of the game is visible as one rule: push when the dot is on the far side of centre and the orbit shrinks; push when it is on the near side and you have made your problem bigger. The small teal ring in the middle is the size the orbit has to be before a landing will seat.
Wind arrives after the second box. It does not make the load swing — it makes it hang somewhere other than plumb, which is a different problem and one you solve by aiming the trolley off the cell rather than by fighting it. Both instruments account for it: the dial’s centre and the envelope both move with the wind.
What’s under it
src/sway.js is the engine — framework-free, headless, no canvas anywhere in
it. The pendulum is integrated at 240 Hz in a substep loop:
θ'' = ( −g·sinθ − a_trolley·cosθ + a_wind·cosθ − 2·L̇·ω ) / L − c·ω
The 2·L̇·ω term is the one that earns its keep. It is the Coriolis coupling
between hoisting and swinging, and it is why hoisting a swinging load makes it
worse — without it the rope length would just be a number and the hoist would be
a second, boring axis.
Collision is one function. surfaceUnder(x0, x1) returns the height of the
tallest thing under a footprint — quay, deck, deckhouse, the box waiting on the
apron, every box already stacked — and that single query answers landing,
stacking, and “did I just drag this through the tier I built”. If the box’s
bottom is under that height, it is either a seat (aligned, slow, over the live
cell) or it is a strike, and the resolution is the same either way: take the rope
back up to where the box actually rests.
Gravity is 30, not 9.81. At true crane proportions a thirty-metre rope swings with an eleven second period; you would spend the entire game waiting for the load to come back. Scaling g puts the working range at 3.5-5 seconds per swing, which is slow enough to plan inside and fast enough that a mistake costs a beat instead of a minute. Everything else — the coupling, the amplitude law, the input shaping, landing on the arc’s end — is the real physics, and it all still works, because none of it cares what g is.
Reuse
src/sway.js— the engine.createGame()returns something youstep(dt, {tx, hoist})withtxandhoistin-1|0|1. ReadloadPoint(),loadVelocityX(),amplitude(),equilibrium(),period(). It has no imports.src/render.js— the drawing, in world metres against a fitted camera. OnlymakeCamera()anddraw()touch a 2D context.demo/carries its own copies of both (ADR-0002). Editsrc/, thencp src/*.js demo/.
Gotchas
- The swing angle is hard-clamped at ~30°. A player who pumps the arrows in rhythm will find the resonance and put the box over the top of the boom otherwise, which is both wrong and very silly.
- Amplitude is measured from the wind equilibrium, not from vertical. Measure it from plumb and every gust reads as a swing the player is supposed to cancel, except they can’t, because it isn’t one.
- A gantry that stops instantly would let you cheat the whole game, so the trolley brakes harder than it accelerates (2.6 vs 2.2 m/s²) but never instantly. Releasing the key is a control input, and a good operator uses it as one.
window.__demoexposes the live game for the site’s validation harness. It is also the easiest way to drive the thing with a controller of your own — the notes in this repo’s history came from doing exactly that.

