localhost); on Linux Chrome also enable
chrome://flags/#enable-unsafe-webgpu and #enable-vulkan, then relaunch.
PixelChess never really sees "a board" — it sees a picture, once, in a single forward pass. No search, no rules, no piece values are handed to it. Yet to match a real engine's evaluation, the network is forced to internalize proxies for everything that number depends on: material (learning, from pixels alone, that a star-shape ≈ a queen and is worth far more than a circle), position (king exposure, space, pawn structure, activity — the diffuse features that move an evaluation without changing material), and even faint tactical signals (an undefended piece, an overloaded square) that correlate with the label. None of it is programmed — it's inferred from images and their evaluations. The model builds its own blurry notion of chess value the way a strong player builds intuition: by recognizing patterns, not by calculating.
And that's exactly where it — and the analogy — breaks down. This network and human intuition share the same strength (a fast, holistic read of a position) and the same weakness: intuition estimates, it doesn't calculate. It can feel that a position "looks better for White" and still miss that a queen hangs to a two-move sequence. That gap is why the CNN is wrapped in a search: the network supplies the glance, the minimax + quiescence layer supplies the calculation the picture alone can't. The rest of this page is how that glance was built.
PixelChess is a chess engine whose evaluation function is a convolutional neural network that only ever sees a picture of the board — never a list of pieces or a bitboard. Below: how a pure vision model was turned into something that actually plays chess, and how the whole thing runs entirely in your browser.
Modern chess AIs (AlphaZero, Leela) feed the network a symbolic board — stacks of 8×8 planes, one per piece type. PixelChess deliberately does the opposite: the model receives a rendered image of the position and outputs an evaluation. Everything else is built around that constraint.
Hundreds of thousands of legal positions were generated two ways — random self-play plies, and directly constructed random legal positions — and stratified by material (sparse ≤10 pieces / medium / dense) so the model isn't biased toward opening-like clutter.
Each position was labeled with Stockfish's evaluation (centipawns from White's point of view, clamped to ±800) and grouped into 9 ordered evaluation buckets, from "Black winning" to "White winning".
Positions are drawn as a legible, non-photorealistic image (shown live on the Play tab as "what the model sees"): a neutral board with a grid, each piece a geometric shape + its letter. Side is encoded three redundant ways for the CNN — solid blue = White, hollow pink outline = Black, plus a side-to-move marker; kings and queens get a halo. These are legibility cues (which piece, whose piece), never hints about the evaluation.
A small ResNet-style CNN: a 3-channel stem plus three residual stages (64→128→256 channels) and a global average pool, ~1–2M parameters. A larger ~12M ResNet-18 variant ("BigNet") was also trained. Input is the 320×320 render; the output is the position's evaluation, and summing the ordinal sigmoids then interpolating the bucket centers gives a continuous centipawn value.
How you frame "predict the evaluation" changed the results significantly. Four heads were compared on identical data:
The ordinal formulation won because it encodes that buckets are ordered. That directly attacked the most damaging error — getting the sign wrong (calling White better when Black is winning): the sign-error rate fell from ~0.20 (hard classification) to ~0.17, and to ~0.15 at higher resolution.
An instructive failure: training without augmentation on a data subset made the network memorize and collapse — training loss dived toward zero while validation accuracy fell to chance (~0.11 over 9 classes). A broken augmentation config (a degenerate random-crop with zero scale) reproduced the exact same collapse. Light augmentation turned out to be necessary purely as a regularizer, forcing the model to read the position instead of memorizing individual renders.
Trained with Adam, dropout, optional label smoothing, gradient clipping, and early stopping on a plateau of the validation metric. A subtlety in reading the curves: the loss flattens well before accuracy stops improving, so patience was tracked on validation "within ±1 bucket" accuracy, not on the loss. The regression head was unstable at first (MAE blowing up past ~1250 cp) until it was tanh-bounded to ±800 with gradient clipping and a lower learning rate, after which it settled around ~230 cp.
Two levers reliably moved the needle: more diverse data (material-stratified positions instead of opening-heavy self-play) and resolution (320px clearly beat 160px). Capacity helped too — the ~12M BigNet reached the best "within ±1 bucket" accuracy (≈0.66) versus ≈0.63 for the small net.
On held-out test positions the deployed model gets the exact bucket right about a third of the time (~33%), lands within ±1 bucket ~64% of the time, and gets the sign wrong on ~16% of positions. For a network judging chess purely from a picture, "within one bucket two times out of three" is the level that lets the search produce reasonable — if imperfect — play.
Training ran on an AMD Radeon RDNA3 iGPU via ROCm — strictly no CUDA/NVIDIA. It came with caveats: mixed-precision (fp16) crashed because that GPU lacks the required fp16 kernels, so training stayed in fp32; under sustained load the GPU would occasionally hang outright, so many runs fell back to CPU, with the larger models trained on cloud GPUs (Colab T4 / A100).
Each card below is a real image from the training set (exactly what the model saw), its Stockfish label, and what the model now predicts for that same image. Notice it nails clear and balanced positions, drifts by a bucket on others, and can badly under- or over-shoot in sharp ones — the visual, material-blind nature discussed in Honest limitations below.
The cards above are anecdotes. Here is the model scored on 2,700 held-out test positions — a balanced 300 per bucket it never saw in training. It sorts each board image into one of 9 evaluation buckets, from crushing-for-Black to crushing-for-White.
Rows are the true bucket (Stockfish), columns the model's prediction. Each row holds 300 positions; a cell's shade is that row's share, so a bright diagonal means "right" and a bright off-diagonal means "confused with a neighbouring bucket".
Confident at the extremes, fuzzy in the middle — a U-shaped curve.
When one side is clearly winning (the far ends) the picture alone is enough — the model hits ≈50% exactly and lands within one bucket ~75–78% of the time. In roughly balanced middlegames (buckets 5–7) exact accuracy drops to 13–20%: those positions look alike and the difference is tactical, not visual. That's exactly the human-intuition failure mode — a fast glance reads who's better far more reliably than by how much — and why the glance is wrapped in a search.
The CNN only scores a single static picture. To play, it's wrapped in a classic search:
Every candidate position is rendered, evaluated by the CNN, and the best line is backed up — that's what the ply tabs and the PV on the Play tab show.
The PyTorch model was exported to ONNX and runs with onnxruntime-web — on the CPU (WASM) or, when available, on the GPU (WebGPU). The search was ported to JavaScript, with chess.js handling move legality. No server does any thinking: the model and engine download once and run on your device.
Because it judges a picture and never counts material symbolically, the model has real blind spots — in sharp positions it can misjudge material or even the sign (e.g. believe a side is fine right after it dropped a queen). Search depth and quiescence compensate partially; the ceiling is the model's visual eval. PixelChess is a proof of concept that a vision-only evaluator can drive a genuinely playable engine — not a Stockfish rival.
onnxruntime-web · model chess.js · rules sprite compositor · bit-exact render JS minimax + α-β + quiescence · search PyTorch → ONNX · training Stockfish · labels