/** Copyright (c) Scott Gasch Module Name: eval.c Abstract: Position evaluation routines. Author: Scott Gasch (scott.gasch@gmail.com) 14 Jun 2004 Revision History: $Id: eval.c 357 2008-07-03 16:18:11Z scott $ **/ #include "chess.h" // // CountBits and the CoorFromBitBoard{Rank8ToRank1,Rank1ToRank8} // wrappers (bitboard.c) are real out-of-line asm calls on every one of // eval.c's ~20 production call sites (passed-pawn detection, bishop- // pair logic, etc.), hit on every real Eval() call. CountBits' asm // body (x64.asm) isn't even O(1) popcnt, it's a Kernighan bit-clearing // loop -- O(popcount) iterations plus call overhead. Redirected via // #define, the same mechanism chess.h already uses for the OTHER // direction (#ifdef CROUTINES routes CountBits/FirstBit/LastBit to the // slow C fallbacks) -- this routes them to fast inline compiler // builtins instead, for every existing call site below with no further // edits. Gated on !CROUTINES so the CROUTINES debug/comparison build // still gets the real (slow, cross-checked) implementations. // // _FastCoorFromBitBoardRank1ToRank8 also bakes in the wrong-bit-clear // fix applied to bitboard.c's real CoorFromBitBoardRank1ToRank8 (that // function used to clear the LOWEST set bit via `*pbb &= (*pbb - 1)` // regardless of uLastBit, correct only when a single bit was set) -- // this version clears the bit it actually just reported, via BBSQUARE. // #ifndef CROUTINES static ULONG INLINE _FastCountBits(IN BITBOARD bb) { return (ULONG)__builtin_popcountll(bb); } static COOR INLINE _FastCoorFromBitBoardRank8ToRank1(IN OUT BITBOARD *pbb) { COOR c = ILLEGAL_COOR; ULONG uBitIndex; if (*pbb) { uBitIndex = (ULONG)__builtin_ctzll(*pbb); c = BIT_NUMBER_TO_COOR(uBitIndex); *pbb &= (*pbb - 1); } return c; } static COOR INLINE _FastCoorFromBitBoardRank1ToRank8(IN OUT BITBOARD *pbb) { COOR c = ILLEGAL_COOR; ULONG uBitIndex; if (*pbb) { uBitIndex = (ULONG)(63 - __builtin_clzll(*pbb)); c = BIT_NUMBER_TO_COOR(uBitIndex); *pbb &= ~BBSQUARE[uBitIndex]; } return c; } #define CountBits _FastCountBits #define CoorFromBitBoardRank8ToRank1 _FastCoorFromBitBoardRank8ToRank1 #define CoorFromBitBoardRank1ToRank8 _FastCoorFromBitBoardRank1ToRank8 #endif // !CROUTINES // // Bishop-mobility ray-walk outcome categories -- see BMobCaseTable in // _EvalBishop. Replaces a table of function pointers (one indirect // call/ret per square visited) with a table of these tags dispatched // via switch, inlined directly into the ray-walking loop. // typedef enum _BMOB_CASE { BMOB_EMPTY = 0, // empty square BMOB_INVALID, // should never occur (off-board sentinel) BMOB_FRIEND_PAWN, // own-color pawn BMOB_ENEMY_PAWN, // enemy pawn (worth less than a bishop) BMOB_FRIEND_BLOCK, // own-color knight/rook/king: blocks, no xray BMOB_ENEMY_SAME, // enemy bishop/knight: captures, blocks BMOB_FRIEND_XRAY, // own-color bishop/queen: xray through, keep going BMOB_ENEMY_GREATER, // enemy rook/queen/king: captures, xray bit, keep going } BMOB_CASE; // // Knight-mobility outcome categories -- see NMobCaseTable in _EvalKnight. // Knight mobility only ever looks at the single landing square (no ray to // walk/stop), so there's no xray/stop concept here, just "does landing on // this square count as mobility." // typedef enum _NMOB_CASE { NMOB_INVALID = 0, // should never occur (off-board sentinel) NMOB_MOBILE_SQUARE, // empty square or enemy pawn: counts unless unsafe for a minor NMOB_ENEMY_OTHER, // any other enemy piece: always counts NMOB_FRIEND, // any friendly piece: never counts } NMOB_CASE; // // Rook-mobility outcome categories -- see RMobCaseTable in _EvalRook. // RMOB_FRIEND_ROOK carries a real side effect (the "connected rooks" // bonus), so unlike bishop/knight this isn't purely mobility/xray-bit // bookkeeping -- kept as a distinct case rather than folded into // RMOB_FRIEND_QUEEN even though both keep scanning with the xray bit set. // typedef enum _RMOB_CASE { RMOB_EMPTY = 0, // empty square RMOB_INVALID, // should never occur (off-board sentinel) RMOB_ENEMY_LESS, // enemy pawn/knight/bishop: captures, blocks RMOB_FRIEND_BLOCK, // own-color knight/bishop/king: blocks, no xray RMOB_FRIEND_ROOK, // own-color rook: connected-rooks bonus, xray, keep going RMOB_ENEMY_SAME, // enemy rook: captures, blocks RMOB_FRIEND_QUEEN, // own-color queen: xray through, keep going RMOB_ENEMY_GREATER, // enemy queen/king: captures, xray bit, keep going } RMOB_CASE; // // Queen-mobility outcome categories -- see QMobCaseTable in _EvalQueen. // A queen's ray set is the union of a bishop's 4 diagonals and a rook's // 4 orthogonals, so QMOB_FRIEND_BISHOP/QMOB_FRIEND_ROOK only let the // queen xray through when the *ray it's currently walking* matches that // piece's own move pattern (diagonal for a bishop, orthogonal for a // rook) -- computed once per ray below (fOrthogonalRay), not per square, // since every square on a given ray shares the same rank/file // relationship to the queen's home square. // typedef enum _QMOB_CASE { QMOB_EMPTY = 0, // empty square QMOB_INVALID, // should never occur (off-board sentinel) QMOB_ENEMY_LESS, // enemy piece worth less than a queen: captures, blocks QMOB_FRIEND_BLOCK, // own-color knight/king: blocks, no xray QMOB_FRIEND_BISHOP, // own-color bishop: xray only on a diagonal ray QMOB_FRIEND_ROOK, // own-color rook: xray only on an orthogonal ray QMOB_FRIEND_QUEEN, // own-color queen: xray through, keep going QMOB_ENEMY_GE, // enemy queen/king: captures, blocks } QMOB_CASE; // // To simplify code / maintenance I use the same loop for both colors // in some places. These globals coorespond to "ahead of the piece" // or "behind the piece" for each color. // const int g_iAhead[2] = { +1, -1 }; 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 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] = { // none na na m na R 2m na 0, 0, 0, 0, 0, 0, 0, 0, // Rm 3m/Q 2R R2m 4m/Qm 2Rm R3m/QR Q2m 0, 1, 1, 1, 1, 1, 2, 3, // 2R2m R4m/QRm Q3m 2R3m/ QR2m Q4m 2R4m/ QR3m // Q2R Q2Rm 4, 5, 6, 6, 7, 7, 7, 7, // na Q2R2m QR4m na Q2R3m na na full 8, 8, 8, 8, 8, 8, 8, 8, }; static ULONG REDUCED_MATERIAL_UP_SCALER[32] = { // none na na m na R 2m na 8, 8, 8, 8, 8, 8, 8, 8, // Rm 3m/Q 2R R2m 4m/Qm 2Rm R3m/QR Q2m 8, 7, 7, 7, 7, 6, 6, 5, // 2R2m R4m/QRm Q3m 2R3m/ QR2m Q4m 2R4m/ QR3m // Q2R Q2Rm 4, 3, 3, 3, 2, 2, 2, 1, // na Q2R2m QR4m na Q2R3m na na full 1, 1, 0, 0, 0, 0, 0, 0, }; static ULONG PASSER_MATERIAL_UP_SCALER[32] = { // none na na m na R 2m na 8, 8, 8, 8, 8, 8, 7, 7, // Rm 3m/Q 2R R2m 4m/Qm 2Rm R3m/QR Q2m 7, 6, 7, 5, 4, 5, 4, 3, // 2R2m R4m/QRm Q3m 2R3m/ QR2m Q4m 2R4m/ QR3m // Q2R Q2Rm 2, 2, 1, 1, 0, 0, 0, 0, // na Q2R2m QR4m na Q2R3m na na full 0, 0, 0, 0, 0, 0, 0, 0, }; COOR QUEENING_RANK[2] = { 1, 8 }; COOR JUMPING_RANK[2] = { 7, 2 }; // // Pawn eval terms // --------------------------------------------------------------------------- // // "There is one case which can be treated as positional or material, // namely the rook's pawn, which differs from other pawns in that it // can only capture one way instead of two. Since this handicap cannot // be corrected without the opponent's help, I teach my students to // regard the rook's pawn as a different piece type, a crippled // pawn. Database statistics indicate that it is on average worth // about 15% less than a normal pawn. The difference is enough so that // it is usually advantageous to make a capture with a rook's pawn, // promoting it to a knights pawn, even if that produces doubled pawns // and even if there is no longer a rook on the newly opened rook's // file." // --Larry Kaufman, IM // "Evaluation of Material Imbalance" // static SCORE PAWN_CENTRALITY_BONUS[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0, -8, 0, 0, 0, 0, 0, 0, -8, 0,0,0,0,0,0,0,0, -8, 0, 5, 5, 5, 5, 0, -8, 0,0,0,0,0,0,0,0, -8, 0, 5, 9, 9, 5, 0, -8, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------- -8, 0, 5, 9, 9, 5, 0, -8, 0,0,0,0,0,0,0,0, -8, 0, 5, 5, 5, 5, 0, -8, 0,0,0,0,0,0,0,0, -8, 0, 0, 0, 0, 0, 0, -8, 0,0,0,0,0,0,0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0 }; // Hand-restored 2026-08-30: the DNA-tuned values here had drifted to // mostly-positive across the board (a shielded backward pawn was scoring // as a +12..+17 bonus on most files, and several EXPOSED entries were // less negative than the corresponding SHIELDED ones on the same file -- // i.e. "more exposed" scored better than "less exposed"). Both the // ASSERT(BACKWARD_SHIELDED_BY_LOCATION[c] < 0) and the analogous EXPOSED // assert below had been silently commented out rather than investigated. // Replaced with a monotonic, strictly-negative, rank-mirrored shape: // backward pawns are always a defect, exposed is always worse than // shielded at the same rank, and the penalty grows as the pawn advances // toward the more contested center ranks (4/5) where a backward pawn is // most exploitable. Table is indexed by absolute square (not // color-relative), hence the rank2/rank7, rank3/rank6, rank4/rank5 // mirroring -- same as the original layout. static SCORE BACKWARD_SHIELDED_BY_LOCATION[128] = { +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0, -7, -5, -5, -7, -7, -5, -5, -7, 0,0,0,0,0,0,0,0, -5, -4, -4, -5, -5, -4, -4, -5, 0,0,0,0,0,0,0,0, -4, -3, -3, -4, -4, -3, -3, -4, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------- -4, -3, -3, -4, -4, -3, -3, -4, 0,0,0,0,0,0,0,0, -5, -4, -4, -5, -5, -4, -4, -5, 0,0,0,0,0,0,0,0, -7, -5, -5, -7, -7, -5, -5, -7, 0,0,0,0,0,0,0,0, +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0 }; static SCORE BACKWARD_EXPOSED_BY_LOCATION[128] = { +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0, -12, -9, -9, -11, -11, -9, -9, -12, 0,0,0,0,0,0,0,0, -9, -7, -7, -9, -9, -7, -7, -9, 0,0,0,0,0,0,0,0, -7, -6, -6, -7, -7, -6, -6, -7, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------- -7, -6, -6, -7, -7, -6, -6, -7, 0,0,0,0,0,0,0,0, -9, -7, -7, -9, -9, -7, -7, -9, 0,0,0,0,0,0,0,0, -12, -9, -9, -11, -11, -9, -9, -12, 0,0,0,0,0,0,0,0, +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0 }; // // "The first statement I can confirm is that doubled pawns are indeed // on average undesirable. However this statistic needs to be broken // down to be useful. Doubled pawns themselves are really more // serious than this generally, but when your pawns are doubled you // automatically get an extra half-open file for your rooks (or queen, // but the queen can also use diagonals). So it follows logically // that the net cost of doubled pawns is much greater in the absence // of major pieces. The database shows that with all rooks present, // doubled pawns "cost" only about 1/16th of a pawn on average. With // one rook each the cost rises to 1/4th of a pawn and with no rooks // present to 3/8ths of a pawn. With the queens present the cost is // again only 1/16th of a pawn; without them it's a quarter pawn. So // the lessons are clear; beware of doubled pawns when major pieces // have been exchanged, and beware of exchanging major pieces when you // are the one with the doubled pawns." // --Larry Kaufman, IM // "All About Doubled Pawns" // // Note: indexed by number of files with 1+ pawn on them. static SCORE DOUBLED_PAWN_PENALTY_BY_COUNT[4][9] = { // ------------ count of doubled+ pawns ------------ {// 0 1 2 3 4 5 6 7 8 : no majors alive +0, -32, -65, -99, -134, -170, -207, -222, -250 }, {// : 1 rook +0, -23, -47, -76, -108, -144, -184, -200, -216 }, {// : 2 rooks or 1 queen +0, -13, -23, -34, -46, -64, -86, -111, -138 }, {// : all majors alive +0, -7, -13, -25, -39, -55, -73, -95, -121 }, }; static SCORE ISOLATED_PAWN_BY_PAWNFILE[9] = { 0, -7, -8, -9, -10, -10, -9, -8, -7 }; static SCORE ISOLATED_EXPOSED_PAWN = -5; // Turned down (2026-08-30): a per-pawn modifier that stacks on top of // DOUBLED_PAWN_PENALTY_BY_COUNT's whole-position doubled-pawn aggregate // for the specific case of a pawn that's both isolated and doubled -- // a different angle (single-worst-case flag vs. whole-position // severity), not the same fact counted twice, so scaled down rather // than removed. static SCORE ISOLATED_DOUBLED_PAWN = -7; // // Note: -25% to -33% if the enemy occupies or controls the next sq. // static SCORE PASSER_BY_RANK[2][9] = {// 0 1 2 3 4 5 6 7 8 { +0, +0,+162,+111, +62, +36, +18, +13, +0 }, // black { +0, +0, +13, +18, +36, +62,+111,+162, +0 } // white }; static SCORE CANDIDATE_PASSER_BY_RANK[2][9] = {// 0 1 2 3 4 5 6 7 8 { +0, +0, +0, +48, +34, +22, +13, +9, +0 }, // black { +0, +0, +9, +13, +22, +34, +48, +0, +0 } // white }; // Note: x2 // Scaled to 1/3 of the original hand-tuned magnitude (2026-08-30): these // three terms, plus PASSER_BY_RANK and OUTSIDE_PASSER_BY_DISTANCE, all // separately price "how good is this passer" from different angles // (connected to a partner passer, backed by an ordinary pawn, outside // the opposing pawn majority) and can stack for the same pawn -- a // rank-7 connected+outside passer could hit ~370cp in this pawn-hash- // cached family alone, ~9x Crafty's ~40cp ceiling for the equivalent // concept. Rather than remove any of these (they're each pricing a // genuinely distinct structural fact, not re-counting the same one -- // and pawn-hash caching makes the compute cost of keeping all of them // free), turned the magnitude down on the three secondary/modifier // terms so the stack lands in a saner range; PASSER_BY_RANK (the core // "how far advanced" signal) is left as the primary, least-reduced // term. static SCORE CONNECTED_PASSERS_BY_RANK[2][9] = {// 0 1 2 3 4 5 6 7 8 { +0, +0, +32, +25, +16, +7, +3, +2, +0 }, // black { +0, +0, +2, +3, +7, +16, +25, +32, +0 } // white }; static SCORE SUPPORTED_PASSER_BY_RANK[2][9] = {// 0 1 2 3 4 5 6 7 8 { +0, +0, +20, +13, +4, +2, +1, +1, +0 }, // black { +0, +0, +1, +1, +2, +4, +13, +20, +0 } // white }; // Split out 2026-08-30 from SUPPORTED_PASSER_BY_RANK, which _EvalKing's // "kings in front of passers" endgame bonus was silently reusing -- // pawn-support and king-escort are different concepts (this fires when // the KING stands next to its own passer, not when a pawn does), so // scaling one for its real purpose was silently also scaling the // other. Seeded with SUPPORTED_PASSER_BY_RANK's original (pre-scaling) // hand-tuned magnitude, since that's what this use case was actually // getting before today's pawn-hash pass touched the shared table for // an unrelated reason. static SCORE KING_SUPPORTING_OWN_PASSER_BY_RANK[2][9] = {// 0 1 2 3 4 5 6 7 8 { +0, +0, +60, +40, +13, +6, +3, +1, +0 }, // black { +0, +0, +1, +3, +6, +13, +40, +60, +0 } // white }; static SCORE OUTSIDE_PASSER_BY_DISTANCE[9] = {// 0 1 2 3 4 5 6 7 8 +0, +0, +2, +5, +7, +9, +11, +14, +18 }; static SCORE PASSER_BONUS_AS_MATERIAL_COMES_OFF[32] = { // none na na m na R 2m na +60, -1, -1, +37, -1, +43, +19, -1, // Rm 3m/Q 2R R2m 4m/Qm 2Rm R3m/QR Q2m +14, +9, +20, +4, +3, +3, 0, 0, // 2R2m R4m/QRm Q3m 2R3m/ QR2m Q4m 2R4m/ QR3m // Q2R Q2Rm 0, 0, 0, 0, 0, 0, 0, 0, // na Q2R2m QR4m na Q2R3m na na full 0, 0, 0, 0, 0, 0, 0, 0, }; SCORE RACER_WINS_RACE = +800; static SCORE UNDEVELOPED_MINORS_IN_OPENING[5] = {// 0 1 2 3 4 0, -6, -10, -16, -22 }; // // Bishop eval terms // --------------------------------------------------------------------------- // static SCORE BISHOP_OVER_KNIGHT_IN_ENDGAME = +33; // // "The bishop pair has an average value of half a pawn (more when the // opponent has no minor pieces to exchange for one of the bishops), // enough to regard it as part of the material evaluation of the // position, and enough to overwhelm most positional considerations. // Moreover, this substantial bishop pair value holds up in all // situations tested, regardless of what else is on the board... // // ...One rule which I often teach to students is that if you have the // bishop pair, and your opponent's single bishop is a bad bishop // (hemmed in by his own pawns), you already have full compensation // for a pawn... Kasparov has said something similar... // // As noted before, the bishop pair is worth more with fewer pawns on // the board. Aside from this factor, the half pawn value of the // bishop pair is remarkably constant, applying even when there are no // pieces on the board except two minors each." // --Larry Kaufman, IM // "Evaluation of Material Imbalance" static SCORE BISHOP_PAIR[2][17] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 53, 52, 51, 50, 49, 48, 47, 45, 43, 41, 40, 40, 40, 40, 40, 40, 40 } }; static SCORE STATIONARY_PAWN_ON_BISHOP_COLOR[128] = { +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0, -4, -6, -7, -7, -7, -7, -6, -4, 0,0,0,0,0,0,0,0, -6, -7, -9, -8, -8, -9, -7, -6, 0,0,0,0,0,0,0,0, -7, -8, -10, -11, -11, -10, -8, -7, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------- -7, -8, -10, -11, -11, -10, -8, -7, 0,0,0,0,0,0,0,0, -6, -7, -9, -8, -8, -9, -7, -6, 0,0,0,0,0,0,0,0, -4, -6, -7, -7, -7, -7, -6, -4, 0,0,0,0,0,0,0,0, +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0 }; static SCORE TRANSIENT_PAWN_ON_BISHOP_COLOR[128] = { +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0, -1, -1, -2, -2, -2, -2, -1, -1, 0,0,0,0,0,0,0,0, -3, -4, -4, -4, -4, -4, -4, -3, 0,0,0,0,0,0,0,0, -4, -5, -7, -8, -8, -7, -5, -4, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------- -4, -5, -7, -8, -8, -7, -5, -4, 0,0,0,0,0,0,0,0, -3, -4, -4, -4, -4, -4, -4, -3, 0,0,0,0,0,0,0,0, -1, -1, -2, -2, -2, -2, -1, -1, 0,0,0,0,0,0,0,0, +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0 }; // // A bishop can move to between 0..13 squares. // static SCORE BISHOP_MOBILITY_BY_SQUARES[14] = {// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 -22, -14, -10, -5, -1, 0, +1, +3, +4, +5, +6, +7, +8, +9 };// ^ | static SCORE BISHOP_MAX_MOBILITY_IN_A_ROW_BONUS[8] = {// 0 1 2 3 4 5 6 7 -10, -4, +1, +3, +4, +5, +5, +5 }; static SCORE BISHOP_UNASSAILABLE_BY_DIST_FROM_EKING[9] = {// 0 1 2 3 4 5 6 7 8 +0, +33, +28, +18, +8, +4, +0, -10, -16 }; // // Knight eval terms // --------------------------------------------------------------------------- // static SCORE KNIGHT_CENTRALITY_BONUS[128] = { -12, -8, -8, -8, -8, -8, -8, -12, 0,0,0,0,0,0,0,0, -8, -2, -2, 0, 0, -2, -2, -8, 0,0,0,0,0,0,0,0, -8, -2, +4, +5, +5, +4, -2, -8, 0,0,0,0,0,0,0,0, -8, -2, +5, +8, +8, +5, -2, -8, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------- -8, -2, +5, +8, +8, +5, -2, -8, 0,0,0,0,0,0,0,0, -8, -2, +4, +5, +5, +4, -2, -8, 0,0,0,0,0,0,0,0, -8, -2, -2, 0, 0, -2, -2, -8, 0,0,0,0,0,0,0,0, -12, -8, -8, -8, -8, -8, -8, -12, 0,0,0,0,0,0,0,0 }; static SCORE KNIGHT_KING_TROPISM_BONUS[9] = {// 0 1 2 3 4 5 6 7 8 0, +15, +12, +9, +4, +0, +0, +0, +0 }; static SCORE KNIGHT_UNASSAILABLE_BY_DIST_FROM_EKING[9] = {// 0 1 2 3 4 5 6 7 8 +0, +25, +19, +14, +6, +0, +0, +0, +0 }; static SCORE KNIGHT_ON_INTERESTING_SQUARE_BY_RANK[2][9] = {// 0 1 2 3 4 5 6 7 8 { +0, +12, +11, +9, +6, +3, +0, +0, +0 }, // black { +0, +0, +0, +0, +3, +6, +9, +11, +12 } // white }; // // A knight can move to between 0..8 squares // ----------------------------------------- // 0 = supported enemy pawn // 1 = unsupported enemy pawn, friend pawn, enemy B/N // enemy controlled empty sq. // 2 = enemy >B, friend controlled empty sq, no one controlled empty sq // // Total: 16 mobility max // static SCORE KNIGHT_MOBILITY_BY_COUNT[9] = {// 0 1 2 3 4 5 6 7 8 -17, -10, -6, 0, +3, +5, +7, +8, +8 };// ^ | static SCORE KNIGHT_WITH_N_PAWNS_SUPPORTING[3] = { +0, +4, +8 }; static SCORE KNIGHT_IN_CLOSED_POSITION[33] = {// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 6, 9, 11, 12, 13, 15, 17, 19, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27 // 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 }; // // Rook eval terms // --------------------------------------------------------------------------- // static SCORE ROOK_ON_FULL_OPEN_BY_DIST_FROM_EKING[8] = {// 0 1 2 3 4 5 6 7 +24, +22, +17, +14, +13, +13, +12, +12 }; static SCORE ROOK_ON_HALF_OPEN_WITH_ENEMY_BY_DIST_FROM_EKING[8] = {// 0 1 2 3 4 5 6 7 +12, +11, +9, +9, +8, +8, +8, +7 }; static SCORE ROOK_ON_HALF_OPEN_WITH_FRIEND_BY_DIST_FROM_EKING[8] = {// 0 1 2 3 4 5 6 7 +13, +12, +11, +11, +9, +9, +9, +8 }; static SCORE ROOK_BEHIND_PASSER_BY_PASSER_RANK[2][9] = {// 0 1 2 3 4 5 6 7 8 { +0, +0, +25, +17, +12, +6, +1, +0, +0 }, // black { +0, +0, +0, +1, +6, +12, +17, +25, +0 } // white }; static SCORE ROOK_LEADS_PASSER_BY_PASSER_RANK[2][9] = {// 0 1 2 3 4 5 6 7 8 { +0, +0, -22, -16, -13, -9, -5, -3, +0 }, // black { +0, +0, -3, -5, -9, -13, -16, -22, +0 } // white }; static SCORE KING_TRAPPING_ROOK = -40; static SCORE ROOK_VALUE_AS_PAWNS_COME_OFF[17] = {// 0 1 2 3 4 5 6 7 8 +55, +51, +44, +38, +33, +27, +22, +16, +7, // 9 10 11 12 13 14 15 16 +1, -3, -6, -9, -12, -18, -22, -25 }; // // Note: these are multiplied by two (one per rook). // static SCORE ROOK_CONNECTED_VERT = +7; // x2 static SCORE ROOK_CONNECTED_HORIZ = +4; // x2 // // A rook can move to between 0..14 squares // ---------------------------------------- // 0 = supported enemy P/N/B, friend piece // 1 = unsupported enemy piece, enemy controlled empty sq, enemy R // 2 = enemy >R, friend controlled empty sq, no one controlled empty sq // // Total: 28 mobility max // static SCORE ROOK_MOBILITY_BY_SQUARES[15] = {// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 -28, -24, -20, -14, -7, -2, +0, +4, +8, +12, +15, +17, +19, +21, +22 };// ^ | static SCORE ROOK_MAX_MOBILITY_IN_A_ROW_BONUS[8] = {// 0 1 2 3 4 5 6 7 -15, -6, +0, +4, +8, +8, +8, +8 }; // // Queen eval terms // --------------------------------------------------------------------------- // // // A queen can move to between 0..27 squares // ----------------------------------------- // 0 = supported enemy P/N/B/R, friend piece // 1 = unsupported enemy piece, enemy controlled empty sq, enemy Q // 2 = enemy K, no one controlled empty sq, friend controlled empty sq // // Total: 54 mobility max // static SCORE QUEEN_MOBILITY_BY_SQUARES[28] = {// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 -30, -26, -22, -17, -11, -8, -4, -2, -1, +2, +4, +7, +10, +12, +14, // | //15 16 17 18 19 20 21 22 23 24 25 26 27 +15, +16, +17, +18, +19, +20, +20, +21, +21, +22, +22, +23, +23 }; static SCORE QUEEN_OUT_EARLY[5] = {// 0 1 2 3 4 : num unmoved minors 0, -14, -22, -26, -33 }; // // In "Evaluation of Material Imbalance", IM Larry Kaufman makes the // point that queens are worth more then the standard nine "points". // Thus, this scale is biased upwards by about 0.15 pawn. // static SCORE QUEEN_KING_TROPISM[8] = {// 0 1 2 3 4 5 6 7 0, +34, +28, +22, +19, +17, +16, +15 }; // // King eval terms // --------------------------------------------------------------------------- // static ULONG KING_INITIAL_COUNTER_BY_LOCATION[2][128] = { { 1, 0, 0, 0, 1, 0, 0, 1, 0,0,0,0,0,0,0,0, 1, 1, 1, 1, 1, 1, 1, 1, 0,0,0,0,0,0,0,0, 3, 3, 3, 3, 3, 3, 3, 3, 0,0,0,0,0,0,0,0, 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------- 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0 }, { 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------- 4, 4, 4, 4, 4, 4, 4, 4, 0,0,0,0,0,0,0,0, 3, 3, 3, 3, 3, 3, 3, 3, 0,0,0,0,0,0,0,0, 1, 1, 1, 1, 1, 1, 1, 1, 0,0,0,0,0,0,0,0, 1, 0, 0, 0, 1, 0, 0, 1, 0,0,0,0,0,0,0,0 } }; static SCORE KING_TO_CENTER[128] = { +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0, +1, +3, +5, +7, +7, +5, +3, +1, 0,0,0,0,0,0,0,0, +3, +5, +13, +15, +15, +13, +5, +3, 0,0,0,0,0,0,0,0, +5, +7, +17, +23, +23, +17, +7, +5, 0,0,0,0,0,0,0,0, // ------------------------------------------------------------------ +5, +7, +17, +23, +23, +17, +7, +5, 0,0,0,0,0,0,0,0, +3, +5, +13, +15, +15, +13, +5, +3, 0,0,0,0,0,0,0,0, +1, +3, +5, +7, +7, +5, +3, +1, 0,0,0,0,0,0,0,0, +0, +0, +0, +0, +0, +0, +0, +0, 0,0,0,0,0,0,0,0 }; // // If a side has less than this much material we don't bother scoring // the other side's king safety. Note: this doesn't incl pawn // material. // #define DO_KING_SAFETY_THRESHOLD \ (VALUE_ROOK + VALUE_BISHOP + VALUE_KING + 1) // // If a side has less than this much material the other side's king // can come out. Note: this doesn't incl pawn material. // #define KEEP_KING_AT_HOME_THRESHOLD \ (VALUE_ROOK + VALUE_ROOK + VALUE_BISHOP + VALUE_KING + 1) static ULONG KING_COUNTER_BY_ATTACK_PATTERN[32] = { //p 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 //m 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 //r 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 //q 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 //k 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0,0,2,2,1,1,3,3,1,1,3,3,2,2,4,4,2,2,3,3,2,2,4,4,2,2,4,4,3,4,5,5 }; static SCORE KING_SAFETY_BY_COUNTER[42] = { -0, -4, -8, -12, -16, -20, -25, -31, -38, -50, -63, -76, -90, -105,-120,-135,-150,-165,-180,-195,-210,-225,-240,-255,-270,-290, -310,-330,-350,-370,-390,-410,-440,-470,-500,-500,-500,-500,-500, -500,-500,-500 }; // Added 2026-08-30: penalty for the enemy queen directly attacking or // x-raying squares near this king, indexed by count (capped at 6). // Replaces the old per-queen "pointing near enemy K" bolt-on // (QUEEN_ATTACKS_SQ_NEXT_TO_KING, computed via the queen's own // mobility ray-cast, direct-attacks only) with a version computed from // data this loop already reads for king safety (no extra attack-table // work) and that also catches x-ray/latent queen threats the old // version missed. Calibrated against the removed term's own magnitude // (uNearKing * 8, capped at 6 -> max 48) rather than guessed, since // that's what was empirically shown to matter for real solve rate -- // see the 180-position king-safety-term distribution sampled // 2026-08-30 (median -27, p75 -16, only ~1% worse than -200) for the // scale this needs to slot into. static SCORE KING_QUEEN_PROXIMITY_DANGER[7] = {// 0 1 2 3 4 5 6 +0, -8, -16, -24, -32, -40, -48 }; static SCORE KING_MISSING_ONE_CASTLE_OPTION = -23; // Flat "this is bad" flags for _EvalTrappedPieces -- not an attempt to // price the material outcome (search resolves that), just a nudge away // from positions with a piece that looks stuck. ENPRISE_AND_TRAPPED is // larger because that case is opponent-to-move / imminently capturable; // TRAPPED_WITH_MOVE is our own move, so there's still a chance to // wriggle out. static SCORE ENPRISE_AND_TRAPPED_PENALTY = -50; static SCORE TRAPPED_WITH_MOVE_PENALTY = -10; typedef struct _DNA_BASE_SIZE { SCORE *pBase; ULONG uCount; } DNA_BASE_SIZE; #define DNA_VAR(x) {(SCORE *)&(x), 1} #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(REDUCED_MATERIAL_UP_SCALER), DNA_ARRAY(PASSER_MATERIAL_UP_SCALER), DNA_ARRAY(PAWN_CENTRALITY_BONUS), DNA_ARRAY(BACKWARD_SHIELDED_BY_LOCATION), DNA_ARRAY(BACKWARD_EXPOSED_BY_LOCATION), DNA_MATRIX(DOUBLED_PAWN_PENALTY_BY_COUNT), DNA_ARRAY(ISOLATED_PAWN_BY_PAWNFILE), DNA_VAR(ISOLATED_EXPOSED_PAWN), DNA_VAR(ISOLATED_DOUBLED_PAWN), DNA_MATRIX(PASSER_BY_RANK), DNA_MATRIX(CANDIDATE_PASSER_BY_RANK), DNA_MATRIX(CONNECTED_PASSERS_BY_RANK), DNA_MATRIX(SUPPORTED_PASSER_BY_RANK), DNA_MATRIX(KING_SUPPORTING_OWN_PASSER_BY_RANK), DNA_ARRAY(OUTSIDE_PASSER_BY_DISTANCE), DNA_ARRAY(PASSER_BONUS_AS_MATERIAL_COMES_OFF), DNA_VAR(RACER_WINS_RACE), DNA_ARRAY(UNDEVELOPED_MINORS_IN_OPENING), DNA_VAR(BISHOP_OVER_KNIGHT_IN_ENDGAME), DNA_MATRIX(BISHOP_PAIR), DNA_ARRAY(STATIONARY_PAWN_ON_BISHOP_COLOR), DNA_ARRAY(TRANSIENT_PAWN_ON_BISHOP_COLOR), DNA_ARRAY(BISHOP_MOBILITY_BY_SQUARES), DNA_ARRAY(BISHOP_MAX_MOBILITY_IN_A_ROW_BONUS), DNA_ARRAY(BISHOP_UNASSAILABLE_BY_DIST_FROM_EKING), DNA_ARRAY(KNIGHT_CENTRALITY_BONUS), DNA_ARRAY(KNIGHT_KING_TROPISM_BONUS), DNA_ARRAY(KNIGHT_UNASSAILABLE_BY_DIST_FROM_EKING), DNA_MATRIX(KNIGHT_ON_INTERESTING_SQUARE_BY_RANK), DNA_ARRAY(KNIGHT_MOBILITY_BY_COUNT), DNA_ARRAY(KNIGHT_WITH_N_PAWNS_SUPPORTING), DNA_ARRAY(KNIGHT_IN_CLOSED_POSITION), DNA_ARRAY(ROOK_ON_FULL_OPEN_BY_DIST_FROM_EKING), DNA_ARRAY(ROOK_ON_HALF_OPEN_WITH_ENEMY_BY_DIST_FROM_EKING), DNA_ARRAY(ROOK_ON_HALF_OPEN_WITH_FRIEND_BY_DIST_FROM_EKING), DNA_MATRIX(ROOK_BEHIND_PASSER_BY_PASSER_RANK), DNA_MATRIX(ROOK_LEADS_PASSER_BY_PASSER_RANK), DNA_VAR(KING_TRAPPING_ROOK), DNA_ARRAY(ROOK_VALUE_AS_PAWNS_COME_OFF), DNA_VAR(ROOK_CONNECTED_VERT), DNA_VAR(ROOK_CONNECTED_HORIZ), DNA_ARRAY(ROOK_MOBILITY_BY_SQUARES), DNA_ARRAY(ROOK_MAX_MOBILITY_IN_A_ROW_BONUS), DNA_ARRAY(QUEEN_MOBILITY_BY_SQUARES), DNA_ARRAY(QUEEN_OUT_EARLY), DNA_ARRAY(QUEEN_KING_TROPISM), DNA_MATRIX(KING_INITIAL_COUNTER_BY_LOCATION), DNA_ARRAY(KING_TO_CENTER), DNA_ARRAY(KING_SAFETY_BY_COUNTER), DNA_ARRAY(KING_QUEEN_PROXIMITY_DANGER), DNA_VAR(KING_MISSING_ONE_CASTLE_OPTION) }; ULONG DNABufferSizeBytes() { ULONG u; ULONG uSize = 0; for (u = 0; u < ARRAY_LENGTH(g_EvalDNA); u++) { uSize += 10 * g_EvalDNA[u].uCount; } return uSize + 1; } char * ExportEvalDNA() { ULONG uSize = DNABufferSizeBytes(); char *p = malloc(uSize); ULONG u, v; SCORE *q; FLAG fFirst = TRUE; memset(p, 0, uSize); for (u = 0; u < ARRAY_LENGTH(g_EvalDNA); u++) { q = g_EvalDNA[u].pBase; for (v = 0; v < g_EvalDNA[u].uCount; v++) { if (!fFirst) { sprintf(p, "%s,%d", p, *q); } else { sprintf(p, "%s%d", p, *q); fFirst = FALSE; } q++; } strcat(p, "\n"); fFirst = TRUE; } return p; // caller must free } FLAG WriteEvalDNA(char *szFilename) { FILE *p = NULL; char *q = NULL; FLAG fRet = FALSE; if (SystemDoesFileExist(szFilename)) goto end; p = fopen(szFilename, "a+b"); if (!p) goto end; q = ExportEvalDNA(); if (!q) goto end; fprintf(p, "%s", q); fRet = TRUE; end: if (p) fclose(p); if (q) free(q); return fRet; } FLAG ImportEvalDNA(char *p) { ULONG u, v; for (u = 0; u < ARRAY_LENGTH(g_EvalDNA); u++) { for (v = 0; v < g_EvalDNA[u].uCount; v++) { while(*p && (!isdigit(*p) && (*p != '-'))) p++; if (*p == '\0') return FALSE; *(g_EvalDNA[u].pBase + v) = atoi(p); while(*p && (isdigit(*p) || (*p == '-'))) p++; } } return TRUE; } FLAG ReadEvalDNA(char *szFilename) { FILE *p = NULL; ULONG uSize = DNABufferSizeBytes(); char *q = malloc(uSize); FLAG fRet = FALSE; static char line[1024]; char *c; if (!q) goto end; memset(q, 0, uSize); p = fopen(szFilename, "rb"); if (!p) { Trace("Failed to open file \"%s\"\n", szFilename); goto end; } while(fgets(line, 1024, p) != NULL) { c = strchr(line, '#'); if (c) *c = '\0'; c = line; strcat(q, c); } fRet = ImportEvalDNA(q); end: if (q) free(q); if (p) fclose(p); return fRet; } // // Misc stuff // --------------------------------------------------------------------------- // // // The three ranks "around" a rank (indexed by rank, used in eval.c) // BITBOARD BBADJACENT_RANKS[9] = { 0, BBRANK11 | BBRANK22, BBRANK11 | BBRANK22 | BBRANK33, BBRANK22 | BBRANK33 | BBRANK44, BBRANK33 | BBRANK44 | BBRANK55, BBRANK44 | BBRANK55 | BBRANK66, BBRANK55 | BBRANK66 | BBRANK77, BBRANK66 | BBRANK77 | BBRANK88, BBRANK77 | BBRANK88 }; // // The files "around" one, indexed by file (used in eval.c) // BITBOARD BBADJACENT_FILES[8] = { // A = 0 BBFILEB, // B = 1 BBFILEA | BBFILEC, // C = 2 BBFILEB | BBFILED, // D = 3 BBFILEC | BBFILEE, // E = 4 BBFILED | BBFILEF, // F = 5 BBFILEE | BBFILEG, // G = 6 BBFILEF | BBFILEH, // H = 7 BBFILEG }; // // The ranks preceeding one, indexed by rank/color of pawn (used in // eval.c) // BITBOARD BBPRECEEDING_RANKS[8][2] = { // BLACK WHITE // ----- ----- { // 0 == RANK8 BBRANK72, 0 }, { // 1 == RANK7 BBRANK62, 0 }, { // 2 == RANK6 BBRANK52, BBRANK77 }, { // 3 == RANK5 BBRANK42, BBRANK67 }, { // 4 == RANK4 BBRANK32, BBRANK57 }, { // 5 == RANK3 BBRANK22, BBRANK47 }, { // 6 == RANK2 0, BBRANK37 }, { // 7 == RANK1 0, BBRANK27 } }; static FLAG _IsSquareSafeFromEnemyPawn(IN POSITION *pos, IN COOR c, IN BITBOARD bb) /** Routine description: Determine if a piece in square c is "outposted". A piece is outposted if no enemy pawns can advance to attack it / drive it away. Parameters: POSITION *pos : the board COOR c : the square (must not be empty!) BITBOARD bb : the bitboard of pawns you want to consider Return value: FLAG : TRUE if the piece is safe/outposted, FALSE otherwise **/ { ULONG uColor = GET_COLOR(pos->rgSquare[c].pPiece); ULONG uFile, uRank; #ifdef DEBUG COOR cSquare; PIECE p = pos->rgSquare[c].pPiece; BITBOARD dbb = bb; ASSERT(IS_ON_BOARD(c)); ASSERT(p && (IS_BISHOP(p) || IS_KNIGHT(p) || IS_PAWN(p))); ASSERT(IS_VALID_COLOR(uColor)); #endif uFile = FILE(c); ASSERT(uFile < 8); bb &= BBADJACENT_FILES[uFile]; ASSERT(((c & 0x70) >> 4) == (c >> 4)); uRank = c >> 4; ASSERT(uRank < 8); bb &= BBPRECEEDING_RANKS[uRank][uColor]; #ifdef DEBUG if (bb != 0) { while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&dbb))) { ASSERT(pos->rgSquare[cSquare].pPiece); ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece)); if ((FILE(cSquare) == (FILE(c) - 1)) || (FILE(cSquare) == (FILE(c) + 1))) { switch(uColor) { case WHITE: if (RANK(cSquare) > RANK(c)) { return(FALSE); } break; case BLACK: if (RANK(cSquare) < RANK(c)) { return(FALSE); } break; } } } ASSERT(FALSE); } else { ASSERT(bb == 0); while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&dbb))) { ASSERT(pos->rgSquare[cSquare].pPiece); ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece)); if ((FILE(cSquare) == (FILE(c) - 1)) || (FILE(cSquare) == (FILE(c) + 1))) { switch(uColor) { case WHITE: if (RANK(cSquare) > RANK(c)) { ASSERT(FALSE); } break; case BLACK: if (RANK(cSquare) < RANK(c)) { ASSERT(FALSE); } break; } } } } #endif return(bb == 0); } static ULONG _WhoControlsSquareFast(IN POSITION *pos, IN COOR c) /** Routine description: Determine which side controls a board square. Parameters: POSITION *pos : the board COOR c : the square in question Return value: static ULONG : (ULONG)-1 if neither side controls it or the sides control it evenly, WHITE if white controls the square, or BLACK if black controls the square. TODO: fix this to use counts / xrays **/ { // // board_representation/EVAL.md section 9: bvAttacks/ATTACK_BITV // retired entirely now that king (the last piece to write it) has // converted -- every side's presence at this square is read // straight off its own bbXAttacks accumulator instead. Bit // positions (PAWN_BIT/MINOR_BIT/ROOK_BIT/QUEEN_BIT/KING_BIT) are // unchanged so g_SwapTable's indexing below still sees the same // shape it always has; x-ray-only presence (bbMinorXrayAttacks/ // bbRookXrayAttacks/bbQueenXrayAttacks) ORs into the same // byte-scale bit as its direct-attack counterpart, matching the // old struct's .uSmall/.uXray sharing one bit position for // "attacks or x-rays" (see g_SwapTable's own construction). King // has no x-ray (can't move through a blocker). This function is // only ever called after *both* kings finish evaluating (the // passed-pawn re-check and trapped-piece/danger passes all run // after Eval()'s king-eval block), so pos->bbKingAttacks is always // fully populated for both colors here -- unlike _EvalKing's own, // deliberately asymmetric internal bvAttack (see that function's // comment), this is a plain, symmetric fact query. // BITBOARD sq = COOR_TO_BB(c); ULONG uWhite = ((pos->bbPawnAttacks[WHITE] & sq) ? PAWN_BIT : 0) | ((pos->bbMinorAttacks[WHITE] & sq) ? MINOR_BIT : 0) | ((pos->bbMinorXrayAttacks[WHITE] & sq) ? MINOR_BIT : 0) | ((pos->bbRookAttacks[WHITE] & sq) ? ROOK_BIT : 0) | ((pos->bbRookXrayAttacks[WHITE] & sq) ? ROOK_BIT : 0) | ((pos->bbQueenAttacks[WHITE] & sq) ? QUEEN_BIT : 0) | ((pos->bbQueenXrayAttacks[WHITE] & sq) ? QUEEN_BIT : 0) | ((pos->bbKingAttacks[WHITE] & sq) ? KING_BIT : 0); ULONG uBlack = ((pos->bbPawnAttacks[BLACK] & sq) ? PAWN_BIT : 0) | ((pos->bbMinorAttacks[BLACK] & sq) ? MINOR_BIT : 0) | ((pos->bbMinorXrayAttacks[BLACK] & sq) ? MINOR_BIT : 0) | ((pos->bbRookAttacks[BLACK] & sq) ? ROOK_BIT : 0) | ((pos->bbRookXrayAttacks[BLACK] & sq) ? ROOK_BIT : 0) | ((pos->bbQueenAttacks[BLACK] & sq) ? QUEEN_BIT : 0) | ((pos->bbQueenXrayAttacks[BLACK] & sq) ? QUEEN_BIT : 0) | ((pos->bbKingAttacks[BLACK] & sq) ? KING_BIT : 0); ULONG u; PIECE p; CHAR ch; ASSERT((uWhite & 0xFFFFFF00) == 0); ASSERT((uBlack & 0xFFFFFF00) == 0); // TODO: keep these and update the table to use them uWhite >>= 3; uBlack >>= 3; p = pos->rgSquare[c].pPiece; // p -= 2; ch = g_SwapTable[p][uWhite][uBlack]; if (ch != 0) { u = ch; u >>= 7; u = u & 1; ASSERT(((u == 1) && (ch < 0)) || ((u == 0) && (ch > 0))); return(FLIP(u)); } return((ULONG)-1); } /** Routine description: Zero out the Eval()-scoped attack-bitboard accumulators before building them for this call. Used to clear the old per-square rgSquare[c|8].bvAttacks/ATTACK_BITV structure too (a full-board macro-unrolled loop, since every square's storage needed zeroing); retired along with that structure (2026-09-05, board_ representation/EVAL.md section 9) -- nothing left to clear but the bbXAttacks accumulators themselves. Parameters: POSITION *pos Return value: void **/ static void _ClearAttackTables(IN OUT POSITION *pos) { pos->bbMinorAttacks[WHITE] = pos->bbMinorAttacks[BLACK] = 0; pos->bbMinorXrayAttacks[WHITE] = pos->bbMinorXrayAttacks[BLACK] = 0; pos->bbRookAttacks[WHITE] = pos->bbRookAttacks[BLACK] = 0; pos->bbRookXrayAttacks[WHITE] = pos->bbRookXrayAttacks[BLACK] = 0; pos->bbQueenAttacks[WHITE] = pos->bbQueenAttacks[BLACK] = 0; pos->bbQueenXrayAttacks[WHITE] = pos->bbQueenXrayAttacks[BLACK] = 0; pos->bbKingAttacks[WHITE] = pos->bbKingAttacks[BLACK] = 0; } static INLINE void _InitializePawnHashEntry(IN OUT PAWN_HASH_ENTRY *pHash, IN POSITION *pos) /** Routine description: Parameters: PAWN_HASH_ENTRY *pHash, POSITION *pos Return value: static INLINE void **/ { memset(pHash, 0, sizeof(PAWN_HASH_ENTRY)); pHash->u64Key = pos->u64PawnSig; } static INLINE void _EvaluateCandidatePasser(IN POSITION *pos, IN OUT PAWN_HASH_ENTRY *pHash, IN COOR c) /** Routine description: Parameters: POSITION *pos, PAWN_HASH_ENTRY *pHash, COOR c Return value: static INLINE **/ { COOR c1, cSquare; PIECE pSentry, pHelper; ULONG uPawnFile = FILE(c) + 1; BITBOARD bb; ULONG uSentries, uHelpers; ULONG uColor; int d1; #ifdef DEBUG COOR cVerifySquare; ULONG uVerifySentries; #endif ASSERT(IS_ON_BOARD(c)); ASSERT(RANK(c) >= 2); ASSERT(RANK(c) <= 7); pHelper = pos->rgSquare[c].pPiece; ASSERT(IS_PAWN(pHelper)); uColor = GET_COLOR(pHelper); ASSERT(IS_VALID_COLOR(uColor)); if (pHash->uCountPerFile[FLIP(uColor)][uPawnFile] != 0) { ASSERT((pos->bbPawns[FLIP(uColor)] & BBFILE[FILE(c)]) != 0); // // The only way a pawn can be a passer/candidate if the other // side has a pawn on its same file is if that enemy pawn is // behind it. This is a corner case but it is important to // detect all passers. // switch(uColor) { case WHITE: bb = pos->bbPawns[BLACK] & BBFILE[FILE(c)]; ASSERT(bb); while(IS_ON_BOARD(c1 = CoorFromBitBoardRank8ToRank1(&bb))) { ASSERT(FILE(c1) == FILE(c)); if (c1 < c) return; } break; case BLACK: bb = pos->bbPawns[WHITE] & BBFILE[FILE(c)]; ASSERT(bb); while(IS_ON_BOARD(c1 = CoorFromBitBoardRank1ToRank8(&bb))) { ASSERT(FILE(c1) == FILE(c)); if (c1 > c) return; } break; } } // // Count FLIP(uColor)'s sentries and determine the location of the // critical square. Note if there are no sentries then this // pawn (on square c) is a passer already, not a candidate. // bb = pos->bbPawns[FLIP(uColor)] & BBADJACENT_FILES[FILE(c)]; bb &= BBPRECEEDING_RANKS[(c & 0x70) >> 4][uColor]; if (!bb) { ASSERT(CountBits(bb) == 0); // // There are no sentries so this pawn is a passer. // pHash->bbPasserLocations[uColor] |= COOR_TO_BB(c); ASSERT(CountBits(pHash->bbPasserLocations[uColor]) > 0); ASSERT(CountBits(pHash->bbPasserLocations[uColor]) <= 8); EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], PASSER_BY_RANK[uColor][RANK(c)], "passed pawn"); // // However, don't give doubled passers such a big bonus. // if (IS_PAWN(pos->rgSquare[c - 16 * g_iAhead[uColor]].pPiece)) { EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], -(PASSER_BY_RANK[uColor][RANK(c)] / 2), "doubled passer"); } return; } uSentries = CountBits(bb); // // There's one or more sentry pawns so we'll look for helpers to // decide if this pawn is a candidate passer. // if (uColor == WHITE) { pSentry = BLACK_PAWN; cSquare = CoorFromBitBoardRank1ToRank8(&bb); cSquare += 0x10; } else { pSentry = WHITE_PAWN; cSquare = CoorFromBitBoardRank8ToRank1(&bb); cSquare -= 0x10; } cSquare &= 0xF0; cSquare |= FILE(c); ASSERT(IS_ON_BOARD(cSquare)); ASSERT(FILE(cSquare) == FILE(c)); #ifdef DEBUG uVerifySentries = 0; cVerifySquare = 0; d1 = 16 * g_iAhead[uColor]; c1 = c + d1; do { if (IS_ON_BOARD(c1 - 1)) { if (pos->rgSquare[c1 - 1].pPiece == pSentry) { uVerifySentries++; if (0 == cVerifySquare) { cVerifySquare = c1 - d1; ASSERT(cVerifySquare == cSquare); } } } if (IS_ON_BOARD(c1 + 1)) { if (pos->rgSquare[c1 + 1].pPiece == pSentry) { uVerifySentries++; if (0 == cVerifySquare) { cVerifySquare = c1 - d1; ASSERT(cVerifySquare == cSquare); } } } c1 = c1 + d1; } while(!RANK8(c1) && !RANK1(c1)); ASSERT(uVerifySentries == uSentries); #endif // // Note: we don't do this with bitboards because we want to not // consider a pawn to be a potential passer if it can't safely // advance to get into helper position. // // IDEA: scale the candidate passer bonus based on rank AND on // the distance the helper(s) have to go to get into position. // // KNOWN BUG, found 2026-09-05 while removing rgSquare[c|8].bvAttacks // entirely (board_representation/EVAL.md section 9), NOT fixed // here -- flagged for its own separate investigation instead of // being bundled into a mechanical cleanup commit. This function // runs from _EvalPawns, which is the *first* piece type evaluated // each Eval() call -- every non-pawn piece (and, since commit // 57502d6, pawns themselves) writes its attack bits later in the // same call, so the "is c1 safe to advance a helper pawn into" // check below has read an always-zero attack table for as long as // bvAttacks has existed in its post-57502d6 form. The condition // this used to gate on (skip a candidate passer if its helper // square isn't actually safe to advance into) has therefore been // unconditionally true -- a silent no-op -- since that commit, // not something introduced by today's cleanup. Preserved exactly // as that already-dead behavior (unconditional) rather than // "fixed" here, since a real fix changes eval scoring and deserves // its own before/after check, not one buried in a rename commit. // uHelpers = 0; d1 = 16 * g_iAhead[uColor]; ASSERT(-d1 == 16 * g_iBehind[uColor]); c1 = cSquare + 1 - d1; if ((IS_ON_BOARD(c1)) && (pHash->uCountPerFile[uColor][FILE(c1) + 1])) { ASSERT(pos->bbPawns[uColor] & BBFILE[FILE(c1)]); // // The square c1 the place a helper pawn must get to in // order to aide the candidate past a sentry. // if (pos->rgSquare[c1].pPiece == pHelper) { uHelpers = 1; goto do_left; } // // There is no helper pawn in the support position yet. // See if one can get there. // c1 = c1 - d1; while (IS_ON_BOARD(c1)) { if (pos->rgSquare[c1].pPiece == pHelper) { uHelpers = 1; break; } else if (pos->rgSquare[c1].pPiece == pSentry) { break; } c1 = c1 - d1; } } do_left: c1 = cSquare - 1 - d1; if ((IS_ON_BOARD(c1)) && (pHash->uCountPerFile[uColor][FILE(c1) + 1])) { ASSERT(pos->bbPawns[uColor] & BBFILE[FILE(c1)]); // // The square c1 is the place a helper pawn must get to in // order to aide the candidate. // if (pos->rgSquare[c1].pPiece == pHelper) { uHelpers++; goto done_helpers; } // // There is no pawn in the left support position yet. See // if one can get there. // c1 -= d1; while (IS_ON_BOARD(c1)) { if (pos->rgSquare[c1].pPiece == pHelper) { uHelpers++; break; } else if (pos->rgSquare[c1].pPiece == pSentry) { break; } c1 -= d1; } } done_helpers: if ((uHelpers >= uSentries) || ((pHash->uCountPerFile[uColor][uPawnFile + 1] + pHash->uCountPerFile[uColor][uPawnFile - 1]) && (((WHITE == uColor) && (RANK(c) > 5)) || ((BLACK == uColor) && (RANK(c) < 4))))) { // Tuning can (and has) flipped this sign; not a real invariant. //ASSERT(CANDIDATE_PASSER_BY_RANK[uColor][RANK(c)] > 0); EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], CANDIDATE_PASSER_BY_RANK[uColor][RANK(c)], "candidate passer"); // // If the other side has no pieces then give this candidate an // extra bonus -- used to be a second full copy of the same // CANDIDATE_PASSER_BY_RANK value (an exact duplicate of the // term just added above, not a different angle on it), now a // fractional modifier. // if (pos->uNonPawnCount[FLIP(uColor)][0] == 1) { EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], CANDIDATE_PASSER_BY_RANK[uColor][RANK(c)] / 2, "candidate passer in endgame"); } } } static void _EvaluateConnectedSupportedOutsidePassers(IN POSITION *pos, IN OUT PAWN_HASH_ENTRY *pHash) /** Routine description: Parameters: POSITION *pos, PAWN_HASH_ENTRY *pHash Return value: void **/ { ULONG uColor; ULONG u, v; COOR c; COOR cLeftmostPasser, cRightmostPasser; PIECE pFriend; COOR cSupport; BITBOARD bb; static const INT iDelta[5] = { -1, +1, +17, +15, 0 }; FOREACH_COLOR(uColor) { ASSERT(IS_VALID_COLOR(uColor)); if (pHash->bbPasserLocations[uColor]) { ASSERT(pos->uPawnCount[uColor] > 0); pFriend = BLACK_PAWN | uColor; cLeftmostPasser = cRightmostPasser = ILLEGAL_COOR; for (u = A; u <= H; u++) { bb = pHash->bbPasserLocations[uColor] & BBFILE[u]; if (bb != 0) { ASSERT(pHash->uCountPerFile[uColor][u+1] != 0); while(IS_ON_BOARD(c = CoorFromBitBoardRank8ToRank1(&bb))) { // // Keep track of leftmost/rightmost passer for // outside passer code later on. // ASSERT(FILE(c) == u); ASSERT(RANK(c) > 1); ASSERT(RANK(c) < 8); if (cLeftmostPasser == ILLEGAL_COOR) { cLeftmostPasser = c; } cRightmostPasser = c; v = 0; while(iDelta[v] != 0) { cSupport = c + iDelta[v] * g_iBehind[uColor]; if (IS_ON_BOARD(cSupport)) { if (pHash->bbPasserLocations[uColor] & COOR_TO_BB(cSupport)) { ASSERT(CONNECTED_PASSERS_BY_RANK[uColor] [RANK(c)] > 0); EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], CONNECTED_PASSERS_BY_RANK[uColor] [RANK(c)], "connected passers"); // // TODO: connected passers vs a R is // strong. // } else if (pos->rgSquare[cSupport].pPiece == pFriend) { ASSERT(SUPPORTED_PASSER_BY_RANK[uColor] [RANK(c)] > 0); EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], SUPPORTED_PASSER_BY_RANK[uColor] [RANK(c)], "supported passer"); } } v++; } } } } #ifdef DEBUG ASSERT(IS_ON_BOARD(cLeftmostPasser)); ASSERT(IS_ON_BOARD(cRightmostPasser)); ASSERT(RANK(cLeftmostPasser) > 1); ASSERT(RANK(cRightmostPasser) > 1); ASSERT(RANK(cLeftmostPasser) < 8); ASSERT(RANK(cRightmostPasser) < 8); if (CountBits(pHash->bbPasserLocations[uColor]) == 1) { ASSERT(cLeftmostPasser == cRightmostPasser); } #endif for (u = A; u <= D; u++) { if (pHash->uCountPerFile[FLIP(uColor)][u + 1] != 0) { ASSERT(pos->bbPawns[FLIP(uColor)] & BBFILE[u]); if (!(pHash->bbPasserLocations[FLIP(uColor)] & BBFILE[u])) { break; } } } if (u > FILE(cLeftmostPasser)) { EVAL_TERM(uColor, PAWN, ILLEGAL_COOR, pHash->iScore[uColor], OUTSIDE_PASSER_BY_DISTANCE[u-FILE(cLeftmostPasser)], "left outside passer"); } for (u = H; u >= E; u--) { if (pHash->uCountPerFile[FLIP(uColor)][u + 1] != 0) { ASSERT(pos->bbPawns[FLIP(uColor)] & BBFILE[u]); if (!(pHash->bbPasserLocations[FLIP(uColor)] & BBFILE[u])) { break; } } } if (u < FILE(cRightmostPasser)) { EVAL_TERM(uColor, PAWN, ILLEGAL_COOR, pHash->iScore[uColor], OUTSIDE_PASSER_BY_DISTANCE[FILE(cRightmostPasser)-u], "right outside passer"); } } } } // board_representation/EVAL.md section 2/0d: bitboard replacement for // the old per-pawn mailbox delta+IS_ON_BOARD population this function // used to do. pos->bbPawns[2] already has every pawn location // (incrementally maintained, chess.h) with zero per-pawn iteration // needed to query it -- the exact shift-and-mask technique // generate.c's _GenerateAllPawnMovesBB already uses to bulk-generate a // whole side's pawn captures (its bbCapLeft/bbCapRight, before being // masked down to actual enemy-occupied squares) is exactly "every // square this side's pawns attack" -- reused here verbatim, minus the // enemy-occupancy mask, since attack-bit population doesn't care what // (if anything) sits on the attacked square. See that function's // header comment for the full square-numbering/shift-direction // derivation (WHITE forward = bb >> 8, BLACK forward = bb << 8, // diagonals need the *opposite* file excluded to prevent same-row // wraparound) -- not repeated here. // // Unlike the old version, this does NOT write PAWN_BIT into the old // rgSquare[c|8].bvAttacks/ATTACK_BITV mechanism (retired entirely as // of 2026-09-05, once king -- the last piece writing it -- converted // too) -- pos->bbPawnAttacks[2] (chess.h) is the single source of // truth for "does a pawn attack this square", read directly by every // consumer. _ClearAttackTables(pos) still needs to run here first, to // zero the other bbXAttacks accumulators knight/bishop/rook/queen/king // populate as they each evaluate. static void _PopulatePawnAttackBits(IN OUT POSITION *pos) /** Routine description: Populate the attack table with pawn bits. Parameters: POSITION *pos : the board Return value: void **/ { _ClearAttackTables(pos); 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); } static PAWN_HASH_ENTRY * _EvalPawns(IN OUT SEARCHER_THREAD_CONTEXT *ctx, OUT FLAG *pfDeferred) /** Routine description: Evaluate pawn structures; return a ptr to a pawn hash entry. Parameters: SEARCHER_THREAD_CONTEXT *ctx : the searcher thread context Return value: PAWN_HASH_ENTRY * **/ { static ULONG uUnmovedRank[2] = { 0x10, 0x60 }; POSITION *pos = &ctx->sPosition; PAWN_HASH_ENTRY *pHash; COOR c, cSquare; PIECE p; ULONG uIsolated[2]; ULONG uDoubled[2]; SCORE iDuos[2]; ULONG u, uPawnFile; ULONG uColor; ULONG uUnsupportable; BITBOARD bb; int d1; #ifdef DEBUG SCORE t; #endif // // Now, look up the hash entry for this position. // INC(ctx->sCounters.pawnhash.u64Probes); pHash = PawnHashLookup(ctx); ASSERT(NULL != pHash); if (pHash->u64Key == pos->u64PawnSig) { *pfDeferred = TRUE; INC(ctx->sCounters.pawnhash.u64Hits); return(pHash); } // // We need the attack table to evaluate the pawns... and we missed // the hash table so we have to populate it now. Can't defer this // one. :( // *pfDeferred = FALSE; _InitializePawnHashEntry(pHash, pos); _PopulatePawnAttackBits(pos); uIsolated[BLACK] = uIsolated[WHITE] = 0; uDoubled[BLACK] = uDoubled[WHITE] = 0; iDuos[BLACK] = iDuos[WHITE] = 0; // // First pass // FOREACH_COLOR(uColor) { ASSERT(IS_VALID_COLOR(uColor)); ASSERT(pos->uPawnCount[uColor] <= 8); d1 = 16 * g_iAhead[uColor]; for (u = 0; u < pos->uPawnCount[uColor]; u++) { c = pos->cPawns[uColor][u]; ASSERT(IS_ON_BOARD(c)); ASSERT(pos->rgSquare[c].pPiece); ASSERT(IS_PAWN(pos->rgSquare[c].pPiece)); uPawnFile = FILE(c) + 1; ASSERT((1 <= uPawnFile) && (uPawnFile <= 8)); // // Update files counter // ASSERT(pHash->uCountPerFile[uColor][uPawnFile] < 6); pHash->uCountPerFile[uColor][uPawnFile]++; // // pos->bbPawns[uColor] is plain POSITION state, maintained // incrementally by move.c on every pawn move -- already // has this pawn's bit set by the time we get here, nothing // to build. (No running-count cross-check against // uCountPerFile here: unlike the old bit-by-bit // pHash->bbPawnLocations this replaces, bbPawns already // holds every pawn on the board up front, not just the // ones this loop has visited so far, so a per-iteration // "counts so far agree" comparison isn't meaningful // against it -- only a post-loop total would be.) // ASSERT(pos->bbPawns[uColor] & COOR_TO_BB(c)); // // Count unmoved pawns // pHash->uNumUnmovedPawns[uColor] += ((c & 0xF0) == uUnmovedRank[uColor]); ASSERT(pHash->uNumUnmovedPawns[uColor] <= 8); // // Detect rammed and other stationary pawns. // cSquare = c + d1; ASSERT(IS_ON_BOARD(cSquare)); p = pos->rgSquare[cSquare].pPiece; if (IS_PAWN(p)) { pHash->bbStationaryPawns[uColor] |= COOR_TO_BB(c); ASSERT(CountBits(pHash->bbStationaryPawns[uColor]) <= 8); pHash->uNumRammedPawns += OPPOSITE_COLORS(p, uColor); ASSERT(pHash->uNumRammedPawns <= 16); } // // Give central pawns a small bonus; penalize rook pawns: // EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], PAWN_CENTRALITY_BONUS[c], "centrality"); } } // // We counted rammed pawns twice (once for the black pawn and once // for the white one). Fix this now. // ASSERT(!(pHash->uNumRammedPawns & 1)); pHash->uNumRammedPawns /= 2; // // Second pass // FOREACH_COLOR(uColor) { ASSERT(IS_VALID_COLOR(uColor)); ASSERT(pos->uPawnCount[uColor] <= 8); ASSERT(CountBits(pos->bbPawns[uColor]) <= 8); d1 = 16 * g_iAhead[uColor]; for (u = 0; u < pos->uPawnCount[uColor]; u++) { c = pos->cPawns[uColor][u]; ASSERT(IS_ON_BOARD(c)); ASSERT(IS_PAWN(pos->rgSquare[c].pPiece)); ASSERT((1 < RANK(c)) && (RANK(c) < 8)); // // Find weak pawns... pawns that are isolated are weak as // are pawns that have advanced beyond their support. // Both of these can tie up one or more pieces in order to // defend them. // uUnsupportable = 0; uPawnFile = FILE(c) - 1; if (pHash->uCountPerFile[uColor][uPawnFile + 1] > 0) { bb = (pos->bbPawns[uColor] & BBFILE[uPawnFile] & BBADJACENT_RANKS[RANK(c)]); if (!bb) { bb = (pos->bbPawns[FLIP(uColor)] & BBFILE[uPawnFile]); while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb))) { ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece)); if (uColor == WHITE) { if ((cSquare & 0xF0) >= (c & 0xF0)) { ASSERT(RANK(cSquare) <= RANK(c)); uUnsupportable = 1; break; } } else { ASSERT(uColor == BLACK); if ((cSquare & 0xF0) <= (c & 0xF0)) { ASSERT(RANK(cSquare) >= RANK(c)); uUnsupportable = 1; break; } } } } } else { uUnsupportable = 1; // no friend pawn on that file } if (0 == uUnsupportable) { goto fast_skip; } ASSERT(1 == uUnsupportable); uPawnFile = FILE(c) + 1; if (pHash->uCountPerFile[uColor][uPawnFile + 1] > 0) { bb = (pos->bbPawns[uColor] & BBFILE[uPawnFile] & BBADJACENT_RANKS[RANK(c)]); if (!bb) { bb = (pos->bbPawns[FLIP(uColor)] & BBFILE[uPawnFile]); while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb))) { ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece)); if (uColor == WHITE) { if ((cSquare & 0xF0) >= (c & 0xF0)) { ASSERT(RANK(cSquare) <= RANK(c)); uUnsupportable = 2; break; } } else { ASSERT(uColor == BLACK); if ((cSquare & 0xF0) <= (c & 0xF0)) { ASSERT(RANK(cSquare) >= RANK(c)); uUnsupportable = 2; break; } } } } } else { uUnsupportable = 2; } // // If this pawn is not supportable from either side then // it is either isolated or has been pushed too far ahead // of its support. Either way it's a target -- penalize // it. // if (2 == uUnsupportable) { // // Single consolidated isolated-pawn penalty: base // severity by file, plus modifiers if the pawn is also // exposed and/or doubled. These used to be three // separate additive EVAL_TERM calls that all fired for // the same pawn, plus a fourth, whole-position // "exponential isolated" aggregate below that // double-counted the same uIsolated[] count this loop // already prices in one pawn at a time -- that // aggregate is removed entirely below rather than just // consolidated (see the comment there). // uPawnFile = FILE(c) + 1; uIsolated[uColor] += 1; ASSERT(uIsolated[uColor] > 0); ASSERT(uIsolated[uColor] <= 8); EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], (ISOLATED_PAWN_BY_PAWNFILE[uPawnFile] + ((pHash->uCountPerFile[FLIP(uColor)][uPawnFile]==0) * ISOLATED_EXPOSED_PAWN) + ((pHash->uCountPerFile[uColor][uPawnFile] > 1) * ISOLATED_DOUBLED_PAWN)), "isolated pawn"); } fast_skip: uPawnFile = FILE(c) + 1; ASSERT((1 <= uPawnFile) && (uPawnFile <= 8)); ASSERT(CountBits(pos->bbPawns[uColor] & BBFILE[FILE(c)]) == pHash->uCountPerFile[uColor][uPawnFile]); // // Keep count of doubled pawns // uDoubled[uColor] += (pHash->uCountPerFile[uColor][uPawnFile] > 1); ASSERT((0 <= uDoubled[uColor]) && (uDoubled[uColor] <= 8)); // // We can detect pawn duos (triads, etc...) and backward pawns // by considering the control of the pawn's stopsquare. // cSquare = c + d1; ASSERT(IS_ON_BOARD(cSquare)); // // At this point in Eval()'s sequence (inside _EvalPawns, // before any other piece type has run this call), pawns // are the only thing that could possibly have marked an // attack -- bvAttacks itself no longer carries the pawn // bit at all (see _PopulatePawnAttackBits), so this reads // pos->bbPawnAttacks directly instead of the (permanently // zero, at this point in execution) bvAttacks word. // if (pos->bbPawnAttacks[uColor] & COOR_TO_BB(cSquare)) { // // Count pawn duos. See "Pawn Power in Chess" pp 10-16 // #ifdef DEBUG t = ((uColor * RANK(cSquare)) + (FLIP(uColor) * (9 - RANK(cSquare)))); if (uColor == WHITE) { ASSERT(t == RANK(cSquare)); } else { ASSERT(uColor == BLACK); ASSERT(t == (9 - RANK(cSquare))); } #endif iDuos[uColor] += ((uColor * RANK(cSquare)) + (FLIP(uColor) * (9 - RANK(cSquare)))); ASSERT(iDuos[uColor] <= (7 * 8)); } else if (pos->bbPawnAttacks[FLIP(uColor)] & COOR_TO_BB(cSquare)) { // // Detect backwards pawns. See "Pawn Power in Chess" pp 25-27 // if (!IS_PAWN(pos->rgSquare[cSquare].pPiece)) { p = (BLACK_PAWN | uColor); if (((WHITE == uColor) && (RANK(c) < 4)) || ((BLACK == uColor) && (RANK(c) > 5))) { if ((IS_ON_BOARD(cSquare - 1) && pos->rgSquare[cSquare - 1].pPiece == p) || (IS_ON_BOARD(cSquare + 1) && pos->rgSquare[cSquare + 1].pPiece == p)) { // // The pawn at c is backward. Determine // whether it is shielded or exposed. // pHash->bbStationaryPawns[uColor] |= COOR_TO_BB(c); ASSERT(CountBits(pHash->bbStationaryPawns[uColor]) <= 8); if (pHash->uCountPerFile[FLIP(uColor)][uPawnFile]) { // Tuning can (and has) flipped this sign; not a real invariant. //ASSERT(BACKWARD_SHIELDED_BY_LOCATION[c] < 0); EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], BACKWARD_SHIELDED_BY_LOCATION[c], "shielded backward pawn"); } else { //ASSERT(BACKWARD_EXPOSED_BY_LOCATION[c] < 0); EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], BACKWARD_EXPOSED_BY_LOCATION[c], "exposed backward pawn"); } } } } // // TODO: Think about backward doubled pawns; read Kauffman's // paper "All About Doubled Pawns". // } // // Handle passers / candidate passers // _EvaluateCandidatePasser(pos, pHash, c); } } // // Reward pawn duos, see "Pawn Power in Chess" pp. 10-16 // ASSERT(iDuos[WHITE] >= 0); ASSERT(iDuos[WHITE] <= 56); ASSERT(iDuos[BLACK] >= 0); ASSERT(iDuos[BLACK] <= 56); EVAL_TERM(WHITE, PAWN, ILLEGAL_COOR, pHash->iScore[WHITE], iDuos[WHITE] / 2, "pawn duos"); EVAL_TERM(BLACK, PAWN, ILLEGAL_COOR, pHash->iScore[BLACK], iDuos[BLACK] / 2, "pawn duos"); // // The penalty for doubled pawns is scaled based on two primary // factors: the presence of major pieces for the side with doubled // pawns and the number of doubled pawns on the board. The // presence of major pieces makes the doubled pawns less severe // while the presence of many doubled pawns makes the penalty for // each more severe. // ASSERT((uDoubled[WHITE] >= 0) && (uDoubled[WHITE] <= 8)); ASSERT((uDoubled[BLACK] >= 0) && (uDoubled[BLACK] <= 8)); u = pos->uNonPawnCount[BLACK][ROOK] + pos->uNonPawnCount[BLACK][QUEEN] * 2; u = MINU(u, 3); EVAL_TERM(BLACK, PAWN, ILLEGAL_COOR, pHash->iScore[BLACK], DOUBLED_PAWN_PENALTY_BY_COUNT[u][uDoubled[BLACK]], "exponential doubled"); u = pos->uNonPawnCount[WHITE][ROOK] + pos->uNonPawnCount[WHITE][QUEEN] * 2; u = MINU(u, 3); EVAL_TERM(WHITE, PAWN, ILLEGAL_COOR, pHash->iScore[WHITE], DOUBLED_PAWN_PENALTY_BY_COUNT[u][uDoubled[WHITE]], "exponential doubled"); ASSERT(uIsolated[WHITE] >= 0); ASSERT(uIsolated[WHITE] <= 8); ASSERT(uIsolated[BLACK] >= 0); ASSERT(uIsolated[BLACK] <= 8); // // Removed: a redundant "exponential isolated" whole-position term // keyed by uIsolated[color] used to be added here on top of the // per-pawn "isolated pawn" term above, which already sums once per // isolated pawn and so already scales with the same count. Having // both double-counted the same structural fact. // // // Look for connected, supported and outside passed pawns to give // extra bonuses. // _EvaluateConnectedSupportedOutsidePassers(pos, pHash); // // TODO: recognize quartgrips and stonewalls // return(pHash); } ULONG CountKingSafetyDefects(IN OUT POSITION *pos, IN ULONG uSide) /** Routine description: Determine how many defects uSide's king position has _quickly_. TODO: add more knowledge as cheaply as possible... Parameters: POSITION *pos, ULONG uSide Return value: ULONG **/ { ULONG uCounter = 0; ULONG xSide = FLIP(uSide); COOR cKing; COOR c; int i; PIECE p; ULONG u; // // Don't count king safety defects if the real eval code in // _EvalKing would not... // if (pos->uNonPawnMaterial[xSide] < DO_KING_SAFETY_THRESHOLD) { return 0; } cKing = pos->cNonPawns[uSide][0]; uCounter = KING_INITIAL_COUNTER_BY_LOCATION[uSide][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); // // Make sure cKing - 1, cKing and cKing + 1 are on the board // cKing += (!IS_ON_BOARD(cKing - 1)); cKing -= (!IS_ON_BOARD(cKing + 1)); ASSERT(IS_ON_BOARD(cKing)); ASSERT(IS_ON_BOARD(cKing + 1)); ASSERT(IS_ON_BOARD(cKing - 1)); // // Consider all enemy pieces except the king (not including pawns) // for (u = 1; u < pos->uNonPawnCount[xSide][0]; u++) { c = pos->cNonPawns[xSide][u]; ASSERT(IS_ON_BOARD(c)); i = (int)c - (int)(cKing + 1); ASSERT((i >= -128) && (i <= 125)); p = pos->rgSquare[c].pPiece; ASSERT(pos->rgSquare[c].uIndex == u); ASSERT(!IS_KING(p)); ASSERT(GET_COLOR(p) == xSide); p = 1 << PIECE_TYPE(p); uCounter += (int)(i == 0) | (int)(i == -2) | (int)((CHECK_VECTOR_WITH_INDEX(i, xSide) & p) != 0) | (int)((CHECK_VECTOR_WITH_INDEX(i + 1, xSide) & p) != 0) | (int)((CHECK_VECTOR_WITH_INDEX(i + 2, xSide) & p) != 0); } ASSERT(uCounter < 15); // Save the number of enemy pieces pointing at this king for later use. pos->uPiecesPointingAtKing[uSide] = (uCounter - (KING_INITIAL_COUNTER_BY_LOCATION[uSide][cKing] >> 1)); return uCounter; } static void EstimatePositionalScore(IN POSITION *pos, IN UNUSED PAWN_HASH_ENTRY *pHash, IN OUT SCORE *piAlphaMargin, IN OUT SCORE *piBetaMargin) /** Routine description: Before doing an early lazy eval, look at the position and widen the alpha/beta margins to account for the positional terms that have not been computed yet at this point in Eval() -- king safety plus a flat residual covering mobility, passers, and everything else that genuinely requires attack-generation to know exactly. Both terms below are calibrated from measured data (CALIBRATE_ POSITIONAL instrumentation, ~1.6M full-eval samples over an ECM slice), not guessed: for each value, p90 of the *actual* score swing between this point in Eval() and full-eval completion, so the resulting margin is wrong (too narrow) on at most ~10% of calls, in either bucket. See conversation history for the percentile tables -- if re-tuning, regenerate them, don't hand- edit these numbers. Note: king safety and "everything else" turned out to be close to independent of piece count and of each other, so summing their two p90s (rather than deriving one joint p90) is a deliberately conservative (wider than strictly necessary) combination. O(1) -- no attack bitboard generation -- so it's safe to call whenever the cheap material-only lazy check (the caller's first-pass margin) wasn't enough to resolve the cutoff on its own. Parameters: POSITION *pos PAWN_HASH_ENTRY *pHash : unused now (kept for call-site symmetry); pHash->iScore is already folded into pos->iScore by this point, and the passer-specific estimate this used to compute turned out to be negligible next to the residual term below. SCORE *piAlphaMargin, *piBetaMargin : widened in place, identically (no measured basis for an asymmetric alpha/beta split) Return value: void **/ { // p90 of |true king-safety swing|, indexed by combined defect // count (CountKingSafetyDefects(stm) + CountKingSafetyDefects(xsm)), // clamped above index 10 (sparse data beyond that). static const SCORE iKingSwingP90[11] = { 47, 62, 87, 119, 169, 157, 181, 282, 342, 342, 385 }; // p90 of |mobility + passers + everything else combined|, measured // directly (no useful correlation found with piece count). static const SCORE iResidualP90 = 154; ULONG uDefects = (CountKingSafetyDefects(pos, WHITE) + CountKingSafetyDefects(pos, BLACK)); SCORE iKingTerm = iKingSwingP90[MINU(10, uDefects)]; *piAlphaMargin += iKingTerm + iResidualP90; *piBetaMargin += iKingTerm + iResidualP90; } // // ====================================================================== // static void _RecordTrappedCandidate(IN OUT POSITION *pos, IN ULONG uColor, IN COOR c) /** 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. Parameters: POSITION *pos, ULONG uColor, COOR c Return value: void **/ { if (pos->uNumTrapped[uColor] < ARRAY_LENGTH(pos->cTrapped[uColor])) { pos->cTrapped[uColor][pos->uNumTrapped[uColor]++] = c; } } static void _EvalBishop(IN OUT POSITION *pos, IN COOR c, IN PAWN_HASH_ENTRY *pHash) /** Routine description: Parameters: POSITION *pos, COOR c, PAWN_HASH_ENTRY *pHash, Return value: FLAG **/ { static const BITBOARD bbColorSq[2] = { 0x55aa55aa55aa55aaULL, 0xaa55aa55aa55aa55ULL }; static const COOR cBishopAtHome[2][2] = { { C8, F8 }, // BLACK { C1, F1 } // WHITE }; ULONG uColor; BITBOARD bb; BITBOARD bbMask; BITBOARD bbPc; COOR cSquare; SCORE i; ULONG u; ULONG uTotalMobility; ULONG uMaxMobility; PIECE p; ASSERT(IS_ON_BOARD(c)); p = pos->rgSquare[c].pPiece; ASSERT(p && IS_BISHOP(p)); uColor = GET_COLOR(p); #ifdef DEBUG pos->cPiece = c; #endif // // Unmoved piece // pos->uMinorsAtHome[uColor] += (c == cBishopAtHome[uColor][0]); pos->uMinorsAtHome[uColor] += (c == cBishopAtHome[uColor][1]); ASSERT(pos->uMinorsAtHome[uColor] <= 4); // // Good and bad bishops // i = 16; bbMask = bbColorSq[IS_SQUARE_WHITE(c)]; bb = pHash->bbStationaryPawns[uColor] & bbMask; ASSERT(CountBits(bb) <= 16); while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb))) { // // Consider stationary (rammed or backward) pawns of the same // color as the bishop. // ASSERT(pos->rgSquare[cSquare].pPiece); ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece)); ASSERT(GET_COLOR(pos->rgSquare[cSquare].pPiece) == uColor); i += STATIONARY_PAWN_ON_BISHOP_COLOR[cSquare]; } EVAL_TERM(uColor, BISHOP, c, pos->iScore[uColor], i, "good/bad stationary pawns ++"); i = 0; bbPc = ~(pHash->bbStationaryPawns[WHITE] | pHash->bbStationaryPawns[BLACK]); bbPc &= bbMask; bb = pos->bbPawns[uColor] & bbPc; while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb))) { ASSERT(pos->rgSquare[cSquare].pPiece); ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece)); ASSERT(GET_COLOR(pos->rgSquare[cSquare].pPiece) == uColor); ASSERT(pos->bbPawns[uColor] & COOR_TO_BB(cSquare)); i += TRANSIENT_PAWN_ON_BISHOP_COLOR[cSquare]; } bb = pos->bbPawns[FLIP(uColor)] & 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)); i += ((pos->bbPawnAttacks[FLIP(uColor)] & COOR_TO_BB(cSquare)) != 0) * TRANSIENT_PAWN_ON_BISHOP_COLOR[cSquare] / 2; } EVAL_TERM(uColor, BISHOP, c, pos->iScore[uColor], i, "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 // generation) gives the whole ray-to-first-blocker attack set in // one shot -- no per-square delta walk, no mailbox read, no switch // dispatch. The old BMobCaseTable's seven cases collapse to: // - terminal enemy non-pawn (opposing minor/rook/queen/king): // counts unconditionally -- covers both BMOB_ENEMY_SAME (no // x-ray) and BMOB_ENEMY_GREATER (x-rays past too); the // x-ray/no-x-ray distinction only matters for attack-bit // population below, not the mobility count itself. // - empty or terminal enemy pawn: counts unless pawn-unsafe // (BMOB_EMPTY / BMOB_ENEMY_PAWN) -- both terminal-square // categories together are exactly "not friend-occupied and not // enemy-non-pawn-occupied," the same partition knight's // reduction already uses. // - terminal friendly, non-stationary pawn on this bishop's own // color complex (BMOB_FRIEND_PAWN): still counts, the one case // that isn't a pure occupancy mask -- bbPc (already computed // above for the good/bad transient-pawn scoring) is exactly // "non-stationary pawns of either color on this color complex"; // ANDing with pos->bbPawns[uColor] and the attack set isolates // just this bishop's own transient pawns. // - any other friendly piece (knight/rook/king): blocks, no // count, no x-ray -- simply excluded by the friendly-occupied // mask, nothing else needed. // 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 bbFriendBQ = pos->bbPieces[uColor][BISHOP] | pos->bbPieces[uColor][QUEEN]; BITBOARD bbUnsafeForMinor = pos->bbPawnAttacks[FLIP(uColor)]; BITBOARD bbExclude = 0; BITBOARD bbSeen = 0; BITBOARD bbLayer = bbAttack; BITBOARD bbMobility = 0; BITBOARD bbXrayAccum = 0; BITBOARD bbFirstLayerMask = 0; ULONG d; pos->bbMinorAttacks[uColor] |= bbAttack; // // Unified chain walk over both mobility and x-ray population // in one pass -- same technique and same reason as rook's // identical rewrite (board_representation/EVAL.md section 9): // the old mailbox walk's BMOB_FRIEND_XRAY/BMOB_ENEMY_GREATER // cases don't just x-ray past their blocker for attack-bit // purposes, they keep walking (fStop=FALSE) and keep crediting // mobility for whatever lies beyond. The originally-committed // bitboard version (and its single-hop-then-chain-following // x-ray fix) only ever handled the near side for *mobility*, // an under-count caught the same way rook's was: a two-bishop // battery on one diagonal with open squares beyond (old // mailbox: 12/8 mobility for the two bishops; near-side-only // bitboard version: 11/5). // for (;;) { BITBOARD bbNew = bbLayer & ~bbSeen; BITBOARD bbBlockersHere = bbNew & pos->bbOccupied; BITBOARD bbEmptyHere = bbNew & ~pos->bbOccupied; BITBOARD bbFriendBQHere = bbBlockersHere & bbFriendBQ; BITBOARD bbEnemySameHere = bbBlockersHere & bbEnemySame; BITBOARD bbEnemyGEHere = bbBlockersHere & bbEnemyGEContinue; BITBOARD bbFriendPawnHere = bbBlockersHere & pos->bbPawns[uColor]; BITBOARD bbEnemyPawnHere = bbBlockersHere & pos->bbPawns[FLIP(uColor)]; BITBOARD bbTransientHere = bbFriendPawnHere & bbPc; BITBOARD bbContinueHere; bbSeen |= bbNew; bbXrayAccum |= (bbNew & bbFirstLayerMask); bbFirstLayerMask = ~(BITBOARD)0; bbMobility |= (bbEmptyHere & ~bbUnsafeForMinor) | bbEnemySameHere | bbEnemyGEHere | (bbEnemyPawnHere & ~bbUnsafeForMinor) | bbTransientHere; bbContinueHere = bbFriendBQHere | bbEnemyGEHere; if (!bbContinueHere) { break; } bbExclude |= bbContinueHere; bbLayer = _BishopAttacksBB(c, pos->bbOccupied & ~bbExclude); } pos->bbMinorXrayAttacks[uColor] |= bbXrayAccum; uTotalMobility = CountBits(bbMobility); uMaxMobility = 0; for (d = 0; d < 4; d++) { ULONG uThisRay = CountBits(bbMobility & g_BishopRayToEdge[d][c]); uMaxMobility = MAXU(uMaxMobility, uThisRay); } } ASSERT(uTotalMobility <= 13); ASSERT(uMaxMobility <= 7); EVAL_TERM(uColor, BISHOP, c, pos->iScore[uColor], BISHOP_MOBILITY_BY_SQUARES[uTotalMobility], "bishop mobility"); EVAL_TERM(uColor, BISHOP, c, pos->iScore[uColor], BISHOP_MAX_MOBILITY_IN_A_ROW_BONUS[uMaxMobility], "consecutive bishop mobility"); // // Look for bishops with no mobility, they are trapped and, later, // we'll see if they are also under attack too. // if (uTotalMobility == 0) { _RecordTrappedCandidate(pos, uColor, c); } #if 0 // This is never used right now. uTotalMobility /= 2; ASSERT((pos->uMinMobility[uColor] & 0x80000000) == 0); ASSERT((uTotalMobility & 0x80000000) == 0); pos->uMinMobility[uColor] = MINU(pos->uMinMobility[uColor], uTotalMobility); #endif // // Bonus for a bishop that's securely placed -- safe from ever // being challenged by an enemy pawn, and (checked below) defended // 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)]); if (TRUE == _IsSquareSafeFromEnemyPawn(pos, c, bb)) { // Defended (and defending) a friendly pawn. if (pos->bbPawnAttacks[uColor] & COOR_TO_BB(c)) { #ifdef DEBUG p = BLACK_PAWN | uColor; ASSERT(((IS_ON_BOARD(c + 17 * g_iBehind[uColor]) && (pos->rgSquare[c + 17 * g_iBehind[uColor]].pPiece==p)) || ((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]); i = BISHOP_UNASSAILABLE_BY_DIST_FROM_EKING[u]; EVAL_TERM(uColor, BISHOP, c, pos->iReducedMaterialDownScaler[uColor], i, "[scaled] unassailable"); } } } static void _EvalKnight(IN OUT POSITION *pos, IN COOR c, IN PAWN_HASH_ENTRY *pHash) /** Routine description: Parameters: POSITION *pos, COOR c, PAWN_HASH_ENTRY *pHash, Return value: FLAG **/ { static const int iPawnStart[2] = { -17, +15 }; static const COOR cKnightAtHome[2][2] = { { B8, G8 }, // BLACK { B1, G1 } // WHITE }; PIECE p; COOR cSquare; BITBOARD bb; ULONG uColor; ULONG uPawnsSupporting; ULONG uMobilitySquares; SCORE i; ULONG uDist; p = pos->rgSquare[c].pPiece; ASSERT(p && IS_KNIGHT(p)); uColor = GET_COLOR(p); ASSERT(IS_VALID_COLOR(uColor)); #ifdef DEBUG pos->cPiece = c; #endif // // Unmoved piece // pos->uMinorsAtHome[uColor] += (c == cKnightAtHome[uColor][0]); pos->uMinorsAtHome[uColor] += (c == cKnightAtHome[uColor][1]); ASSERT(pos->uMinorsAtHome[uColor] <= 4); // // Centrality bonus // EVAL_TERM(uColor, KNIGHT, c, pos->iScore[uColor], KNIGHT_CENTRALITY_BONUS[c], "board centrality"); // // Give a bonus to knights on a closed / busy board // ASSERT(pos->uClosedScaler <= 32); EVAL_TERM(uColor, KNIGHT, c, pos->iScore[uColor], KNIGHT_IN_CLOSED_POSITION[pos->uClosedScaler], "in closed/open position"); // // See if square c is safe from enemy pawns; 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]); ASSERT((uDist > 0) && (uDist <= 8)); bb = pos->bbPawns[FLIP(uColor)] & (~pHash->bbStationaryPawns[FLIP(uColor)]); if (TRUE == _IsSquareSafeFromEnemyPawn(pos, c, bb)) { // // Count the number of supporting pawns the knight has // uPawnsSupporting = 0; p = BLACK_PAWN | uColor; cSquare = c + iPawnStart[uColor]; uPawnsSupporting = (IS_ON_BOARD(cSquare) && (pos->rgSquare[cSquare].pPiece == p)); cSquare += 2; uPawnsSupporting += (IS_ON_BOARD(cSquare) && (pos->rgSquare[cSquare].pPiece == p)); ASSERT(uPawnsSupporting <= 2); // // Give a bonus based on distance from enemy king // i = KNIGHT_UNASSAILABLE_BY_DIST_FROM_EKING[uDist] + KNIGHT_WITH_N_PAWNS_SUPPORTING[uPawnsSupporting]; EVAL_TERM(uColor, KNIGHT, c, pos->iReducedMaterialDownScaler[uColor], i, "[scaled] unassailable/support"); // // Now, if there's zero or one supporter see if the enemy has a // bishop the color of the knight it could capture with. // if (uPawnsSupporting != 2) { if (IS_SQUARE_WHITE(c)) { if (pos->uWhiteSqBishopCount[FLIP(uColor)]) { EVAL_TERM(uColor, KNIGHT, c, pos->iScore[uColor], -(i / 2), "not safe from enemy B"); } } else { if (pos->uNonPawnCount[FLIP(uColor)][BISHOP] - pos->uWhiteSqBishopCount[FLIP(uColor)]) { EVAL_TERM(uColor, KNIGHT, c, pos->iScore[uColor], -(i / 2), "not safe from enemy B"); } } } } else // not outposted { // // Still good to be close to the enemy king. // i = KNIGHT_KING_TROPISM_BONUS[uDist]; EVAL_TERM(uColor, KNIGHT, c, pos->iReducedMaterialDownScaler[uColor], i, "[scaled] enemy king tropism"); } // // Give a bonus for blockading an enemy backward pawn. (2026-08-30: // the old comment here claimed we also reward pieces for blocking // enemy passers in the passer code -- checked, no longer true if // it ever was. _EvalPassers' "enemy controls/occupies sq ahead" // terms penalize the PASSER'S OWNER for having its stop-square // blocked; they don't reward the blocking piece directly. This is // the only place a blocking piece gets a direct bonus, not a // duplicate of anything.) // cSquare = c + 16 * g_iAhead[uColor]; bb = pHash->bbStationaryPawns[FLIP(uColor)]; if (bb & COOR_TO_BB(cSquare)) { ASSERT(pos->rgSquare[cSquare].pPiece); ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece)); EVAL_TERM(uColor, KNIGHT, c, pos->iScore[uColor], KNIGHT_ON_INTERESTING_SQUARE_BY_RANK[uColor][RANK(c)], "blockades enemy pawn"); } // // Removed 2026-08-30 ("A knight with an open file behind it is // good"): dubious chess reasoning on its own (open-file bonuses // are classically a rook concept -- knights don't use files the // way rooks do) compounded by reusing KNIGHT_ON_INTERESTING_SQUARE // _BY_RANK, the *same* table as the backward-pawn-blockade bonus // just above, for a completely unrelated condition. That reuse // muddied what the table means and made it untunable // independently for either concept. // // // Do mobility and piece relevance. Also update attack tables. // // board_representation/EVAL.md section 1b/2: g_KnightAttacksBB[c] // (generate.c's precomputed table, already used by move // generation) is exactly the old per-square g_iNDeltas walk's // destination set, IS_ON_BOARD baked in at table-build time -- no // per-square branch, no mailbox read, no switch. Knights never // x-ray or have a battery partner, so the old NMobCaseTable's four // cases collapse to two bitboard masks: NMOB_ENEMY_OTHER (any // enemy non-pawn -- count unconditionally) and NMOB_MOBILE_SQUARE // (empty or enemy pawn -- count unless pawn-unsafe). NMOB_FRIEND // needs no term at all, it's just "neither of the above." // { 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 bbMobility; pos->bbMinorAttacks[uColor] |= bbAttack; bbMobility = (bbAttack & bbEnemyNonPawnOcc) | (bbAttack & ~bbFriendOcc & ~bbEnemyNonPawnOcc & ~bbUnsafeForMinor); uMobilitySquares = CountBits(bbMobility); } ASSERT(uMobilitySquares >= 0); ASSERT(uMobilitySquares <= 8); EVAL_TERM(uColor, KNIGHT, c, pos->iScore[uColor], KNIGHT_MOBILITY_BY_COUNT[uMobilitySquares], "knight mobility"); if (uMobilitySquares == 0) { _RecordTrappedCandidate(pos, uColor, c); } #if 0 // This is never used right now. ASSERT((pos->uMinMobility[uColor] & 0x80000000) == 0); ASSERT((uMobilitySquares & 0x80000000) == 0); pos->uMinMobility[uColor] = MINU(pos->uMinMobility[uColor], uMobilitySquares); #endif } static void _EvalRook(IN OUT POSITION *pos, IN COOR c, IN PAWN_HASH_ENTRY *pHash) /** Routine description: Parameters: IN POSITION *pos, IN COOR c, IN PAWN_HASH_ENTRY *pHash, Return value: FLAG **/ { PIECE p; ULONG uPawnFile = FILE(c) + 1; ULONG uColor; ULONG u; ULONG uMaxMobility; ULONG uTotalMobility; COOR cSquare; BITBOARD bb; SCORE i; ASSERT(IS_ON_BOARD(c)); p = pos->rgSquare[c].pPiece; ASSERT(p && IS_ROOK(p)); uColor = GET_COLOR(p); ASSERT(IS_VALID_COLOR(uColor)); pos->cPiece = c; // // Reward being on a half open or full open file. // if (0 == pHash->uCountPerFile[uColor][uPawnFile]) { u = FILE_DISTANCE(c, pos->cNonPawns[FLIP(uColor)][0]); if (0 == pHash->uCountPerFile[FLIP(uColor)][uPawnFile]) { // // Full open // EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], ROOK_ON_FULL_OPEN_BY_DIST_FROM_EKING[u], "on full open"); } else { // // Half open, no friendly, just enemy. // EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], ROOK_ON_HALF_OPEN_WITH_ENEMY_BY_DIST_FROM_EKING[u], "on half open"); // // Added bonus if the enemy pawn is a passer. // bb = pHash->bbPasserLocations[FLIP(uColor)] & BBFILE[FILE(c)]; if (bb) { cSquare = CoorFromBitBoardRank8ToRank1(&bb); EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], PASSER_BY_RANK[FLIP(uColor)][RANK(cSquare)] / 4, "hassles enemy passer"); } } } else if (0 == pHash->uCountPerFile[FLIP(uColor)][uPawnFile]) { // // Half open, no enemy pawn, just a friendly // u = FILE_DISTANCE(c, pos->cNonPawns[FLIP(uColor)][0]); EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], ROOK_ON_HALF_OPEN_WITH_FRIEND_BY_DIST_FROM_EKING[u], "on half open"); // // See if the friend is a passed pawn and if the rook's in // front of it or behind it. // bb = pHash->bbPasserLocations[uColor] & BBFILE[FILE(c)]; if (bb) { // // IDEA: Rook behind candidates, helpers or sentries is good // because the file may open soon. // while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb))) { if (uColor == WHITE) { if (cSquare < c) { EVAL_TERM(WHITE, ROOK, c, pos->iScore[WHITE], ROOK_BEHIND_PASSER_BY_PASSER_RANK[WHITE] [RANK(cSquare)], "behind own passer"); } else { EVAL_TERM(WHITE, ROOK, c, pos->iScore[WHITE], ROOK_LEADS_PASSER_BY_PASSER_RANK[WHITE] [RANK(cSquare)], "in the way of passer"); } } else { ASSERT(uColor == BLACK); if (cSquare > c) { EVAL_TERM(BLACK, ROOK, c, pos->iScore[BLACK], ROOK_BEHIND_PASSER_BY_PASSER_RANK[BLACK] [RANK(cSquare)], "behind own passer"); } else { EVAL_TERM(BLACK, ROOK, c, pos->iScore[BLACK], ROOK_LEADS_PASSER_BY_PASSER_RANK[BLACK] [RANK(cSquare)], "in the way of passer"); } } } } } // // Removed 2026-08-30 ("rook trapping enemy king"): a rook on the // 7th/8th rank aligned with the enemy king is exactly the // geometric pattern CountKingSafetyDefects' CHECK_VECTOR // line-of-sight scan already picks up into // uPiecesPointingAtKing/king-safety -- this was pricing the same // king-danger fact a second time via a rook-specific bonus. Belongs // in king safety, not here. (pFriendRook, only ever assigned in // this block and never read, is gone too.) // // // Rooks increase in value as pawns come off: // // "A further refinement would be to raise the knight's value by // 1/16 and lower the rook's value by 1/8 for each pawn above five // of the side being valued, with the opposite adjustment for each // pawn short of five." // --Larry Kaufman, IM // "Evaluation of Material Imbalance" // EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], ROOK_VALUE_AS_PAWNS_COME_OFF[pos->uPawnCount[uColor] + pos->uPawnCount[FLIP(uColor)]], "increase in value/pawns"); // // Rook mobility (and update attack tables) -- board_representation/ // EVAL.md section 1b/9: _RookAttacksBB(c, pos->bbOccupied) // (generate.c's magic-bitboard slider lookup) gives the whole // ray-to-first-blocker attack set in one shot, same technique // already landed for knight/bishop. The old RMobCaseTable's six // live cases collapse to: // - terminal enemy rook/queen/king (RMOB_ENEMY_SAME/_GREATER): // counts unconditionally, no unsafe check -- capturing an // equal-or-higher piece is never "unsafe" in the mobility // sense. Queen/king additionally x-ray past (_GREATER); rook // does not (_SAME). // - empty or terminal enemy pawn/knight/bishop (RMOB_EMPTY / // RMOB_ENEMY_LESS): counts unless the square is attacked by an // enemy pawn or minor -- both terminal-square categories // together are exactly "not friend-occupied and not // enemy-rook/queen/king-occupied," the same partition // bishop/knight's reductions already use with their own value // thresholds. // - any friendly piece (RMOB_FRIEND_BLOCK/_ROOK/_QUEEN): never // counts, simply excluded by the friendly-occupied mask. // Friendly rook/queen additionally x-ray past (no mobility // credit at the blocker itself, matching the old table); a // friendly rook blocker also earns the connected-rook bonus. // pos->cPiece = c; { 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(pos->cNonPawns[FLIP(uColor)][0]); BITBOARD bbFriendRQ = pos->bbPieces[uColor][ROOK] | pos->bbPieces[uColor][QUEEN]; BITBOARD bbUnsafeForRook = pos->bbPawnAttacks[FLIP(uColor)] | pos->bbMinorAttacks[FLIP(uColor)]; BITBOARD bbExclude = 0; BITBOARD bbSeen = 0; BITBOARD bbLayer = bbAttack; BITBOARD bbMobility = 0; BITBOARD bbXrayAccum = 0; BITBOARD bbFirstLayerMask = 0; ULONG d; pos->bbRookAttacks[uColor] |= bbAttack; // // Unified chain walk over both mobility and x-ray population // in one pass, board_representation/EVAL.md section 9. The // old mailbox walk's RMOB_FRIEND_ROOK/_QUEEN and // RMOB_ENEMY_GREATER cases don't just x-ray past their blocker // for attack-bit purposes -- they keep walking (fStop=FALSE) // and keep crediting mobility for whatever safe/empty squares // and further captures lie beyond, however many such blockers // are stacked on one ray. A first version of this conversion // (committed, since fixed) only handled the near side (up to // the first blocker) for mobility and treated the chain purely // as an attack-bit population exercise -- caught by comparing // against the old mailbox walk directly on a battery position // (two same-color rooks on an open file: old code credited 13 // mobility to the far rook's near companion via squares beyond // it, the near-side-only version credited only 9). Fixed by // reusing the exact same "recompute with blockers excluded" // chain used for x-ray population to also accumulate mobility // credit at every layer, not just the first. // // Each iteration classifies the newly-revealed blockers on // each still-open ray: friendly rook/queen and enemy queen/ // king continue the chain (recompute with them excluded too); // everything else (friendly non-R/Q, enemy pawn/knight/bishop, // enemy rook, or the board edge) stops that ray. Bounded: each // iteration either terminates or consumes at least one new // piece into the exclusion set, so this can loop at most // once per piece on the board. // for (;;) { BITBOARD bbNew = bbLayer & ~bbSeen; BITBOARD bbBlockersHere = bbNew & pos->bbOccupied; BITBOARD bbEmptyHere = bbNew & ~pos->bbOccupied; BITBOARD bbFriendRQHere = bbBlockersHere & bbFriendRQ; BITBOARD bbEnemySameHere = bbBlockersHere & bbEnemySame; BITBOARD bbEnemyGEHere = bbBlockersHere & bbEnemyGEContinue; BITBOARD bbEnemyLEHere = bbBlockersHere & bbEnemyOcc & ~bbEnemySameHere & ~bbEnemyGEHere; BITBOARD bbFriendRookHere = bbBlockersHere & pos->bbPieces[uColor][ROOK]; BITBOARD bbContinueHere; bbSeen |= bbNew; bbXrayAccum |= (bbNew & bbFirstLayerMask); bbFirstLayerMask = ~(BITBOARD)0; bbMobility |= (bbEmptyHere & ~bbUnsafeForRook) | bbEnemySameHere | bbEnemyGEHere | (bbEnemyLEHere & ~bbUnsafeForRook); // // Connected-rook bonus fires at every friendly rook found // along the chain, not just the first (matches the old // walk: each RMOB_FRIEND_ROOK hit its own EVAL_TERM call). // while (bbFriendRookHere) { COOR cBlocker = CoorFromBitBoardRank8ToRank1(&bbFriendRookHere); FLAG fHoriz = (RANK(cBlocker) == RANK(c)); EVAL_TERM(uColor, ROOK, cBlocker, pos->iScore[uColor], (ROOK_CONNECTED_HORIZ * fHoriz + ROOK_CONNECTED_VERT * FLIP(fHoriz)), "rook connected"); } bbContinueHere = bbFriendRQHere | bbEnemyGEHere; if (!bbContinueHere) { break; } bbExclude |= bbContinueHere; bbLayer = _RookAttacksBB(c, pos->bbOccupied & ~bbExclude); } pos->bbRookXrayAttacks[uColor] |= bbXrayAccum; uTotalMobility = CountBits(bbMobility); uMaxMobility = 0; for (d = 0; d < 4; d++) { ULONG uThisRay = CountBits(bbMobility & g_RookRayToEdge[d][c]); uMaxMobility = MAXU(uMaxMobility, uThisRay); } } ASSERT(uTotalMobility <= 14); ASSERT(uMaxMobility <= 7); ASSERT(pos->uArmyScaler[FLIP(uColor)] <= 31); // Tuning can (and has) pushed this out of its original hand-picked // bound; not a real invariant. //ASSERT(REDUCED_MATERIAL_UP_SCALER[pos->uArmyScaler[FLIP(uColor)]] <= 8); 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"); EVAL_TERM(uColor, ROOK, c, pos->iScore[uColor], ROOK_MAX_MOBILITY_IN_A_ROW_BONUS[uMaxMobility], "consecutive rook mobility"); #if 0 // This is never used right now. ASSERT((pos->uMinMobility[uColor] & 0x80000000) == 0); ASSERT((uTotalMobility & 0x80000000) == 0); pos->uMinMobility[uColor] = MINU(pos->uMinMobility[uColor], uTotalMobility); #endif if (uTotalMobility < 3) { // // Look for rooks with no mobility who are under attack. These // pieces are trapped! // if (uTotalMobility == 0) { _RecordTrappedCandidate(pos, uColor, c); } // // Rook trapped in the corner by a stupid friendly king? // ASSERT(IS_VALID_COLOR(uColor)); if (uColor == WHITE) { ASSERT(IS_ON_BOARD(c)); if (RANK1(c)) { cSquare = pos->cNonPawns[WHITE][0]; ASSERT(IS_ON_BOARD(cSquare)); if (RANK1(cSquare)) { if (((cSquare > E1) && (c > cSquare)) || ((cSquare < D1) && (c < cSquare))) { EVAL_TERM(WHITE, ROOK, c, pos->iScore[WHITE], KING_TRAPPING_ROOK, "king trapping rook"); } } } } else { ASSERT(uColor == BLACK); ASSERT(IS_ON_BOARD(c)); if (RANK8(c)) { cSquare = pos->cNonPawns[BLACK][0]; ASSERT(IS_ON_BOARD(cSquare)); if (RANK8(cSquare)) { if (((cSquare > E8) && (c > cSquare)) || ((cSquare < D8) && (c < cSquare))) { EVAL_TERM(BLACK, ROOK, c, pos->iScore[BLACK], KING_TRAPPING_ROOK, "king trapping rook"); } } } } } // if low mobility } static void _EvalQueen(IN OUT POSITION *pos, IN COOR c, IN PAWN_HASH_ENTRY *pHash) /** Parameters: POSITION *pos, COOR c, PAWN_HASH_ENTRY *pHash, Return value: FLAG **/ { PIECE p = pos->rgSquare[c].pPiece; ULONG uColor; ULONG uTotalMobility; ULONG u; COOR cKing; ASSERT(IS_ON_BOARD(c)); ASSERT(p && IS_QUEEN(p)); uColor = GET_COLOR(p); ASSERT(IS_VALID_COLOR(uColor)); pos->cPiece = c; cKing = pos->cNonPawns[FLIP(uColor)][0]; ASSERT(IS_ON_BOARD(cKing)); ASSERT(IS_KING(pos->rgSquare[cKing].pPiece)); if ((FALSE == pos->fCastled[uColor]) && (pos->uMinorsAtHome[uColor] > 1)) { // // Discourage queen being out too early // if (!RANK1(c) && !RANK8(c)) { // Tuning can (and has) flipped this sign; not a real invariant. //ASSERT(QUEEN_OUT_EARLY[pos->uMinorsAtHome[uColor]] < 0); ASSERT(pos->uMinorsAtHome[uColor] <= 4); EVAL_TERM(uColor, QUEEN, c, pos->iScore[uColor], QUEEN_OUT_EARLY[pos->uMinorsAtHome[uColor]], "queen out too early"); } } else { // // Encourage enemy king tropism // u = DISTANCE(c, cKing); ASSERT((u <= 7) && (u > 0)); EVAL_TERM(uColor, QUEEN, c, pos->iScore[uColor], QUEEN_KING_TROPISM[u], "enemy king tropism"); } // // Do queen mobility (and update attack tables) -- two-pass // rook-direction/bishop-direction magic lookups, board_ // representation/EVAL.md section 1b/9: MOVEGEN_MIGRATION.md // already found (in the stashed first bitboard-eval attempt's // _EvalQueenOccupancyBB PoC) that a combined 8-ray table measures // *slower* than reusing the rook/bishop tables in two passes -- // reuse that structure here too, don't rediscover the regression. // Each pass is its own unified chain walk, identical technique to // rook's/bishop's own conversions (including their fix for // crediting mobility beyond a battery partner, not just x-ray // attack bits): continue through a friendly queen in both passes, // a friendly rook only in the rook-direction pass, a friendly // bishop only in the bishop-direction pass (QMOB_FRIEND_ROOK/ // _BISHOP's old per-ray-family split falls out for free from // *which* lookup a blocker shows up in -- no fOrthogonalRay flag // needed). Unlike rook/bishop, queen never x-rays through *any* // enemy piece (QMOB_ENEMY_GE always stopped in the old table, the // one case that's a *fewer*-cases asymmetry versus bishop, not // 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] | COOR_TO_BB(cKing); BITBOARD bbUnsafeForQueen = pos->bbPawnAttacks[FLIP(uColor)] | pos->bbMinorAttacks[FLIP(uColor)] | pos->bbRookAttacks[FLIP(uColor)]; BITBOARD bbAttackTotal = 0; BITBOARD bbXrayAccum = 0; BITBOARD bbMobility = 0; ULONG uPass; for (uPass = 0; uPass < 2; uPass++) { BITBOARD bbFriendContinue = (uPass == 0) ? (pos->bbPieces[uColor][ROOK] | pos->bbPieces[uColor][QUEEN]) : (pos->bbPieces[uColor][BISHOP] | pos->bbPieces[uColor][QUEEN]); BITBOARD bbInitial = (uPass == 0) ? _RookAttacksBB(c, pos->bbOccupied) : _BishopAttacksBB(c, pos->bbOccupied); BITBOARD bbExclude = 0; BITBOARD bbSeen = 0; BITBOARD bbLayer = bbInitial; BITBOARD bbFirstLayerMask = 0; bbAttackTotal |= bbInitial; for (;;) { BITBOARD bbNew = bbLayer & ~bbSeen; BITBOARD bbBlockersHere = bbNew & pos->bbOccupied; BITBOARD bbEmptyHere = bbNew & ~pos->bbOccupied; BITBOARD bbFriendContinueHere = bbBlockersHere & bbFriendContinue; BITBOARD bbEnemyGEHere = bbBlockersHere & bbEnemyGE; BITBOARD bbEnemyLEHere = bbBlockersHere & bbEnemyOcc & ~bbEnemyGEHere; bbSeen |= bbNew; bbXrayAccum |= (bbNew & bbFirstLayerMask); bbFirstLayerMask = ~(BITBOARD)0; bbMobility |= (bbEmptyHere & ~bbUnsafeForQueen) | bbEnemyGEHere | (bbEnemyLEHere & ~bbUnsafeForQueen); if (!bbFriendContinueHere) { break; } bbExclude |= bbFriendContinueHere; bbLayer = (uPass == 0) ? _RookAttacksBB(c, pos->bbOccupied & ~bbExclude) : _BishopAttacksBB(c, pos->bbOccupied & ~bbExclude); } } pos->bbQueenAttacks[uColor] |= bbAttackTotal; pos->bbQueenXrayAttacks[uColor] |= bbXrayAccum; uTotalMobility = CountBits(bbMobility); } ASSERT(uTotalMobility <= 27); EVAL_TERM(uColor, QUEEN, c, pos->iScore[uColor], QUEEN_MOBILITY_BY_SQUARES[uTotalMobility], "queen mobility"); // // Look for queens with no mobility who are under attack. These // pieces are trapped! // if (uTotalMobility == 0) { _RecordTrappedCandidate(pos, uColor, c); } #if 0 // This is never used right now ASSERT((pos->uMinMobility[uColor] & 0x80000000) == 0); ASSERT((uTotalMobility & 0x80000000) == 0); pos->uMinMobility[uColor] = MINU(pos->uMinMobility[uColor], uTotalMobility); #endif // // 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. // } static void _EvalKing(IN OUT POSITION *pos, IN const COOR c, IN const PAWN_HASH_ENTRY *pHash) /** Routine description: Parameters: POSITION *pos, COOR c, PAWN_HASH_ENTRY *pHash, Return value: FLAG **/ { PIECE p; ULONG uColor, ufColor; COOR cSquare; COOR cFileSq; ULONG u, v; int w; ULONG uCounter; BITV bvAttack; BITV bvXray; BITV bvDefend; BITV bvPattern = 0; ULONG uFlightSquares; ULONG uQueenNearKing = 0; BITBOARD bb; SCORE i; SCORE iKingScore = 0; static ULONG KingFlightDefects[9] = { +4, +2, +1, 0, 0, 0, 0, 0, 0 }; static ULONG KingStormingPawnDefects[8] = { +0, +4, +3, +1, +0, +0, +0, +0 }; static ULONG KingFileDefects[3] = { +2, +1, +0 }; static INT KingSafetyDeltas[11] = { -17, -16, -15, -1, +1, +15, +16, +17, -2, +2, 0 }; static ULONG EmptyAttackedSquare[2][11] = { { 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 0 }, { 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0 }, }; static ULONG EmptyUnattackedSquare[2][11] = { { 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, }; static ULONG OccupiedAttackedSquare[2] = { 1, 2 }; ASSERT(IS_ON_BOARD(c)); p = pos->rgSquare[c].pPiece; ASSERT(p && IS_KING(p)); uColor = GET_COLOR(p); ufColor = FLIP(uColor); ASSERT(IS_VALID_COLOR(uColor)); u = pos->uNonPawnMaterial[ufColor]; 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. pos->bbKingAttacks[uColor] |= g_KingAttacksBB[c]; goto skip_safety; } // Prepare to do king safety uCounter = (u >= KEEP_KING_AT_HOME_THRESHOLD) * KING_INITIAL_COUNTER_BY_LOCATION[uColor][c]; #ifdef EVAL_DUMP Trace("%s Initial KS Counter: %u\n", COLOR_NAME(uColor), uCounter); #endif uCounter += pos->uPiecesPointingAtKing[uColor] / 2; #ifdef EVAL_DUMP Trace("%s KS Counter after pieces pointing: %u\n", COLOR_NAME(uColor), uCounter); #endif // board_representation/EVAL.md section 9: written once, before the // loop below, instead of per-square inside it -- also the bugfix // agreed on for this conversion. The old per-square write used // KingSafetyDeltas (11 entries) rather than the real 8-square king // move pattern, so it spuriously marked two squares 2 files away // on the same rank (KingSafetyDeltas' -2/+2 entries, present only // for this loop's own file-distance bookkeeping) as // "king-attacked" too. g_KingAttacksBB[c] is the real pattern. pos->bbKingAttacks[uColor] |= g_KingAttacksBB[c]; uFlightSquares = 0; u = 0; ASSERT(KingSafetyDeltas[u] != 0); do { cSquare = c + KingSafetyDeltas[u]; if (IS_ON_BOARD(cSquare)) { p = pos->rgSquare[cSquare].pPiece; // // board_representation/EVAL.md section 9: bvAttacks/ // ATTACK_BITV retired entirely -- every bit here now comes // straight from a bbXAttacks accumulator read, no more // c|8 shadow-index struct storage or per-square writes. // No enemy-king contribution in bvAttack, by direct // instruction (see this function's header comment on the // black-then-white evaluation-order asymmetry this // sidesteps). bvDefend's own-king bit is real, load- // bearing signal (see the bvDefend &= ~8 below, which // strips it back out when the square is x-rayed or // multiply attacked -- "a lone king isn't adequate defense // against that") -- not just self-consistency noise, so // it keeps its own-color check. // { BITBOARD sq = COOR_TO_BB(cSquare); bvAttack = ((pos->bbPawnAttacks[ufColor] & sq) ? PAWN_BIT : 0) | ((pos->bbMinorAttacks[ufColor] & sq) ? MINOR_BIT : 0) | ((pos->bbRookAttacks[ufColor] & sq) ? ROOK_BIT : 0) | ((pos->bbQueenAttacks[ufColor] & sq) ? QUEEN_BIT : 0); bvXray = ((pos->bbMinorXrayAttacks[ufColor] & sq) ? MINOR_BIT : 0) | ((pos->bbRookXrayAttacks[ufColor] & sq) ? ROOK_BIT : 0) | ((pos->bbQueenXrayAttacks[ufColor] & sq) ? QUEEN_BIT : 0); bvDefend = ((pos->bbPawnAttacks[uColor] & sq) ? PAWN_BIT : 0) | ((pos->bbMinorAttacks[uColor] & sq) ? MINOR_BIT : 0) | ((pos->bbRookAttacks[uColor] & sq) ? ROOK_BIT : 0) | ((pos->bbQueenAttacks[uColor] & sq) ? QUEEN_BIT : 0) | ((pos->bbKingAttacks[uColor] & sq) ? KING_BIT : 0); } // // Count squares near the king the enemy queen specifically // attacks or x-rays (2026-08-30, replaces the old // per-queen "pointing near enemy K" bolt-on this loop's // own bvAttack/bvXray data already made redundant). Reuses // data already read above -- no extra attack-table work -- // and, unlike the old term, catches x-ray/latent queen // threats too, not just direct ray-cast hits. // // Queen no longer writes small.uQueen/xray.uQueen at all // (2026-09-05 conversion) -- bvAttack/bvXray above already // fold its contribution back in via QUEEN_BIT (same byte // position .small.uQueen used), so just read those instead // of the now-permanently-zero raw struct fields. // uQueenNearKing += (((bvAttack | bvXray) & QUEEN_BIT) != 0); if (bvAttack != 0) { bvPattern |= (bvAttack | bvXray); if (bvXray || (bvAttack & (bvAttack - 1))) { bvDefend &= ~8; } if (IS_EMPTY(p)) { ASSERT(u < 11); uCounter += EmptyAttackedSquare[uColor][u]; } else { uCounter += OccupiedAttackedSquare[OPPOSITE_COLORS(p, uColor)]; } uCounter += (bvDefend == 0); } else { uCounter += (bvXray != 0); ASSERT(u < 11); uCounter += EmptyUnattackedSquare[uColor][u]; ASSERT((IS_EMPTY(p) == 0) || (IS_EMPTY(p) == 1)); uFlightSquares += ((IS_EMPTY(p)) && (u < 8)); } } u++; } while(KingSafetyDeltas[u] != 0); #ifdef EVAL_DUMP Trace("%s KS Counter post-squares: %u\n", COLOR_NAME(uColor), uCounter); #endif // // King's own file plus the two adjacent ones (collapsed 2026-08-30 // from three copy-pasted blocks that differed only in c-1/c/c+1 -- // same behavior and iteration order, just not repeated three times // in the source; confirmed behaviorally neutral by isolated sd10 // suite testing before landing). // for (w = -1; w <= 1; w++) { cFileSq = c + w; if (!IS_ON_BOARD(cFileSq)) { continue; } u = FILE(cFileSq) + 1; v = (pHash->uCountPerFile[WHITE][u] > 0) + (pHash->uCountPerFile[BLACK][u] > 0); ASSERT((v >= 0) && (v <= 2)); uCounter += (KingFileDefects[v] + ((v < 2) && ((u == 1) || (u == 8)))); bb = pos->bbPawns[ufColor] & BBFILE[u - 1]; if (bb) { if (uColor == WHITE) { cSquare = CoorFromBitBoardRank1ToRank8(&bb); } else { cSquare = CoorFromBitBoardRank8ToRank1(&bb); } ASSERT(IS_ON_BOARD(cSquare)); uCounter += KingStormingPawnDefects[DISTANCE(cSquare, c)]; } } #ifdef EVAL_DUMP Trace("%s KS Counter post-open file/stormers: %u\n", COLOR_NAME(uColor), uCounter); #endif bvPattern >>= 3; ASSERT(bvPattern >= 0); ASSERT(bvPattern < 32); v = KING_COUNTER_BY_ATTACK_PATTERN[bvPattern]; uCounter += v; #ifdef EVAL_DUMP Trace("%s KS Counter post-attack pattern: %u\n", COLOR_NAME(uColor), uCounter); #endif ASSERT(uFlightSquares <= 8); uCounter += KingFlightDefects[uFlightSquares] * (v > 1); #ifdef EVAL_DUMP 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]; i *= REDUCED_MATERIAL_DOWN_SCALER[pos->uArmyScaler[ufColor]]; i /= 8; EVAL_TERM(uColor, KING, c, iKingScore, i, "king safety"); ASSERT(uQueenNearKing <= 10); EVAL_TERM(uColor, KING, c, iKingScore, 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]) { if (pos->uNonPawnCount[uColor][0] > 4) { ASSERT(pos->uMinorsAtHome[uColor] <= 4); EVAL_TERM(uColor, 0, ILLEGAL_COOR, pos->iScore[uColor], UNDEVELOPED_MINORS_IN_OPENING[pos->uMinorsAtHome[uColor]], "development"); if (uColor == BLACK) { EVAL_TERM(BLACK, KING, c, iKingScore, (KING_MISSING_ONE_CASTLE_OPTION * ((CASTLE_BLACK_SHORT & pos->bvCastleInfo) == 0)), "can't castle short"); EVAL_TERM(BLACK, KING, c, iKingScore, (KING_MISSING_ONE_CASTLE_OPTION * ((CASTLE_BLACK_LONG & pos->bvCastleInfo) == 0)), "can't castle long"); } else { ASSERT(uColor == WHITE); EVAL_TERM(WHITE, KING, c, iKingScore, (KING_MISSING_ONE_CASTLE_OPTION * ((CASTLE_WHITE_SHORT & pos->bvCastleInfo) == 0)), "can't castle short"); EVAL_TERM(WHITE, KING, c, iKingScore, (KING_MISSING_ONE_CASTLE_OPTION * ((CASTLE_WHITE_LONG & pos->bvCastleInfo) == 0)), "can't castle long"); } } } skip_safety: // // Special code for kings in the endgame // u = pos->uNonPawnCount[WHITE][0] + pos->uNonPawnCount[BLACK][0]; if (u < 8) { // // Encourage kings to come to the center // i = KING_TO_CENTER[c]; EVAL_TERM(uColor, KING, c, iKingScore, i, "centralize king"); // // Kings in front of passers in the late endgame are strong... // cSquare = FILE(c); bb = pHash->bbPasserLocations[uColor] & (BBADJACENT_FILES[cSquare] | BBFILE[cSquare]); if (bb) { while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb))) { if (DISTANCE(cSquare, c) == 1) { ASSERT(abs((INT)FILE(c) - (INT)FILE(cSquare)) <= 1); ASSERT(abs((INT)RANK(c) - (INT)RANK(cSquare)) <= 1); i = 2; i += KING_SUPPORTING_OWN_PASSER_BY_RANK[uColor][RANK(cSquare)]; switch(uColor) { case WHITE: i += 8 * (c < (cSquare - 1)); break; case BLACK: i += 8 * (c > (cSquare + 1)); break; } EVAL_TERM(uColor, KING, c, iKingScore, i, "supporting own passer"); } } } // // Detect kings way out of the action in KP endgames: // // 8/8/1p6/p6k/P3K3/8/1P6/8 w - - 0 11 // if ((u == 2) && (uColor == WHITE)) { i = 0; bb = (pos->bbPawns[WHITE] | pos->bbPawns[BLACK]); while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb))) { i += (DISTANCE(cSquare, pos->cNonPawns[BLACK][0]) - DISTANCE(cSquare, pos->cNonPawns[WHITE][0])); } EVAL_TERM(WHITE, KING, c, iKingScore, i * 12, "in/out of \"action\""); } } pos->iScore[uColor] += iKingScore; pos->iTempScore = iKingScore; } FLAG EvalPasserRaces(IN OUT POSITION *pos, IN PAWN_HASH_ENTRY *pHash) /** Routine description: Determine if one side has a pawn that is unstoppable. This is broken for positions like: Parameters: POSITION *pos, PAWN_HASH_ENTRY *pHash, Return value: void **/ { BITBOARD bb[2]; ULONG uColor; int d1; COOR cQueen; COOR cKing, c; ULONG uKingDist, uPawnDist, uFriendDist; ULONG uRacerDist[2] = { 99, 99 }; FLAG fDontCountMeOut[2] = { ((RANK(pos->cNonPawns[BLACK][0]) <= 3) && (pos->uPawnCount[BLACK] != 0)), ((RANK(pos->cNonPawns[WHITE][0]) >= 6) && (pos->uPawnCount[WHITE] != 0)) }; bb[BLACK] = pHash->bbPasserLocations[BLACK]; bb[WHITE] = pHash->bbPasserLocations[WHITE]; if (!(bb[BLACK] | bb[WHITE])) { return(FALSE); } ASSERT((pos->uNonPawnCount[WHITE][0] == 1) || (pos->uNonPawnCount[BLACK][0] == 1)) FOREACH_COLOR(uColor) { if (pos->uNonPawnCount[FLIP(uColor)][0] == 1) { d1 = 16 * g_iAhead[uColor]; ASSERT((d1 * 2) == (32 * g_iAhead[uColor])); if (bb[uColor]) { while(IS_ON_BOARD(c = CoorFromBitBoardRank8ToRank1( &(bb[uColor])))) { // If the enemy king can take this passer and is // on the move, all bets are off! cKing = pos->cNonPawns[FLIP(uColor)][0]; ASSERT(IS_KING(pos->rgSquare[cKing].pPiece)); ASSERT(GET_COLOR(pos->rgSquare[cKing].pPiece) != uColor); if (pos->uToMove == FLIP(uColor) && (DISTANCE(cKing, c) == 1)) { // TODO: something clever here about protected pawns continue; } cQueen = FILE_RANK_TO_COOR(FILE(c), QUEENING_RANK[uColor]); ASSERT(FILE(cQueen) == FILE(c)); uPawnDist = RANK_DISTANCE(cQueen, c); if ((RANK(c) == JUMPING_RANK[uColor]) && (IS_EMPTY(pos->rgSquare[c + d1].pPiece) && IS_EMPTY(pos->rgSquare[c + d1 * 2].pPiece))) { uPawnDist--; } ASSERT((uPawnDist >= 1) && (uPawnDist <= 6)); uKingDist = DISTANCE(cKing, cQueen); if (uKingDist > 0) { uKingDist -= (pos->uToMove == FLIP(uColor)); } ASSERT((uKingDist >= 0) && (uPawnDist <= 7)); if (uPawnDist < uKingDist) { uRacerDist[uColor] = MINU(uRacerDist[uColor], uPawnDist); } cKing = pos->cNonPawns[uColor][0]; ASSERT(IS_KING(pos->rgSquare[cKing].pPiece)); ASSERT(GET_COLOR(pos->rgSquare[cKing].pPiece) == uColor); uFriendDist = DISTANCE(cKing, cQueen); if ((uFriendDist <= 1) && (DISTANCE(cKing, c) <= 1)) { if (FILE(c) != FILE(cKing)) { uRacerDist[uColor] = MINU(uRacerDist[uColor], uPawnDist); } else if ((FILE(cKing) != A) && (FILE(cKing) != H)) { uRacerDist[uColor] = MINU(uRacerDist[uColor], (uPawnDist + 1)); } } fDontCountMeOut[uColor] = TRUE; // // TODO: winning connected passers // } } } } if ((uRacerDist[WHITE] < uRacerDist[BLACK]) && (FALSE == fDontCountMeOut[BLACK])) { EVAL_TERM(WHITE, PAWN, ILLEGAL_COOR, pos->iScore[WHITE], RACER_WINS_RACE - uRacerDist[WHITE] * 4, "winning racer pawn"); return(TRUE); } else if ((uRacerDist[BLACK] < uRacerDist[WHITE]) && (FALSE == fDontCountMeOut[WHITE])) { EVAL_TERM(BLACK, PAWN, ILLEGAL_COOR, pos->iScore[BLACK], RACER_WINS_RACE - uRacerDist[BLACK] * 4, "winning racer pawn"); return(TRUE); } return(FALSE); } static void _EvalPassers(IN OUT POSITION *pos, IN PAWN_HASH_ENTRY *pHash) /** Routine description: Parameters: POSITION *pos, PAWN_HASH_ENTRY *pHash, Return value: void **/ { ULONG u; ULONG uColor; COOR c; COOR cSquare; BITBOARD bb; PIECE p; SCORE i; ULONG uNumPassers[2] = { 0, 0 }; int d1; ASSERT(pHash->bbPasserLocations[WHITE] | pHash->bbPasserLocations[BLACK]); FOREACH_COLOR(uColor) { bb = pHash->bbPasserLocations[uColor]; if (bb != 0) { uNumPassers[uColor] = CountBits(bb); ASSERT(uNumPassers[uColor] > 0); d1 = 16 * g_iAhead[uColor]; ASSERT((d1 * 2) == (32 * g_iAhead[uColor])); while(IS_ON_BOARD(c = CoorFromBitBoardRank8ToRank1(&bb))) { // // Consider the control of the square in front of the passer // cSquare = c + d1; ASSERT(IS_ON_BOARD(cSquare)); u = _WhoControlsSquareFast(pos, cSquare); if (u == FLIP(uColor)) { EVAL_TERM(uColor, PAWN, c, pos->iScore[uColor], -((PASSER_BY_RANK[uColor][RANK(c)] / 4) + (PASSER_BY_RANK[uColor][RANK(c)] / 16)), "enemy controls sq ahead"); } else if (u == (ULONG)-1) { p = pos->rgSquare[cSquare].pPiece; if (!IS_EMPTY(p) && (OPPOSITE_COLORS(p, uColor))) { ASSERT(GET_COLOR(p) != uColor); EVAL_TERM(uColor, PAWN, c, pos->iScore[uColor], -(PASSER_BY_RANK[uColor][RANK(c)] / 4), "enemy occupies sq ahead"); } } #ifdef DEBUG else { ASSERT(u == uColor); } #endif // // Consider distance from friend king / enemy king // i = (8 - DISTANCE(c, pos->cNonPawns[uColor][0])); if (((uColor == WHITE) && ((pos->cNonPawns[BLACK][0] >> 4) <= (c >> 4))) || ((uColor == BLACK) && ((pos->cNonPawns[WHITE][0] >> 4) >= (c >> 4)))) { // // The enemy king is ahead of the passer // i += FILE_DISTANCE(c, pos->cNonPawns[FLIP(uColor)][0]) * 4; } else { // // The enemy king is behind the passer // i += 10 + DISTANCE(c, pos->cNonPawns[FLIP(uColor)][0]) * 4; } i *= PASSER_MATERIAL_UP_SCALER[pos->uArmyScaler[FLIP(uColor)]]; i /= 8; EVAL_TERM(uColor, PAWN, c, pos->iScore[uColor], i, "passer distance to kings"); // // If a side is a piece down then the other side's passers // are even more dangerous. // if (pos->uNonPawnCount[uColor][0] > pos->uNonPawnCount[FLIP(uColor)][0]) { i = (PASSER_BY_RANK[uColor][RANK(c)] / 2); EVAL_TERM(uColor, PAWN, c, pos->iScore[uColor], i, "passer and piece up"); } // // TODO: For connected passers vs KR, consult the oracle // } } } // // Game stage bonus // u = pos->uArmyScaler[BLACK]; EVAL_TERM(WHITE, PAWN, ILLEGAL_COOR, pos->iScore[WHITE], uNumPassers[WHITE] * PASSER_BONUS_AS_MATERIAL_COMES_OFF[u], "passer value material"); // ASSERT(PASSER_BONUS_AS_MATERIAL_COMES_OFF[u] >= 0); u = pos->uArmyScaler[WHITE]; EVAL_TERM(BLACK, PAWN, ILLEGAL_COOR, pos->iScore[BLACK], uNumPassers[BLACK] * PASSER_BONUS_AS_MATERIAL_COMES_OFF[u], "passer value material"); // ASSERT(PASSER_BONUS_AS_MATERIAL_COMES_OFF[u] >= 0); } static void _EvalBadTrades(IN OUT POSITION *pos) /** Routine description: Parameters: POSITION *pos, Return value: void **/ { 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); #ifdef DEBUG if (pos->uNonPawnMaterial[WHITE] > pos->uNonPawnMaterial[BLACK]) { ASSERT(uAhead == WHITE); ASSERT(uBehind == BLACK); } else { ASSERT(pos->uNonPawnMaterial[BLACK] > pos->uNonPawnMaterial[WHITE]); ASSERT(uAhead == BLACK); 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 // trading pieces and not to trade pawns. This also has the // effect of making the side that is behind in material want to // trade pawns and not pieces. This also gives the side that is // ahead a bit of a "bad trade" bonus. Note that the bonus is not // given if the side "ahead" has no pawns; this is so that the // engine will not like positions like KNB vs KRP - the side with // the pawn has the winning chances. // EVAL_TERM(uAhead, 0, ILLEGAL_COOR, pos->iScore[uAhead], ((pos->uPawnCount[uAhead] != 0) * TRADE_PIECES[uMagnitude][pos->uNonPawnCount[uBehind][0]]), "trade pieces"); // Tuning can (and has) flipped this sign; not a real invariant. //ASSERT(TRADE_PIECES[uMagnitude][pos->uNonPawnCount[uBehind][0]] > 0); EVAL_TERM(uAhead, 0, ILLEGAL_COOR, pos->iScore[uAhead], DONT_TRADE_PAWNS[uMagnitude][pos->uPawnCount[uAhead]], "don't trade pawns"); } static void _EvalLookForDanger(IN OUT SEARCHER_THREAD_CONTEXT *ctx) /** Routine description: This routine looks for squares on the board where the side to move has a piece that is en prise. So far in eval we will have tagged pieces in situations where they are attacked by lesser-valued enemy pieces as "in danger". However, because of the order in which the attack table was generated, we will have missed situations where a piece is undefended (or underdefended) and attacked by an enemy piece that is the same (or greater) value. Note: This routine only considers side to move. Also Note: this routine does not find pawns that are en prise, only pieces. Parameters: SEARCHER_THREAD_CONTEXT *ctx Return value: void **/ { COOR c; ULONG u; POSITION *pos = &ctx->sPosition; ULONG uSide = pos->uToMove; #ifdef DEBUG PIECE p; #endif for (u = 1; u < pos->uNonPawnCount[uSide][0]; u++) { c = pos->cNonPawns[uSide][u]; #ifdef DEBUG ASSERT(IS_ON_BOARD(c)); p = pos->rgSquare[c].pPiece; ASSERT(p); ASSERT(GET_COLOR(p) == uSide); ASSERT(!IS_PAWN(p)); #endif if (_WhoControlsSquareFast(pos, c) == FLIP(uSide)) { RecordEnprisePiece(ctx, c); } } } static void _EvalTrappedPieces(IN OUT SEARCHER_THREAD_CONTEXT *ctx) /** Routine description: Parameters: SEARCHER_THREAD_CONTEXT *ctx Return value: static void **/ { COOR c; COOR cBestOwnTrapped; ULONG uBestOwnTrappedValue; ULONG uColor; ULONG u; POSITION *pos = &ctx->sPosition; PIECE p; FOREACH_COLOR(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)) { p = pos->rgSquare[c].pPiece; (void)p; #ifdef DEBUG ASSERT(p); ASSERT(!IS_PAWN(p)); ASSERT(GET_COLOR(p) == uColor); #endif // 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. if (OPPOSITE_COLORS(uColor, pos->uToMove)) { if (ctx->uPly > 0) { RecordEnprisePieceAtPly(ctx, ctx->uPly - 1, c); } EVAL_TERM(uColor, p, c, pos->iScore[uColor], ENPRISE_AND_TRAPPED_PENALTY, "en prise and trapped"); } // ctx->cTrapped[uPly] (RecordTrappedPiece's target) // is a single slot, not a list -- if more than one // of our own pieces is genuinely trapped this ply, // only report the most valuable one. else { if (PIECE_VALUE(pos->rgSquare[c].pPiece) > uBestOwnTrappedValue) { uBestOwnTrappedValue = PIECE_VALUE(pos->rgSquare[c].pPiece); cBestOwnTrapped = c; } } } } // uColor has the move but we found at least one piece that // seems to have no safe place to move and is actively under // attack now. It may not be lost, at least uColor has the // move. But this is still a bad thing. if (IS_ON_BOARD(cBestOwnTrapped)) { RecordTrappedPiece(ctx, cBestOwnTrapped); p = pos->rgSquare[cBestOwnTrapped].pPiece; EVAL_TERM(uColor, p, cBestOwnTrapped, pos->iScore[uColor], TRAPPED_WITH_MOVE_PENALTY, "trapped piece, our move"); } } } static void _EvalBishopPairs(IN POSITION *pos) /*++ Routine description: Give a bonus to sides that have a viable bishop pair based; scale the bonus by the number of pawns remaining on the board. Parameters: IN POSITION *pos - position Return value: static void --*/ { ULONG uPawnSum = pos->uPawnCount[WHITE] + pos->uPawnCount[BLACK]; FLAG fPair; ULONG uBishopCount = pos->uNonPawnCount[BLACK][BISHOP]; ULONG uWhiteSqBishopCount = pos->uWhiteSqBishopCount[BLACK]; ASSERT(uPawnSum <= 16); ASSERT(uBishopCount <= 10); ASSERT(uWhiteSqBishopCount <= 10); fPair = ((uBishopCount > 1) & (uWhiteSqBishopCount != 0) & (uWhiteSqBishopCount != uBishopCount)); EVAL_TERM(BLACK, 0, ILLEGAL_COOR, pos->iScore[BLACK], BISHOP_PAIR[fPair][uPawnSum], "bishop pair"); uBishopCount = pos->uNonPawnCount[WHITE][BISHOP]; uWhiteSqBishopCount = pos->uWhiteSqBishopCount[WHITE]; ASSERT(uBishopCount <= 10); ASSERT(uWhiteSqBishopCount <= 10); fPair = ((uBishopCount > 1) & (uWhiteSqBishopCount != 0) & (uWhiteSqBishopCount != uBishopCount)); EVAL_TERM(WHITE, 0, ILLEGAL_COOR, pos->iScore[WHITE], BISHOP_PAIR[fPair][uPawnSum], "bishop pair"); } SCORE Eval(IN SEARCHER_THREAD_CONTEXT *ctx, IN SCORE iAlpha, IN SCORE iBeta, OUT SCORE *piPositional) /** Routine description: Parameters: SEARCHER_THREAD_CONTEXT *ctx, SCORE iAlpha, SCORE iBeta, SCORE *piPositional : if non-NULL, filled in with a magnitude (always >= 0) estimating the non-material component of the score -- exact if a full eval ran, a cheap estimate otherwise. Callers must treat it as an estimate either way; it's only ever used to size a pruning margin, never as a hard fact. Return value: SCORE **/ { POSITION *pos = &(ctx->sPosition); SCORE iScoreForSideToMove; SCORE iAlphaMargin, iBetaMargin; PAWN_HASH_ENTRY *pHash; COOR c; ULONG u; ULONG uColor; BITBOARD bb; FLAG fDeferred; #ifdef EVAL_TIME UINT64 uTimer = SystemReadTimeStampCounter(); #endif ASSERT(IS_VALID_SCORE(iAlpha)); ASSERT(IS_VALID_SCORE(iBeta)); ASSERT(iAlpha < iBeta); ASSERT((pos->iMaterialBalance[WHITE] * -1) == pos->iMaterialBalance[BLACK]); // ASSERT(!InCheck(pos, pos->uToMove)); #if 0 // This is never used right now. pos->uMinMobility[BLACK] = pos->uMinMobility[WHITE] = 100; #endif pos->uNumTrapped[BLACK] = pos->uNumTrapped[WHITE] = 0; pos->iScore[BLACK] = (pos->uPawnMaterial[BLACK] + pos->uNonPawnMaterial[BLACK]); pos->iScore[WHITE] = (pos->uPawnMaterial[WHITE] + pos->uNonPawnMaterial[WHITE]); #ifdef EVAL_DUMP EvalTraceClear(); Trace("Material:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif // // Pawn eval. Note: if fDeferred comes back as TRUE then we have // neither cleared nor initialized the attack tables. This is // because we hope that we can get a lazy eval cutoff here and // save some time. BEFORE ANY CODE BELOW TOUCHES THE ATTACK // TABLES IT NEEDS TO CLEAR/POPULATE THEM THOUGH!!! // #ifdef EVAL_TIME { UINT64 u64PawnTimer = SystemReadTimeStampCounter(); pHash = _EvalPawns(ctx, &fDeferred); { UINT64 u64Elapsed = SystemReadTimeStampCounter() - u64PawnTimer; ctx->sCounters.tree.u64CyclesEvalPawns += u64Elapsed; if (fDeferred) { ctx->sCounters.tree.u64CyclesEvalPawnsHit += u64Elapsed; ctx->sCounters.tree.u64CountEvalPawnsHit++; } else { ctx->sCounters.tree.u64CyclesEvalPawnsMiss += u64Elapsed; ctx->sCounters.tree.u64CountEvalPawnsMiss++; } } } #else pHash = _EvalPawns(ctx, &fDeferred); #endif ASSERT(NULL != pHash); ASSERT(IS_VALID_FLAG(fDeferred)); pos->iScore[WHITE] += pHash->iScore[WHITE]; pos->iScore[BLACK] += pHash->iScore[BLACK]; ASSERT(IS_VALID_SCORE(pos->iScore[WHITE] - pos->iScore[BLACK])); #ifdef EVAL_DUMP Trace("After pawns:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif // // Look for won passers races. Note: This code cannot trust the // state of the attack tables yet! // if ((pos->uNonPawnCount[WHITE][0] == 1) || (pos->uNonPawnCount[BLACK][0] == 1)) { (void)EvalPasserRaces(pos, pHash); #ifdef EVAL_DUMP Trace("After passer races:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif } // // Bad trade code. Right. Note: this code cannot trust the state // of the attack tables... // if (pos->uNonPawnMaterial[WHITE] != pos->uNonPawnMaterial[BLACK]) { _EvalBadTrades(pos); #ifdef EVAL_DUMP Trace("After bad trades:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif } // // Bishop pairs. Again... Note: this code cannot trust the state // of the attack tables. // _EvalBishopPairs(pos); #ifdef EVAL_DUMP Trace("After bishop pair bonus:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif #ifdef LAZY_EVAL // // Compute an estimate of the score for the side to move based on the // eval terms we have already considered: // // 1. Material balance // 2. Pawn structure bonuses/penalties (incl passers/candidates) // 3. Passer races are detected already // 4. "Bad trade" code has already run // 5. We've already detected unwinnable endgames // 6. Bishop pairs // iScoreForSideToMove = (pos->iScore[pos->uToMove] - pos->iScore[FLIP(pos->uToMove)]); ASSERT(IS_VALID_SCORE(iScoreForSideToMove)); // // Eval has not considered several potentially large terms: // // 1. Piece positional components (such as mobility, trapped) // 2. King safety penalties // 3. Other miscellaneous bonuses/penalties // // We build two "margins" to account for these components of the // score. // iAlphaMargin = iBetaMargin = LAZY_EVAL_BASE_MARGIN; // // If (score + alpha_margin) is already > alpha -OR- // (score - beta_margin) is already < beta // // ...then we can stop thinking about lazy eval; the rest of the // computation only increases the margin so we know LE will fail // and can save some work here. // if ((iScoreForSideToMove + iAlphaMargin < iAlpha) || (iScoreForSideToMove - iBetaMargin > iBeta)) { // // Ok, we can't say for sure that we won't take a lazy exit // yet. So do the expensive part of lazy eval estimation and // widen the margin further for king safety issues and passed // pawns. // EstimatePositionalScore(pos, pHash, &iAlphaMargin, &iBetaMargin); if (iScoreForSideToMove + iAlphaMargin <= iAlpha) { INC(ctx->sCounters.tree.u64LazyEvals); if (NULL != piPositional) { *piPositional = iAlphaMargin; } #ifdef EVAL_TIME ctx->sCounters.tree.u64CyclesEvalPreLazy += (SystemReadTimeStampCounter() - uTimer); #endif goto end; } else if (iScoreForSideToMove - iBetaMargin >= iBeta) { INC(ctx->sCounters.tree.u64LazyEvals); if (NULL != piPositional) { *piPositional = iBetaMargin; } #ifdef EVAL_TIME ctx->sCounters.tree.u64CyclesEvalPreLazy += (SystemReadTimeStampCounter() - uTimer); #endif goto end; } } else { // // EstimatePositionalScore (above) is the only place that // refreshes pos->uPiecesPointingAtKing[] -- _EvalKing reads it // unconditionally further down. When this branch is skipped // (position wasn't close enough to the window to risk a lazy // exit), nothing else sets it for the current position, so // _EvalKing would silently score king safety off of whatever // stale value was left over from a prior, unrelated node. // Refresh it here instead -- once, not twice, since this is // the innermost eval loop. // (void)CountKingSafetyDefects(pos, WHITE); (void)CountKingSafetyDefects(pos, BLACK); } #endif #ifdef EVAL_TIME ctx->sCounters.tree.u64CyclesEvalPreLazy += (SystemReadTimeStampCounter() - uTimer); #endif // // If we have to clear/populate the attack table, do it now that // we know we aren't taking a lazy exit. // if (TRUE == fDeferred) { TIMED_EVAL_CALL(ctx, u64CyclesEvalAttackTablePop, _PopulatePawnAttackBits(pos)); } // // Pre-compute some common terms used in per-piece evals: // // This is a scaler based on the size of the army for each side. // pos->uArmyScaler[BLACK] = pos->uNonPawnMaterial[BLACK] - VALUE_KING; pos->uArmyScaler[WHITE] = pos->uNonPawnMaterial[WHITE] - VALUE_KING; pos->uArmyScaler[BLACK] /= VALUE_PAWN; pos->uArmyScaler[WHITE] /= VALUE_PAWN; ASSERT(!(pos->uArmyScaler[BLACK] & 0x80000000)); ASSERT(!(pos->uArmyScaler[WHITE] & 0x80000000)); pos->uArmyScaler[BLACK] = MINU(31, pos->uArmyScaler[BLACK]); pos->uArmyScaler[WHITE] = MINU(31, pos->uArmyScaler[WHITE]); ASSERT(pos->uArmyScaler[BLACK] >= 0); ASSERT(pos->uArmyScaler[BLACK] <= 31); ASSERT(pos->uArmyScaler[WHITE] >= 0); ASSERT(pos->uArmyScaler[WHITE] <= 31); pos->iReducedMaterialDownScaler[BLACK] = pos->iReducedMaterialDownScaler[WHITE] = 0; // // This is a "position is closed|open" number. // pos->uClosedScaler = (pos->uPawnCount[WHITE] + pos->uPawnCount[BLACK]) - (pHash->uNumUnmovedPawns[WHITE] + pHash->uNumUnmovedPawns[BLACK]) + (pHash->uNumRammedPawns); ASSERT(pos->uClosedScaler >= 0); ASSERT(pos->uClosedScaler <= 32); // // Evaluate individual pieces. // // board_representation/EVAL.md section 0/2: replaces the old // cNonPawns[color][1..N] walk + per-piece mailbox lookup + p&0x4/ // IS_KNIGHT branch dispatch (a hard-to-predict branch per piece, // on top of a redundant mailbox read the callee already redoes // for its own ASSERT) with a direct per-type location-bitboard // walk. pos->bbPieces[color][PIECE_TYPE] already exists, // incrementally maintained by move.c, and was simply unused by // Eval() until now -- no new infrastructure, purely a consumer // change. Also retires the cDefer/uDefer "remember the rooks and // queens for later" bookkeeping entirely: since each type is now // its own direct bitboard walk, "evaluate rooks after minors" is // 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. // pos->uMinorsAtHome[BLACK] = pos->uMinorsAtHome[WHITE] = 0; uColor = pos->uToMove; bb = pos->bbPieces[uColor][KNIGHT]; while (bb) { u = FastFirstBit(bb) - 1; bb &= (bb - 1); c = BIT_NUMBER_TO_COOR(u); 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]); #endif } bb = pos->bbPieces[uColor][BISHOP]; while (bb) { u = FastFirstBit(bb) - 1; bb &= (bb - 1); c = BIT_NUMBER_TO_COOR(u); 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]); #endif } uColor = FLIP(uColor); ASSERT(uColor != pos->uToMove); bb = pos->bbPieces[uColor][KNIGHT]; while (bb) { u = FastFirstBit(bb) - 1; bb &= (bb - 1); c = BIT_NUMBER_TO_COOR(u); 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]); #endif } bb = pos->bbPieces[uColor][BISHOP]; while (bb) { u = FastFirstBit(bb) - 1; bb &= (bb - 1); c = BIT_NUMBER_TO_COOR(u); 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]); #endif } // // Evaluate any rook(s) for side on move then for side not on move. // uColor = FLIP(uColor); bb = pos->bbPieces[uColor][ROOK]; while (bb) { u = FastFirstBit(bb) - 1; bb &= (bb - 1); 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]); #endif } uColor = FLIP(uColor); bb = pos->bbPieces[uColor][ROOK]; while (bb) { u = FastFirstBit(bb) - 1; bb &= (bb - 1); 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]); #endif } // // Evaluate any queen(s) for side on move then for side not on move. // uColor = FLIP(uColor); bb = pos->bbPieces[uColor][QUEEN]; while (bb) { u = FastFirstBit(bb) - 1; bb &= (bb - 1); 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]); #endif } uColor = FLIP(uColor); bb = pos->bbPieces[uColor][QUEEN]; while (bb) { u = FastFirstBit(bb) - 1; bb &= (bb - 1); 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]); #endif } // // Evaluate the two kings last. // c = pos->cNonPawns[BLACK][0]; #ifdef DEBUG ASSERT(IS_ON_BOARD(c)); { PIECE pDebugKing = pos->rgSquare[c].pPiece; ASSERT(IS_VALID_PIECE(pDebugKing)); ASSERT(GET_COLOR(pDebugKing) == BLACK); ASSERT(IS_KING(pDebugKing)); } #endif TIMED_EVAL_CALL(ctx, u64CyclesEvalKing, _EvalKing(pos, c, pHash)); 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 c = pos->cNonPawns[WHITE][0]; #ifdef DEBUG ASSERT(IS_ON_BOARD(c)); { PIECE pDebugKing = pos->rgSquare[c].pPiece; ASSERT(IS_VALID_PIECE(pDebugKing)); ASSERT(GET_COLOR(pDebugKing) == WHITE); ASSERT(IS_KING(pDebugKing)); } #endif TIMED_EVAL_CALL(ctx, u64CyclesEvalKing, _EvalKing(pos, c, pHash)); 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 // // Now that we have the whole attack table generated, think about // passed pawns identified by the pawn eval routine again. Also // see if the side not on move has a trapped piece. // #ifdef EVAL_TIME UINT64 u64PostLazyMiscTimer = SystemReadTimeStampCounter(); #endif bb = (pHash->bbPasserLocations[WHITE] | pHash->bbPasserLocations[BLACK]); if (0 != bb) { _EvalPassers(pos, pHash); #ifdef EVAL_DUMP Trace("After passers:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif } #if 0 // // Never used right now. // // Make one more pass over the piece list for the side on move now // that the full attack table is computed to detect // under/unprotected pieces en prise to more valuable enemy pieces // which we missed when building the attack table incrementally. // ctx->sPlyInfo[ctx->uPly].uMinMobility[BLACK] = pos->uMinMobility[BLACK]; ctx->sPlyInfo[ctx->uPly].uMinMobility[WHITE] = pos->uMinMobility[WHITE]; #endif // // _EvalLookForDanger/_EvalTrappedPieces only ever *add* a hint // when they find one; a full eval that finds nothing this time // would otherwise leave a stale, unrelated hint from an earlier // visit to this ply sitting here (still piece-identity-valid by // coincidence). Clear this ply's self-danger slots first so a // clean full eval reliably means a clean slate. // ctx->cEnprise[ctx->uPly][0] = ctx->cEnprise[ctx->uPly][1] = ILLEGAL_COOR; ctx->cTrapped[ctx->uPly] = ILLEGAL_COOR; _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 } // // Roll in the reduced material down scaler terms. // ASSERT(pos->iReducedMaterialDownScaler[BLACK] > -200); ASSERT(pos->iReducedMaterialDownScaler[BLACK] < +200); iAlphaMargin = (pos->iReducedMaterialDownScaler[BLACK] * (SCORE)REDUCED_MATERIAL_DOWN_SCALER[pos->uArmyScaler[BLACK]]) / 8; pos->iScore[BLACK] += iAlphaMargin; ASSERT(pos->iReducedMaterialDownScaler[WHITE] > -200); ASSERT(pos->iReducedMaterialDownScaler[WHITE] < +200); iAlphaMargin = (pos->iReducedMaterialDownScaler[WHITE] * (SCORE)REDUCED_MATERIAL_DOWN_SCALER[pos->uArmyScaler[WHITE]]) / 8; pos->iScore[WHITE] += iAlphaMargin; #ifdef EVAL_TIME ctx->sCounters.tree.u64CyclesEvalPostLazyMisc += (SystemReadTimeStampCounter() - u64PostLazyMiscTimer); #endif // // Almost done // iScoreForSideToMove = (pos->iScore[pos->uToMove] - pos->iScore[FLIP(pos->uToMove)]); #ifdef EVAL_DUMP Trace("At the end:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif // // TODO: detect and discourage blocked positions? // // // TODO: drive the score towards zero as we approach a 50 move w/o // progress draw. // // // Adjust dynamic positional component. // iAlphaMargin = abs(pos->iMaterialBalance[pos->uToMove] - iScoreForSideToMove); if (NULL != piPositional) { *piPositional = iAlphaMargin; } g_Options.iLastEvalScore = iScoreForSideToMove; #ifdef EVAL_TIME ctx->sCounters.tree.u64CyclesInEval += (SystemReadTimeStampCounter() - uTimer); #endif INC(ctx->sCounters.tree.u64FullEvals); end: ASSERT(IS_VALID_SCORE(iScoreForSideToMove)); ASSERT((iScoreForSideToMove > -NMATE) && (iScoreForSideToMove < +NMATE)); ASSERT(abs(ctx->sPlyInfo[ctx->uPly].iKingScore[BLACK]) < 700); ASSERT(abs(ctx->sPlyInfo[ctx->uPly].iKingScore[WHITE]) < 700); #if 0 // Never used. ASSERT(ctx->sPlyInfo[ctx->uPly].uMinMobility[BLACK] <= 100); ASSERT(ctx->sPlyInfo[ctx->uPly].uMinMobility[WHITE] <= 100); #endif return(iScoreForSideToMove); }