The Invisible Work Matters

Recently I wrote about the software factory I’ve been running for Hordes of Orcs 3, and then I followed up on the topic of specialist sub-agents that made it more economical. Both posts were about the visible scaffolding: the layers, the lanes, the agents, the hooks. This post is about something harder to see but no less important.

A lot of engineering is invisible work. Work that never surfaces to a manager, and that either doesn’t show up in a PR diff or isn’t explicitly explained for what it is. It’s often work that the engineer doesn’t consciously register as work at all. Because it doesn’t get talked about, very little of it ends up written down. Because very little of it ends up written down, very little of it ended up in the training data for the models I’m now handing my codebase to.

That absence is a quiet, compounding risk to the long-term health of a project. By its nature, it tends to shorten what “long-term” even means.

Now, don’t get me wrong – I’m not saying that things like risk management and architectural considerations don’t get talked about. They do. But when they get talked about, the people talking about them are looking to make large-scale impacts on projects so they get talked about in terms of large-scale considerations. Think Inheritance vs. Composition vs. Service Objects vs. Flavor of the Week.

What I’m talking about is what happens at the smaller scale. The little decision-points that get made quickly and in passing, and gradually become the patterns and norms of a codebase.

The work nobody writes down

When I make a change to a piece of code, a whole layer of judgment runs underneath the part you’d see in the diff. I simplify the thing I’m touching while I’m in there. I notice that the task as specified is more complicated than it needs to be, and I quietly narrow it. I weigh the blast radius of a change against the benefit. I decide that a defensive check is paranoia and leave it out — or decide it’s operationally necessary and put it in. I look at an assumption baked into the surrounding code and ask whether it’s actually true, or just something that’s been true so far.

None of that shows up in a standup. None of it shows up in a ticket. Most of it I couldn’t fully articulate in the moment if you asked me to — it’s professional intuition. The accumulated residue of a couple of decades of watching things break. It is also, I’d argue, a major factor in whether a codebase stays healthy or slowly strangles itself.

I also think it’s almost entirely missing from the public record. Engineers blog about the clever thing they built, not the three defensive checks they decided not to write because the failure they’d guard against can’t (or mustn’t) actually happen. So the models never saw it. They saw the guard code. They didn’t see the judgment that decided when guard code is a liability.

How the absence shows up

Claude, left to its own devices, is strikingly reluctant to question an assumption that’s baked into the code in front of it. If the architecture implies something, Claude treats that implication as ground truth and builds on top of it.

It’s also extremely conscientious about failure. If Claude can see a way for something to go wrong, it will proactively write code to defend against that condition — even when the condition is, in practice, impossible. On its own, that instinct is admirable. The problem is what happens next.

Once the guard code exists, the next worker to come along reads it as evidence. There’s a null check here, so it is normal for this to be null. There’s a fallback lookup here, so the normal path must fail sometimes. The defensive code written in the face of ambiguity about requirements and workflow suddenly becomes a concrete claim about reality. The next change defends against the knock-on consequences of a failure mode that was never real to begin with. Then the change after that defends against those consequences.

In a software factory — where the whole point is to keep my hands off the code — this compounds fast. You end up with layers of accidental complexity defending against the Nth-order consequences of things that cannot (or must not) happen. Each layer looks locally reasonable. Collectively, they’re a tax on every future change, and a fog over every real defect.

The concrete version: prefab wiring

The clearest case of this in Hordes of Orcs 3 has been prefab wiring.

Early on, Claude correctly noticed that a component might have one of its connections left unset — a reference that’s supposed to point at another component, but doesn’t, because someone forgot to hook it up in the editor. A reasonable thing to worry about. What Claude did about it might seem reasonable, until you take a step back and look at the bigger picture.

It didn’t just write null checks. It wrote active recovery: if a reference was missing, the code would go hunting for the component it needed — GetComponentInChildren here, GetComponentInParent there, a scene-wide search somewhere else, depending on where you’d expect to find the thing in that particular case. The intent was to make the code resilient at runtime, so a missing wire wouldn’t crash the game.

The actual effect was to make missing wires invisible. The code would quietly find a substitute and carry on, and nobody — not me, not a play-tester — would get any signal that something had been set up wrong.

That bit us with a real bug that I didn’t track down until last week.

When an orc gets irradiated

When an orc is hit by radioactive goop, it gains an Irradiated affliction. That affliction does damage over time: directly to the orc carrying it, and to nearby enemies as well — an “aura,” in game-design terms. It’s also supposed to come with the obvious visual: a sickly cloud of radioactive gas around the afflicted orc.

I had forgotten to explicitly wire up that connection for the Irradiated afflictions. And because I’d forgotten, GetComponentInChildren happily papered over it — reaching into the affliction’s hierarchy, finding the effect component, and attaching to it instead of failing.

Now contrast that with what should have happened. Had the code simply tried to call SetActive on a null reference, the console would have lit up with an error the instant an Irradiated affliction was applied. The mistake would have been obvious — embarrassingly so. The defensive guard code is the only reason it wasn’t.

That’s the trade nobody wrote down. A human engineer makes it almost without thinking: what’s the risk of masking a real error, versus the risk of the game breaking at runtime? And in this case the answer is lopsided. A missing wire here isn’t a subtle, intermittent failure that a null check saves you from in production. It would happen every single time someone used the relevant tower. And how many chances are there for a human to make this mistake? Radiation, Fire, Ice — each with five upgrade levels, each level a separate opportunity to forget the wiring. That’s only fifteen chances to make the mistake, and all fifteen would announce themselves in the console the moment anyone exercised that tower.

When the failure is that loud and that certain, you want it to crash. The crash is the feature. A human engineer would never have reached for GetComponentInChildren to hide it, and would never have written the if (effectSystem != null) that guards the effectSystem.SetActive(true) call. They’ve internalized a risk calculation that never got written down anywhere Claude could learn it.

From latent bug to actual bug

The bug was latent for weeks. It manifested when doing cleanup work to improve the codebase. And then went unnoticed for a while longer.

At some point I had Claude eliminate the use of GetComponentInChildren and GetComponentInParent. They’re expensive at runtime, and every place they appeared in our code was a place where the association could simply have been wired statically, at edit time, once.

The cleanup removed that fallback lookup, but the if (effectSystem != null) guard around it stayed. So instead of crashing, the code now silently did nothing: suddenly, orcs being irradiated showed no signs of it. Here’s why it went unnoticed: My attention had turned from the basic mechanics of the game to the bigger-picture concerns of game balance / play feel, and so on.

I was no longer at the stage of setting up a scene and putting a single tower in the middle and watching how it interacted with a stream of orcs. I was focused on playing the game like an actual player would. That’s a very different conext. At any given moment there are dozens of projectiles in the air, hitting dozens of orcs. When that much is happening on screen at once, it is genuinely hard to confirm that every effect of every kind of projectile type is firing correctly. The missing radioactive cloud was a needle in a very busy haystack.

So the “invisible work” here was doubly invisible: the defensive code hid a real bug. The actual manifestation of the bug was the result of cleaning up the code. And it only got found because I happened to notice something weird in a very chaotic scene.

So I wrote it down

CLAUDE.md now has explicit guidance about this whole family of patterns: prefer static wiring at edit time, don’t use scene/hierarchy searches as a substitute for a connection that should be made explicitly, and don’t guard against conditions that should be loud failures. Let it crash where a crash is the fastest path to the truth. On top of that, I have a prefab validation tool that checks every prefab to identify things that should be statically wired but aren’t. Since some things need to be wired up in-scene, the validation code for each component checks to see whether it’s being run on an instance in a scene or a prefab on disk and performs the validations appropriate to that context.

But that’s one pattern. One. I codified it only because it bit me hard enough to go looking, and only after I’d already paid for it with a bug that was latent for weeks and present for days.

Which leaves me with the question this whole post is really about: what else? How much of my own judgment have I never enumerated, never contextualized for this specific project, never codified into instructions — because I don’t even experience it as a decision when I’m doing it myself? Every one of those un-codified judgments is a place where the factory will, sooner or later, do something locally reasonable and globally corrosive.

That’s the real long-term risk of building this way. It’s not that the AI writes bad code — most of the time the code is fine. It’s that the AI cannot do the invisible work on its own — not until I’ve found it, named it, and written it down — and the invisible work is what keeps “long-term” long. Every gap in what I’ve managed to write down pulls the horizon a little closer.

I don’t have a clean answer yet. The honest version of “what’s next” is: keep play-testing, keep auditing, and treat every bug that the factory’s defensiveness managed to hide not just as a bug to fix, but as a piece of invisible work to finally make visible.

The game, for what it’s worth, continues to come along nicely. The radioactive clouds look great now. Well… “great” as far as Programmer Art goes.

Comments