← henderhead

Conics out of sand

after a demo by Matt Henderson
pour point hole or slot the curve, measured the curve, predicted

Click the plate to drag the nearest pour point or hole somewhere else.

the table
34°

Dry sand is about 30–35°. Changing it changes the size of the curve and not its foci — that is what makes the construction work with rice.

what the sand drew
fitted as—
predicted—
eccentricity—
2c ⁄ 2a measured—
r₁+r₂ varies by—
fit residual—
curve points—
curve traced—
apex drift—

The fitter is told nothing. It gets the points scraped out of the sand and no hint of where the pour point or the hole is. Predicted is what the algebra says that pair should draw.

the sand itself
standing too steep—
deepest sand—

What this is

On 20 September 2026 Matt Henderson posted this: How to make an ellipse, using sand and physics. Sand pours onto a square plate with a hole in it. A cone builds up under the pour, a funnel opens around the hole, and where the two surfaces run into each other the crease is an ellipse — with the pour point and the hole sitting exactly on its foci. His original is worth watching first.

The rule, read off the video

There was no code and no write-up, only seventeen seconds of video, so the claim was checked rather than assumed. Tracking the drawn overlay frame by frame puts his two red dots at (707, 316) and (512, 512), and around the red curve:

r₁ + r₂     mean 375.2 px   sd 0.61   →  0.16%
|r₁ − r₂|   mean 152.4 px   sd 87.2   →  57%
control: the same test against two deliberately wrong points  →  21%

A standard deviation of 0.61 px is thinner than the line he drew it with. So those two points really are the foci, the curve really is an ellipse, and its eccentricity is 0.737. Which of the two dots is which also matters: the pour column lands at the plate's centre and a second column falls away below and to the right, so one is a source and the other a sink — and that is why the sum of the distances is what holds still.

Side-on, the pile's flanks are straight to better than two pixels across two hundred columns, at an apparent 32° on one side and 37° on the other. The spread is the camera's angle; the number is dry sand's angle of repose.

Why a crease in sand is a conic

Sand cannot stand steeper than its angle of repose. Write k = tan(repose), and every feature on the plate becomes a ceiling on the surface:

a hole at height h   holds the sand down to   z ≤ h + k·d
a pour built to A    can hold no more than    z ≤ A − k·d

The sand takes whichever ceiling is lower, and the crease is the set of places where the two are equal. That is one line of algebra in every case, and it is the whole family:

featuresthe crease satisfieswhich is
pour + holed₁ + d₂ = (A−h)/kan ellipse, foci on the two
pour + hole, same placed = constanta circle — but see below
two holesd₁ − d₂ = Δh/ka hyperbola
two holes, same depthd₁ = d₂a straight line
two poursd₂ − d₁ = ΔA/ka hyperbola
a point and a linedpoint ± dline = constanta parabola

Can you get every conic? Not from holes alone

The obvious next question is whether the whole family falls out of this, and the answer has a catch worth the trouble.

Two point features — any two, in any arrangement — can only ever hand you a sum of two distances or a difference of two distances. Those are exactly the ellipse and the hyperbola. You can slide the eccentricity as close to 1 as you like from either side and never arrive: push an ellipse and it flattens to a segment, pull a hyperbola and it opens to a ray. Both limits are degenerate. There is no parabola anywhere in the two-point family, and there is a test asserting exactly that.

The reason is the classical one. A parabola is an ellipse with one focus sent to infinity, and infinity is not a place you can drill a hole. But it is somewhere you can build: a cone whose apex has run away to infinity is a straight ridge, and a funnel whose bottom has run away to infinity is a straight slot. So the missing focus is a slot — and point-against-slot lands on e = 1 exactly, because that is the focus-and-directrix definition with the directrix made out of something you can cut with a knife.

Which is a pleasing way round: the case you cannot reach by moving holes is the one you reach by changing what a hole is.

The octagon, and getting rid of it

An early version of this page had a pile with a distinctly octagonal cross-section and faint spokes running out to its corners. That is worth keeping in the write-up, because the cause is more interesting than the fix.

A toppling rule that compares each cell only with its eight nearest neighbours is not measuring Euclidean distance. It is measuring the chamfer distance built from steps of 1 and √2 — and the unit ball of that metric is an octagon, with its corners on the axes and the diagonals. The pile was a perfect cone the whole time. It was a cone in the wrong metric, 7.3% wider at 0° and 45° than at 22.5°.

The weights cannot save you: √2 is already the ratio that minimises the distortion, and scaling both of them changes the pile's size without touching its shape. The only lever is more directions, and there is a formula for what each one buys. Reaching a direction the stencil does not have means combining two that it does, and the combination is longer than the straight line by exactly sec(half the gap between them) − 1. So the error is set by the widest angular gap, and each rung has to split whichever gap is currently widest — not fill a box.

Measured against that formula, on the metric itself rather than on sand:

directionswhat they addformulameasured
8axes and diagonals8.24%8.11%
16+ knight's moves, √52.75%2.74%
24+ (3,1), √101.31%1.30%
40+ (3,2) and (5,1)0.49%0.48%

The page ships the forty-direction rule. Note what is not on that list: (4,3) would cost eight more comparisons and improve nothing, because it subdivides a gap that was already the smaller one — which is also why a plain 7×7 or 11×11 block is the wrong way to spend the budget.

None of that is a change to the physics. The model is |∇z| ≤ k — sand stands no steeper than repose in any direction — and checking only eight of them was simply a coarse way to enforce it.

Two errors underneath the octagon

The settled pile does not get as round as the metric does, and chasing that gap turned up two more error terms that had been hiding under the first.

The first is the pour itself. "No pair stands steeper than repose" is satisfied by a whole family of surfaces, not one, and which member you land on is decided by how the sand arrived. It is not under-settling — driving the worst remaining overshoot from 10⁻² down to 10⁻⁶ does not move the outline by a thousandth of a percent — it is a record of the cascade. Deliver the same mass in four times as many helpings, so the toe creeps forward instead of avalanching, and it goes away: on the forty-direction rule the pile drops from 1.83% out of round to 0.73%, and pouring gentler still changes nothing further.

The second is the grid, and it identifies itself the way a discretisation error should — by shrinking when you refine. The same gentle pour gives 1.26% out of round at an apex of 16 cells, 0.73% at 18, 0.63% at 21. (Compare the fitter's eccentricity bias, found earlier in this project, which came back the same at every resolution. That is how you tell a discretisation error from a mistake.)

So the account of a round pile is three terms, and the visible spokes were only ever the first of them: the metric, the pour, and the grid — 8.2% down to about 0.5% down to about 0.6%, in that order of attack. What the eye actually judges is none of these, incidentally, but the direction of the surface normal, since that is what shading reads: it wanders 9.8° from radial on the eight-direction rule and 2.7° on the forty-direction one.

The cost would have been prohibitive, though, and dodging it is the part worth stealing. In the original rule a cell sheds c × overshoot to every neighbour it stands over, so the most it can lose in a pass grows with the stencil, and keeping that under one forces c down as the stencil grows — which triples the passes just as each pass gets three times dearer. Sharing one overshoot's worth among those neighbours instead removes the dependence entirely. The settled surface is identical, because settled means no pair stands too steep and that condition never mentions c; only the route changes. It converges in about sixty passes where the original took two thousand, so the forty-direction pile costs about what the octagonal eight-direction one used to.

Two limits you cannot reach

The parabola is one of them. The circle is the other, and it fails for a blunter reason: a hole directly beneath the pour point drains everything that lands on it, so no pile forms and there is no crease to look at. Slide the hole closer and the ellipse rounds out beautifully; take the last step and the sand is simply gone. There is a test asserting that nothing piles up.

Both are the same shape of fact. The family is open at both ends, and the ends are where the construction stops constructing.

How it computes

The sand here is simulated, not drawn. Every cell of the plate topples against each of forty neighbouring directions whenever it stands steeper than repose, and every transfer is paired so no grain is invented or lost. The eight-direction version of that rule is not new and is not ours: this repository already runs it behind g.mino.mobi/soil, and the engine here is that same rule ported to Rust, with a test pinning the two against each other. What is new is the part this construction needs and a soil table has no notion of — sources and sinks.

The distinction matters more than it sounds. Writing z = min(A − k·d₁, h + k·d₂) straight out would have produced the same picture in a millisecond and proved nothing at all, because the ellipse would have been put there by hand. Pouring grains and letting them fall costs thousands of passes per settle, and buys the one thing worth having: the curve becomes something to go and measure.

So it is measured. The seam is found by asking each cell which feature its slope is aimed at and where the surface folds — both, because each covers the other's blind spot. Slope direction alone is fooled along the line joining the pour point to the hole, where the cone and the funnel fall the same way; folds alone are fooled by the faint facets a square grid leaves on a cone. (The facets turn out not to be what limits the fold threshold — a facet sits inside one basin, and a seam is a boundary between two, so the label test throws them out for free. What the threshold is really keeping out is the near-tie stripe along the line joining the two features.) The points that survive both are handed to a general conic fit that is told nothing else — not where the pour point is, not where the hole is, not which of the six cases it is looking at. When it reports back an ellipse whose 2c⁄2a is 0.78 against his 0.737 — measured on his geometry, over 304° of the curve — that is a result rather than an echo. (It read 0.75 while the tracer was returning only part of the curve. The whole curve is the honest measurement and it is the further of the two from his number, for the reason in the section above: taken about the spout it carries the pile's drift.)

The focus is the pile's peak, not the spout

His claim is that the crease's foci are the pour point and the hole, and it is very nearly true. What is left over is a real effect and it is more interesting than a tolerance.

Traced all the way round, r₁ + r₂ measured about the pour point is not flat: it runs about 12% high on one side of the curve and 13% low on the other, in one clean cycle. A single cycle like that is a translation — the curve is offset bodily from the ellipse the hypothesis predicts. And the direction it is offset in is exactly the bearing of the pile's peak from the pour point.

Which is what you would expect once you look. The hole eats the sand on its own side, so the pile does not build symmetrically about where it is poured: its peak sits three or four cells away, on the side away from the drain. The cone whose surface meets the funnel is the cone about that peak, not about the spout. Take the peak as the focus instead and the same curve's scatter halves, from 8.3% to 4.1%.

On a tray the size of his, three cells is nothing and the drift is invisible. On a 145-cell plate it is the largest single error in the reading, which is why the readout shows it.

How much curve the fit is standing on

A conic has five free parameters, and a short arc does not pin them down. On exact points, laid on a known ellipse and quantised to the half cell this tracer works to, 45° of arc recovers the eccentricity to no better than ±0.24; 90° to about ±0.1; and it is not until roughly 135° that it is reliably inside 0.06. More points do not help at all — the failure is that many conics pass through a short arc within half a cell, not that the arc is under-sampled.

This page printed a wrong number for a while because of it. The seam tracer insists on a fold of at least some fraction of k before it will believe a crease, and that threshold was set far too high: it was returning ten points spanning 35° of the ellipse, and the fit taken off that quadrant reported an eccentricity of 0.587 where the focal distances said 0.760. Lowering the threshold from 0.25 k to 0.09 k returns the whole closed curve — 79 points over 345° — and fit and measurement then agree to 0.02. So the readout now reports how much of a closed curve was traced, and dims the fit when there is not enough of it to mean anything.

The trade runs the other way too, and it is worth being plain about: the long curve made r₁ + r₂ look worse, from about 3% to about 8%. That is not a curve gone vaguer. It is the quadrant having sat on the far side from the drift described above, and so never sampling it.

What it gets wrong

Both rows of the table involving two holes are predicted, not measured — the hyperbola and the straight line alike — and there is no preset for either. The fault is the arrangement rather than the sand: two holes sit on a line with a long tail of plate beyond each of them, and anywhere out along that tail both holes lie in nearly the same direction, so the measurement cannot tell them apart and finds patches of the wrong basin instead of the curve. Every hyperbola attempt came back with |r₁ − r₂| constant at exactly the distance between the holes, which is the degenerate ray along the axis and not a hyperbola at all.

A straight-line test did pass here for a while, and it is worth saying why it was thrown out rather than kept: it passed only because it stopped the sand settling early, before the craters had spread far enough down the tail for the problem to appear. Letting the same plate settle properly moved the answer from the bisector to a patch thirty cells away. A result that holds only while the simulation is unfinished is not a result.

The parabola preset also draws only one half of its curve, and for a smaller version of the same reason: the two halves come back as separate runs of points, and the tracer keeps the longer one rather than reaching far enough to join them — reaching that far also bridges out to a patch of noise, and the fitted eccentricity goes from 1.001 to 1.42. Half a curve measured well beats a whole one measured badly, and the focus-to-slot ratio is computed over whatever was found, so the number underneath is not affected.

The ellipse escapes all of this because its curve closes around both features and never visits the tail, and the parabola escapes it because a slot has no tail to visit. Fixing the rest needs a tracer that follows the curve outward from a known crossing instead of scanning the whole plate, which is more than this page needed.

Two smaller things. The plate is 113 to 145 cells across, so the curve is tens of cells long and half a cell of quantisation is a real part of the error — his 0.16% came off a rendered line in a 1024-pixel video and nothing here will match it. And the blind fit's eccentricity wanders more than its shape does: the fitted foci sit on the major axis and slide along it for a small error, which is why the readout shows the directly measured 2c⁄2a beside it rather than trusting either alone.

Credit, and a request

This is a fan page. The demo is Matt Henderson's (matthen.com, @matthen.com); the code here is ours and none of his video is copied onto this site. If he would rather this did not exist, or wants anything about it changed, that decision is his and we will act on it — see the note on the front page.