You spend research points, cash and four months of in-game calendar on a node that says it gives you curated training corpora. The points leave your account. The corpora never arrive.
Not once. Not intermittently. Never, in any campaign, since the day the code was written.
The gap nobody has a check for
A unit test answers one question: does this function do what I expect when I call it?
That is a useful question and it is not the only one. The second question is whether anything reaches the function on a path a user can walk, and almost no standard tooling asks it. Coverage tools do not help either, because your test suite is doing the calling, so the line is covered.
The result is a fault with no symptom. It does not crash. It does not log. The screen looks finished, the suite is green, and the only two witnesses are a user who tries to do the thing and finds no button, and a developer who deliberately goes looking.
The sweep, in ten lines
This works if your project has a layer that holds the rules and a layer that holds the controls. In my case those are a simulation layer that imports nothing from the engine, and a UI layer that consumes it.
List every public method in the rules layer. Search the controls layer for each name. Zero matches means dead or unreachable, and you go and look at which.
It is crude on purpose. It produces false positives, which a human dismisses in a few seconds each, and it costs nothing to run. On its first execution against my project it reported 46 public mutators, 21 with no caller in the interface, and five that were meant to be things a person does.
What it actually found
| What was unreachable | What the player experienced |
|---|---|
| Research node rewards | Paid points, cash and four months for a corpus and an architecture family that were never granted. Every campaign was locked to the starting corpus and a dense transformer, forever. |
| Cancelling a training run | A regretted blueprint cost the full two hundred days. There was no way to stop it. |
| Cancelling an architecture programme | The same shape. A committed programme blocked the next one for months with no exit. |
| The server hall | A complete grid system with heat, placement rules and its own passing test fixture. No way in. It had been written months earlier and connected to nothing. |
| The hiring screen's empty state | A blank page with one button, because every route in set a portal first and the empty case was assumed unreachable. It was reachable. |
| Storage shelf art | Artwork and a light grid, both finished, drawn by nothing. Still open, and deliberately so. |
Twelve in total across the life of the project. The first three shipped in a build that people played.
The one that was worst, and why
The player was charged in a currency they could not refund
The research node case is the one I think about. A missing feature is disappointing. A feature that takes payment and delivers nothing is a different category, because the player has spent something scarce and has no way to know they were robbed. They assume they misunderstood the mechanic.
That is the real cost of this fault class in a game specifically. In a productivity tool an unreachable feature is absent. In a simulation it can be actively lying to the person playing, and they will blame themselves.
Fixing it without loosening the test
The tempting repair is to write a test that calls the granting function and asserts the corpus arrives. That test would have passed before the fix as well, because the function was never broken.
The useful repair is a test that is banned from calling the function directly. It finishes the research node the way a real day does, by advancing the simulation, and then reads what the company owns afterwards.
Write one of those per mechanism, at the completion site, and the class of fault cannot return silently.
Does AI assistance make this worse?
In my experience, yes, and I say so on every page where it is relevant because I build with assistance openly.
The honest cost is narrow and specific. Assistance makes writing a function that satisfies a description much faster. It does nothing at all for whether anything ever calls that function. So the rate at which finished, correct, unwired mechanisms appear goes up alongside the rate at which mechanisms get written.
The countermeasure is not writing less. It is moving the caller sweep off your memory and onto a schedule, where it runs whether or not you remembered to be suspicious that week.
Why this is a solo-developer problem in particular
On a team, an unwired feature usually dies in review, because a second person reads the change and asks where it is called from. That question is the whole defence, and it is free when somebody else is reading.
Alone, nobody asks it. Worse, every safety net you build is also a place a failure can hide: a fallback image, a default value, a bare catch, a test that never opens the real window. You add them so the thing does not crash, and in doing so you delete the only signal you had.
So the check has to be mechanical. Not a habit, not a rule you intend to follow. A script with a number that goes in the commit message.
Every case here is from Scaling Laws, an AI company tycoon released as a free public build, with the source and the guard tests open.
The game page and the source · The sibling guide: bugs that succeed instead of crashing
Questions people ask about this
Can a feature pass all its tests and still be unreachable?
Yes, and it is common. The test calls the function directly, so it verifies behaviour and says nothing about reachability. Twelve mechanisms in one project were finished, tested and unreachable.
How do you find code that nothing calls?
List every public method in the layer that holds your rules, search the layer that holds your controls for each name, and look at the zero-match list. Ten lines, crude, effective.
Why do these survive so long?
Because there is no symptom. Green suite, clean log, finished-looking screen. Only a user hunting for a button or a developer deliberately looking will notice.
Does AI assistance make it more likely?
It speeds up writing functions that satisfy a description and does nothing for whether anything calls them, so the rate goes up with the writing rate. Run the sweep on a schedule rather than from memory.