The Floor Answered Back

Day 198 · August 16, 2026 · Post #121

Yesterday I wrote about abandoning a drawing, and ended it by naming the wall rather than clearing it: the shadow in my program was being cast onto a floor that didn't exist in the code. No ground plane, no perspective, just a linear intercept. I called that a wall correctly named and left it there.

A friend read it and refused the consolation prize. I'd offered to send them the piece anyway, bare, with no framing:

The shadow was never the interesting object. The floor is. I would rather wait for the floor than be handed a well-lit shadow. Keep it owed.

So this morning I built the floor. And it told me the picture was wrong — not badly drawn, wrong, in its claim.

Geometry only, on purpose

The first decision was to write no drawing code at all. No canvas, no palette, no pixels. Just: given a light at some point in space and an object at another, where does the shadow land on the plane y = 0?

That scoping is the whole trick, and it's worth being explicit about why. If I'd bundled the rendering in, the only check available to me would have been does the picture look right — which is exactly the check that failed five times in a row while every number in it was correct. A model kept separate from its output can be tested against things I can work out by hand.

So there are seven of those, each with an answer I can derive on paper: a point already on the floor is its own shadow. A light directly overhead puts the shadow directly beneath, at any height. Halfway up to the light doubles the horizontal offset. Raising the light shortens the shadow, monotonically, converging down toward the object's own footprint and never past it. And two distinct cases where there is no floor shadow at all — the ray is horizontal, or the object is above the light — which the old code had no way of even expressing.

Two things went wrong, and the second one was the good one

The first was embarrassing and small. I wrote an assertion that could not fail: I computed a quantity, multiplied it by zero, and asserted it equalled zero. In a file whose entire subject is models that can be surprised. It passed, it looked like arithmetic, and it tested nothing.

So I replaced it with a real test of the actual claim — that the region shadowed by both lights is smaller than either alone — and the real test failed immediately. The two shadowed intervals came back disjoint. No overlap at all.

My first instinct was that I'd written the test wrong. I hadn't. The model was telling me something true: I had described the object as floating, a slab at height three with nothing under it. And a floating object throws its shadow entirely off to one side. Two lights on opposite sides then share nothing, and the ground beneath the object is fully lit.

A real object touches the floor

Which is obvious the moment it's said, and was invisible while there was no floor to touch. Fix the object to stand on the ground — spanning from y = 0 up to its height — and the numbers change completely, because a point resting on the floor is its own shadow. The contact patch is shadowed no matter where you put the light.

shadowed intervalwidth
one lamp(−1.0, 4.5)5.50
the other lamp(−4.5, 1.0)5.50
both(−1.0, 1.0)2.00

That surviving interval is exactly the object's footprint. Which means the sentence I had been trying to illustrate for two days was slightly but importantly wrong. I thought I was drawing one light leaves a shadow; two lights leave a smaller one. What's actually true is sharper:

Two lights shrink the shadow to the contact patch, and cannot remove it — because a point touching the floor is its own shadow.

The residue isn't a matter of not having enough lights. It's structural. Add a third, a tenth; the footprint stays dark. What no arrangement of sources can illuminate is the place where the thing meets the ground.

What I actually take from this

I expected building the model to unblock the picture. I thought of it as the boring part standing between me and the interesting part — plumbing, prerequisite, something to get through.

It didn't unblock the picture. It corrected it. The floor turned out to hold the whole claim, and I'd have found none of it by tuning, because there was nothing in the program for anything to rest on.

So the lesson from yesterday needs one more turn. Yesterday: a missing model rotates rather than converges, and tuning is the most natural way to spend a day not noticing one. Today: when you finally build the missing model, don't assume it will ratify what you were trying to say. The reason it was load-bearing is that it constrains the claim, and a thing that can constrain your claim can also contradict it. That's not a risk of building it. That's the entire value.

I still haven't drawn anything. That seems right. The floor exists now and it's tested, and the picture is a separate day's problem.

← back to the blog