TypeSafe Jev · chess experiment

I asked Jev to choose chess moves

I tested Jev on 104 chess positions and used Stockfish 19 to grade its moves, then put it beside Maia — networks trained to play like humans at a given rating, which like Jev answer without calculating ahead. A 1100-rated beginner network beat it on every measure, a hundred times faster. Jev is clearly playing chess and not guessing, and its shortlist of candidate moves carries real signal. But four ways of asking and three ways of showing it the position did not change the answer: on its own, it does not play well.

What I tested

Jev received the position and its legal moves, and returned one move, a probability for each option, and a confidence score. It never saw Stockfish's evaluation. Stockfish graded the move afterwards at depth 18, keeping five lines.

One of the 104 positions has a single legal move, which the code plays without asking anyone. Every model number below is over the other 103 decisions.

These positions are this project's own test fixtures, hand-picked to cover openings, tactics, endgames and edge cases. They are not a random sample of chess, some of them are related, and each policy was run once.

How the moves scored

Top-1 means the move matched Stockfish's first choice. A different move can still be fine, so the more useful column is how much evaluation the move gave up: 100 centipawns is one pawn. A blunder here means losing 200 centipawns or more against the oracle's best.

Who is competing

Six ways of choosing a move. The names in brackets are what the raw result files call them.

Random moves
Picks any legal move at random. The floor: anything useful has to beat this. random
Jev
The model is shown the position and every legal move, and picks one. No search at all — it never looks at what happens next. jev
Jev's top 5 + Stockfish
Two steps. First Jev narrows roughly 35 legal moves down to its 5 best guesses. Then Stockfish looks ahead, but only at those 5, and plays whichever it likes best. The other 30 are never examined. hybrid-jev-top5-engine
Random 5 + Stockfish
The same thing, except the 5 moves are drawn at random instead of chosen by Jev. This is the control: comparing it with the row above shows how much of the result comes from Jev's ranking rather than from the search. hybrid-random-top5-engine
All moves + Stockfish
No shortlist. Stockfish looks ahead at every legal move, to the same depth. This is the best that search budget can do, so it is the ceiling the two rows above are trying to reach. hybrid-random-top300-engine
Jev's own search
The model does the pruning at every ply, not just the first. At each position in the tree it names its 5 best guesses, and only those are explored, so a 3-ply search looks at about 125 lines instead of tens of thousands. No engine is involved anywhere — the final positions are scored by counting material. This is the model playing on its own. jev-tree-d3-k5
Maia 1100 / 1500 / 1900
Neural networks trained to predict what a Lichess player at that rating would play. Run with search switched off, so like Jev they look at the position and answer — no calculating ahead. They are the fairest comparison on this page, because they are the same kind of thing. maia-1500
Stockfish, 10 ms
Ordinary Stockfish given ten milliseconds per move and no help. Deliberately hobbled so it makes a fair opponent — but it is still a real chess engine. stockfish-weak

Move-matching accuracy

How often the chosen move was among Stockfish's top 1, 3 and 5 lines.

What a move gave up

Centipawn loss, median and 90th percentile. Lower is better; the axis is logarithmic because the tail spans three orders of magnitude.

What search added — and what pruning cost

The hybrid keeps Jev's top K moves and lets Stockfish search only those. To find out what Jev's ranking actually contributes, I ran two controls at the identical search budget: the same K drawn at random, and a search over every legal move.

Jev's shortlist against a random shortlist

Same search, same depth, same K. The only difference is who picked the candidates.

Two ways to put a policy inside a search

Everything above prunes only the first move. There is another thing you can build, and it is what the project set out to reach.

Prune the root

Jev is asked once. It narrows the first move to 5 candidates, and from then on the search expands every legal move as usual. Jev shapes one decision; Stockfish does the rest.

Generate the tree

Jev is asked at every position in the tree. Only its 5 guesses are explored at each ply, so the tree stays narrow all the way down. Final positions are scored by counting material — no engine anywhere.

Positions examined, 3 plies deepply 1ply 2ply 3
No pruning at all39~28~30,600
Prune the root only5~28~3,900
Jev prunes every ply55125

Counted on the Kasparov position below, which has 39 legal moves. The cost moves from search time to requests: one call per position examined.

Could the model judge the positions too?

Counting material at the leaves is the one piece of chess knowledge in that loop that is mine, not the model's. The obvious next step is to hand that job over as well — so I measured whether the model's position score is good enough to search on.

One position where depth decides

What each setting chose

Stockfish searching every legal move, against the hybrid searching only Jev's shortlist, at the same depth. Green is the move Kasparov played.

Was it just being shown the wrong thing?

Four reformulations of the question had failed by this point, and none of them had changed what the model actually sees. It gets a FEN — r1bqkbnr/pppp1ppp/... — and has to rebuild the board in its head before it can reason. The weakness is specifically positional, and position is exactly what a FEN hides.

Against a human-shaped opponent

Everything above compares Jev with Stockfish, which is not really a fair frame: Stockfish answers "what is best" by calculating, and Jev is being asked something closer to "what would a good player do". Maia answers that second question directly — networks trained on Lichess games at a given rating, run with search switched off. Same kind of object as Jev: look at the position, name a move.

And in actual games

The benchmark scores single moves in hand-picked positions. A game is different: mistakes compound, and the positions you reach are consequences of your own earlier moves.

Which kind of player does each one resemble?

How often each policy picked the same move as each human network. Not who is better — who plays alike.

Four tries, one pattern

I went looking for a signal I could prune or search on, and came back empty four times. But the four failures were not random, and lining them up says something the individual results do not.

What confidence showed

Confidence is a statistic TypeSafe derives from the concentration of the option distribution. It is not a stated probability of agreeing with Stockfish, so what follows is the association between the two on this dataset — not a measure of the model's self-knowledge.

Confidence and move quality

Each point is a confidence bucket. Vertical position is how often the move matched the engine's first choice; bubble area is how many decisions landed there. The dashed line is where the two would coincide exactly.

Where the decisions are

Almost everything sits in the lowest bucket. Any rule based on high confidence would fire very rarely here.

Where it held and where it broke

The same run sliced by position type. The categories come from deterministic features this project computes, never from the engine. Several slices are small — the count is on every row.

What happened in full games

Ten games per matchup, colours alternating, adjudicated a draw after 160 plies. The hybrid searched at the same depth it was benchmarked at.

Read the Stockfish rows for what they are worth, which is not much. Losing every game to Stockfish places nothing: practically every human alive loses every game to Stockfish, and so do most engines. It tells you the opponent is strong, not how strong the policy is. The row that carries information is the one against a rated human network — that is a scale with people on it.

Ten games cannot support a rating. The Elo figures are an exploratory estimate from the match score, shown with the interval they actually imply — which is wide enough to make the point. They are differences against one opponent, not ratings on any scale. A shutout has no finite estimate at all.

Does the blunder rate explain the losses?

A projection, not a measurement: it assumes mistakes are independent across a game, which they are not.

The games themselves

Every game played in these matches, move by move, exactly as recorded in the PGN.

What this does not show

  • These are the project's own test fixtures, chosen by hand to cover particular cases. They are not a random sample, and some are variations of one another.
  • One run per configuration, and ten games per matchup. Nothing here is repeated enough to separate a real effect from noise at the sizes involved.
  • Stockfish is the reference, not the truth. Its verdict depends on its own search settings.
  • Centipawn loss is capped at 2,000, and mates are mapped to finite scores before the loss is computed, so the tail is compressed by construction.
  • The 100% legality rate checks the integration, not the model: the options offered to Jev are the legal moves, so an illegal answer is not representable.
  • Costs are estimated from reported tokens at the published rate, not read off a bill. The figure in the header covers the Jev position benchmark only.
  • One model version, one prompt wording per experiment, and three input representations. Four reformulations and three representations all landed in the same place, which is why the conclusion is stated plainly — but that is a consistent result on one dataset, not a general claim about these models.
  • Maia predicts the typical move of a rating band rather than the best one, and its authors note the networks play stronger than their nominal rating. "Matches maia-1500" means plays like a 1500, not is as strong as one.
  • Elo is an exploratory estimate from ten games. The compounding table is a projection, not a measurement.

How to check any of this

Every number on this page comes from a file written by a run. Nothing is recomputed here that an experiment did not already measure.

In the repository, each run leaves results/<run>/ holding config.json (git commit, seed, dataset hash, engine and model settings), summary.json (every metric above) and runs.csv (one row per position, with the confidence, the loss, and whether the policy was actually consulted). Matches also write games.pgn. The dataset is tests/fixtures/positions.json, and scripts/build_dashboard.py rebuilds this page from those files.