oneshotlm
Compare all models on this prompt →

2048 · mercury-3

Output generated Cost / run $0.0317 Runtime 504.4s

Prompt for this run

Build a playable 2048 game using HTML elements and CSS with a 4 by 4 board. Use HTML, CSS, and JavaScript as needed. Write it to `index.html` in the current working directory using your file tools. Inline your own CSS and JavaScript. Do not use external assets, CDNs, or network requests. No third-party libraries are provided. Required behavior: - Arrow keys slide tiles toward the selected edge. Adjacent equal tiles merge once per tile per move, and the score increases by the value of each new merged tile. - For example, moving [2, 2, 2, 2] left produces [4, 4, 0, 0], not [8, 0, 0, 0]. Moving [2, 2, 4, 0] left produces [4, 4, 0, 0]. These examples describe the move before spawning a new tile. - Start with two tiles. After a move that changes the board, spawn exactly one tile in an empty cell: 2 with 90% probability, otherwise 4. Do not spawn after an unchanged move. - Animate movement and merging without allowing rapid input to corrupt board state. Prevent arrow keys from scrolling the page. - Show score, a win message on reaching 2048, and Game Over only when no valid move remains. Provide New Game to reset board and score; allow continuing after a win. Verify: Play a short sequence of arrow-key moves and check that tiles slide, a valid move adds a tile, and any observed merge updates the score by its merged value. Use New Game and check that the board returns to two tiles with a reset score.

⛨ sandboxed Open in new tab ↗

TAKE IT TO CODE

A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.

RUN

modelmercury-3
vendorInception
harnesspi 0.87.1 · browser iteration
generated2026-10-23

METRICS

runtime504.4s
tokens in1214k
tokens out37k
cached1142k
cost / run$0.0317
Transcript · 85 messages
system

user

Build a playable 2048 game using HTML elements and CSS with a 4 by 4 board. Use HTML, CSS, and JavaScript as needed. Write it to `index.html` in the current working directory using your file tools. Inline your own CSS and JavaScript. Do not use external assets, CDNs, or network requests. No third-party libraries are provided. Required behavior: - Arrow keys slide tiles toward the selected edge. Adjacent equal tiles merge once per tile per move, and the score increases by the value of each new merged tile. - For example, moving [2, 2, 2, 2] left produces [4, 4, 0, 0], not [8, 0, 0, 0]. Moving [2, 2, 4, 0] left produces [4, 4, 0, 0]. These examples describe the move before spawning a new tile. - Start with two tiles. After a move that changes the board, spawn exactly one tile in an empty cell: 2 with 90% probability, otherwise 4. Do not spawn after an unchanged move. - Animate movement and merging without allowing rapid input to corrupt board state. Prevent arrow keys from scrolling the page. - Show score, a win message on reaching 2048, and Game Over only when no valid move remains. Provide New Game to reset board and score; allow continuing after a win. Verify: Play a short sequence of arrow-key moves and check that tiles slide, a valid move adds a tile, and any observed merge updates the score by its merged value. Use New Game and check that the board returns to two tiles with a reset score.

assistant
→ bash {"command":"pwd && ls -la"}
toolResult

/tmp/job-1yTXAk total 12 drwx------ 3 node node 4096 Oct 23 09:53 . drwxrwxrwt 8 root root 4096 Oct 23 09:53 .. drwxr-xr-x 3 node node 4096 Oct 23 09:53 .home

assistant
→ write /tmp/job-1yTXAk/index.html (11692 bytes)
toolResult

Successfully wrote to /tmp/job-1yTXAk/index.html

assistant
→ browser {"action":"reload"}
toolResult

{"console":[],"dom":"2048\nSCORE\n0\nNew Game\n2\n4","images":"model does not support images"}

assistant
→ browser {"action":"inspect"}
toolResult

{"console":[],"dom":"2048\nSCORE\n0\nNew Game\n2\n4","images":"model does not support images"}

assistant
→ browser {"action":"key","text":"ArrowLeft"}
toolResult

{"console":[],"dom":"2048\nSCORE\n0\nNew Game\n2\n4","images":"model does not support images"}

assistant
→ bash {"command":"which node; node -e \"console.log(process.version)\"; ls /app/pipeline/node_modules 2>/dev/null | head -50"}
toolResult

/usr/local/bin/node v24.18.0 @cloudflare @earendil-works @sinclair entities parse5 playwright playwright-core yaml

assistant
→ bash {"command":"ls /app/pipeline/node_modules/playwright-core/.local-browsers 2>/dev/null; ls ~/.cache/ms-playwright 2>/dev/null; find / -maxdep
toolResult

/opt/playwright/chromium_headless_shell-1234 /opt/playwright/chromium-1234 /tmp/playwright_chromiumdev_profile-y38CHa

assistant

Playwright and Chromium are available. Let me write a verification script that drives the real page with keyboard events and differentially checks the game state against the correct 2048 algorithm.

→ write /tmp/job-1yTXAk/verify.js (10914 bytes)
toolResult

Successfully wrote to /tmp/job-1yTXAk/verify.js

assistant
→ bash {"command":"cd /tmp/job-1yTXAk && node verify.js 2>&1 | tail -40","timeout":120}
toolResult

pure tests done node:internal/process/promises:394 triggerUncaughtException(err, true /* fromPromise */); ^ browserType.launch: Failed to launch chromium because executable doesn't exist at /opt/playwright/chromium-1234/chrome-linux/chrome at /tmp/job-1yTXAk/verify.js:83:34 at Object.<anonymous> (/tmp/job-1yTXAk/verify.js:231:3) { log: [], name: 'Error' } Node.js v24.18.0

assistant
→ bash {"command":"find /opt/playwright -maxdepth 4 -type f \\( -name \"chrome\" -o -name \"headless_shell\" -o -name \"chrome-headless-shell\" \\)
toolResult

/opt/playwright/chromium_headless_shell-1234/chrome-headless-shell-linux64/chrome-headless-shell /opt/playwright/chromium-1234/chrome-linux64/chrome --- DEPENDENCIES_VALIDATED INSTALLATION_COMPLETE chrome-linux64

assistant
→ edit {"path":"/tmp/job-1yTXAk/verify.js","edits":[{"oldText":" const browser = await chromium.launch({ executablePath: \"/opt/playwright/chromiu
toolResult

Successfully replaced 1 block(s) in /tmp/job-1yTXAk/verify.js.

assistant
→ bash {"command":"cd /tmp/job-1yTXAk && node verify.js 2>&1 | tail -30","timeout":180}
toolResult

FAIL count +1 iff changed (step 32 dir ArrowUp) FAIL count +1 iff changed (step 33 dir ArrowDown) FAIL count +1 iff changed (step 34 dir ArrowRight) FAIL new tile in empty cell FAIL count +1 iff changed (step 37 dir ArrowUp) FAIL new tile in empty cell FAIL count +1 iff changed (step 43 dir ArrowLeft) FAIL new tile in empty cell FAIL count +1 iff changed (step 44 dir ArrowDown) FAIL count +1 iff changed (step 45 dir ArrowRight) FAIL new tile in empty cell FAIL count +1 iff changed (step 47 dir ArrowLeft) FAIL new tile in empty cell FAIL count +1 iff changed (step 48 dir ArrowDown) FAIL count +1 iff changed (step 49 dir ArrowRight) FAIL new tile in empty cell FAIL count +1 iff changed (step 51 dir ArrowRight) FAIL new tile in empty cell FAIL new tile in empty cell FAIL new tile in empty cell FAIL count +1 iff changed (step 58 dir ArrowUp) FAIL count +1 iff changed (step 59 dir ArrowUp) FAIL new tile in empty cell differential moves done, fails=48 new game count=14 score=0 FAIL new game tiles != 2 overlay buttons: {"cont":true,"winNew":true,"overNew":true,"labels":"\n You win!\n You reached 2048.\n \n Keep going\n New Game\n \n "} arrow key defaultPrevented=false FAIL arrow key not prevented DONE

assistant
→ edit {"path":"/tmp/job-1yTXAk/index.html","edits":[{"oldText":" function restart() {\n while (board.firstChild && board.firstChild.classList
toolResult

Successfully replaced 1 block(s) in /tmp/job-1yTXAk/index.html.

assistant
→ edit {"path":"/tmp/job-1yTXAk/verify.js","edits":[{"oldText":" await page.keyboard.press(dir);\n await page.waitForTimeout(320); // animati
toolResult

Successfully replaced 2 block(s) in /tmp/job-1yTXAk/verify.js.

assistant
→ bash {"command":"cd /tmp/job-1yTXAk && node verify.js 2>&1 | tail -30","timeout":180}
toolResult

FAIL new tile in empty cell FAIL count +1 iff changed (step 36 dir ArrowLeft) FAIL new tile in empty cell FAIL count +1 iff changed (step 37 dir ArrowUp) FAIL new tile in empty cell FAIL count +1 iff changed (step 42 dir ArrowDown) FAIL new tile in empty cell FAIL count +1 iff changed (step 46 dir ArrowDown) FAIL new tile in empty cell FAIL new tile in empty cell FAIL count +1 iff changed (step 48 dir ArrowDown) FAIL count +1 iff changed (step 49 dir ArrowRight) FAIL new tile in empty cell FAIL new tile in empty cell FAIL new tile in empty cell FAIL count +1 iff changed (step 52 dir ArrowLeft) FAIL new tile in empty cell FAIL count +1 iff changed (step 53 dir ArrowLeft) FAIL new tile in empty cell FAIL count +1 iff changed (step 54 dir ArrowDown) FAIL new tile in empty cell FAIL count +1 iff changed (step 56 dir ArrowRight) FAIL new tile in empty cell FAIL count +1 iff changed (step 57 dir ArrowUp) FAIL new tile in empty cell differential moves done, fails=49 new game count=2 score=0 overlay buttons: {"cont":true,"winNew":true,"overNew":true,"labels":"\n You win!\n You reached 2048.\n \n Keep going\n New Game\n \n "} arrow key defaultPrevented=true DONE

assistant
→ write /tmp/job-1yTXAk/debug.js (4622 bytes)
toolResult

Successfully wrote to /tmp/job-1yTXAk/debug.js

assistant
→ bash {"command":"cd /tmp/job-1yTXAk && node debug.js 2>&1 | head -120","timeout":180}
toolResult

=== FAIL at step 2 dir=ArrowDown changed=true merges=1 before tiles: id=1 v=2 cell=2 id=2 v=2 cell=0 id=3 v=2 cell=3 id=4 v=2 cell=11 pred moves: move id=2 from=0 to=12 move id=1 from=2 to=14 move id=4 from=11 to=15 merge id=3 from=3 to=15 into=4 v=4 after tiles (count 4 score 4): id=1 v=2 cell=14 id=2 v=2 cell=12 id=4 v=4 cell=15 id=5 v=2 cell=11 === FAIL at step 5 dir=ArrowUp changed=true merges=1 before tiles: id=1 v=2 cell=14 id=2 v=2 cell=12 id=4 v=4 cell=15 id=5 v=2 cell=11 id=6 v=2 cell=13 id=7 v=2 cell=8 pred moves: move id=7 from=8 to=0 merge id=2 from=12 to=0 into=7 v=4 move id=6 from=13 to=1 move id=1 from=14 to=2 move id=5 from=11 to=3 move id=4 from=15 to=7 after tiles (count 6 score 8): id=1 v=2 cell=2 id=4 v=4 cell=7 id=5 v=2 cell=3 id=6 v=2 cell=1 id=7 v=4 cell=0 id=8 v=4 cell=13 === FAIL at step 7 dir=ArrowLeft changed=true merges=2 before tiles: id=1 v=2 cell=2 id=4 v=4 cell=7 id=5 v=2 cell=3 id=6 v=2 cell=1 id=7 v=4 cell=0 id=8 v=4 cell=5 id=9 v=2 cell=10 pred moves: move id=7 from=0 to=0 move id=6 from=1 to=1 merge id=1 from=2 to=1 into=6 v=4 move id=5 from=3 to=2 move id=8 from=5 to=4 merge id=4 from=7 to=4 into=8 v=8 move id=9 from=10 to=8 after tiles (count 6 score 20): id=5 v=2 cell=2 id=6 v=4 cell=1 id=7 v=4 cell=0 id=8 v=8 cell=4 id=9 v=2 cell=8 id=10 v=2 cell=3 === FAIL at step 8 dir=ArrowLeft changed=true merges=2 before tiles: id=5 v=2 cell=2 id=6 v=4 cell=1 id=7 v=4 cell=0 id=8 v=8 cell=4 id=9 v=2 cell=8 id=10 v=2 cell=3 pred moves: move id=7 from=0 to=0 merge id=6 from=1 to=0 into=7 v=8 move id=5 from=2 to=1 merge id=10 from=3 to=1 into=5 v=4 move id=8 from=4 to=4 move id=9 from=8 to=8 after tiles (count 5 score 32): id=5 v=4 cell=1 id=7 v=8 cell=0 id=8 v=8 cell=4 id=9 v=2 cell=8 id=11 v=2 cell=13 === FAIL at step 10 dir=ArrowRight changed=true merges=1 before tiles: id=5 v=4 cell=1 id=7 v=8 cell=0 id=8 v=8 cell=4 id=9 v=2 cell=8 id=11 v=2 cell=12 id=12 v=2 cell=14 pred moves: move id=5 from=1 to=3 move id=7 from=0 to=2 move id=8 from=4 to=7 move id=9 from=8 to=11 move id=12 from=14 to=15 merge id=11 from=12 to=15 into=12 v=4 after tiles (count 6 score 36): id=5 v=4 cell=3 id=7 v=8 cell=2 id=8 v=8 cell=7 id=9 v=2 cell=11 id=12 v=4 cell=15 id=13 v=2 cell=12 === FAIL at step 11 dir=ArrowDown changed=true merges=0 before tiles: id=5 v=4 cell=3 id=7 v=8 cell=2 id=8 v=8 cell=7 id=9 v=2 cell=11 id=12 v=4 cell=15 id=13 v=2 cell=12 pred moves:

assistant
→ edit {"path":"/tmp/job-1yTXAk/verify.js","edits":[{"oldText":" const after = await snapshot();\n if (after.overVisible) {\n console.lo
toolResult

Successfully replaced 1 block(s) in /tmp/job-1yTXAk/verify.js.

assistant
→ edit {"path":"/tmp/job-1yTXAk/verify.js","edits":[{"oldText":" for (const t of after.tiles) {\n const mv = pred.all.find(m => m.id === t.
toolResult

Successfully replaced 1 block(s) in /tmp/job-1yTXAk/verify.js.

assistant
→ edit {"path":"/tmp/job-1yTXAk/verify.js","edits":[{"oldText":" console.log(\"differential moves done, fails=\" + fails);","newText":" console.l
toolResult

Successfully replaced 1 block(s) in /tmp/job-1yTXAk/verify.js.

assistant
→ bash {"command":"cd /tmp/job-1yTXAk && node verify.js 2>&1 | tail -20","timeout":300}
toolResult

FAIL new tile in empty cell (step 27) FAIL new tile in empty cell (step 29) FAIL new tile in empty cell (step 30) FAIL new tile in empty cell (step 33) FAIL new tile in empty cell (step 36) FAIL new tile in empty cell (step 41) FAIL new tile in empty cell (step 42) FAIL new tile in empty cell (step 45) FAIL new tile in empty cell (step 46) FAIL new tile in empty cell (step 47) FAIL new tile in empty cell (step 49) FAIL new tile in empty cell (step 52) FAIL new tile in empty cell (step 54) FAIL new tile in empty cell (step 57) differential moves done, fails=19 game over reached after spam? true (score 1364, count 16) new game count=2 score=0 overlay buttons: {"cont":true,"winNew":true,"overNew":true,"labels":"\n You win!\n You reached 2048.\n \n Keep going\n New Game\n \n "} arrow key defaultPrevented=true DONE

assistant
→ edit {"path":"/tmp/job-1yTXAk/verify.js","edits":[{"oldText":" const extras = after.tiles.filter(t => !pred.all.find(m => m.id === t.id));\n
toolResult

Successfully replaced 1 block(s) in /tmp/job-1yTXAk/verify.js.

assistant
→ bash {"command":"cd /tmp/job-1yTXAk && node verify.js 2>&1 | tail -12","timeout":300}
toolResult

pure tests done initial count=2 score=0 differential moves done, fails=0 game over reached after spam? true (score 3056, count 16) new game count=2 score=0 overlay buttons: {"cont":true,"winNew":true,"overNew":true,"labels":"\n You win!\n You reached 2048.\n \n Keep going\n New Game\n \n "} arrow key defaultPrevented=true DONE

assistant
→ edit {"path":"/tmp/job-1yTXAk/verify.js","edits":[{"oldText":" // drive until game over (or cap) to verify the game-over state\n let overState
toolResult

Successfully replaced 1 block(s) in /tmp/job-1yTXAk/verify.js.

assistant
→ bash {"command":"cd /tmp/job-1yTXAk && node verify.js 2>&1 | tail -12","timeout":400}
toolResult

pure tests done initial count=2 score=0 differential moves done, fails=0 win overlay exercised? false game over reached after spam? true (score 1572, count 16) new game count=2 score=0 overlay buttons: {"cont":true,"winNew":true,"overNew":true,"labels":"\n You win!\n You reached 2048.\n \n Keep going\n New Game\n \n "} arrow key defaultPrevented=true DONE

assistant
→ bash {"command":"cd /tmp/job-1yTXAk && python3 - <<'EOF'\nsrc = open('index.html').read()\n# make a scratch copy with a lower win threshold + alw
toolResult

/bin/bash: line 1: python3: command not found Command exited with code 127

assistant
→ bash {"command":"cd /tmp/job-1yTXAk && node - <<'EOF'\nconst fs = require('fs');\nlet src = fs.readFileSync('index.html','utf8');\nsrc = src.repl
toolResult

written

assistant
→ write /tmp/job-1yTXAk/verify-win.js (2710 bytes)
toolResult

Successfully wrote to /tmp/job-1yTXAk/verify-win.js

assistant
→ bash {"command":"cd /tmp/job-1yTXAk && node verify-win.js 2>&1 | tail -10","timeout":240}
toolResult

' - waiting 100ms', ' 58 × waiting for element to be visible, enabled and stable', ' - element is not visible', ' - retrying click action', ' - waiting 500ms' ], name: 'TimeoutError' } Node.js v24.18.0

assistant
→ edit {"path":"/tmp/job-1yTXAk/verify-win.js","edits":[{"oldText":" console.log(\"win visible=\" + s.winVisible + \" over=\" + s.overVisible + \"
toolResult

Successfully replaced 1 block(s) in /tmp/job-1yTXAk/verify-win.js.

assistant
→ bash {"command":"cd /tmp/job-1yTXAk && node verify-win.js 2>&1 | tail -8","timeout":240}
toolResult

' - element is not visible', ' - retrying click action', ' - waiting 500ms' ], name: 'TimeoutError' } Node.js v24.18.0

assistant
→ bash {"command":"cd /tmp/job-1yTXAk && node verify-win.js 2>&1 | head -40","timeout":240}
toolResult

win visible=true over=false text="You win!" maxTile=32 score=136 after continue: winVisible=false post-continue move: count 7 -> 7, score 136 -> 144 node:internal/process/promises:394 triggerUncaughtException(err, true /* fromPromise */); ^ page.click: Timeout 30000ms exceeded. Call log: - waiting for locator('#win-new-btn') - locator resolved to <button id="win-new-btn">New Game</button> - attempting click action 2 × waiting for element to be visible, enabled and stable - element is not visible - retrying click action - waiting 20ms 2 × waiting for element to be visible, enabled and stable - element is not visible - retrying click action - waiting 100ms 58 × waiting for element to be visible, enabled and stable - element is not visible - retrying click action - waiting 500ms at /tmp/job-1yTXAk/verify-win.js:39:16 { log: [ " - waiting for locator('#win-new-btn')", ' - locator resolved to <button id="win-new-btn">New Game</button>', ' - attempting click action', ' 2 × waiting for element to be visible, enabled and stable', ' - element is not visible', ' - retrying click action', ' - waiting 20ms', ' 2 × waiting for element to be visible, enabled and stable', ' - element is not visible', ' - retrying click action', ' - waiting 100ms', ' 58 × waiting for element to be visible, enabled and stable', ' - element is not visible',

assistant
→ edit {"path":"/tmp/job-1yTXAk/verify-win.js","edits":[{"oldText":" if (after.tiles.length === before.tiles.length && after.score === before.sc
toolResult

Successfully replaced 1 block(s) in /tmp/job-1yTXAk/verify-win.js.

assistant
→ bash {"command":"cd /tmp/job-1yTXAk && node verify-win.js 2>&1 | head -12 && echo === && node verify.js 2>&1 | tail -8","timeout":400}
toolResult

win visible=true over=false text="You win!" maxTile=32 score=112 after continue: winVisible=false post-continue move: count 6 -> 6, score 112 -> 128 New Game: count=2 score=0 winVisible=false overVisible=false WIN TEST DONE === initial count=2 score=0 differential moves done, fails=0 win overlay exercised? false game over reached after spam? true (score 1324, count 16) new game count=2 score=0 overlay buttons: {"cont":true,"winNew":true,"overNew":true,"labels":"\n You win!\n You reached 2048.\n \n Keep going\n New Game\n \n "} arrow key defaultPrevented=true DONE

assistant
→ edit {"path":"/tmp/job-1yTXAk/index.html","edits":[{"oldText":" <div class=\"board\" id=\"board\">\n <script type=\"text/plain\" id=\"cells-t
toolResult

Successfully replaced 1 block(s) in /tmp/job-1yTXAk/index.html.

assistant
→ bash {"command":"cd /tmp/job-1yTXAk && rm -f verify.js debug.js verify-win.js /tmp/win-test.html && ls -la"}
toolResult

total 24 drwx------ 3 node node 4096 Oct 23 10:01 . drwxrwxrwt 16 root root 4096 Oct 23 10:01 .. drwxr-xr-x 6 node node 4096 Oct 23 09:54 .home -rw-r--r-- 1 node node 11656 Oct 23 10:01 index.html

assistant
→ browser {"action":"reload"}
toolResult

{"console":[],"dom":"2048\nSCORE\n0\nNew Game\n2\n2","images":"model does not support images"}

assistant
→ browser {"action":"key","text":"ArrowLeft"}
toolResult

{"console":[],"dom":"2048\nSCORE\n0\nNew Game\n2\n2","images":"model does not support images"}

assistant
→ browser {"action":"key","text":"ArrowLeft"}
toolResult

{"console":[],"dom":"2048\nSCORE\n0\nNew Game\n2\n2\n2","images":"model does not support images"}

assistant
→ browser {"action":"click","x":955,"y":44}
toolResult

{"console":[],"dom":"2048\nSCORE\n0\nNew Game\n2\n2\n2\n2","images":"model does not support images"}

assistant
→ bash {"command":"cd /tmp/job-1yTXAk && node - <<'EOF'\nconst { chromium } = require(\"/app/pipeline/node_modules/playwright\");\nconst { pathToFi
toolResult

initial [{"id":1,"v":2,"left":130,"top":130},{"id":2,"v":4,"left":14,"top":362}] after L1 [{"id":1,"v":2,"left":14,"top":130},{"id":2,"v":4,"left":14,"top":362},{"id":3,"v":2,"left":246,"top":14}] after L2 [{"id":1,"v":2,"left":14,"top":130},{"id":2,"v":4,"left":14,"top":362},{"id":3,"v":2,"left":14,"top":14},{"id":4,"v":2,"left":246,"top":14}] after click 955,44 [{"id":1,"v":2,"left":14,"top":130},{"id":2,"v":4,"left":14,"top":362},{"id":3,"v":2,"left":14,"top":14},{"id":4,"v":2,"left":246,"top":14}] new-game button box {"x":736.40625,"y":43,"w":103.59375,"h":36} after clicking New Game [{"id":1,"v":2,"left":130,"top":246},{"id":2,"v":2,"left":130,"top":14}]

assistant

All checks pass. Final state of the working directory: ``` /tmp/job-1yTXAk/index.html (single self-contained file, no external resources) ``` ## What I built A single-file 2048 clone in `index.html`: - **Board**: 4×4 grid of empty cells rendered in CSS, tiles absolutely positioned (cell size/gap computed in JS) with `left/top` transitions for slide animation, plus scale keyframes for new-tile `appear` and merge `pop`. - **Move logic**: computes moves per line in traversal order; adjacent equal tiles merge once per tile per move (so `[2,2,2,2]`→`[4,4,0,0]` and `[2,2,4,0]`→`[4,4,0,0]`). Score increases by the value of each merged tile. - **Spawning**: starts with 2 tiles; after any board-changing move spawns exactly one tile (2 with 90%, else 4); no spawn on unchanged moves. - **Input safety**: arrow keys `preventDefault()` (no page scroll); `busy` lock blocks input during the ~180 ms animation so rapid keys can't corrupt state. - **UI**: score box, New Game button (plus `R` key), win overlay at 2048 with "Keep going" (dismisses, play continues) and "New Game", Game Over overlay only when no valid move remains. ## What I actually checked (Playwright/Chromium against the real file) - **Pure logic unit tests**: the exact spec examples (`2222`→two merges not one, `2240`→`[4,4,0,0]`, `0202`→`[4,0,0,0]`), unchanged moves detected (`2420`, `2000` left), edge movement (`2000` right → col 3). - **60-move differential test**: played 60 random arrow-key moves; after each, compared the live DOM (tile positions, values, count, score) against a replica of the reference algorithm — all matched: correct slide targets, merge values, score deltas, and exactly one new tile (2 or 4, in a post-move empty cell) iff the board changed. - **Game over**: reached via scripted play; overlay appeared with correct text and blocked further moves. - **New Game**: resets to 2 tiles, score 0, overlays hidden (verified via button click). - **Win flow**: exercised on a scratch copy of the identical engine (win threshold lowered to 32, spawns forced to 4) — overlay appeared with "You win!", "Keep going" dismissed it and play resumed (score kept climbing), New Game reset. - **Arrow-key scroll prevention**: handler verifiably calls `preventDefault()`; console had no errors throughout. ## Unverified / noted - Visual animation smoothness and exact colors weren't visually inspected (the browser tool's image output isn't supported in this environment); animation logic was verified via settled-state snapshots after each move. - Reaching a genuine 2048 win through natural play wasn't reproducible in the time budget; the win path was validated through the identical-engine scratch copy described above.