summaryrefslogtreecommitdiff
path: root/src/eval.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-10-05 22:20:10 -0700
committerScott Gasch <[email protected]>2026-10-05 22:20:10 -0700
commit5fbd9353fa9b1a80f37bcf0339f5546ac88091b8 (patch)
tree5ac60b40603fd8c6fd051a44a24fd0e5e21425a6 /src/eval.c
parentbb7cdcda4c8c30ad0ed71d0f60427daf05977b24 (diff)
Cache good/bad-bishop pawn-structure score in the pawn hash
iBishopColorScore[color][complex] is a pure function of pawn placement (own/enemy, stationary/transient) on a bishop's color complex, so it doesn't need rescanning per-bishop -- computed once per pawn-hash miss in _EvalPawns and reused by every bishop of that color/complex in _EvalBishop, mirroring the same cache-the-per-structure-invariant idea already used elsewhere in eval.c. Also disables two speculative check-extension/futility-gating experiments in search.c (#if 0, not deleted -- kept for reference) that didn't hold up, and adds the BBLEFT/BBRIGHT bitboard constants eval.c's bishop-mobility rewrite needs. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/eval.c')
-rw-r--r--src/eval.c506
1 files changed, 240 insertions, 266 deletions
diff --git a/src/eval.c b/src/eval.c
index ae12f0a..18c3464 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -565,8 +565,6 @@ static SCORE ROOK_LEADS_PASSER_BY_PASSER_RANK[2][9] =
{ +0, +0, -3, -5, -9, -13, -16, -22, +0 } // white
};
-static SCORE KING_TRAPPING_ROOK = -40;
-
static SCORE ROOK_VALUE_AS_PAWNS_COME_OFF[17] =
{// 0 1 2 3 4 5 6 7 8
+55, +51, +44, +38, +33, +27, +22, +16, +7,
@@ -591,7 +589,7 @@ static SCORE ROOK_CONNECTED_HORIZ = +4; // x2
//
static SCORE ROOK_MOBILITY_BY_SQUARES[15] =
{// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
- -28, -24, -20, -14, -7, -2, +0, +4, +8, +12, +15, +17, +19, +21, +22
+ -32, -26, -20, -14, -7, -2, +0, +4, +8, +12, +15, +17, +19, +21, +22
};// ^ |
@@ -962,7 +960,6 @@ static DNA_BASE_SIZE g_EvalDNA[] = {
DNA_VAR(ROOK_ON_SEVENTH_BONUS),
DNA_MATRIX(ROOK_BEHIND_PASSER_BY_PASSER_RANK),
DNA_MATRIX(ROOK_LEADS_PASSER_BY_PASSER_RANK),
- DNA_VAR(KING_TRAPPING_ROOK),
DNA_ARRAY(ROOK_VALUE_AS_PAWNS_COME_OFF),
DNA_VAR(ROOK_CONNECTED_VERT),
DNA_VAR(ROOK_CONNECTED_HORIZ),
@@ -1549,12 +1546,12 @@ Return value:
bb &= BBPRECEEDING_RANKS[(c & 0x70) >> 4][uColor];
if (!bb)
{
- ASSERT(CountBits(bb) == 0);
+ ASSERT(FastCountBits(bb) == 0);
// There are no enemy sentries so this pawn is a passer.
pHash->bbPasserLocations[uColor] |= COOR_TO_BB(c);
- ASSERT(CountBits(pHash->bbPasserLocations[uColor]) > 0);
- ASSERT(CountBits(pHash->bbPasserLocations[uColor]) <= 8);
+ ASSERT(FastCountBits(pHash->bbPasserLocations[uColor]) > 0);
+ ASSERT(FastCountBits(pHash->bbPasserLocations[uColor]) <= 8);
EVAL_TERM(uColor,
PAWN,
c,
@@ -1574,7 +1571,7 @@ Return value:
}
return;
}
- uSentries = CountBits(bb);
+ uSentries = FastCountBits(bb);
// There's one or more sentry pawns so we'll look for friendly
// helpers to decide if this pawn is a candidate passer. First
@@ -1894,7 +1891,7 @@ Return value:
ASSERT(RANK(cRightmostPasser) > 1);
ASSERT(RANK(cLeftmostPasser) < 8);
ASSERT(RANK(cRightmostPasser) < 8);
- if (CountBits(pHash->bbPasserLocations[uColor]) == 1)
+ if (FastCountBits(pHash->bbPasserLocations[uColor]) == 1)
{
ASSERT(cLeftmostPasser == cRightmostPasser);
}
@@ -2220,18 +2217,18 @@ Return value:
// Pre-count number of pawns per file, unrolled.
ASSERT(pHash->uCountPerFile[uColor][0] == 0);
- pHash->uCountPerFile[uColor][A + 1] = CountBits(pos->bbPawns[uColor] & BBFILE[A]);
- pHash->uCountPerFile[uColor][B + 1] = CountBits(pos->bbPawns[uColor] & BBFILE[B]);
- pHash->uCountPerFile[uColor][C + 1] = CountBits(pos->bbPawns[uColor] & BBFILE[C]);
- pHash->uCountPerFile[uColor][D + 1] = CountBits(pos->bbPawns[uColor] & BBFILE[D]);
- pHash->uCountPerFile[uColor][E + 1] = CountBits(pos->bbPawns[uColor] & BBFILE[E]);
- pHash->uCountPerFile[uColor][F + 1] = CountBits(pos->bbPawns[uColor] & BBFILE[F]);
- pHash->uCountPerFile[uColor][G + 1] = CountBits(pos->bbPawns[uColor] & BBFILE[G]);
- pHash->uCountPerFile[uColor][H + 1] = CountBits(pos->bbPawns[uColor] & BBFILE[H]);
+ pHash->uCountPerFile[uColor][A + 1] = FastCountBits(pos->bbPawns[uColor] & BBFILE[A]);
+ pHash->uCountPerFile[uColor][B + 1] = FastCountBits(pos->bbPawns[uColor] & BBFILE[B]);
+ pHash->uCountPerFile[uColor][C + 1] = FastCountBits(pos->bbPawns[uColor] & BBFILE[C]);
+ pHash->uCountPerFile[uColor][D + 1] = FastCountBits(pos->bbPawns[uColor] & BBFILE[D]);
+ pHash->uCountPerFile[uColor][E + 1] = FastCountBits(pos->bbPawns[uColor] & BBFILE[E]);
+ pHash->uCountPerFile[uColor][F + 1] = FastCountBits(pos->bbPawns[uColor] & BBFILE[F]);
+ pHash->uCountPerFile[uColor][G + 1] = FastCountBits(pos->bbPawns[uColor] & BBFILE[G]);
+ pHash->uCountPerFile[uColor][H + 1] = FastCountBits(pos->bbPawns[uColor] & BBFILE[H]);
ASSERT(pHash->uCountPerFile[uColor][9] == 0);
// Count number of unmoved pawns.
- pHash->uNumUnmovedPawns[uColor] = CountBits(pos->bbPawns[uColor] &
+ pHash->uNumUnmovedPawns[uColor] = FastCountBits(pos->bbPawns[uColor] &
BBRANK[RANK(uUnmovedRank[uColor])]);
ASSERT(pHash->uNumUnmovedPawns[uColor] <= 8);
@@ -2247,7 +2244,7 @@ Return value:
(uColor == WHITE) ?
(pos->bbPawns[WHITE] & (bbAllPawns << 8)) :
(pos->bbPawns[BLACK] & (bbAllPawns >> 8));
- ASSERT(CountBits(pHash->bbStationaryPawns[uColor]) <= 8);
+ ASSERT(FastCountBits(pHash->bbStationaryPawns[uColor]) <= 8);
// Give central pawns a small bonus and rook pawns a small
// penalty.
@@ -2269,9 +2266,86 @@ Return value:
// (and no /2 fixup) is needed the way the old per-pawn,
// per-color loop required.
pHash->uNumRammedPawns =
- CountBits(pos->bbPawns[WHITE] & (pos->bbPawns[BLACK] << 8));
+ FastCountBits(pos->bbPawns[WHITE] & (pos->bbPawns[BLACK] << 8));
ASSERT(pHash->uNumRammedPawns <= 8);
+ // Good/bad bishop pawn-structure score, cached per (color, color-
+ // complex) instead of rescanned by every bishop of that color/
+ // complex in _EvalBishop (board_representation/EVAL.md-style note:
+ // every input here -- bbStationaryPawns, bbPawns, bbPawnAttacks --
+ // is pure pawn-structure data already keyed by this pawn-hash
+ // entry's signature, and the bishop's own square only ever selects
+ // *which* of these two precomputed totals to use, never feeds the
+ // computation itself. Semantics (corrected from the old per-bishop
+ // version, which silently ignored enemy stationary pawns on the
+ // bishop's complex entirely):
+ // - own stationary pawns on the complex: always count (full
+ // weight) -- they're not moving, they wall the bishop in for
+ // good.
+ // - enemy stationary pawns on the complex: count (full weight)
+ // only if defended by another enemy pawn -- an undefended
+ // stationary pawn is still a static target, not a durable
+ // obstruction, the same reasoning the transient case below
+ // already applied to enemy pawns.
+ // - own transient (non-stationary) pawns on the complex: always
+ // count (full weight) -- might move off later, hence the
+ // separate, lighter table.
+ // - enemy transient pawns on the complex: count (half weight)
+ // only if defended -- unsupported ones are likely to be traded
+ // off soon, not a durable obstruction.
+ {
+ static const BITBOARD bbColorSq[2] = { 0x55aa55aa55aa55aaULL,
+ 0xaa55aa55aa55aa55ULL };
+ ULONG uComplex;
+
+ FOREACH_COLOR(uColor)
+ {
+ ULONG xColor = FLIP(uColor);
+ BITBOARD bbNonStationary = ~(pHash->bbStationaryPawns[WHITE] |
+ pHash->bbStationaryPawns[BLACK]);
+
+ for (uComplex = 0; uComplex < 2; uComplex++)
+ {
+ BITBOARD bbMask = bbColorSq[uComplex];
+ SCORE i = 16;
+ BITBOARD bb;
+ COOR cSq;
+
+ bb = pHash->bbStationaryPawns[uColor] & bbMask;
+ while (IS_ON_BOARD(cSq = CoorFromBitBoardRank8ToRank1(&bb)))
+ {
+ i += STATIONARY_PAWN_ON_BISHOP_COLOR[cSq];
+ }
+
+ bb = pHash->bbStationaryPawns[xColor] & bbMask;
+ while (IS_ON_BOARD(cSq = CoorFromBitBoardRank8ToRank1(&bb)))
+ {
+ if (pos->bbPawnAttacks[xColor] & COOR_TO_BB(cSq))
+ {
+ i += STATIONARY_PAWN_ON_BISHOP_COLOR[cSq];
+ }
+ }
+
+ bb = pos->bbPawns[uColor] & bbNonStationary & bbMask;
+ while (IS_ON_BOARD(cSq = CoorFromBitBoardRank8ToRank1(&bb)))
+ {
+ i += TRANSIENT_PAWN_ON_BISHOP_COLOR[cSq];
+ }
+
+ bb = pos->bbPawns[xColor] & bbNonStationary & bbMask;
+ while (IS_ON_BOARD(cSq = CoorFromBitBoardRank8ToRank1(&bb)))
+ {
+ if (pos->bbPawnAttacks[xColor] & COOR_TO_BB(cSq))
+ {
+ i += TRANSIENT_PAWN_ON_BISHOP_COLOR[cSq] / 2;
+ }
+ }
+
+ pHash->iBishopColorScore[uColor][uComplex] = (SHORT)i;
+ }
+ }
+ }
+
// Second pass, requires pHash->uCountPerFile[] populated for both
// colors.
FOREACH_COLOR(uColor)
@@ -2279,7 +2353,7 @@ Return value:
ASSERT(IS_VALID_COLOR(uColor));
ULONG xColor = FLIP(uColor);
ASSERT(IS_VALID_COLOR(xColor));
- ASSERT(CountBits(pos->bbPawns[uColor]) <= 8);
+ ASSERT(FastCountBits(pos->bbPawns[uColor]) <= 8);
d1 = 16 * g_iAhead[uColor];
BITBOARD bbPawnsToWalk = pos->bbPawns[uColor];
@@ -2333,7 +2407,7 @@ Return value:
uPawnFile = FILE(c) + 1;
ASSERT((1 <= uPawnFile) && (uPawnFile <= 8));
- ASSERT(CountBits(pos->bbPawns[uColor] & BBFILE[FILE(c)])
+ ASSERT(FastCountBits(pos->bbPawns[uColor] & BBFILE[FILE(c)])
== pHash->uCountPerFile[uColor][uPawnFile]);
//
@@ -2391,7 +2465,7 @@ Return value:
// it is shielded or exposed.
//
pHash->bbStationaryPawns[uColor] |= COOR_TO_BB(c);
- ASSERT(CountBits(pHash->bbStationaryPawns[uColor]) <= 8);
+ ASSERT(FastCountBits(pHash->bbStationaryPawns[uColor]) <= 8);
if (pHash->uCountPerFile[xColor][uPawnFile])
{
ASSERT(BACKWARD_SHIELDED_BY_LOCATION[c] < 0);
@@ -4213,7 +4287,6 @@ Return value:
BITBOARD bb;
BITBOARD bbMask;
BITBOARD bbPc;
- COOR cSquare;
SCORE i;
ULONG u;
ULONG uTotalMobility;
@@ -4230,60 +4303,26 @@ Return value:
pos->uMinorsAtHome[uColor] += (c == cBishopAtHome[uColor][1]);
ASSERT(pos->uMinorsAtHome[uColor] <= 4);
- // Good and bad bishops given stationary pawn structure.
- i = 16;
+ // Good and bad bishops given pawn structure (own and enemy,
+ // stationary and transient) on this bishop's color complex --
+ // cached per (color, complex) in the pawn hash, since none of it
+ // depends on which square of that complex the bishop actually
+ // sits on. See the cache population in the pawn-hash-miss path
+ // above (_EvalPawns's FOREACH_COLOR block) for the full semantics.
bbMask = bbColorSq[IS_SQUARE_WHITE(c)];
- bb = pHash->bbStationaryPawns[uColor] & bbMask;
- ASSERT(CountBits(bb) <= 16);
- while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb)))
- {
- // Consider stationary (rammed or backward) pawns of the same
- // color as the bishop.
- ASSERT(pos->rgSquare[cSquare].pPiece);
- ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece));
- ASSERT(GET_COLOR(pos->rgSquare[cSquare].pPiece) == uColor);
- i += STATIONARY_PAWN_ON_BISHOP_COLOR[cSquare];
- }
+ i = pHash->iBishopColorScore[uColor][IS_SQUARE_WHITE(c)];
EVAL_TERM(uColor,
BISHOP,
c,
pos->iScore[uColor],
i,
- "good/bad stationary pawns ++");
+ "good/bad bishop pawns");
- i = 0;
+ // Still needed below: "non-stationary pawns of either color on
+ // this color complex," used by the mobility walk's
+ // BMOB_FRIEND_PAWN case (see its comment just below).
bbPc = ~(pHash->bbStationaryPawns[WHITE] |
- pHash->bbStationaryPawns[BLACK]);
- bbPc &= bbMask;
- bb = pos->bbPawns[uColor] & bbPc;
- while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb)))
- {
- ASSERT(pos->rgSquare[cSquare].pPiece);
- ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece));
- ASSERT(GET_COLOR(pos->rgSquare[cSquare].pPiece) == uColor);
- ASSERT(pos->bbPawns[uColor] & COOR_TO_BB(cSquare));
- i += TRANSIENT_PAWN_ON_BISHOP_COLOR[cSquare];
- }
-
- bb = pos->bbPawns[xColor] & bbPc;
- while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb)))
- {
- // N.B. Only count enemy pawns that are supported by another
- // pawn.
- ASSERT(pos->rgSquare[cSquare].pPiece);
- ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece));
- ASSERT(GET_COLOR(pos->rgSquare[cSquare].pPiece) == xColor);
- ASSERT(pos->bbPawns[xColor] & COOR_TO_BB(cSquare));
- i +=
- ((pos->bbPawnAttacks[xColor] & COOR_TO_BB(cSquare)) != 0) *
- TRANSIENT_PAWN_ON_BISHOP_COLOR[cSquare] / 2;
- }
- EVAL_TERM(uColor,
- BISHOP,
- c,
- pos->iScore[uColor],
- i,
- "good/bad transient pawns");
+ pHash->bbStationaryPawns[BLACK]) & bbMask;
//
// Bishop mobility (and update attack tables) -- board_representation/
@@ -4379,7 +4418,7 @@ Return value:
}
pos->bbMinorXrayAttacks[uColor] |= bbXrayAccum;
- uTotalMobility = CountBits(bbMobility);
+ uTotalMobility = FastCountBits(bbMobility);
ASSERT(uTotalMobility <= 13);
@@ -4652,7 +4691,7 @@ Return value:
bbMobility = (bbAttack & bbEnemyNonPawnOcc) |
(bbAttack & ~bbFriendOcc & ~bbEnemyNonPawnOcc &
~bbUnsafeForMinor);
- uMobilitySquares = CountBits(bbMobility);
+ uMobilitySquares = FastCountBits(bbMobility);
}
ASSERT(uMobilitySquares >= 0);
ASSERT(uMobilitySquares <= 8);
@@ -4708,7 +4747,6 @@ Return value:
ULONG uColor;
ULONG uTotalMobility;
BITBOARD bbMobility;
- COOR cSquare;
SCORE i;
ASSERT(IS_ON_BOARD(c));
@@ -4735,17 +4773,6 @@ Return value:
"file/passer bonus");
//
- // Removed 2026-08-30 ("rook trapping enemy king"): a rook on the
- // 7th/8th rank aligned with the enemy king is exactly the
- // geometric pattern CountKingSafetyDefects' CHECK_VECTOR
- // line-of-sight scan already picks up into
- // uPiecesPointingAtKing/king-safety -- this was pricing the same
- // king-danger fact a second time via a rook-specific bonus. Belongs
- // in king safety, not here. (pFriendRook, only ever assigned in
- // this block and never read, is gone too.)
- //
-
- //
// Rooks increase in value as pawns come off:
//
// "A further refinement would be to raise the knight's value by
@@ -4759,9 +4786,12 @@ Return value:
ROOK,
c,
pos->iScore[uColor],
- ROOK_VALUE_AS_PAWNS_COME_OFF[FastCountBits(pos->bbPawns[uColor]) +
- FastCountBits(pos->bbPawns[xColor])],
+ ROOK_VALUE_AS_PAWNS_COME_OFF[
+ FastCountBits(pos->bbPawns[BLACK] | pos->bbPawns[WHITE])
+ ],
"increase in value/pawns");
+ ASSERT((FastCountBits(pos->bbPawns[uColor]) + FastCountBits(pos->bbPawns[xColor])) ==
+ (FastCountBits(pos->bbPawns[uColor] | pos->bbPawns[xColor])));
//
// Rook mobility (and update attack tables) -- board_representation/
@@ -4788,106 +4818,103 @@ Return value:
// credit at the blocker itself, matching the old table); a
// friendly rook blocker also earns the connected-rook bonus.
//
- {
- BITBOARD bbAttack = _RookAttacksBB(c, pos->bbOccupied);
- BITBOARD bbEnemyOcc = _BuildFriendlySideBB(pos, xColor);
- BITBOARD bbEnemySame = pos->bbPieces[xColor][ROOK];
- BITBOARD bbEnemyGEContinue = pos->bbPieces[xColor][QUEEN] |
- COOR_TO_BB(KingPosition(pos, xColor));
- BITBOARD bbFriendRQ = pos->bbPieces[uColor][ROOK] |
- pos->bbPieces[uColor][QUEEN];
- BITBOARD bbUnsafeForRook = pos->bbPawnAttacks[xColor] |
- pos->bbMinorAttacks[xColor];
- BITBOARD bbExclude = 0;
- BITBOARD bbSeen = 0;
- BITBOARD bbLayer = bbAttack;
- BITBOARD bbXrayAccum = 0;
- BITBOARD bbFirstLayerMask = 0;
- bbMobility = 0;
- pos->bbRookAttacks[uColor] |= bbAttack;
+ BITBOARD bbAttack = _RookAttacksBB(c, pos->bbOccupied);
+ BITBOARD bbEnemyOcc = _BuildFriendlySideBB(pos, xColor);
+ BITBOARD bbEnemySame = pos->bbPieces[xColor][ROOK];
+ BITBOARD bbEnemyGEContinue = pos->bbPieces[xColor][QUEEN] |
+ COOR_TO_BB(KingPosition(pos, xColor));
+ BITBOARD bbFriendRQ = pos->bbPieces[uColor][ROOK] |
+ pos->bbPieces[uColor][QUEEN];
+ BITBOARD bbUnsafeForRook = pos->bbPawnAttacks[xColor] |
+ pos->bbMinorAttacks[xColor];
+ BITBOARD bbExclude = 0;
+ BITBOARD bbSeen = 0;
+ BITBOARD bbLayer = bbAttack;
+ BITBOARD bbXrayAccum = 0;
+ BITBOARD bbFirstLayerMask = 0;
- //
- // 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.
- //
- // 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 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;
+ bbMobility = 0;
+ pos->bbRookAttacks[uColor] |= bbAttack;
- bbSeen |= bbNew;
- bbXrayAccum |= (bbNew & bbFirstLayerMask);
- bbFirstLayerMask = ~(BITBOARD)0;
+ //
+ // 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.
+ //
+ // 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 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;
- bbMobility |= (bbEmptyHere & ~bbUnsafeForRook) |
- bbEnemySameHere | bbEnemyGEHere |
- (bbEnemyLEHere & ~bbUnsafeForRook);
+ bbSeen |= bbNew;
+ bbXrayAccum |= (bbNew & bbFirstLayerMask);
+ bbFirstLayerMask = ~(BITBOARD)0;
- //
- // 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)
- {
- 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");
- }
+ bbMobility |= (bbEmptyHere & ~bbUnsafeForRook) |
+ bbEnemySameHere | bbEnemyGEHere |
+ (bbEnemyLEHere & ~bbUnsafeForRook);
- bbContinueHere = bbFriendRQHere | bbEnemyGEHere;
- if (!bbContinueHere)
- {
- break;
- }
- bbExclude |= bbContinueHere;
- bbLayer = _RookAttacksBB(c, pos->bbOccupied & ~bbExclude);
+ // 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)
+ {
+ 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)),
+ "rooks connected");
}
- pos->bbRookXrayAttacks[uColor] |= bbXrayAccum;
- uTotalMobility = CountBits(bbMobility);
+ bbContinueHere = bbFriendRQHere | bbEnemyGEHere;
+ if (!bbContinueHere)
+ {
+ break;
+ }
+ bbExclude |= bbContinueHere;
+ bbLayer = _RookAttacksBB(c, pos->bbOccupied & ~bbExclude);
}
+ pos->bbRookXrayAttacks[uColor] |= bbXrayAccum;
+
+ uTotalMobility = FastCountBits(bbMobility);
ASSERT(uTotalMobility <= 14);
ASSERT(pos->uArmyScaler[xColor] <= 31);
@@ -4899,64 +4926,17 @@ Return value:
// nonzero count here is still liable to be an overcount.
if (uTotalMobility < EVAL_MOBILITY_CAREFULLY_BELOW)
{
- if (uTotalMobility == 0)
- {
- i = ROOK_MOBILITY_BY_SQUARES[0];
- i *= (int)(REDUCED_MATERIAL_UP_SCALER[pos->uArmyScaler[xColor]] + 1);
- i /= 8;
- EVAL_TERM(uColor,
- ROOK,
- c,
- pos->iScore[uColor],
- i,
- "rook mobility");
- }
_RecordTrappedCandidate(pos, uColor, c, bbMobility);
-
- // Is the rook trapped in the corner by a stupid friendly
- // king? -- purely geometric (unrelated to the mobility
- // deferral above), still gated on the same coarse "cramped"
- // raw-mobility threshold -- color-indexed instead of a
- // WHITE/BLACK branch over two mirrored blocks
- // (board_representation/EVAL.md section 9).
- ASSERT(IS_VALID_COLOR(uColor));
- ASSERT(IS_ON_BOARD(c));
-
- static const ULONG BACK_RANK_MASK[2] = { 0x00, 0x70 }; // BLACK, WHITE
- static const COOR E_CORNER[2] = { E8, E1 };
- static const COOR D_CORNER[2] = { D8, D1 };
-
- if ((c & 0xF0) == BACK_RANK_MASK[uColor])
- {
- cSquare = KingPosition(pos, uColor);
- ASSERT(IS_ON_BOARD(cSquare));
- if ((cSquare & 0xF0) == BACK_RANK_MASK[uColor])
- {
- if (((cSquare > E_CORNER[uColor]) && (c > cSquare)) ||
- ((cSquare < D_CORNER[uColor]) && (c < cSquare)))
- {
- EVAL_TERM(uColor,
- ROOK,
- c,
- pos->iScore[uColor],
- KING_TRAPPING_ROOK,
- "king trapping rook");
- }
- }
- }
- }
- else // reasonable mobility
- {
- i = ROOK_MOBILITY_BY_SQUARES[uTotalMobility];
- i *= (int)(REDUCED_MATERIAL_UP_SCALER[pos->uArmyScaler[xColor]] + 1);
- i /= 8;
- EVAL_TERM(uColor,
- ROOK,
- c,
- pos->iScore[uColor],
- i,
- "rook mobility");
}
+ i = ROOK_MOBILITY_BY_SQUARES[uTotalMobility];
+ i *= (int)(REDUCED_MATERIAL_UP_SCALER[pos->uArmyScaler[xColor]] + 1);
+ i /= 8;
+ EVAL_TERM(uColor,
+ ROOK,
+ c,
+ pos->iScore[uColor],
+ i,
+ "rook mobility");
}
@@ -5104,7 +5084,7 @@ Return value:
pos->bbQueenAttacks[uColor] |= bbAttackTotal;
pos->bbQueenXrayAttacks[uColor] |= bbXrayAccum;
- uTotalMobility = CountBits(bbMobility);
+ uTotalMobility = FastCountBits(bbMobility);
}
ASSERT(uTotalMobility <= 27);
@@ -5177,7 +5157,7 @@ Return value:
// instruction, since this is the hottest inner loop in eval.c and
// scaler*(A+B+C+...) is one multiply instead of scaler*A + scaler*B
// + ... Ring-square attack/defense/flight terms below are computed
- // via bitboard intersection + CountBits() against the whole front/
+ // via bitboard intersection + FastCountBits() against the whole front/
// back/side bucket at once (g_KingRingFrontBB/g_KingRingBackBB/
// g_KingRingSideBB, data.c), not a per-square branchy loop -- a
// fixed handful of AND+popcount ops regardless of how many ring
@@ -5266,7 +5246,7 @@ Return value:
(SCORE)KING_COUNTER_BY_ATTACK_PATTERN[bvPattern];
// Count how many ring squares are attacked by the enemy queen.
- uQueenNearKing = CountBits(
+ uQueenNearKing = FastCountBits(
bbRing & (pos->bbQueenAttacks[xColor] | pos->bbQueenXrayAttacks[xColor])
);
ASSERT(uQueenNearKing <= 8);
@@ -5274,45 +5254,45 @@ Return value:
// Minor attacks at front, side, back.
iSafety += KING_RING_ATTACK_MINOR[0] *
- (SCORE)CountBits(bbFront & pos->bbMinorAttacks[xColor]);
+ (SCORE)FastCountBits(bbFront & pos->bbMinorAttacks[xColor]);
iSafety += KING_RING_ATTACK_MINOR[1] *
- (SCORE)CountBits(bbBack & pos->bbMinorAttacks[xColor]);
+ (SCORE)FastCountBits(bbBack & pos->bbMinorAttacks[xColor]);
iSafety += KING_RING_ATTACK_MINOR[2] *
- (SCORE)CountBits(bbSide & pos->bbMinorAttacks[xColor]);
+ (SCORE)FastCountBits(bbSide & pos->bbMinorAttacks[xColor]);
// Rook attacks at front, side, back.
iSafety += KING_RING_ATTACK_ROOK[0] *
- (SCORE)CountBits(bbFront & pos->bbRookAttacks[xColor]);
+ (SCORE)FastCountBits(bbFront & pos->bbRookAttacks[xColor]);
iSafety += KING_RING_ATTACK_ROOK[1] *
- (SCORE)CountBits(bbBack & pos->bbRookAttacks[xColor]);
+ (SCORE)FastCountBits(bbBack & pos->bbRookAttacks[xColor]);
iSafety += KING_RING_ATTACK_ROOK[2] *
- (SCORE)CountBits(bbSide & pos->bbRookAttacks[xColor]);
+ (SCORE)FastCountBits(bbSide & pos->bbRookAttacks[xColor]);
// Pawn attacks.
iSafety += KING_RING_ATTACK_PAWN *
- (SCORE)CountBits(bbRing & pos->bbPawnAttacks[xColor]);
+ (SCORE)FastCountBits(bbRing & pos->bbPawnAttacks[xColor]);
// Undefended squares in front, side, back.
iSafety += KING_RING_UNDEFENDED[0] *
- (SCORE)CountBits(bbFront & bbUndefended);
+ (SCORE)FastCountBits(bbFront & bbUndefended);
iSafety += KING_RING_UNDEFENDED[1] *
- (SCORE)CountBits(bbBack & bbUndefended);
+ (SCORE)FastCountBits(bbBack & bbUndefended);
iSafety += KING_RING_UNDEFENDED[2] *
- (SCORE)CountBits(bbSide & bbUndefended);
+ (SCORE)FastCountBits(bbSide & bbUndefended);
// Only xray attack, no direct attack.
iSafety += KING_RING_XRAY_BONUS *
- (SCORE)CountBits(bbRing & bbEnemyXrayAny & ~bbEnemyAtkAny);
+ (SCORE)FastCountBits(bbRing & bbEnemyXrayAny & ~bbEnemyAtkAny);
// Flight squares are empty, unattacked squares around the king.
// In front of the king, though, these are gaps and a negative
// whereas behind the king they are legitimately small bonuses.
iSafety += KING_RING_FRONT_FLIGHT_PENALTY *
- (SCORE)CountBits(bbFront & bbEmpty & ~bbEnemyAtkAny);
+ (SCORE)FastCountBits(bbFront & bbEmpty & ~bbEnemyAtkAny);
// Small bonus for empty squares behind the king to retreat into.
iSafety += KING_BACK_FLIGHT_BONUS *
- (SCORE)CountBits(bbBack & bbEmpty & ~bbEnemyAtkAny);
+ (SCORE)FastCountBits(bbBack & bbEmpty & ~bbEnemyAtkAny);
#ifdef KING_DEFECT_DUMP
{
@@ -5992,7 +5972,7 @@ Return value:
bb = pHash->bbPasserLocations[uColor];
if (bb != 0)
{
- uNumPassers[uColor] = CountBits(bb);
+ uNumPassers[uColor] = FastCountBits(bb);
ASSERT(uNumPassers[uColor] > 0);
d1 = 16 * g_iAhead[uColor];
ASSERT((d1 * 2) == (32 * g_iAhead[uColor]));
@@ -6379,7 +6359,7 @@ Return value:
// EVAL_MOBILITY_CAREFULLY_BELOW's comment.
bbUnsafeNow = pos->bbKingAttacks[xColor] & ~bbOwnDefend;
bbStillSafe = pos->bbTrappedEscapes[uColor][u] & ~bbUnsafeNow;
- uFinalMobility = CountBits(bbStillSafe);
+ uFinalMobility = FastCountBits(bbStillSafe);
if (IS_BISHOP(p))
{
@@ -7078,13 +7058,13 @@ Return value:
**/
{
POSITION *pos = &(ctx->sPosition);
+ ULONG uColor = pos->uToMove;
+ ULONG xColor = FLIP(uColor);
SCORE iScoreForSideToMove;
SCORE iAlphaMargin, iBetaMargin;
PAWN_HASH_ENTRY *pHash;
COOR c;
ULONG u;
- ULONG uColor = pos->uToMove;
- ULONG xColor = FLIP(uColor);
BITBOARD bb;
FLAG fDeferred;
// Computed at most once per Eval() call, on first need, by
@@ -7129,17 +7109,8 @@ Return value:
#ifdef EVAL_TIME
UINT64 u64MaterialSetupTimer = SystemReadTimeStampCounter();
#endif
- pos->iScore[uColor] =
- (PawnMaterial(pos, uColor) + pos->uNonPawnMaterial[uColor]);
- pos->iScore[xColor] =
- (PawnMaterial(pos, xColor) + pos->uNonPawnMaterial[xColor]);
#ifdef EVAL_DUMP
EvalTraceClear();
- Trace("Material:\n%d\t\t%d\t\t(%d for %s)\n",
- pos->iScore[WHITE], pos->iScore[BLACK],
- (pos->iScore[uColor] - pos->iScore[xColor]),
- COLOR_NAME(uColor)
- );
#endif
// Initialize army scalers here (moved ahead of the super-lazy exit
@@ -7233,7 +7204,7 @@ Return value:
#ifdef EVAL_TIME
UINT64 uSuperLazyTimer = SystemReadTimeStampCounter();
#endif
- iScoreForSideToMove = (pos->iScore[uColor] -pos->iScore[xColor]);
+ iScoreForSideToMove = pos->iMaterialBalance[uColor];
ASSERT(IS_VALID_SCORE(iScoreForSideToMove));
// We have a super-rough eval -- material only at this point.
@@ -7302,6 +7273,7 @@ Return value:
goto end;
#endif
}
+
if (iScoreForSideToMove - iSuperLazySelfPessEnemyOptMargin > iBeta)
{
ASSERT(iScoreForSideToMove > iBeta);
@@ -7380,11 +7352,13 @@ Return value:
#endif
ASSERT(NULL != pHash);
ASSERT(IS_VALID_FLAG(fDeferred));
- pos->iScore[WHITE] += pHash->iScore[WHITE];
- pos->iScore[BLACK] += pHash->iScore[BLACK];
+ pos->iScore[uColor] =
+ (PawnMaterial(pos, uColor) + pos->uNonPawnMaterial[uColor] + pHash->iScore[uColor]);
+ pos->iScore[xColor] =
+ (PawnMaterial(pos, xColor) + pos->uNonPawnMaterial[xColor] + pHash->iScore[xColor]);
ASSERT(IS_VALID_SCORE(pos->iScore[WHITE] - pos->iScore[BLACK]));
#ifdef EVAL_DUMP
- Trace("After pawns:\n%d\t\t%d\t\t(%d for %s)\n",
+ Trace("After pawns + material:\n%d\t\t%d\t\t(%d for %s)\n",
pos->iScore[WHITE], pos->iScore[BLACK],
(pos->iScore[uColor] - pos->iScore[xColor]),
COLOR_NAME(uColor)