summaryrefslogtreecommitdiff
path: root/src/recogn.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-08 16:50:55 -0700
committerScott Gasch <[email protected]>2026-09-08 16:50:55 -0700
commitfbb138cc1dd13da2f30129206cdcd3d128344f54 (patch)
tree7055d2344a378c5f9f064490f6b25822166518b6 /src/recogn.c
parent434fa0406e1b01395a2b7f0aa481ca5dc367fa09 (diff)
Retire asm GetAttacks, recogn.c/fen.c bugfixes, misc bugfixes verified at parity
Confirmed self-play regression traced to a stale test_vs_head.sh reference binary (typhoon_allbitboards/550ea81, deleted): every "vs head" comparison since 92fc412 (Sep 4) was checking new work against that fixed Sep-4 snapshot, never against real HEAD or the working tree. Rebuilt clean reference binaries directly from git and re-verified everything from scratch. This commit lands only the pieces confirmed safe against clean 434fa04 (fast st1 match, ~30-40 games, score ~0.44-0.55, consistent with parity; plus a DEBUG-build smoke test pass): - recogn.c, fen.c: real bugfixes - data.c, draw.c, ics.c: whitespace only - x64.asm: retires the asm GetAttacks implementation now that chess.h's GetAttacks macro unconditionally selects the already-verified-faster _GetAttacksBB bitboard version instead of a three-way build-flag toggle (GETATTACKS_BITBOARD/CROUTINES/asm default) - see.c, testsee.c: SEE/test-harness updates supporting that default - root.c: per-tier eval-exit reporting (super-lazy counters currently always read 0 -- accurate, since no super-lazy exit exists yet) - main.c: startup banner update, InitEval() call, TestRecogn() added to the #ifdef TEST self-test sequence - command.c: InitEval() DNA-reload hook, new qsearchfutility diagnostic - dynamic.c: minor changes - chess.h: the GetAttacks default change above, three FUTILITY_BASE_MARGIN_* compatibility aliases (all still equal to the original flat FUTILITY_BASE_MARGIN -- search.c has not been split into per-tier margins here), placeholder super-lazy counters, and an EvalPasserRaces -> _EvalPasserRacesAgainstLoneKings rename (confirmed byte-identical body) to match recogn.c's call site - eval.c: the same rename, plus a no-op InitEval() stub (nothing to initialize until the ROOK_FULL_HALF_OPEN_BONUS cache below exists) Deliberately NOT included: the full eval.c overhaul (~1770 lines) and search.c's qsearch-futility rework (~650 lines), including yesterday's loosened SUPER_LAZY_MARGIN_BY_ARMY/FUTILITY_BASE_MARGIN_BY_SOURCE tables. Reverting just those two tables while keeping the rest of the eval.c overhaul still lost badly to 434fa04 (0.20 over 10 games), so the regression isn't fully explained by the margins alone -- the eval.c overhaul needs careful, incremental re-verification against this commit as the new baseline, not a bulk re-apply. Full original work preserved in git stash (stash@{0} as of this commit) for that follow-up. Note: two pre-existing, position/state-dependent assertion crashes were found during this verification (util.c:1093 WalkPV, recogn.c:1359 _SanityCheckRecognizers), both reproducing on unmodified 434fa04 -- not introduced by anything here, not yet root-caused. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Ka9o3S2eKqh4jNfmxVZ6fH
Diffstat (limited to 'src/recogn.c')
-rw-r--r--src/recogn.c388
1 files changed, 224 insertions, 164 deletions
diff --git a/src/recogn.c b/src/recogn.c
index 0ab9514..32a618d 100644
--- a/src/recogn.c
+++ b/src/recogn.c
@@ -12,7 +12,7 @@ Abstract:
Interior-Node Recognition" * ICCA Journal Volume 21, No. 3, pp
156-167 (also "Scalable Search in Computer Chess" pp 65-81). This
code also borrows ideas from Thorsten Greiner's AMY chess program.
-
+
Author:
Scott Gasch ([email protected]) 16 Oct 2005
@@ -24,10 +24,10 @@ Revision History:
#include "chess.h"
extern ULONG g_uIterateDepth;
-static COOR QUEENING_SQUARE_BY_COLOR_FILE[2][8] =
-{
+static COOR QUEENING_SQUARE_BY_COLOR_FILE[2][8] =
+{
{ A1, B1, C1, D1, E1, F1, G1, H1 },
- { A8, B8, C8, D8, E8, F8, G8, H8 }
+ { A8, B8, C8, D8, E8, F8, G8, H8 }
};
#define RECOGN_INDEX(w, b) \
@@ -38,12 +38,12 @@ typedef ULONG RECOGNIZER(SEARCHER_THREAD_CONTEXT *ctx, SCORE *piScore);
static RECOGNIZER *g_pRecognizers[64];
static BITV g_bvRecognizerAvailable[32];
-static ULONG
-_MakeMaterialSig(IN FLAG fPawn,
+static ULONG
+_MakeMaterialSig(IN FLAG fPawn,
IN FLAG fKnight,
IN FLAG fBishop,
- IN FLAG fRook,
- IN FLAG fQueen)
+ IN FLAG fRook,
+ IN FLAG fQueen)
/**
Routine description:
@@ -66,7 +66,7 @@ Return value:
**/
{
ULONG x;
-
+
ASSERT(IS_VALID_FLAG(fPawn));
ASSERT(IS_VALID_FLAG(fKnight));
ASSERT(IS_VALID_FLAG(fBishop));
@@ -74,7 +74,7 @@ Return value:
ASSERT(IS_VALID_FLAG(fQueen));
x = fPawn | (fKnight << 1) | (fBishop << 2) | (fRook << 3) | (fQueen << 4);
-
+
ASSERT((0 <= x) && (x <= 31));
return(x);
}
@@ -84,29 +84,29 @@ Return value:
static FLAG
-_TablebasesSaySideWins(IN SEARCHER_THREAD_CONTEXT *ctx,
+_TablebasesSaySideWins(IN SEARCHER_THREAD_CONTEXT *ctx,
IN ULONG uSide)
{
SCORE iScore;
if (TRUE == ProbeEGTB(ctx, &iScore))
{
- if (ctx->sPosition.uToMove == uSide)
+ if (ctx->sPosition.uToMove == uSide)
{
return iScore > 0;
- }
- else
+ }
+ else
{
return iScore < 0;
}
- }
+ }
return TRUE;
}
static FLAG
-_TablebasesSayDraw(IN SEARCHER_THREAD_CONTEXT *ctx)
+_TablebasesSayDraw(IN SEARCHER_THREAD_CONTEXT *ctx)
{
SCORE iScore;
- if (TRUE == ProbeEGTB(ctx, &iScore))
+ if (TRUE == ProbeEGTB(ctx, &iScore))
{
return iScore == 0;
}
@@ -114,13 +114,13 @@ _TablebasesSayDraw(IN SEARCHER_THREAD_CONTEXT *ctx)
}
static FLAG
-_TablebasesSayDrawOrWin(IN SEARCHER_THREAD_CONTEXT *ctx,
- IN ULONG uSide)
+_TablebasesSayDrawOrWin(IN SEARCHER_THREAD_CONTEXT *ctx,
+ IN ULONG uSide)
{
SCORE iScore;
- if (TRUE == ProbeEGTB(ctx, &iScore))
+ if (TRUE == ProbeEGTB(ctx, &iScore))
{
- return ((iScore == 0) ||
+ return ((iScore == 0) ||
((iScore > 0) && (ctx->sPosition.uToMove == uSide)) ||
((iScore < 0) && (ctx->sPosition.uToMove != uSide)));
}
@@ -128,9 +128,9 @@ _TablebasesSayDrawOrWin(IN SEARCHER_THREAD_CONTEXT *ctx,
}
-static FLAG
-_SanityCheckRecognizers(IN SEARCHER_THREAD_CONTEXT *ctx,
- IN SCORE iScore,
+static FLAG
+_SanityCheckRecognizers(IN SEARCHER_THREAD_CONTEXT *ctx,
+ IN SCORE iScore,
IN ULONG uVal) {
ULONG uToMove = ctx->sPosition.uToMove;
switch(uVal) {
@@ -146,22 +146,34 @@ _SanityCheckRecognizers(IN SEARCHER_THREAD_CONTEXT *ctx,
return _TablebasesSaySideWins(ctx, !uToMove);
}
case RECOGN_LOWER:
+ //
+ // iScore is only a LOWER bound: the true score is >=
+ // iScore, so we can only make a directional claim when the
+ // bound itself pins one down. iScore > 0 forces a genuine
+ // win for uToMove; iScore == 0 forces at least a draw. A
+ // negative lower bound ("at least this bad, could be
+ // better or worse") licenses no claim about who's actually
+ // winning, so don't assert one.
if (iScore == 0) {
return _TablebasesSayDrawOrWin(ctx, uToMove);
} else if (iScore > 0) {
return _TablebasesSaySideWins(ctx, uToMove);
} else {
- ASSERT(iScore < 0);
- return _TablebasesSaySideWins(ctx, !uToMove);
+ return TRUE;
}
case RECOGN_UPPER:
+ //
+ // Symmetric reasoning: iScore is only an UPPER bound (true
+ // score <= iScore). iScore < 0 forces a genuine win for the
+ // opponent; iScore == 0 forces at least a draw for the
+ // opponent. A positive upper bound doesn't preclude uToMove
+ // still winning by less than iScore, so no claim there.
if (iScore == 0) {
return _TablebasesSayDrawOrWin(ctx, !uToMove);
- } else if (iScore > 0) {
- return _TablebasesSayDrawOrWin(ctx, !uToMove);
- } else {
- ASSERT(iScore < 0);
+ } else if (iScore < 0) {
return _TablebasesSaySideWins(ctx, !uToMove);
+ } else {
+ return TRUE;
}
default:
ASSERT(FALSE);
@@ -169,9 +181,9 @@ _SanityCheckRecognizers(IN SEARCHER_THREAD_CONTEXT *ctx,
}
}
-static FLAG
-_NothingBut(IN POSITION *pos,
- IN PIECE p,
+static FLAG
+_NothingBut(IN POSITION *pos,
+ IN PIECE p,
IN ULONG uColor)
/**
@@ -196,7 +208,7 @@ static FLAG
{
static PIECE q[] = { KNIGHT, BISHOP, ROOK, QUEEN };
ULONG u;
-
+
if (!(p & PAWN))
{
if (pos->uPawnCount[uColor] > 0) return(FALSE);
@@ -213,8 +225,8 @@ static FLAG
}
#endif
-static ULONG
-_RecognizeKK(IN SEARCHER_THREAD_CONTEXT *ctx,
+static ULONG
+_RecognizeKK(IN SEARCHER_THREAD_CONTEXT *ctx,
IN OUT SCORE *piScore)
/**
@@ -237,8 +249,8 @@ Return value:
return(RECOGN_EXACT);
}
-static ULONG
-_RecognizeKBK(IN SEARCHER_THREAD_CONTEXT *ctx,
+static ULONG
+_RecognizeKBK(IN SEARCHER_THREAD_CONTEXT *ctx,
IN OUT SCORE *piScore)
/**
@@ -263,7 +275,7 @@ Return value:
ULONG u;
ULONG uAdjacent;
POSITION *pos = &ctx->sPosition;
-
+
ASSERT((pos->uNonPawnCount[WHITE][0] <= 3) &&
(pos->uNonPawnCount[BLACK][0] <= 3));
ASSERT(_NothingBut(pos, BISHOP, WHITE));
@@ -272,7 +284,7 @@ Return value:
//
// Recognize KBKB as a draw unless there's a cornered king (in
// which case it may be a mate-in-1)
- //
+ //
if ((pos->uNonPawnCount[WHITE][0] == 2) &&
(pos->uNonPawnCount[BLACK][0] == 2))
{
@@ -283,20 +295,20 @@ Return value:
return(RECOGN_EXACT);
}
}
-
+
//
// Otherwise we want to deal with KB+ vs lone K. KBKBB etc are
// too hard to recognize.
- //
+ //
if ((pos->uNonPawnCount[WHITE][0] != 1) &&
(pos->uNonPawnCount[BLACK][0] != 1))
{
return(UNRECOGNIZED);
}
-
+
//
// If we get here then one side has no pieces (except the king).
- //
+ //
uStrong = BLACK;
if (pos->uNonPawnCount[WHITE][0] > 1)
{
@@ -308,7 +320,7 @@ Return value:
//
// KB vs K is a draw, KB+ vs K is still a draw if all bishops are the
// same color.
- //
+ //
uBishops = pos->uNonPawnCount[uStrong][BISHOP];
if ((uBishops == 1) ||
(pos->uWhiteSqBishopCount[uStrong] == 0) ||
@@ -317,18 +329,18 @@ Return value:
*piScore = 0;
return(RECOGN_EXACT);
}
-
+
//
// If we get here the strong side has more than one bishop and has
// at least one bishop on each color.
- //
+ //
//
// If the weak king is next to a strong side piece, fail to
// recognize since the weak king may take the bishop with the
// move. Note: we allow the weak king to be adjacent to up to one
// enemy bishop as long as it's the strong side's turn to move.
- //
+ //
cWeakKing = pos->cNonPawns[FLIP(uStrong)][0];
ASSERT(DISTANCE(cWeakKing, pos->cNonPawns[uStrong][0]) > 1);
uAdjacent = 0;
@@ -366,8 +378,8 @@ Return value:
// This is a recognized win for the strong side. Compute a score
// that encourages cornering the weak king and making progress
// towards a checkmate.
- //
- *piScore = (pos->iMaterialBalance[uStrong] + VALUE_QUEEN -
+ //
+ *piScore = (pos->iMaterialBalance[uStrong] + VALUE_QUEEN -
(u * 16) - (CORNER_DISTANCE(cWeakKing) * 32));
ASSERT(IS_VALID_SCORE(*piScore));
if (pos->uToMove != uStrong)
@@ -378,8 +390,8 @@ Return value:
return(RECOGN_LOWER);
}
-static ULONG
-_RecognizeKNK(IN SEARCHER_THREAD_CONTEXT *ctx,
+static ULONG
+_RecognizeKNK(IN SEARCHER_THREAD_CONTEXT *ctx,
IN OUT SCORE *piScore)
/**
@@ -405,11 +417,11 @@ Return value:
(pos->uNonPawnCount[BLACK][0] <= 3));
ASSERT(_NothingBut(pos, KNIGHT, WHITE));
ASSERT(_NothingBut(pos, KNIGHT, BLACK));
-
+
//
// KNKN is a draw unless someone has a K in the corner (in which case,
// with the friend knight in the way, there's a possible mate)
- //
+ //
if ((pos->uNonPawnCount[WHITE][0] == 2) &&
(pos->uNonPawnCount[BLACK][0] == 2))
{
@@ -421,20 +433,20 @@ Return value:
}
return(UNRECOGNIZED);
}
-
+
//
// KNNKN etc... unrecognized. Heinz says "exceptional wins possible for
// any side by mates in seven or less moves." TODO: add this knowledge.
- //
+ //
if ((pos->uNonPawnCount[WHITE][0] != 1) ||
(pos->uNonPawnCount[BLACK][0] != 1))
{
return(UNRECOGNIZED);
}
-
+
//
// If we get here somebody has no pieces (except a lone king).
- //
+ //
uStrong = WHITE;
if (pos->uNonPawnCount[BLACK][0] > 1)
{
@@ -449,7 +461,7 @@ Return value:
// Everything else in here is a draw.
//
ASSERT(pos->uNonPawnCount[uStrong][0] < 4);
- if (ON_EDGE(pos->cNonPawns[FLIP(uStrong)][0]))
+ if (ON_EDGE(pos->cNonPawns[FLIP(uStrong)][0]))
{
return(UNRECOGNIZED);
}
@@ -458,8 +470,18 @@ Return value:
}
-static ULONG
-_RecognizeKBNK(IN SEARCHER_THREAD_CONTEXT *ctx,
+//
+// DISABLED -- not currently registered in InitializeInteriorNodeRecognizers
+// (see the comment there). testrecogn.c's EGTB cross-check found a
+// counterexample in the bare-lone-king mating branch below (1k6/8/8/8/
+// 1bn5/8/1K6/8 w): the classic KBN-vs-K "wrong corner" subtlety --
+// mate is only forceable in the corner matching the bishop's square
+// color, and this function's bail-out conditions don't fully capture
+// that. Left in place as a starting point; validate any future fix
+// against testrecogn.c's KNBK case before re-registering.
+//
+static ULONG __attribute__((unused))
+_RecognizeKBNK(IN SEARCHER_THREAD_CONTEXT *ctx,
IN OUT SCORE *piScore)
/**
@@ -488,22 +510,22 @@ Return value:
(pos->uNonPawnCount[BLACK][0] <= 3));
ASSERT(_NothingBut(pos, BISHOP | KNIGHT, WHITE));
ASSERT(_NothingBut(pos, BISHOP | KNIGHT, BLACK));
-
+
if ((pos->uNonPawnCount[WHITE][0] > 1) &&
(pos->uNonPawnCount[BLACK][0] > 1))
{
//
// Do not recognize stuff like KNNKB or KNKBB etc...
- //
+ //
if (pos->uNonPawnCount[WHITE][0] + pos->uNonPawnCount[BLACK][0] > 4)
{
return(UNRECOGNIZED);
}
-
+
//
// This is KNKB; unless someone's king is on the edge,
// recognize a draw.
- //
+ //
ASSERT((pos->uNonPawnCount[WHITE][0] == 2) &&
(pos->uNonPawnCount[BLACK][0] == 2));
if (ON_EDGE(pos->cNonPawns[WHITE][0]) ||
@@ -517,7 +539,7 @@ Return value:
//
// If we get here we are in a KBNK endgame.
- //
+ //
uStrong = WHITE;
if (pos->uNonPawnCount[BLACK][0] > 1)
{
@@ -540,11 +562,11 @@ Return value:
{
return(UNRECOGNIZED);
}
-
+
//
// Don't recognize anything if the weak king is next to a strong side's
// piece.
- //
+ //
uAdjacent = 0;
for (u = 1; u < pos->uNonPawnCount[uStrong][0]; u++)
{
@@ -559,7 +581,7 @@ Return value:
// Don't recognize if the two kings are close enough to each other
// that there might be a stalemate if the weak side is on move and
// on the edge.
- //
+ //
ASSERT(IS_ON_BOARD(pos->cNonPawns[uStrong][0]));
ASSERT(IS_KING(pos->rgSquare[pos->cNonPawns[uStrong][0]].pPiece));
u = DISTANCE(cWeakKing, pos->cNonPawns[uStrong][0]);
@@ -577,7 +599,7 @@ Return value:
// Calculate a score that grabs the search's attention and makes
// progress towards driving the weak king to the correct corner to
// mate him.
- //
+ //
if (pos->uWhiteSqBishopCount[uStrong] > 0)
{
uDist = WHITE_CORNER_DISTANCE(cWeakKing);
@@ -587,7 +609,7 @@ Return value:
uDist = BLACK_CORNER_DISTANCE(cWeakKing);
}
ASSERT((0 <= uDist) && (uDist <= 7));
-
+
*piScore = (pos->iMaterialBalance[uStrong] + (7 * VALUE_PAWN)
- (uDist * 32) - (u * 16));
ASSERT(IS_VALID_SCORE(*piScore));
@@ -600,14 +622,35 @@ Return value:
}
-static ULONG
-_RecognizeKNKP(IN SEARCHER_THREAD_CONTEXT *ctx,
+//
+// RE-ENABLED for the exact single-knight/single-pawn case only, backed
+// by exhaustive (not sampled) proof: testrecogn.c's
+// TestRecognExhaustiveKNKP enumerates every legal KNKP position with
+// exactly one knight and one pawn -- 10.2M raw square placements, 5.2M
+// of them actually checked against real Syzygy EGTB data (the rest
+// UNRECOGNIZED or outside coverage) -- and found zero disagreements.
+// The claim this function makes ("at best a draw for the pawn side")
+// genuinely is a two-knights-can't-force-mate fact in that exact
+// sub-case.
+//
+// It is FALSE once a second knight or a second pawn enters the
+// picture, though -- testrecogn.c's random sampling found two live
+// counterexamples in those cases (a real forced loss for the pawn
+// side despite its king being right next to its own pawn) before this
+// function was tightened to exclude them via the count==1 checks
+// below. Don't loosen those checks back to "<=2 knights" / "any pawn
+// count" without first extending the exhaustive verifier to cover
+// whatever case is being added and confirming zero disagreements the
+// same way.
+//
+ULONG
+_RecognizeKNKP(IN SEARCHER_THREAD_CONTEXT *ctx,
IN OUT SCORE *piScore)
/**
Routine description:
- Recognize KN+KP+ positions.
+ Recognize KNKP positions: exactly one knight vs exactly one pawn.
Parameters:
@@ -622,14 +665,14 @@ Return value:
{
ULONG uStrong;
POSITION *pos = &ctx->sPosition;
-
+
ASSERT((pos->uNonPawnCount[WHITE][0] <= 3) &&
(pos->uNonPawnCount[BLACK][0] <= 3));
ASSERT(_NothingBut(pos, PAWN | KNIGHT, WHITE));
ASSERT(_NothingBut(pos, PAWN | KNIGHT, BLACK));
//
- // Call the side with knight(s) "strong"
+ // Call the side with the knight "strong"
//
uStrong = WHITE;
if (pos->uNonPawnCount[BLACK][0] > 1)
@@ -640,18 +683,21 @@ Return value:
ASSERT(pos->uNonPawnCount[FLIP(uStrong)][0] == 1);
//
- // Don't recognize KNNKP or KNKP with K on edge
- //
- if ((pos->uNonPawnCount[uStrong][KNIGHT] > 2) ||
+ // Exhaustively proven correct only for exactly one knight and
+ // exactly one pawn (see the comment above) -- also still exclude
+ // K on the edge, per the original ON_EDGE reasoning.
+ //
+ if ((pos->uNonPawnCount[uStrong][KNIGHT] != 1) ||
+ (pos->uPawnCount[FLIP(uStrong)] != 1) ||
(ON_EDGE(pos->cNonPawns[FLIP(uStrong)][0])))
{
return(UNRECOGNIZED);
}
//
- // This is at least a draw for the side with the pawn(s) and at
- // best a draw for the side with the knight(s)
- //
+ // This is at least a draw for the side with the pawn and at best a
+ // draw for the side with the knight
+ //
*piScore = 0;
if (pos->uToMove == uStrong)
{
@@ -662,7 +708,7 @@ Return value:
static ULONG
-_RecognizeKBKP(IN SEARCHER_THREAD_CONTEXT *ctx,
+_RecognizeKBKP(IN SEARCHER_THREAD_CONTEXT *ctx,
IN OUT SCORE *piScore)
/**
@@ -711,7 +757,7 @@ Return value:
//
// Construct a strong side bitboard of pawn locations
- //
+ //
bb = 0ULL;
for (u = 0; u < pos->uPawnCount[uStrong]; u++)
{
@@ -719,25 +765,25 @@ Return value:
ASSERT(IS_ON_BOARD(c));
bb |= COOR_TO_BB(c);
}
-
+
if ((pos->uNonPawnCount[BLACK][0] + pos->uPawnCount[BLACK] > 1) &&
(pos->uNonPawnCount[WHITE][0] + pos->uPawnCount[WHITE] > 1))
{
//
// Neither side has a lone king. This is either KBKP+ or
// KBP+KP+.
- //
+ //
if (pos->uPawnCount[uStrong] > 0)
{
//
// Strong side can maybe take an adjacent pawn and survive the
// bad bishop.
- //
+ //
if (uStrong == pos->uToMove)
{
return(UNRECOGNIZED);
}
-
+
//
// Make sure the strong side has the right color bishop
// for his pawns.
@@ -750,9 +796,9 @@ Return value:
{
goto at_best_draw_for_strong;
}
-
+
if (!(bb & ~BBFILE[H]) &&
- (pos->uWhiteSqBishopCount[WHITE] ==
+ (pos->uWhiteSqBishopCount[WHITE] ==
pos->uNonPawnCount[WHITE][BISHOP]) &&
(DISTANCE(cWeakKing, H8) <= 1))
{
@@ -762,13 +808,13 @@ Return value:
else
{
if (!(bb & ~BBFILE[A]) &&
- (pos->uWhiteSqBishopCount[BLACK] ==
+ (pos->uWhiteSqBishopCount[BLACK] ==
pos->uNonPawnCount[BLACK][BISHOP]) &&
(DISTANCE(cWeakKing, A1) <= 1))
{
goto at_best_draw_for_strong;
}
-
+
if (!(bb & ~BBFILE[H]) &&
(pos->uWhiteSqBishopCount[BLACK] == 0) &&
(DISTANCE(cWeakKing, H1) <= 1))
@@ -791,13 +837,13 @@ Return value:
}
goto at_best_draw_for_strong;
}
- }
- else
+ }
+ else
{
//
// KBPK: make sure the bishop is the right color. This time
// there is no need to check for on-move.
- //
+ //
ASSERT(pos->uNonPawnCount[FLIP(uStrong)][0] == 1);
ASSERT(pos->uNonPawnCount[uStrong][0] > 1);
@@ -816,11 +862,11 @@ Return value:
{
goto draw;
}
- }
- else
+ }
+ else
{
if (!(bb & ~BBFILE[A]) &&
- (pos->uWhiteSqBishopCount[BLACK] ==
+ (pos->uWhiteSqBishopCount[BLACK] ==
pos->uNonPawnCount[BLACK][BISHOP]) &&
(DISTANCE(cWeakKing, A1) <= 1))
{
@@ -836,7 +882,7 @@ Return value:
return(UNRECOGNIZED);
}
#ifdef DEBUG
- UtilPanic(SHOULD_NOT_GET_HERE,
+ UtilPanic(SHOULD_NOT_GET_HERE,
NULL, NULL, NULL, NULL,
__FILE__, __LINE__);
#endif
@@ -856,7 +902,7 @@ Return value:
static void
_GetPassersCriticalSquares(IN ULONG uColor,
- IN COOR cPawn,
+ IN COOR cPawn,
IN OUT COOR *cSquare)
/**
@@ -884,9 +930,9 @@ Return value:
**/
{
- static COOR cCriticalSquare[2][128] =
+ static COOR cCriticalSquare[2][128] =
{
- {
+ {
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0,0,0,0,0,0,0,0,
0x61, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x66, 0,0,0,0,0,0,0,0,
0x61, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x66, 0,0,0,0,0,0,0,0,
@@ -896,7 +942,7 @@ Return value:
0x61, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x66, 0,0,0,0,0,0,0,0,
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0,0,0,0,0,0,0,0,
},
- {
+ {
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0,0,0,0,0,0,0,0,
0x11, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x16, 0,0,0,0,0,0,0,0,
0x11, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x16, 0,0,0,0,0,0,0,0,
@@ -905,14 +951,14 @@ Return value:
0x11, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x16, 0,0,0,0,0,0,0,0,
0x11, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x16, 0,0,0,0,0,0,0,0,
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0,0,0,0,0,0,0,0,
- }
+ }
};
ULONG uFile = FILE(cPawn);
ASSERT(IS_VALID_COLOR(uColor));
ASSERT(IS_ON_BOARD(cPawn));
ASSERT((RANK(cPawn) != 1) && (RANK(cPawn) != 8));
-
+
if ((uFile == A) || (uFile == H))
{
cSquare[0] = cCriticalSquare[uColor][cPawn];
@@ -923,19 +969,22 @@ Return value:
cSquare[1] = cCriticalSquare[uColor][cPawn];
cSquare[0] = cSquare[1] - 1;
cSquare[2] = cSquare[1] + 1;
-
+
end:
- ASSERT(cSquare[0] != 0);
- ASSERT(cSquare[1] != 0);
- ASSERT(cSquare[2] != 0);
+ //
+ // Note: don't assert cSquare[n] != 0 here -- COOR value 0 is A8, a
+ // perfectly legal critical square (e.g. a black pawn's B-file
+ // critical square at rank 7 has its adjacent/rook-file neighbor at
+ // A8), not a sentinel for "uninitialized". IS_ON_BOARD is the
+ // correct validity check.
ASSERT(IS_ON_BOARD(cSquare[0]));
ASSERT(IS_ON_BOARD(cSquare[1]));
ASSERT(IS_ON_BOARD(cSquare[2]));
}
-static ULONG
-_RecognizeKPK(IN SEARCHER_THREAD_CONTEXT *ctx,
+static ULONG
+_RecognizeKPK(IN SEARCHER_THREAD_CONTEXT *ctx,
IN OUT SCORE *piScore)
/**
@@ -976,7 +1025,7 @@ Return value:
if (pHash->u64Key == pos->u64PawnSig)
{
pos->iScore[BLACK] = pos->iScore[WHITE] = 0;
- if (TRUE == EvalPasserRaces(pos, pHash))
+ if (TRUE == _EvalPasserRacesAgainstLoneKings(pos, pHash))
{
//
// Someone wins.
@@ -1034,7 +1083,7 @@ Return value:
ASSERT(pos->uPawnCount[uStrong] > 0);
uWeak = FLIP(uStrong);
ASSERT(pos->uPawnCount[uWeak] == 0);
-
+
if (pos->uPawnCount[uStrong] > 1)
{
*piScore = 0;
@@ -1049,12 +1098,12 @@ Return value:
// The side with pawns has only one pawn, do some more
// sophisticated analysis here to spot winning KPK
// configurations earlier by using "critical squares"
- //
+ //
ASSERT(pos->uPawnCount[uStrong] == 1);
cPawn = pos->cPawns[uStrong][0];
ASSERT(IS_ON_BOARD(cPawn));
ASSERT(IS_PAWN(pos->rgSquare[cPawn].pPiece));
-
+
//
// Step 1: the strong king must be closer to the pawn than
// the weak king.
@@ -1072,12 +1121,12 @@ Return value:
_GetPassersCriticalSquares(uStrong, cPawn, cCritical);
for (u = 0; u < 3; u++)
{
- uDist[uStrong] = DISTANCE(pos->cNonPawns[uStrong][0],
+ uDist[uStrong] = DISTANCE(pos->cNonPawns[uStrong][0],
cCritical[u]);
ASSERT((0 <= uDist[uStrong]) && (uDist[uStrong] <= 7));
- uDist[uWeak] = DISTANCE(pos->cNonPawns[uWeak][0],
+ uDist[uWeak] = DISTANCE(pos->cNonPawns[uWeak][0],
cCritical[u]);
-
+
//
// Assume if the weak side is on move he will move
// towards the critical square. Also assume that
@@ -1094,7 +1143,7 @@ Return value:
ASSERT((0 <= uDist[uWeak]) && (uDist[uWeak] <= 7));
if (uDist[uStrong] < uDist[uWeak])
{
- cQueen =
+ cQueen =
QUEENING_SQUARE_BY_COLOR_FILE[uStrong][FILE(cPawn)];
*piScore = (pos->iMaterialBalance[uStrong] +
VALUE_QUEEN + (2 * VALUE_PAWN) -
@@ -1127,9 +1176,9 @@ Return value:
}
-static void
-_NewRecognizer(IN RECOGNIZER *pFunct,
- IN ULONG uWhiteSig,
+static void
+_NewRecognizer(IN RECOGNIZER *pFunct,
+ IN ULONG uWhiteSig,
IN ULONG uBlackSig)
/**
@@ -1154,7 +1203,7 @@ Return value:
g_pRecognizers[RECOGN_INDEX(uWhiteSig, uBlackSig)] = pFunct;
}
-void
+void
InitializeInteriorNodeRecognizers(void)
/**
@@ -1180,7 +1229,7 @@ Return value:
_MakeMaterialSig(0, 0, 0, 0, 0),
_MakeMaterialSig(0, 0, 0, 0, 0));
- // KB+K P N B R Q
+ // KB+K P N B R Q
_NewRecognizer(_RecognizeKBK,
_MakeMaterialSig(0, 0, 1, 0, 0),
_MakeMaterialSig(0, 0, 0, 0, 0));
@@ -1189,61 +1238,67 @@ Return value:
_NewRecognizer(_RecognizeKBK,
_MakeMaterialSig(0, 0, 1, 0, 0),
_MakeMaterialSig(0, 0, 1, 0, 0));
-
- // KN+K P N B R Q
+
+ // KN+K P N B R Q
_NewRecognizer(_RecognizeKNK,
- _MakeMaterialSig(0, 1, 0, 0, 0),
+ _MakeMaterialSig(0, 1, 0, 0, 0),
_MakeMaterialSig(0, 0, 0, 0, 0));
- // KN+KN+ P N B R Q
- _NewRecognizer(_RecognizeKNK,
- _MakeMaterialSig(0, 1, 0, 0, 0),
+ // KN+KN+ P N B R Q
+ _NewRecognizer(_RecognizeKNK,
+ _MakeMaterialSig(0, 1, 0, 0, 0),
_MakeMaterialSig(0, 1, 0, 0, 0));
- // KN+KB+ P N B R Q
- _NewRecognizer(_RecognizeKBNK,
- _MakeMaterialSig(0, 1, 0, 0, 0),
- _MakeMaterialSig(0, 0, 1, 0, 0));
-
- // KN+B+K P N B R Q
- _NewRecognizer(_RecognizeKBNK,
- _MakeMaterialSig(0, 1, 1, 0, 0),
- _MakeMaterialSig(0, 0, 0, 0, 0));
+ // KN+KB+ and KN+B+K disabled: _RecognizeKBNK's lone-king mating
+ // branch was found wrong by testrecogn.c's EGTB cross-check (the
+ // KBN-vs-K "wrong corner" subtlety -- see the comment on
+ // _RecognizeKBNK above). Not registered until that's fixed.
+ //
+ // _NewRecognizer(_RecognizeKBNK,
+ // _MakeMaterialSig(0, 1, 0, 0, 0),
+ // _MakeMaterialSig(0, 0, 1, 0, 0));
+ //
+ // _NewRecognizer(_RecognizeKBNK,
+ // _MakeMaterialSig(0, 1, 1, 0, 0),
+ // _MakeMaterialSig(0, 0, 0, 0, 0));
- // KN+KP+ P N B R Q
- _NewRecognizer(_RecognizeKNKP,
- _MakeMaterialSig(1, 0, 0, 0, 0),
+ // KN+KP+ -- re-enabled for exactly one knight vs exactly one pawn
+ // only (the function itself bails to UNRECOGNIZED for anything
+ // else); see the comment on _RecognizeKNKP for the exhaustive
+ // proof backing this. P N B R Q
+ _NewRecognizer(_RecognizeKNKP,
+ _MakeMaterialSig(1, 0, 0, 0, 0),
_MakeMaterialSig(0, 1, 0, 0, 0));
- // KB+KP+ P N B R Q
- _NewRecognizer(_RecognizeKBKP,
- _MakeMaterialSig(1, 0, 0, 0, 0),
+ // KB+KP+ P N B R Q
+ _NewRecognizer(_RecognizeKBKP,
+ _MakeMaterialSig(1, 0, 0, 0, 0),
_MakeMaterialSig(0, 0, 1, 0, 0));
-
- // KP+B+KP+ P N B R Q
- _NewRecognizer(_RecognizeKBKP,
- _MakeMaterialSig(1, 0, 1, 0, 0),
+
+ // KP+B+KP+ P N B R Q
+ _NewRecognizer(_RecognizeKBKP,
+ _MakeMaterialSig(1, 0, 1, 0, 0),
_MakeMaterialSig(1, 0, 0, 0, 0));
-
+
// KP+B+K P N B R Q
- _NewRecognizer(_RecognizeKBKP,
- _MakeMaterialSig(1, 0, 1, 0, 0),
+ _NewRecognizer(_RecognizeKBKP,
+ _MakeMaterialSig(1, 0, 1, 0, 0),
_MakeMaterialSig(0, 0, 0, 0, 0));
- // KP+K P N B R Q
- _NewRecognizer(_RecognizeKPK,
- _MakeMaterialSig(0, 0, 0, 0, 0),
+ // KP+K P N B R Q
+ _NewRecognizer(_RecognizeKPK,
+ _MakeMaterialSig(0, 0, 0, 0, 0),
_MakeMaterialSig(1, 0, 0, 0, 0));
// KP+KP+ P N B R Q
- _NewRecognizer(_RecognizeKPK,
- _MakeMaterialSig(1, 0, 0, 0, 0),
+ _NewRecognizer(_RecognizeKPK,
+ _MakeMaterialSig(1, 0, 0, 0, 0),
_MakeMaterialSig(1, 0, 0, 0, 0));
}
-ULONG
+ULONG
RecognLookup(IN SEARCHER_THREAD_CONTEXT *ctx,
IN OUT SCORE *piScore,
IN FLAG fProbeEGTB)
@@ -1277,7 +1332,7 @@ Return value:
//
// Try interior node recognizers
- //
+ //
if ((pos->uNonPawnCount[WHITE][0] <= 3) &&
(pos->uNonPawnCount[BLACK][0] <= 3))
{
@@ -1309,11 +1364,16 @@ Return value:
}
//
- // Try EGTB probe as long as some conditions are met
- //
- if ((FALSE != fProbeEGTB) &&
- ((pos->uNonPawnCount[WHITE][0] + pos->uNonPawnCount[BLACK][0] +
- pos->uPawnCount[WHITE] + pos->uPawnCount[BLACK]) <= 5))
+ // Try EGTB probe. No piece-count gate here: ProbeEGTB already
+ // checks the position's piece count against the dynamic
+ // TB_LARGEST (set from whatever tablebase files Fathom actually
+ // found at init), so hardcoding a ceiling here would only ever
+ // make this stricter than what's really installed, silently
+ // capping us below the on-disk tables (e.g. if 6-man WDL files are
+ // ever added alongside the 5-man set already present -- see
+ // CLAUDE.md).
+ //
+ if (FALSE != fProbeEGTB)
{
if (TRUE == ProbeEGTB(ctx, &iScore))
{