diff options
Diffstat (limited to 'src')
| -rwxr-xr-x | src/chess.h | 13 | ||||
| -rwxr-xr-x | src/eval.c | 457 |
2 files changed, 258 insertions, 212 deletions
diff --git a/src/chess.h b/src/chess.h index 137344f..a83a13d 100755 --- a/src/chess.h +++ b/src/chess.h @@ -652,7 +652,18 @@ typedef struct _POSITION SCORE iMaterialBalance[2]; // material balance // temporary storage space for use in eval - COOR cTrapped[2]; + // + // Small fixed-capacity list of zero-mobility piece candidates per + // color (bishop/knight/rook/queen evals each can push one), not + // just the single most-recently-evaluated one -- a single COOR + // here used to silently let a later piece's candidacy overwrite an + // earlier one on the same side, discarding it even if it was a + // genuinely trapped (zero mobility + actually attacked) piece. + // Capacity of 4 is deliberately generous; if a position somehow + // has more than 4 simultaneously zero-mobility non-pawn pieces on + // one side, the rest are just dropped, not a real concern. + COOR cTrapped[2][4]; + ULONG uNumTrapped[2]; ULONG uArmyScaler[2]; ULONG uClosedScaler; SCORE iScore[2]; @@ -208,11 +208,6 @@ static SCORE DOUBLED_PAWN_PENALTY_BY_COUNT[4][9] = }, }; -static SCORE ISOLATED_PAWN_PENALTY_BY_COUNT[9] = -{// 0 1 2 3 4 5 6 7 8 - +0, -3, -7, -9, -16, -35, -50, -85, -100 -}; - static SCORE ISOLATED_PAWN_BY_PAWNFILE[9] = { 0, -7, -8, -9, -10, -10, -9, -8, -7 @@ -220,7 +215,13 @@ static SCORE ISOLATED_PAWN_BY_PAWNFILE[9] = static SCORE ISOLATED_EXPOSED_PAWN = -5; -static SCORE ISOLATED_DOUBLED_PAWN = -11; +// 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. @@ -238,13 +239,42 @@ static SCORE CANDIDATE_PASSER_BY_RANK[2][9] = }; // Note: x2 -static SCORE CONNECTED_PASSERS_BY_RANK[2][9] = +// 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, +96, +74, +48, +21, +10, +5, +0 }, // black - { +0, +0, +5, +10, +21, +48, +74, +96, +0 } // white + { +0, +0, +20, +13, +4, +2, +1, +1, +0 }, // black + { +0, +0, +1, +1, +2, +4, +13, +20, +0 } // white }; -static SCORE SUPPORTED_PASSER_BY_RANK[2][9] = +// 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 @@ -252,7 +282,7 @@ static SCORE SUPPORTED_PASSER_BY_RANK[2][9] = static SCORE OUTSIDE_PASSER_BY_DISTANCE[9] = {// 0 1 2 3 4 5 6 7 8 - +0, +0, +7, +14, +21, +28, +34, +41, +55 + +0, +0, +2, +5, +7, +9, +11, +14, +18 }; static SCORE PASSER_BONUS_AS_MATERIAL_COMES_OFF[32] = @@ -353,13 +383,6 @@ static SCORE BISHOP_UNASSAILABLE_BY_DIST_FROM_EKING[9] = +0, +33, +28, +18, +8, +4, +0, -10, -16 }; -static SCORE BISHOP_IN_CLOSED_POSITION[33] = -{// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - 10, 10, 10, 9, 9, 8, 8, 7, 7, 6, 5, 4, 3, 2, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -// 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 -}; - // // Knight eval terms // --------------------------------------------------------------------------- @@ -454,9 +477,7 @@ static SCORE ROOK_LEADS_PASSER_BY_PASSER_RANK[2][9] = static SCORE KING_TRAPPING_ROOK = -40; -static SCORE ROOK_TRAPPING_EKING = +22; - -static SCORE ROOK_VALUE_AS_PAWNS_COME_OFF[17] = +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 @@ -527,8 +548,6 @@ static SCORE QUEEN_KING_TROPISM[8] = 0, +34, +28, +22, +19, +17, +16, +15 }; -static SCORE QUEEN_ATTACKS_SQ_NEXT_TO_KING = 8; // x #sq_attacked - // // King eval terms // --------------------------------------------------------------------------- @@ -605,6 +624,24 @@ static SCORE KING_SAFETY_BY_COUNTER[42] = -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; typedef struct _DNA_BASE_SIZE { SCORE *pBase; @@ -624,7 +661,6 @@ static DNA_BASE_SIZE g_EvalDNA[] = { DNA_ARRAY(BACKWARD_SHIELDED_BY_LOCATION), DNA_ARRAY(BACKWARD_EXPOSED_BY_LOCATION), DNA_MATRIX(DOUBLED_PAWN_PENALTY_BY_COUNT), - DNA_ARRAY(ISOLATED_PAWN_PENALTY_BY_COUNT), DNA_ARRAY(ISOLATED_PAWN_BY_PAWNFILE), DNA_VAR(ISOLATED_EXPOSED_PAWN), DNA_VAR(ISOLATED_DOUBLED_PAWN), @@ -632,6 +668,7 @@ static DNA_BASE_SIZE g_EvalDNA[] = { 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), @@ -643,7 +680,6 @@ static DNA_BASE_SIZE g_EvalDNA[] = { 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(BISHOP_IN_CLOSED_POSITION), DNA_ARRAY(KNIGHT_CENTRALITY_BONUS), DNA_ARRAY(KNIGHT_KING_TROPISM_BONUS), DNA_ARRAY(KNIGHT_UNASSAILABLE_BY_DIST_FROM_EKING), @@ -657,7 +693,6 @@ static DNA_BASE_SIZE g_EvalDNA[] = { DNA_MATRIX(ROOK_BEHIND_PASSER_BY_PASSER_RANK), DNA_MATRIX(ROOK_LEADS_PASSER_BY_PASSER_RANK), DNA_VAR(KING_TRAPPING_ROOK), - DNA_VAR(ROOK_TRAPPING_EKING), DNA_ARRAY(ROOK_VALUE_AS_PAWNS_COME_OFF), DNA_VAR(ROOK_CONNECTED_VERT), DNA_VAR(ROOK_CONNECTED_HORIZ), @@ -666,10 +701,10 @@ static DNA_BASE_SIZE g_EvalDNA[] = { DNA_ARRAY(QUEEN_MOBILITY_BY_SQUARES), DNA_ARRAY(QUEEN_OUT_EARLY), DNA_ARRAY(QUEEN_KING_TROPISM), - DNA_VAR(QUEEN_ATTACKS_SQ_NEXT_TO_KING), 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) }; @@ -1406,7 +1441,10 @@ Return value: // // If the other side has no pieces then give this candidate an - // extra bonus. + // 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) { @@ -1414,7 +1452,7 @@ Return value: PAWN, c, pHash->iScore[uColor], - CANDIDATE_PASSER_BY_RANK[uColor][RANK(c)], + CANDIDATE_PASSER_BY_RANK[uColor][RANK(c)] / 2, "candidate passer in endgame"); } } @@ -1940,46 +1978,32 @@ Return value: // if (2 == uUnsupportable) { - uPawnFile = FILE(c) + 1; - EVAL_TERM(uColor, - PAWN, - c, - pHash->iScore[uColor], - ISOLATED_PAWN_BY_PAWNFILE[uPawnFile], - "target/isolated pawn"); - // - // Isolated + exposed? Extra penalty. - // - // Tuning can (and has) flipped this sign; not a real invariant. - //ASSERT(ISOLATED_EXPOSED_PAWN < 0); - EVAL_TERM(uColor, - PAWN, - c, - pHash->iScore[uColor], - ((pHash->uCountPerFile[FLIP(uColor)][uPawnFile]==0)* - ISOLATED_EXPOSED_PAWN), - "exposed target"); - - // - // Exponential penalty term + // 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); - - // - // Isolated + doubled? Extra penalty. - // - // Tuning can (and has) flipped this sign; not a real invariant. - //ASSERT(ISOLATED_DOUBLED_PAWN < 0); EVAL_TERM(uColor, PAWN, c, pHash->iScore[uColor], - ((pHash->uCountPerFile[uColor][uPawnFile] > 1) * - ISOLATED_DOUBLED_PAWN), - "doubled + target"); + (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: @@ -2134,18 +2158,13 @@ Return value: ASSERT(uIsolated[WHITE] <= 8); ASSERT(uIsolated[BLACK] >= 0); ASSERT(uIsolated[BLACK] <= 8); - EVAL_TERM(BLACK, - PAWN, - ILLEGAL_COOR, - pHash->iScore[BLACK], - ISOLATED_PAWN_PENALTY_BY_COUNT[uIsolated[BLACK]], - "exponential isolated"); - EVAL_TERM(WHITE, - PAWN, - ILLEGAL_COOR, - pHash->iScore[WHITE], - ISOLATED_PAWN_PENALTY_BY_COUNT[uIsolated[WHITE]], - "exponential isolated"); + // + // 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 @@ -3429,6 +3448,39 @@ Return value: // ====================================================================== // +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, @@ -3584,15 +3636,17 @@ Return value: "good/bad transient pawns"); // - // Bonus to bishops in very open positions. + // 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. // - ASSERT(pos->uClosedScaler <= 32); - EVAL_TERM(uColor, - BISHOP, - c, - pos->iScore[uColor], - BISHOP_IN_CLOSED_POSITION[pos->uClosedScaler], - "in closed/open position"); // // Bishop mobility (and update attack table bits) @@ -3657,7 +3711,7 @@ Return value: // if (uTotalMobility == 0) { - pos->cTrapped[uColor] = c; + _RecordTrappedCandidate(pos, uColor, c); } uTotalMobility /= 2; ASSERT((pos->uMinMobility[uColor] & 0x80000000) == 0); @@ -3666,8 +3720,16 @@ Return value: uTotalMobility); // - // Look for "active bad bishops". See if square c is safe from - // enemy pawns. + // 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 really a + // bishop-specific king-tropism/outpost bonus; despite the + // historical "active bad bishop" name, it does NOT check whether + // the bishop is actually "bad" (see the good/bad pawn-count terms + // above) before applying. Candidate for folding into a unified + // per-piece-type tropism system alongside + // KNIGHT_KING_TROPISM_BONUS/QUEEN_KING_TROPISM (shelved for now, + // pending a look at what every piece type currently does). // bb = pHash->bbPawnLocations[FLIP(uColor)] & (~pHash->bbStationaryPawns[FLIP(uColor)]); @@ -3764,7 +3826,6 @@ Return value: ULONG uColor; ULONG uPawnsSupporting; ULONG u; - ULONG uPawnFile; ULONG uMobilitySquares; SCORE i; ULONG uDist; @@ -3786,10 +3847,6 @@ Return value: ASSERT(pos->uMinorsAtHome[uColor] <= 4); // - // TODO: Don't block unmoved E2/D2 pawns - // - - // // Centrality bonus // EVAL_TERM(uColor, @@ -3894,9 +3951,14 @@ Return value: } // - // Give a bonus for blockading an enemy backward pawn. Note: - // we also reward pieces for blocking enemy passers in the - // passer code. + // 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)]; @@ -3913,16 +3975,15 @@ Return value: } // - // A knight with an open file behind it is good. + // 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. // - uPawnFile = FILE(c) + 1; - EVAL_TERM(uColor, - KNIGHT, - c, - pos->iScore[uColor], - ((pHash->uCountPerFile[uColor][uPawnFile] == 0) * - KNIGHT_ON_INTERESTING_SQUARE_BY_RANK[uColor][RANK(c)]), - "open file behind"); // // Do mobilility and piece relevance. Also update attack tables. @@ -3971,7 +4032,7 @@ Return value: "knight mobility"); if (uMobilitySquares == 0) { - pos->cTrapped[uColor] = c; + _RecordTrappedCandidate(pos, uColor, c); } ASSERT((pos->uMinMobility[uColor] & 0x80000000) == 0); ASSERT((uMobilitySquares & 0x80000000) == 0); @@ -4036,7 +4097,6 @@ Return value: }; PIECE p; - PIECE pFriendRook; ULONG uPawnFile = FILE(c) + 1; ULONG uColor; ULONG u; @@ -4178,47 +4238,15 @@ Return value: } // - // Look for rooks that are trapping enemy kings. + // 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.) // - if (uColor == WHITE) - { - pFriendRook = WHITE_ROOK; - - // - // Rook on 7th/8th rank with an enemy king back there. - // - if ((c < A6) && (pos->cNonPawns[BLACK][0] < A6)) - { - ASSERT(RANK(c) > 6); - ASSERT(RANK(pos->cNonPawns[BLACK][0]) > 6); - - EVAL_TERM(WHITE, - ROOK, - c, - pos->iScore[WHITE], - ROOK_TRAPPING_EKING, - "rook trapping enemy king"); - } - } - else - { - pFriendRook = BLACK_ROOK; - - // - // Rook on 7th/8th rank with an enemy king back there. - // - if ((c > H3) && (pos->cNonPawns[WHITE][0] > H3)) - { - ASSERT(RANK(c) < 3); - ASSERT(RANK(pos->cNonPawns[WHITE][0]) < 3); - EVAL_TERM(BLACK, - ROOK, - c, - pos->iScore[BLACK], - ROOK_TRAPPING_EKING, - "rook trapping enemy king"); - } - } // // Rooks increase in value as pawns come off: @@ -4314,7 +4342,7 @@ Return value: // if (uTotalMobility == 0) { - pos->cTrapped[uColor] = c; + _RecordTrappedCandidate(pos, uColor, c); } // @@ -4432,7 +4460,6 @@ Return value: ULONG u; ULONG uBit; PMOBILITY_HELPER pFun; - ULONG uNearKing = 0; COOR cKing; ASSERT(IS_ON_BOARD(c)); @@ -4498,7 +4525,6 @@ Return value: // What did we hit? // p = pos->rgSquare[cSquare].pPiece; - uNearKing += (DISTANCE(cSquare, cKing) <= 1); pFun = QMobJumpTable[uColor][p]; if (TRUE == (*pFun)(pos, cSquare, @@ -4527,7 +4553,7 @@ Return value: // if (uTotalMobility == 0) { - pos->cTrapped[uColor] = c; + _RecordTrappedCandidate(pos, uColor, c); } ASSERT((pos->uMinMobility[uColor] & 0x80000000) == 0); ASSERT((uTotalMobility & 0x80000000) == 0); @@ -4535,15 +4561,17 @@ Return value: uTotalMobility); // - // If queen points near enemy king, that's a good thing too. + // 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. // - ASSERT(uNearKing <= 6); - EVAL_TERM(uColor, - QUEEN, - c, - pos->iScore[uColor], - (uNearKing * QUEEN_ATTACKS_SQ_NEXT_TO_KING), - "pointing near enemy K"); } static void @@ -4569,13 +4597,16 @@ Return value: 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; @@ -4690,6 +4721,19 @@ Return value: pos->rgSquare[cSquare].bvAttacks[uColor].small.uKing = 1; bvDefend = pos->rgSquare[cSquare].bvAttacks[uColor].uSmall; + // + // 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. + // + uQueenNearKing += + (pos->rgSquare[cSquare].bvAttacks[ufColor].small.uQueen | + pos->rgSquare[cSquare].bvAttacks[ufColor].xray.uQueen); + if (bvAttack != 0) { bvPattern |= (bvAttack | bvXray); @@ -4725,65 +4769,25 @@ Return value: Trace("%s KS Counter post-squares: %u\n", COLOR_NAME(uColor), uCounter); #endif - if (IS_ON_BOARD(c - 1)) - { - u = FILE(c - 1) + 1; - v = (pHash->uCountPerFile[WHITE][u] > 0) + - (pHash->uCountPerFile[BLACK][u] > 0); - ASSERT((v >= 0) && (v <= 2)); - - // Note: open rook files are worse than open interior files - uCounter += (KingFileDefects[v] + ((v < 2) && ((u == 1) || (u == 8)))); - - bb = pHash->bbPawnLocations[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)]; - } - } - - u = FILE(c) + 1; - v = (pHash->uCountPerFile[WHITE][u] > 0) + - (pHash->uCountPerFile[BLACK][u] > 0); - ASSERT((v >= 0) && (v <= 2)); - - // Note: open rook files are worse than open interior files - uCounter += (KingFileDefects[v] + ((v < 2) && ((u == 1) || (u == 8)))); - bb = pHash->bbPawnLocations[ufColor] & BBFILE[u - 1]; - if (bb) + // + // 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++) { - if (uColor == WHITE) - { - cSquare = CoorFromBitBoardRank1ToRank8(&bb); - } - else + cFileSq = c + w; + if (!IS_ON_BOARD(cFileSq)) { - cSquare = CoorFromBitBoardRank8ToRank1(&bb); + continue; } - ASSERT(IS_ON_BOARD(cSquare)); - uCounter += KingStormingPawnDefects[DISTANCE(cSquare, c)]; - } - - if (IS_ON_BOARD(c + 1)) - { - ASSERT((FILE(c + 1) + 1) == (u + 1)); - u += 1; + u = FILE(cFileSq) + 1; v = (pHash->uCountPerFile[WHITE][u] > 0) + (pHash->uCountPerFile[BLACK][u] > 0); ASSERT((v >= 0) && (v <= 2)); - - // Note: open rook files are worse than open interior files uCounter += (KingFileDefects[v] + ((v < 2) && ((u == 1) || (u == 8)))); - bb = pHash->bbPawnLocations[ufColor] & BBFILE[u - 1]; if (bb) { @@ -4835,6 +4839,14 @@ Return value: 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. @@ -4922,7 +4934,7 @@ Return value: ASSERT(abs((INT)RANK(c) - (INT)RANK(cSquare)) <= 1); i = 2; - i += SUPPORTED_PASSER_BY_RANK[uColor][RANK(cSquare)]; + i += KING_SUPPORTING_OWN_PASSER_BY_RANK[uColor][RANK(cSquare)]; switch(uColor) { case WHITE: @@ -5417,7 +5429,10 @@ Return value: **/ { COOR c; + COOR cBestOwnTrapped; + ULONG uBestOwnTrappedValue; ULONG uColor; + ULONG u; POSITION *pos = &ctx->sPosition; #ifdef DEBUG PIECE p; @@ -5425,9 +5440,12 @@ Return value: FOREACH_COLOR(uColor) { - c = pos->cTrapped[uColor]; - if (IS_ON_BOARD(c)) + 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)) { #ifdef DEBUG @@ -5447,10 +5465,27 @@ Return value: } else { - RecordTrappedPiece(ctx, c); + // + // 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 rather than + // whichever happened to be found last. + // + if (PIECE_VALUE(pos->rgSquare[c].pPiece) > + uBestOwnTrappedValue) + { + uBestOwnTrappedValue = + PIECE_VALUE(pos->rgSquare[c].pPiece); + cBestOwnTrapped = c; + } } } } + if (IS_ON_BOARD(cBestOwnTrapped)) + { + RecordTrappedPiece(ctx, cBestOwnTrapped); + } } } @@ -5573,7 +5608,7 @@ Return value: // ASSERT(!InCheck(pos, pos->uToMove)); pos->uMinMobility[BLACK] = pos->uMinMobility[WHITE] = 100; - pos->cTrapped[BLACK] = pos->cTrapped[WHITE] = ILLEGAL_COOR; + pos->uNumTrapped[BLACK] = pos->uNumTrapped[WHITE] = 0; pos->iScore[BLACK] = (pos->uPawnMaterial[BLACK] + pos->uNonPawnMaterial[BLACK]); |
