Technology · Testing

A test proves a function works.
It proves nothing about
whether anything calls it.

Twelve mechanisms that were finished, covered by passing tests, and impossible for a player to reach. What they cost, the ten-line script that finds them, and why this fault outlives every crash you will ever write.

By Marcin Firmuga·2026-08-19·updated 2026-09-01·8 min read·Technology

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.

531 automated tests were passing while that was true. They passed because every one of them called the granting function directly. The function worked. It has always worked. Nothing in the interface had ever called it.

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.

MUTATOR = re.compile( r'public\s+(?:bool|void|int|double|[A-Z]\w*)\s+' r'(Try[A-Z]\w*|Set[A-Z]\w*|Buy[A-Z]\w*|Start[A-Z]\w*|Cancel[A-Z]\w*)\s*\(') for path, source in simulation_files: for match in MUTATOR.finditer(source): name = match.group(1) if not re.search(r'\b' + name + r'\s*\(', ui_layer_text): print('no caller outside the rules layer:', name, path)

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 unreachableWhat 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.

// Not allowed to call TryAcquireDataSource. That is the entire point. var simulation = CompanyWithResearchUnderway(); simulation.Advance(NodeDurationDays); Assert.That(simulation.State.DataSources, Contains.Item(DataSource.CuratedCorpora), "The node completed and the corpus it advertises never arrived. " + "The player paid points, cash and four months of calendar for nothing.");

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.

MF

Marcin Firmuga

Solo developer · HCK_Labs · building in public

I write about what I actually shipped, with real numbers and real code, including the parts that did nothing. More: my story.