click anywhere to close
How it works
Every frame you see is a real prime number. The digits are arranged in a grid
and colored so they recreate the game image — but the underlying number is (probably) prime.
You can select and copy it.
1. Capture & downsample
Each frame from the DOOM engine (running in WebAssembly) is captured and downsampled
to a small grid — e.g. 40 columns by 30 rows = 1,200 cells. Each cell gets the average
color of the source pixels it covers.
2. Color quantization (k-means)
We have 10 digits (0–9) and need 10 colors. An adaptive k-means algorithm clusters
the grid's colors into 10 groups, sorted by luminance: digit 0 gets the darkest color,
digit 9 the brightest. Each cell is assigned the digit of its nearest cluster.
Frame pixels → downsample → k-means(k=10) → digit grid
8855533111
8865431111
8888654321 ← each digit = a color
The palette is adaptive per frame — colors shift naturally with the scene.
To avoid flicker, each frame's k-means is seeded with the previous frame's centroids
(warm start, 2 iterations instead of 5).
3. Find a prime
The 1,200-digit string isn't prime yet. We fix the first digit to be nonzero and the
last digit to 1 (so the number is odd and not divisible by 5), then search
for a prime by varying only the last few "free" digits.
8855533111...████1
↑↑↑↑↑
free digits — try 0000 → 0001 → ... until prime
A sieve over the first 168 primes (up to 997) eliminates ~92% of candidates
cheaply. The survivors are tested with the Miller-Rabin probabilistic primality
test. Only the tail digits change — the image stays visually identical.
4. Miller-Rabin & the primality slider
Miller-Rabin is a probabilistic test: each round has at most a 25% chance of
incorrectly calling a composite "prime." Multiple rounds compound:
- 1 round — ≤25% false positive rate (fast)
- 2 rounds — ≤6.25%
- 3 rounds — ≤1.6%
- 5 rounds — ≤0.1% (effectively certain)
- 10 rounds — ≤0.0001%
The Primality slider lets you trade speed for certainty in real time.
5. Optimizations for real-time
- Frame skip — if the grid changed less than 1.5% from the previous
frame, the cached result is reused instantly (shown as
[cached] in the stats bar).
- Prime reuse — if less than 5% of digits changed, the previous prime is
kept, skipping the expensive Miller-Rabin search entirely.
- Palette seeding — warm-starting k-means from the previous frame eliminates
random initialization and stabilizes colors.
- Auto-size — the grid resolution adapts up or down to maintain ~20 fps.
6. The stack
DOOM — doomgeneric C engine compiled to WASM via Emscripten + SDL2.
Prime engine — Rust compiled to WASM via wasm-pack.
Uses num-bigint for arbitrary-precision arithmetic.
Frontend — vanilla JS. Digits are real DOM <span> elements
(not canvas), so you can select and copy them.