Chess has a basic endgame table that every player learns. King and rook wins. King and two knights doesn’t force it. King and bishop is dead. Xiangqi has the same table, and if you read Chinese you learn it the same way, out of endgame manuals that all carry the same verdicts. In English you get fragments.

So I collected 32 of those verdicts, built a representative position for each one, and asked Pikafish what it thought. 29 hold up, 2 the engine flatly contradicts, and 1 leans without resolving.

Depth 26, 32 verdicts  
engine agrees 29
engine flatly contradicts 2
leaning but unresolved 1

The 29 are the boring part. The table stops tracking piece values almost immediately.

Three soldiers beat a defence the chariot can’t crack

The chariot is the strongest piece in xiangqi, the rook by another name. The standard full defence is 士象全: general at home, both advisors, both elephants, arranged so the two guards sit directly in front of the general where a cannon can’t use them as a screen.

A bare chariot against that defence is a draw. Pikafish scores it at exactly 0.

Three soldiers beat it. Mate in 15.

Two horses beat it. Mate in 32.

Two xiangqi boards side by side, both showing the same four-piece defence. On the left a bare red chariot, marked Draw. On the right three red soldiers on the 7th rank, marked Mate in 15. Same defence, same side to move. Pikafish scores the chariot at 0 and mates with the soldiers in 15.

A chariot is worth more than three soldiers by any piece table you’ll find, and more than two horses. In this endgame it’s worth less than either. Soldiers can’t retreat, which sounds like a weakness and turns into the reason they win: three of them advancing on a nine-point palace can’t be driven back, and the defence has no way to trade them off.

The same logic runs the other way. Five soldiers, all sitting on the last rank, against a bare general is a draw. Five pieces against zero. They can’t move backward and they’re already at the wall, so they can’t force mate or even stalemate. Material and mating power come apart.

Same material, one elephant moved, draw becomes win

The clearest case in the whole corpus is a pair of positions that differ by one piece placement.

A bare chariot against a horse and two elephants. With the defence in its fortress (horse on e8, elephants on i8 and g6) it holds. Engine says +14, which is noise.

Move that g6 elephant to g10. Same material on both sides, same attacker, same side to move. Red wins, starting with 1.Rb7. Engine says +968.

That’s a swing of nearly a full chariot from moving one elephant four points. A table that lists these by material alone is lying to you about what it knows.

What the engine caught in my own positions

I wrote verify-xiangqi-endgames.ts expecting it to say something about the manuals. It mostly said things about my own positions, which is what a check like this is actually for.

It caught three illegal positions. In xiangqi the two generals can’t face each other down an open file, and I’d built three boards where they did. Those parse fine, look fine, and are not legal xiangqi.

It caught two positions I’d labelled book draws where Pikafish announced mate in 2. The verdict was right and my representative position was garbage: a badly placed snapshot of a drawn class can be a tactical loss, which is exactly the failure mode the whole exercise is exposed to.

And it caught a real precision point. Three soldiers on the 6th rank do not show the win that the same three soldiers show on the 7th. One rank. Two different verdicts.

A fixed-depth search can’t prove a book result. It can’t settle a 60-move fortress, and a drawish score is evidence rather than proof. What it can do reliably is tell you your position is wrong, and it did that six times.

The two it contradicts

Two rows Pikafish refuses to confirm from any position I could build.

A bare chariot against two minor pieces with no defensive pieces is a book draw. Pikafish finds forced mate from all eight placements I tried, including the most resistant one. A chariot and cannon against a lone chariot holding the middle file is a book draw. Pikafish scores it +861 and calls it clearly winning.

Both are recorded on the entries as disputes rather than tuned until the engine agreed, which was tempting and would have been dishonest. The likely reading is that my representative position doesn’t capture the defensive setup the claim assumes. But a reader deserves to know the claim didn’t reproduce, so the rows carry a warning instead of a verdict.

The second one has a candidate explanation that’s specific to xiangqi. With both generals bare, that defence leans on checking resources. In chess, perpetual check is how the analogous endgame draws. In xiangqi perpetual check is a loss for the side giving check. The attacker has to vary or lose the game. A defence built on repeated checks hands over the point, so the engine is scoring a position where the chess intuition about perpetuals runs backward.

The rule my own engine got wrong

That rule is also where this whole thread started, because Mistboard was getting it wrong.

The xiangqi course on the site teaches perpetual check correctly, and has since it shipped: the attacker must vary or the attacker loses. Meanwhile the game kernel underneath adjudicated a threefold repetition as a plain draw. So a losing player could perpetual-check their way to half a point on a site that was, in a different tab, teaching them that this loses.

Nobody reported it. It’s not the kind of bug that produces a crash or a bad-looking board, and I found it reading the Wikipedia rules article closely enough to notice the mismatch. The fix replays the move log, takes the cycle that closed the repetition, and names the side that checked on every one of its own moves in it. Mutual perpetual check stays a draw. Check-free repetitions stay a draw. Perpetual chase (长捉) is the same class of rule and is still unmodeled.

A rule your tutorial teaches and your kernel doesn’t enforce is a bug in the kernel, and the only way to find that class of bug is to audit taught rules against actual engine behaviour rather than assuming the teaching followed the code. I’d guess most variant implementations have at least one.


The 32 entries, with provenance on every row and the disputes marked, are in xiangqi-endgame-corpus.ts. The checker is verify-xiangqi-endgames.ts; point it at a Pikafish binary and it prints book verdict against engine read, side by side, with the principal variation.

You can play xiangqi on Mistboard, where perpetual check now costs you the game.