diff options
| author | Scott Gasch <[email protected]> | 2026-09-15 14:43:55 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-09-15 14:43:55 -0700 |
| commit | 83dbf1e3ac18bad09c2225ec846b5df2a8e04c69 (patch) | |
| tree | 975193826ae63db8a8ee2f1db33180764c4506f6 /src/eval.c | |
| parent | 390e675849ff42c8b7e1806f75552de988741fff (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/eval.c')
| -rw-r--r-- | src/eval.c | 305 |
1 files changed, 153 insertions, 152 deletions
@@ -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]) - |
