thu.ndero.us Experiments in engine, systems, and iteration
Learning

Six things I could only find by looking

I spent a day putting the headset on, looking at the clearing, and writing down what was wrong. Six defects came out of it. Not one of them could have been found any other way, and that's the part worth writing up. What …

I spent a day putting the headset on, looking at the clearing, and writing down what was wrong. Six defects came out of it. Not one of them could have been found any other way, and that's the part worth writing up.

What was actually broken

Mushrooms dissolved for fifteen metres. Unity's LOD cross-fade takes a fadeTransitionWidth, and I'd assumed it was a distance. It isn't — it's a fraction of that LOD's own band. A mushroom has one LOD, so its band is nearly the entire screen-height range, and the 0.10 I'd set meant the thing started dithering at 1.6 m and didn't finish until it culled at 16.4 m. The comment I'd written above the value said "a 0.3 m tuft dissolving over a metre of approach is invisible." Right intent, wrong unit. It computes the fraction per prefab now so the blend always spans 1.5 m.

The grass was exactly the same colour as the ground. Not similar — identical. The ground atlas has four swatches and the green one is #92c559; the grass material's base colour was #92c559. Measured distance: 0.0. Two objects the same colour scale together under any light, which is why it looked wrong lit and shadowed. No amount of lighting work could have touched it.

Two cards rendered as grass on a black rectangle. My first guess was alpha clipping switched off. Wrong: the texture atlas has no alpha channel at all, so there was nothing to clip. The black was real pixels. Those prefabs are terrain-detail cards meant to be drawn by a terrain system, not scattered as objects.

Every clump of one grass family was a single blade. The asset pack ships seven density variants — one card, two, three, up to twelve — and the bucket that indexes them picked _01 as its representative. So every one ever placed was the sparsest possible version while six denser siblings sat unused on disk. That's the second time a "which variant represents this group" decision has quietly made a choice for every consumer downstream; the first cost me concave mesh colliders underfoot.

A shrine floated 8 to 20 cm above everything near it, because the placement tool derives one height from the ground tile's bounding box top — the tile's maximum — rather than the terrain height under the piece.

Broken trees floated their broken bit by exactly their pivot height. The seating logic drops the lowest geometry to the ground; on those prefabs the lowest geometry is the root flare, well below the pivot, so the fallen section rides up with it. The two I noticed were, when I checked, the two highest-seated of the ten.

The pattern

Static analysis can count objects, measure bounds, check references, and run assertions. Every one of these passed all of that.

Worse, several actively reported healthy. The broken trees sit lower than the treeline median, so the numbers say they're fine. The tool whose entire job is reporting LOD bands printed width - for every single-LOD prefab, because it showed raw fractions and dropped the last entry — and a single-LOD group has only a last entry. Every mushroom in the scene was dissolving over fifteen metres and the LOD report said nothing.

There's a category of defect here that isn't "the check failed to run" but "the check ran, passed, and was blind to the axis the problem lives on." A colour comparison catches a magenta material and not two identical greens. A bounds check catches an object underground and not a sub-mesh floating inside a correctly-placed prefab.

I don't have a general answer to that. The specific one that keeps working is rendering the thing and looking at it — I have a test that writes PNGs of named scene objects from three angles, and it settled three of the six in one frame each.

Though it fooled me once today too. Pointed at a tree, it aimed at the bounding-box centre, which for an 8.4 m oak is 4.2 m up, so from standing height it filled the frame with canopy and sky and put the trunk's base outside it entirely. A perfectly good render of the wrong part of the object, which looks exactly like a working check. It never aims above its own eye height now.

And the headset kept freezing

Twice the view locked up and tore at the edges. The first time I found the Link server had been running 18.5 hours, matched it to a documented failure, restarted the service, got a clean session, and called it fixed.

It wasn't. It came back on a server 39 minutes old.

Nothing logged it. Unity rendered 16,000 frames with a textbook session lifecycle and no errors; no GPU driver reset; the runtime's error rate flat before, during and after. The answer was in per-minute telemetry rather than in any log: a counter called num_glitches_with_high_bitrate read 63 and 77 on the sessions that froze, against 27 on one that didn't. Same records showed dynamic bitrate disabled with the rate pinned at 200 Mbps — so the encoder could never back off when the link degraded, and a hiccup became a stall instead of a quality dip.

Enabled dynamic bitrate: 17 glitches in the first minute, then zero, then zero.

The thing I want to remember isn't the fix, it's the first diagnosis. It matched a real documented case, the remedy was followed by a good session, and I believed it — with no test that could have proved me wrong. One success after a change proves nothing at all, and I already had a note in my own project docs about exactly this, from a previous time I'd done it.