diff options
| author | Scott Gasch <[email protected]> | 2026-09-22 13:04:39 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-09-22 13:04:39 -0700 |
| commit | b7e4cdfeb3c1aadb3f488f6794e6d1a748dc6c8f (patch) | |
| tree | ffc5bb4e2a4a4d9d027df38c9619739a3dbb478e /src | |
| parent | f422daab223103bbd62a5611be0b73eaa6a5dc05 (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')
| -rwxr-xr-x | src/chess.h | 37 | ||||
| -rw-r--r-- | src/eval.c | 430 |
2 files changed, 335 insertions, 132 deletions
diff --git a/src/chess.h b/src/chess.h index 1dcb517..8d9a8d8 100755 --- a/src/chess.h +++ b/src/chess.h @@ -749,16 +749,33 @@ typedef struct _POSITION // temporary storage space for use in eval // - // Small fixed-capacity list of zero-mobility piece candidates per - // color (bishop/knight/rook/queen evals each can push one), not - // just the single most-recently-evaluated one -- a single COOR - // here used to silently let a later piece's candidacy overwrite an - // earlier one on the same side, discarding it even if it was a - // genuinely trapped (zero mobility + actually attacked) piece. - // Capacity of 4 is deliberately generous; if a position somehow - // has more than 4 simultaneously zero-mobility non-pawn pieces on - // one side, the rest are just dropped, not a real concern. - COOR cTrapped[2][4]; + // Small fixed-capacity list of zero-mobility (or, for a deferred + // bishop/knight/rook -- see EVAL_MOBILITY_CAREFULLY_BELOW in + // eval.c -- low-mobility) piece candidates per color, not just the + // single most-recently-evaluated one -- a single COOR here used to + // silently let a later piece's candidacy overwrite an earlier one + // on the same side, discarding it even if it was a genuinely + // trapped (zero mobility + actually attacked) piece. Capacity of 6 + // is deliberately generous; if a position somehow has more than 6 + // simultaneously trapped-candidate non-pawn pieces on one side, the + // rest are just dropped, not a real concern. + // + // bbTrappedEscapes[side][u] is the nominating bishop/knight/rook's + // own bbMobility snapshot at nomination time, for a genuinely + // deferred candidate (see eval.c's _EvalBishop/_EvalKnight/ + // _EvalRook) -- necessarily computed from incomplete attack-table + // information (the enemy king's, and for bishop/knight any + // not-yet-evaluated enemy slider's, attacks aren't known yet). Zero + // for every non-deferred candidate (queen, which never has this + // partial-information problem in the first place, or a + // bishop/knight/rook nominated at already-zero mobility -- nothing + // left to re-derive there, see eval.c's comment on why 0-mobility + // pieces are never deferred) -- _EvalTrappedPieces uses this being + // nonzero as the signal that a deferred re-check (and its own + // "mobility (deferred)" EVAL_TERM, skipped at nomination time) is + // owed to this candidate. + COOR cTrapped[2][6]; + BITBOARD bbTrappedEscapes[2][6]; ULONG uNumTrapped[2]; ULONG uArmyScaler[2]; ULONG uClosedScaler; @@ -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. |
