Good At Business

Good At Business

Cleaning up AI Code like a Pro

Lessons learned from cleaning up a chaotic weekend of AI coding.

Drew Maring's avatar
Drew Maring
Feb 03, 2026
∙ Paid

Last November, Carly and I entered Joe Reis’s Practical Data Unhinged AI Vibe Coding Hackathon. The theme options included “Minimum Viable Chaos” and “The App Nobody Asked For.” Award categories included “Best AI Abuse of the Weekend” and “How Is This Even Working?” It was a genius idea that poked fun at vibe coding while embracing the chaotic and unhinged fun that only vibe coding can bring.

We chose “Minimum Viable Chaos” and built a real-time multiplayer air traffic control game to crowd source air traffic control. This seemed absolutely in line with the ‘unhingedness’ of the world today. So we went about vibe coding it with Claude Code.

In two days.

With some light day drinking.

The game is called Unhinged ATC. Multiple players share the same airspace, selecting and directing aircraft on a retro CRT radar screen. React frontend, Node.js backend, WebSocket state sync at 60 frames per second. We vibe-coded the entire thing with Claude, shipping features as fast as we could think of them. New chaos abilities? Sure, add them. Sassy NPC Commentary? Why not. Weather cells? Throw it in. The hackathon energy was perfect for this kind of thing. You don’t stop to write specs when you’re three drinks deep and the demo is in six hours.

Unhinged ATC Gameplay

We had a blast. The game worked. We deployed it to Google Cloud and real people on the net played it.

Chaos controls add to the already unhinged
Random chaos events are triggered to keep things interesting

And then I found a bug that took me the better part of an afternoon to track down.

The fix was one word.

If you read my last post, ”How to Vibe Code like a Google Engineer,” you saw me build a project the right way. Spec-driven from day one, constitution file, acceptance criteria before code, 94% test coverage. That was MacroMetric. Unhinged ATC is the other thing. The thing you build when the vibes are flowing and nobody is asking about test coverage and the idea of tech debt doesn’t exist except in a parallel universe.

The first article was aspirational. This one is confessional.

The Poster Child: The Ownership Color Bug

In Unhinged ATC, each player gets assigned a unique color when they join. When you select an aircraft to control, it turns your color on everyone’s radar screen. When you select a different aircraft, the old one should revert to the default green.

It didn’t.

When a player selected a new aircraft, the old one kept their color. Every player in the game saw it. Multiplayer visual state was stuck forever.

I stared at this for a while. The selection code looked correct. The rendering code looked correct. The WebSocket sync looked correct. Each piece of the system was, individually, doing exactly what it was supposed to do.

The bug existed in the space between the systems.

Here’s the pipeline:

Step 1: Server releases ownership

// GameRoom.ts, when player selects a new aircraft
Object.values(this.gameState.aircraft).forEach(a => {
  if (a.ownerId === controller.id) {
    a.ownerId = undefined;   // <-- valid JavaScript
    a.ownerColor = undefined; // <-- valid JavaScript
  }
});

This is correct JavaScript. Setting a property to `undefined` clears it.

Step 2: Server serializes the state delta for WebSocket broadcast

JSON.stringify({ ownerId: undefined, ownerColor: undefined })
// Output: “{}”

JSON.stringify strips keys with undefined values. This is correct JSON behavior, defined in the spec. The delta payload goes over the wire with no ownerId or ownerColor keys at all.

Step 3: Client merges the delta into local state

// gameStore.ts, Zustand spread-merge
[id]: {
  ...store.gameState.aircraft[id],  // old state (has ownerColor: “#FF5733”)
  ..updates,                        // delta (has no ownerColor key)
}

The spread operator preserves existing keys when the source doesn’t contain them. Since the delta has no ownerColor key, the stale ”#FF5733” survives. The aircraft stays red forever.

Independently all of this code could be considered correct. They all contribute to a massive bug, though.

No AI would reason about this without guidance. It requires understanding the full pipeline from server mutation through JSON serialization through WebSocket transport through client-side merge to canvas rendering. Each step is owned by a different layer of the stack, and the bug only shows up when all three interact in sequence.

The fix:

diff

- a.ownerId = undefined;

- a.ownerColor = undefined;

+ a.ownerId = null;

+ a.ownerColor = null;

One word, two lines.

null survives JSON.stringify. The delta arrives with ownerColor: null, the spread-merge overwrites the stale value, and the rendering code (`if (aircraft.ownerColor)`) already treats null as falsy, falling through to default green.

My first instinct was actually to fix this on the client side. When the selected aircraft changes, just clear the old one’s color locally. But that would only fix it for the player doing the selecting. Every other player in the game would still see the stuck color. The fix had to happen at the source, on the server, so the corrected state propagates to everyone through the existing WebSocket broadcast.

This is the kind of bug that makes you reconsider your whole process (and possibly life choices).

How to fix Vibe Code

Enter OpenSpec: Change-Scoped Specs for Brownfield Code

User's avatar

Continue reading this post for free, courtesy of Carly Taylor.

Or purchase a paid subscription.
© 2026 Carly Taylor · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture