Critical Review

Critical Review — 2048

  • Reviewed: 2026-08-14T20:19:13.523187+00:00
  • Reviewer model: qwen/qwen3.7-max (temperature 0, independent of builder)
  • Builder model: deepseek-v4-flash
  • Objective verification: 17/17 checks passed

Process & bias controls

This review was produced by an independent frontier model in a sandbox.

Controls: blind context (no builder claims), deterministic rubric,

evidence-required scoring, independent factual verification by the

harness, and full transparency (prompt + raw output archived in review.json).

Scores

  • correctness: 8/10
  • fidelity: 9/10
  • twist: 7/10
  • ux: 7/10
  • mobile: 7/10
  • accessibility: 4/10
  • performance: 9/10
  • security: 8/10
  • docs: 9/10
  • originality: 6/10

Verdict: needs-work

### Summary

A faithful 2048 clone with a working combo bonus twist and solid engineering (SEO, icons, leaderboard). Core merge/scoring logic is correct and the 90/10 spawn ratio matches the original. Main weaknesses are accessibility (no keyboard button activation, no reduced-motion, no grid ARIA), modest gameplay impact from the combo twist, and a single CSS breakpoint that leaves tablets poorly served.

### Issues

  • [major] (accessibility) Buttons use <a role='button'> but bindButtonPress only attaches click+touchend listeners — Enter/Space keys cannot activate New Game, Retry, or Keep Playing buttons, blocking keyboard-only users. — keyboard_input_manager.js:107-110 — bindButtonPress adds only 'click' and touchend; no keydown handler for Enter (13) or Space (32).
  • [major] (accessibility) No prefers-reduced-motion media query — all tile animations (appear, pop, move-up, combo-float) play unconditionally, violating WCAG 2.3.3. — style/main.css — no @media (prefers-reduced-motion: reduce) block anywhere in the file.
  • [major] (accessibility) Game grid and tiles have no ARIA roles or labels. Screen reader users cannot perceive board state, tile positions, or move results. — index.html:127-147 — grid-container and tile-container are plain divs with no role, aria-label, or aria-live region for tile changes.
  • [minor] (correctness) comboCount accumulates streak bonus but resets to 0 on any non-combo move. The stats bar label 'Combo bonus' implies a cumulative total, which will confuse players who see it drop to zero. — game_manager.js:176-183 — comboCount += bonus on combo, comboCount = 0 on non-combo; index.html:119 labels it 'Combo bonus'.
  • [minor] (correctness) Dead CSS: score-addition has 'color: red' immediately overridden by 'color: rgba(119, 110, 101, 0.9)' on the next declaration — the red is never visible. — style/main.css — .score-container .score-addition block has 'color: red' then 'color: rgba(119, 110, 101, 0.9)'.
  • [minor] (mobile) Only one responsive breakpoint at 520px. Tablets (768px-1024px) get the fixed 500px desktop layout which may overflow on smaller tablets in portrait or leave large margins. — style/main.css — single @media screen and (max-width: 520px); .container is width: 500px with no fluid scaling.
  • [minor] (twist) Combo bonus of (merges-1)*50 is small relative to merge values (e.g., two 512-tile merges yield 1024 points but only 50 combo bonus). The twist rarely changes strategic decisions. — game_manager.js:174 — var bonus = (merges - 1) * 50. A typical late-game merge of two 256s scores 512, dwarfing the 50-point combo bonus.
  • [minor] (ux) No first-play tutorial or hint explaining the combo mechanic. New players see 'Combo!' popups without understanding what triggers them. — index.html — no tutorial overlay, tooltip, or contextual help for the combo feature. The game-explanation section only says 'Join the numbers and get to the 2048 tile!'
  • [minor] (security) Client-side HTML sanitization in x-embed.js strips known dangerous tags/attributes but uses a blocklist approach that may miss novel injection vectors (e.g., SVG-based XSS, mutation XSS via malformed HTML). — js/x-embed.js:17-32 — sanitize() removes specific tags and event handler attributes via blocklist, but does not use a DOMPurify-level allowlist approach.

### Strengths

  • Core 2048 logic is a faithful, correct reimplementation — merge rules, traversal order, spawn ratio, and win/lose detection all match the original.
  • Thorough attribution and licensing: dual copyright in LICENSE, source file comments credit Gabriele Cirulli, HTML footer links to the original repo.
  • Deterministic SVG icon master (meta/icon-master.html) rasterized at multiple sizes from a single vector source — a strong engineering practice for reproducibility.
  • Clean separation of concerns: GameManager, Grid, Tile, HTMLActuator, InputManager, and StorageManager follow the original's well-structured module pattern.
  • XSS mitigation in oEmbed rendering: x-embed.js sanitizes server-returned HTML before injection, stripping scripts, iframes, event handlers, and javascript: URLs.
  • Game state persistence with corruption recovery: LocalStorageManager.getGameState wraps JSON.parse in try/catch and clears corrupted state instead of crashing.

### Recommendations

  • Add keyboard Enter/Space handling for all role='button' elements — bindButtonPress should listen for keydown events with keyCode 13 and 32.
  • Add @media (prefers-reduced-motion: reduce) to disable or shorten all CSS animations (appear, pop, move-up, combo-float, fade-in).
  • Add ARIA attributes to the game grid: role='grid' on grid-container, role='gridcell' on cells, and an aria-live region that announces tile changes after each move.
  • Increase the combo bonus scaling or make it multiplicative (e.g., score multiplier) so the twist meaningfully affects strategy at higher tile values.
  • Add a second responsive breakpoint around 768px for tablets, or switch to fluid/vw-based sizing instead of fixed pixel widths.
  • Clarify the 'Combo bonus' stat: either make it cumulative across the entire game (never reset) or rename the label to 'Combo streak' to match the reset behavior.

### Objective verification (harness)

  • ✅ file:index.html
  • ✅ file:js/game_manager.js
  • ✅ file:js/grid.js
  • ✅ file:js/tile.js
  • ✅ file:js/html_actuator.js
  • ✅ file:js/keyboard_input_manager.js
  • ✅ file:js/local_storage_manager.js
  • ✅ file:js/application.js
  • ✅ file:js/leaderboard.js
  • ✅ file:style/main.css
  • ✅ file:LICENSE
  • ✅ license:MIT
  • ✅ tests:pass 14 pass, 0 fail
  • ✅ selfcontained
  • ✅ viewport
  • ✅ x-share
  • ✅ leaderboard