workshop private

← all sprites

Half Past

sprites · created 2026-10-08

128×160 pixel hourglass and a water clock of the same 18.33 cm³, five loops, one timeline. Sand's discharge has no head term in it — Beverloo gives Q = C·ρ·√g·(D−1.4d)^(5/2) and nothing else — because the grains above the hole arch, and what leaves is dropped 0.34 mm rather than pushed. So every eighth of this glass takes 22.83 s, while the matched water column spends 11.80 s on its first eighth and 64.57 s on its last: a spread of 5.47× across one drain. The exponent is the bill. Five halves on the aperture means ±1 s wants the 1.6 mm neck held to ±3.5 µm, which nobody bores — so you weigh the sand instead, where ±1 s is ±154 mg — and 0.1 mm of wear costs 32 seconds. The same arch is also the floor: below about five grain diameters it stops breaking on its own, and the 0.680 mm hole that makes the water take 182.6 s is 2.7 grains across. The two clocks cannot share a hole.

physicssimulationcanvaspixel-art

A water clock knows how much is above it.

That is Torricelli: the jet leaves at sqrt(2*g*h), so the rate falls as the level falls, and the marks on the outside of a clepsydra have to be engraved unevenly — crowded at the top, spread out at the bottom — or the thing lies about the time. Half the water in a straight-sided vessel is gone at 29.3% of the run. Its first eighth takes 11.80 seconds and its last takes 64.57, for the same volume through the same hole.

Sand does not do this. Fill the bulb or nearly empty it and the grains come out at the same rate. The measured statement is Beverloo’s correlation:

Q = C * rho_bulk * sqrt(g) * (D - k*d)^(5/2)
  = 0.58 * 1537 * sqrt(9.80665) * (0.0016 - 1.4*0.00025)^2.5
  = 0.1542 g/s

and the interesting part of that equation is what is missing from it. There is no head. There is no bulb. There is the aperture D, the grain diameter d, and gravity. A hole does not know what is stacked over it.

Why not

Because the grains above the orifice are not a column pressing down on it. They arch. A dome of mutually wedged grains stands over the hole, carrying whatever is above it sideways into the walls, and the grains underneath that dome are not squeezed out — they are dropped. They fall freely through a short distance set by the hole’s own size and leave at whatever speed that fall gives them.

You can read the distance straight off Beverloo without fitting anything. Take the measured flow, divide by the hole it came through, and ask what fall produces that speed:

v     = Q / (rho * A)           = 8.18 cm/s
h     = v^2 / (2 g)             = 0.341 mm
h / (D - k*d) = (4C/pi)^2 / 2   = 0.2727

The last line is the whole mechanism as one number. The grains leave as if they had been dropped 0.27 aperture-widths, for every aperture and every grain size, and archFall() asserts that against four of them. That is why the head is not in the equation: the fall that sets the exit speed is a quarter of a millimetre, and a quarter of a millimetre is the same whether there are 28 grams of sand overhead or one.

The companion fact, which is real but is not the explanation, is Janssen: a granular bed’s own weight goes sideways into the walls over a screening length of R / (2 mu K), 50 mm here, so the stress at the bottom of this bulb is 428 Pa where a liquid of the same bulk density would read 633. Worth drawing, and often wheeled out as the reason. It is not the reason. The flow does not read the stress at all.

What that buys, and what it costs

It buys even marks. Every eighth of this glass takes 22.83 seconds. All eight of them. That is not arranged, it is what a constant rate means, and it is why an hourglass is the only common clock whose scale is a ruler. The run loop puts the two clocks side by side on one timeline with their eighth marks drawn up the right-hand edge — green evenly spaced, purple bunched at the top — and you can watch the water finish three quarters of its work while the sand is still at half.

It costs five halves of an exponent. dT/T = -2.5 dD/D. Holding this 182.6-second glass to one second means holding the neck to 3.5 microns. Nobody bores that. The sand’s mass, by contrast, enters linearly: one second is 154 milligrams out of 28.17 grams, which a kitchen scale does. So the glass as drawn runs 182.6 s rather than a round 180, and the fix is not to re-bore the hole to 1.6073 mm — it is to weigh out 27.76 g instead of 28.17 and move on. That is how hourglasses are actually calibrated, and it falls straight out of the exponents.

The same exponent is why the worn loop finishes early. A neck that has had 1.3 million grains a turn dragged through it, widened by a tenth of a millimetre, runs 151 seconds: a 6% change in the hole is a 17% change in the clock.

The floor

The arch that makes the clock work is also the thing that can refuse to break. Below roughly five grain diameters of aperture the dome over the hole stops being transient, and the run stops dead until something taps it. The neck here is 6.4 grains across, which is barely clear. The jam loop runs the same glass with a 0.9 mm neck — 3.6 grains — and it locks, visibly, while the water beside it keeps going.

Which closes the piece. The hole that makes the water column take the same 182.6 seconds is 0.680 mm. That is 2.7 grains across. At that aperture sand does not run slowly; it does not run. The two clocks cannot share a hole, and the reason is the same arch that lets one of them keep time at all.

The far end

Sand lands at its angle of repose, 34°, and the bulb wall stands at 65.4°, which is not decoration — a wall shallower than the repose angle would hold sand on it forever and the glass would never empty. The same inequality is why the heap does fill the lower bulb completely at the end (heapVolume(42) comes out within 0.01% of the bulb) instead of leaving dead corners.

While its cone is still free-standing — the first 31% of the drain — the heap does not grow smoothly. The stream lands on the apex, the slope creeps from the dynamic angle to the static one, and the surface lets go. The volume that buys one slide is (pi/3) * base^3 * (tan 34 - tan 31), so the period grows with the cube of the heap’s radius: 5.6 s at the size the heap loop draws. Which means the count is a logarithm, and that caught me out while building it — the pile’s volume and the volume one avalanche costs are both cubic in the same radius, so t / period barely moves and is a useless phase. avalancheCount integrates dV / avalancheVolume(V) instead, and the check that it is logarithmic is that every doubling of the heap buys the same number of slides. About 47 of them by the time the pile is a quarter built.

What is certain here and what is not

Kept apart on purpose, in the module header and again in the code:

validateGeometry() asserts the ones the prose leans on — about sixty of them, including that the sand’s eight eighths come out equal off the simulation rather than by construction, that the water’s land on 1 - sqrt(1 - f), that no sand leaves while the neck is arched, and that every frame of every loop actually paints. It runs in source/render.mjs, in the demo on load, and in scripts/screenshot-demo.mjs.

Reuse

source/half-past.mjs is framework-free and has no DOM in it except the bitmap painter. The granular half is usable on its own: beverloo, exitSpeed, archFall, drainTime, sandFor, neckForTime, neckTolerance, heapApex, avalancheVolume. The drawing is a Uint8Array of palette indices and paintBitmap is the only thing that touches a canvas, so a different renderer is a different 40-line function.

node source/render.mjs           # every PNG, and the demo's bundled copy
node scripts/screenshot-demo.mjs # media shots, and the demo's smoke test

Five loops: run (the whole drain, both clocks), neck (20 ms at the hole, where a grain is five pixels), jam (a 0.9 mm neck locking), heap (one avalanche cycle), worn (the same glass, 0.1 mm wider, 32 seconds fast).