summaryrefslogtreecommitdiff
path: root/src/recogn.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-16 08:27:00 -0700
committerScott Gasch <[email protected]>2026-09-16 08:27:00 -0700
commit4a7ec9ac91939957fb3d3e5564fc24a385b422ff (patch)
tree8cb4ff2bc95faadd559b89e0d23cade65eb405ec /src/recogn.c
parent966a0ef4da9cf3911eb3da0533179199808717b4 (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.c37
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)