The Orphan a Deletion Leaves

The Shading Path That Never Shaded was a clean kill: 2,600 lines of dormant deferred renderer, gone in one commit. The tests passed. The problem was in what the deletion left behind, and it only turned up on a second pass back over the change.

A deletion this size is never as contained as the diff makes it look. The thing worth catching was not in what got removed. It was in what suddenly had nothing left to call it.

The ghost limb

The deferred chain had a GPU-side partner: ShadeClassifier, the device-resident half of the tile classifier. A struct holding classification buffers, a new, an ensure_size, accessors, a counter reset, a Debug. Around it the supporting cast: PerClassDispatchArgs, a PER_CLASS_DISPATCH_BYTES size constant, the CLASSIFY_WG_X and CLASSIFY_WG_Y workgroup dimensions.

All still there, still compiling, still exported. And with exactly zero callers. Its only consumers had been three functions, record_classify, record_shade_class, and record_shade_class_6mrt, every one of which had been deleted in the previous commit. The classifier was a hand with no arm: the limb above it amputated, the fingers still twitching, waiting for instructions from a nervous system that no longer existed.

This is the second-order problem with deleting dead code. The thing you delete is rarely the only dead thing. It is just the dead thing you happened to notice. Pull it out and it orphans whatever it was holding up. A deletion is not done when it compiles. It is done when the cascade reaches zero.

“Might be useful later” is a cost you pass to the user

The easy move is to keep it. It is good code. A GPU tile classifier is genuinely useful, and if Lux ever wants fine-grained per-material dispatch, here is a working one, pre-built. Why not leave it as framework for the future?

Because “framework for the future” is not free, and the person who pays for it is the one running the app. Every kilobyte of code that ships is code a machine downloads, compiles into the binary, loads into memory, and carries on every launch. Dead code is not weightless just because nothing calls it. It is slower cold starts, a larger download, more surface area for a bug to hide in, and one more thing the next person has to read past and wonder about. “Might be useful later” is a standing charge on load time and on the engine’s clarity, billed today for a feature that may never arrive. The pre-launch tenets are blunt about it: no dormant scaffolding. Code earns its place by being used, not by being potentially useful someday.

So the orphan went too. ShadeClassifier, the dispatch args, the byte constant, the workgroup dimensions, all dropped from the public surface and deleted. If the future wants a GPU classifier, the future can write one against whatever the engine actually looks like by then, which will not be today’s engine.

The rarest dead code

My favourite detail is the smallest. The classifier’s module had a doc comment pointing at a method called record_pass, describing its place in the frame and how it allocated.

There is no record_pass. There never was. Not deleted-in-this-change never-was, never-written never-was. At some point the docs got ahead of the code and described a method that was always going to exist and never did, and then the code around it died without anyone touching the comment. Documentation for a function that was never born. You delete dead code all the time. You delete the API docs of a method that never existed about once.

What is left, and what you get

shade_classify.rs is not empty. What survived is the part that earns its keep: the CPU reference model. classify_cpu, the TileHistogram, and the tile-space arithmetic, all with real consumers, a proportional-cost test and a benchmark that lean on it as the ground truth for how tiling should behave. The module’s description was rewritten to say what it now is, a CPU reference model rather than a GPU stage, and it dropped off the no-hot-path-sync audit, because with the GPU half gone there is no per-frame allocation left in the file to guard. There was never any risk of a colour regression here, because nothing downstream had ever read a byte the classifier produced.

Two posts, north of 2,800 lines deleted, and the picture on your screen is identical to before. That is the deal you get from this kind of work, even when it is invisible: the engine you run is exactly the engine that is alive. Nothing you wait for at launch, nothing you carry in memory, nothing you can hit a bug in is code that was only ever pretending to help.

← Back to devlog