Three models, one game spec, one file each
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 5 | Gemini 3.1 Pro | Sol 5.6 | |
|---|---|---|---|
| Rule correctness | 16 / 16 | 15 / 16 | 16 / 16 |
| One-shot playable | Yes | Yes | Yes |
| Console + page errors (300 moves) | 0 | 0 | 0 |
| Inputs accepted at 30 ms cadence | 9 / 40 | 40 / 40 | 40 / 40 |
| Feel and polish (0–5) | 3 | 5 | 2 |
| Code quality (0–5) | 4 | 3 | 5 |
| Single file, zero dependencies | Yes | Yes | Yes |
| Lines / size | 709 / 22 KB | 582 / 19 KB | 757 / 24 KB |
| Weighted total | 90.6 | 92.4 | 89.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
| # | Assertion | Fable | Gemini | Sol |
|---|---|---|---|---|
| 1 | 5×5 grid, 2 tiles at start | ✓ | ✓ | ✓ |
| 2 | Arrow keys move | ✓ | ✓ | ✓ |
| 3 | WASD moves | ✓ | ✗ | ✓ |
| 4 | Touch swipe moves | ✓ | ✓ | ✓ |
| 5 | Tiles slide fully to the wall | ✓ | ✓ | ✓ |
| 6 | Equal tiles merge into their sum | ✓ | ✓ | ✓ |
| 7 | Merged tile does not merge again same move | ✓ | ✓ | ✓ |
| 8 | Spawn after a board-changing move | ✓ | ✓ | ✓ |
| 9 | 90/10 spawn distribution | ✓ | ✓ | ✓ |
| 10 | No spawn when the move changes nothing | ✓ | ✓ | ✓ |
| 11 | Score increases by merged value | ✓ | ✓ | ✓ |
| 12 | Lock on every 10th counted move, visibly marked | ✓ | ✓ | ✓ |
| 13 | Locked tile does not move or merge | ✓ | ✓ | ✓ |
| 14 | Tiles cannot slide past a locked tile | ✓ | ✓ | ✓ |
| 15 | Lock expires after exactly 5 moves | ✓ | ✓ | ✓ |
| 16 | Game 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 presses | Fable | Gemini | Sol |
|---|---|---|---|
| 30 ms | 9 / 40 | 40 / 40 | 40 / 40 |
| 50 ms | 14 / 40 | 40 / 40 | 40 / 40 |
| 80 ms | 20 / 40 | 40 / 40 | 40 / 40 |
| 120 ms | 20 / 40 | 40 / 40 | 40 / 40 |
| 160 ms | 40 / 40 | 40 / 40 | 40 / 40 |
| 200 ms | 40 / 40 | 40 / 40 | 40 / 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.randomthe 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.