summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-08 18:15:36 -0700
committerScott Gasch <[email protected]>2026-09-08 18:15:36 -0700
commit85b71cf793a85074d8d4483b21a2a06c84553d1d (patch)
tree767e5bb8b03976b80e578913a3fbd009571be40f
parentfbb138cc1dd13da2f30129206cdcd3d128344f54 (diff)
eval.c: bad-trades fix, rook cache, xColor speedups, draw scaling, main-body reorder
Continues re-applying the eval.c overhaul from stash@{0} (see fbb138c), each piece verified individually against clean 434fa04 (fast st1 matches, ~30-40 games each) before landing: - _EvalBadTrades: replaced the TRADE_PIECES/DONT_TRADE_PAWNS lookup tables with direct arithmetic (part of a broader "eval.c is too slow, cut lookup tables where possible" effort). The stash's first attempt at this had a real bug: uInverseBehindPieceCount was computed by subtracting a raw material sum from 9 in unsigned arithmetic, which underflows to ~4 billion in any non-bare-endgame position with unequal material -- confirmed via match_play (score 0.15-0.225 over 20 games vs clean 434fa04, a near-total wipeout). Fixed to use the existing uNonPawnCount piece-count field directly instead of reinventing it via material math, and restored a cheap zero-pawn guard (old DONT_TRADE_PAWNS punished "material lead with zero pawns left" specifically -- the classic KNB-vs-KRP false positive -- which the arithmetic replacement had dropped entirely). Verified back at parity (0.475/40 games) after the fix. - _EvalRook: ROOK_FULL_HALF_OPEN_BONUS is now a startup-computed cache (InitEval(), refreshed on every DNA reload) instead of a per-call local array rebuild. - xColor = FLIP(uColor) cached once instead of recomputed inline: _EvalBishop, _EvalKnight, CountKingSafetyDefects (also renamed its uSide/xSide params to uColor/xColor to match), and Eval()'s own per-piece-type loop. - _EvalBishopPairs collapsed to one FOREACH_COLOR loop instead of duplicated per-side code. - eval.c "diet" cleanup: removed pos->iTempScore (a scalar handoff between _EvalKing and Eval()'s per-color copy, now redundant -- _EvalKing writes directly into ctx->sPlyInfo[ply].iKingScore[uColor]); removed several stale comments documenting already-historical removals; _EvalPassers renamed to _ReEvalPassers; re-enabled a previously-disabled ASSERT in _EvaluateCandidatePasser (confirmed live via DEBUG smoke test, does not fire under current DNA). - Added _SideHasWinningChances (Crafty-style material-only "can this side force a win at all" classifier) plus the fifty-move-rule dampening in Eval() -- both scale the score toward g_iDrawScore when material alone rules out real winning chances, without touching Eval()'s signature (kept the existing scalar piPositional convention rather than pulling in the super-lazy exit's per-color rework). - Removed the "B over N in the endgame with 2 pawn wings" term entirely, by direct instruction -- BISHOP_OVER_KNIGHT_IN_ENDGAME is now an orphaned DNA constant (matches the stash's own choice, which left it similarly unused). - Eval()'s main per-piece-type loop reordered from "all of our minors, then all of the enemy's minors, then rooks both colors, then queens both colors" to "ours then enemy's, interleaved per type" (knights, then bishops, then rooks, then queens). Verified the real dependency this ordering has to preserve -- _EvalRook/_EvalQueen read the enemy's bbMinorAttacks, _EvalKing reads both colors' bbMinorAttacks/ bbQueenAttacks, _ReEvalPassers needs the king scores -- and confirmed the new interleaving still satisfies it (all minors both colors done before any rook/queen; both colors done before king). Verification methodology throughout: gmake clean release build, DEBUG smoke test (10 random ECM positions at sd 5, asserts compiled in), then a fast (st 1, 40-game) match_play.py run against a clean 434fa04 reference binary before landing each piece. A same-binary-vs-itself control match (score 0.500/20 games) confirms the harness itself has no structural bias, so per-checkpoint scores in the 0.44-0.58 band across this session reflect real (if noisy) parity, not measurement artifacts. Still not re-applied (remains in stash@{0}): the super-lazy exit, the material-based normal-lazy floor, and search.c's qsearch futility rework -- these three are one coupled unit (the futility margins index by which Eval() exit tier fired) and go in as the next, more carefully scrutinized step, since the original regression that started this whole investigation traced to exactly this area. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Ka9o3S2eKqh4jNfmxVZ6fH
-rwxr-xr-xsrc/chess.h1
-rwxr-xr-xsrc/eval.c611
2 files changed, 326 insertions, 286 deletions
diff --git a/src/chess.h b/src/chess.h
index 7d01e64..702defb 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -729,7 +729,6 @@ typedef struct _POSITION
ULONG uClosedScaler;
SCORE iScore[2];
SCORE iReducedMaterialDownScaler[2];
- SCORE iTempScore;
ULONG uMinorsAtHome[2];
BITBOARD bb;
}
diff --git a/src/eval.c b/src/eval.c
index 06e6bc7..bc5f6f4 100755
--- a/src/eval.c
+++ b/src/eval.c
@@ -96,19 +96,19 @@ const int g_iBehind[2] = { -1, +1 };
// General eval terms
// ---------------------------------------------------------------------------
//
-static SCORE TRADE_PIECES[3][17] =
-{// 0 1 2 3 4 5 6 7 8 | 9..15 -- down piece count
- { -1, 90, 81, 73, 67, 62, 57, 52, 50, 50,50,50,50,50,50,50,-1},
- { -1, 125, 117, 109, 100, 91, 84, 78, 71, 66,66,66,66,66,66,66,-1},
- { -1, 150, 132, 124, 116, 109, 102, 94, 87, 77,77,77,77,77,77,77,-1},
-};
+//static SCORE TRADE_PIECES[3][17] =
+//{// 0 1 2 3 4 5 6 7 8 | 9..15 -- down piece count
+// { -1, 90, 81, 73, 67, 62, 57, 52, 50, 50,50,50,50,50,50,50,-1},
+// { -1, 125, 117, 109, 100, 91, 84, 78, 71, 66,66,66,66,66,66,66,-1},
+// { -1, 150, 132, 124, 116, 109, 102, 94, 87, 77,77,77,77,77,77,77,-1},
+//};
-static SCORE DONT_TRADE_PAWNS[3][9] =
-{// 0 1 2 3 4 5 6 7 8 -- up pawn count
- { -43, -15, 0, +3, +6, +10, +15, +21, +28 }, // 0
- { -63, -25, 0, +4, +9, +14, +19, +25, +32 }, // 1
- { -10, 0, 0, +7, +15, +20, +24, +29, +36 }, // 2
-};
+//static SCORE DONT_TRADE_PAWNS[3][9] =
+//{// 0 1 2 3 4 5 6 7 8 -- up pawn count
+// { -43, -15, 0, +3, +6, +10, +15, +21, +28 }, // // 0
+// { -63, -25, 0, +4, +9, +14, +19, +25, +32 }, // // 1
+// { -10, 0, 0, +7, +15, +20, +24, +29, +36 }, // // 2
+//};
static ULONG REDUCED_MATERIAL_DOWN_SCALER[32] =
{
@@ -531,6 +531,14 @@ static SCORE KNIGHT_IN_CLOSED_POSITION[33] =
static SCORE ROOK_ON_FULL_OPEN = 16;
static SCORE ROOK_ON_HALF_OPEN_WITH_ENEMY = 9;
static SCORE ROOK_ON_HALF_OPEN_WITH_FRIEND = 10;
+// Cache of the three constants above, indexed [friend pawn on file?]
+// [enemy pawn on file?] so _EvalRook's hot path is a single array read
+// instead of rebuilding a local array every call. Not `static const`
+// -- the three source constants are DNA-tunable globals (plain mutable
+// SCORE, not compile-time constants), so this has to be a plain
+// mutable array, rebuilt by InitEval() whenever they change (startup,
+// and after every `evaldna read`), not frozen at compile time.
+static SCORE ROOK_FULL_HALF_OPEN_BONUS[2][2] = { { 16, 9 }, { 10, 0 } };
// Same treatment for the enemy/friend-passer-on-file bonuses -- was
// PASSER_BY_RANK[xColor][rank]/4 (enemy case) and a rank-scaled
@@ -729,8 +737,6 @@ typedef struct _DNA_BASE_SIZE {
#define DNA_ARRAY(x) {(SCORE *)(x), ARRAY_LENGTH(x)}
#define DNA_MATRIX(x) {(SCORE *)(x), ARRAY_LENGTH(x) * ARRAY_LENGTH((x)[0])}
static DNA_BASE_SIZE g_EvalDNA[] = {
- DNA_MATRIX(TRADE_PIECES),
- DNA_MATRIX(DONT_TRADE_PAWNS),
DNA_ARRAY(REDUCED_MATERIAL_DOWN_SCALER),
DNA_ARRAY(CASTLE_AND_DEVELOPMENT_SCALER),
DNA_ARRAY(REDUCED_MATERIAL_UP_SCALER),
@@ -1163,7 +1169,6 @@ Return value:
ASSERT((uBlack & 0xFFFFFF00) == 0);
p = pos->rgSquare[c].pPiece;
- // p -= 2;
ch = g_SwapTable[p][uWhite][uBlack];
if (ch != 0)
{
@@ -1523,8 +1528,7 @@ Return value:
(((WHITE == uColor) && (RANK(c) > 5)) ||
((BLACK == uColor) && (RANK(c) < 4)))))
{
- // Tuning can (and has) flipped this sign; not a real invariant.
- //ASSERT(CANDIDATE_PASSER_BY_RANK[uColor][RANK(c)] > 0);
+ ASSERT(CANDIDATE_PASSER_BY_RANK[uColor][RANK(c)] > 0);
EVAL_TERM(uColor,
PAWN,
c,
@@ -1760,7 +1764,6 @@ Return value:
pos->bbPawnAttacks[BLACK] =
((pos->bbPawns[BLACK] & ~BBFILE[0]) << 7) |
((pos->bbPawns[BLACK] & ~BBFILE[7]) << 9);
-
pos->bbPawnAttacks[WHITE] =
((pos->bbPawns[WHITE] & ~BBFILE[0]) >> 9) |
((pos->bbPawns[WHITE] & ~BBFILE[7]) >> 7);
@@ -2240,18 +2243,18 @@ Return value:
ULONG
CountKingSafetyDefects(IN OUT POSITION *pos,
- IN ULONG uSide)
+ IN ULONG uColor)
/**
Routine description:
- Determine how many defects uSide's king position has _quickly_.
+ Determine how many defects uColor's king position has _quickly_.
TODO: add more knowledge as cheaply as possible...
Parameters:
POSITION *pos,
- ULONG uSide
+ ULONG uColor
Return value:
@@ -2260,7 +2263,7 @@ Return value:
**/
{
ULONG uCounter = 0;
- ULONG xSide = FLIP(uSide);
+ ULONG xColor = FLIP(uColor);
COOR cKing;
BITBOARD bbKingZone;
BITBOARD bbBlockers;
@@ -2271,16 +2274,16 @@ Return value:
// Don't count king safety defects if the real eval code in
// _EvalKing would not...
//
- if (pos->uNonPawnMaterial[xSide] < DO_KING_SAFETY_THRESHOLD) {
+ if (pos->uNonPawnMaterial[xColor] < DO_KING_SAFETY_THRESHOLD) {
return 0;
}
- cKing = pos->cNonPawns[uSide][0];
- uCounter = KING_INITIAL_COUNTER_BY_LOCATION[uSide][cKing] >> 1;
+ cKing = pos->cNonPawns[uColor][0];
+ uCounter = KING_INITIAL_COUNTER_BY_LOCATION[uColor][cKing] >> 1;
ASSERT(IS_KING(pos->rgSquare[cKing].pPiece));
ASSERT(pos->rgSquare[cKing].uIndex == 0);
ASSERT(IS_ON_BOARD(cKing));
- ASSERT(GET_COLOR(pos->rgSquare[cKing].pPiece) == uSide);
+ ASSERT(GET_COLOR(pos->rgSquare[cKing].pPiece) == uColor);
//
// board_representation/EVAL.md section 9 follow-up (2026-09-05):
@@ -2307,9 +2310,9 @@ Return value:
// lazy-eval check across the whole search tree, for what's already
// a rough magnitude estimate, not an exact score) -- but the
// occupancy fed to the slider lookups below is deliberately just
- // xSide's own pieces (_BuildFriendlySideBB(pos, xSide)), not the
- // whole board: a piece belonging to uSide (a defending pawn, the
- // king itself) is transparent to these rays, only xSide's own
+ // xColor's own pieces (_BuildFriendlySideBB(pos, xColor)), not the
+ // whole board: a piece belonging to uColor (a defending pawn, the
+ // king itself) is transparent to these rays, only xColor's own
// pieces actually block. This gets latent-threat detection (a
// rook aimed at the king zone but currently shielded by the
// king's own pawn still counts) for free, at zero extra cost --
@@ -2318,21 +2321,21 @@ Return value:
// own pieces as blocking the enemy king.
//
bbKingZone = g_KingAttacksBB[cKing] | COOR_TO_BB(cKing);
- bbBlockers = _BuildFriendlySideBB(pos, xSide);
+ bbBlockers = _BuildFriendlySideBB(pos, xColor);
- bb = pos->bbPieces[xSide][KNIGHT];
+ bb = pos->bbPieces[xColor][KNIGHT];
while (bb)
{
c = CoorFromBitBoardRank8ToRank1(&bb);
uCounter += (g_KnightAttacksBB[c] & bbKingZone) != 0;
}
- bb = pos->bbPieces[xSide][BISHOP];
+ bb = pos->bbPieces[xColor][BISHOP];
while (bb)
{
c = CoorFromBitBoardRank8ToRank1(&bb);
uCounter += (_BishopAttacksBB(c, bbBlockers) & bbKingZone) != 0;
}
- bb = pos->bbPieces[xSide][ROOK];
+ bb = pos->bbPieces[xColor][ROOK];
while (bb)
{
c = CoorFromBitBoardRank8ToRank1(&bb);
@@ -2345,7 +2348,7 @@ Return value:
// (a queen hitting the zone counted exactly the same as a lone
// knight doing so). Raises the bound below accordingly (up to 8
// queens are legal via promotion, each now worth 2).
- bb = pos->bbPieces[xSide][QUEEN];
+ bb = pos->bbPieces[xColor][QUEEN];
while (bb)
{
c = CoorFromBitBoardRank8ToRank1(&bb);
@@ -3102,6 +3105,7 @@ Return value:
{ C1, F1 } // WHITE
};
ULONG uColor;
+ ULONG xColor;
BITBOARD bb;
BITBOARD bbMask;
BITBOARD bbPc;
@@ -3115,6 +3119,7 @@ Return value:
p = pos->rgSquare[c].pPiece;
ASSERT(p && IS_BISHOP(p));
uColor = GET_COLOR(p);
+ xColor = FLIP(uColor);
// Undeveloped minor piece; maybe penalized later in EvalKing.
pos->uMinorsAtHome[uColor] += (c == cBishopAtHome[uColor][0]);
@@ -3156,17 +3161,17 @@ Return value:
i += TRANSIENT_PAWN_ON_BISHOP_COLOR[cSquare];
}
- bb = pos->bbPawns[FLIP(uColor)] & bbPc;
+ 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) == FLIP(uColor));
- ASSERT(pos->bbPawns[FLIP(uColor)] & COOR_TO_BB(cSquare));
+ ASSERT(GET_COLOR(pos->rgSquare[cSquare].pPiece) == xColor);
+ ASSERT(pos->bbPawns[xColor] & COOR_TO_BB(cSquare));
i +=
- ((pos->bbPawnAttacks[FLIP(uColor)] & COOR_TO_BB(cSquare)) != 0) *
+ ((pos->bbPawnAttacks[xColor] & COOR_TO_BB(cSquare)) != 0) *
TRANSIENT_PAWN_ON_BISHOP_COLOR[cSquare] / 2;
}
EVAL_TERM(uColor,
@@ -3220,14 +3225,14 @@ Return value:
pos->bb = bbPc;
{
BITBOARD bbAttack = _BishopAttacksBB(c, pos->bbOccupied);
- BITBOARD bbEnemySame = pos->bbPieces[FLIP(uColor)][BISHOP] |
- pos->bbPieces[FLIP(uColor)][KNIGHT];
- BITBOARD bbEnemyGEContinue = pos->bbPieces[FLIP(uColor)][ROOK] |
- pos->bbPieces[FLIP(uColor)][QUEEN] |
- COOR_TO_BB(pos->cNonPawns[FLIP(uColor)][0]);
+ BITBOARD bbEnemySame = pos->bbPieces[xColor][BISHOP] |
+ pos->bbPieces[xColor][KNIGHT];
+ BITBOARD bbEnemyGEContinue = pos->bbPieces[xColor][ROOK] |
+ pos->bbPieces[xColor][QUEEN] |
+ COOR_TO_BB(pos->cNonPawns[xColor][0]);
BITBOARD bbFriendBQ = pos->bbPieces[uColor][BISHOP] |
pos->bbPieces[uColor][QUEEN];
- BITBOARD bbUnsafeForMinor = pos->bbPawnAttacks[FLIP(uColor)];
+ BITBOARD bbUnsafeForMinor = pos->bbPawnAttacks[xColor];
BITBOARD bbExclude = 0;
BITBOARD bbSeen = 0;
BITBOARD bbLayer = bbAttack;
@@ -3262,7 +3267,7 @@ Return value:
BITBOARD bbEnemyGEHere = bbBlockersHere & bbEnemyGEContinue;
BITBOARD bbFriendPawnHere = bbBlockersHere & pos->bbPawns[uColor];
BITBOARD bbEnemyPawnHere = bbBlockersHere &
- pos->bbPawns[FLIP(uColor)];
+ pos->bbPawns[xColor];
BITBOARD bbTransientHere = bbFriendPawnHere & bbPc;
BITBOARD bbContinueHere;
@@ -3310,8 +3315,8 @@ Return value:
// by a friendly one -- near the enemy king. This is a
// bishop-specific king-tropism/outpost bonus.
//
- bb = pos->bbPawns[FLIP(uColor)] &
- (~pHash->bbStationaryPawns[FLIP(uColor)]);
+ bb = pos->bbPawns[xColor] &
+ (~pHash->bbStationaryPawns[xColor]);
if (TRUE == _IsSquareSafeFromEnemyPawn(pos, c, bb))
{
// Defended (and defending) a friendly pawn.
@@ -3324,7 +3329,7 @@ Return value:
((IS_ON_BOARD(c + 15 * g_iBehind[uColor])) &&
(pos->rgSquare[c + 15 * g_iBehind[uColor]].pPiece==p))));
#endif
- u = DISTANCE(c, pos->cNonPawns[FLIP(uColor)][0]);
+ u = DISTANCE(c, pos->cNonPawns[xColor][0]);
i = BISHOP_UNASSAILABLE_BY_DIST_FROM_EKING[u];
EVAL_TERM(uColor,
BISHOP,
@@ -3366,6 +3371,7 @@ Return value:
COOR cSquare;
BITBOARD bb;
ULONG uColor;
+ ULONG xColor;
ULONG uPawnsSupporting;
ULONG uMobilitySquares;
SCORE i;
@@ -3375,6 +3381,7 @@ Return value:
ASSERT(p && IS_KNIGHT(p));
uColor = GET_COLOR(p);
ASSERT(IS_VALID_COLOR(uColor));
+ xColor = FLIP(uColor);
// Unmoved piece; potentially penalized in EvalKing.
pos->uMinorsAtHome[uColor] += (c == cKnightAtHome[uColor][0]);
@@ -3402,10 +3409,10 @@ Return value:
// if so give bonus for outposted knight which increases the
// closer it is to the enemy king and the more pawns it has
// supporting it.
- uDist = DISTANCE(c, pos->cNonPawns[FLIP(uColor)][0]);
+ uDist = DISTANCE(c, pos->cNonPawns[xColor][0]);
ASSERT((uDist > 0) && (uDist <= 8));
- bb = pos->bbPawns[FLIP(uColor)] &
- (~pHash->bbStationaryPawns[FLIP(uColor)]);
+ bb = pos->bbPawns[xColor] &
+ (~pHash->bbStationaryPawns[xColor]);
if (TRUE == _IsSquareSafeFromEnemyPawn(pos, c, bb))
{
// Count the number of supporting pawns the knight has
@@ -3435,7 +3442,7 @@ Return value:
{
if (IS_SQUARE_WHITE(c))
{
- if (pos->uWhiteSqBishopCount[FLIP(uColor)])
+ if (pos->uWhiteSqBishopCount[xColor])
{
EVAL_TERM(uColor,
KNIGHT,
@@ -3447,8 +3454,8 @@ Return value:
}
else
{
- if (pos->uNonPawnCount[FLIP(uColor)][BISHOP] -
- pos->uWhiteSqBishopCount[FLIP(uColor)])
+ if (pos->uNonPawnCount[xColor][BISHOP] -
+ pos->uWhiteSqBishopCount[xColor])
{
EVAL_TERM(uColor,
KNIGHT,
@@ -3474,7 +3481,7 @@ Return value:
// Give a bonus for blockading an enemy backward pawn.
cSquare = c + 16 * g_iAhead[uColor];
- bb = pHash->bbStationaryPawns[FLIP(uColor)];
+ bb = pHash->bbStationaryPawns[xColor];
if (bb & COOR_TO_BB(cSquare))
{
ASSERT(pos->rgSquare[cSquare].pPiece);
@@ -3515,9 +3522,9 @@ Return value:
{
BITBOARD bbAttack = g_KnightAttacksBB[c];
BITBOARD bbFriendOcc = _BuildFriendlySideBB(pos, uColor);
- BITBOARD bbEnemyNonPawnOcc = _BuildFriendlySideBB(pos, FLIP(uColor)) &
- ~pos->bbPawns[FLIP(uColor)];
- BITBOARD bbUnsafeForMinor = pos->bbPawnAttacks[FLIP(uColor)];
+ BITBOARD bbEnemyNonPawnOcc = _BuildFriendlySideBB(pos, xColor) &
+ ~pos->bbPawns[xColor];
+ BITBOARD bbUnsafeForMinor = pos->bbPawnAttacks[xColor];
BITBOARD bbMobility;
pos->bbMinorAttacks[uColor] |= bbAttack;
@@ -3565,7 +3572,6 @@ Return value:
ULONG uColor;
ULONG uTotalMobility;
COOR cSquare;
- BITBOARD bb;
SCORE i;
ASSERT(IS_ON_BOARD(c));
@@ -3573,38 +3579,27 @@ Return value:
ASSERT(p && IS_ROOK(p));
uColor = GET_COLOR(p);
ASSERT(IS_VALID_COLOR(uColor));
- {
- ULONG xColor = FLIP(uColor);
- BITBOARD bbFile = BBFILE[FILE(c)];
- BITBOARD bbFriendPawns = pos->bbPawns[uColor] & bbFile;
- BITBOARD bbEnemyPawns = pos->bbPawns[xColor] & bbFile;
- // Not `static` -- ROOK_ON_FULL_OPEN et al are DNA-tunable
- // globals (plain mutable SCORE, not compile-time constants), so
- // a static initializer would freeze in whatever value happened
- // to be compiled in and never see a later `evaldna read`. A
- // plain local array is rebuilt from the live values every call
- // (cheap: 4 loads, no branches) and stays correct under tuning.
- SCORE ROOK_FULL_HALF_OPEN_BONUS[2][2] =
- {
- { ROOK_ON_FULL_OPEN, ROOK_ON_HALF_OPEN_WITH_ENEMY },
- { ROOK_ON_HALF_OPEN_WITH_FRIEND, 0 },
- };
- // Collapsed 2026-09-06: friend-passer bonus used to distinguish
- // rook-behind-passer (good, rank-scaled +1..+25) from rook-in-
- // front-of-passer (bad, -3..-22) -- now a single flat bonus
- // regardless of which side of the pawn the rook is on, by
- // direct instruction.
- SCORE bonus = ROOK_FULL_HALF_OPEN_BONUS[bbFriendPawns != 0]
- [bbEnemyPawns != 0];
- bonus += (SCORE)((bbEnemyPawns & pHash->bbPasserLocations[xColor]) != 0) *
- ROOK_WITH_ENEMY_PASSER;
- bonus += (SCORE)((bbFriendPawns & pHash->bbPasserLocations[uColor]) != 0) *
- ROOK_WITH_FRIEND_PASSER;
+ ULONG xColor = FLIP(uColor);
+ ASSERT(IS_VALID_COLOR(xColor));
- EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], bonus,
- "file/passer bonus");
- }
+ BITBOARD bbFile = BBFILE[FILE(c)];
+ BITBOARD bbFriendPawns = pos->bbPawns[uColor] & bbFile;
+ BITBOARD bbEnemyPawns = pos->bbPawns[xColor] & bbFile;
+ // Collapsed 2026-09-06: friend-passer bonus used to distinguish
+ // rook-behind-passer (good, rank-scaled +1..+25) from rook-in-
+ // front-of-passer (bad, -3..-22) -- now a single flat bonus
+ // regardless of which side of the pawn the rook is on, by
+ // direct instruction.
+ SCORE bonus = ROOK_FULL_HALF_OPEN_BONUS[bbFriendPawns != 0][bbEnemyPawns != 0];
+
+ bonus += (SCORE)((bbEnemyPawns & pHash->bbPasserLocations[xColor]) != 0) *
+ ROOK_WITH_ENEMY_PASSER;
+ bonus += (SCORE)((bbFriendPawns & pHash->bbPasserLocations[uColor]) != 0) *
+ ROOK_WITH_FRIEND_PASSER;
+
+ EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], bonus,
+ "file/passer bonus");
//
@@ -3958,19 +3953,6 @@ Return value:
{
_RecordTrappedCandidate(pos, uColor, c);
}
-
- //
- // Removed 2026-08-30 ("pointing near enemy K" /
- // QUEEN_ATTACKS_SQ_NEXT_TO_KING): counted how many king-adjacent
- // squares the queen's own mobility ray-cast just attacked, using
- // the same pos->rgSquare[...].bvAttacks bits _EvalKing's real
- // danger computation (not the lazy-eval estimate) reads directly a
- // few squares away in the same file. King safety already derives
- // this more comprehensively -- across every attacking piece type,
- // properly weighted -- from the identical attack-table data
- // mobility just paid to populate. This was a narrower, redundant
- // re-derivation of a subset of that same signal.
- //
}
@@ -4073,15 +4055,12 @@ Return value:
ASSERT(IS_VALID_COLOR(uColor));
pos->bbKingAttacks[uColor] |= g_KingAttacksBB[c];
+ // Don't bother with king safety when the other side has no mating
+ // material.
u = pos->uNonPawnMaterial[xColor];
ASSERT(u >= VALUE_KING);
if (u < DO_KING_SAFETY_THRESHOLD)
{
- // board_representation/EVAL.md section 9: g_KingAttacksBB[c]
- // (generate.c's precomputed table, already used by move
- // generation) is exactly the old g_iQKDeltas walk's
- // destination set, IS_ON_BOARD baked in at table-build time --
- // one OR instead of an 8-iteration loop.
goto skip_safety;
}
@@ -4093,7 +4072,6 @@ Return value:
#endif
BITBOARD bbEnemyOcc = _BuildFriendlySideBB(pos, xColor);
-
uFlightSquares = 0;
u = 0;
ASSERT(KingSafetyDeltas[u] != 0);
@@ -4206,12 +4184,6 @@ Return value:
uCounter);
#endif
- // No shift needed (2026-09-06): PAWN_BIT/MINOR_BIT/ROOK_BIT/
- // QUEEN_BIT now sit directly in bits 1-4, and bvPattern never gets
- // KING_BIT (bit 0) set (bvAttack/bvXray deliberately exclude it --
- // see this function's header comment), so bvPattern already lands
- // in [0, 30] here, matching KING_COUNTER_BY_ATTACK_PATTERN's range
- // directly.
ASSERT(bvPattern >= 0);
ASSERT(bvPattern < 32);
v = KING_COUNTER_BY_ATTACK_PATTERN[bvPattern];
@@ -4227,10 +4199,8 @@ Return value:
Trace("%s KS Counter post-flight sq: %u\n", COLOR_NAME(uColor), uCounter);
#endif
- //
// Note: can't use pos->iReducedMaterialDownScaler here because we
// are scaling it based on the _other_ side's material.
- //
uCounter = MINU(uCounter, 41);
i = KING_SAFETY_BY_COUNTER[uCounter];
#ifdef CALIBRATE_POSITIONAL
@@ -4257,10 +4227,8 @@ Return value:
KING_QUEEN_PROXIMITY_DANGER[MINU(uQueenNearKing, 6)],
"queen proximity danger");
- //
// Bonus for castling / penalty for loss of castle. Also, if side has
// not yet castled, be concerned with undeveloped minor pieces.
- //
if (FALSE == pos->fCastled[uColor])
{
ULONG uDevelopmentScaler = CASTLE_AND_DEVELOPMENT_SCALER[pos->uArmyScaler[uColor]];
@@ -4381,7 +4349,7 @@ Return value:
}
}
pos->iScore[uColor] += iKingScore;
- pos->iTempScore = iKingScore;
+ ctx->sPlyInfo[ctx->uPly].iKingScore[uColor] = iKingScore;
}
@@ -4539,7 +4507,7 @@ Return value:
static void
-_EvalPassers(IN OUT POSITION *pos,
+_ReEvalPassers(IN OUT POSITION *pos,
IN PAWN_HASH_ENTRY *pHash)
/**
@@ -4708,15 +4676,9 @@ Return value:
**/
{
- ULONG uAhead, uBehind;
- ULONG uMagnitude;
-
- //
- // See who is ahead
- //
ASSERT(pos->uNonPawnMaterial[WHITE] != pos->uNonPawnMaterial[BLACK]);
- uAhead = (pos->uNonPawnMaterial[WHITE] > pos->uNonPawnMaterial[BLACK]);
- uBehind = FLIP(uAhead);
+ ULONG uAhead = (pos->uNonPawnMaterial[WHITE] > pos->uNonPawnMaterial[BLACK]);
+ ULONG uBehind = FLIP(uAhead);
#ifdef DEBUG
if (pos->uNonPawnMaterial[WHITE] > pos->uNonPawnMaterial[BLACK])
{
@@ -4730,15 +4692,6 @@ Return value:
ASSERT(uBehind == WHITE);
}
#endif
- uMagnitude = ((pos->uNonPawnMaterial[uAhead] +
- pos->uNonPawnCount[uAhead][0] * 128) -
- (pos->uNonPawnMaterial[uBehind] +
- pos->uNonPawnCount[uBehind][0] * 128));
- uMagnitude /= 128;
- uMagnitude -= (uMagnitude != 0);
- ASSERT(!(uMagnitude & 0x80000000));
- uMagnitude = MINU(2, uMagnitude);
- ASSERT(uMagnitude <= 2);
//
// Encourage the side that is ahead in piece material to continue
@@ -4750,19 +4703,26 @@ Return value:
// engine will not like positions like KNB vs KRP - the side with
// the pawn has the winning chances.
//
+ ULONG uAheadPawnCount = pos->uPawnCount[uAhead];
+ ULONG uInverseBehindPieceCount = 9 - MINU(9, pos->uNonPawnCount[uBehind][0]);
EVAL_TERM(uAhead,
0,
ILLEGAL_COOR,
pos->iScore[uAhead],
- ((pos->uPawnCount[uAhead] != 0) *
- TRADE_PIECES[uMagnitude][pos->uNonPawnCount[uBehind][0]]),
+ (uInverseBehindPieceCount * 16) * (uAheadPawnCount != 0),
"trade pieces");
- ASSERT(TRADE_PIECES[uMagnitude][pos->uNonPawnCount[uBehind][0]] > 0);
+ // Zero pawns left for the ahead side is the classic false-positive
+ // (KNB vs KRP, minor-up-no-pawns endings that are often drawn or
+ // even lost) -- the old DONT_TRADE_PAWNS table existed specifically
+ // to punish it (-43/-10 depending on lead size) rather than just
+ // trailing off toward a small positive number. Keep that guard as
+ // a cheap branch rather than a lookup table; the positive slope
+ // for 1+ pawns doesn't need the same care.
EVAL_TERM(uAhead,
0,
ILLEGAL_COOR,
pos->iScore[uAhead],
- DONT_TRADE_PAWNS[uMagnitude][pos->uPawnCount[uAhead]],
+ (0 == uAheadPawnCount) ? -30 : (SCORE)(uAheadPawnCount * 2),
"don't trade pawns");
}
@@ -4857,9 +4817,8 @@ Return value:
ASSERT(IS_ON_BOARD(c));
if (_WhoControlsSquareFast(pos, c) == FLIP(uColor))
{
- p = pos->rgSquare[c].pPiece;
- (void)p;
#ifdef DEBUG
+ p = pos->rgSquare[c].pPiece;
ASSERT(p);
ASSERT(!IS_PAWN(p));
ASSERT(GET_COLOR(p) == uColor);
@@ -4931,43 +4890,142 @@ Return value:
--*/
{
- ULONG uBishopCount = pos->uNonPawnCount[BLACK][BISHOP];
- ASSERT(uBishopCount <= 10);
- ULONG uWhiteSqBishopCount = pos->uWhiteSqBishopCount[BLACK];
- ASSERT(uWhiteSqBishopCount <= 10);
- FLAG fPair = ((uBishopCount > 1) &
- (uWhiteSqBishopCount != 0) &
- (uWhiteSqBishopCount != uBishopCount));
- EVAL_TERM(BLACK,
- 0,
- ILLEGAL_COOR,
- pos->iScore[BLACK],
- fPair * BISHOP_PAIR_BONUS,
- "bishop pair");
-
- uBishopCount = pos->uNonPawnCount[WHITE][BISHOP];
- ASSERT(uBishopCount <= 10);
- uWhiteSqBishopCount = pos->uWhiteSqBishopCount[WHITE];
- ASSERT(uWhiteSqBishopCount <= 10);
- fPair = ((uBishopCount > 1) &
- (uWhiteSqBishopCount != 0) &
- (uWhiteSqBishopCount != uBishopCount));
- EVAL_TERM(WHITE,
- 0,
- ILLEGAL_COOR,
- pos->iScore[WHITE],
- fPair * BISHOP_PAIR_BONUS,
- "bishop pair");
+ ULONG uColor;
+ FOREACH_COLOR(uColor)
+ {
+ ULONG uBishopCount = pos->uNonPawnCount[uColor][BISHOP];
+ ASSERT(uBishopCount <= 10);
+ ULONG uWhiteSqBishopCount = pos->uWhiteSqBishopCount[uColor];
+ ASSERT(uWhiteSqBishopCount <= 10);
+ FLAG fPair = ((uBishopCount > 1) &
+ (uWhiteSqBishopCount != 0) &
+ (uWhiteSqBishopCount != uBishopCount));
+ EVAL_TERM(uColor,
+ 0,
+ ILLEGAL_COOR,
+ pos->iScore[uColor],
+ fPair * BISHOP_PAIR_BONUS,
+ "bishop pair");
+ }
}
+static FLAG
+_SideHasWinningChances(IN POSITION *pos,
+ IN ULONG uSide)
+/**
+
+Routine description:
+
+ Cheap, material-count-only classifier (the same idea as Crafty's
+ EvaluateWinningChances): can uSide possibly force a win from this
+ material alone, ignoring the actual position entirely? Used only
+ to soften (scale toward drawscore) an otherwise-misleading raw
+ material lead in Eval() below -- unlike recogn.c's interior-node
+ recognizers (RECOGN_EXACT/UPPER/LOWER), this is never a hard claim
+ fed into a search bound, so a wrong answer here just biases the
+ eval a little; it can't corrupt alpha-beta the way a wrong hard
+ recognizer bound can (see recogn.c's _RecognizeKNKP/_RecognizeKBNK
+ incident).
+
+ Deliberately conservative: only covers material shapes where "no,
+ this can't be forced" is basic, textbook chess knowledge
+ (insufficient mating material, up the exchange with nothing else
+ changed, bare knights vs a bare king), not trickier exceptions
+ (fortress draws, wrong-bishop-pawn-with-king-in-time geometry)
+ that need real board information -- those are recogn.c's
+ hard-recognizer territory (already exact for tiny material there)
+ or plain search's job, not this cheap pre-check's.
+
+Parameters:
+
+ POSITION *pos,
+ ULONG uSide
+
+Return value:
+
+ static FLAG : TRUE if uSide has any winning chances at all, FALSE
+ if this exact material can provably never be forced to a win
+ regardless of position.
+
+**/
+{
+ ULONG uEnemy = FLIP(uSide);
+ INT iMajorDiff;
+ INT iMinorDiff;
+
+ //
+ // A pawn always gives some winning chances (it can always try to
+ // queen with support) -- nothing below applies.
+ //
+ if (pos->uPawnCount[uSide] > 0)
+ {
+ return(TRUE);
+ }
+
+ //
+ // No pawns and the only piece besides the king is a single minor:
+ // never enough material to force mate (K+N or K+B vs anything is
+ // never a forced win on material alone).
+ //
+ if ((2 == pos->uNonPawnCount[uSide][0]) &&
+ (1 == (pos->uNonPawnCount[uSide][KNIGHT] +
+ pos->uNonPawnCount[uSide][BISHOP])))
+ {
+ return(FALSE);
+ }
+
+ //
+ // No pawns and up exactly the exchange (one extra rook/queen,
+ // balanced by one extra enemy minor elsewhere): not enough to
+ // force a win either -- e.g. KRB vs KR can be held by the
+ // defender with correct play.
+ //
+ iMajorDiff = (INT)(pos->uNonPawnCount[uSide][ROOK] +
+ 2 * pos->uNonPawnCount[uSide][QUEEN]) -
+ (INT)(pos->uNonPawnCount[uEnemy][ROOK] +
+ 2 * pos->uNonPawnCount[uEnemy][QUEEN]);
+ if ((1 == iMajorDiff) || (-1 == iMajorDiff))
+ {
+ iMinorDiff = (INT)(pos->uNonPawnCount[uEnemy][KNIGHT] +
+ pos->uNonPawnCount[uEnemy][BISHOP]) -
+ (INT)(pos->uNonPawnCount[uSide][KNIGHT] +
+ pos->uNonPawnCount[uSide][BISHOP]);
+ if (iMajorDiff == iMinorDiff)
+ {
+ return(FALSE);
+ }
+ }
+
+ //
+ // No pawns, exactly two bare knights (no bishops/rooks/queens) for
+ // uSide, and the enemy has nothing left at all: two knights can't
+ // force mate against a bare king. Two bishops CAN (excluded here
+ // by requiring BISHOP count == 0) and knight+bishop CAN too (also
+ // excluded, since that has one bishop, not zero) -- both are
+ // genuine, if sometimes technique-heavy, forced wins.
+ //
+ if ((2 == pos->uNonPawnCount[uSide][KNIGHT]) &&
+ (0 == pos->uNonPawnCount[uSide][BISHOP]) &&
+ (0 == pos->uNonPawnCount[uSide][ROOK]) &&
+ (0 == pos->uNonPawnCount[uSide][QUEEN]) &&
+ (1 == pos->uNonPawnCount[uEnemy][0]) &&
+ (0 == pos->uPawnCount[uEnemy]))
+ {
+ return(FALSE);
+ }
+
+ return(TRUE);
+}
+
void
InitEval(void)
{
- // No-op placeholder: nothing to initialize until the
- // ROOK_FULL_HALF_OPEN_BONUS static cache (from the eval.c overhaul,
- // not yet re-applied here -- see stash) exists.
+ ROOK_FULL_HALF_OPEN_BONUS[0][0] = ROOK_ON_FULL_OPEN;
+ ROOK_FULL_HALF_OPEN_BONUS[0][1] = ROOK_ON_HALF_OPEN_WITH_ENEMY;
+ ROOK_FULL_HALF_OPEN_BONUS[1][0] = ROOK_ON_HALF_OPEN_WITH_FRIEND;
+ ROOK_FULL_HALF_OPEN_BONUS[1][1] = 0;
}
@@ -5004,6 +5062,7 @@ Return value:
COOR c;
ULONG u;
ULONG uColor;
+ ULONG xColor;
BITBOARD bb;
FLAG fDeferred;
#ifdef EVAL_TIME
@@ -5359,19 +5418,26 @@ Return value:
// just "do the rook-bitboard walk after the minor-bitboard walks"
// -- nothing to defer.
//
- // Phase order preserved exactly as before (side-to-move's minors,
- // then the other side's minors, then rooks both colors, then
- // queens both colors) -- that ordering is load-bearing for
- // bvAttacks accumulation (each piece's mobility/danger depends on
- // attack bits already written by earlier-evaluated pieces this
- // same Eval() call). Knight-vs-bishop order *within* the same
- // color/phase, and encounter order within a single type's own
- // bitboard walk, were never meaningful before (cNonPawns' order
- // is arbitrary swap-with-last-on-removal, not stable) and stay
- // that way -- EVAL_TERM's plain score accumulation doesn't care.
+ // board_representation/EVAL.md section 9: the real dependency
+ // isn't "side to move first" -- it's "both colors' minors done
+ // before any rook or queen, both rooks done before any queen,
+ // everything above done before either king" (_EvalRook/_EvalQueen
+ // read the enemy's pos->bbMinorAttacks; _EvalKing reads both
+ // colors' bbMinorAttacks/bbQueenAttacks; _ReEvalPassers further
+ // down needs the king scores). Interleaving ours-then-enemy within
+ // each type (rather than both colors' minors, then both colors'
+ // rooks, then both colors' queens as separate phases) still
+ // satisfies that same ordering -- knight-vs-bishop order *within*
+ // a color, and encounter order within a single type's own bitboard
+ // walk, were never meaningful (cNonPawns' order is arbitrary
+ // swap-with-last-on-removal, not stable) and stay that way --
+ // EVAL_TERM's plain score accumulation doesn't care.
//
pos->uMinorsAtHome[BLACK] = pos->uMinorsAtHome[WHITE] = 0;
uColor = pos->uToMove;
+ xColor = FLIP(uColor);
+
+ // Knights.
bb = pos->bbPieces[uColor][KNIGHT];
while (bb)
{
@@ -5381,37 +5447,44 @@ Return value:
TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight,
_EvalKnight(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After N:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After our N at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
- bb = pos->bbPieces[uColor][BISHOP];
+
+ // Enemy knights.
+ ASSERT(xColor != pos->uToMove);
+ bb = pos->bbPieces[xColor][KNIGHT];
while (bb)
{
u = FastFirstBit(bb) - 1;
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
- TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
- _EvalBishop(pos, c, pHash));
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight,
+ _EvalKnight(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After B:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After enemy N at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
- uColor = FLIP(uColor);
- ASSERT(uColor != pos->uToMove);
- bb = pos->bbPieces[uColor][KNIGHT];
+ // Bishops.
+ bb = pos->bbPieces[uColor][BISHOP];
while (bb)
{
u = FastFirstBit(bb) - 1;
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
- TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight,
- _EvalKnight(pos, c, pHash));
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
+ _EvalBishop(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After N:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After our B at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
- bb = pos->bbPieces[uColor][BISHOP];
+
+ // Enemy bishops.
+ bb = pos->bbPieces[xColor][BISHOP];
while (bb)
{
u = FastFirstBit(bb) - 1;
@@ -5420,14 +5493,12 @@ Return value:
TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
_EvalBishop(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After B:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After enemy B at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
- //
- // Evaluate any rook(s) for side on move then for side not on move.
- //
- uColor = FLIP(uColor);
+ // Rooks.
bb = pos->bbPieces[uColor][ROOK];
while (bb)
{
@@ -5436,12 +5507,13 @@ Return value:
c = BIT_NUMBER_TO_COOR(u);
TIMED_EVAL_CALL(ctx, u64CyclesEvalRook, _EvalRook(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After R:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After our R at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
- uColor = FLIP(uColor);
- bb = pos->bbPieces[uColor][ROOK];
+ // Enemy rooks.
+ bb = pos->bbPieces[xColor][ROOK];
while (bb)
{
u = FastFirstBit(bb) - 1;
@@ -5449,14 +5521,12 @@ Return value:
c = BIT_NUMBER_TO_COOR(u);
TIMED_EVAL_CALL(ctx, u64CyclesEvalRook, _EvalRook(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After R:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After enemy R at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
- //
- // Evaluate any queen(s) for side on move then for side not on move.
- //
- uColor = FLIP(uColor);
+ // Our queen(s).
bb = pos->bbPieces[uColor][QUEEN];
while (bb)
{
@@ -5465,12 +5535,13 @@ Return value:
c = BIT_NUMBER_TO_COOR(u);
TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen, _EvalQueen(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After Q:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After our Q at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
- uColor = FLIP(uColor);
- bb = pos->bbPieces[uColor][QUEEN];
+ // Enemy queen(s).
+ bb = pos->bbPieces[xColor][QUEEN];
while (bb)
{
u = FastFirstBit(bb) - 1;
@@ -5478,7 +5549,8 @@ Return value:
c = BIT_NUMBER_TO_COOR(u);
TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen, _EvalQueen(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After Q:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After enemy Q at %s:\n%d\t\t%d\n",
+ CoorToString(c), pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
@@ -5496,7 +5568,6 @@ Return value:
}
#endif
TIMED_EVAL_CALL(ctx, u64CyclesEvalKing, _EvalKing(pos, c, pHash, ctx));
- ctx->sPlyInfo[ctx->uPly].iKingScore[BLACK] = pos->iTempScore;
#ifdef EVAL_DUMP
Trace("After *k:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
@@ -5512,7 +5583,6 @@ Return value:
}
#endif
TIMED_EVAL_CALL(ctx, u64CyclesEvalKing, _EvalKing(pos, c, pHash, ctx));
- ctx->sPlyInfo[ctx->uPly].iKingScore[WHITE] = pos->iTempScore;
#ifdef EVAL_DUMP
Trace("After .k:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
@@ -5528,7 +5598,7 @@ Return value:
bb = (pHash->bbPasserLocations[WHITE] | pHash->bbPasserLocations[BLACK]);
if (0 != bb)
{
- _EvalPassers(pos, pHash);
+ _ReEvalPassers(pos, pHash);
#ifdef EVAL_DUMP
Trace("After passers:\n%d\t\t%d\n", pos->iScore[WHITE],
pos->iScore[BLACK]);
@@ -5548,73 +5618,8 @@ Return value:
_EvalLookForDanger(ctx);
_EvalTrappedPieces(ctx);
- //
- // B over N in the endgame with 2 pawn wings.
- //
- if ((pos->uNonPawnCount[WHITE][0] <= 2) &&
- (pos->uNonPawnCount[BLACK][0] <= 2))
- {
- if ((pos->uNonPawnCount[WHITE][BISHOP] > 0) &&
- (pos->uNonPawnCount[BLACK][BISHOP] > 0))
- {
- if ((pos->uNonPawnCount[BLACK][0] == 2) &&
- (pos->uNonPawnCount[WHITE][0] == 2))
- {
- ASSERT(pos->uNonPawnCount[BLACK][BISHOP] == 1);
- ASSERT(pos->uNonPawnCount[WHITE][BISHOP] == 1);
- if (pos->uWhiteSqBishopCount[BLACK] +
- pos->uWhiteSqBishopCount[WHITE] == 1)
- {
- pos->iScore[WHITE] /= 2;
- pos->iScore[BLACK] /= 2;
- }
- }
- }
- else
- {
- //
- // At least one side has no bishop. Look for positions
- // with two pawn wings where having a bishop is an
- // advantage.
- //
- bb = (pos->bbPawns[WHITE] |
- pos->bbPawns[BLACK]);
- ASSERT((BBFILE[A] | BBFILE[B] | BBFILE[C]) ==
- 0x0707070707070707ULL);
- ASSERT((BBFILE[F] | BBFILE[G] | BBFILE[H]) ==
- 0xe0e0e0e0e0e0e0e0ULL);
- if ((bb & 0x0707070707070707ULL) &&
- (bb & 0xe0e0e0e0e0e0e0e0ULL))
- {
- if ((pos->uNonPawnCount[BLACK][BISHOP] == 0) &&
- (pos->uNonPawnCount[WHITE][BISHOP] > 0))
- {
- EVAL_TERM(WHITE,
- BISHOP,
- 0x88,
- pos->iScore[WHITE],
- BISHOP_OVER_KNIGHT_IN_ENDGAME *
- pos->uNonPawnCount[WHITE][BISHOP],
- "endgame w/ 2 pawn flanks");
- }
- else if ((pos->uNonPawnCount[BLACK][BISHOP] > 0) &&
- (pos->uNonPawnCount[WHITE][BISHOP] == 0))
- {
- EVAL_TERM(BLACK,
- BISHOP,
- 0x88,
- pos->iScore[BLACK],
- BISHOP_OVER_KNIGHT_IN_ENDGAME *
- pos->uNonPawnCount[BLACK][BISHOP],
- "endgame w/ 2 pawn flanks");
- }
- }
- }
-#ifdef EVAL_DUMP
- Trace("After BOOC / BvsN:\n%d\t\t%d\n",
- pos->iScore[WHITE], pos->iScore[BLACK]);
-#endif
- }
+ // TODO: endgame-specific knowledge? e.g. B over N in an endgame
+ // with 2 pawn wings?
//
// Roll in the reduced material down scaler terms.
@@ -5649,10 +5654,46 @@ Return value:
// TODO: detect and discourage blocked positions?
//
- //
- // TODO: drive the score towards zero as we approach a 50 move w/o
+ // If the side who's ahead by raw material/positional score can't
+ // actually force a win with what it has left on the board (see
+ // _SideHasWinningChances), squash the score hard towards draw --
+ // same idiom as the 50-move dampening just below (and the same
+ // signed-arithmetic care: g_iDrawScore is currently always 0, but
+ // write this relative to it rather than assuming that, matching
+ // the 50-move code's own convention). /16 (not a full collapse to
+ // drawscore) deliberately mirrors Crafty's EvaluateDraws -- e.g.
+ // KRB vs KR is still theoretically losable by the run-of-the-mill
+ // defender with the checks _SideHasWinningChances gates on, so
+ // some residual signal survives.
+ if ((iScoreForSideToMove > g_iDrawScore[pos->uToMove]) &&
+ (FALSE == _SideHasWinningChances(pos, pos->uToMove)))
+ {
+ iScoreForSideToMove = g_iDrawScore[pos->uToMove] +
+ ((iScoreForSideToMove - g_iDrawScore[pos->uToMove]) / 16);
+ }
+ else if ((iScoreForSideToMove < g_iDrawScore[pos->uToMove]) &&
+ (FALSE == _SideHasWinningChances(pos, FLIP(pos->uToMove))))
+ {
+ iScoreForSideToMove = g_iDrawScore[pos->uToMove] +
+ ((iScoreForSideToMove - g_iDrawScore[pos->uToMove]) / 16);
+ }
+
+ // Drive the score towards draw as we approach a 50 move w/o
// progress draw.
- //
+ if (pos->uFifty > 84)
+ {
+ ULONG uDrawDist = 101 - pos->uFifty;
+ ASSERT(uDrawDist > 0);
+ // uDrawDist is ULONG -- multiplying a negative SCORE by it
+ // directly promotes the SCORE to unsigned first (usual
+ // arithmetic conversions, same rank), wrapping a negative
+ // iScoreForSideToMove into a huge positive garbage value
+ // instead of scaling it down. Cast uDrawDist to SCORE so the
+ // multiply happens in signed arithmetic; its range (1-16) is
+ // always representable.
+ iScoreForSideToMove = g_iDrawScore[pos->uToMove] +
+ (iScoreForSideToMove * (SCORE)uDrawDist / 16);
+ }
//
// Adjust dynamic positional component.