journeylessonstoolingworkflow

Done Is Not Proven: The Bugs That Taught Me to Check the Claim

A month of work on the prototype, compressed into the one lesson that kept repeating: the distance between a thing being finished and a thing being proven finished is where most of my lost time lives.

The lesson before the stories

I run a one person studio with an AI team doing a lot of the typing. Over the last month I shipped an engine upgrade, a generated forest, a deck screen, a first clean packaged build, and a full backlog audit. I also lost more hours than I want to admit to three separate problems that all had the same shape, plus one that was not code at all.

Every one of them was a claim I accepted without evidence.

That sounds obvious written down. It was not obvious while it was happening, because in each case the claim came from something I had good reason to trust: a profiler, a build tool, my own eyes, a teammate. If you are building alone, or building with AI assistance, this is the failure mode I would warn you about first. Not bad code. Unverified claims.

Here are the stories, and what each one changed about how I work. The upgrade and the forest get their own post next week, because they are the other half of this lesson: what happens when the check is written down before the work starts.

The bug that was not where I was looking

Throwing Mjolnir felt bad. A hitch, every time, on every bounce. I did what you are supposed to do: I profiled it. Three times. The frame rate never dropped once. Nothing in the frame timing was wrong, so I started blaming the animation, then the projectile, then the systems around them.

The answer was our own hitstop system. Every time the hammer connected, the game froze the entire world for a fraction of a second on purpose, four to six times per throw. That is a feature. It makes a single hit feel heavy. It makes a bouncing hammer feel broken.

There was nothing to find in frame time because time was being stopped deliberately, and a profiler measures how long work takes, not whether the clock was paused. The evidence was sitting in a game feel log the whole time, one line per freeze. In the new build, Mjolnir opts out of the freeze while every other hit keeps it.

What changed: when a stutter has a rhythm, stop profiling and go look at what pauses the clock on purpose. More generally, if three passes with a tool find nothing, consider that the tool may not be able to see the thing you are hunting.

The tool that always said yes

We build our own in-editor tooling so the AI team can work inside Unreal directly. One of those tools checked whether a material compiled. It returned success. It returned success for everything, including materials that were broken, because of how it was written.

A separate tool read interface layout and returned an empty result with a green light when it failed. That one cost me a night of guessing why a screen looked wrong while the reader kept insisting it was fine.

The first has been replaced with a version that can actually fail. The second now reports its own blind spot in plain words, and the rewrite that lets it fail properly is queued.

What changed: a check that cannot fail is not a check, it is decoration. Now when we add a tool, we deliberately feed it something broken and confirm it complains. If it cannot produce a red result on demand, it does not ship. This is the highest return habit I picked up all month, and it applies whether your tooling is homemade or bought.

What is not referenced is not cooked

Survival mode had no music in packaged builds. It worked in the editor every single time. I assumed an audio bug and went looking in the audio systems.

The music was never in the packaged game at all. Unreal packages what it can trace a reference to, and those tracks were only ever looked up by name at runtime, so nothing pointed at them and the packager quietly left them behind. One line of configuration fixed it.

The same class of problem had been hiding a broken vendor sound asset that every earlier build carried silently, until a stricter engine version finally refused to ignore it. We fixed the asset rather than muting the error, which I mention because muting it was genuinely tempting at the time.

What changed: anything loaded by name at runtime has to be explicitly marked for packaging, and "it works in the editor" is not evidence that it works in a build. Test the build, not the editor.

The rule underneath all of it

Every failure above became a rule, and the rules accumulate. That is the actual system:

  • Tools must be able to fail, and get tested with something broken before they are trusted.
  • Work is checked against a written definition, not against my mood on the day.
  • Claims arrive with evidence attached, or they are not claims yet.
  • The team captures its own proof before a human looks. This cut my review time and shrank the misses I still catch.

If you are one person trying to build something large, you cannot personally inspect everything. What you can do is make it hard for an unverified claim to reach you. That is a different problem, and a far more solvable one.

And a reminder to stay humble

Late one night I watched a character in an empty test session dash and swing at nothing, repeatedly, with no input from me. I lost real time to that before the logs pointed at the operating system rather than the game.

It was my own Bluetooth controller, drifting on a shelf across the room.

Not every bug is code. Check the boring explanation first, because sometimes the evidence is that you need to unplug something.

Where the project is

The prototype runs on the current engine version with a clean packaged build. Survival has its deck screen and a fog of war map that no longer shows you enemies you have not discovered. The first generated forest layer is built to a written spec instead of a feeling. The backlog has been audited end to end, and every open item now has a written reason to exist.

The goals board tracks what is actually in flight. It carries statuses rather than dates, because I would rather show you what is true than what I hope will be true.

If any of the above saves you an evening, that is the reason to write it down. If you disagree with how I am working, I would genuinely like to hear it: info@viseterna.com reaches me.