Skip to content

devlog

Aix looked soft. I was wrong about why, twice.

2026-08-20

Of the four captured towns, one always looked blurrier than the rest. I had a confident explanation, then a better one, and the thing that actually fixed it was neither.

Aix-en-Provence is the biggest capture on the site: three kilometres of streets through a medieval district, walked with a laser scanner. It is also the one that always looked softest, and the obvious explanation was that we were not using everything we had. That turned out to be true, and it was not why.

The captures ship as a ladder of versions, coarse to sharp, and the site picks a rung based on what your graphics hardware can hold. My first guess was that Aix's ladder stopped too low. It does. The bundle we downloaded holds five levels, from 14 million points down to under a million, and every rung we ship is cut from the middle one. Two sharper levels sit on disk, unused. So: found it, bake the missing rung, done.

Except the machine I was testing on could not hold the rung it already had. It loaded the sharpest version we ship, ran at 18 frames a second, and stepped down to the middle one within seconds. Adding something twice as heavy would have made that worse, not better. The first answer was true and useless.

The second answer was better and I liked it more, which should have been the warning. These bundles are not baked by us; they are downloaded from the publishers, and different publishers exported them differently. Aix is cut into seven thousand pieces with five levels of detail. La Sarraz, from a different publisher, is cut into eleven hundred pieces with eight. That matters because every piece has to be present at some level of detail at all times, so the floor cost of simply showing the whole district is three and a half times higher on Aix. On the same laptop, same code, same settings, Aix ran at 5 frames a second and La Sarraz at 36. I had my culprit and a plan to re-bake a 110 MB asset.

Before spending a day on that I checked what version of the rendering engine we were on. Three behind. I pulled the newer one, diffed the function that decides how much of a scene to keep in memory, and found it had been rewritten: the per-piece maths that used to allocate objects and call a logarithm now reads a flat array of numbers and looks up a cached table. Every one of those changes scales with the number of pieces, which is precisely the thing Aix has seven thousand of.

So I swapped only the engine build, changed nothing else, and measured again. Aix stopped stepping down. It holds its sharpest version now, roughly double the detail on screen, on the machine that could not hold it an hour earlier. The asset re-bake I had been about to spend a day on would have helped a bit and fixed nothing.

The upgrade was not free, and the way it was not free is worth writing down. One of the rendering modes was removed, and our code asks for it by name. The name still exists as a constant, so the code still compiled, still passed type checking, and still looked correct in review. It just gets rejected at runtime and quietly falls back to a slower path with no sorting on the graphics card and no discarding of what is behind you. A removal that leaves the constant behind is the kind of break you find in a frame-rate report weeks later rather than in a compiler error, and the only reason I caught it was that I was watching the browser console during a measurement.

The honest coda is that Aix will never match the other three. Its capture holds 14 million points and La Sarraz holds 33, and Aix spreads its across nearly three times the ground. Even shipping every point we have, it lands at a lower density than La Sarraz manages today. No rung fixes that. It is a district captured at the effort of a small town, and the fix for that is a better capture, not a better ladder.

Three explanations, in order of how good they sounded and inverse order of how much they helped. The one that worked was the one I nearly skipped because checking a dependency version felt too boring to be the answer.