← BACK TO THE DOCKET

Three models, one game spec, one file each

RULING

One run, one file: Gemini 3.1 Pro takes it. Fastest, cheapest, the only one that plays well.

One spec, three models, one file each. The requested stack is HTML/CSS/JS for a game called Locked 2048, a 2048 variant with a locking rule. I evaluated with this game because most of the usual candidates (Snake, Sudoku, Tetris) have been built a thousand times and are now part of the training data.

The models under test, all at high reasoning:

  • Claude Fable 5
  • OpenAI Sol 5.6
  • Gemini 3.1 Pro

Play them first

Play all three before you read a single number. Form your own verdict, then see whether mine holds up.

Unmodified output. Nothing was fixed by hand.

The scorecard

Measured 2026-08-02. Builds tested exactly as generated, no edits. Method: static review of all three sources, plus an automated harness that plays each build in headless Chromium with Math.random replaced by the same seeded PRNG, so all three see an identical spawn sequence. 300 counted moves per build, plus an input-cadence sweep and a 390×844 mobile pass.

Fable 5Gemini 3.1 ProSol 5.6
Rule correctness16 / 1615 / 1616 / 16
One-shot playableYesYesYes
Console + page errors (300 moves)000
Inputs accepted at 30 ms cadence9 / 4040 / 4040 / 40
Feel and polish (0–5)352
Code quality (0–5)435
Single file, zero dependenciesYesYesYes
Lines / size709 / 22 KB582 / 19 KB757 / 24 KB
Weighted total90.692.489.4

The weighted total is normalised over the 85% of the rubric that has been scored; the repair round (15%) has not been run yet and will move these numbers.

All three shipped a playable, dependency-free game that ran unedited and threw zero errors across 300 moves, and all three got the hard part right: the lock fires on exactly every 10th counted move, lasts exactly 5, and behaves as a wall. The split isn't in the rules, it's in everything around them. Fable drops three of every four inputs when you play fast, Sol rebuilds every tile on every move so nothing slides, and Gemini, the only build to miss an assertion, is the only one you can actually play.

Correctness detail: 16 assertions

#AssertionFableGeminiSol
15×5 grid, 2 tiles at start
2Arrow keys move
3WASD moves
4Touch swipe moves
5Tiles slide fully to the wall
6Equal tiles merge into their sum
7Merged tile does not merge again same move
8Spawn after a board-changing move
990/10 spawn distribution
10No spawn when the move changes nothing
11Score increases by merged value
12Lock on every 10th counted move, visibly marked
13Locked tile does not move or merge
14Tiles cannot slide past a locked tile
15Lock expires after exactly 5 moves
16Game over accounts for locks

Assertion 3, the only failure. Gemini matches key input with ['ArrowUp','ArrowDown','ArrowLeft','ArrowRight','w','a','s','d'].includes(e.key) and compares e.key === 'w' directly. With Caps Lock on or Shift held, e.key is 'W' and nothing happens. Measured: 8 uppercase WASD presses produced 0 moves. Fable maps both cases explicitly; Sol falls back to .toLowerCase().

Assertions 12, 13, 15: measured, not inferred. Across 300 counted moves per build, every lock started on a move divisible by 10 (10, 20, 30, 40, 50, 60, 70, 80...), every lock spanned exactly 5 counted moves, never more than one lock was alive at once, and no locked tile changed position or value while locked. Zero violations, all three builds.

Assertion 14: two different correct solutions. Fable and Sol both split each line into segments at locked cells and compact each segment independently. Gemini walks tile-by-tile and breaks the scan on any occupied cell, refusing the merge when the blocker is locked. Different code, same behaviour.

Where the real split is

1. Fable drops inputs under speed

Fable sets an animating flag for 150 ms after every move and rejects input during it, with no buffering. Measured, 40 keypresses at each cadence:

Gap between pressesFableGeminiSol
30 ms9 / 4040 / 4040 / 40
50 ms14 / 4040 / 4040 / 40
80 ms20 / 4040 / 4040 / 40
120 ms20 / 4040 / 4040 / 40
160 ms40 / 4040 / 4040 / 40
200 ms40 / 4040 / 4040 / 40

Fable accepts everything only at ≥160 ms, about 6 moves per second. macOS key auto-repeat runs at roughly 30 ms, so holding an arrow key loses about three of every four presses. This does not show up in a slow correctness pass; it shows up the moment a human plays fast.

2. Sol has no movement animation at all

Sol's render() clears every cell (cell.textContent = "") and constructs fresh .tile elements each frame. There is no stable tile identity and no transform transition, so tiles teleport rather than slide. Worse, the appear scale animation sits on the base .tile class, so every tile on the board replays the pop-in animation on every single move; the whole grid pulses on each keypress. Fable and Gemini both keep tile nodes alive and animate transform; Gemini additionally slides a ghost of the absorbed tile into the merge target before removing it, which is the nicest merge of the three.

3. Gemini's architecture is the weakest

Gemini keeps all state in globals, wires buttons with inline onclick= in the HTML, omits strict mode, and (the real smell) mutates the DOM inside attemptMove(), so game logic and rendering are welded together. Sol is the cleanest: strict mode, a pure calculateMove(direction, sourceBoard, countScore) that returns a new board and is reused verbatim for the game-over lookahead. Fable sits between them: well-commented and correctly separated, but written in ES5-style var throughout.

The surprise worth leading with

Given the same seeded RNG and the same 300-keypress sequence, Fable and Gemini finished with identical scores, 3,692, on identical move counts. Two independently generated implementations, different code style, different algorithms for the wall rule, and the games ran in lockstep. Sol finished at 3,592, and the reason is not a rule difference: Sol spawns the new tile before applying the lock, while the other two lock first and spawn second, so it consumes the random stream in a different order and desynchronises by one move.

The repair round

This is the unscored 15% of the rubric, and it will move the totals. The first-run defects were there on all three with no repair instructions given; to be fair, this could be labeled as an omission in the prompt. What each build goes back to the chat with:

  • Claude Fable 5: the worst first run. The layout was broken because the tiles did not match the squares of the board.
  • OpenAI Sol 5.6: review the blocked-tile functionality to make sure it works as expected.
  • Gemini 3.1 Pro: add more guidance on the controls and improve the UI and UX.

On time and cost: Sol took twice as long as Fable, was 30% less expensive, and produced the cleanest source of the three. Fable is the most expensive brain, and on the first go the layout was broken, with the squares not aligned with the grid. Gemini was by far the simplest outcome: fast, cheap, and it did well enough to call the app complete.

Honest limits of this scorecard

  • One prompt, one run per model. These are nondeterministic; a re-run could reorder the field.
  • The harness verifies the lock mechanic, input handling, error count and mobile layout. It does not verify assertions 5–11 by play; those were confirmed by reading the source of all three, which is a weaker method and should be described as such.
  • The seeded-PRNG comparison is only valid because all three call Math.random the same number of times per move. That held here; it may not on a re-run.
  • Feel (3 / 5 / 2) and code quality (4 / 3 / 5) are subjective and provisional: my read from source, not from a human playing blind. The weighted totals are within 3 points of each other, which means these rows decide the ruling.
  • Assertion 3 is scored strictly binary per the rubric. An argument exists that lowercase WASD satisfies the spec and the Caps Lock case is a footnote. Whichever you choose, it's the difference between 16/16 and 15/16 for Gemini.
  • Scored 2026-08-02 against the versions generated that day.

The ruling

One run, one file: Gemini 3.1 Pro takes it. Fastest, cheapest, the only one that plays well. 92.4 on the scored 85% of the rubric, 40/40 inputs at every cadence, 5/5 on feel, and the smallest build of the three at 582 lines.

Sol matches it on inputs and beats it on rules (16/16 to Gemini's 15/16) and on code quality, but it renders the board by rebuilding every tile on every move, so nothing slides and the whole grid pulses on each keypress. That is the product, not a detail of it. Gemini's single miss is uppercase WASD, one line of .toLowerCase(), against a defect in Sol that runs through its entire render path. Gemini's architecture is the weakest of the three and that cost it the code-quality row; it still finished ahead. If you intend to keep extending the code rather than play the game, take Sol.


Subscribe

One ruling per post, in your inbox.

No cadence promises. Unsubscribe in one click.

Form not loading? Subscribe here.