diff options
| author | Scott Gasch <[email protected]> | 2026-09-16 08:27:00 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-09-16 08:27:00 -0700 |
| commit | 4a7ec9ac91939957fb3d3e5564fc24a385b422ff (patch) | |
| tree | 8cb4ff2bc95faadd559b89e0d23cade65eb405ec /src/recogn.c | |
| parent | 966a0ef4da9cf3911eb3da0533179199808717b4 (diff) | |
Fix _RecognizeKPK misjudging a bishop-pawn stalemate-corner draw as a large win, and add exhaustive KPK/EGTB regression coverage to catch this class of bug going forward.
7k/8/5KP1/8/8/8/8/8 w - - (Kf6/Pg6/Kh8, White to move) was scored
+11.33 with PV=Kf7 -- Kf7 actually stalemates Black immediately; the
position is a draw with correct defense. Root cause:
_RecognizeKPK's critical-squares win check trusted a razor-thin
"strictly closer by any margin" king-race comparison, which Fine's
rule doesn't actually license once the approximate weak/strong tempo
adjustment above it is accounted for.
Two narrower, file-specific guards (bishop-pawn-only, one each in
recogn.c and eval.c's _EvalPasserRacesAgainstLoneKings) were tried
first based on the single known repro, then proven unnecessary and
removed once exhaustive testing showed the real fix: requiring the
strong king to be closer by 2+ tempi, not just 1, at the critical
square. Verified via a new TestRecognExhaustiveKPK (testrecogn.c,
on-demand, mirrors the existing TestRecognExhaustiveKNKP) against
EGTB ground truth: the old margin was wrong on 35 of 165,676
EGTB-covered legal single-pawn KPK positions (not just bishop-pawn
files -- rook pawns and even some central-file positions were also
affected); the new margin is wrong on zero.
Also added a small fixed-FEN regression list (g_rgRecognFenCases),
wired into the fast, always-run TestRecogn(), so this exact repro
(plus its b-file and color-flipped mirrors) is checked on every
engine startup rather than relying on the random fuzzer's luck --
confirmed the existing 150-iterations-per-case uniform random KPK
fuzz test essentially never lands on this class of narrow
corner/stalemate configuration.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/recogn.c')
| -rw-r--r-- | src/recogn.c | 37 |
1 files changed, 31 insertions, 6 deletions
diff --git a/src/recogn.c b/src/recogn.c index deb2285..07ee0d5 100644 --- a/src/recogn.c +++ b/src/recogn.c @@ -992,7 +992,6 @@ Return value: } -// BUG: 7k/8/5KP1/8/8/8/8/8 w - - static ULONG _RecognizeKPK(IN SEARCHER_THREAD_CONTEXT *ctx, IN OUT SCORE *piScore) @@ -1151,7 +1150,32 @@ Return value: uDist[uStrong] += (pos->uToMove == uWeak) * 2; } ASSERT((0 <= uDist[uWeak]) && (uDist[uWeak] <= 7)); - if (uDist[uStrong] < uDist[uWeak]) + // + // Was `uDist[uStrong] < uDist[uWeak]` (any margin at all). + // Fine's rule ("if the attacking king reaches a key square + // first, the pawn wins regardless of who's on move") + // doesn't actually license a razor-thin margin here -- + // the tempo fudge-factor above (+1/-2 per side when the + // weak side is on move) is itself an approximation, and a + // 1-tempo-or-less margin turned out to go wrong for real + // stalemate/zugzwang reasons across multiple files and + // configurations, not just the bishop-pawn corner case + // this was first found from (7k/8/5KP1/8/8/8/8/8 w - -, + // Kf6/Pg6/Kh8 White to move -- this recognizer used to + // claim +11.33/win via Kf7, which actually stalemates + // Black; real value is a draw). Requiring a 2+ tempo + // margin instead was verified to fully close the gap via + // TestRecognExhaustiveKPK (testrecogn.c): 0 wrong verdicts + // across all 165,676 EGTB-covered legal single-pawn KPK + // positions, vs. 35 wrong with the razor-thin margin. Two + // narrower, file-specific guards (bishop-pawn-only here, + // and a matching one in eval.c's + // _EvalPasserRacesAgainstLoneKings) were tried first and + // both proved unnecessary once this margin was in place -- + // removed rather than left as redundant dead code, see git + // log for the full investigation if this needs revisiting. + // + if (uDist[uStrong] + 1 < uDist[uWeak]) { cQueen = QUEENING_SQUARE_BY_COLOR_FILE[uStrong][FILE(cPawn)]; @@ -1172,10 +1196,11 @@ Return value: // If we get here then the weak king is as close to or // closer to the lone passer than the strong king -or- the // weak king is as close to or closer to the passer's - // critical squares than the strong king. This may still - // be won for the strong side but we don't know -- the - // logic is ugly. This is still at best drawn for the - // weak side though. + // critical squares than the strong king -or- the strong king's + // margin at its best critical square was too thin to trust (see + // the 2+ tempo comment above). This may still be won for the + // strong side but we don't know -- the logic is ugly. This is + // still at best drawn for the weak side though. // *piScore = 0; if (pos->uToMove == uStrong) |
