Skip to content

devlog

The first piece of capture repair: dropping the Gaussians that cannot be geometry

2026-10-10

The changelog said the real fix was upstream in the capture step, and that nothing in that step looked at splat size. Now something does. It also caught a rule I had invented, within one run.

Last week three of the eleven captures here turned out to be carrying Gaussians hundreds of metres wide, which is why they read soft at every distance while the other eight read sharp. What shipped was a ceiling in the renderer, and the changelog said what that was: a repair in the renderer and not in the captures, with the real fix upstream in the step that turns a capture into the levels the engine streams, and nothing in that step looking at splat size. This is that step.

It turned out to be much smaller than I expected, because of how the format stores scale. A bake keeps a 256-entry codebook of log radii and a webp whose pixels index into it, so the oversized splats are not scattered through the image data: they are a handful of entries at the top of one small table. Clamping those entries repairs every splat that pointed at them without touching a single pixel. On la-filature that is 19 of 256 entries in the worst file. On the eight clean captures it is zero, which is the number that makes it safe to run across everything, because on a capture that is already fine the pass is a no-op rather than a re-quantisation.

Then I ran it over the real bakes and it told me I was wrong about something.

I had written two rules, not one. The fixed ceiling of 20 metres, which the eleven captures argue for on their own, and a proportional one: a splat cannot be a meaningful fraction of the scene it sits in, so a forty metre courtyard should not get the same cap as a six hundred metre district. Five per cent seemed reasonable. On the first pass over real data it flagged Olsztyn, whose largest Gaussian is 7.1 metres inside a 124 metre bound, and Olsztyn is in the group that looks right. A rule that condemns a capture the eye says is fine is a rule with no evidence behind it. I had invented a tuning knob and dressed it as a principle, and it took one run against eleven real bakes to catch it.

So it is gone, and the fixed number needs no help anyway: the clean captures top out at 12.8 metres and the broken ones start at 270. A cap sitting inside a gap more than twentyfold wide is a cap on the impossible rather than a judgement about taste.

The run surfaced two other things. The first is that the bakes list was incomplete. The earlier measurement named three broken captures; walking every bake that an octree actually references finds six, including one variant at 1029 metres that is worse than the 974 already known about, and a level-of-detail bake at 461 metres whose merged sibling is completely clean at 12.8. Two bakes of the same place, one fine and one not, is its own thing to understand.

The second is a small irony worth writing down. The script prints each bake’s bounding box next to its worst splat, and on the broken ones the box reads six or seven kilometres across for a district a few hundred metres wide. Of course it does: the root node encloses everything, including the oversized Gaussians, so the bound is inflated by the exact thing being searched for. The measurement cannot use the scene size to judge the splats, because the splats have already corrupted the scene size.

What this does not do is the part I want to be clear about, because it is the part people mean when they say AI. It drops what cannot be geometry. It does not drop what merely looks wrong. A 974 metre splat inside a 280 metre scene is impossible and no judgement is involved; a 15 metre one might be a wall, or might be a floater hanging over a courtyard, and the difference is context rather than size. That second half is still open, and it is genuinely where a learned model earns its place, because judging context from examples is the thing models are for and thresholds are not.

It has been run. Six bakes repaired, 393 files changed, every one of them a single line: only the scale codebook moves, the splat count and the image data are untouched, and the files come out byte-identical in length because the baker already wrote them minified. Nothing needed uploading anywhere, which surprised me. The oversized payloads live on object storage and the small manifests ship with the site, so a repair that only edits JSON travels with the next deploy and reaches an existing visitor inside five minutes, which is what that cache header was set to five minutes for.

Two guards caught me on the way. The manifest records a byte length and a hash for every file in a bake, so the first pass produced a hundred and seventy-one audit errors, each one a few bytes of drift between a repaired file and the index describing it. A script that edits a file and leaves the index stale has not finished; it reconciles both now. And a test left behind last week turned out to be waiting for exactly this: it asserted that the three broken bakes still exceeded the ceiling, with a note saying that the day a pipeline prune fixed them this test would fail, and that the failure was the signal to retire the renderer workaround rather than to loosen the test. It fired. Writing a test whose failure is a message to whoever comes next is a habit I would like to keep.

The workaround stays for now, and not out of sentiment. The prune is a script somebody has to remember to run, not yet a gate inside the pipeline, so a capture baked tomorrow could still arrive with a nine hundred metre Gaussian and nothing would stop it. The clamp costs nothing on data that is already clean and it covers the gap between a bad bake and somebody noticing. It can go when the prune runs as part of the pipeline rather than beside it.