workshop private

← all creations

Coned

sprites · created 2026-10-07

112×132 pixel wheelset with five loops: the weave it traces, the two contacts it rides on, and what it slides. A train has no differential — two wheels welded to one axle — so the only steering it owns is the 1:20 cone on the tread. Three things fall out. It weaves, at a wavelength of 14.48 m that is a fact about distance and not about speed, so the same shape runs past at 0.96 Hz on a subway and 2.76 Hz on a mainline. It is unstable: a free wheelset with creep forces grows from a 0.3 mm nudge to flange contact in 150 m, while the same wheelset sprung, let go hard against a rail, comes back down a clean exponential at 2.6 to a swing — what makes a train stable is not the cone but the springs. And it runs out almost at once — 7 mm of play against a cone that needs 35.39 mm to steer a 150 m curve, so the tightest radius it manages unaided is 758 m. Past that the steering is the flange: a 150 m curve asks the contact for 0.995% creepage, the cone can give 0.197%, and the 0.798% left over is 1.88 m of tread slid through a quarter circle by a force 4.7 times what friction can hold. Grind the cone steeper and the curves come back — 1:3.3 steers 126 m — and the model's critical speed falls from 330 km/h to 131.

physicssimulationcanvaspixel-art

A train has no differential.

Not “a simplified one”, not “one in the bogie” — none, anywhere. The two wheels of a wheelset are pressed onto one axle and turn at exactly the same rate forever. On a curve, where the outer wheel has further to go, that should be impossible.

What makes it possible is that the tread is not cylindrical. It is a cone, nominally 1 in 20, with the radius growing toward the flange. Shove the wheelset sideways and one wheel starts rolling on a bigger circle than the other, so the two ends of the axle advance at different rates and the wheelset yaws. That is the entire steering system of a railway vehicle: a taper, and the fact that the wheels are welded to the same shaft. No linkage, no actuator, nothing anybody adjusts.

Three things follow from it, and this piece is the three.

It weaves, and the weave has no clock in it

A displacement makes a radius difference, a radius difference makes a yaw, a yaw makes a displacement. That is a second-order system, and because every term in it is per-metre-of-track rather than per-second, the thing it produces is a wavelength:

lambda = 2*pi*sqrt(r0*b/taper)
       = 2*pi*sqrt(0.3556 * 0.7465 / 0.05)
       = 14.48 m

Klingel wrote that down in 1883 and there is no velocity in it. A wheelset crossing a yard and one at line speed trace the same shape; they differ only in how fast they trace it. The weave loop is the rolling constraint integrated and nothing else, and validateGeometry() checks the trace’s zero crossings against the closed form to half a percent — because if the integrator and the formula ever disagreed, one of them would be describing a different wheelset.

What that wavelength means in a vehicle is a frequency that happens to land exactly where people notice:

speedthe weave arrives at
30 km/h0.58 Hz
50 km/h0.96 Hz
144 km/h2.76 Hz

It is tempting to go one step further and say that the sway you feel standing on a subway is this. That is not a claim this file can make: whether a given car’s lateral motion is kinematic hunting, bogie hunting or track geometry is a question about that car’s suspension. The honest statement is narrower and still worth having: the cone puts a weave into every wheelset on the railway at around 1 Hz at city speeds, for free, with no input at all.

It is unstable, and the cone is not what fixes it

Pure rolling conserves the weave: the weave loop runs 44 m and comes out at the amplitude it went in at, and would do that forever. Real contacts do not roll purely. They transmit force through creep — a small relative slip in a contact patch the size of a coin — and once you put creep forces into the same two equations, a free wheelset is unstable at every speed. Not above a critical speed. Every speed.

The grow loop is that: the same track, 144 km/h, a 0.3 mm nudge, and no suspension. It builds through 150 m, reaches the flanges, and spends the rest of its life banging between them — eight contacts in the frame.

The held loop is the same wheelset with a primary suspension attached, started hard against a rail at the full 7 mm, and it comes back down a clean exponential — each swing about 2.6 times smaller than the one before:

7.00 mm   2.67   1.02   0.39   0.15   0.06 …

under a tenth of a millimetre by 30 m, with half the frame left over.

So the lesson a railway wheel teaches is the opposite of the one it looks like it teaches. The cone is a steering mechanism that cannot be left to itself. The thing that makes a train stable is the springs between the wheelset and the rest of the vehicle, and designing them is the whole subject.

It runs out at 758 metres

This is the part that surprises.

A curve of radius R needs the outer wheel to roll (R+b)/(R−b) times as far as the inner one. The cone supplies radius difference at 2*taper*y, so the sideways displacement a curve asks for is

y = b*r0/(taper*R)

and a wheelset has about 7 mm of lateral play before a flange touches rail. Set those equal:

R* = b*r0/(taper * 7 mm) = 758 m

Seven hundred and fifty-eight metres. That is the tightest curve a new 1:20 tread can steer on its own. A mainline curve is often tighter. A transit curve is tighter by a factor of five or ten. The whole elegant self-steering mechanism is in charge on tangent track and on the gentlest curves in the network, and essentially nowhere else.

Run the curve loop — 150 m radius, which is an ordinary piece of subway — and watch what it does instead. It weaves on the straight, enters the transition, and goes to the flange and stays there:

the cone is asked for35.39 mm
it has7.00 mm
creepage the curve needs0.995%
the cone can supply0.197%
so this slides0.798%

0.798% of a 90° arc at 150 m radius is 1.88 metres of tread dragged sideways past rail, per wheelset, per curve. And it is not a near miss on adhesion either: the longitudinal force that creepage would need is 69.7 kN against a contact that can hold 14.7, so the demand is 4.7 times what friction will deliver. The contact is not creeping. It is sliding, grossly, which is the condition the literature puts behind curve squeal — the sound itself is a wheel’s acoustic modes and is not modelled here at all.

That is what the purple strip under the wheelset is counting.

And the fix costs you the top end

The obvious move is to grind a steeper cone, and it works exactly as advertised. At an equivalent conicity of 0.30 — which is not exotic; it is roughly what a worn wheel on a worn rail reaches once the contact climbs toward the flange root — the same 150 m curve asks for 5.90 mm, which fits inside the play. The worn loop steers it with 0.58 mm to spare and never touches a flange.

The bill arrives at the other end of the speed range. Conicity is the coupling term in the stability problem, so the same change that bought the curve takes the critical speed down with it:

conetightest curve it steersand the model’s critical speed
1:401517 m481 km/h
1:20758 m330 km/h
1:10379 m230 km/h
1:3.3126 m131 km/h

Which is the trade-off the whole subject is organised around, and it explains the two halves of the railway. A metro runs 150 m curves at 50 km/h, so it can afford all the conicity it likes and pays for the curves in metal — scrubbed treads, ground rail, squeal. A high-speed line cannot afford the conicity, so it buys the curves in civil engineering instead, and is built straight.

The demo’s right-hand panel is that sentence as a picture: the green region is where this model’s weave dies out, and the blue line is the cone the curve you have selected is demanding. Drag the curve tighter and watch the line walk into the red.

How far to trust this

Kept apart in the source and here, because they are not the same kind of claim.

Certain. All of the kinematics. The rolling radii, Klingel’s wavelength, the displacement a curve demands, the radius at which that exceeds the play, the creepage needed against the creepage available, and the sliding the difference forces. Those are geometry and bookkeeping about a rigid wheelset on rigid rails, and validateGeometry() asserts the ones this page leans on — including the 758 m itself, so that if a constant ever drifts the build fails instead of the prose quietly lying.

Modelled. Everything with a force in it. step() is the standard two-degree-of-freedom wheelset — lateral and yaw — with linear creep coefficients, a friction cap on each contact, a stiff one-sided flange stop, and the primary suspension as springs to ground. It is not a bogie. A real vehicle has two wheelsets in a frame with its own freedom to yaw, and its critical speed is lower than anything in that table. Believe the signs and the shapes — free wheelset grows, sprung wheelset decays, conicity buys curves and costs speed. Do not quote the critical speed at anybody.

There is one more honest limit worth naming, because it decided how this file is organised. A wheelset on grounded springs does not curve properly. The lateral spring pulls it back toward the track centre, so its steady displacement in a curve is set partly by the spring instead of entirely by the cone: on a 2000 m curve where the geometry asks for 2.65 mm, the deliberately soft springs used here settle at 2.38, and a realistically stiff primary suspension settles at 0.20 — an order of magnitude short, steering almost not at all.

That is not a bug to patch. It is the reason the primary suspension is the central compromise in railway vehicle dynamics — the stiffness that holds the wheelset straight at speed is the stiffness that stops it turning — and here it is the reason the two curve loops run the rolling constraint, where the claims are geometry, while the two stability loops run the forces on tangent track, where springs to ground are a fair stand-in for a bogie going straight.

Where this sits

run-in is the same railway seen along its length — a mile of freight as a chain with 5.21 m of slack in it — and treats each car as a point that follows the track. This is what one of those points is actually doing underneath, and the answer is that it is weaving at 14.48 m and, on anything tighter than a third of a mile of radius, grinding.

The loops

Reading the frame

Three bands, and the middle one is drawn with its scales declared because they have to be:

Reuse

Source

No .aseprite — the canonical source is source/coned.mjs, which is the kinematics, the dynamic model, the Routh test and the pixel drawing in one file. Nothing is keyframed: a frame is renderFrame({ run, s }) evaluated at a distance in metres down the track, and the wheelset is wherever the integration put it. Regenerate everything with:

node source/render.mjs             # export/, media/ loops, thumb, demo copy
node scripts/screenshot-demo.mjs   # media/ demo shots, and the demo smoke test

(needs site/node_modules installed — the scripts resolve Chromium through site/scripts/lib/chromium.mjs.)