diff options
Diffstat (limited to 'src/eval.c')
| -rwxr-xr-x | src/eval.c | 597 |
1 files changed, 341 insertions, 256 deletions
@@ -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) { |
