Snake Again, This Time Actually in Godot

A confession about the last post. When I asked Claude Code for a playable Snake, what I had in mind was the Godot pipeline from the Pong article. What I got back was a hand-rolled canvas game in React. It is good, it loads instantly, and it never touched a game engine. That was partly my fault for not saying so, and partly the model doing what models do: picking the shortest sensible path to "playable on my site." The lesson about being specific is free. The game below cost a little more.
So, round three. Same Snake, real engine this time, exported to WebAssembly and embedded in this post. It is not loaded yet, because it is nine megabytes over the wire and I am not going to spend your data plan without asking. Click it and it is yours. It even plays itself while it waits, like an arcade cabinet.
arrows / WASD or swipe · it plays itself until you press something
Claude wrote all of this too: the GDScript, the tests, the export setup, and the embed you just clicked. Here is what building the same game twice taught us.
The harness earned its keep
The Pong post ended with a reusable harness: make targets for headless tests, a screenshot tool that renders a scene to a PNG with no monitor, and a tiny test framework. The Snake project starts from exactly that skeleton. Same Makefile, same loop: change code, run tests, render a frame, look at it.
The rules live in a node-free script, snake_logic.gd, that knows nothing about scenes or rendering, exactly like the browser version keeps its tick function away from the canvas. The test suite runs 20 assertions against it in about a second, headless. Wall collisions, growth, the tail-vacating edge case where moving onto the cell your tail is about to leave is legal. That last one is the kind of rule you get wrong silently, and it is covered by a named test.
Building the same game twice found a real bug
Here is my favorite part. The browser Snake from the last post queues up to two turns and validates each new turn against the last queued one. The Godot version validated new turns against the direction the snake was currently facing. Both sound identical in a sentence. They are not.
Moving right, press up then left, faster than one tick. The browser version turns up, then left, like you asked. The Godot version turned up and silently ate the left, because left is the opposite of the direction the snake was still facing when you pressed it. Not fatal, just unresponsive, the game equivalent of a dropped input. Nobody would ever file a bug report about it. They would just feel that the game was slightly worse and not know why.
We only caught it because there were two implementations of the same rule sitting side by side and the diff read like a bug report. Claude fixed the Godot version to match, with a turn queue, and added two tests: quick chained turns both land, and a queued reversal is still impossible. If you only ever build a thing once, you never meet this class of bug.
What the engine actually costs
Numbers, because the last post bragged about five kilobytes of JavaScript.
The game logic and scene compile into a 20 KB pack file. Twenty kilobytes. The engine that runs it is a 41.7 MB WebAssembly binary, which compresses to about 9 MB on the wire. The ratio is the whole story: the game is a rounding error riding on an engine that ships a full renderer, physics, audio, and scene system whether you use them or not. Snake uses approximately none of it.
There is a second cost nobody warns you about. Godot's web builds normally use threads, and threaded WebAssembly requires cross-origin isolation headers that would have meant fiddling with how this whole site is served, and can break other embeds. Godot also ships a single-threaded web template exactly for this situation, so this build uses it. Snake does not need threads. The header requirement quietly disappears.
That is also why the game hides behind a click. The canvas Snake from the last post is part of the page. This one is a deliberate download, with the size printed on the button. Embed etiquette for game engines on blogs: posters first, megabytes on request.
So which one was right?
For a snake on a blog, the browser won and it is not close. Instant load, no engine tax, and the whole thing is one readable file.
But the moment the game grows, the answer flips. Add sound effects, particles when you eat, a title screen with animations, or physics for anything at all, and in the browser version each of those is a subsystem you write by hand. In Godot each one is a node you add. The 9 MB is not overhead, it is prepayment. The Pong harness, the tests, the screenshot loop: all of it transfers to a game with actual scope, and that is the version of this experiment I want to run next.
Same dare as last time, now with two games to lose at. My high scores are set on both. The contact form is on the home page, and I will still want proof.