summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/chess.h43
-rwxr-xr-xsrc/command.c18
-rwxr-xr-xsrc/data.c16
-rwxr-xr-xsrc/draw.c12
-rwxr-xr-xsrc/dynamic.c3
-rwxr-xr-xsrc/eval.c15
-rwxr-xr-xsrc/fen.c84
-rwxr-xr-xsrc/ics.c2
-rwxr-xr-xsrc/main.c12
-rw-r--r--src/recogn.c388
-rwxr-xr-xsrc/root.c66
-rwxr-xr-xsrc/see.c175
-rw-r--r--src/testsee.c106
-rw-r--r--src/x64.asm7
14 files changed, 491 insertions, 456 deletions
diff --git a/src/chess.h b/src/chess.h
index 8d16837..7d01e64 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -945,6 +945,16 @@ typedef struct _COUNTERS
UINT64 u64EvalHashHits;
UINT64 u64LazyEvals;
UINT64 u64FullEvals;
+ // Placeholder counters for root.c's per-tier eval-exit
+ // reporting -- eval.c's super-lazy exit itself hasn't been
+ // re-applied yet (see stash), so these always read 0 for now;
+ // that's accurate, not a stub bug, since no super-lazy exit
+ // exists in this build to increment them.
+ UINT64 u64SuperLazyEvals;
+ UINT64 u64CyclesSuperLazyExit;
+ UINT64 u64CyclesLazyExit;
+ UINT64 u64CyclesFullEvalExit;
+ UINT64 u64CyclesEvalSuperLazy;
UINT64 u64CyclesInEval;
//
@@ -2812,6 +2822,13 @@ IsDraw(SEARCHER_THREAD_CONTEXT *ctx);
//
#define QPLIES_OF_NON_CAPTURE_CHECKS (2)
#define FUTILITY_BASE_MARGIN (50)
+// Compatibility aliases for root.c/main.c's per-tier reporting, which
+// expects these three names -- search.c hasn't been split into
+// per-tier margins yet (still one flat FUTILITY_BASE_MARGIN), so all
+// three alias the same value until that split is re-applied.
+#define FUTILITY_BASE_MARGIN_FULL FUTILITY_BASE_MARGIN
+#define FUTILITY_BASE_MARGIN_LAZY FUTILITY_BASE_MARGIN
+#define FUTILITY_BASE_MARGIN_SUPERLAZY FUTILITY_BASE_MARGIN
// Measured: disabling this entirely (see lmr_testing/RESULTS.md) is a
// clear net loss across ringers/confident_quick/hard_quick, so IID itself
// is load-bearing. The "is the top move crappy" gate in search.c's DO_IID
@@ -3146,7 +3163,14 @@ PawnHashLookup(SEARCHER_THREAD_CONTEXT *ctx);
extern const int g_iAhead[2];
extern const int g_iBehind[2];
-ULONG
+// No-op placeholder: eval.c's ROOK_FULL_HALF_OPEN_BONUS static cache
+// (the thing InitEval() is meant to (re)build, called at startup and
+// after every DNA reload) hasn't been re-applied yet -- see stash.
+// Nothing to initialize until that cache exists.
+void
+InitEval(void);
+
+ULONG
DNABufferSizeBytes();
char *
@@ -3167,7 +3191,7 @@ SCORE
Eval(SEARCHER_THREAD_CONTEXT *, SCORE, SCORE, SCORE *);
FLAG
-EvalPasserRaces(POSITION *,
+_EvalPasserRacesAgainstLoneKings(POSITION *,
PAWN_HASH_ENTRY *);
ULONG
@@ -3362,17 +3386,12 @@ _WhoAttacksSquareBB(POSITION *pos,
ULONG uSide,
BITBOARD bbOccupied);
-// Three-way choice for which GetAttacks implementation is actually
-// live -- see MIGRATION.md section 6:
-// GETATTACKS_BITBOARD defined -> _GetAttacksBB (bitboard, new)
-// else CROUTINES defined -> SlowGetAttacks (C mailbox)
-// else (default) -> GetAttacks (asm x86/x64 mailbox,
-// the literal function declared above)
-#if defined(GETATTACKS_BITBOARD)
+// _GetAttacksBB is the only implementation as of 2026-09-06 (see
+// MIGRATION.md section 6) -- verified correct and faster than the old
+// asm/CROUTINES mailbox versions (SlowGetAttacks, asm GetAttacks),
+// which have been retired. Every call site written against the name
+// "GetAttacks" didn't need touching when the default changed.
#define GetAttacks _GetAttacksBB
-#elif defined(CROUTINES)
-#define GetAttacks SlowGetAttacks
-#endif
#ifdef _X86_
//
diff --git a/src/command.c b/src/command.c
index cf8f939..e6bf9fc 100755
--- a/src/command.c
+++ b/src/command.c
@@ -732,6 +732,13 @@ Return value:
{
Trace("Error reading dna file.\n");
} else {
+ // ROOK_FULL_HALF_OPEN_BONUS is a static cache of
+ // ROOK_ON_FULL_OPEN/ROOK_ON_HALF_OPEN_WITH_ENEMY/
+ // ROOK_ON_HALF_OPEN_WITH_FRIEND, kept static rather than
+ // rebuilt every _EvalRook call since it's speed-critical
+ // code -- a DNA reload has to explicitly refresh it.
+ InitEval();
+
// EvalCommand's persistent SEARCHER_THREAD_CONTEXT keeps
// its embedded eval hash alive across calls for speed; a
// DNA reload must invalidate stale cached scores from the
@@ -807,11 +814,20 @@ Return value:
#endif
return;
}
+ if (argc >= 2 && !STRCMPI(argv[1], "qsearchfutility"))
+ {
+#ifdef CALIBRATE_QSEARCH_FUTILITY
+ DumpQSearchFutilityCalibration();
+#else
+ Trace("This binary was not built with CALIBRATE_QSEARCH_FUTILITY.\n");
+#endif
+ return;
+ }
#ifdef CALIBRATE_POSITIONAL
if ((argc < 2) || STRCMPI(argv[1], "dump"))
{
Trace("Usage: calibrate dump | calibrate basemargin | "
- "calibrate marginsafety\n");
+ "calibrate marginsafety | calibrate qsearchfutility\n");
return;
}
DumpPositionalCalibration();
diff --git a/src/data.c b/src/data.c
index aa5ef91..7c84b1a 100755
--- a/src/data.c
+++ b/src/data.c
@@ -363,15 +363,15 @@ InitializeSwapTable(void)
2, // 00011 = 3
4, // 00100 = 4
4, // 00101 = 5
- 4, // 00110 = 6
+ 4, // 00110 = 6
4, // 00111 = 7
- 8, // 01000 = 8
+ 8, // 01000 = 8
8, // 01001 = 9
8, // 01010 = 10
8, // 01011 = 11
8, // 01100 = 12
8, // 01101 = 13
- 8, // 01110 = 14
+ 8, // 01110 = 14
8, // 01111 = 15
16, // 10000 = 16
16, // 10001 = 17
@@ -386,7 +386,7 @@ InitializeSwapTable(void)
16, // 11010 = 26
16, // 11011 = 27
16, // 11100 = 28
- 16, // 11101 = 29
+ 16, // 11101 = 29
16, // 11110 = 30
16 // 11111 = 31
};
@@ -399,7 +399,7 @@ InitializeSwapTable(void)
ULONG uGains[2];
ULONG uAttacks[2];
INT iDiff;
-
+
for (p = 0; p <= WHITE_KING; p++)
{
if (p > 1)
@@ -419,7 +419,7 @@ InitializeSwapTable(void)
}
//
- // Ok, if the side on move has an attack, play it
+ // Ok, if the side on move has an attack, play it
// and give them credit for some plunder.
//
while(uAttacks[uOnMove])
@@ -464,7 +464,7 @@ InitializeSwapTable(void)
uAttacks[uOnMove] &= ~GET_HIGH_BIT[uAttacks[uOnMove]];
uOnMove = FLIP(uOnMove);
}
-
+
//
// The side on move doesn't have an attack... does
// the side not on move have an attack? If so they
@@ -517,7 +517,7 @@ InitializeSwapTable(void)
}
}
}
- }
+ }
}
}
diff --git a/src/draw.c b/src/draw.c
index 7b2b1d5..7df10b4 100755
--- a/src/draw.c
+++ b/src/draw.c
@@ -29,10 +29,10 @@ IsDraw(SEARCHER_THREAD_CONTEXT *ctx)
{
ULONG uPly;
UINT64 u64CurrentSig;
-
+
//
// Recognize 50-moves w/o progress draw rule
- //
+ //
if (ctx->sPosition.uFifty >= 100)
{
return(TRUE);
@@ -40,7 +40,7 @@ IsDraw(SEARCHER_THREAD_CONTEXT *ctx)
//
// Check for repeated positions if needed.
- //
+ //
if (ctx->sPosition.uFifty < 4)
{
return(FALSE);
@@ -52,7 +52,7 @@ IsDraw(SEARCHER_THREAD_CONTEXT *ctx)
while(uPly < MAX_PLY_PER_SEARCH)
{
#ifdef DEBUG
- if ((GET_COLOR(ctx->sPlyInfo[uPly].mv.pMoved) !=
+ if ((GET_COLOR(ctx->sPlyInfo[uPly].mv.pMoved) !=
ctx->sPosition.uToMove) &&
(ctx->sPlyInfo[uPly].mv.uMove != 0))
{
@@ -65,7 +65,7 @@ IsDraw(SEARCHER_THREAD_CONTEXT *ctx)
{
return(TRUE);
}
-
+
if (IS_PAWN(ctx->sPlyInfo[uPly].mv.pMoved) ||
(ctx->sPlyInfo[uPly].mv.pCaptured))
{
@@ -73,7 +73,7 @@ IsDraw(SEARCHER_THREAD_CONTEXT *ctx)
}
uPly -= 2;
}
-
+
//
// Keep looking in the official game record.
//
diff --git a/src/dynamic.c b/src/dynamic.c
index a923e1a..992b842 100755
--- a/src/dynamic.c
+++ b/src/dynamic.c
@@ -618,6 +618,7 @@ Return value:
FLAG fHaveValue = FALSE;
COOR c, c1;
+ // Two different pieces hanging.
if (_EnpriseSlotValid(ctx, 0, uSide) && _EnpriseSlotValid(ctx, 1, uSide))
{
c = ctx->cEnprise[uPly][0];
@@ -627,6 +628,8 @@ Return value:
PIECE_VALUE(ctx->sPosition.rgSquare[c1].pPiece));
fHaveValue = TRUE;
}
+
+ // A trapped piece under attack with nowhere to run.
if (_TrappedSlotValid(ctx, uSide))
{
c = ctx->cTrapped[uPly];
diff --git a/src/eval.c b/src/eval.c
index 63fd301..06e6bc7 100755
--- a/src/eval.c
+++ b/src/eval.c
@@ -4386,7 +4386,7 @@ Return value:
FLAG
-EvalPasserRaces(IN OUT POSITION *pos,
+_EvalPasserRacesAgainstLoneKings(IN OUT POSITION *pos,
IN PAWN_HASH_ENTRY *pHash)
/**
@@ -4427,7 +4427,7 @@ Return value:
// Both deferred past the no-passer early return above -- no need to
// pay for this on the common case (most positions have no passer at
- // all, and EvalPasserRaces itself is only even called when a side
+ // all, and _EvalPasserRacesAgainstLoneKings itself is only even called when a side
// is down to a bare king, so this function runs unconditionally in
// the pre-lazy-exit segment whenever that's true).
uRacerDist[BLACK] = 99;
@@ -4962,6 +4962,15 @@ Return value:
+void
+InitEval(void)
+{
+ // No-op placeholder: nothing to initialize until the
+ // ROOK_FULL_HALF_OPEN_BONUS static cache (from the eval.c overhaul,
+ // not yet re-applied here -- see stash) exists.
+}
+
+
SCORE
Eval(IN SEARCHER_THREAD_CONTEXT *ctx,
IN SCORE iAlpha,
@@ -5074,7 +5083,7 @@ Return value:
if ((pos->uNonPawnCount[WHITE][0] == 1) ||
(pos->uNonPawnCount[BLACK][0] == 1))
{
- (void)EvalPasserRaces(pos, pHash);
+ (void)_EvalPasserRacesAgainstLoneKings(pos, pHash);
#ifdef EVAL_DUMP
Trace("After passer races:\n%d\t\t%d\n", pos->iScore[WHITE],
pos->iScore[BLACK]);
diff --git a/src/fen.c b/src/fen.c
index b42ee04..33810e6 100755
--- a/src/fen.c
+++ b/src/fen.c
@@ -588,32 +588,94 @@ Return value:
**/
{
- ULONG u = 0;
+ ULONG u = 1;
+ ULONG uChunkIndex = 0;
int i;
CHAR *q, *op;
-
+
p->uFifty = 0;
+ //
+ // A FEN that omits the halfmove-clock/fullmove-number suffix
+ // entirely (routine in EPD test suites like tests/ecm.ep_ -- e.g.
+ // "... w - -" with nothing after the en passant field) makes
+ // szFifty NULL here (FindChunk(szCapturedFen, 5) found no 5th
+ // chunk at all). The original code happened to survive that
+ // silently because FindChunk(NULL, 0) hits its "0 means whole
+ // string" fast path and returns NULL without ever dereferencing
+ // sz; starting at chunk 1 below (see the comment further down)
+ // loses that accidental safety net since chunk-1 lookups always
+ // dereference sz. Handle it explicitly instead of relying on
+ // FindChunk's early-return shape again -- found live by
+ // precommit_check.sh's debug_smoke_test.sh crashing on the very
+ // first random ecm.ep_ position after the chunk-index fix below
+ // was added.
+ //
+ if (NULL == szFifty)
+ {
+ return;
+ }
+ //
+ // Start at chunk 1 (FindChunk's convention: 0 means "whole
+ // remaining string", 1 means "the first token"), not 0. Starting
+ // at 0 here made this loop visit the first token twice -- once via
+ // the u==0 "whole string" fetch below, again via the very first
+ // u==1 fetch inside the loop -- throwing off the uChunkIndex
+ // bookkeeping used to tell the halfmove clock (real chunk 1) apart
+ // from the fullmove number (real chunk 2) below.
+ //
q = FindChunk(szFifty, u);
u++;
while(NULL != q)
{
//printf("%u: %s\n", u-1, q);
-
- i = atoi(q);
- if ((i > 0) && (i < 100))
+
+ if (0 == uChunkIndex)
+ {
+ //
+ // In a real FEN string this chunk is always the halfmove
+ // (fifty-move-rule) clock -- unlike every later chunk, 0 is
+ // a legitimate value here (a fresh game, or right after a
+ // capture/pawn move), not "absent". The generic i>0 check
+ // below would skip a genuine 0 and fall through to the
+ // *next* chunk (the fullmove number) instead, silently
+ // assigning that to uFifty. Confirmed live: the engine's
+ // own hardcoded starting-position FEN ("... - 0 1") was
+ // parsed as uFifty=1 (the fullmove number) rather than 0.
+ //
+ i = atoi(q);
+ if ((i >= 0) && (i < 100))
+ {
+ p->uFifty = (ULONG)i;
+ }
+ }
+ else if (1 == uChunkIndex)
{
- p->uFifty = (ULONG)i;
+ //
+ // The second chunk is the fullmove number, not used by
+ // this engine -- just consumed here so the generic
+ // EPD-opcode scan below doesn't misread it as a repeated
+ // (and wrong) fifty-move value.
+ //
}
else
{
- if (!STRCMPI(q, "bm"))
+ i = atoi(q);
+ if ((i > 0) && (i < 100))
{
- op = FindChunk(szFifty, u);
- u++;
- if (NULL == op) break;
+ p->uFifty = (ULONG)i;
+ }
+ else
+ {
+ if (!STRCMPI(q, "bm"))
+ {
+ op = FindChunk(szFifty, u);
+ u++;
+ if (NULL == op) break;
+ }
+
}
-
}
+ uChunkIndex++;
q = FindChunk(szFifty, u);
u++;
}
diff --git a/src/ics.c b/src/ics.c
index fba1af6..0ace535 100755
--- a/src/ics.c
+++ b/src/ics.c
@@ -60,7 +60,7 @@ Return value:
*p++ = RANK(mv.cFrom) + '0';
*p++ = FILE(mv.cTo) + 'a';
*p++ = RANK(mv.cTo) + '0';
-
+
if (mv.pPromoted)
{
if (IS_QUEEN(mv.pPromoted))
diff --git a/src/main.c b/src/main.c
index d697532..351e848 100755
--- a/src/main.c
+++ b/src/main.c
@@ -83,7 +83,9 @@ Return value:
(g_uHashTableSizeEntries * sizeof(HASH_ENTRY)) / MB,
PAWN_HASH_TABLE_SIZE * sizeof(PAWN_HASH_ENTRY) / MB);
Trace(" QCheckPlies: %u\n", QPLIES_OF_NON_CAPTURE_CHECKS);
- Trace(" FutilityBase: %u\n", FUTILITY_BASE_MARGIN);
+ Trace(" FutilityBase: full=%u lazy=%u superlazy=%u\n",
+ FUTILITY_BASE_MARGIN_FULL, FUTILITY_BASE_MARGIN_LAZY,
+ FUTILITY_BASE_MARGIN_SUPERLAZY);
p = ExportEvalDNA();
Trace(" Logging Eval DNA.\n");
Log(p);
@@ -475,6 +477,7 @@ Return value:
InitializeDynamicMoveOrdering();
InitLMRTable();
InitializeHashSystem();
+ InitEval();
#ifdef MP
InitializeParallelSearch();
#endif
@@ -586,6 +589,13 @@ Return value:
}
#endif
+ // TODO(temporary): TestRecogn hoisted to the front of the self-test
+ // sequence while iterating on recogn.c so a failure doesn't require
+ // waiting through the slow move-gen speed benchmarks first. Move
+ // back down next to TestMakeUnmakeMove (its natural home) once
+ // recogn.c work settles.
+ TestRecogn();
+
TestDraw();
#ifdef EVAL_DUMP
TestEval();
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))
{
diff --git a/src/root.c b/src/root.c
index d7113c0..5b51009 100755
--- a/src/root.c
+++ b/src/root.c
@@ -549,15 +549,47 @@ Return value:
Trace("First move beta cutoff rate was %5.3f percent.\n",
((n / d) * 100.0));
#ifdef LAZY_EVAL
- d = (double)ctx->sCounters.tree.u64LazyEvals;
- d += (double)ctx->sCounters.tree.u64FullEvals;
- d += (double)ctx->sCounters.tree.u64EvalHashHits;
- d += 1;
- ASSERT(d);
- Trace("Eval percentages: (%5.2f hash, %5.2f lazy, %5.2f full)\n",
- ((double)ctx->sCounters.tree.u64EvalHashHits / d) * 100.0,
- ((double)ctx->sCounters.tree.u64LazyEvals / d) * 100.0,
- ((double)ctx->sCounters.tree.u64FullEvals / d) * 100.0);
+ {
+ // u64CyclesSuperLazyExit/u64CyclesLazyExit/u64CyclesFullEvalExit
+ // are each the *total call cost* (entry to exit) of Eval()
+ // calls that left via that specific path -- mutually exclusive
+ // and summing to u64CyclesInEval, so dividing each by its own
+ // matching call count gives a real per-path average, and
+ // u64CyclesInEval / dRealTotal gives a real overall average --
+ // unlike averaging u64CyclesInEval (which used to only
+ // accumulate on the full-eval path) against the total call
+ // count across all three paths.
+ double dSuperLazy = (double)ctx->sCounters.tree.u64SuperLazyEvals;
+ double dLazy = (double)ctx->sCounters.tree.u64LazyEvals;
+ double dFull = (double)ctx->sCounters.tree.u64FullEvals;
+ double dRealTotal = dSuperLazy + dLazy + dFull;
+ d = dRealTotal + 1;
+ Trace("Eval exit breakdown: (%5.2f%% super lazy, %5.2f%% lazy, "
+ "%5.2f%% full)\n",
+ (dSuperLazy / d) * 100.0,
+ (dLazy / d) * 100.0,
+ (dFull / d) * 100.0);
+#ifdef EVAL_TIME
+ {
+ UINT64 u64SLCyc = ctx->sCounters.tree.u64CyclesSuperLazyExit;
+ UINT64 u64LCyc = ctx->sCounters.tree.u64CyclesLazyExit;
+ UINT64 u64FCyc = ctx->sCounters.tree.u64CyclesFullEvalExit;
+ UINT64 u64AllCyc = ctx->sCounters.tree.u64CyclesInEval;
+ Trace("Avg. cpu cycles in eval, by exit path:\n"
+ " super lazy: %8.1f (%5.1f%% of total eval cycles)\n"
+ " lazy: %8.1f (%5.1f%% of total eval cycles)\n"
+ " full: %8.1f (%5.1f%% of total eval cycles)\n"
+ " overall: %8.1f\n",
+ (dSuperLazy ? (double)u64SLCyc / dSuperLazy : 0.0),
+ (u64AllCyc ? 100.0 * (double)u64SLCyc / (double)u64AllCyc : 0.0),
+ (dLazy ? (double)u64LCyc / dLazy : 0.0),
+ (u64AllCyc ? 100.0 * (double)u64LCyc / (double)u64AllCyc : 0.0),
+ (dFull ? (double)u64FCyc / dFull : 0.0),
+ (u64AllCyc ? 100.0 * (double)u64FCyc / (double)u64AllCyc : 0.0),
+ (dRealTotal ? (double)u64AllCyc / dRealTotal : 0.0));
+ }
+#endif
+ }
#endif
Trace("Extensions: (%u +, %u q+, %u 1mv, %u !kmvs, %u mult+, %u pawn\n"
" %u threat, %u zug, %u sing, %u endg, %u bm, %u recap)\n",
@@ -574,8 +606,6 @@ Return value:
ctx->sCounters.extension.uBotvinnikMarkoff,
ctx->sCounters.extension.uRecapture);
#ifdef EVAL_TIME
- n = (double)ctx->sCounters.tree.u64CyclesInEval;
- Trace("Avg. cpu cycles in eval: %8.1f.\n", (n / d));
{
//
// Per-term breakdown of the average above -- board_
@@ -631,9 +661,14 @@ Return value:
UINT64 u64LazyDecision = ctx->sCounters.tree.u64CyclesEvalLazyDecision;
UINT64 u64CKSD = ctx->sCounters.tree.u64CyclesEvalCountKingSafetyDefects;
UINT64 u64Storm = ctx->sCounters.tree.u64CyclesEvalFileStormDefects;
+ UINT64 u64SuperLazy = ctx->sCounters.tree.u64CyclesEvalSuperLazy;
UINT64 u64PreLazyRest = (u64PreLazyOther >= u64LazyDecision) ?
(u64PreLazyOther - u64LazyDecision) : 0;
+ u64PreLazyRest = (u64PreLazyRest >= u64SuperLazy) ?
+ (u64PreLazyRest - u64SuperLazy) : 0;
Trace(" -- of which, pre-lazy breakdown --\n");
+ Trace(" super lazy check: %5.1f%%\n",
+ (u64Total ? (100.0 * (double)u64SuperLazy / (double)u64Total) : 0.0));
Trace(" material/passers/badtrades/bishoppairs: %5.1f%%\n",
(u64Total ? (100.0 * (double)u64PreLazyRest / (double)u64Total) : 0.0));
Trace(" lazy gate + EstimatePositionalScore: %5.1f%% "
@@ -662,6 +697,15 @@ Return value:
}
#endif
#endif
+ // Unconditional, build-flag-independent end-of-report marker. Every
+ // block above this point is gated behind some #ifdef (PERF_COUNTERS,
+ // LAZY_EVAL, EVAL_TIME, ...), so the exact shape/length of this
+ // report varies build to build -- a tool driving this engine over
+ // the xboard protocol (e.g. eval_tune/match_play.py) has no
+ // build-flag-independent way to know the report is fully drained
+ // before sending its next command otherwise. This line is always
+ // printed exactly once, always last, regardless of build profile.
+ Trace("ReportEnd\n");
}
diff --git a/src/see.c b/src/see.c
index dc308d5..893a9bb 100755
--- a/src/see.c
+++ b/src/see.c
@@ -28,144 +28,17 @@ Revision History:
pList->data[pList->uCount].uVal = (v); \
pList->uCount++;
-void CDECL
-SlowGetAttacks(IN OUT SEE_LIST *pList,
- IN POSITION *pos,
- IN COOR cSquare,
- IN ULONG uSide)
-/*++
-
-Routine description:
-
- SlowGetAttacks is the C version of GetAttacks; it should be
- identical to the GetAttacks code in x86.asm. The job of the
- function is, given a position, square and side, to populate the
- SEE_LIST with the locations and types of enemy pieces attacking
- the square.
-
-Parameters:
-
- SEE_LIST *pList : list to populate
- POSITION *pos : the board
- COOR cSquare : square in question
- ULONG uSide : side we are looking for attacks from
-
-Return value:
-
- void
-
---*/
-{
- register ULONG x;
- PIECE p;
- COOR c;
- int iIndex;
- COOR cBlockIndex;
- int iDelta;
- static PIECE pPawn[2] = { BLACK_PAWN, WHITE_PAWN };
- static int iSeeDelta[2] = { -17, +15 };
-
-#ifdef DEBUG
- ASSERT(IS_ON_BOARD(cSquare));
- ASSERT(IS_VALID_COLOR(uSide));
- VerifyPositionConsistency(pos, FALSE);
-#endif
- pList->uCount = 0;
-
- //
- // Check for pawns attacking cSquare
- //
- c = cSquare + (iSeeDelta[uSide]);
- if (IS_ON_BOARD(c))
- {
- p = pos->rgSquare[c].pPiece;
- if (p == pPawn[uSide])
- {
- //
- // N.B. Don't use ADD_ATTACKER here because we know we're
- // at element zero.
- //
- pList->data[0].pPiece = p;
- pList->data[0].cLoc = c;
- pList->data[0].uVal = VALUE_PAWN;
- pList->uCount = 1;
- }
- }
-
- c += 2;
- if (IS_ON_BOARD(c))
- {
- p = pos->rgSquare[c].pPiece;
- if (p == pPawn[uSide])
- {
- ADD_ATTACKER(p, c, VALUE_PAWN);
- }
- }
-
- //
- // Check for pieces attacking cSquare
- //
- for (x = pos->uNonPawnCount[uSide][0] - 1;
- x != (ULONG)-1;
- x--)
- {
- c = pos->cNonPawns[uSide][x];
- ASSERT(IS_ON_BOARD(c));
-
- p = pos->rgSquare[c].pPiece;
- ASSERT(p && !IS_PAWN(p));
- ASSERT(GET_COLOR(p) == uSide);
-
- iIndex = (int)c - (int)cSquare;
- if (0 == (CHECK_VECTOR_WITH_INDEX(iIndex, GET_COLOR(p)) &
- (1 << PIECE_TYPE(p))))
- {
- continue;
- }
-
- if (IS_KNIGHT_OR_KING(p))
- {
- ASSERT(IS_KNIGHT(p) || IS_KING(p));
- ADD_ATTACKER(p, c, PIECE_VALUE(p));
- continue;
- }
-
- //
- // Check to see if there is a piece in the path from cSquare
- // to c that blocks the attack.
- //
- iDelta = NEG_DELTA_WITH_INDEX(iIndex);
- ASSERT(iDelta == -1 * CHECK_DELTA_WITH_INDEX(iIndex));
- ASSERT(iDelta != 0);
- for (cBlockIndex = cSquare + iDelta;
- cBlockIndex != c;
- cBlockIndex += iDelta)
- {
- if (!IS_EMPTY(pos->rgSquare[cBlockIndex].pPiece))
- {
- goto done;
- }
- }
-
- //
- // Nothing in the way.
- //
- ADD_ATTACKER(p, c, PIECE_VALUE(p));
-
- done:
- ;
- }
-}
-
//
-// board_representation/MIGRATION.md section 3: bbPieces-backed
-// "who attacks square X" primitive, and a GetAttacks PoC built on
-// it. Not wired into the GetAttacks macro yet -- see MIGRATION.md
-// section 6 for the eventual toggle. Uses chess.h's FastFirstBit/
-// FastLastBit (static inline bsf/bsr wrappers) rather than the real
-// out-of-line FirstBit/LastBit -- worth avoiding call overhead in a
-// per-move-generated, per-node hot path like this one.
+// board_representation/MIGRATION.md sections 3/6/7: bbPieces-backed
+// "who attacks square X" primitive. _GetAttacksBB below is now the
+// only GetAttacks implementation -- the old mailbox SlowGetAttacks/
+// asm GetAttacks were retired 2026-09-06 once all of section 7's
+// retirement criteria (correctness sweep, isolated + whole-engine
+// benchmarks, match_play.py gate) cleared. Uses chess.h's
+// FastFirstBit/FastLastBit (static inline bsf/bsr wrappers) rather
+// than the real out-of-line FirstBit/LastBit -- worth avoiding call
+// overhead in a per-move-generated, per-node hot path like this one.
//
// (This file used to have its own static _BuildOccupiedBB here,
// byte-for-byte identical to generate.c's _BuildFullOccupiedBB --
@@ -191,8 +64,8 @@ Routine description:
Return a bitboard of every uSide knight/bishop/rook/queen/king
that attacks cSquare in the current position, blockers included.
- Pawns are deliberately excluded -- see GetAttacksBB, which handles
- them the same 2-square-delta way SlowGetAttacks always has (already
+ Pawns are deliberately excluded -- see _GetAttacksBB, which handles
+ them the same 2-square-delta way the old mailbox code did (already
O(1), nothing to improve).
Knights and the king are pure O(1) table/delta lookups (no
@@ -319,19 +192,21 @@ _GetAttacksBB(IN OUT SEE_LIST *pList,
Routine description:
- PROOF OF CONCEPT -- not called from anywhere yet, and not a
- replacement for GetAttacks/SlowGetAttacks until section 4/5/6 of
- board_representation/MIGRATION.md (correctness sweep, benchmark,
- toggle) are done. Reproduces SlowGetAttacks's exact semantics
- (same deliberately-approximate no-pin/no-en-passant contract) via
- _WhoAttacksSquareBB instead of the O(non-pawn-piece-count) mailbox
- walk -- pawns handled identically to SlowGetAttacks (2-square
- delta, unchanged, already O(1)).
+ The only GetAttacks implementation as of 2026-09-06 -- the old
+ mailbox SlowGetAttacks/asm GetAttacks retired once all of
+ board_representation/MIGRATION.md section 7's retirement criteria
+ cleared (correctness sweep, isolated + whole-engine benchmarks,
+ match_play.py gate). Reproduces the old mailbox code's exact
+ semantics (same deliberately-approximate no-pin/no-en-passant
+ contract) via _WhoAttacksSquareBB instead of an
+ O(non-pawn-piece-count) mailbox walk -- pawns handled the same way
+ the mailbox version always did (2-square delta, unchanged, already
+ O(1)).
- Attacker order is not guaranteed to match SlowGetAttacks -- see()
- sorts/heaps the list immediately after GetAttacks returns, so only
- the *set* of attackers needs to match, not the sequence
- (board_representation/MIGRATION.md section 4).
+ Attacker order is not guaranteed to match the old mailbox
+ implementation -- see() sorts/heaps the list immediately after
+ GetAttacks returns, so only the *set* of attackers needs to match,
+ not the sequence (board_representation/MIGRATION.md section 4).
Parameters:
diff --git a/src/testsee.c b/src/testsee.c
index d196d54..978e1c6 100644
--- a/src/testsee.c
+++ b/src/testsee.c
@@ -26,19 +26,6 @@ Revision History:
#include "chess.h"
-// This harness's job is to validate _GetAttacksBB against a fixed
-// baseline (the real asm/CROUTINES implementation), not to compare
-// the engine's own current GetAttacks macro target against itself --
-// but chess.h's GETATTACKS_BITBOARD toggle (board_representation/
-// MIGRATION.md section 6) can make that macro resolve to
-// _GetAttacksBB. Undefine it here so every "GetAttacks(...)" call
-// below always reaches the real asm/CROUTINES function (still
-// declared under that name in chess.h, just no longer macro-routed),
-// regardless of which implementation is live in production.
-#ifdef GetAttacks
-#undef GetAttacks
-#endif
-
// Same reasoning, for IsAttacked/InCheck (board_representation/
// MOVEGEN_MIGRATION.md section 6b's ISATTACKED_BITBOARD toggle):
// TestIsAttackedBB below must always be able to call the real mailbox
@@ -197,21 +184,21 @@ SeeListsAreEqual(SEE_LIST *pA, SEE_LIST *pB)
return TRUE;
}
+// This used to diff _GetAttacksBB against the mailbox asm GetAttacks and
+// the C SlowGetAttacks reference implementation; both were retired
+// 2026-09-06 (board_representation/MIGRATION.md section 7) once that
+// three-way comparison had run clean for long enough. With no second
+// implementation left to diff against, this is now just a crash/
+// no-degenerate-output smoke test plus a standalone cycles/call number.
void
-TestGetAttacks(void)
+TestGetAttacks(void)
{
POSITION pos;
ULONG u;
COOR c;
- SEE_LIST rgSlowList;
- SEE_LIST rgAsmList;
SEE_LIST rgBBList;
ULONG color;
-#if !defined(_X86_) && !defined(_X64_)
- return;
-#endif
-
Trace("Testing GetAttacks...\n");
for (u = 0; u < 20000; u++)
{
@@ -221,51 +208,17 @@ TestGetAttacks(void)
if (!IS_ON_BOARD(c)) continue;
for (color = BLACK; color <= WHITE; color++)
{
- SlowGetAttacks(&rgSlowList,
- &pos,
- c,
- color);
- GetAttacks(&rgAsmList,
- &pos,
- c,
- color);
- if (!SeeListsAreEqual(&rgSlowList, &rgAsmList))
- {
- UtilPanic(TESTCASE_FAILURE,
- &pos,
- "SEE_LIST mismatch", &rgSlowList, &rgAsmList,
- __FILE__, __LINE__);
- }
-
- // board_representation/MIGRATION.md section 3/4:
- // bbPieces-backed GetAttacks PoC, same correctness
- // gate as the asm/C comparison above.
- _GetAttacksBB(&rgBBList,
- &pos,
- c,
- color);
- if (!SeeListsAreEqual(&rgSlowList, &rgBBList))
- {
- UtilPanic(TESTCASE_FAILURE,
- &pos,
- "SEE_LIST mismatch (_GetAttacksBB)",
- &rgSlowList, &rgBBList,
- __FILE__, __LINE__);
- }
+ _GetAttacksBB(&rgBBList, &pos, c, color);
+ ASSERT(rgBBList.uCount <= ARRAY_LENGTH(rgBBList.data));
}
}
}
//
- // Speed: board_representation/MIGRATION.md section 5's isolated
- // cycles/call microbenchmark, pulled forward here since it's cheap
- // to add right alongside the correctness gate that just proved the
- // two implementations equivalent. Three positions spanning piece
- // density (opening/middlegame/endgame), SlowGetAttacks vs
- // _GetAttacksBB interleaved call-by-call (not phase-by-phase) to
- // cancel shared-box noise -- a red flag (flat or inverted result)
- // here would mean stopping before wiring this in any further, same
- // as the Eval occupancy-bitboard work that motivated this file.
+ // Speed: standalone cycles/call number, three positions spanning
+ // piece density (opening/middlegame/endgame). No longer a
+ // comparison (nothing left to compare against), just a number to
+ // watch for regressions over time.
{
static const char *rgszFen[3] =
{
@@ -279,26 +232,18 @@ TestGetAttacks(void)
};
POSITION posBench;
SEE_LIST rgList;
- UINT64 u64SlowTotal, u64AsmTotal, u64BBTotal, u64Start;
+ UINT64 u64BBTotal, u64Start;
ULONG uIter;
ULONG uSq;
COOR cBench;
ULONG uSide;
const ULONG uCallsPerPosition = 200000;
- // GetAttacks (unqualified) is the real production entry point --
- // the hand-tuned x86/x64 asm routine, not SlowGetAttacks (the C
- // reference used only for correctness comparison above). That's
- // the actual competitor _GetAttacksBB has to beat; SlowGetAttacks
- // is included only as a third data point, not the bar to clear.
- Trace("Benchmarking GetAttacks: asm GetAttacks vs SlowGetAttacks "
- "vs _GetAttacksBB (interleaved, %lu calls/position)...\n",
+ Trace("Benchmarking _GetAttacksBB (%lu calls/position)...\n",
uCallsPerPosition);
for (u = 0; u < 3; u++)
{
FenToPosition(&posBench, (char *)rgszFen[u]);
- u64SlowTotal = 0;
- u64AsmTotal = 0;
u64BBTotal = 0;
for (uIter = 0; uIter < uCallsPerPosition; uIter++)
{
@@ -308,28 +253,13 @@ TestGetAttacks(void)
if (!IS_ON_BOARD(cBench)) continue;
u64Start = SystemReadTimeStampCounter();
- GetAttacks(&rgList, &posBench, cBench, uSide);
- u64AsmTotal += (SystemReadTimeStampCounter() - u64Start);
-
- u64Start = SystemReadTimeStampCounter();
- SlowGetAttacks(&rgList, &posBench, cBench, uSide);
- u64SlowTotal += (SystemReadTimeStampCounter() - u64Start);
-
- u64Start = SystemReadTimeStampCounter();
_GetAttacksBB(&rgList, &posBench, cBench, uSide);
u64BBTotal += (SystemReadTimeStampCounter() - u64Start);
}
- printf(" %s: asm GetAttacks %" COMPILER_LONGLONG_UNSIGNED_FORMAT
- " cycles/call, SlowGetAttacks %"
- COMPILER_LONGLONG_UNSIGNED_FORMAT
- " cycles/call, _GetAttacksBB %"
- COMPILER_LONGLONG_UNSIGNED_FORMAT " cycles/call "
- "(BB is %.2fx asm)\n",
+ printf(" %s: _GetAttacksBB %" COMPILER_LONGLONG_UNSIGNED_FORMAT
+ " cycles/call\n",
rgszLabel[u],
- u64AsmTotal / uCallsPerPosition,
- u64SlowTotal / uCallsPerPosition,
- u64BBTotal / uCallsPerPosition,
- (double)u64BBTotal / (double)u64AsmTotal);
+ u64BBTotal / uCallsPerPosition);
}
}
}
diff --git a/src/x64.asm b/src/x64.asm
index 89351f2..fb42ff3 100644
--- a/src/x64.asm
+++ b/src/x64.asm
@@ -83,6 +83,12 @@ _CountBits:
.done: ret
int 3
+%if 0
+;; Retired 2026-09-06 (board_representation/MIGRATION.md section 7):
+;; _GetAttacksBB (see.c) is now the only GetAttacks implementation --
+;; all retirement criteria (correctness sweep, isolated + whole-engine
+;; benchmarks, match_play.py gate) cleared. Left assembled-out rather
+;; than deleted.
[GLOBAL GetAttacks]
[GLOBAL _GetAttacks]
@@ -262,6 +268,7 @@ _GetAttacks:
pop rbx
pop rbp
ret
+%endif ; 0 (retired GetAttacks)
%endif ; !CROUTINES
[GLOBAL LockCompareExchange]