summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-15 14:43:55 -0700
committerScott Gasch <[email protected]>2026-09-15 14:43:55 -0700
commit83dbf1e3ac18bad09c2225ec846b5df2a8e04c69 (patch)
tree975193826ae63db8a8ee2f1db33180764c4506f6 /src
parent390e675849ff42c8b7e1806f75552de988741fff (diff)
Rework _EvalKing's own-passer support scan, _EvalPawns' per-pawn bookkeeping, and _EvaluateConnectedSupportedOutsidePassers/_EvaluateCandidatePasser to use bitboard aggregates and cheap short-circuits instead of per-pawn mailbox walks.
_EvalKing: replace the file-mask-plus-DISTANCE-filter loop with a direct g_KingAttacksBB intersection, and the color-branching coordinate-inequality "is the king leading the pawn" check with a g_KingRingBackBB test -- same scoring, no wasted far-rank bitscans. _EvalPawns' first pass: uCountPerFile/uNumUnmovedPawns/bbStationaryPawns are all closed-form popcounts over pos->bbPawns now instead of a per-pawn mailbox loop; also drops the "counted rammed pawns twice, divide by 2" hack entirely, since the closed-form rammed count never double-counts in the first place. Only the per-square centrality bonus still needs a per-pawn walk. _EvaluateConnectedSupportedOutsidePassers: leftmost/rightmost passer tracking no longer needs an outer per-file loop just to get file-ordered iteration -- a single flat walk over bbPasserLocations with explicit min/max comparison visits the same squares. _EvaluateCandidatePasser: short-circuit the expensive helper-square walk when the pawn already qualifies as a candidate via the cheap "advanced rank + adjacent-file support" rule, since that disjunct makes the walk's result irrelevant to the final decision. All four verified semantically identical to the pre-change code via matched-flag EVAL_DUMP term tracing plus targeted eval-score sweeps (~45 positions total covering unmoved/rammed/isolated/doubled/candidate/connected/supported/outside-passer cases for both colors and edge files). Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/eval.c305
1 files changed, 153 insertions, 152 deletions
diff --git a/src/eval.c b/src/eval.c
index ab4d07d..d310817 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -1573,6 +1573,23 @@ Return value:
// helpers to decide if this pawn is a candidate passer. First
// figure out the critical square we need to get the passer
// through.
+ //
+ // Cheap short-circuit first: an advanced pawn (rank 6/7 for WHITE,
+ // rank 2/3 for BLACK) with a friend pawn on either adjacent file
+ // already qualifies as a candidate regardless of what the (far
+ // more expensive) helper-square walk below would find -- the two
+ // conditions are OR'd together at the bottom of this function, so
+ // computing uHelpers when this is already true is pure wasted
+ // work. Skip straight to the candidate-passer bonus in that case.
+ //
+ if ((pHash->uCountPerFile[uColor][uPawnFile + 1] +
+ pHash->uCountPerFile[uColor][uPawnFile - 1]) &&
+ (((WHITE == uColor) && (RANK(c) > 5)) ||
+ ((BLACK == uColor) && (RANK(c) < 4))))
+ {
+ goto give_candidate_bonus;
+ }
+
if (uColor == WHITE)
{
pSentry = BLACK_PAWN;
@@ -1729,12 +1746,9 @@ Return value:
}
done_helpers:
- if ((uHelpers >= uSentries) ||
- ((pHash->uCountPerFile[uColor][uPawnFile + 1] +
- pHash->uCountPerFile[uColor][uPawnFile - 1]) &&
- (((WHITE == uColor) && (RANK(c) > 5)) ||
- ((BLACK == uColor) && (RANK(c) < 4)))))
+ if (uHelpers >= uSentries)
{
+ give_candidate_bonus:
ASSERT(CANDIDATE_PASSER_BY_RANK[uColor][RANK(c)] > 0);
EVAL_TERM(uColor,
PAWN,
@@ -1798,66 +1812,69 @@ Return value:
ASSERT(pos->uPawnCount[uColor] > 0);
pFriend = BLACK_PAWN | uColor;
cLeftmostPasser = cRightmostPasser = ILLEGAL_COOR;
- for (u = A; u <= H; u++)
+ // Leftmost/rightmost is just a min/max over FILE(c) -- no
+ // need to wrap the passer walk in an outer per-file loop
+ // just to get a file-ordered traversal; a single flat walk
+ // over the whole bbPasserLocations bitboard visits the same
+ // squares (CoorFromBitBoardRank8ToRank1 pops in rank-major
+ // order, so leftmost/rightmost has to be tracked by explicit
+ // comparison either way, not by iteration order).
+ bb = pHash->bbPasserLocations[uColor];
+ while(IS_ON_BOARD(c = CoorFromBitBoardRank8ToRank1(&bb)))
{
- bb = pHash->bbPasserLocations[uColor] & BBFILE[u];
- if (bb != 0)
+ ASSERT(pHash->uCountPerFile[uColor][FILE(c)+1] != 0);
+ ASSERT(RANK(c) > 1);
+ ASSERT(RANK(c) < 8);
+ if ((cLeftmostPasser == ILLEGAL_COOR) ||
+ (FILE(c) < FILE(cLeftmostPasser)))
+ {
+ cLeftmostPasser = c;
+ }
+ if ((cRightmostPasser == ILLEGAL_COOR) ||
+ (FILE(c) > FILE(cRightmostPasser)))
+ {
+ cRightmostPasser = c;
+ }
+
+ v = 0;
+ while(iDelta[v] != 0)
{
- ASSERT(pHash->uCountPerFile[uColor][u+1] != 0);
- while(IS_ON_BOARD(c = CoorFromBitBoardRank8ToRank1(&bb)))
+ cSupport = c + iDelta[v] * g_iBehind[uColor];
+ if (IS_ON_BOARD(cSupport))
{
- // Keep track of leftmost/rightmost passer for
- // outside passer code later on.
- ASSERT(FILE(c) == u);
- ASSERT(RANK(c) > 1);
- ASSERT(RANK(c) < 8);
- if (cLeftmostPasser == ILLEGAL_COOR)
+ if (pHash->bbPasserLocations[uColor] &
+ COOR_TO_BB(cSupport))
{
- cLeftmostPasser = c;
- }
- cRightmostPasser = c;
+ ASSERT(CONNECTED_PASSERS_BY_RANK[uColor]
+ [RANK(c)] > 0);
+ EVAL_TERM(uColor,
+ PAWN,
+ c,
+ pHash->iScore[uColor],
+ CONNECTED_PASSERS_BY_RANK[uColor]
+ [RANK(c)],
+ "connected passers");
- v = 0;
- while(iDelta[v] != 0)
+ //
+ // TODO: connected passers vs a R is
+ // strong.
+ //
+ }
+ else if (pos->rgSquare[cSupport].pPiece ==
+ pFriend)
{
- cSupport = c + iDelta[v] * g_iBehind[uColor];
- if (IS_ON_BOARD(cSupport))
- {
- if (pHash->bbPasserLocations[uColor] &
- COOR_TO_BB(cSupport))
- {
- ASSERT(CONNECTED_PASSERS_BY_RANK[uColor]
- [RANK(c)] > 0);
- EVAL_TERM(uColor,
- PAWN,
- c,
- pHash->iScore[uColor],
- CONNECTED_PASSERS_BY_RANK[uColor]
- [RANK(c)],
- "connected passers");
-
- //
- // TODO: connected passers vs a R is
- // strong.
- //
- }
- else if (pos->rgSquare[cSupport].pPiece ==
- pFriend)
- {
- ASSERT(SUPPORTED_PASSER_BY_RANK[uColor]
- [RANK(c)] > 0);
- EVAL_TERM(uColor,
- PAWN,
- c,
- pHash->iScore[uColor],
- SUPPORTED_PASSER_BY_RANK[uColor]
- [RANK(c)],
- "supported passer");
- }
- }
- v++;
+ ASSERT(SUPPORTED_PASSER_BY_RANK[uColor]
+ [RANK(c)] > 0);
+ EVAL_TERM(uColor,
+ PAWN,
+ c,
+ pHash->iScore[uColor],
+ SUPPORTED_PASSER_BY_RANK[uColor]
+ [RANK(c)],
+ "supported passer");
}
}
+ v++;
}
}
@@ -2034,84 +2051,81 @@ Return value:
iDuos[BLACK] = iDuos[WHITE] = 0;
//
- // First pass
+ // First pass -- everything here except the per-square centrality
+ // bonus is a closed-form aggregate over pos->bbPawns[uColor]
+ // (uCountPerFile, uNumUnmovedPawns, bbStationaryPawns/
+ // uNumRammedPawns), so none of it needs a per-pawn mailbox walk;
+ // only the centrality lookup genuinely needs one (it's a per-square
+ // table, not summable any other way). Replaces the old single loop
+ // over pos->cPawns[uColor][] that did all of these one pawn at a
+ // time, including a "counted rammed pawns twice, divide by 2 to fix
+ // it" hack that a closed-form popcount doesn't need in the first
+ // place (see below).
//
- FOREACH_COLOR(uColor)
{
- ASSERT(IS_VALID_COLOR(uColor));
- ASSERT(pos->uPawnCount[uColor] <= 8);
+ BITBOARD bbAllPawns = pos->bbPawns[WHITE] | pos->bbPawns[BLACK];
- d1 = 16 * g_iAhead[uColor];
- for (u = 0;
- u < pos->uPawnCount[uColor];
- u++)
+ FOREACH_COLOR(uColor)
{
- c = pos->cPawns[uColor][u];
- ASSERT(IS_ON_BOARD(c));
- ASSERT(pos->rgSquare[c].pPiece);
- ASSERT(IS_PAWN(pos->rgSquare[c].pPiece));
- uPawnFile = FILE(c) + 1;
- ASSERT((1 <= uPawnFile) && (uPawnFile <= 8));
+ ASSERT(IS_VALID_COLOR(uColor));
+ ASSERT(CountBits(pos->bbPawns[uColor]) == pos->uPawnCount[uColor]);
- //
- // Update files counter
- //
- ASSERT(pHash->uCountPerFile[uColor][uPawnFile] < 6);
- pHash->uCountPerFile[uColor][uPawnFile]++;
+ for (u = A; u <= H; u++)
+ {
+ pHash->uCountPerFile[uColor][u + 1] =
+ CountBits(pos->bbPawns[uColor] & BBFILE[u]);
+ ASSERT(pHash->uCountPerFile[uColor][u + 1] < 6);
+ }
- //
- // pos->bbPawns[uColor] is plain POSITION state, maintained
- // incrementally by move.c on every pawn move -- already
- // has this pawn's bit set by the time we get here, nothing
- // to build. (No running-count cross-check against
- // uCountPerFile here: unlike the old bit-by-bit
- // pHash->bbPawnLocations this replaces, bbPawns already
- // holds every pawn on the board up front, not just the
- // ones this loop has visited so far, so a per-iteration
- // "counts so far agree" comparison isn't meaningful
- // against it -- only a post-loop total would be.)
- //
- ASSERT(pos->bbPawns[uColor] & COOR_TO_BB(c));
+ pHash->uNumUnmovedPawns[uColor] =
+ CountBits(pos->bbPawns[uColor] &
+ BBRANK[RANK(uUnmovedRank[uColor])]);
+ ASSERT(pHash->uNumUnmovedPawns[uColor] <= 8);
//
- // Count unmoved pawns
+ // Detect rammed and other stationary pawns. A pawn is
+ // "stationary" if *any* pawn (either color -- e.g. a
+ // doubled own-pawn block counts too) sits on its stop
+ // square; it's specifically "rammed" only when that
+ // blocker is the enemy's. WHITE's stop square is one rank
+ // toward rank8 (bit index -8 in the linear 0-63 bitboard
+ // numbering -- see _PopulatePawnAttackBits' >>8/<<8 shifts
+ // for the same convention), BLACK's is +8.
//
- pHash->uNumUnmovedPawns[uColor] +=
- ((c & 0xF0) == uUnmovedRank[uColor]);
- ASSERT(pHash->uNumUnmovedPawns[uColor] <= 8);
+ pHash->bbStationaryPawns[uColor] =
+ (uColor == WHITE) ?
+ (pos->bbPawns[WHITE] & (bbAllPawns << 8)) :
+ (pos->bbPawns[BLACK] & (bbAllPawns >> 8));
+ ASSERT(CountBits(pHash->bbStationaryPawns[uColor]) <= 8);
//
- // Detect rammed and other stationary pawns.
+ // Give central pawns a small bonus; penalize rook pawns.
+ // This is the one term above that's genuinely per-square
+ // and can't be folded into a popcount.
//
- cSquare = c + d1;
- ASSERT(IS_ON_BOARD(cSquare));
- p = pos->rgSquare[cSquare].pPiece;
- if (IS_PAWN(p))
+ bb = pos->bbPawns[uColor];
+ while(IS_ON_BOARD(c = CoorFromBitBoardRank8ToRank1(&bb)))
{
- pHash->bbStationaryPawns[uColor] |= COOR_TO_BB(c);
- ASSERT(CountBits(pHash->bbStationaryPawns[uColor]) <= 8);
- pHash->uNumRammedPawns += OPPOSITE_COLORS(p, uColor);
- ASSERT(pHash->uNumRammedPawns <= 16);
+ EVAL_TERM(uColor,
+ PAWN,
+ c,
+ pHash->iScore[uColor],
+ PAWN_CENTRALITY_BONUS[c],
+ "centrality");
}
-
- //
- // Give central pawns a small bonus; penalize rook pawns:
- //
- EVAL_TERM(uColor,
- PAWN,
- c,
- pHash->iScore[uColor],
- PAWN_CENTRALITY_BONUS[c],
- "centrality");
}
- }
- //
- // We counted rammed pawns twice (once for the black pawn and once
- // for the white one). Fix this now.
- //
- ASSERT(!(pHash->uNumRammedPawns & 1));
- pHash->uNumRammedPawns /= 2;
+ //
+ // Rammed pairs (an enemy pawn, not just any pawn, on the stop
+ // square): each pair shows up as exactly one bit in each
+ // side's own stationary-pawn count, so no double-counting
+ // (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));
+ ASSERT(pHash->uNumRammedPawns <= 8);
+ }
//
// Second pass
@@ -4833,38 +4847,26 @@ Return value:
"centralize king");
// Kings in front of passers in the late endgame are strong...
- cSquare = FILE(c);
- bb = pHash->bbPasserLocations[uColor] &
- BBADJACENT_FILES_INCLUSIVE[cSquare];
- if (bb)
+ // -- bb here is exactly the own-passer squares at king-move
+ // distance 1 (g_KingAttacksBB is that mask by construction), so
+ // every popped square counts; no separate DISTANCE() filter or
+ // ASSERTs needed. The extra "leading the pawn toward promotion"
+ // bonus applies iff the passer square is in the king's own
+ // back ring (g_KingRingBackBB) -- side- and front-ring passers
+ // still get the base rank-scaled support bonus, just not this
+ // extra.
+ bb = pHash->bbPasserLocations[uColor] & g_KingAttacksBB[c];
+ while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb)))
{
- while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb)))
- {
- if (DISTANCE(cSquare, c) == 1)
- {
- ASSERT(abs((INT)FILE(c) - (INT)FILE(cSquare)) <= 1);
- ASSERT(abs((INT)RANK(c) - (INT)RANK(cSquare)) <= 1);
-
- i = 2;
- i += KING_SUPPORTING_OWN_PASSER_BY_RANK[uColor][RANK(cSquare)];
- switch(uColor)
- {
- case WHITE:
- i += 8 * (c < (cSquare - 1));
- break;
-
- case BLACK:
- i += 8 * (c > (cSquare + 1));
- break;
- }
- EVAL_TERM(uColor,
- KING,
- c,
- iKingScore,
- i,
- "supporting own passer");
- }
- }
+ i = 2;
+ i += KING_SUPPORTING_OWN_PASSER_BY_RANK[uColor][RANK(cSquare)];
+ i += 8 * ((COOR_TO_BB(cSquare) & g_KingRingBackBB[uColor][c]) != 0);
+ EVAL_TERM(uColor,
+ KING,
+ c,
+ iKingScore,
+ i,
+ "supporting own passer");
}
//
@@ -4875,8 +4877,7 @@ Return value:
if ((u == 2) && (uColor == WHITE))
{
i = 0;
- bb = (pos->bbPawns[WHITE] |
- pos->bbPawns[BLACK]);
+ bb = (pos->bbPawns[WHITE] | pos->bbPawns[BLACK]);
while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb)))
{
i += (DISTANCE(cSquare, pos->cNonPawns[BLACK][0]) -