Synthetic Data for Chess Explanation: Building Human-Relevant Reasoning Traces

1 Sep 2026

Introduction and Initial Approach

I’m currently working on training an LLM that explains the key ideas in a chess position. In my previous post, I surveyed the literature on distilling chess explanations from a frontier model into a smaller one, and concluded that distillation was ill-suited for chess reasoning. The more important realization came from staring at the teacher’s outputs along the way.

Over the years, frontier LLMs have gotten decent at playing moves. However, as a chess learner, I care more about the explanations and rationales behind the moves, which are often wrong: they mislabel pieces on the board, highlight irrelevant ideas, and worst of all, provide no insightful analysis.

A chess position shown beside an LLM analysis panel. The engine marks the candidate move f4+ as a mistake at −0.64, while the panel’s prose calls it a brilliant and highly forcing tactical shot that disrupts White’s coordination.
Gemini Flash 3.5 misses the simple checkmate in one: bringing the knight from a3 to c2. Instead it blabs on about “forcing tactical shots to disrupt White’s coordination”.

The teacher’s own explanations were bad, so there was nothing worth distilling. I went back to basics: train a model directly, on a dataset of explanations worth learning from. While there is an abundance of chess games available online, data for chess explanations is hard to come by.

A dataset column browser showing a movetext string field, with a histogram of value lengths and one sample value: a complete game in PGN notation, 1. e4 e6 through 13. Qe8# 1-0.
Raw game data is easily available
A photograph of a page from a printed chess book. A diagram of an endgame position sits above prose beginning “Black is in a fatal zugzwang”, followed by annotated move variations.
Explanation data is mostly in books

I scoured the internet for an existing dataset and came up empty. The closest thing I found was ChessBase’s MegaDatabase, which was unusable: the database is a licensed product stored in ChessBase’s own file format, and its annotations are game commentary rather than structured explanations of positions.

Synthetic data was my only option. I picked Gemini Flash 3.5, the best teacher model to come out of my previous post. The goal this time was not to copy Gemini, but to get Gemini to produce high-quality chess explanations.

Trying to Benchmark Explanation Quality

My initial approach was to quantitatively measure explanation quality, so I could define my problem concretely. There is no benchmark that measures the natural language description of a chess position and the faithful rationalization of moves. Existing benchmarks grade the moves that a model produces: I did the same in my previous post. Grading free form text, like explanations, is challenging.

My first idea was to use a less powerful LLM to extract claims that Gemini made about the position into structured text, and then parse that text and evaluate it with a chess library. For example, if Gemini said The bishop on g2 attacks the knight on b7, I would use a less powerful LLM to convert that into parsable claims like exists("bishop", "g2"), exists("knight", "b7"), attacks("bishop", "knight"), and then verify those claims.

But, when reading through the explanations, I realized that Gemini got its basic, easy to check facts right. It could summarize the position on the board and name pieces correctly. Its main failure modes were reasoning about future moves and prioritizing the position’s most relevant ideas for a human player.

For example, Gemini produced This is the most forcing and strongest move in the position, immediately putting the Black king on the run. It clears the path for the queen. Converting this into gradable boolean claims is difficult: I can’t easily verify putting the Black king on the run programmatically, and clears the path for the queen is vague. I also can’t verify if those ideas are accurate or even relevant to the position. Essentially, the claims that were easy to grade were mostly correct, but the reasoning and justification claims that I cared about were non-gradable.

Deriving Context from First Principles

Instead of trying to correct false statements generated by Gemini, I thought about deriving the answer from first principles, assuming Gemini had no chess knowledge. The first step is to visually evaluate the position by counting the pieces on the board and observing their structure. This is called a static evaluation.

A traditional chess engine starts with a static evaluation, then performs a search by efficiently going through millions of positions to find the best future state. Naturally, my first step was to compare the static evaluation of a traditional chess engine like Stockfish to Gemini’s description of the position.

Stockfish simply produces a number indicating what side is winning, and its magnitude. For example, an evaluation of -2.0 means that Black is winning by 2 pawns, which is considered a decent advantage. To calculate the static evaluation, Stockfish currently uses a neural net, making it difficult to break down the atomic components of the evaluation. Luckily, older versions of Stockfish used a human tuned static evaluator (HCE). HCE scores piece types (knights, bishops, rooks) and features like king safety and mobility.

An HCE evaluation breakdown beside the position it scores. A table gives one row per term, imbalance, king, material, mobility, passed pawns, pawns, pieces, space, threats and winnable, with separate middle game and end game columns for White, for Black, and for the difference between them. The terms sum to a main evaluation of 138, or 0.66 pawns.
HCE feature table

Cataloging Strengths and Weaknesses

I manually graded Gemini’s positional explanations against the HCE features for a few dozen positions and noticed that the two correlated highly. Gemini was already great at evaluating positions statically: its qualitative assessment roughly matched the numerical scores in each category.

This suggested a useful distinction. Gemini could describe positions whose evaluation was visible from the board, but struggled when the evaluation only emerged after calculating a sequence of moves. That matched the failure mode I had seen earlier: Gemini named the pieces correctly and summarized the board well, but its claims about future moves were often incorrect. In engine terms, Gemini had a solid static evaluation and no search.

In chess, each position can be classified as “quiet” or “sharp”. Quiet positions have at least two good moves and multiple future ideas, and the position doesn’t change significantly in the short term. Sharp positions have a precise sequence of moves that transforms the position and shifts the balance decisively. Stockfish handles both: it can statically evaluate a quiet position, and it can search exhaustively to find the critical sequence in a sharp one. Gemini handles only the quiet ones.

Fortunately, I had stumbled upon a heuristic for classifying a position as sharp or quiet: compare Stockfish’s static evaluation to its search evaluation. If the two agree, the position is quiet. If they disagree, the search has found something the board alone doesn’t show, and the position is sharp.

A quiet chess position in an analysis tool. Stockfish gives a static evaluation of −0.26 and a depth-15 search evaluation of −0.52: the two nearly agree.
A quiet, equal position, with only a 0.26 difference between static and search evaluation

A sharp chess position in the same analysis tool. Stockfish gives a static evaluation of −1.50 but a depth-15 search evaluation of +4.88, with Qh5+ as the best move: the two disagree by more than six pawns.
A sharp position: on first glance, it looks like Black has an advantage (-1.5) but actually White is completely winning (+4.88)

Now, I could effectively predict whether Gemini would describe a position correctly based on the position’s search and static evaluations alone.

I came across Understanding Reasoning from Pretraining to Post-Training, which gave me a useful way to think about this gap. The paper splits chess ability into two stages. Pretraining on human games teaches a model what positions look like and which moves are typical. Post-training on synthetic reasoning traces, generated by rolling out many possible continuations of a position, teaches the model to calculate. That split lined up with what I had been seeing: whatever chess Gemini absorbed from the internet looked like the first stage. It was unclear whether Gemini had been post-trained on chess reasoning, but from what I saw, Gemini lacked it.

But a broad reasoning trace is not necessarily a useful chess explanation. Human players do not need every plausible calculation; they need the critical line and the natural responses that reveal why the idea works. If I could give human-relevant lines to Gemini, maybe I could compensate for its missing calculation at test time.

Generating a Human-Relevant Reasoning Trace

Human players calculate through sharp positions, and chess books and commentary make that calculation explicit. They focus on the critical sequence of moves that resolves the sharp position into a favorable, quiet one, and include refutations to natural opponent responses.

A Lichess study chapter. Prose defines an attraction as drawing a piece, usually the king, onto a square that leaves it vulnerable. The moves it refers to are boxed alongside it: the main line 19. Rxe7+ Qxe7 20. Re1, and the side line 19…Kxe7 20. Qe3+ Kf8 21. Qxa7.
The main motif in this sharp position, attraction, is justified with the main line (red). The refutation is also addressed with a side line (orange)

I needed a way to generate the same compact structure:

  1. The main sequence of moves, or the “main line” of the position: 19. Rxe7+ Qxe7 20. Re1 (highlighted in red)
  2. Any “side lines” that address natural opponent responses: 19...Kxe7 20. Qe3+ Kf8 21. Qxa7 (highlighted in orange).

Given those lines, I could ask Gemini to verbalize:

  1. The main idea of the position: An attraction draws a piece, usually the king, to a square leaving it vulnerable to another tactical idea.
  2. Why the main idea works: If Qxe7 as a response, we have Re1, pinning the queen to the king and winning material and It's important that the knight defends the rook or the queen can take it with mate!
  3. Why another natural refutation fails: If Kxe7, we have a follow-up fork, with Qe3+ winning the undefended a7 rook.

To generate these lines, I went back to static and search evaluations. Rather than sampling many possible continuations as the paper did, my version of the rollout played the best moves until the static and search evaluations matched. The resulting line carries the position through its sharp, tactical phase into a quieter position that Gemini can evaluate and explain reliably.

I hit one snag: the best Stockfish moves were not always the moves a human player would actually consider. Stockfish often produces theoretically perfect, “alien-level” responses that make the explanation less useful to a human reader. To resolve this, I used Maia, a neural network built to generate human-like chess moves. At every step, I compared Maia’s move to Stockfish’s. If the two moves’ evaluations were roughly similar, I used Maia’s move in the main line. If they differed, I kept Stockfish’s theoretically correct move in the main line and added Maia’s move as a side line.

A rollout tool comparing Stockfish against Maia ply by ply. A table gives each move’s static and searched win percentage and the gap between them, flagging where Maia preferred a different move and what it cost. Below it, the generated critical line: 1. Rxe7+ Qxe7 ( 1… Kxe7 2. Qe3+ Kf8 3. Qxa7 ) 2. Re1 Qxe1+ 3. Nxe1.
The generated rollout matches the human picked lines in the position above almost perfectly. At each step, the static and search evaluation is compared to determine whether the rollout should end

For the position above, the generated rollout is 19. Rxe7+ Qxe7 ( 19... Kxe7 20. Qe3+ Kf8 21. Qxa7 ) 20. Re1 Qxe1+ 21. Nxe1. The side line is an exact match with the commentary; the main line matches with two extra moves. The implementation can be found in this GitHub repo.

The paper’s reasoning traces are built for breadth: eight first-move candidates, several continuations per candidate, each run for a handful of moves, which works out to 300 moves of calculation per position. My rollouts had a much tighter budget. I needed sharp positions to resolve within 5 to 15 moves, the range I had seen in books and enough context to explain a position without projecting too far into the future. I also needed the Stockfish/Maia divergence to produce only a few side lines rather than a branching tree.

To verify this, I generated rollouts for ~2000 positions, with 50% sharp positions and 50% randomly sampled positions. Sharper positions had 8 moves on average, while quieter positions had 3, and the combined average was 5 moves. There was also limited branching, with only 1.2 lines per position on average. Most of the time, Stockfish and Maia chose similarly evaluated moves, so the rollout simply followed Maia’s more human-like move.

I also clicked through the rollouts to check whether I would have chosen the same lines myself. After some tuning, I was satisfied that they captured the core idea and natural refutations without adding irrelevant calculation. In most cases, the rollout made the position clearer to me as well.

Mapping Observed Failures to Benchmarks

The rollout gave Gemini the right lines to explain, but it did not solve a separate problem: tracking the board accurately through those lines. The LLM can summarize a critical line, but it can’t confidently make a specific claim about a future board state like “the knight defends the rook or the queen can take”. This is a well-documented failure mode, visible both in ChessQA, a chess benchmark, and the thinking traces of models.

In ChessQA, one of the five tasks is solving short tactical puzzles, which requires reasoning about future positions. It was the hardest category, with an average accuracy of 17.4%. ChessQA’s #1 “recurrent failure family” was board-state hallucination/misrecognition.

Looking at thinking traces about chess positions, it is clear that LLMs are ill-suited to tracking board state across positions. Below is an example:

Okay, hold on a second. I need to retrace this position. Where in the world *is* that black rook? This is key. Alright, let's see... the move was 31. Bxg6. Okay, so that means a bishop took something on g6. Did the bishop capture the rook specifically? No, not necessarily. The bishop was on d3, and it captured on g6. Right. Now, where *was* the rook before that? Did I miss something? Wait, is the black rook actually on d6?

When evaluating LLMs, ChessQA turned off agentic functionality like tool calling and code execution. I proceeded to design tools specifically to address board-state hallucination and the clunky spatial reasoning observed in thinking traces.

Giving Tools to Bridge the Gap

My starting point was to go to ChessQA’s repository and convert all of their verifiers to tool calls for Gemini. If an LLM is observed to be bad at something, it should “cheat” its way to a correct answer using a tool call. I also experimented with different prompts to compel Gemini to use tools instead of guessing.

The fix for board-state hallucination was a tool called get_piece_positions(moves), which provides piece arrangements given a future state. To enhance Gemini’s spatial reasoning about future states, I introduced check_motif(moves, motifs, square).

I implemented the same motifs that ChessQA checked for: pin, skewer, fork, battery, discovered_check, double_check. These motifs are widely recognized and standard across chess literature. I rounded out my motifs with hanging: the thinking traces showed that the LLM could not detect hanging pieces.

After extensive prompt and context engineering, Gemini Flash 3.5 began producing explanations I would trust as a chess player. Below is a representative one.

A chess position beside an LLM analysis panel. For Kf8 at +5.08 the panel gives a concrete forcing sequence, 31. Qh8+ Ke7 32. Qxe8+ Kd6 33. Rd1+ Kc7 34. bxc3, and identifies the rook on e8 as hanging.
Gemini correctly identifies the main idea in this position, which is to win the Black rook, and also identifies the Bxf3 counterattack

Closing Thoughts

As I noted at the start, no benchmark measures explanation quality. So I spent weeks reading agent traces, grading explanations by hand, and reworking the prompt and context until the output held up to my own standards as a chess player. The pipeline combines every lever I found: rollouts to supply the calculation Gemini lacks, tools to plug the failure modes ChessQA documented, and a prompt tuned against hundreds of traces. I do not see another technique to add. These are the best chess explanations I can get out of a frontier model.

Future Work: Owning the Intelligence

A fair question at this point is why train a model at all. If the pipeline already produces good explanations, why not serve those directly?

Generating explanations are slow and expensive. Each position costs 7 to 10 cents and can take a couple of minutes, most of it spent round-tripping tool calls. When I described the setup to Rakshit, the founder of Chessiro, his reaction was that the latency was unusable for a product and that even one cent per position was more than he could spend. As a chess player, I know what the bar is: Stockfish answers in seconds, runs in a browser tab, and is free.

The entire pipeline is fragile. It rests on prompt and context engineering against a model I do not control; Google can change the weights under me and break everything.

The rollout and tools bolted chess-specific machinery onto a language model from the outside: search, board tracking, motif detection. That machinery already exists as a single neural network. Chess transformers like Leela play at a grandmaster level, carry search and spatial reasoning in their weights, and were built with interpretability in mind. Instead of probing a black box with prompts and hoping it cooperates, the next step is to graft Leela onto a small language model directly, so that a grandmaster-level explanation comes out of one forward pass.