Day one ended with 60 passing tests and a game that had never left my machine. The next twelve commits, from Monday evening to Tuesday midday, are the record of what those tests did not cover: a real server, a real phone, and real players.

Getting it to run at all took three commits. The plan was Render's free tier with the database hosted on Turso, and the first casualty was the database driver: better-sqlite3 is a native binding, which had no business in the production dependencies there, so the runtime driver switched to a libsql client while better-sqlite3 stayed in devDependencies for the local dictionary build. The swap was not a one-liner. The new driver is fully async where the old one was synchronous, so every database call in the API routes and the daily seeder gained an await, and the migrations had to become idempotent so that running them against an already-populated hosted database is a no-op.

Then the migrations could not find themselves. Resolving the migrations directory relative to import.meta.url works fine locally; after adapter-node bundles the server, that URL points somewhere inside build/server/chunks/, where no migration files exist. process.cwd() is the project root in both environments, so it won. Neither of these problems is a bug in the game. They are the class of assumption you cannot catch on the machine that formed them, and no amount of unit testing would have surfaced either.

The deployed game revealed a better one: the snake did not visibly move. The engine processed each word correctly, the animation queue played its steps on schedule, and the board stayed frozen until the last step, at which point the snake teleported by the length of the word. The store's animation handler was updating the snake's positions on every step but never rebuilding the grid, and the grid is what the board component renders from. Every engine test passed while this happened, because the engine was correct. The bug lived in the thin reactive layer binding engine to screen, which is exactly the strip of code that day one's framework-free purity had left without tests. The fix rebuilds the grid on every animation step, so each letter of a submitted word produces one visible board update. Watching the snake actually walk a word for the first time was the moment the game started existing; everything before that was a data structure with opinions.

Then the first real players arrived, and within hours they had found what I had not. The apple never respawned after the first pickup, because the animation handler mutated the edibles before the engine's apply step read them, so the replacement spawn was computed from the wrong state. Opening the About page silently restarted your game in progress. Pressing Enter while an on-screen key had focus typed that letter instead of submitting the word. And the snake grew forever: growth from eating an apple was applied once during the animation (the eating step keeps the tail) and then counted again when the result was applied, because the apply step restored the pre-animation growth counter instead of the remaining one. Every apple eaten left a permanent extra count behind, and the snake grew one segment more per word than it should for the rest of the game.

Two smaller corrections from the same night say something about shipping fast. The alphabetical-streak combo was checking full lexicographic ordering, so any three words in dictionary order counted as a streak ("apple, mango, zebra" qualified); it now requires the first letters of three consecutive words to be directly adjacent in the alphabet, A then B then C. And the share text got its emoji mini-grid deleted: mapping 20x20 board coordinates down to 10 cells collapsed most games into a single column that conveyed nothing. Day one had been proud of that grid. Score and word count communicate more.

By Tuesday morning the fixes had grown into deliberate UX work: a navigation guard with a real confirmation dialog (where "Keep Playing" navigates back to the ongoing game rather than just closing the dialog), a 50-word cap on the daily challenge, a guarantee that at least one apple is always on the board, expanded high-contrast coverage, and a snake outline drawn only on exposed edges so the contour follows the whole body. Both of the visual ones needed a second pass the same morning. The outline merged across the body wherever the snake doubled back on itself, until border suppression required segment indices differing by exactly one. High-contrast mode combined with the light theme produced white-on-white text, a combination nobody tests until someone uses it.

The range closes with a pixel-art SVG logo, Open Graph metatags, and one more apple respawn fix: eating a regular and a golden apple in the same word could leave the board with no apple at all, because a per-step replacement spawn raced the end-of-word guarantee check. The per-step logic was deleted; the single check after all removals and additions is the one that stayed. Worth noticing early: the second fix to the same bug is usually a deletion.

Everything in this range still went straight to main, unreviewed. Day two kept that habit and added a leaderboard page, a unified toolbar, and a first Norwegian translation that arrived far too early. That last one is its own post.