summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xsrc/chess.h18
-rwxr-xr-xsrc/eval.c597
2 files changed, 359 insertions, 256 deletions
diff --git a/src/chess.h b/src/chess.h
index 421667f..6b09840 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -752,6 +752,24 @@ typedef struct _POSITION
BITBOARD bbRookAttacks[2];
BITBOARD bbRookXrayAttacks[2];
+ // Queen's turn to convert (board_representation/EVAL.md section
+ // 9, 2026-09-05): "which squares does this side's queen(s)
+ // attack," ORed in from the two-pass rook-direction/bishop-
+ // direction magic lookups _EvalQueen's mobility computation
+ // already needs (MOVEGEN_MIGRATION.md's own already-tested
+ // "combined 8-ray table measured slower than reusing the
+ // rook/bishop tables in two passes" finding -- reuse that
+ // structure here too). bbQueenXrayAttacks mirrors
+ // bbRookXrayAttacks/bbMinorXrayAttacks: squares seen past a
+ // friendly queen/rook/bishop battery partner, chained arbitrarily
+ // deep like rook's (not single-hop like bishop's original cut) --
+ // queen's own case table never x-rays past an enemy piece (unlike
+ // rook/bishop), only through friendly battery partners. King
+ // hasn't converted yet and still writes its own bits into the old
+ // bvAttacks structure.
+ BITBOARD bbQueenAttacks[2];
+ BITBOARD bbQueenXrayAttacks[2];
+
ULONG uWhiteSqBishopCount[2]; // num bishops on white squares
SCORE iMaterialBalance[2]; // material balance
diff --git a/src/eval.c b/src/eval.c
index 9eafc1a..12783ba 100755
--- a/src/eval.c
+++ b/src/eval.c
@@ -1416,6 +1416,116 @@ _IsSquareXrayedByRook(IN POSITION *pos,
return fResult;
}
+//
+// Queen's turn to convert (board_representation/EVAL.md section 9,
+// 2026-09-05) -- same transitional purpose and same DEBUG cross-check
+// discipline as _IsSquareAttackedByRook/_IsSquareXrayedByRook above,
+// now that queen no longer writes QUEEN_BIT/QUEEN_XRAY_BIT into
+// rgSquare[c|8].bvAttacks at all. Goes away once king converts and
+// _EvalKing reads pos->bbQueenAttacks/bbQueenXrayAttacks directly.
+//
+static FLAG
+_IsSquareAttackedByQueen(IN POSITION *pos,
+ IN ULONG uColor,
+ IN COOR c)
+{
+ BITBOARD sq = COOR_TO_BB(c);
+ FLAG fResult = (pos->bbQueenAttacks[uColor] & sq) != 0;
+#ifdef DEBUG
+ {
+ BITBOARD bbTrueQueenAttacks = 0;
+ ULONG u;
+ for (u = 1; u < pos->uNonPawnCount[uColor][0]; u++)
+ {
+ COOR cPiece = pos->cNonPawns[uColor][u];
+ if (IS_QUEEN(pos->rgSquare[cPiece].pPiece))
+ {
+ bbTrueQueenAttacks |= _RookAttacksBB(cPiece, pos->bbOccupied) |
+ _BishopAttacksBB(cPiece, pos->bbOccupied);
+ }
+ }
+ ASSERT(((bbTrueQueenAttacks & sq) != 0) == (fResult != 0));
+ }
+#endif
+ return fResult;
+}
+
+//
+// Companion to _IsSquareAttackedByQueen. Unlike rook/bishop, queen's
+// own case table never x-rays through *any* enemy piece (QMOB_ENEMY_GE
+// always stops) -- only through a friendly queen, or a friendly rook
+// on an orthogonal ray / friendly bishop on a diagonal ray (the same
+// per-ray-family split _EvalQueen's own mobility walk uses). Chains
+// arbitrarily deep, same as rook's own x-ray (not single-hop).
+//
+static FLAG
+_IsSquareXrayedByQueen(IN POSITION *pos,
+ IN ULONG uColor,
+ IN COOR c)
+{
+ BITBOARD sq = COOR_TO_BB(c);
+ FLAG fResult = (pos->bbQueenXrayAttacks[uColor] & sq) != 0;
+#ifdef DEBUG
+ {
+ BITBOARD bbTrueXray = 0;
+ ULONG u;
+ for (u = 1; u < pos->uNonPawnCount[uColor][0]; u++)
+ {
+ COOR cQueen = pos->cNonPawns[uColor][u];
+ ULONG d;
+ if (!IS_QUEEN(pos->rgSquare[cQueen].pPiece))
+ {
+ continue;
+ }
+ for (d = 0; d < 8; d++)
+ {
+ COOR cWalk = cQueen + g_iQKDeltas[d];
+ FLAG fOrthogonal;
+
+ if (!IS_ON_BOARD(cWalk))
+ {
+ continue;
+ }
+ fOrthogonal = (((cWalk & 0xF0) == (cQueen & 0xF0)) ||
+ ((cWalk & 0x0F) == (cQueen & 0x0F)));
+ while (IS_ON_BOARD(cWalk) && IS_EMPTY(pos->rgSquare[cWalk].pPiece))
+ {
+ cWalk += g_iQKDeltas[d];
+ }
+ while (IS_ON_BOARD(cWalk))
+ {
+ PIECE pq = pos->rgSquare[cWalk].pPiece;
+ FLAG fWorthy;
+
+ if (GET_COLOR(pq) != uColor)
+ {
+ break;
+ }
+ fWorthy = IS_QUEEN(pq) ||
+ (fOrthogonal ? IS_ROOK(pq) : IS_BISHOP(pq));
+ if (!fWorthy)
+ {
+ break;
+ }
+ cWalk += g_iQKDeltas[d];
+ while (IS_ON_BOARD(cWalk))
+ {
+ bbTrueXray |= COOR_TO_BB(cWalk);
+ if (!IS_EMPTY(pos->rgSquare[cWalk].pPiece))
+ {
+ break;
+ }
+ cWalk += g_iQKDeltas[d];
+ }
+ }
+ }
+ }
+ ASSERT(((bbTrueXray & sq) != 0) == (fResult != 0));
+ }
+#endif
+ return fResult;
+}
+
static ULONG
_WhoControlsSquareFast(IN POSITION *pos,
IN COOR c)
@@ -1449,10 +1559,10 @@ Return value:
//
// .uXray is a standalone byte view (see _IsSquareXrayedByMinor's
// comment on why the byte-scale MINOR_BIT, not MINOR_XRAY_BIT, is
- // the right constant to OR in here) -- bishop's and now rook's
- // contributions moved to pos->bbMinorXrayAttacks/bbRookXrayAttacks,
- // queen hasn't converted yet so its xray sub-bit is still valid
- // straight off .uXray.
+ // the right constant to OR in here) -- bishop's, rook's, and now
+ // queen's contributions all moved to pos->bbMinorXrayAttacks/
+ // bbRookXrayAttacks/bbQueenXrayAttacks; only king hasn't converted
+ // yet, still valid straight off .uXray.
ULONG uWhite = pos->rgSquare[c|8].bvAttacks[WHITE].uSmall |
pos->rgSquare[c|8].bvAttacks[WHITE].uXray |
((pos->bbPawnAttacks[WHITE] & COOR_TO_BB(c)) ?
@@ -1460,7 +1570,9 @@ Return value:
(_IsSquareAttackedByMinor(pos, WHITE, c) ? MINOR_BIT : 0) |
(_IsSquareXrayedByMinor(pos, WHITE, c) ? MINOR_BIT : 0) |
(_IsSquareAttackedByRook(pos, WHITE, c) ? ROOK_BIT : 0) |
- (_IsSquareXrayedByRook(pos, WHITE, c) ? ROOK_BIT : 0);
+ (_IsSquareXrayedByRook(pos, WHITE, c) ? ROOK_BIT : 0) |
+ (_IsSquareAttackedByQueen(pos, WHITE, c) ? QUEEN_BIT : 0) |
+ (_IsSquareXrayedByQueen(pos, WHITE, c) ? QUEEN_BIT : 0);
ULONG uBlack = pos->rgSquare[c|8].bvAttacks[BLACK].uSmall |
pos->rgSquare[c|8].bvAttacks[BLACK].uXray |
((pos->bbPawnAttacks[BLACK] & COOR_TO_BB(c)) ?
@@ -1468,7 +1580,9 @@ Return value:
(_IsSquareAttackedByMinor(pos, BLACK, c) ? MINOR_BIT : 0) |
(_IsSquareXrayedByMinor(pos, BLACK, c) ? MINOR_BIT : 0) |
(_IsSquareAttackedByRook(pos, BLACK, c) ? ROOK_BIT : 0) |
- (_IsSquareXrayedByRook(pos, BLACK, c) ? ROOK_BIT : 0);
+ (_IsSquareXrayedByRook(pos, BLACK, c) ? ROOK_BIT : 0) |
+ (_IsSquareAttackedByQueen(pos, BLACK, c) ? QUEEN_BIT : 0) |
+ (_IsSquareXrayedByQueen(pos, BLACK, c) ? QUEEN_BIT : 0);
ULONG u;
PIECE p;
CHAR ch;
@@ -1542,6 +1656,8 @@ _ClearAttackTables(IN OUT POSITION *pos)
pos->bbMinorXrayAttacks[WHITE] = pos->bbMinorXrayAttacks[BLACK] = 0;
pos->bbRookAttacks[WHITE] = pos->bbRookAttacks[BLACK] = 0;
pos->bbRookXrayAttacks[WHITE] = pos->bbRookXrayAttacks[BLACK] = 0;
+ pos->bbQueenAttacks[WHITE] = pos->bbQueenAttacks[BLACK] = 0;
+ pos->bbQueenXrayAttacks[WHITE] = pos->bbQueenXrayAttacks[BLACK] = 0;
#if 1
CLEAR_A_SQ; c += 16;
CLEAR_A_SQ; c += 16;
@@ -2929,71 +3045,72 @@ Return value:
pos->bb = bbPc;
{
BITBOARD bbAttack = _BishopAttacksBB(c, pos->bbOccupied);
- BITBOARD bbFriendOcc = _BuildFriendlySideBB(pos, uColor);
- BITBOARD bbEnemyNonPawnOcc = _BuildFriendlySideBB(pos, FLIP(uColor)) &
- ~pos->bbPawns[FLIP(uColor)];
+ BITBOARD bbEnemySame = pos->bbPieces[FLIP(uColor)][BISHOP] |
+ pos->bbPieces[FLIP(uColor)][KNIGHT];
+ BITBOARD bbEnemyGEContinue = pos->bbPieces[FLIP(uColor)][ROOK] |
+ pos->bbPieces[FLIP(uColor)][QUEEN] |
+ COOR_TO_BB(pos->cNonPawns[FLIP(uColor)][0]);
+ BITBOARD bbFriendBQ = pos->bbPieces[uColor][BISHOP] |
+ pos->bbPieces[uColor][QUEEN];
BITBOARD bbUnsafeForMinor = pos->bbPawnAttacks[FLIP(uColor)];
- BITBOARD bbTransientCredit = bbAttack & pos->bbPawns[uColor] & bbPc;
- BITBOARD bbMobility;
+ BITBOARD bbExclude = 0;
+ BITBOARD bbSeen = 0;
+ BITBOARD bbLayer = bbAttack;
+ BITBOARD bbMobility = 0;
+ BITBOARD bbXrayAccum = 0;
+ BITBOARD bbFirstLayerMask = 0;
ULONG d;
pos->bbMinorAttacks[uColor] |= bbAttack;
//
- // X-ray: squares seen through a friendly bishop/queen
- // (BMOB_FRIEND_XRAY) or an enemy rook/queen/king
- // (BMOB_ENEMY_GREATER) -- the only two case-table entries that
- // don't stop the ray outright.
- //
- // Originally shipped 2026-09-05 as a single-hop-only
- // approximation (see rook's identical, now-superseded comment
- // for the reasoning) on the theory that >=2 consecutive
- // x-ray-worthy blockers on one diagonal is rare. Backported
- // rook's bounded chain-following fix once that theory was
- // actually checked instead of assumed: bishop's own rate is
- // indeed low (2/191, ~1%, vs. rook's 14%) but the fix is
- // mechanically identical and effectively free (the loop only
- // iterates again when a real chained battery exists), so there
- // is no reason left to keep the approximation once rook's
- // fidelity-preserving version exists to copy. See
- // _IsSquareXrayedByMinor's DEBUG cross-check, updated to the
- // same chain-following rule.
+ // Unified chain walk over both mobility and x-ray population
+ // in one pass -- same technique and same reason as rook's
+ // identical rewrite (board_representation/EVAL.md section 9):
+ // the old mailbox walk's BMOB_FRIEND_XRAY/BMOB_ENEMY_GREATER
+ // cases don't just x-ray past their blocker for attack-bit
+ // purposes, they keep walking (fStop=FALSE) and keep crediting
+ // mobility for whatever lies beyond. The originally-committed
+ // bitboard version (and its single-hop-then-chain-following
+ // x-ray fix) only ever handled the near side for *mobility*,
+ // an under-count caught the same way rook's was: a two-bishop
+ // battery on one diagonal with open squares beyond (old
+ // mailbox: 12/8 mobility for the two bishops; near-side-only
+ // bitboard version: 11/5).
//
+ for (;;)
{
- BITBOARD bbXrayAccum = 0;
- BITBOARD bbExclude = 0;
- BITBOARD bbFrontier = bbAttack;
- for (;;)
- {
- BITBOARD bbBlockers = bbFrontier &
- (pos->bbPieces[uColor][BISHOP] |
- pos->bbPieces[uColor][QUEEN] |
- pos->bbPieces[FLIP(uColor)][ROOK] |
- pos->bbPieces[FLIP(uColor)][QUEEN] |
- COOR_TO_BB(pos->cNonPawns[FLIP(uColor)][0]));
- BITBOARD bbBeyond;
+ BITBOARD bbNew = bbLayer & ~bbSeen;
+ BITBOARD bbBlockersHere = bbNew & pos->bbOccupied;
+ BITBOARD bbEmptyHere = bbNew & ~pos->bbOccupied;
+ BITBOARD bbFriendBQHere = bbBlockersHere & bbFriendBQ;
+ BITBOARD bbEnemySameHere = bbBlockersHere & bbEnemySame;
+ BITBOARD bbEnemyGEHere = bbBlockersHere & bbEnemyGEContinue;
+ BITBOARD bbFriendPawnHere = bbBlockersHere & pos->bbPawns[uColor];
+ BITBOARD bbEnemyPawnHere = bbBlockersHere &
+ pos->bbPawns[FLIP(uColor)];
+ BITBOARD bbTransientHere = bbFriendPawnHere & bbPc;
+ BITBOARD bbContinueHere;
- if (!bbBlockers)
- {
- break;
- }
- bbExclude |= bbBlockers;
- bbBeyond = _BishopAttacksBB(c, pos->bbOccupied & ~bbExclude) &
- ~bbAttack & ~bbXrayAccum;
- if (!bbBeyond)
- {
- break;
- }
- bbXrayAccum |= bbBeyond;
- bbFrontier = bbBeyond;
+ bbSeen |= bbNew;
+ bbXrayAccum |= (bbNew & bbFirstLayerMask);
+ bbFirstLayerMask = ~(BITBOARD)0;
+
+ bbMobility |= (bbEmptyHere & ~bbUnsafeForMinor) |
+ bbEnemySameHere | bbEnemyGEHere |
+ (bbEnemyPawnHere & ~bbUnsafeForMinor) |
+ bbTransientHere;
+
+ bbContinueHere = bbFriendBQHere | bbEnemyGEHere;
+ if (!bbContinueHere)
+ {
+ break;
}
- pos->bbMinorXrayAttacks[uColor] |= bbXrayAccum;
+ bbExclude |= bbContinueHere;
+ bbLayer = _BishopAttacksBB(c, pos->bbOccupied & ~bbExclude);
}
+ pos->bbMinorXrayAttacks[uColor] |= bbXrayAccum;
- bbMobility = (bbAttack & bbEnemyNonPawnOcc) |
- (bbAttack & ~bbFriendOcc & ~bbEnemyNonPawnOcc &
- ~bbUnsafeForMinor) |
- bbTransientCredit;
uTotalMobility = CountBits(bbMobility);
uMaxMobility = 0;
@@ -3523,92 +3640,103 @@ Return value:
pos->cPiece = c;
{
BITBOARD bbAttack = _RookAttacksBB(c, pos->bbOccupied);
- BITBOARD bbFriendOcc = _BuildFriendlySideBB(pos, uColor);
- BITBOARD bbEnemyGE = pos->bbPieces[FLIP(uColor)][ROOK] |
- pos->bbPieces[FLIP(uColor)][QUEEN] |
- COOR_TO_BB(pos->cNonPawns[FLIP(uColor)][0]);
+ BITBOARD bbEnemyOcc = _BuildFriendlySideBB(pos, FLIP(uColor));
+ BITBOARD bbEnemySame = pos->bbPieces[FLIP(uColor)][ROOK];
+ BITBOARD bbEnemyGEContinue = pos->bbPieces[FLIP(uColor)][QUEEN] |
+ COOR_TO_BB(pos->cNonPawns[FLIP(uColor)][0]);
+ BITBOARD bbFriendRQ = pos->bbPieces[uColor][ROOK] |
+ pos->bbPieces[uColor][QUEEN];
BITBOARD bbUnsafeForRook = pos->bbPawnAttacks[FLIP(uColor)] |
pos->bbMinorAttacks[FLIP(uColor)];
- BITBOARD bbMobility;
- BITBOARD bbFriendRook;
+ BITBOARD bbExclude = 0;
+ BITBOARD bbSeen = 0;
+ BITBOARD bbLayer = bbAttack;
+ BITBOARD bbMobility = 0;
+ BITBOARD bbXrayAccum = 0;
+ BITBOARD bbFirstLayerMask = 0;
ULONG d;
pos->bbRookAttacks[uColor] |= bbAttack;
//
- // Connected-rook bonus: a friendly rook reached on this ray is
- // a battery partner. At most one bit per ray direction can be
- // this case (the attack set stops there), but more than one
- // direction can each terminate at a different friendly rook.
- //
- bbFriendRook = bbAttack & pos->bbPieces[uColor][ROOK];
- while (bbFriendRook)
- {
- COOR cBlocker = CoorFromBitBoardRank8ToRank1(&bbFriendRook);
- FLAG fHoriz = (RANK(cBlocker) == RANK(c));
- EVAL_TERM(uColor,
- ROOK,
- cBlocker,
- pos->iScore[uColor],
- (ROOK_CONNECTED_HORIZ * fHoriz +
- ROOK_CONNECTED_VERT * FLIP(fHoriz)),
- "rook connected");
- }
-
- //
- // X-ray: squares seen through a friendly rook/queen battery
- // partner or an x-rayable enemy queen/king -- the only
- // case-table entries that don't stop the ray outright.
+ // Unified chain walk over both mobility and x-ray population
+ // in one pass, board_representation/EVAL.md section 9. The
+ // old mailbox walk's RMOB_FRIEND_ROOK/_QUEEN and
+ // RMOB_ENEMY_GREATER cases don't just x-ray past their blocker
+ // for attack-bit purposes -- they keep walking (fStop=FALSE)
+ // and keep crediting mobility for whatever safe/empty squares
+ // and further captures lie beyond, however many such blockers
+ // are stacked on one ray. A first version of this conversion
+ // (committed, since fixed) only handled the near side (up to
+ // the first blocker) for mobility and treated the chain purely
+ // as an attack-bit population exercise -- caught by comparing
+ // against the old mailbox walk directly on a battery position
+ // (two same-color rooks on an open file: old code credited 13
+ // mobility to the far rook's near companion via squares beyond
+ // it, the near-side-only version credited only 9). Fixed by
+ // reusing the exact same "recompute with blockers excluded"
+ // chain used for x-ray population to also accumulate mobility
+ // credit at every layer, not just the first.
//
- // Unlike bishop's single-hop cut, this chains until it stops
- // finding a new x-ray-worthy terminal: checked against the
- // curated suites (2026-09-05), 14% of positions (27/191) have
- // a genuine 2+-deep rook/queen battery on some ray, far more
- // common than bishop's "rare" diagonal case -- a one-hop
- // approximation here would be a real fidelity loss, not a
- // negligible one. The stashed first bitboard-eval attempt
- // (git stash@{1}, "measured 17-41% slower") already solved
- // this correctly by walking blocker-to-blocker via bit-scan on
- // bbRay & bbOccupied; this reproduces the same unbounded
- // chain-following behavior via repeated magic-lookup
- // recomputation instead (each iteration excludes one more
- // ring of blockers), which stays cheap because the loop body
- // only runs again when an actual chained battery exists --
- // the common case exits after zero or one iteration.
+ // Each iteration classifies the newly-revealed blockers on
+ // each still-open ray: friendly rook/queen and enemy queen/
+ // king continue the chain (recompute with them excluded too);
+ // everything else (friendly non-R/Q, enemy pawn/knight/bishop,
+ // enemy rook, or the board edge) stops that ray. Bounded: each
+ // iteration either terminates or consumes at least one new
+ // piece into the exclusion set, so this can loop at most
+ // once per piece on the board.
//
+ for (;;)
{
- BITBOARD bbXrayAccum = 0;
- BITBOARD bbExclude = 0;
- BITBOARD bbFrontier = bbAttack;
- for (;;)
+ BITBOARD bbNew = bbLayer & ~bbSeen;
+ BITBOARD bbBlockersHere = bbNew & pos->bbOccupied;
+ BITBOARD bbEmptyHere = bbNew & ~pos->bbOccupied;
+ BITBOARD bbFriendRQHere = bbBlockersHere & bbFriendRQ;
+ BITBOARD bbEnemySameHere = bbBlockersHere & bbEnemySame;
+ BITBOARD bbEnemyGEHere = bbBlockersHere & bbEnemyGEContinue;
+ BITBOARD bbEnemyLEHere = bbBlockersHere & bbEnemyOcc &
+ ~bbEnemySameHere & ~bbEnemyGEHere;
+ BITBOARD bbFriendRookHere = bbBlockersHere &
+ pos->bbPieces[uColor][ROOK];
+ BITBOARD bbContinueHere;
+
+ bbSeen |= bbNew;
+ bbXrayAccum |= (bbNew & bbFirstLayerMask);
+ bbFirstLayerMask = ~(BITBOARD)0;
+
+ bbMobility |= (bbEmptyHere & ~bbUnsafeForRook) |
+ bbEnemySameHere | bbEnemyGEHere |
+ (bbEnemyLEHere & ~bbUnsafeForRook);
+
+ //
+ // Connected-rook bonus fires at every friendly rook found
+ // along the chain, not just the first (matches the old
+ // walk: each RMOB_FRIEND_ROOK hit its own EVAL_TERM call).
+ //
+ while (bbFriendRookHere)
{
- BITBOARD bbBlockers = bbFrontier &
- (pos->bbPieces[uColor][ROOK] |
- pos->bbPieces[uColor][QUEEN] |
- pos->bbPieces[FLIP(uColor)][QUEEN] |
- COOR_TO_BB(pos->cNonPawns[FLIP(uColor)][0]));
- BITBOARD bbBeyond;
+ COOR cBlocker = CoorFromBitBoardRank8ToRank1(&bbFriendRookHere);
+ FLAG fHoriz = (RANK(cBlocker) == RANK(c));
+ EVAL_TERM(uColor,
+ ROOK,
+ cBlocker,
+ pos->iScore[uColor],
+ (ROOK_CONNECTED_HORIZ * fHoriz +
+ ROOK_CONNECTED_VERT * FLIP(fHoriz)),
+ "rook connected");
+ }
- if (!bbBlockers)
- {
- break;
- }
- bbExclude |= bbBlockers;
- bbBeyond = _RookAttacksBB(c, pos->bbOccupied & ~bbExclude) &
- ~bbAttack & ~bbXrayAccum;
- if (!bbBeyond)
- {
- break;
- }
- bbXrayAccum |= bbBeyond;
- bbFrontier = bbBeyond;
+ bbContinueHere = bbFriendRQHere | bbEnemyGEHere;
+ if (!bbContinueHere)
+ {
+ break;
}
- pos->bbRookXrayAttacks[uColor] |= bbXrayAccum;
+ bbExclude |= bbContinueHere;
+ bbLayer = _RookAttacksBB(c, pos->bbOccupied & ~bbExclude);
}
+ pos->bbRookXrayAttacks[uColor] |= bbXrayAccum;
- bbMobility = (bbAttack & bbEnemyGE) |
- (bbAttack & ~bbFriendOcc & ~bbEnemyGE &
- ~bbUnsafeForRook);
uTotalMobility = CountBits(bbMobility);
uMaxMobility = 0;
@@ -3731,48 +3859,10 @@ Return value:
**/
{
- static const UCHAR QMobCaseTable[2][14] =
- {
- {
- QMOB_EMPTY, // EMPTY_SQUARE (0)
- QMOB_INVALID, // INVALID_PIECE (1)
- QMOB_FRIEND_BLOCK, // BLACK_PAWN (2)
- QMOB_ENEMY_LESS, // WHITE_PAWN (3)
- QMOB_FRIEND_BLOCK, // BLACK_KNIGHT (4)
- QMOB_ENEMY_LESS, // WHITE_KNIGHT (5)
- QMOB_FRIEND_BISHOP, // BLACK_BISHOP (6)
- QMOB_ENEMY_LESS, // WHITE_BISHOP (7)
- QMOB_FRIEND_ROOK, // BLACK_ROOK (8)
- QMOB_ENEMY_LESS, // WHITE_ROOK (9)
- QMOB_FRIEND_QUEEN, // BLACK_QUEEN (10)
- QMOB_ENEMY_GE, // WHITE_QUEEN (11)
- QMOB_FRIEND_BLOCK, // BLACK_KING (12)
- QMOB_ENEMY_GE, // WHITE_KING (13)
- },
- {
- QMOB_EMPTY, // EMPTY_SQUARE (0)
- QMOB_INVALID, // INVALID_PIECE (1)
- QMOB_ENEMY_LESS, // BLACK_PAWN (2)
- QMOB_FRIEND_BLOCK, // WHITE_PAWN (3)
- QMOB_ENEMY_LESS, // BLACK_KNIGHT (4)
- QMOB_FRIEND_BLOCK, // WHITE_KNIGHT (5)
- QMOB_ENEMY_LESS, // BLACK_BISHOP (6)
- QMOB_FRIEND_BISHOP, // WHITE_BISHOP (7)
- QMOB_ENEMY_LESS, // BLACK_ROOK (8)
- QMOB_FRIEND_ROOK, // WHITE_ROOK (9)
- QMOB_ENEMY_GE, // BLACK_QUEEN (10)
- QMOB_FRIEND_QUEEN, // WHITE_QUEEN (11)
- QMOB_ENEMY_GE, // BLACK_KING (12)
- QMOB_FRIEND_BLOCK, // WHITE_KING (13)
- },
- };
-
PIECE p = pos->rgSquare[c].pPiece;
ULONG uColor;
- COOR cSquare;
ULONG uTotalMobility;
ULONG u;
- ULONG uBit;
COOR cKing;
ASSERT(IS_ON_BOARD(c));
@@ -3818,102 +3908,87 @@ Return value:
}
//
- // Do queen mobility
+ // Do queen mobility (and update attack tables) -- two-pass
+ // rook-direction/bishop-direction magic lookups, board_
+ // representation/EVAL.md section 1b/9: MOVEGEN_MIGRATION.md
+ // already found (in the stashed first bitboard-eval attempt's
+ // _EvalQueenOccupancyBB PoC) that a combined 8-ray table measures
+ // *slower* than reusing the rook/bishop tables in two passes --
+ // reuse that structure here too, don't rediscover the regression.
+ // Each pass is its own unified chain walk, identical technique to
+ // rook's/bishop's own conversions (including their fix for
+ // crediting mobility beyond a battery partner, not just x-ray
+ // attack bits): continue through a friendly queen in both passes,
+ // a friendly rook only in the rook-direction pass, a friendly
+ // bishop only in the bishop-direction pass (QMOB_FRIEND_ROOK/
+ // _BISHOP's old per-ray-family split falls out for free from
+ // *which* lookup a blocker shows up in -- no fOrthogonalRay flag
+ // needed). Unlike rook/bishop, queen never x-rays through *any*
+ // enemy piece (QMOB_ENEMY_GE always stopped in the old table, the
+ // one case that's a *fewer*-cases asymmetry versus bishop, not
+ // more), so the enemy side of each pass's continue-set is empty.
//
- uTotalMobility = 0;
- u = 0;
- ASSERT(g_iQKDeltas[u] != 0);
- do
{
- // Ray-invariant: every square on this ray shares the same
- // rank/file relationship to c as the very first step does, so
- // this only needs computing once per direction, not per square
- // (or even per friend-slider hit).
- FLAG fOrthogonalRay;
+ BITBOARD bbEnemyOcc = _BuildFriendlySideBB(pos, FLIP(uColor));
+ BITBOARD bbEnemyGE = pos->bbPieces[FLIP(uColor)][QUEEN] |
+ COOR_TO_BB(cKing);
+ BITBOARD bbUnsafeForQueen = pos->bbPawnAttacks[FLIP(uColor)] |
+ pos->bbMinorAttacks[FLIP(uColor)] |
+ pos->bbRookAttacks[FLIP(uColor)];
+ BITBOARD bbAttackTotal = 0;
+ BITBOARD bbXrayAccum = 0;
+ BITBOARD bbMobility = 0;
+ ULONG uPass;
- uBit = QUEEN_BIT;
- cSquare = c + g_iQKDeltas[u];
- fOrthogonalRay = (((cSquare & 0xF0) == (c & 0xF0)) ||
- ((cSquare & 0x0F) == (c & 0x0F)));
- while(IS_ON_BOARD(cSquare))
+ for (uPass = 0; uPass < 2; uPass++)
{
- FLAG fStop;
+ BITBOARD bbFriendContinue = (uPass == 0) ?
+ (pos->bbPieces[uColor][ROOK] | pos->bbPieces[uColor][QUEEN]) :
+ (pos->bbPieces[uColor][BISHOP] | pos->bbPieces[uColor][QUEEN]);
+ BITBOARD bbInitial = (uPass == 0) ?
+ _RookAttacksBB(c, pos->bbOccupied) :
+ _BishopAttacksBB(c, pos->bbOccupied);
+ BITBOARD bbExclude = 0;
+ BITBOARD bbSeen = 0;
+ BITBOARD bbLayer = bbInitial;
+ BITBOARD bbFirstLayerMask = 0;
- //
- // Toggle attack table bits.
- //
- pos->rgSquare[cSquare|8].bvAttacks[uColor].uWholeThing |= uBit;
+ bbAttackTotal |= bbInitial;
- //
- // What did we hit? Dispatched via switch instead of an
- // indirect call through a function pointer -- same
- // rationale as the bishop ray-walk above.
- //
- p = pos->rgSquare[cSquare].pPiece;
- switch (QMobCaseTable[uColor][p])
+ for (;;)
{
- case QMOB_EMPTY:
- uTotalMobility +=
- !((pos->bbPawnAttacks[FLIP(uColor)] & COOR_TO_BB(cSquare)) ||
- _IsSquareAttackedByMinor(pos, FLIP(uColor), cSquare) ||
- _IsSquareAttackedByRook(pos, FLIP(uColor), cSquare));
- fStop = FALSE;
- break;
-
- case QMOB_ENEMY_LESS:
- uTotalMobility +=
- !((pos->bbPawnAttacks[FLIP(uColor)] & COOR_TO_BB(cSquare)) ||
- _IsSquareAttackedByMinor(pos, FLIP(uColor), cSquare) ||
- _IsSquareAttackedByRook(pos, FLIP(uColor), cSquare));
- fStop = TRUE;
- break;
-
- case QMOB_FRIEND_BLOCK:
- fStop = TRUE;
- break;
+ BITBOARD bbNew = bbLayer & ~bbSeen;
+ BITBOARD bbBlockersHere = bbNew & pos->bbOccupied;
+ BITBOARD bbEmptyHere = bbNew & ~pos->bbOccupied;
+ BITBOARD bbFriendContinueHere = bbBlockersHere &
+ bbFriendContinue;
+ BITBOARD bbEnemyGEHere = bbBlockersHere & bbEnemyGE;
+ BITBOARD bbEnemyLEHere = bbBlockersHere & bbEnemyOcc &
+ ~bbEnemyGEHere;
- case QMOB_FRIEND_BISHOP:
- // Bishop only xrays a diagonal ray; blocks on an
- // orthogonal one.
- uBit = QUEEN_XRAY_BIT;
- fStop = fOrthogonalRay;
- break;
-
- case QMOB_FRIEND_ROOK:
- // Rook only xrays an orthogonal ray; blocks on a
- // diagonal one.
- uBit = QUEEN_XRAY_BIT;
- fStop = !fOrthogonalRay;
- break;
-
- case QMOB_FRIEND_QUEEN:
- uBit = QUEEN_XRAY_BIT;
- fStop = FALSE;
- break;
+ bbSeen |= bbNew;
+ bbXrayAccum |= (bbNew & bbFirstLayerMask);
+ bbFirstLayerMask = ~(BITBOARD)0;
- case QMOB_ENEMY_GE:
- uTotalMobility += 1;
- fStop = TRUE;
- break;
+ bbMobility |= (bbEmptyHere & ~bbUnsafeForQueen) |
+ bbEnemyGEHere |
+ (bbEnemyLEHere & ~bbUnsafeForQueen);
- case QMOB_INVALID:
- default:
- UtilPanic(SHOULD_NOT_GET_HERE,
- NULL, NULL, NULL, NULL,
- __FILE__, __LINE__);
- fStop = TRUE;
+ if (!bbFriendContinueHere)
+ {
break;
+ }
+ bbExclude |= bbFriendContinueHere;
+ bbLayer = (uPass == 0) ?
+ _RookAttacksBB(c, pos->bbOccupied & ~bbExclude) :
+ _BishopAttacksBB(c, pos->bbOccupied & ~bbExclude);
}
- ASSERT(uTotalMobility <= 27);
- if (TRUE == fStop)
- {
- break;
- }
- cSquare += g_iQKDeltas[u];
}
- u++;
+ pos->bbQueenAttacks[uColor] |= bbAttackTotal;
+ pos->bbQueenXrayAttacks[uColor] |= bbXrayAccum;
+
+ uTotalMobility = CountBits(bbMobility);
}
- while(g_iQKDeltas[u] != 0);
ASSERT(uTotalMobility <= 27);
EVAL_TERM(uColor,
@@ -4112,7 +4187,9 @@ Return value:
(_IsSquareAttackedByMinor(pos, ufColor, cSquare) ?
MINOR_BIT : 0) |
(_IsSquareAttackedByRook(pos, ufColor, cSquare) ?
- ROOK_BIT : 0);
+ ROOK_BIT : 0) |
+ (_IsSquareAttackedByQueen(pos, ufColor, cSquare) ?
+ QUEEN_BIT : 0);
{
COOR cRealSquare = cSquare;
cSquare |= 8;
@@ -4120,7 +4197,9 @@ Return value:
(_IsSquareXrayedByMinor(pos, ufColor, cRealSquare) ?
MINOR_BIT : 0) |
(_IsSquareXrayedByRook(pos, ufColor, cRealSquare) ?
- ROOK_BIT : 0);
+ ROOK_BIT : 0) |
+ (_IsSquareXrayedByQueen(pos, ufColor, cRealSquare) ?
+ QUEEN_BIT : 0);
pos->rgSquare[cSquare].bvAttacks[uColor].small.uKing = 1;
bvDefend = pos->rgSquare[cSquare].bvAttacks[uColor].uSmall |
((pos->bbPawnAttacks[uColor] & COOR_TO_BB(cRealSquare)) ?
@@ -4128,7 +4207,9 @@ Return value:
(_IsSquareAttackedByMinor(pos, uColor, cRealSquare) ?
MINOR_BIT : 0) |
(_IsSquareAttackedByRook(pos, uColor, cRealSquare) ?
- ROOK_BIT : 0);
+ ROOK_BIT : 0) |
+ (_IsSquareAttackedByQueen(pos, uColor, cRealSquare) ?
+ QUEEN_BIT : 0);
}
//
@@ -4140,9 +4221,13 @@ Return value:
// and, unlike the old term, catches x-ray/latent queen
// threats too, not just direct ray-cast hits.
//
- uQueenNearKing +=
- (pos->rgSquare[cSquare].bvAttacks[ufColor].small.uQueen |
- pos->rgSquare[cSquare].bvAttacks[ufColor].xray.uQueen);
+ // Queen no longer writes small.uQueen/xray.uQueen at all
+ // (2026-09-05 conversion) -- bvAttack/bvXray above already
+ // fold its contribution back in via QUEEN_BIT (same byte
+ // position .small.uQueen used), so just read those instead
+ // of the now-permanently-zero raw struct fields.
+ //
+ uQueenNearKing += (((bvAttack | bvXray) & QUEEN_BIT) != 0);
if (bvAttack != 0)
{