summaryrefslogtreecommitdiff
path: root/src/eval.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-22 13:04:39 -0700
committerScott Gasch <[email protected]>2026-09-22 13:04:39 -0700
commitb7e4cdfeb3c1aadb3f488f6794e6d1a748dc6c8f (patch)
treeffc5bb4e2a4a4d9d027df38c9619739a3dbb478e /src/eval.c
parentf422daab223103bbd62a5611be0b73eaa6a5dc05 (diff)
eval.c: defer B/N/R mobility scoring near enemy king, queen king-adjacency fix, FLIP() -> precomputed xColor
Bishop/knight/rook mobility computed during their own eval pass only sees enemy pawn (B/N) or pawn+minor (R) attacks -- a square right next to the enemy king looks "safe" and counts toward mobility even though it plainly isn't (e.g. a pawn push cutting off a bishop's retreat onto squares next to the enemy king). Below EVAL_MOBILITY_CAREFULLY_BELOW (3) candidate squares, defer scoring to _EvalTrappedPieces, which re-derives the true safe-escape count once every piece type -- the enemy king included -- has contributed to the attack tables. Genuinely 0-mobility pieces are scored immediately instead: more attack info can only shrink an already-empty set, so there's nothing to gain by waiting. Queen doesn't need deferral (its own eval pass runs after every smaller piece type, so it already has complete information) -- instead bbUnsafeForQueen now also excludes squares adjacent to the enemy king unless we already defend them. Also: precompute xColor = FLIP(uColor) once per function/loop iteration everywhere eval.c could hit FLIP() more than once evaluating it, instead of recomputing it at each use site. Verified: precommit_check.sh (self-test + DEBUG smoke test) clean. Curated ECM suites at sd10 vs head_reference: ecm_ringers 4/11 (was 3/11), ecm_confident_quick 70/90 (flat), ecm_hard_quick 19/90 (was 18/90) -- no regressions. Overnight self-play SPRT trending positive (370 games, llr=+0.20). Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/eval.c')
-rw-r--r--src/eval.c430
1 files changed, 308 insertions, 122 deletions
diff --git a/src/eval.c b/src/eval.c
index 7d3f38b..e84470b 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -50,6 +50,18 @@ Revision History:
#define CoorFromBitBoardRank1ToRank8 FastCoorFromBitBoardRank1ToRank8
#endif // !CROUTINES
+// Bishop/knight/rook mobility computed during their own eval pass only
+// knows about enemy PAWN attacks (bishop/knight) or enemy PAWN+MINOR
+// attacks (rook) -- the only attack tables fully populated that early
+// -- so a square right next to the enemy king, or (for bishop/knight)
+// covered by an enemy piece not yet evaluated, looks "safe" and counts
+// toward mobility even though it plainly isn't. Below this many
+// candidate squares, don't trust that partial-information count: defer
+// to _EvalTrappedPieces, which re-derives a final safe-escape count
+// once the full attack picture (both colors, both kings) is in.
+// Genuinely 0-mobility pieces are the one exception -- see
+// _EvalBishop's comment on why those are scored immediately instead.
+#define EVAL_MOBILITY_CAREFULLY_BELOW (3)
//
// To simplify code / maintenance I use the same loop for both colors
@@ -1782,6 +1794,7 @@ Return value:
**/
{
ULONG uColor;
+ ULONG xColor;
ULONG u, v;
COOR c;
COOR cLeftmostPasser, cRightmostPasser;
@@ -1796,6 +1809,7 @@ Return value:
FOREACH_COLOR(uColor)
{
ASSERT(IS_VALID_COLOR(uColor));
+ xColor = FLIP(uColor);
if (pHash->bbPasserLocations[uColor])
{
ASSERT(FastCountBits(pos->bbPawns[uColor]) > 0);
@@ -1881,10 +1895,10 @@ Return value:
#endif
for (u = A; u <= D; u++)
{
- if (pHash->uCountPerFile[FLIP(uColor)][u + 1] != 0)
+ if (pHash->uCountPerFile[xColor][u + 1] != 0)
{
- ASSERT(pos->bbPawns[FLIP(uColor)] & BBFILE[u]);
- if (!(pHash->bbPasserLocations[FLIP(uColor)] & BBFILE[u]))
+ ASSERT(pos->bbPawns[xColor] & BBFILE[u]);
+ if (!(pHash->bbPasserLocations[xColor] & BBFILE[u]))
{
break;
}
@@ -1902,10 +1916,10 @@ Return value:
for (u = H; u >= E; u--)
{
- if (pHash->uCountPerFile[FLIP(uColor)][u + 1] != 0)
+ if (pHash->uCountPerFile[xColor][u + 1] != 0)
{
- ASSERT(pos->bbPawns[FLIP(uColor)] & BBFILE[u]);
- if (!(pHash->bbPasserLocations[FLIP(uColor)] & BBFILE[u]))
+ ASSERT(pos->bbPawns[xColor] & BBFILE[u]);
+ if (!(pHash->bbPasserLocations[xColor] & BBFILE[u]))
{
break;
}
@@ -2017,6 +2031,8 @@ Return value:
**/
{
+ ULONG xColor = FLIP(uColor);
+
if (0 == pHash->uCountPerFile[uColor][uPawnFile + 1])
{
return FALSE; // no friendly pawn on that file at all
@@ -2049,7 +2065,7 @@ Return value:
{
BITBOARD bbDestination = (uColor == WHITE) ?
(bbHelper >> 8) : (bbHelper << 8);
- if ((bbDestination & pos->bbPawnAttacks[FLIP(uColor)]) &&
+ if ((bbDestination & pos->bbPawnAttacks[xColor]) &&
!(bbDestination & pos->bbPawnAttacks[uColor]))
{
return FALSE;
@@ -2062,7 +2078,7 @@ Return value:
// enemy pawn on this same file already sits at c's rank or behind
// it (from our own forward direction), permanently blocking the
// way.
- return (0 == (pos->bbPawns[FLIP(uColor)] & BBFILE[uPawnFile] & bbBlockingRanks));
+ return (0 == (pos->bbPawns[xColor] & BBFILE[uPawnFile] & bbBlockingRanks));
}
@@ -4065,23 +4081,29 @@ Return value:
static void
_RecordTrappedCandidate(IN OUT POSITION *pos,
IN ULONG uColor,
- IN COOR c)
+ IN COOR c,
+ IN BITBOARD bbEscapes)
/**
Routine description:
- Record a zero-mobility piece as a trapped-piece candidate for
- uColor. Used by _EvalBishop/_EvalKnight/_EvalRook/_EvalQueen; the
- actual attacked-or-not check happens later, in
- _EvalTrappedPieces. Capped at ARRAY_LENGTH(pos->cTrapped[0])
- candidates per side -- extras are just dropped, not a real concern
- in practice.
+ Record a trapped-piece candidate for uColor -- either a genuinely
+ zero-mobility piece (bbEscapes passed as 0: nothing to re-derive,
+ see _EvalBishop's comment), or a bishop/knight deferred because its
+ own partial-information mobility count was low but nonzero
+ (bbEscapes is that piece's own bbMobility snapshot at nomination
+ time). Used by _EvalBishop/_EvalKnight/_EvalRook/_EvalQueen; the
+ actual attacked-or-not check, and (for a deferred candidate) the
+ final mobility re-derivation, happen later in _EvalTrappedPieces.
+ Capped at ARRAY_LENGTH(pos->cTrapped[0]) candidates per side --
+ extras are just dropped, not a real concern in practice.
Parameters:
POSITION *pos,
ULONG uColor,
- COOR c
+ COOR c,
+ BITBOARD bbEscapes
Return value:
@@ -4091,6 +4113,7 @@ Return value:
{
if (pos->uNumTrapped[uColor] < ARRAY_LENGTH(pos->cTrapped[uColor]))
{
+ pos->bbTrappedEscapes[uColor][pos->uNumTrapped[uColor]] = bbEscapes;
pos->cTrapped[uColor][pos->uNumTrapped[uColor]++] = c;
}
}
@@ -4201,19 +4224,6 @@ Return value:
"good/bad transient pawns");
//
- // Removed 2026-08-30: BISHOP_IN_CLOSED_POSITION[pos->uClosedScaler]
- // duplicated bishop mobility (computed just below) rather than
- // adding a distinct angle on it. uClosedScaler is a coarse,
- // whole-board openness proxy for exactly the same fact bishop
- // mobility measures directly and per-bishop via ray-casting -- how
- // much this specific bishop's diagonals are blocked by pawns. The
- // mobility term is strictly more precise (opposite-bishop-color
- // endgames are the clearest case where "position is open overall"
- // and "this bishop's diagonal is blocked" diverge), so cut the
- // redundant whole-board proxy rather than scale it down.
- //
-
- //
// 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
@@ -4310,20 +4320,51 @@ Return value:
uTotalMobility = CountBits(bbMobility);
ASSERT(uTotalMobility <= 13);
- EVAL_TERM(uColor,
- BISHOP,
- c,
- pos->iScore[uColor],
- BISHOP_MOBILITY_BY_SQUARES[uTotalMobility],
- "bishop mobility");
//
- // Look for bishops with no mobility, they are trapped and, later,
- // we'll see if they are also under attack too.
+ // uTotalMobility here is necessarily pessimistic-as-an-upper-bound
+ // (not pessimistic-as-a-lower-bound): at this point in Eval() only
+ // enemy PAWN attacks are known, so a square next to the enemy king,
+ // or covered by an enemy piece that hasn't run its own eval pass
+ // yet, still looks safe and gets counted. More attack information
+ // can only mark MORE squares unsafe later, never fewer -- so this
+ // count can only ever be too high, never too low.
//
- if (uTotalMobility == 0)
+ // That asymmetry is exactly why a genuinely 0-mobility bishop is
+ // scored and recorded immediately rather than deferred: 0 is
+ // already the floor, so a fuller attack picture later can't change
+ // it (0 minus more unsafe squares is still 0) -- there is nothing
+ // to gain by waiting. A LOW but nonzero count, however, could
+ // easily still be an overcount (the classic case that motivated
+ // this: a pawn push that cuts off the bishop's retreat leaves two
+ // "safe" squares right next to the enemy king that are only safe
+ // because the king's own attacks aren't in the table yet) -- defer
+ // those to _EvalTrappedPieces, which re-derives the true count once
+ // both kings and every piece type have contributed to the attack
+ // tables, instead of locking in a score that a fuller picture might
+ // revise downward.
+ //
+ if (uTotalMobility < EVAL_MOBILITY_CAREFULLY_BELOW)
{
- _RecordTrappedCandidate(pos, uColor, c);
+ if (uTotalMobility == 0)
+ {
+ EVAL_TERM(uColor,
+ BISHOP,
+ c,
+ pos->iScore[uColor],
+ BISHOP_MOBILITY_BY_SQUARES[0],
+ "bishop mobility");
+ }
+ _RecordTrappedCandidate(pos, uColor, c, bbMobility);
+ }
+ else // reasonable mobility
+ {
+ EVAL_TERM(uColor,
+ BISHOP,
+ c,
+ pos->iScore[uColor],
+ BISHOP_MOBILITY_BY_SQUARES[uTotalMobility],
+ "bishop mobility");
}
//
@@ -4391,6 +4432,7 @@ Return value:
ULONG xColor;
ULONG uPawnsSupporting;
ULONG uMobilitySquares;
+ BITBOARD bbMobility;
SCORE i;
ULONG uDist;
@@ -4542,7 +4584,6 @@ Return value:
BITBOARD bbEnemyNonPawnOcc = _BuildFriendlySideBB(pos, xColor) &
~pos->bbPawns[xColor];
BITBOARD bbUnsafeForMinor = pos->bbPawnAttacks[xColor];
- BITBOARD bbMobility;
pos->bbMinorAttacks[uColor] |= bbAttack;
@@ -4553,15 +4594,31 @@ Return value:
}
ASSERT(uMobilitySquares >= 0);
ASSERT(uMobilitySquares <= 8);
- EVAL_TERM(uColor,
- KNIGHT,
- c,
- pos->iScore[uColor],
- KNIGHT_MOBILITY_BY_COUNT[uMobilitySquares],
- "knight mobility");
- if (uMobilitySquares == 0)
+
+ // See _EvalBishop's comment above its own identically-structured
+ // mobility scoring for why 0-mobility is scored immediately while
+ // a low-but-nonzero count is deferred to _EvalTrappedPieces instead.
+ if (uMobilitySquares < EVAL_MOBILITY_CAREFULLY_BELOW)
{
- _RecordTrappedCandidate(pos, uColor, c);
+ if (uMobilitySquares == 0)
+ {
+ EVAL_TERM(uColor,
+ KNIGHT,
+ c,
+ pos->iScore[uColor],
+ KNIGHT_MOBILITY_BY_COUNT[0],
+ "knight mobility");
+ }
+ _RecordTrappedCandidate(pos, uColor, c, bbMobility);
+ }
+ else // reasonable mobility
+ {
+ EVAL_TERM(uColor,
+ KNIGHT,
+ c,
+ pos->iScore[uColor],
+ KNIGHT_MOBILITY_BY_COUNT[uMobilitySquares],
+ "knight mobility");
}
}
@@ -4588,6 +4645,7 @@ Return value:
PIECE p;
ULONG uColor;
ULONG uTotalMobility;
+ BITBOARD bbMobility;
COOR cSquare;
SCORE i;
@@ -4640,7 +4698,7 @@ Return value:
c,
pos->iScore[uColor],
ROOK_VALUE_AS_PAWNS_COME_OFF[FastCountBits(pos->bbPawns[uColor]) +
- FastCountBits(pos->bbPawns[FLIP(uColor)])],
+ FastCountBits(pos->bbPawns[xColor])],
"increase in value/pawns");
//
@@ -4670,21 +4728,21 @@ Return value:
//
{
BITBOARD bbAttack = _RookAttacksBB(c, pos->bbOccupied);
- BITBOARD bbEnemyOcc = _BuildFriendlySideBB(pos, FLIP(uColor));
- BITBOARD bbEnemySame = pos->bbPieces[FLIP(uColor)][ROOK];
- BITBOARD bbEnemyGEContinue = pos->bbPieces[FLIP(uColor)][QUEEN] |
- COOR_TO_BB(KingPosition(pos, FLIP(uColor)));
+ 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[FLIP(uColor)] |
- pos->bbMinorAttacks[FLIP(uColor)];
+ BITBOARD bbUnsafeForRook = pos->bbPawnAttacks[xColor] |
+ pos->bbMinorAttacks[xColor];
BITBOARD bbExclude = 0;
BITBOARD bbSeen = 0;
BITBOARD bbLayer = bbAttack;
- BITBOARD bbMobility = 0;
BITBOARD bbXrayAccum = 0;
BITBOARD bbFirstLayerMask = 0;
+ bbMobility = 0;
pos->bbRookAttacks[uColor] |= bbAttack;
//
@@ -4769,57 +4827,74 @@ Return value:
uTotalMobility = CountBits(bbMobility);
}
ASSERT(uTotalMobility <= 14);
+ ASSERT(pos->uArmyScaler[xColor] <= 31);
- ASSERT(pos->uArmyScaler[FLIP(uColor)] <= 31);
- i = ROOK_MOBILITY_BY_SQUARES[uTotalMobility];
- i *= (int)(REDUCED_MATERIAL_UP_SCALER[pos->uArmyScaler[FLIP(uColor)]] + 1);
- i /= 8;
- EVAL_TERM(uColor,
- ROOK,
- c,
- pos->iScore[uColor],
- i,
- "rook mobility");
-
- if (uTotalMobility < 3)
+ // Same partial-information reasoning, and same immediate-0/deferred-
+ // low-nonzero split, as _EvalBishop's identically-structured
+ // mobility scoring (see its comment) -- bbUnsafeForRook above only
+ // knows about enemy pawn/minor attacks, not the enemy king's or (for
+ // a rook specifically) a not-yet-evaluated enemy queen's, so a low
+ // nonzero count here is still liable to be an overcount.
+ if (uTotalMobility < EVAL_MOBILITY_CAREFULLY_BELOW)
{
- // Look for rooks with no mobility who are under attack. These
- // pieces are trapped!
if (uTotalMobility == 0)
{
- _RecordTrappedCandidate(pos, uColor, c);
+ 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);
- // Rook trapped in the corner by a stupid friendly king? --
- // color-indexed instead of a WHITE/BLACK branch over two
- // mirrored blocks (board_representation/EVAL.md section 9).
+ // 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));
- {
- 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 };
+ ASSERT(IS_ON_BOARD(c));
- ASSERT(IS_ON_BOARD(c));
- if ((c & 0xF0) == BACK_RANK_MASK[uColor])
+ 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])
{
- 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)))
{
- 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");
- }
+ EVAL_TERM(uColor,
+ ROOK,
+ c,
+ pos->iScore[uColor],
+ KING_TRAPPING_ROOK,
+ "king trapping rook");
}
}
}
- } // if low mobility
+ }
+ 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");
+ }
}
@@ -4843,6 +4918,7 @@ Return value:
{
PIECE p = pos->rgSquare[c].pPiece;
ULONG uColor;
+ ULONG xColor;
ULONG uTotalMobility;
ULONG u;
COOR cKing;
@@ -4851,7 +4927,8 @@ Return value:
ASSERT(p && IS_QUEEN(p));
uColor = GET_COLOR(p);
ASSERT(IS_VALID_COLOR(uColor));
- cKing = KingPosition(pos, FLIP(uColor));
+ xColor = FLIP(uColor);
+ cKing = KingPosition(pos, xColor);
ASSERT(IS_ON_BOARD(cKing));
ASSERT(IS_KING(pos->rgSquare[cKing].pPiece));
@@ -4890,12 +4967,29 @@ Return value:
// more), so the enemy side of each pass's continue-set is empty.
//
{
- BITBOARD bbEnemyOcc = _BuildFriendlySideBB(pos, FLIP(uColor));
- BITBOARD bbEnemyGE = pos->bbPieces[FLIP(uColor)][QUEEN] |
+ BITBOARD bbEnemyOcc = _BuildFriendlySideBB(pos, xColor);
+ BITBOARD bbEnemyGE = pos->bbPieces[xColor][QUEEN] |
COOR_TO_BB(cKing);
- BITBOARD bbUnsafeForQueen = pos->bbPawnAttacks[FLIP(uColor)] |
- pos->bbMinorAttacks[FLIP(uColor)] |
- pos->bbRookAttacks[FLIP(uColor)];
+ //
+ // Enemy king attacks are also unsafe for the queen -- a free
+ // O(1) table lookup by king square, independent of eval order
+ // (unlike the enemy minor/rook attacks above, which do need
+ // both colors' knight/bishop/rook passes to finish first, and
+ // already have by queen's turn: see the eval call order in
+ // Eval() below). Only unsafe when the queen wouldn't be
+ // recaptured by something of our own first -- if a friendly
+ // pawn/minor/rook already covers that square, the king can't
+ // just take the queen for free. Every one of those tables (ours
+ // AND the enemy's) is fully populated by now: pawns, minors,
+ // and rooks all run their own eval pass before queen does.
+ //
+ BITBOARD bbUnsafeForQueen = pos->bbPawnAttacks[xColor] |
+ pos->bbMinorAttacks[xColor] |
+ pos->bbRookAttacks[xColor] |
+ (g_KingAttacksBB[cKing] &
+ ~(pos->bbPawnAttacks[uColor] |
+ pos->bbMinorAttacks[uColor] |
+ pos->bbRookAttacks[uColor]));
BITBOARD bbAttackTotal = 0;
BITBOARD bbXrayAccum = 0;
BITBOARD bbMobility = 0;
@@ -4963,7 +5057,7 @@ Return value:
// pieces are trapped!
if (uTotalMobility == 0)
{
- _RecordTrappedCandidate(pos, uColor, c);
+ _RecordTrappedCandidate(pos, uColor, c, 0);
}
}
@@ -5494,6 +5588,7 @@ Return value:
{
BITBOARD bb[2];
ULONG uColor;
+ ULONG xColor;
int d1;
COOR cQueen;
COOR cKing, c;
@@ -5523,7 +5618,8 @@ Return value:
(pos->bbPawns[WHITE] != 0));
FOREACH_COLOR(uColor)
{
- if (TotalNonPawnCount(pos, FLIP(uColor)) == 1)
+ xColor = FLIP(uColor);
+ if (TotalNonPawnCount(pos, xColor) == 1)
{
d1 = 16 * g_iAhead[uColor];
ASSERT((d1 * 2) == (32 * g_iAhead[uColor]));
@@ -5534,10 +5630,10 @@ Return value:
{
// If the enemy king can take this passer and is
// on the move, all bets are off!
- cKing = KingPosition(pos, FLIP(uColor));
+ cKing = KingPosition(pos, xColor);
ASSERT(IS_KING(pos->rgSquare[cKing].pPiece));
ASSERT(GET_COLOR(pos->rgSquare[cKing].pPiece) != uColor);
- if (pos->uToMove == FLIP(uColor) &&
+ if (pos->uToMove == xColor &&
(DISTANCE(cKing, c) == 1))
{
// TODO: something clever here about protected pawns
@@ -5608,7 +5704,7 @@ Return value:
uKingDist = DISTANCE(cKing, cQueen);
if (uKingDist > 0)
{
- uKingDist -= (pos->uToMove == FLIP(uColor));
+ uKingDist -= (pos->uToMove == xColor);
}
ASSERT((uKingDist >= 0) && (uPawnDist <= 7));
@@ -5701,6 +5797,7 @@ Return value:
{
ULONG u;
ULONG uColor;
+ ULONG xColor;
COOR c;
COOR cSquare;
BITBOARD bb;
@@ -5714,6 +5811,7 @@ Return value:
FOREACH_COLOR(uColor)
{
+ xColor = FLIP(uColor);
bb = pHash->bbPasserLocations[uColor];
if (bb != 0)
{
@@ -5730,7 +5828,7 @@ Return value:
cSquare = c + d1;
ASSERT(IS_ON_BOARD(cSquare));
u = _WhoControlsSquareFast(pos, cSquare);
- if (u == FLIP(uColor))
+ if (u == xColor)
{
EVAL_TERM(uColor,
PAWN,
@@ -5772,16 +5870,16 @@ Return value:
//
// The enemy king is ahead of the passer
//
- i += FILE_DISTANCE(c, KingPosition(pos, FLIP(uColor))) * 4;
+ i += FILE_DISTANCE(c, KingPosition(pos, xColor)) * 4;
}
else
{
//
// The enemy king is behind the passer
//
- i += 10 + DISTANCE(c, KingPosition(pos, FLIP(uColor))) * 4;
+ i += 10 + DISTANCE(c, KingPosition(pos, xColor)) * 4;
}
- i *= PASSER_MATERIAL_UP_SCALER[pos->uArmyScaler[FLIP(uColor)]];
+ i *= PASSER_MATERIAL_UP_SCALER[pos->uArmyScaler[xColor]];
i /= 8;
EVAL_TERM(uColor,
PAWN,
@@ -5795,7 +5893,7 @@ Return value:
// are even more dangerous.
//
if (TotalNonPawnCount(pos, uColor) >
- TotalNonPawnCount(pos, FLIP(uColor)))
+ TotalNonPawnCount(pos, xColor))
{
i = (PASSER_BY_RANK[uColor][RANK(c)] / 2);
EVAL_TERM(uColor,
@@ -5976,32 +6074,120 @@ Return value:
COOR cBestOwnTrapped;
ULONG uBestOwnTrappedValue;
ULONG uColor;
+ ULONG xColor;
ULONG u;
+ ULONG uFinalMobility;
+ BITBOARD bbOwnDefend;
+ BITBOARD bbUnsafeNow;
+ BITBOARD bbStillSafe;
POSITION *pos = &ctx->sPosition;
PIECE p;
+ SCORE i;
FOREACH_COLOR(uColor)
{
+ xColor = FLIP(uColor);
cBestOwnTrapped = ILLEGAL_COOR;
uBestOwnTrappedValue = 0;
for (u = 0; u < pos->uNumTrapped[uColor]; u++)
{
c = pos->cTrapped[uColor][u];
ASSERT(IS_ON_BOARD(c));
- if (_WhoControlsSquareFast(pos, c) == FLIP(uColor))
- {
- // Unconditional even outside DEBUG builds: EVAL_TERM
- // below reads p unconditionally (its PIE argument
- // feeds EvalTrace() in EVAL_TRACE builds), so an
- // assignment gated behind #ifdef DEBUG alone left p
- // uninitialized on read in a non-DEBUG/EVAL_TRACE
- // build.
- p = pos->rgSquare[c].pPiece;
+ p = pos->rgSquare[c].pPiece;
#ifdef DEBUG
- ASSERT(p);
- ASSERT(!IS_PAWN(p));
- ASSERT(GET_COLOR(p) == uColor);
+ ASSERT(p);
+ ASSERT(!IS_PAWN(p));
+ ASSERT(GET_COLOR(p) == uColor);
#endif
+
+ // A nonzero bbTrappedEscapes snapshot means this candidate
+ // was genuinely deferred (a bishop/knight/rook nominated at
+ // low-but-nonzero partial-information mobility -- see
+ // EVAL_MOBILITY_CAREFULLY_BELOW's comment) and is still
+ // owed both its real mobility score (skipped at nomination
+ // time) and a final trapped/not-trapped verdict, now that
+ // every piece type -- including the enemy king -- has
+ // contributed to the attack tables. A zero snapshot means
+ // either a genuinely 0-mobility piece (already scored
+ // inline, nothing left to re-derive -- see _EvalBishop's
+ // comment on why those are never deferred) or a queen
+ // (never deferred at all -- its own eval pass already runs
+ // after every smaller piece type, so it doesn't have this
+ // partial-information problem in the first place; see
+ // _EvalQueen's bbUnsafeForQueen).
+ if (pos->bbTrappedEscapes[uColor][u] != 0)
+ {
+ // bbOwnDefend excludes the candidate's own piece-type
+ // bucket: bbMinorAttacks/bbRookAttacks already include
+ // this very piece's own attack squares (populated
+ // unconditionally before the nomination decision),
+ // which are exactly its candidate escape squares --
+ // crediting that back as "defense" would be circular (a
+ // piece can't defend the square it would occupy after
+ // moving off its own square). Conservative side effect:
+ // a genuinely different friendly minor/rook defending
+ // the same square as a trapped minor/rook is also
+ // excluded, since the merged bitboards can't
+ // distinguish "this piece" from "another piece of the
+ // same bucket" -- known limitation, not fixed here.
+ bbOwnDefend = pos->bbPawnAttacks[uColor] |
+ pos->bbQueenAttacks[uColor];
+ if (!IS_BISHOP(p) && !IS_KNIGHT(p))
+ {
+ bbOwnDefend |= pos->bbMinorAttacks[uColor];
+ }
+ if (!IS_ROOK(p))
+ {
+ bbOwnDefend |= pos->bbRookAttacks[uColor];
+ }
+
+ // The only thing nomination-time couldn't see: the
+ // enemy king's own attacks (pos->bbKingAttacks[xColor]
+ // -- both kings have finished evaluating well before
+ // _EvalTrappedPieces runs, see Eval()'s call order).
+ // Everything else a candidate square could be unsafe
+ // for (a cheaper enemy piece attacking it) was already
+ // known and excluded at nomination time -- see
+ // EVAL_MOBILITY_CAREFULLY_BELOW's comment.
+ bbUnsafeNow = pos->bbKingAttacks[xColor] & ~bbOwnDefend;
+ bbStillSafe = pos->bbTrappedEscapes[uColor][u] & ~bbUnsafeNow;
+ uFinalMobility = CountBits(bbStillSafe);
+
+ if (IS_BISHOP(p))
+ {
+ EVAL_TERM(uColor, BISHOP, c, pos->iScore[uColor],
+ BISHOP_MOBILITY_BY_SQUARES[uFinalMobility],
+ "bishop mobility (deferred)");
+ }
+ else if (IS_KNIGHT(p))
+ {
+ EVAL_TERM(uColor, KNIGHT, c, pos->iScore[uColor],
+ KNIGHT_MOBILITY_BY_COUNT[uFinalMobility],
+ "knight mobility (deferred)");
+ }
+ else if (IS_ROOK(p))
+ {
+ ASSERT(pos->uArmyScaler[xColor] <= 31);
+ i = ROOK_MOBILITY_BY_SQUARES[uFinalMobility];
+ i *= (int)(REDUCED_MATERIAL_UP_SCALER[pos->uArmyScaler[xColor]] + 1);
+ i /= 8;
+ EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], i,
+ "rook mobility (deferred)");
+ }
+
+ if (bbStillSafe != 0)
+ {
+ // A real escape exists once full attack information
+ // is in -- the loose, partial-information nomination
+ // was a false alarm, not actually trapped. Its
+ // (corrected, lower) mobility score was already
+ // banked above; nothing more to do for it.
+ continue;
+ }
+ }
+
+ if (_WhoControlsSquareFast(pos, c) == xColor)
+ {
// See if the side who created the trap and controls
// the square the trapped piece is sitting on has the
// move too. If so, uColor moved at ply-1.