How 2048 decides the next tile — and why four twos don't make eight
The game's reputation for cruelty comes from two decisions in one file: what value appears, and how far a merge is allowed to chain. Both were read from the original source at a pinned commit. The harness rebuilds the behaviour from the rule alone.
Independent guide. We read published source and rebuilt the rule in our own code. Not affiliated with the project's authors. Nothing here is a score-protection tool or a bot.
Findings
Read on 18 Oct 2026 from js/game_manager.js and js/grid.js at commit 478b6ec346e3787f589e4af751378d06ded4cbbc.
| Behaviour | What the source says | Confidence | What it does not prove |
|---|---|---|---|
| Next tile is a 2 nine times out of ten | var value = Math.random() < 0.9 ? 2 : 4; inside addRandomTile | High — one line, no config | Does not apply to the two starting tiles, which are placed by a separate loop |
| Starting board has exactly 2 tiles | this.startTiles = 2; then a loop of that many calls | High | — |
| Position is uniform over empty cells only | cells[Math.floor(Math.random() * cells.length)] from availableCells() | High | No weighting toward centre or corners |
| A blocked swipe spawns nothing | The spawn call sits inside if (moved), after the tile loop | High | — |
| One merge per tile per move | Guard is tile.value === next.value && !next.mergedFrom | High | That the same guard exists in every later fork |
| Score gain equals the resulting tile | self.score += merged.value; where the merged value is the sum | High | — |
| Win flag at 2048, game continues after it | if (merged.value === 2048) self.won = true; plus a keepPlaying flag | High | — |
| Over only when full and no adjacent pair | cellsAvailable || tileMatchesAvailable, scanning right and down neighbours | High | — |
| Merge order runs toward the swipe | Directions are 0 up, 1 right, 2 down, 3 left; traversal is reversed for right and down | High | — |
| Board size comes from a data attribute, default 4 | size = parseInt(gridElement.dataset.size, 10) || 4; | High | Whether any released build shipped a non-4 board |
| Best score is kept client-side only | A StorageManager reads and writes local state; the game is restored from it on load | Medium | Not confirmed by this run: the storage key names, and whether the saved state includes the score and the won flag. Open your own localStorage to check. |
The counter-intuitive one. [2,2,2,2] swiped left gives [4,4], score +8 — not [8]. The merge guard marks a product as spent for this traversal. A chain that would double twice in one move does not exist.
The rule, in pseudocode
on swipe(dir):
moved = false
for cell in traversal(dir):
if cell has tile t:
n = first neighbour ahead of t
if n is empty:
slide t into n; moved = true
else if n.value == t.value and n is not flagged merged:
m = new tile(n.pos, t.value + n.value); m.merged = true
replace n with m; t.clear()
score += m.value
if m.value == 2048: won = true
moved = true
if moved and any cell empty:
v = (random() < 0.9) ? 2 : 4
place v in a uniformly chosen empty cell
if no empty cell and no adjacent equal pair: over = trueIs it really 90/10?
The simulation uses only the ternary. If the share of fours moves toward 10% as N grows, the line is what we claim.
press RunThe merge guard, live
Edit the row, then swipe left. With guard is the source's rule: a tile merges once. Without guard is the cascade people expect, where [2, 2, 2, 2] becomes 8.
press SwipeTry it yourself
Inspect the 2048 repository I have checked out. Do not summarise the
game. Find the one place where a new tile's value is chosen, and quote
that line. Then tell me which behaviours are decided by that line
alone, and which ones I would have to read elsewhere. Name the file and
line for every claim, and say what you could not determine.Pinning the commit matters. This repository's master is stable, but the Chrome dino repository used on the jump walkthrough has moved and its master path now returns 404. A repository name is not a citation. New to this? Set up REA first.
What we could not confirm. The spawn line uses the platform RNG, so the sequence is not reproducible without overriding Math.random — we did not verify whether any build seeds it. We also did not check the two starting tiles against a long sample, so "20% of games open with a 4 somewhere" is arithmetic, not measurement.