summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-10-10 10:29:48 -0700
committerScott Gasch <[email protected]>2026-10-10 10:29:48 -0700
commit03545c4600df81b1a47dbc63fc606c065e857d8b (patch)
treea433fdce69fd27359679ad91e0ebedd52e2506cd /src
parentfbdcb9d76dd2444d8bc631331eaeb9435cc247f8 (diff)
Eval mobility/pseudo-mobility tweaks; drop unproductive move-ordering experiments.
eval.c: add bishop/rook x-ray pseudo-mobility terms (squares seen past a directly-attacked higher-value piece), trim pawn-centrality edge penalty and bishop-pair bonus. chess.h/dynamic.c/generate.c/search.c/searchsup.c: revert three move-ordering experiments (recapture bonus, flee-to-safety GOOD_MOVE promotion, ply-2 move-pair bonus) that calibrated with real fail-high signal but never converted into a measurable node-count or solve-rate win on the curated suites -- see MOVE_ORDERING_IDEAS.md for the evidence and reasoning behind each drop. Add MOVE_ORDERING_EVIDENCE_COLLECTION as its own GNUmakefile flag, independent of PERF_COUNTERS, for any future such harness. command.c: remove the KING_DEFECT_DUMP-gated evalkingdump command, dead since the king-safety remine work moved to eval_tune/king_safety_data/. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/GNUmakefile12
-rwxr-xr-xsrc/chess.h95
-rw-r--r--src/command.c110
-rw-r--r--src/dynamic.c108
-rw-r--r--src/eval.c890
-rw-r--r--src/generate.c38
-rw-r--r--src/search.c37
-rw-r--r--src/searchsup.c14
8 files changed, 519 insertions, 785 deletions
diff --git a/src/GNUmakefile b/src/GNUmakefile
index 8a4904b..5879d31 100644
--- a/src/GNUmakefile
+++ b/src/GNUmakefile
@@ -6,6 +6,13 @@
# EVAL_DUMP=1: make a version that can dump eval breakdowns
# EVAL_TIME=1: make a version that counts cycles spent in eval
# PERF_COUNTERS=1: make version with perf counters enabled
+# MOVE_ORDERING_EVIDENCE_COLLECTION=1: instrument the move-ordering
+# evidence-bucket harnesses (countermove/move-pair hit tracking,
+# CM/MP evidence-bucket tries/FH -- see MOVE_ORDERING_IDEAS.md) --
+# a dedicated flag, independent of PERF_COUNTERS, since this
+# collection (per-move IS_SAME_MOVE checks + bucket lookups on every
+# quiet move scored) is its own cost, not bundled into the general
+# perf-counter set by default.
# BOUNDS_CHECKING=1: make version with bounds checking enabled
# MP=1: make version for multi-processor machine
# DUMP_TREE=1: internal tree dumping version
@@ -136,7 +143,7 @@ ifndef DISABLE_BITBOARD_MOVEGEN
endif
ifdef EVERYTHING
-PROFILE += -DEVAL_DUMP -DEVAL_TIME -DPERF_COUNTERS -DMP -DSMP -DTEST_NULL -DDUMP_TREE -fbounds-checking
+PROFILE += -DEVAL_DUMP -DEVAL_TIME -DPERF_COUNTERS -DMP -DSMP -DTEST_NULL -DDUMP_TREE -fbounds-checking -DMOVE_ORDERING_EVIDENCE_COLLECTION
else
ifdef EVAL_DUMP
PROFILE += -DEVAL_DUMP
@@ -147,6 +154,9 @@ endif
ifdef PERF_COUNTERS
PROFILE += -DPERF_COUNTERS
endif
+ifdef MOVE_ORDERING_EVIDENCE_COLLECTION
+PROFILE += -DMOVE_ORDERING_EVIDENCE_COLLECTION
+endif
ifdef BOUNDS_CHECKING
PROFILE += -fbounds-checking
endif
diff --git a/src/chess.h b/src/chess.h
index c66dbc0..7e37245 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -2163,6 +2163,18 @@ _assert(CHAR *szFile, ULONG uLine);
#define INC(x)
#endif
+// Dedicated gate for the move-ordering evidence-bucket harnesses
+// (countermove/move-pair hit tracking, CM/MP evidence buckets -- see
+// MOVE_ORDERING_IDEAS.md) -- deliberately a separate flag/macro from
+// INC/PERF_COUNTERS above, not nested under it, so this collection's
+// own cost (per-move IS_SAME_MOVE checks + bucket lookups) can be
+// toggled independently of the general perf-counter set.
+#ifdef MOVE_ORDERING_EVIDENCE_COLLECTION
+#define MOI_INC(x) ((x) += 1)
+#else
+#define MOI_INC(x)
+#endif
+
#define BREAKPOINT SystemDebugBreakpoint()
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
@@ -2910,6 +2922,27 @@ TestMakeUnmakeMove(void);
// moving it, don't just nudge it up.
#define COUNTERMOVE_TIER_BONUS (0x03000000)
+// DROPPED (MOVE_ORDERING_IDEAS.md item 4, recapture bonus): a capture
+// landing on the opponent's last move's destination square showed a
+// real, well-calibrated FH% signal at SEE >= 0 (64-65% vs. a 49-52%
+// all-captures-at-the-same-SEE-sign baseline, consistent across all
+// three curated suites, large samples -- MOVE_ORDERING_EVIDENCE_
+// COLLECTION's RECAPTURE_BUCKETS/u64RecaptureTries/FH vs.
+// u64CaptureBaselineTries/FH, both removed along with this). Two
+// magnitudes tried as a same-tier nudge in generate.c's
+// _ScoreAllMoves (s >= 0 capture branch), neither converted into a
+// tree-size win: 50 (over 3x the ~16-unit-wide MVV/LVA spread this
+// branch actually sorts by -- PIECE_VALUE_OVER_100(captured) -
+// PIECE_VALUE_OVER_100(mover), ranges ~112-128 across every capture
+// combination -- a near-dominant override, not a tiebreak) caused a
+// real, confirmed tactical loss (ECM.735, see MOVE_ORDERING_IDEAS.md);
+// 4 (inside that band) fixed the regression but still produced no
+// node-count improvement on any of the three curated suites. Same
+// shape as the move-pair backlog entry: a real FH% edge that
+// SelectBestWithHistory/MVV/LVA's existing ordering apparently already
+// captures most of, with no mechanism tried converting the residual
+// signal into a leaner tree.
+//
// Evidence threshold gating the promotion above -- the same
// history+continuation sum already used to score ordinary leftovers,
// evaluated for the specific countermove-matched move. Calibration
@@ -2920,14 +2953,45 @@ TestMakeUnmakeMove(void);
// three curated suites, the strongest defensible bar found.
#define COUNTERMOVE_EVIDENCE_THRESHOLD (10000)
-// EXPERIMENT: flee-to-safety same-tier nudge -- a flat bonus added at
-// selection time (movesup.c's SelectBestWithHistory) for a quiet
-// leftover move whose origin square is currently en prise, alongside
-// history/continuation. Unlike the retired GOOD_MOVE-tier promotion,
-// this never changes a move's classification -- it only nudges its
-// rank among other leftovers. Swept empirically via the zero-selection-
-// budget diagnostic, same methodology as CONTINUATION_SCALE.
-#define FLEE_BONUS (100000)
+// DROPPED (MOVE_ORDERING_IDEAS.md item 6, flee-to-safety revival): a
+// quiet move whose origin matched the cached en-prise hint
+// (FindEnprisePiece, dynamic.c), evidence-gated with the exact same
+// formula/threshold as COUNTERMOVE_TIER_BONUS/COUNTERMOVE_EVIDENCE_
+// THRESHOLD above. Calibration found a real signal (evidence >= 10000
+// hit 56-68% FH vs. a ~1.35-1.54% unprivileged-leftover baseline,
+// comparable to or exceeding countermove's own bar) -- but the
+// qualifying population was too rare (2,444-9,209 tries across an
+// entire suite run, vs. countermove's tens-to-hundreds-of-thousands)
+// to register any measurable sd10 node-count or solve-count effect
+// either way once implemented as a GOOD_MOVE-tier escape. Not a
+// magnitude-tuning problem like move-pair/recapture -- there simply
+// isn't enough volume for any mechanism acting on this signal to move
+// the aggregate needle. u64FleeTries/FH and the scoring/precompute
+// code were fully reverted.
+//
+// DROPPED (MOVE_ORDERING_IDEAS.md item 3, ply-2 move-pair bonus): three
+// mechanisms tried against a real, strongly-calibrated signal
+// (butterfly-history-gated match against the move two plies back hit
+// 53-66% FH in its top evidence band vs. ~1.2-1.4% unprivileged-
+// leftover baseline, on large samples across all three curated
+// suites) -- a GOOD_MOVE-tier escape (+1.4-1.6% nodes at sd10), a
+// dominant same-tier nudge at FLEE_BONUS's old magnitude (+2-20%
+// nodes, worse), and a scaled-down proportionate nudge (mixed,
+// net flat-to-negative, worst specifically on the smallest/noisiest
+// curated suite). No mechanism converted the signal into a tree-size
+// win; SelectBestWithHistory's existing PSQT+history+continuation
+// scoring apparently already captures most of what it offers. The
+// mvMovePair table and its evidence-bucket harness were removed along
+// with it -- re-derive from git history if this is ever revisited
+// with a different approach.
+//
+// FLEE_BONUS itself (flee-to-safety same-tier nudge, a quiet leftover
+// move whose origin square is currently en prise) is separately dead
+// code in movesup.c's SelectBestWithHistory (commented out) -- retired
+// after the GOOD_MOVE-tier version it replaced was shown, by evidence-
+// calibration, to perform at the plain-leftover baseline for the vast
+// majority of its (huge) zero-evidence population. Revive either only
+// if re-measured and shown to actually help.
// EXPERIMENT: is history+continuation evidence actually predictive of
// a countermove match's FH%? Bucket every tried countermove-matched
@@ -2953,10 +3017,10 @@ GenerateMoves(SEARCHER_THREAD_CONTEXT *ctx,
// testgenerate.c's correctness/speed harness can call it directly
// regardless of whether GENERATE_KNIGHT_BITBOARD is defined for this
// build.
-BITBOARD
+BITBOARD FORCEINLINE
_BuildFriendlySideBB(POSITION *pos, ULONG uSide);
-BITBOARD
+BITBOARD FORCEINLINE
_BuildFullOccupiedBB(POSITION *pos);
// board_representation/MOVEGEN_MIGRATION.md section 6b -- exposed so
@@ -4563,6 +4627,17 @@ TestBitboards(void);
//
// dynamic.c
//
+// EXPERIMENT: flat (depth-independent) saturating history update, after
+// Crafty's history.c -- *pu += (HISTORY_MAX - *pu) >> HISTORY_UPDATE_SHIFT
+// on fail-high, *pu -= *pu >> HISTORY_UPDATE_SHIFT on a miss. Replaces the
+// old (depth+1)^2-scaled growth (which biases toward root-near nodes,
+// where depth is large, over the far more numerous near-tip nodes where
+// it's small) with a flat update whose magnitude never depends on depth
+// at all. Self-bounding by construction (asymptotically approaches
+// HISTORY_MAX, never exceeds it), so the old overflow-triggered global
+// >>=4 rescale is no longer needed for this table.
+#define HISTORY_MAX (8192)
+#define HISTORY_UPDATE_SHIFT (5)
extern ULONG g_HistoryCounters[14][128];
// Continuation history: same growth/decay math as g_HistoryCounters
diff --git a/src/command.c b/src/command.c
index 1902784..b30de2b 100644
--- a/src/command.c
+++ b/src/command.c
@@ -547,108 +547,6 @@ Return value:
}
-#ifdef KING_DEFECT_DUMP
-COMMAND(EvalKingDumpCommand)
-/**
-
-Routine description:
-
- This function implements the 'evalkingdump' engine command, only
- built under KING_DEFECT_DUMP. Runs a full static eval (same
- persistent-context pattern as EvalCommand) then prints one
- space-separated key=value line per side with _EvalKing's raw,
- per-defect breakdown (chess.h's KING_DEFECT_DUMP_INFO) instead of
- the single collapsed score -- feeds an offline regression against
- real mate outcomes. See CLAUDE.md's king-safety discussion.
-
- Usage:
-
- evalkingdump
-
-Parameters:
-
- The COMMAND macro hides four arguments from the input parser:
-
- CHAR *szInput : the full line of input
- ULONG argc : number of argument chunks
- CHAR *argv[] : array of ptrs to each argument chunk
- POSITION *pos : a POSITION pointer to operate on
-
-Return value:
-
- void
-
-**/
-{
- static SEARCHER_THREAD_CONTEXT *ctx = NULL;
- static ULONG uLastSeenDnaGeneration = (ULONG)-1;
- static const CHAR *szPieceName[KING_RING_NUM_PIECE_TYPES] =
- { "pawn", "minor", "rook", "queen" };
- ULONG uColor;
- ULONG uSlot, uPieceType;
- KING_DEFECT_DUMP_INFO *p;
-
- if (NULL == ctx)
- {
- ctx = SystemAllocateMemory(sizeof(SEARCHER_THREAD_CONTEXT));
- if (NULL == ctx)
- {
- Trace("Out of memory.\n");
- return;
- }
- InitializeSearcherContext(pos, ctx);
- uLastSeenDnaGeneration = g_uDnaGeneration;
- }
- else if (uLastSeenDnaGeneration != g_uDnaGeneration)
- {
- InitializeSearcherContext(pos, ctx);
- uLastSeenDnaGeneration = g_uDnaGeneration;
- }
- else
- {
- ReInitializeSearcherContext(pos, ctx);
- }
- (void)Eval(ctx, -INFINITY_SCORE, +INFINITY_SCORE, NULL);
-
- for (uColor = 0; uColor < 2; uColor++)
- {
- p = &ctx->sKingDefectDump[uColor];
- Trace("KINGDEFECTDUMP color=%s", COLOR_NAME(uColor));
- for (uSlot = 0; uSlot < KING_RING_NUM_SLOTS; uSlot++)
- {
- if (!p->fOnBoard[uSlot])
- {
- Trace(" sq%u_offboard=1", uSlot);
- continue;
- }
- for (uPieceType = 0; uPieceType < KING_RING_NUM_PIECE_TYPES; uPieceType++)
- {
- Trace(" sq%u_atk_%s=%u",
- uSlot, szPieceName[uPieceType],
- p->fAttackedByPieceType[uSlot][uPieceType]);
- }
- Trace(" sq%u_undef=%u sq%u_def=%u sq%u_xray=%u sq%u_flight=%u",
- uSlot, p->fAttackedUndefended[uSlot],
- uSlot, p->fAttackedDefended[uSlot],
- uSlot, p->fXrayedUnattacked[uSlot],
- uSlot, p->fEmptyFlight[uSlot]);
- }
- Trace(" filestorm=%u filestormopenfile=%u filestormpawn=%u "
- "queenproximity=%u initloc=%u countertotal=%u "
- "safetyvalue=%u attackerarmyscaler=%u castled=%u minorsathome=%u "
- "misscastleshort=%u misscastlelong=%u realsafety=%d\n",
- p->uFileStormDefects, p->uFileStormOpenFile, p->uFileStormStormingPawn,
- p->uQueenNearKing,
- p->uInitialCounterByLocation, p->uCounterTotal,
- p->uSafetyTableValue, p->uAttackerArmyScaler,
- p->fCastled, p->uMinorsAtHome,
- p->fMissingCastleShort, p->fMissingCastleLong,
- p->iRealSafety);
- }
-}
-#endif
-
-
COMMAND(QSearchCommand)
/**
@@ -2579,14 +2477,6 @@ COMMAND_PARSER_ENTRY g_ParserTable[] =
FALSE,
FALSE,
"Run a static eval" },
-#ifdef KING_DEFECT_DUMP
- { "evalkingdump",
- EvalKingDumpCommand,
- FALSE,
- FALSE,
- FALSE,
- "Dump _EvalKing's raw per-defect breakdown for both sides" },
-#endif
{ "evaldna",
EvalDnaCommand,
FALSE,
diff --git a/src/dynamic.c b/src/dynamic.c
index b0bad91..a56cf29 100644
--- a/src/dynamic.c
+++ b/src/dynamic.c
@@ -389,6 +389,7 @@ Return value:
}
u = MOVE_TO_INDEX(mvLast);
+#ifdef MOVE_ORDERING_EVIDENCE_COLLECTION
//
// Hit-rate instrumentation: did the counter-move slot for this
// previous move already predict the move that just won here?
@@ -397,13 +398,14 @@ Return value:
if ((ctx->mvCounter[u][0].uMove != 0) ||
(ctx->mvCounter[u][1].uMove != 0))
{
- INC(ctx->sCounters.tree.u64CounterMoveTries);
+ MOI_INC(ctx->sCounters.tree.u64CounterMoveTries);
if (IS_SAME_MOVE(mv, ctx->mvCounter[u][0]) ||
IS_SAME_MOVE(mv, ctx->mvCounter[u][1]))
{
- INC(ctx->sCounters.tree.u64CounterMoveHits);
+ MOI_INC(ctx->sCounters.tree.u64CounterMoveHits);
}
}
+#endif
// A/B test: write-gating by depth (reject shallower overwrites)
// measured *worse* hit rate with no EBF gain -- this table is
@@ -686,37 +688,18 @@ Return value:
**/
{
- ULONG x, y;
- ULONG uVal;
ULONG *pu;
ASSERT(!IS_CAPTURE_OR_PROMOTION(mv));
- uVal = uDepth / ONE_PLY;
- ASSERT(uVal >= 0);
- ASSERT(uVal <= MAX_PLY_PER_SEARCH);
- uVal += 1;
- uVal *= uVal;
- ASSERT(uVal > 0);
-
- pu = &(g_HistoryCounters[mv.pMoved][mv.cTo]);
- LOCK_DYN;
- *pu += uVal;
//
- // Make sure that the history weight doesn't get large enough to
- // affect the move flags or else our move ordering algorithm is
- // screwed.
+ // EXPERIMENT: flat/saturating update (see HISTORY_MAX/
+ // HISTORY_UPDATE_SHIFT comment, chess.h) -- depth-independent,
+ // asymptotically approaches HISTORY_MAX, never exceeds it.
//
- while (*pu & ~STRIP_OFF_FLAGS)
- {
- for (x = 0; x <= WHITE_KING; x++)
- {
- FOREACH_SQUARE(y)
- {
- g_HistoryCounters[x][y] >>= 4;
- }
- }
- }
+ pu = &(g_HistoryCounters[mv.pMoved][mv.cTo]);
+ LOCK_DYN;
+ *pu += (HISTORY_MAX - *pu) >> HISTORY_UPDATE_SHIFT;
#ifdef MP
//
@@ -734,14 +717,16 @@ Return value:
UNLOCK_DYN;
}
-static void
-_DecrementMoveHistoryCounter(MOVE mv,
+static void
+_DecrementMoveHistoryCounter(MOVE mv,
ULONG uDepth)
/**
-
+
Routine description:
Decrease a move's history counter in the global history table.
+ EXPERIMENT: flat/saturating decay, depth-independent -- see
+ HISTORY_MAX/HISTORY_UPDATE_SHIFT comment in chess.h.
Parameters:
@@ -754,27 +739,14 @@ Return value:
**/
{
- ULONG uVal;
ULONG *pu;
ASSERT(!IS_CAPTURE_OR_PROMOTION(mv));
- uVal = uDepth / ONE_PLY;
- ASSERT(uVal >= 0);
- ASSERT(uVal <= MAX_PLY_PER_SEARCH);
- uVal /= 4;
- uVal += 1;
- ASSERT(uVal > 0);
+ ASSERT(uDepth <= MAX_PLY_PER_SEARCH * ONE_PLY); // param kept for signature parity w/ increment
pu = &(g_HistoryCounters[mv.pMoved][mv.cTo]);
LOCK_DYN;
- if (*pu >= uVal)
- {
- *pu -= uVal;
- }
- else
- {
- *pu = 0;
- }
+ *pu -= (*pu >> HISTORY_UPDATE_SHIFT);
_RecordMoveFailure(mv);
UNLOCK_DYN;
}
@@ -787,9 +759,9 @@ _IncrementContinuationCounter(MOVE mvPrev, MOVE mv, ULONG uRemainingDepth)
Routine description:
Continuation-history counterpart to _IncrementMoveHistoryCounter --
- same (depth+1)^2 growth, same overflow-driven rescale, but keyed by
- (mvPrev, mv) instead of mv alone. No-op if there was no previous
- move (root).
+ EXPERIMENT: same flat/saturating depth-independent update (see
+ HISTORY_MAX/HISTORY_UPDATE_SHIFT, chess.h), keyed by (mvPrev, mv)
+ instead of mv alone. No-op if there was no previous move (root).
Parameters:
@@ -803,33 +775,19 @@ Return value:
**/
{
- ULONG uVal;
ULONG *pu;
- ULONG x;
ASSERT(!IS_CAPTURE_OR_PROMOTION(mv));
if (0 == mvPrev.uMove)
{
return;
}
- uVal = uRemainingDepth / ONE_PLY;
- ASSERT(uVal >= 0);
- ASSERT(uVal <= MAX_PLY_PER_SEARCH);
- uVal += 1;
- uVal *= uVal;
- ASSERT(uVal > 0);
+ ASSERT(uRemainingDepth <= MAX_PLY_PER_SEARCH * ONE_PLY); // unused by design now
pu = &(g_ContinuationHistory[(MOVE_TO_CONT_KEY(mvPrev) * CONT_KEY_RANGE) +
MOVE_TO_CONT_KEY(mv)]);
LOCK_DYN;
- *pu += uVal;
- while (*pu & ~STRIP_OFF_FLAGS)
- {
- for (x = 0; x < CONTINUATION_TABLE_SIZE; x++)
- {
- g_ContinuationHistory[x] >>= 4;
- }
- }
+ *pu += (HISTORY_MAX - *pu) >> HISTORY_UPDATE_SHIFT;
UNLOCK_DYN;
}
@@ -840,8 +798,9 @@ _DecrementContinuationCounter(MOVE mvPrev, MOVE mv, ULONG uRemainingDepth)
Routine description:
- Continuation-history counterpart to _DecrementMoveHistoryCounter.
- No-op if there was no previous move (root).
+ Continuation-history counterpart to _DecrementMoveHistoryCounter --
+ EXPERIMENT: flat/saturating decay, depth-independent. No-op if there
+ was no previous move (root).
Parameters:
@@ -855,7 +814,6 @@ Return value:
**/
{
- ULONG uVal;
ULONG *pu;
ASSERT(!IS_CAPTURE_OR_PROMOTION(mv));
@@ -863,24 +821,12 @@ Return value:
{
return;
}
- uVal = uRemainingDepth / ONE_PLY;
- ASSERT(uVal >= 0);
- ASSERT(uVal <= MAX_PLY_PER_SEARCH);
- uVal /= 4;
- uVal += 1;
- ASSERT(uVal > 0);
+ ASSERT(uRemainingDepth <= MAX_PLY_PER_SEARCH * ONE_PLY); // unused by design now
pu = &(g_ContinuationHistory[(MOVE_TO_CONT_KEY(mvPrev) * CONT_KEY_RANGE) +
MOVE_TO_CONT_KEY(mv)]);
LOCK_DYN;
- if (*pu >= uVal)
- {
- *pu -= uVal;
- }
- else
- {
- *pu = 0;
- }
+ *pu -= (*pu >> HISTORY_UPDATE_SHIFT);
UNLOCK_DYN;
}
diff --git a/src/eval.c b/src/eval.c
index 50a5e6f..a909953 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -192,13 +192,13 @@ COOR JUMPING_RANK[2] = { 7, 2 };
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, 8, 8, 5, 0, -8, 0,0,0,0,0,0,0,0,
+ -5, 0, 0, 0, 0, 0, 0, -5, 0,0,0,0,0,0,0,0,
+ -5, 0, 5, 5, 5, 5, 0, -5, 0,0,0,0,0,0,0,0,
+ -5, 0, 5, 8, 8, 5, 0, -5, 0,0,0,0,0,0,0,0,
// -------------------------------------------------------------------
- -8, 0, 5, 8, 8, 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,
+ -5, 0, 5, 8, 8, 5, 0, -5, 0,0,0,0,0,0,0,0,
+ -5, 0, 5, 5, 5, 5, 0, -5, 0,0,0,0,0,0,0,0,
+ -5, 0, 0, 0, 0, 0, 0, -5, 0,0,0,0,0,0,0,0,
0, 0, 0, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0
};
@@ -412,7 +412,7 @@ static SCORE UNDEVELOPED_MINORS_IN_OPENING[5] =
// pieces on the board except two minors each."
// --Larry Kaufman, IM
// "Evaluation of Material Imbalance"
-static SCORE BISHOP_PAIR_BONUS = 40;
+static SCORE BISHOP_PAIR_BONUS = 30;
static SCORE STATIONARY_PAWN_ON_BISHOP_COLOR[128] =
{
@@ -449,6 +449,22 @@ static SCORE BISHOP_MOBILITY_BY_SQUARES[14] =
};// ^ |
+//
+// Pseudo-mobility: squares this bishop can "see" past a directly-attacked
+// higher-value enemy piece (rook/queen/king) -- real positional pressure
+// (the squares a capture or a moving-away enemy piece would open up), not
+// real mobility (the bishop cannot actually move there this ply). No
+// attempt to model whether the bishop itself is defended -- that's a
+// search-shaped question (SEE/exchange-accurate, whose-move-is-it), not
+// an eval one; see MOBILITY_BITBOARD_REWRITE.md. Deliberately smaller
+// than BISHOP_MOBILITY_BY_SQUARES throughout.
+//
+static SCORE BISHOP_XRAY_MOBILITY_BY_SQUARES[9] =
+{// 0 1 2 3 4 5 6 7 8
+ +0, +1, +1, +2, +2, +3, +3, +4, +4
+};
+
+
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
@@ -582,6 +598,19 @@ static SCORE ROOK_MOBILITY_BY_SQUARES[15] =
//
+// Pseudo-mobility: squares this rook can "see" past a directly-attacked
+// enemy queen or king -- same idea as BISHOP_XRAY_MOBILITY_BY_SQUARES
+// above, same caveats (no defended/undefended modeling -- that's
+// search's job). Deliberately smaller than ROOK_MOBILITY_BY_SQUARES
+// throughout.
+//
+static SCORE ROOK_XRAY_MOBILITY_BY_SQUARES[9] =
+{// 0 1 2 3 4 5 6 7 8
+ +0, +1, +1, +2, +2, +3, +3, +4, +4
+};
+
+
+//
// Queen eval terms
// ---------------------------------------------------------------------------
//
@@ -933,6 +962,7 @@ static DNA_BASE_SIZE g_EvalDNA[] = {
DNA_ARRAY(STATIONARY_PAWN_ON_BISHOP_COLOR),
DNA_ARRAY(TRANSIENT_PAWN_ON_BISHOP_COLOR),
DNA_ARRAY(BISHOP_MOBILITY_BY_SQUARES),
+ DNA_ARRAY(BISHOP_XRAY_MOBILITY_BY_SQUARES),
DNA_ARRAY(BISHOP_UNASSAILABLE_BY_DIST_FROM_EKING),
DNA_ARRAY(KNIGHT_CENTRALITY_BONUS),
DNA_ARRAY(KNIGHT_KING_TROPISM_BONUS),
@@ -951,6 +981,7 @@ static DNA_BASE_SIZE g_EvalDNA[] = {
DNA_VAR(ROOK_CONNECTED_VERT),
DNA_VAR(ROOK_CONNECTED_HORIZ),
DNA_ARRAY(ROOK_MOBILITY_BY_SQUARES),
+ DNA_ARRAY(ROOK_XRAY_MOBILITY_BY_SQUARES),
DNA_ARRAY(QUEEN_MOBILITY_BY_SQUARES),
DNA_ARRAY(QUEEN_KING_TROPISM),
DNA_MATRIX(KING_INITIAL_COUNTER_BY_LOCATION),
@@ -2833,88 +2864,6 @@ Return value:
}
-#ifdef KING_DEFECT_DUMP
-static void
-_ComputeFileStormDefectsSplit(IN POSITION *pos,
- IN const PAWN_HASH_ENTRY *pHash,
- IN ULONG uColor,
- OUT ULONG *puOpenFile,
- OUT ULONG *puStormingPawn)
-/**
-
-Routine description:
-
- Debug-only sibling of _ComputeFileStormDefects that reports its two
- independent sub-ideas (open/half-open file bonus vs. storming-pawn
- distance penalty) separately instead of pre-summed, so a regression
- can weigh them independently -- they already live in two distinct
- hand-picked tables (KingFileDefects / KingStormingPawnDefects) with
- no structural change needed to retune them individually once this
- tells us their relative importance. See CLAUDE.md's king-safety
- discussion.
-
- 2026-09-15: DELIBERATELY DIVERGED from _ComputeFileStormDefects as
- of the linear-SCORE rewrite (king_safety_data/HANDOFF.md's
- 2026-09-15 addendum) -- the real path now returns signed centipawns
- off a differently-shaped table, but this dump sibling must keep
- reporting the OLD unsigned counter-style values so
- king_features_v4.csv-shaped mining data stays interpretable/
- comparable across sessions (extract_king_features.py, this project's
- whole regression pipeline, assumes these units). Traversal shape
- (own file + two adjacent, same v/distance logic) still matches --
- only the two tables' actual values are now intentionally frozen at
- their pre-rewrite originals, not kept in sync.
-
-**/
-{
- static ULONG KingStormingPawnDefects[8] =
- {
- +0, +7, +5, +2, +0, +0, +0, +0
- };
- static ULONG KingFileDefects[3] =
- {
- +4, +2, +0
- };
- ULONG ufColor = FLIP(uColor);
- COOR c = KingPosition(pos, uColor);
- COOR cFileSq, cSquare;
- ULONG u, v;
- BITBOARD bb;
- int w;
-
- *puOpenFile = 0;
- *puStormingPawn = 0;
- 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));
- *puOpenFile += (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));
- *puStormingPawn += KingStormingPawnDefects[DISTANCE(cSquare, c)];
- }
- }
-}
-#endif
-
-
static SCORE
_GetFileStormDefects(IN SEARCHER_THREAD_CONTEXT *ctx,
IN POSITION *pos,
@@ -4377,7 +4326,8 @@ Return value:
static void
_EvalBishop(IN OUT POSITION *pos,
IN COOR c,
- IN PAWN_HASH_ENTRY *pHash)
+ IN PAWN_HASH_ENTRY *pHash,
+ IN BITBOARD bbEnemyRQK)
/**
Routine description:
@@ -4387,6 +4337,10 @@ Parameters:
POSITION *pos,
COOR c,
PAWN_HASH_ENTRY *pHash,
+ BITBOARD bbEnemyRQK -- enemy rook|queen|king, precomputed once per
+ color by Eval()'s driver instead of rebuilt by every bishop of
+ that color (see MOBILITY_BITBOARD_REWRITE.md) -- depends only on
+ static piece locations, no eval-ordering dependency.
Return value:
@@ -4444,98 +4398,79 @@ Return value:
pHash->bbStationaryPawns[BLACK]) & bbMask;
//
- // 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.
+ // Bishop mobility (and update attack tables) -- single magic-bitboard
+ // probe, no chain walk (see MOBILITY_BITBOARD_REWRITE.md). The old
+ // seven-case BMobCaseTable collapses to pure mask arithmetic over the
+ // one-shot first-blocker attack set:
+ // - terminal enemy non-pawn (minor/rook/queen/king): counts
+ // unconditionally -- this is a capture, always "real" mobility.
+ // - empty or terminal enemy pawn: counts unless pawn-unsafe -- both
+ // terminal-square categories together are exactly "not
+ // friend-occupied and not enemy-non-pawn-occupied," the same
+ // partition knight's reduction 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.
+ // color complex: still counts -- bbPc (already computed above for
+ // the good/bad transient-pawn scoring) is exactly "non-stationary
+ // pawns of either color on this color complex."
+ // - any other friendly piece (knight/rook/king): blocks, no count.
+ //
+ // Deliberately NOT replicated: mobility/connectivity credit for
+ // squares beyond a friendly bishop/queen or enemy rook/queen/king --
+ // a bishop cannot physically move past a piece it would capture in
+ // one ply, so that was never "real mobility" to begin with (dropping
+ // it is a correctness fix, not just a simplification). See
+ // MOBILITY_BITBOARD_REWRITE.md for the king-ring x-ray replacement,
+ // which is the one piece of information this loses that's worth
+ // deliberately replacing below.
//
BITBOARD bbAttack = _BishopAttacksBB(c, pos->bbOccupied);
BITBOARD bbEnemySame = pos->bbPieces[xColor][BISHOP] |
pos->bbPieces[xColor][KNIGHT];
- BITBOARD bbEnemyGEContinue = pos->bbPieces[xColor][ROOK] |
- pos->bbPieces[xColor][QUEEN] |
- COOR_TO_BB(KingPosition(pos, xColor));
- BITBOARD bbFriendBQ = pos->bbPieces[uColor][BISHOP] |
- pos->bbPieces[uColor][QUEEN];
BITBOARD bbUnsafeForMinor = pos->bbPawnAttacks[xColor];
- BITBOARD bbExclude = 0;
- BITBOARD bbSeen = 0;
- BITBOARD bbLayer = bbAttack;
- BITBOARD bbMobility = 0;
- BITBOARD bbXrayAccum = 0;
- BITBOARD bbFirstLayerMask = 0;
+ BITBOARD bbEnemyPawnHere = bbAttack & pos->bbPawns[xColor];
+ BITBOARD bbTransientHere = bbAttack & pos->bbPawns[uColor] & bbPc;
+ BITBOARD bbEnemyGEHere = bbAttack & bbEnemyRQK;
+ BITBOARD bbMobility;
+ BITBOARD bbXray;
pos->bbMinorAttacks[uColor] |= bbAttack;
+ bbMobility = (bbAttack & ~pos->bbOccupied & ~bbUnsafeForMinor) |
+ (bbAttack & bbEnemySame) |
+ bbEnemyGEHere |
+ (bbEnemyPawnHere & ~bbUnsafeForMinor) |
+ bbTransientHere;
+
//
- // 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).
+ // King-ring x-ray, replacing the old chain's attack-bit population:
+ // only pay for an extra magic probe when this bishop already
+ // directly attacks an enemy rook/queen/king square (bbEnemyGEHere,
+ // already computed above for mobility -- free to test). Every bit
+ // in bbEnemyGEHere is, by construction, the terminal blocker of a
+ // *different* diagonal ray, so masking all of them off at once and
+ // re-probing in a single magic lookup gives the exact same union a
+ // per-blocker loop would (removing one ray's blocker can't affect a
+ // different ray's result) -- no need to isolate and remove them one
+ // at a time. No deeper chaining needed either way, since the only
+ // consumer (_EvalKingSafety) only ever ANDs this against the king's
+ // ring, never walks further.
//
- 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[xColor];
- BITBOARD bbTransientHere = bbFriendPawnHere & bbPc;
- BITBOARD bbContinueHere;
+ bbXray = bbEnemyGEHere ?
+ _BishopAttacksBB(c, pos->bbOccupied & ~bbEnemyGEHere) : 0;
+ pos->bbMinorXrayAttacks[uColor] |= bbXray;
- 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;
+ // Pseudo-mobility bonus for squares seen past the higher-value
+ // blocker -- see BISHOP_XRAY_MOBILITY_BY_SQUARES's comment. bbXray
+ // already includes the blocker's own square (redundant with
+ // bbAttack, which already counts it as a capture), so subtracting
+ // bbAttack leaves exactly the net-new squares revealed behind it.
+ EVAL_TERM(uColor,
+ BISHOP,
+ c,
+ pos->iScore[uColor],
+ BISHOP_XRAY_MOBILITY_BY_SQUARES[
+ MINU(FastCountBits(bbXray & ~bbAttack), 8)],
+ "xray pseudo-mobility");
uTotalMobility = FastCountBits(bbMobility);
@@ -4845,7 +4780,9 @@ Return value:
static void
_EvalRook(IN OUT POSITION *pos,
IN COOR c,
- IN PAWN_HASH_ENTRY *pHash)
+ IN PAWN_HASH_ENTRY *pHash,
+ IN BITBOARD bbEnemyQK,
+ IN BITBOARD bbUnsafeForRook)
/**
Routine description:
@@ -4855,6 +4792,12 @@ Parameters:
IN POSITION *pos,
IN COOR c,
IN PAWN_HASH_ENTRY *pHash,
+ IN BITBOARD bbEnemyQK -- enemy queen|king, precomputed once per color
+ by Eval()'s driver (see MOBILITY_BITBOARD_REWRITE.md) -- depends
+ only on static piece locations.
+ IN BITBOARD bbUnsafeForRook -- enemy pawn|minor attacks, precomputed
+ once per color right before the rook loops start, valid because
+ both colors' knight+bishop passes are already complete by then.
Return value:
@@ -4911,125 +4854,88 @@ Return value:
(FastCountBits(pos->bbPawns[uColor]) + FastCountBits(pos->bbPawns[xColor])));
//
- // 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.
+ // Rook mobility (and update attack tables) -- single magic-bitboard
+ // probe, no chain walk (see MOBILITY_BITBOARD_REWRITE.md). The old
+ // RMobCaseTable's six cases collapse to mask arithmetic over the
+ // one-shot first-blocker attack set:
+ // - terminal enemy rook/queen/king: counts unconditionally -- a
+ // capture is always "real" mobility regardless of value.
+ // - empty or terminal enemy pawn/knight/bishop: 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 use.
+ // - any friendly piece: blocks, no count. A friendly rook
+ // specifically also earns the connected-rook bonus (direct
+ // attack only now, not chained through a further blocker).
+ //
+ // Deliberately NOT replicated: mobility/connectivity credit for
+ // squares beyond a friendly rook/queen or enemy queen/king -- a
+ // rook cannot physically move past a piece it would capture in one
+ // ply, so that was never "real mobility" to begin with (dropping it
+ // is a correctness fix, not just a simplification). See
+ // MOBILITY_BITBOARD_REWRITE.md for the king-ring x-ray replacement,
+ // which is the one piece of information this loses that's worth
+ // deliberately replacing below.
//
-
BITBOARD bbAttack = _RookAttacksBB(c, pos->bbOccupied);
BITBOARD bbEnemyOcc = _BuildFriendlySideBB(pos, xColor);
BITBOARD bbEnemySame = pos->bbPieces[xColor][ROOK];
- BITBOARD bbEnemyGEContinue = pos->bbPieces[xColor][QUEEN] |
- COOR_TO_BB(KingPosition(pos, xColor));
- BITBOARD bbFriendRQ = pos->bbPieces[uColor][ROOK] |
- pos->bbPieces[uColor][QUEEN];
- BITBOARD bbUnsafeForRook = pos->bbPawnAttacks[xColor] |
- pos->bbMinorAttacks[xColor];
- BITBOARD bbExclude = 0;
- BITBOARD bbSeen = 0;
- BITBOARD bbLayer = bbAttack;
- BITBOARD bbXrayAccum = 0;
- BITBOARD bbFirstLayerMask = 0;
+ BITBOARD bbEnemyGEHere = bbAttack & bbEnemyQK;
+ BITBOARD bbEnemyLEHere = bbAttack & bbEnemyOcc &
+ ~bbEnemySame & ~bbEnemyGEHere;
+ BITBOARD bbFriendRookHere = bbAttack & pos->bbPieces[uColor][ROOK];
+ BITBOARD bbXray;
- bbMobility = 0;
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 = (bbAttack & ~pos->bbOccupied & ~bbUnsafeForRook) |
+ (bbAttack & bbEnemySame) |
+ bbEnemyGEHere |
+ (bbEnemyLEHere & ~bbUnsafeForRook);
- bbMobility |= (bbEmptyHere & ~bbUnsafeForRook) |
- bbEnemySameHere | bbEnemyGEHere |
- (bbEnemyLEHere & ~bbUnsafeForRook);
+ // Connected-rook bonus -- direct attack only now; see the comment
+ // above.
+ 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)),
+ "rooks connected");
+ }
- // 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)),
- "rooks connected");
- }
+ //
+ // King-ring x-ray, replacing the old chain's attack-bit population:
+ // only pay for an extra magic probe when this rook already directly
+ // attacks the enemy queen or king square (bbEnemyGEHere, already
+ // computed above for mobility -- free to test). bbEnemyGEHere has
+ // at most two bits (queen, king), each the terminal blocker of a
+ // *different* ray -- masking both off at once and re-probing in a
+ // single magic lookup gives the same union a per-blocker loop would
+ // (one ray's blocker removal can't affect the other ray's result).
+ // No deeper chaining needed either way, since the only consumer
+ // (_EvalKingSafety) only ever ANDs this against the king's ring,
+ // never walks further.
+ //
+ bbXray = bbEnemyGEHere ?
+ _RookAttacksBB(c, pos->bbOccupied & ~bbEnemyGEHere) : 0;
+ pos->bbRookXrayAttacks[uColor] |= bbXray;
- bbContinueHere = bbFriendRQHere | bbEnemyGEHere;
- if (!bbContinueHere)
- {
- break;
- }
- bbExclude |= bbContinueHere;
- bbLayer = _RookAttacksBB(c, pos->bbOccupied & ~bbExclude);
- }
- pos->bbRookXrayAttacks[uColor] |= bbXrayAccum;
+ // Pseudo-mobility bonus for squares seen past the queen/king blocker
+ // -- see ROOK_XRAY_MOBILITY_BY_SQUARES's comment, same reasoning as
+ // bishop's identical term.
+ EVAL_TERM(uColor,
+ ROOK,
+ c,
+ pos->iScore[uColor],
+ ROOK_XRAY_MOBILITY_BY_SQUARES[
+ MINU(FastCountBits(bbXray & ~bbAttack), 8)],
+ "xray pseudo-mobility");
uTotalMobility = FastCountBits(bbMobility);
ASSERT(uTotalMobility <= 14);
@@ -5060,7 +4966,9 @@ Return value:
static void
_EvalQueen(IN OUT POSITION *pos,
IN COOR c,
- IN PAWN_HASH_ENTRY *pHash)
+ IN PAWN_HASH_ENTRY *pHash,
+ IN BITBOARD bbEnemyQK,
+ IN BITBOARD bbUnsafeForQueen)
/**
Parameters:
@@ -5068,6 +4976,13 @@ Parameters:
POSITION *pos,
COOR c,
PAWN_HASH_ENTRY *pHash,
+ BITBOARD bbEnemyQK -- enemy queen|king, precomputed once per color by
+ Eval()'s driver -- identical formula to _EvalRook's bbEnemyQK, so
+ the same precomputed value is reused for both (see
+ MOBILITY_BITBOARD_REWRITE.md).
+ BITBOARD bbUnsafeForQueen -- precomputed once per color right before
+ the queen loops start, valid because both colors' pawn/minor/rook
+ passes are already complete by then.
Return value:
@@ -5079,113 +4994,54 @@ Return value:
ULONG uColor;
ULONG xColor;
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));
xColor = FLIP(uColor);
- cKing = KingPosition(pos, xColor);
- ASSERT(IS_ON_BOARD(cKing));
- ASSERT(IS_KING(pos->rgSquare[cKing].pPiece));
+ ASSERT(IS_ON_BOARD(KingPosition(pos, xColor)));
+ ASSERT(IS_KING(pos->rgSquare[KingPosition(pos, xColor)].pPiece));
//
- // 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.
+ // Queen mobility (and update attack tables) -- two single magic
+ // probes (rook-direction + bishop-direction), unioned, then one
+ // shared mask pass. No chain walk (see MOBILITY_BITBOARD_REWRITE.md):
+ // - terminal enemy queen/king: counts unconditionally -- a capture
+ // is always "real" mobility.
+ // - empty or terminal enemy pawn/minor/rook: counts unless the
+ // square is attacked by an enemy pawn, minor, rook, or (modulo
+ // our own defense) the enemy king -- same partition
+ // bishop/knight/rook's reductions use, just with a bigger
+ // "cheaper piece" set since the queen is the most valuable.
+ // - any friendly piece: blocks, no count.
//
- BITBOARD bbEnemyOcc = _BuildFriendlySideBB(pos, xColor);
- BITBOARD bbEnemyGE = pos->bbPieces[xColor][QUEEN] |
- COOR_TO_BB(cKing);
- //
- // Enemy king attacks are also unsafe for the queen -- a free
- // O(1) table lookup by king square, independent of eval order
- // (unlike the enemy minor/rook attacks above, which do need
- // both colors' knight/bishop/rook passes to finish first, and
- // already have by queen's turn: see the eval call order in
- // Eval() below). Only unsafe when the queen wouldn't be
- // recaptured by something of our own first -- if a friendly
- // pawn/minor/rook already covers that square, the king can't
- // just take the queen for free. Every one of those tables (ours
- // AND the enemy's) is fully populated by now: pawns, minors,
- // and rooks all run their own eval pass before queen does.
+ // Deliberately NOT replicated: mobility credit for squares beyond a
+ // friendly rook/bishop/queen battery partner -- a queen cannot
+ // physically move past a piece it shares a square with in one ply,
+ // so (same as bishop/rook) that was never "real mobility" to begin
+ // with. Also NOT replicated, and needing no replacement: the old
+ // code's x-ray-attack population. Unlike bishop/rook, queen's old
+ // chain only ever continued through a FRIENDLY blocker (it never
+ // x-rayed through any enemy piece at all -- QMOB_ENEMY_GE always
+ // stopped the ray in the original mailbox table). By the same
+ // redundancy argument used to drop bishop/rook's friendly-blocker
+ // x-ray case, that information was always a duplicate of the
+ // blocking piece's own direct-attack pass -- there's no enemy-GE
+ // case here to replace, so _EvalKingSafety's queen-x-ray reads
+ // (pos->bbQueenXrayAttacks[xColor]) see nothing lost by simply never
+ // populating it.
//
- BITBOARD bbUnsafeForQueen = pos->bbPawnAttacks[xColor] |
- pos->bbMinorAttacks[xColor] |
- pos->bbRookAttacks[xColor] |
- (g_KingAttacksBB[cKing] &
- ~(pos->bbPawnAttacks[uColor] |
- pos->bbMinorAttacks[uColor] |
- pos->bbRookAttacks[uColor]));
- BITBOARD bbAttackTotal = 0;
- BITBOARD bbXrayAccum = 0;
- BITBOARD bbMobility = 0;
-
- 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);
+ BITBOARD bbEnemyOcc = _BuildFriendlySideBB(pos, xColor);
+ BITBOARD bbAttackTotal = _RookAttacksBB(c, pos->bbOccupied) |
+ _BishopAttacksBB(c, pos->bbOccupied);
+ BITBOARD bbEnemyGEHere = bbAttackTotal & bbEnemyQK;
+ BITBOARD bbEnemyLEHere = bbAttackTotal & bbEnemyOcc & ~bbEnemyGEHere;
+ BITBOARD bbMobility = (bbAttackTotal & ~pos->bbOccupied & ~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 = FastCountBits(bbMobility);
ASSERT(uTotalMobility <= 27);
@@ -5246,10 +5102,6 @@ Return value:
{
BITV bvPattern;
ULONG uQueenNearKing;
-#ifdef KING_DEFECT_DUMP
- COOR cSquare;
- KING_DEFECT_DUMP_INFO *pDump = &ctx->sKingDefectDump[uColor];
-#endif
SCORE iKingScore = 0;
// 2026-09-15: linear-SCORE rewrite -- see king_safety_data/
@@ -5378,105 +5230,9 @@ Return value:
iSafety += KING_BACK_FLIGHT_BONUS *
(SCORE)FastCountBits(bbBack & bbEmpty & ~bbEnemyAtkAny);
-#ifdef KING_DEFECT_DUMP
- {
- // Debug-only, per-square breakdown for the mining pipeline
- // (extract_king_features.py) -- kept fully independent of
- // the bitboard computation above, same traversal shape as
- // the pre-rewrite loop so king_features_v4.csv's column
- // semantics stay unchanged. Does not feed iSafety.
- static INT KingSafetyDeltas[9] =
- {
- -17, -16, -15,
- -1, +1,
- +15, +16, +17, 0
- };
- ULONG uSlot = 0;
- BITV bvSlotAttack, bvSlotXray, bvSlotDefend;
-
- do
- {
- cSquare = c + KingSafetyDeltas[uSlot];
- if (IS_ON_BOARD(cSquare))
- {
- BITBOARD sq = COOR_TO_BB(cSquare);
- BITV bvEither;
-
- bvSlotAttack = ((pos->bbPawnAttacks[xColor] & sq) ? PAWN_BIT : 0) |
- ((pos->bbMinorAttacks[xColor] & sq) ? MINOR_BIT : 0) |
- ((pos->bbRookAttacks[xColor] & sq) ? ROOK_BIT : 0) |
- ((pos->bbQueenAttacks[xColor] & sq) ? QUEEN_BIT : 0);
- bvSlotXray = ((pos->bbMinorXrayAttacks[xColor] & sq) ? MINOR_BIT : 0) |
- ((pos->bbRookXrayAttacks[xColor] & sq) ? ROOK_BIT : 0) |
- ((pos->bbQueenXrayAttacks[xColor] & sq) ? QUEEN_BIT : 0);
- bvSlotDefend = ((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);
-
- bvEither = bvSlotAttack | bvSlotXray;
- ASSERT(uSlot < KING_RING_NUM_SLOTS);
- pDump->fOnBoard[uSlot] = TRUE;
- pDump->fAttackedByPieceType[uSlot][KING_RING_PAWN] =
- ((bvEither & PAWN_BIT) != 0);
- pDump->fAttackedByPieceType[uSlot][KING_RING_MINOR] =
- ((bvEither & MINOR_BIT) != 0);
- pDump->fAttackedByPieceType[uSlot][KING_RING_ROOK] =
- ((bvEither & ROOK_BIT) != 0);
- pDump->fAttackedByPieceType[uSlot][KING_RING_QUEEN] =
- ((bvEither & QUEEN_BIT) != 0);
- if (bvSlotAttack != 0)
- {
- BITV bvDefendForDump = bvSlotDefend;
- if (bvSlotXray || (bvSlotAttack & (bvSlotAttack - 1)))
- {
- bvDefendForDump &= ~KING_BIT;
- }
- if (bvDefendForDump == 0)
- {
- pDump->fAttackedUndefended[uSlot] = TRUE;
- }
- else
- {
- pDump->fAttackedDefended[uSlot] = TRUE;
- }
- }
- else
- {
- if (bvSlotXray != 0)
- {
- pDump->fXrayedUnattacked[uSlot] = TRUE;
- }
- if (!(pos->bbOccupied & sq))
- {
- pDump->fEmptyFlight[uSlot] = TRUE;
- }
- }
- }
- uSlot++;
- }
- while (KingSafetyDeltas[uSlot] != 0);
- }
-#endif
-
- //
// _ComputeFileStormDefects's comment.
- //
iSafety += _GetFileStormDefects(ctx, pos, pHash, uColor);
-#ifdef KING_DEFECT_DUMP
- _ComputeFileStormDefectsSplit(pos, pHash, uColor,
- &pDump->uFileStormOpenFile,
- &pDump->uFileStormStormingPawn);
- pDump->uFileStormDefects = pDump->uFileStormOpenFile +
- pDump->uFileStormStormingPawn;
-#endif
-
iSafety += KING_INITIAL_LOCATION_SCALE * KING_EVAL_INITIAL_LOCATION[uColor][c];
-#ifdef KING_DEFECT_DUMP
- pDump->uInitialCounterByLocation =
- (ULONG)KING_EVAL_INITIAL_LOCATION[uColor][c];
-#endif
// Danger-magnitude acceleration -- see KING_DANGER_ACCELERATOR's
// comment above. Applied here (as part of the real per-king
@@ -5513,21 +5269,6 @@ Return value:
iSafety,
"king safety");
-#ifdef KING_DEFECT_DUMP
- pDump->iRealSafety = iSafety;
- pDump->uQueenNearKing = uQueenNearKing;
- // uCounterTotal/uSafetyTableValue no longer have a coherent
- // meaning post-rewrite (no more shared uCounter/KING_SAFETY_
- // BY_COUNTER lookup) -- left zeroed by the memset above rather
- // than populated with something misleading. Both were REFERENCE-
- // only columns in the training pipeline (train_king_safety_
- // model.py explicitly excludes them from the feature set), so
- // this doesn't affect the regression itself.
- pDump->fCastled = pos->fCastled[uColor];
- pDump->uMinorsAtHome = pos->uMinorsAtHome[uColor];
- pDump->uAttackerArmyScaler = pos->uArmyScaler[xColor];
-#endif
-
// Bonus for castling / penalty for loss of castle. Also, if
// side has not yet castled AND castling is still a live
// option, be concerned with undeveloped minor pieces.
@@ -5551,56 +5292,51 @@ Return value:
// copy-pasted blocks -- one fewer per-piece branch for the
// processor to mispredict (board_representation/EVAL.md
// section 9).
- if (FALSE == pos->fCastled[uColor])
+ ULONG uDevelopmentScaler = CASTLE_AND_DEVELOPMENT_SCALER[pos->uArmyScaler[uColor]];
+ if (uDevelopmentScaler > 0)
{
- ULONG uDevelopmentScaler = CASTLE_AND_DEVELOPMENT_SCALER[pos->uArmyScaler[uColor]];
- if (uDevelopmentScaler > 0)
- {
- static const BITV CASTLE_SHORT[2] =
- { CASTLE_BLACK_SHORT, CASTLE_WHITE_SHORT };
- static const BITV CASTLE_LONG[2] =
- { CASTLE_BLACK_LONG, CASTLE_WHITE_LONG };
- BITV bvBothRights = CASTLE_SHORT[uColor] | CASTLE_LONG[uColor];
- // One popcount answers both "can this side still
- // castle at all" (>0, gates development below) and
- // "how many options are missing" (2-count, scales
- // the persistent penalty below) -- replaces two
- // separate (==0) branches/multiplies/EVAL_TERM calls
- // with one shared count.
- ULONG uRightsRemaining =
- FastCountBits(pos->bvCastleInfo & bvBothRights);
- ASSERT(uRightsRemaining <= 2);
+ static const BITV CASTLE_SHORT[2] =
+ { CASTLE_BLACK_SHORT, CASTLE_WHITE_SHORT };
+ static const BITV CASTLE_LONG[2] =
+ { CASTLE_BLACK_LONG, CASTLE_WHITE_LONG };
+ static const BITV bvBothRights[2] =
+ { CASTLE_SHORT[BLACK] | CASTLE_LONG[BLACK],
+ CASTLE_SHORT[WHITE] | CASTLE_LONG[WHITE] };
- // Branchless: (uRightsRemaining > 0) is 0 or 1, multiplied
- // straight into the term instead of an `if` -- nothing
- // left to mispredict.
- ASSERT(pos->uMinorsAtHome[uColor] <= 4);
- EVAL_TERM(uColor,
- 0,
- ILLEGAL_COOR,
- pos->iScore[uColor],
- (SCORE)((UNDEVELOPED_MINORS_IN_OPENING[pos->uMinorsAtHome[uColor]] *
- (SCORE)uDevelopmentScaler) / 8),
- "development");
+ ASSERT(pos->uMinorsAtHome[uColor] <= 4);
+ EVAL_TERM(uColor,
+ 0,
+ ILLEGAL_COOR,
+ pos->iScore[uColor],
+ (SCORE)((UNDEVELOPED_MINORS_IN_OPENING[pos->uMinorsAtHome[uColor]] *
+ (SCORE)uDevelopmentScaler) / 8),
+ "development");
- // Deliberately NOT gated on uRightsRemaining > 0 --
- // see this block's header comment. Fires at full
- // weight (both options gone) even long after the
- // rights were lost, for as long as uDevelopmentScaler
- // itself stays nonzero.
- EVAL_TERM(uColor,
- KING,
- c,
- iKingScore,
- (SCORE)((KING_MISSING_ONE_CASTLE_OPTION *
- (SCORE)(2 - uRightsRemaining) *
- (SCORE)uDevelopmentScaler) / 8),
- "missing castle option(s)");
-#ifdef KING_DEFECT_DUMP
- pDump->fMissingCastleShort = ((CASTLE_SHORT[uColor] & pos->bvCastleInfo) == 0);
- pDump->fMissingCastleLong = ((CASTLE_LONG[uColor] & pos->bvCastleInfo) == 0);
-#endif
- }
+ // One popcount answers both "can this side still
+ // castle at all" (>0, gates development below) and
+ // "how many options are missing" (2-count, scales
+ // the persistent penalty below) -- replaces two
+ // separate (==0) branches/multiplies/EVAL_TERM calls
+ // with one shared count.
+ ULONG uRightsRemaining =
+ FastCountBits(pos->bvCastleInfo & bvBothRights[uColor]);
+ ASSERT(uRightsRemaining <= 2);
+
+ // Deliberately NOT gated on uRightsRemaining > 0 --
+ // see this block's header comment. Fires at full
+ // weight (both options gone) even long after the
+ // rights were lost, for as long as uDevelopmentScaler
+ // itself stays nonzero.
+ EVAL_TERM(uColor,
+ KING,
+ c,
+ iKingScore,
+ (SCORE)(
+ (FALSE == pos->fCastled[uColor]) *
+ (KING_MISSING_ONE_CASTLE_OPTION *
+ (SCORE)(2 - uRightsRemaining) *
+ (SCORE)uDevelopmentScaler) / 8),
+ "missing castle option(s)");
}
return iKingScore;
}
@@ -5756,12 +5492,6 @@ Return value:
// Update attack bitvs for squares around the king.
BITBOARD bbRing = g_KingAttacksBB[c];
pos->bbKingAttacks[uColor] |= bbRing;
-#ifdef KING_DEFECT_DUMP
- {
- KING_DEFECT_DUMP_INFO *pDump = &ctx->sKingDefectDump[uColor];
- memset(pDump, 0, sizeof(KING_DEFECT_DUMP_INFO));
- }
-#endif
// Safety-vs-activity split -- see this function's header comment
// (2026-10-06 restructuring). Don't bother with king safety when
@@ -7883,6 +7613,29 @@ Return value:
// Evaluate individual pieces.
//
+ // Precomputed once per color instead of rebuilt by every bishop/
+ // rook/queen of that color (see MOBILITY_BITBOARD_REWRITE.md's
+ // "squeeze it" follow-up). bbEnemyRQK/bbEnemyQK depend only on
+ // static piece locations -- no ordering dependency, safe to build
+ // right here before any piece loop runs. bbUnsafeForRook/
+ // bbUnsafeForQueen are filled in below, at the loop boundary where
+ // their inputs are guaranteed complete (both colors' knight+bishop
+ // passes before rook; both colors' pawn/minor/rook passes before
+ // queen) -- NOT incrementally mutated during a loop, which would
+ // let one piece's own attack leak into its same-color sibling's
+ // "unsafe" mask.
+ BITBOARD bbEnemyRQK[2];
+ BITBOARD bbEnemyQK[2];
+ BITBOARD bbUnsafeForRook[2];
+ BITBOARD bbUnsafeForQueen[2];
+
+ bbEnemyQK[WHITE] = pos->bbPieces[WHITE][QUEEN] |
+ COOR_TO_BB(pos->cKingSq[WHITE]);
+ bbEnemyQK[BLACK] = pos->bbPieces[BLACK][QUEEN] |
+ COOR_TO_BB(pos->cKingSq[BLACK]);
+ bbEnemyRQK[WHITE] = bbEnemyQK[WHITE] | pos->bbPieces[WHITE][ROOK];
+ bbEnemyRQK[BLACK] = bbEnemyQK[BLACK] | pos->bbPieces[BLACK][ROOK];
+
// Knights.
bb = pos->bbPieces[uColor][KNIGHT];
while (bb)
@@ -7930,7 +7683,7 @@ Return value:
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
- _EvalBishop(pos, c, pHash));
+ _EvalBishop(pos, c, pHash, bbEnemyRQK[xColor]));
#ifdef EVAL_DUMP
Trace("After our B at %s:\n%d\t\t%d\t\t(%d for %s)\n",
CoorToString(c),
@@ -7949,7 +7702,7 @@ Return value:
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
- _EvalBishop(pos, c, pHash));
+ _EvalBishop(pos, c, pHash, bbEnemyRQK[uColor]));
#ifdef EVAL_DUMP
Trace("After enemy B at %s:\n%d\t\t%d\t\t(%d for %s)\n",
CoorToString(c),
@@ -7960,6 +7713,14 @@ Return value:
#endif
}
+ // Both colors' knight+bishop passes are complete now -- safe to
+ // build both colors' "unsafe for rook" masks once, here, instead of
+ // every rook rebuilding its own color's mask from scratch.
+ bbUnsafeForRook[WHITE] = pos->bbPawnAttacks[BLACK] |
+ pos->bbMinorAttacks[BLACK];
+ bbUnsafeForRook[BLACK] = pos->bbPawnAttacks[WHITE] |
+ pos->bbMinorAttacks[WHITE];
+
// Rooks.
bb = pos->bbPieces[uColor][ROOK];
while (bb)
@@ -7967,7 +7728,9 @@ Return value:
u = FastFirstBit(bb) - 1;
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
- TIMED_EVAL_CALL(ctx, u64CyclesEvalRook, _EvalRook(pos, c, pHash));
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalRook,
+ _EvalRook(pos, c, pHash, bbEnemyQK[xColor],
+ bbUnsafeForRook[uColor]));
#ifdef EVAL_DUMP
Trace("After our R at %s:\n%d\t\t%d\t\t(%d for %s)\n",
CoorToString(c),
@@ -7985,7 +7748,9 @@ Return value:
u = FastFirstBit(bb) - 1;
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
- TIMED_EVAL_CALL(ctx, u64CyclesEvalRook, _EvalRook(pos, c, pHash));
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalRook,
+ _EvalRook(pos, c, pHash, bbEnemyQK[uColor],
+ bbUnsafeForRook[xColor]));
#ifdef EVAL_DUMP
Trace("After enemy R at %s:\n%d\t\t%d\t\t(%d for %s)\n",
CoorToString(c),
@@ -7996,6 +7761,27 @@ Return value:
#endif
}
+ // Both colors' pawn/minor/rook passes are complete now -- safe to
+ // build both colors' "unsafe for queen" masks once, here, instead
+ // of every queen rebuilding its own color's mask from scratch (most
+ // positions only have one queen per side anyway, so this mainly
+ // pays off on promoted-extra-queen positions, but it's free either
+ // way).
+ bbUnsafeForQueen[WHITE] = pos->bbPawnAttacks[BLACK] |
+ pos->bbMinorAttacks[BLACK] |
+ pos->bbRookAttacks[BLACK] |
+ (g_KingAttacksBB[pos->cKingSq[BLACK]] &
+ ~(pos->bbPawnAttacks[WHITE] |
+ pos->bbMinorAttacks[WHITE] |
+ pos->bbRookAttacks[WHITE]));
+ bbUnsafeForQueen[BLACK] = pos->bbPawnAttacks[WHITE] |
+ pos->bbMinorAttacks[WHITE] |
+ pos->bbRookAttacks[WHITE] |
+ (g_KingAttacksBB[pos->cKingSq[WHITE]] &
+ ~(pos->bbPawnAttacks[BLACK] |
+ pos->bbMinorAttacks[BLACK] |
+ pos->bbRookAttacks[BLACK]));
+
// Our queen(s).
bb = pos->bbPieces[uColor][QUEEN];
while (bb)
@@ -8003,7 +7789,9 @@ Return value:
u = FastFirstBit(bb) - 1;
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
- TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen, _EvalQueen(pos, c, pHash));
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen,
+ _EvalQueen(pos, c, pHash, bbEnemyQK[xColor],
+ bbUnsafeForQueen[uColor]));
#ifdef EVAL_DUMP
Trace("After our Q at %s:\n%d\t\t%d\t\t(%d for %s)\n",
CoorToString(c),
@@ -8021,7 +7809,9 @@ Return value:
u = FastFirstBit(bb) - 1;
bb &= (bb - 1);
c = BIT_NUMBER_TO_COOR(u);
- TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen, _EvalQueen(pos, c, pHash));
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen,
+ _EvalQueen(pos, c, pHash, bbEnemyQK[uColor],
+ bbUnsafeForQueen[xColor]));
#ifdef EVAL_DUMP
Trace("After enemy Q at %s:\n%d\t\t%d\t\t(%d for %s)\n",
CoorToString(c),
diff --git a/src/generate.c b/src/generate.c
index fdfdc79..d202384 100644
--- a/src/generate.c
+++ b/src/generate.c
@@ -908,7 +908,7 @@ GenerateWhiteKnight(IN MOVE_STACK *pStack,
// ASSERT(pStack->bbOccupied == _BuildFullOccupiedBB(pos)) calls
// below) and for testgenerate.c's benchmark harness. Non-static so
// both of those can call it directly.
-BITBOARD
+BITBOARD FORCEINLINE
_BuildFullOccupiedBB(IN POSITION *pos)
/**
@@ -1004,7 +1004,7 @@ _ComputeCheckTargetMaskBB(IN COOR cKing, IN COOR cAttacker,
// Non-static so testgenerate.c's harness can call it directly to set
// up MOVE_STACK.bbFriendlyOccupied when calling a _Generate*BB
// function outside of _GenerateAllMoves.
-BITBOARD INLINE
+BITBOARD FORCEINLINE
_BuildFriendlySideBB(IN POSITION *pos, IN ULONG uSide)
/**
@@ -4833,10 +4833,18 @@ Return value:
s -= PIECE_VALUE_OVER_100(mv.pMoved);
s += SORT_THESE_FIRST;
- //
- // IDEA: bonus for capturing opponent's last moved
- // piece to encourage quicker fail highs?
- //
+ // DROPPED: recapture bonus (MOVE_ORDERING_IDEAS.md
+ // item 4) -- a real, well-calibrated FH% signal
+ // (64-65% vs. a 49-52% all-captures-at-the-same-
+ // SEE-sign baseline) that never converted into a
+ // tree-size win at two magnitudes tried (50: a
+ // dominant override of MVV/LVA's own ~16-unit
+ // spread, confirmed real tactical loss on
+ // ecm_hard_quick; 4: fixed the regression but
+ // still no node-count improvement on any suite).
+ // See chess.h's DROPPED comment near
+ // COUNTERMOVE_EVIDENCE_THRESHOLD for the full
+ // backlog history.
ASSERT(s >= SORT_THESE_FIRST);
ASSERT(s > 0);
ASSERT((s & STRIP_OFF_FLAGS) < VALUE_KING);
@@ -4899,6 +4907,24 @@ Return value:
s |= COUNTERMOVE_TIER_BONUS;
}
}
+
+ // DROPPED: flee-to-safety tier (MOVE_ORDERING_IDEAS.md
+ // item 6) -- a real, well-calibrated FH% signal
+ // (56-68% vs. ~1.35-1.54% baseline at evidence >=
+ // 10000) that turned out to fire too rarely (2,444-
+ // 9,209 qualifying tries across an entire suite run)
+ // to register any measurable node-count or solve-count
+ // effect either way. See chess.h's DROPPED comment
+ // near COUNTERMOVE_EVIDENCE_THRESHOLD for the full
+ // backlog history.
+
+ // DROPPED: ply-2 move-pair match (MOVE_ORDERING_IDEAS.md
+ // item 3) -- a real, well-calibrated signal (53-66% FH
+ // vs. ~1.2-1.4% baseline) that never converted into a
+ // tree-size win across three mechanisms tried (tier
+ // escape, dominant nudge, proportionate nudge). See
+ // chess.h's DROPPED comment near COUNTERMOVE_EVIDENCE_
+ // THRESHOLD for the full history.
ASSERT(s >= 0);
}
pStack->mvf[u].iValue = s;
diff --git a/src/search.c b/src/search.c
index 0ac0037..89db55c 100644
--- a/src/search.c
+++ b/src/search.c
@@ -244,7 +244,7 @@ _PostReduceDepthBucketEfpAligned(ULONG uNextDepthRaw)
// countermove match's own FH%? See chess.h's CM_EVIDENCE_BUCKETS
// comment. Buckets by log-ish bands rather than linear, since evidence
// values span 0 to ~STRIP_OFF_FLAGS*2 (~16.7M).
-#ifdef PERF_COUNTERS
+#ifdef MOVE_ORDERING_EVIDENCE_COLLECTION
static ULONG
_CMEvidenceBucket(ULONG uEvidence)
{
@@ -261,6 +261,7 @@ _CMEvidenceBucket(ULONG uEvidence)
}
return(0);
}
+
#endif
#ifdef TEST_NULL
@@ -541,9 +542,11 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
// definition gate-1 describes.
FLAG fMoveIsPruneReduceEligible = FALSE;
ULONG uRawDepthB;
-#ifdef PERF_COUNTERS
+#ifdef MOVE_ORDERING_EVIDENCE_COLLECTION
FLAG fThisMoveIsCountermoveMatch = FALSE;
ULONG uCMEvidenceBucket = 0;
+#endif
+#ifdef PERF_COUNTERS
ULONG uThisMoveHangBucket = 0;
ULONG uThisMoveGapBucket = 0;
ULONG uThisMoveNumBucket = 0;
@@ -864,7 +867,7 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
// or hash table results.
if (fThreat)
{
- iPositionalExtend += THREE_QUARTERS_PLY;
+ iPositionalExtend += HALF_PLY;
INC(ctx->sCounters.extension.uMateThreat);
}
@@ -1057,7 +1060,7 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
MVF_MOVE_SEARCHED));
ctx->sMoveStack.mvf[x].bvFlags |= MVF_MOVE_SEARCHED;
#endif
-#ifdef PERF_COUNTERS
+#ifdef MOVE_ORDERING_EVIDENCE_COLLECTION
// Countermove evidence calibration -- ongoing check
// that COUNTERMOVE_EVIDENCE_THRESHOLD (chess.h) is
// still well-calibrated: log every countermove-
@@ -1160,14 +1163,11 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
// mvBest). The killer/counter-move/history-increment
// side of the update below is unaffected.
//
- if (!IS_CAPTURE_OR_PROMOTION(mvBest))
- {
- UpdateDynamicMoveOrdering(ctx,
- uDepth,
- mvBest,
- iBestScore,
- 0);
- }
+ UpdateDynamicMoveOrdering(ctx,
+ uDepth,
+ mvBest,
+ iBestScore,
+ 0);
StoreLowerBound(mvBest, pos, iBestScore, uDepth, fThreat);
if (mvBest.pCaptured && !IS_PAWN(mvBest.pCaptured) &&
(iBeta < +NMATE))
@@ -1336,7 +1336,7 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
(uFHPctThousandths <= EFP_FH_PRUNE_THRESHOLD_PCT_THOUSANDTHS);
fThisMoveEFPPruned =
fThisMoveEFPPruned &&
- (uPredictedFH == 0xFFFF || uPredictedFH < 200);
+ (uPredictedFH == 0xFFFF || uPredictedFH < 300);
#if 0
if (fThisMoveEFPPruned &&
uPredictedFH != 0xFFFF &&
@@ -1525,9 +1525,14 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
}
#endif
}
+#endif // PERF_COUNTERS
+#ifdef MOVE_ORDERING_EVIDENCE_COLLECTION
+ // Deliberately its own gate, not nested under PERF_COUNTERS
+ // above -- see MOI_INC/MOVE_ORDERING_EVIDENCE_COLLECTION
+ // comment (chess.h).
if (TRUE == fThisMoveIsCountermoveMatch)
{
- INC(ctx->sCounters.tree.u64CMEvidenceTries[uCMEvidenceBucket]);
+ MOI_INC(ctx->sCounters.tree.u64CMEvidenceTries[uCMEvidenceBucket]);
}
#endif
// Record the extension/reduction actually applied to
@@ -1877,9 +1882,11 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
[uThisMoveGapBucket][uThisMoveNumBucket]);
}
}
+#endif // PERF_COUNTERS
+#ifdef MOVE_ORDERING_EVIDENCE_COLLECTION
if (TRUE == fThisMoveIsCountermoveMatch)
{
- INC(ctx->sCounters.tree.u64CMEvidenceFH[uCMEvidenceBucket]);
+ MOI_INC(ctx->sCounters.tree.u64CMEvidenceFH[uCMEvidenceBucket]);
}
#endif
// Update history and killers list and store in
diff --git a/src/searchsup.c b/src/searchsup.c
index 69c87a2..decce3b 100644
--- a/src/searchsup.c
+++ b/src/searchsup.c
@@ -998,22 +998,12 @@ Return value:
**/
{
static ULONG uLMPStaticMaxPctThousandths[] = {
- 200, 200, 125, 75, 36, 12, 6, 3, 2, 1, 0
+ 200, 200, 150, 75, 50, 25, 12, 6, 3, 1, 0
};
ASSERT(ctx->uPly > 0);
ASSERT(IS_VALID_DEPTH(uRemainingDepth));
ULONG uRemainingPlies = uRemainingDepth / ONE_PLY;
-#if 0
- // Only consider very late moves -- removed on the rationale that
- // the 4D FH% table already takes move lateness into account.
- ULONG uMoveNumFloor = LMP_BASE_MOVENUM + uRemainingPlies;
- if (uLegalMoves <= uMoveNumFloor)
- {
- return FALSE;
- }
-#endif
-
// Dynamic signal: if there's real, well-evidenced history on this
// exact move identity and it's not essentially zero, don't
// gamble -- this is the ultra-safe tail only, not "probably fine."
@@ -1024,7 +1014,7 @@ Return value:
}
if ((JOINT_TABLE_SENTINEL == uPredictedFH) ||
- (uPredictedFH >= uLMPStaticMaxPctThousandths[MINU(uRemainingPlies, 10)]))
+ (uPredictedFH > uLMPStaticMaxPctThousandths[MINU(uRemainingPlies, 10)]))
{
return FALSE;
}