summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/chess.h13
-rwxr-xr-xsrc/eval.c457
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];
diff --git a/src/eval.c b/src/eval.c
index 9b8a511..424637e 100755
--- a/src/eval.c
+++ b/src/eval.c
@@ -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]);