summaryrefslogtreecommitdiff
path: root/src/eval.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval.c')
-rwxr-xr-xsrc/eval.c336
1 files changed, 205 insertions, 131 deletions
diff --git a/src/eval.c b/src/eval.c
index 10a1327..bb19d9d 100755
--- a/src/eval.c
+++ b/src/eval.c
@@ -1204,6 +1204,116 @@ _IsSquareAttackedByMinor(IN POSITION *pos,
return fResult;
}
+//
+// Companion to _IsSquareAttackedByMinor, same transitional purpose:
+// bishop is the only minor that ever x-rays (knights don't), and its
+// x-ray bit is moving fully to pos->bbMinorXrayAttacks in this same
+// change -- unlike the direct-attack case, there's no old-structure
+// fallback to bridge here (nothing else has ever written a minor
+// x-ray bit), so this is just a plain bitboard read. Goes away once
+// rook/queen convert and _EvalKing/_WhoControlsSquareFast read their
+// xray bitboards directly instead of going through any helper.
+//
+// Deliberate behavior change from the old ray-walk, made for speed
+// per direct instruction (2026-09-05): the old BMobCaseTable's
+// fStop=FALSE for BMOB_FRIEND_XRAY/BMOB_ENEMY_GREATER meant the walk
+// kept going -- and kept counting mobility -- through however many
+// x-ray-worthy blockers were stacked consecutively on one ray (e.g. a
+// bishop x-raying an enemy rook, then continuing to x-ray *through*
+// an enemy king sitting right behind it too). _EvalBishop's bitboard
+// version only extends *one* hop past the first x-ray-worthy blocker
+// (recompute with just that blocker excluded, keep what's newly
+// revealed) -- it does not check whether the newly-revealed terminal
+// square is itself x-ray-worthy and continue again. This is simpler
+// and faster, and the position it changes behavior on (>=2 specific
+// piece types stacked on the same diagonal) is rare enough that
+// trading exact fidelity for it was judged worthwhile; if that
+// judgment turns out wrong, the fix is a bounded loop repeating the
+// "exclude terminal blocker, recompute" step until it stops finding a
+// new x-ray-worthy terminal, not a design change.
+//
+// DEBUG-only: cross-checked against a from-scratch mailbox ray-walk
+// that mirrors this same single-hop rule directly (not the old,
+// unbounded-chain rule) -- deliberately not sharing any code with
+// _EvalBishop's own "recompute with blocker excluded" bitboard
+// technique, so this is a genuinely independent check on that
+// technique, not a restatement of it.
+//
+static FLAG
+_IsSquareXrayedByMinor(IN POSITION *pos,
+ IN ULONG uColor,
+ IN COOR c)
+{
+ BITBOARD sq = COOR_TO_BB(c);
+ FLAG fResult = (pos->bbMinorXrayAttacks[uColor] & sq) != 0;
+#ifdef DEBUG
+ {
+ BITBOARD bbTrueXray = 0;
+ ULONG u;
+ for (u = 1; u < pos->uNonPawnCount[uColor][0]; u++)
+ {
+ COOR cBishop = pos->cNonPawns[uColor][u];
+ ULONG d;
+ if (!IS_BISHOP(pos->rgSquare[cBishop].pPiece))
+ {
+ continue;
+ }
+ for (d = 0; d < 4; d++)
+ {
+ COOR cWalk = cBishop + g_iBDeltas[d];
+ FLAG fFoundXrayBlocker = FALSE;
+ //
+ // First pass: walk to the first blocker (or edge),
+ // exactly like the direct-attack ray would, purely to
+ // classify whether that first blocker is x-ray-worthy
+ // -- no marking yet.
+ //
+ while (IS_ON_BOARD(cWalk) && IS_EMPTY(pos->rgSquare[cWalk].pPiece))
+ {
+ cWalk += g_iBDeltas[d];
+ }
+ if (IS_ON_BOARD(cWalk))
+ {
+ PIECE pq = pos->rgSquare[cWalk].pPiece;
+ if (GET_COLOR(pq) == uColor)
+ {
+ fFoundXrayBlocker = (IS_BISHOP(pq) || IS_QUEEN(pq));
+ }
+ else
+ {
+ fFoundXrayBlocker = (IS_ROOK(pq) || IS_QUEEN(pq) ||
+ IS_KING(pq));
+ }
+ }
+ if (!fFoundXrayBlocker)
+ {
+ continue;
+ }
+ //
+ // Single hop past that one blocker: mark empty
+ // squares as x-ray, then mark and stop at the very
+ // next piece of any kind (or the edge) -- do not
+ // classify that second piece and potentially continue
+ // again, unlike the old chain-following walk.
+ //
+ cWalk += g_iBDeltas[d];
+ while (IS_ON_BOARD(cWalk))
+ {
+ bbTrueXray |= COOR_TO_BB(cWalk);
+ if (!IS_EMPTY(pos->rgSquare[cWalk].pPiece))
+ {
+ break;
+ }
+ cWalk += g_iBDeltas[d];
+ }
+ }
+ }
+ ASSERT(((bbTrueXray & sq) != 0) == (fResult != 0));
+ }
+#endif
+ return fResult;
+}
+
static ULONG
_WhoControlsSquareFast(IN POSITION *pos,
@@ -1236,16 +1346,23 @@ Return value:
// position (PAWN_BIT) so g_SwapTable's indexing below sees the
// same bit shape it always has.
//
+ // .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 contribution to it
+ // moved to pos->bbMinorXrayAttacks, rook/queen's haven't converted
+ // yet so their xray sub-bits are 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)) ?
PAWN_BIT : 0) |
- (_IsSquareAttackedByMinor(pos, WHITE, c) ? MINOR_BIT : 0);
+ (_IsSquareAttackedByMinor(pos, WHITE, c) ? MINOR_BIT : 0) |
+ (_IsSquareXrayedByMinor(pos, WHITE, c) ? MINOR_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)) ?
PAWN_BIT : 0) |
- (_IsSquareAttackedByMinor(pos, BLACK, c) ? MINOR_BIT : 0);
+ (_IsSquareAttackedByMinor(pos, BLACK, c) ? MINOR_BIT : 0) |
+ (_IsSquareXrayedByMinor(pos, BLACK, c) ? MINOR_BIT : 0);
ULONG u;
PIECE p;
CHAR ch;
@@ -1316,6 +1433,7 @@ _ClearAttackTables(IN OUT POSITION *pos)
{
register COOR c = 8;
pos->bbMinorAttacks[WHITE] = pos->bbMinorAttacks[BLACK] = 0;
+ pos->bbMinorXrayAttacks[WHITE] = pos->bbMinorXrayAttacks[BLACK] = 0;
#if 1
CLEAR_A_SQ; c += 16;
CLEAR_A_SQ; c += 16;
@@ -2566,50 +2684,6 @@ Return value:
{
static const BITBOARD bbColorSq[2] = { 0x55aa55aa55aa55aaULL,
0xaa55aa55aa55aa55ULL };
- //
- // Mobility outcome for each (mover-color, piece-landed-on) pair,
- // replacing a table of function pointers with a table of case tags
- // dispatched via switch -- avoids an indirect call/ret (and the
- // associated indirect-branch-predictor miss, since the target piece
- // varies square to square) per square visited on every bishop ray,
- // the hottest inner loop in eval. See the switch in the ray-walking
- // loop below for what each tag actually does.
- //
- static const UCHAR BMobCaseTable[2][14] =
- {
- {// (black)
- BMOB_EMPTY, // EMPTY_SQUARE (0)
- BMOB_INVALID, // INVALID_PIECE (1)
- BMOB_FRIEND_PAWN, // BLACK_PAWN (2)
- BMOB_ENEMY_PAWN, // WHITE_PAWN (3)
- BMOB_FRIEND_BLOCK, // BLACK_KNIGHT (4)
- BMOB_ENEMY_SAME, // WHITE_KNIGHT (5)
- BMOB_FRIEND_XRAY, // BLACK_BISHOP (6)
- BMOB_ENEMY_SAME, // WHITE_BISHOP (7)
- BMOB_FRIEND_BLOCK, // BLACK_ROOK (8)
- BMOB_ENEMY_GREATER, // WHITE_ROOK (9)
- BMOB_FRIEND_XRAY, // BLACK_QUEEN (10)
- BMOB_ENEMY_GREATER, // WHITE_QUEEN (11)
- BMOB_FRIEND_BLOCK, // BLACK_KING (12)
- BMOB_ENEMY_GREATER, // WHITE_KING (13)
- },
- {// (white)
- BMOB_EMPTY, // EMPTY_SQUARE (0)
- BMOB_INVALID, // INVALID_PIECE (1)
- BMOB_ENEMY_PAWN, // BLACK_PAWN (2)
- BMOB_FRIEND_PAWN, // WHITE_PAWN (3)
- BMOB_ENEMY_SAME, // BLACK_KNIGHT (4)
- BMOB_FRIEND_BLOCK, // WHITE_KNIGHT (5)
- BMOB_ENEMY_SAME, // BLACK_BISHOP (6)
- BMOB_FRIEND_XRAY, // WHITE_BISHOP (7)
- BMOB_ENEMY_GREATER, // BLACK_ROOK (8)
- BMOB_FRIEND_BLOCK, // WHITE_ROOK (9)
- BMOB_ENEMY_GREATER, // BLACK_QUEEN (10)
- BMOB_FRIEND_XRAY, // WHITE_QUEEN (11)
- BMOB_ENEMY_GREATER, // BLACK_KING (12)
- BMOB_FRIEND_BLOCK, // WHITE_KING (13)
- }
- };
static const COOR cBishopAtHome[2][2] =
{
{ C8, F8 }, // BLACK
@@ -2624,8 +2698,6 @@ Return value:
ULONG u;
ULONG uTotalMobility;
ULONG uMaxMobility;
- ULONG uCurrentMobility;
- ULONG uBit;
PIECE p;
ASSERT(IS_ON_BOARD(c));
@@ -2719,99 +2791,99 @@ Return value:
//
//
- // Bishop mobility (and update attack table bits)
+ // Bishop mobility (and update attack tables) -- board_representation/
+ // EVAL.md section 1b: _BishopAttacksBB(c, pos->bbOccupied)
+ // (generate.c's magic-bitboard slider lookup, already used by move
+ // generation) gives the whole ray-to-first-blocker attack set in
+ // one shot -- no per-square delta walk, no mailbox read, no switch
+ // dispatch. The old BMobCaseTable's seven cases collapse to:
+ // - terminal enemy non-pawn (opposing minor/rook/queen/king):
+ // counts unconditionally -- covers both BMOB_ENEMY_SAME (no
+ // x-ray) and BMOB_ENEMY_GREATER (x-rays past too); the
+ // x-ray/no-x-ray distinction only matters for attack-bit
+ // population below, not the mobility count itself.
+ // - empty or terminal enemy pawn: counts unless pawn-unsafe
+ // (BMOB_EMPTY / BMOB_ENEMY_PAWN) -- both terminal-square
+ // categories together are exactly "not friend-occupied and not
+ // enemy-non-pawn-occupied," the same partition knight's
+ // reduction already uses.
+ // - terminal friendly, non-stationary pawn on this bishop's own
+ // color complex (BMOB_FRIEND_PAWN): still counts, the one case
+ // that isn't a pure occupancy mask -- bbPc (already computed
+ // above for the good/bad transient-pawn scoring) is exactly
+ // "non-stationary pawns of either color on this color complex";
+ // ANDing with pos->bbPawns[uColor] and the attack set isolates
+ // just this bishop's own transient pawns.
+ // - any other friendly piece (knight/rook/king): blocks, no
+ // count, no x-ray -- simply excluded by the friendly-occupied
+ // mask, nothing else needed.
//
- u = uMaxMobility = uTotalMobility = 0;
pos->bb = bbPc;
- ASSERT(g_iBDeltas[u] != 0);
- do
{
- uCurrentMobility = 0;
- uBit = MINOR_BIT;
- cSquare = c + g_iBDeltas[u];
-
- while(IS_ON_BOARD(cSquare))
- {
- FLAG fStop;
+ BITBOARD bbAttack = _BishopAttacksBB(c, pos->bbOccupied);
+ BITBOARD bbFriendOcc = _BuildFriendlySideBB(pos, uColor);
+ BITBOARD bbEnemyNonPawnOcc = _BuildFriendlySideBB(pos, FLIP(uColor)) &
+ ~pos->bbPawns[FLIP(uColor)];
+ BITBOARD bbUnsafeForMinor = pos->bbPawnAttacks[FLIP(uColor)];
+ BITBOARD bbTransientCredit = bbAttack & pos->bbPawns[uColor] & bbPc;
+ BITBOARD bbMobility;
+ ULONG d;
- //
- // Always toggle attack table bits.
- //
- ASSERT((cSquare|8) == (cSquare + 8));
- pos->rgSquare[cSquare|8].bvAttacks[uColor].uWholeThing |= uBit;
+ pos->bbMinorAttacks[uColor] |= bbAttack;
- //
- // What did we hit? Dispatched via switch instead of an
- // indirect call through a function pointer -- the target
- // piece varies square to square, so the old jump table
- // defeated the CPU's indirect-branch predictor on every
- // step of every ray. See BMOB_CASE above for what each tag
- // means.
- //
- p = pos->rgSquare[cSquare].pPiece;
- switch (BMobCaseTable[uColor][p])
+ //
+ // 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. Recompute with just that
+ // blocker excluded from occupancy and keep only the squares
+ // beyond the original attack set.
+ //
+ // Deliberate behavior change from the old ray-walk, for speed
+ // (2026-09-05): this is a single hop only. The old walk kept
+ // going -- and kept counting mobility -- through however many
+ // x-ray-worthy blockers were stacked consecutively on one ray
+ // (e.g. x-raying an enemy rook, then continuing to x-ray
+ // *through* an enemy king sitting right behind it too, both
+ // contributing to the mobility count along the way); this
+ // version does not re-check whether the newly-revealed
+ // terminal square is itself x-ray-worthy and extend again.
+ // Judged an acceptable trade given how rare a ray with >=2
+ // consecutive x-ray-worthy pieces is -- see
+ // _IsSquareXrayedByMinor's DEBUG cross-check, which verifies
+ // against this same single-hop rule (not the old unbounded
+ // one) via an independent mailbox walk.
+ //
+ {
+ BITBOARD bbXrayBlockers = bbAttack &
+ (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]));
+ if (bbXrayBlockers)
{
- case BMOB_EMPTY:
- uCurrentMobility +=
- !(pos->bbPawnAttacks[FLIP(uColor)] & COOR_TO_BB(cSquare));
- fStop = FALSE;
- break;
-
- case BMOB_ENEMY_PAWN:
- uCurrentMobility +=
- !(pos->bbPawnAttacks[FLIP(uColor)] & COOR_TO_BB(cSquare));
- fStop = TRUE;
- break;
-
- case BMOB_FRIEND_PAWN:
- uCurrentMobility += ((pos->bb & COOR_TO_BB(cSquare)) != 0);
- fStop = TRUE;
- break;
-
- case BMOB_FRIEND_BLOCK:
- fStop = TRUE;
- break;
-
- case BMOB_ENEMY_SAME:
- uCurrentMobility += 1;
- fStop = TRUE;
- break;
-
- case BMOB_FRIEND_XRAY:
- uBit = MINOR_XRAY_BIT;
- fStop = FALSE;
- break;
+ BITBOARD bbBeyond =
+ _BishopAttacksBB(c, pos->bbOccupied & ~bbXrayBlockers) &
+ ~bbAttack;
+ pos->bbMinorXrayAttacks[uColor] |= bbBeyond;
+ }
+ }
- case BMOB_ENEMY_GREATER:
- uCurrentMobility += 1;
- uBit = MINOR_XRAY_BIT;
- fStop = FALSE;
- break;
+ bbMobility = (bbAttack & bbEnemyNonPawnOcc) |
+ (bbAttack & ~bbFriendOcc & ~bbEnemyNonPawnOcc &
+ ~bbUnsafeForMinor) |
+ bbTransientCredit;
+ uTotalMobility = CountBits(bbMobility);
- case BMOB_INVALID:
- default:
- UtilPanic(SHOULD_NOT_GET_HERE,
- NULL, NULL, NULL, NULL,
- __FILE__, __LINE__);
- fStop = TRUE;
- break;
- }
- ASSERT(uCurrentMobility <= 8);
- if (TRUE == fStop)
- {
- break;
- }
- cSquare += g_iBDeltas[u];
+ uMaxMobility = 0;
+ for (d = 0; d < 4; d++)
+ {
+ ULONG uThisRay = CountBits(bbMobility & g_BishopRayToEdge[d][c]);
+ uMaxMobility = MAXU(uMaxMobility, uThisRay);
}
- uTotalMobility += uCurrentMobility;
- ASSERT((uMaxMobility & 0x80000000) == 0);
- ASSERT((uCurrentMobility & 0x80000000) == 0);
- uMaxMobility = MAXU(uMaxMobility, uCurrentMobility);
- ASSERT(uMaxMobility <= 7);
- u++;
}
- while(g_iBDeltas[u] != 0);
ASSERT(uTotalMobility <= 13);
+ ASSERT(uMaxMobility <= 7);
EVAL_TERM(uColor,
BISHOP,
c,
@@ -3942,7 +4014,9 @@ Return value:
{
COOR cRealSquare = cSquare;
cSquare |= 8;
- bvXray = pos->rgSquare[cSquare].bvAttacks[ufColor].uXray;
+ bvXray = pos->rgSquare[cSquare].bvAttacks[ufColor].uXray |
+ (_IsSquareXrayedByMinor(pos, ufColor, cRealSquare) ?
+ MINOR_BIT : 0);
pos->rgSquare[cSquare].bvAttacks[uColor].small.uKing = 1;
bvDefend = pos->rgSquare[cSquare].bvAttacks[uColor].uSmall |
((pos->bbPawnAttacks[uColor] & COOR_TO_BB(cRealSquare)) ?