I Shipped a Feature That Only Existed in the Design Doc
Here’s a fun way to spend an afternoon: pick up a phone, open your own product on staging, and discover that a feature you believed had existed for months was never written at all.
The feature was on-screen touch controls. Our runtime is built around pluggable engines, and each engine is supposed to own the controls a touchscreen player needs — a virtual pad, virtual buttons, whatever the engine’s input model calls for. The design doc is unambiguous about this. It says the engine owns the on-screen controls a touchscreen needs, and that naming a control layout settles two things at once: what the controls look like, and what vocabulary of actions a game can talk about.
On a phone, none of that happens. Taps and drags reach the game fine. Menus respond. But the simulated gamepad that a touch layout is supposed to conjure onto the screen simply never appears, on any engine. There is nothing to press.
What I actually built
When I wrote the first engine, I made a decision that felt clean at the time and turned out to be half a feature: I treated a touch layout as a vocabulary contract, not a widget. Choosing a layout tags the actions a game registers, so the engine and the game agree on what “primary” or “cancel” means. That’s genuinely useful. It’s also only the second half of what the design called for.
I then documented my own decision faithfully. The header comment on every engine’s layout code says, in so many words, that it registers nothing and draws nothing. Every subsequent engine copied that shape, because copying the established shape is what you do. Four engines later, nothing anywhere in the system draws controls, watches for a touch pointer in order to reveal them, or hides them again when a mouse or keyboard shows up.
The most damning artifact is a seam I left in the structured engines: a hook whose comment explains it’s the path a host’s touch chrome would push input through. I wrote the socket and never built the plug. That comment sat there for months as a polite note to myself that I was not finished, and I read past it every single time.
Why nobody caught it
This is the part worth dwelling on, because the error wasn’t only the missing code. It was an ecosystem that made missing code invisible.
The docs agreed with the bug, not the design. Each engine’s own documentation repeated the “draws nothing” claim, and so did the reference docs we seed into tooling workspaces. So we had a design doc saying the engine draws controls and a dozen component docs saying it doesn’t, sitting in the same repository, for months. Nobody reconciled them. If you asked the codebase what the truth was, it answered confidently and wrongly.
Adjacent work made touch look supported. We later did real, careful work on pointer and touch handling — gesture claiming, per-contact capture — and our specs require menus to respond to touch. All of that works. So “touch support” was demonstrably present. It just wasn’t the part a player needs to move a character.
The one check that could have caught it was opt-in. Our case harness grew a touchscreen driver. It’s off by default, enabled per case, and no assertion anywhere says “controls should be on screen now.” A test facility nobody is required to use is a test facility that documents intent rather than enforcing it.
We reviewed by reading and by running tests. Engine review meant reading code and watching a green suite, on a desktop, in a desktop console. Not one review step involved holding a phone.
What I should have done
Treated the doc conflict as a defect the moment it appeared. When I decided a layout would be a vocabulary contract instead of a widget, I diverged from the design doc. The correct move was to either change the design doc — and argue for why a host, not the engine, should own the chrome — or implement what the design said. Instead I wrote a component doc that quietly contradicted the design and moved on. A design doc and a component doc disagreeing is not a wording nit to clean up later. In this case the divergence was the bug, sitting in plain text, waiting to be read.
Treated “draws nothing” and “the seam a host would use” as open work. Those comments are honest, and honesty in comments is good, but honesty is not tracking. A comment describing a half-built feature has no due date, no owner, and no visibility outside the file. Each of those phrases should have had an issue attached to it, linked from the comment, so the absence showed up on a board instead of only in a header I’d stopped reading.
Written a check that runs on the device class the feature is for. A feature that only manifests on touchscreens needs a check that runs on a touchscreen. The harness could already do this. I should have turned the touchscreen driver on for at least one case per engine and asserted that controls actually render — and that they disappear again when a mouse or keyboard takes over. A default-off driver was an invitation I never accepted.
Played the thing on a phone. Nothing exotic. Before any release milestone, open each engine on a real handset and try to play. Reading code confirms the code you have; it is very bad at revealing the code you don’t. A five-minute manual pass would have found this the week it was introduced.
Changes I’m making
Concretely: I’m reconciling the design doc and the component docs into one statement of who owns touch chrome, and implementing that statement — including the reveal-on-touch, hide-on-mouse behavior that nothing currently does. Every “draws nothing” and “seam a host would use” comment gets an issue link or gets deleted because the work is done. The touchscreen driver goes on for at least one case per engine, with an assertion about controls, not just about input plumbing. And phone verification joins the pre-milestone checklist as an explicit step with a name next to it.
The broader lesson I want to keep: a feature can be fully designed, fully documented, and fully absent, and if the documentation drifts toward the absence, the system will stop asking where the feature went. The thing that saved us was not a test or a review. It was someone picking up a phone. I’d like to need that particular kind of luck less often.