Giving the logic a name

Two days pulling shared logic out of stores and routes into small, named, independently tested modules, one of which immediately surfaced a real duel bug and got its own same-day fix.

Two days went into naming things that used to just live inside a bigger file: an animation pipeline, an API client, a service, a scoring formula, a grid query, a counter. None of it changed what a player experiences. All of it changed how much of the codebase has to be understood, or trusted, before touching any one part of it.

The one store that missed the first fix#

The animation queue stopped mutating game state per frame for the daily and duel stores a week ago, but the demo overlay’s own copy of the same step-and-complete logic was never touched, and it still updated game state on every animation frame instead of once the queue finished. A single AnimationMachine module now owns that pipeline for all three stores, daily, demo, and duel alike, so the fix that already existed for two of them finally reaches the third instead of living as three separate copies of the same idea.

Naming the seams#

A raw fetch() scattered across the daily store’s calls to its own analytics endpoints became a small typed client the store calls without knowing a URL exists. A 362-line route handling the duel opponent’s move shrank to about a third that size once its word-candidate fetching, batching, and caching moved into their own service, tested without a live database. And the rule that a collision voids a word’s own score while keeping whatever fruit it already ate stopped living as two separate copies in the daily and duel submission paths, consolidated into one scoring function both call.

A shared counter, and the bug it had been hiding#

Every new apple and combo fruit got its id from one number, incremented globally, shared across every game running in the process, including tests. Moving that counter onto the game state itself, so each game keeps its own, immediately exposed what the shared version had been hiding: two independent games created back to back produced an apple with the identical id, since the second game’s own counter had never actually reset. The duel mode’s own version of the same problem needed one more pass, the same day: the server had been guessing the counter’s value from whatever edibles happened to still be on the board, a heuristic true only because a board always has at least one apple on it, and now the client sends the exact number it already holds, with the guess kept only as a fallback for anything that predates the change.

Where this leaves things#

Six named, independently tested modules now stand where six pieces of shared logic used to sit inside stores and routes, and the one bug the naming itself turned up, a counter that had never really been isolated, was found and closed within the same two days rather than waiting for a player to hit it first.