From de3f3663483f846d369efa5234a84c7da385be95 Mon Sep 17 00:00:00 2001 From: Scott Gasch Date: Sat, 5 Sep 2026 02:17:20 -0700 Subject: Bishop attack/mobility: bitboard rewrite, deliberate x-ray simplification _EvalBishop's mobility ray-walk (per-square g_iBDeltas delta-walk + BMobCaseTable switch dispatch) replaced with _BishopAttacksBB(c, pos->bbOccupied) -- generate.c's magic-bitboard slider lookup, already used by move generation -- plus bitboard masks for the mobility count, matching knight's reduction pattern: enemy-non-pawn-terminal counts unconditionally, empty-or-enemy-pawn- terminal counts unless pawn-unsafe (via bbPawnAttacks), and the one case that isn't a pure occupancy mask -- a terminal friendly, non-stationary pawn on this bishop's own color complex -- keeps its credit via bbPc (already computed for the existing good/bad transient-pawn scoring, untouched this change). Adds POSITION::bbMinorAttacks contributions from bishop (direct attack bits) and POSITION::bbMinorXrayAttacks (new field) for squares seen through a friendly bishop/queen battery partner or an x-rayable enemy rook/queen/king. Deliberate behavior change from the old ray-walk, made for speed per direct instruction: the old walk's fStop=FALSE for the x-ray cases meant it kept going -- and kept counting mobility -- through however many x-ray-worthy blockers were stacked consecutively on one ray (e.g. x-raying an enemy rook, then continuing to x-ray *through* an enemy king sitting right behind it too). The bitboard version only extends one hop past the first x-ray-worthy blocker; it does not re-check whether the newly-revealed terminal square is itself x-ray-worthy and extend again. This was found and deliberately kept (not fixed) after a DEBUG assert caught the exact case on 8/1R1B4/2B1r3/5k2/2P2P2/1p6/1Kb5/7n w - - during precommit's random- sample smoke test -- judged an acceptable trade given how rare a ray with >=2 consecutive x-ray-worthy pieces is. Two new transitional helpers (_IsSquareAttackedByMinor's bishop contribution, and new _IsSquareXrayedByMinor) carry bishop's combined state to every remaining consumer (rook/queen mobility-safety checks, king's danger computation, _WhoControlsSquareFast) -- both DEBUG- asserted against an independent from-scratch mailbox ray-walk (using the still-live rgSquare representation, not any shared code with the production bitboard technique) implementing this same single-hop rule, so a real regression fails loudly rather than drifting into a wrong score. Verified via precommit_check.sh and the full tests/ecm_ringers.ep_ suite at sd10 against the prior commit (7e3e6b6): same 10/11 solved (same single miss, ECM.335, in both), node counts now legitimately differ per position (expected given the semantic change) but stay in a bounded, reasonable range (-17% to +20%), nothing resembling the 60%+ blowups a real bug produced earlier in this same session before being caught, isolated, and traced to this exact x-ray-chain gap. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01P6g6iF6mD1Hau6nCZCzwYj --- src/chess.h | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) (limited to 'src/chess.h') diff --git a/src/chess.h b/src/chess.h index 2e9189a..305c681 100755 --- a/src/chess.h +++ b/src/chess.h @@ -716,19 +716,29 @@ typedef struct _POSITION // bbRookAttacks/bbQueenAttacks that will retire the rest of it. BITBOARD bbPawnAttacks[2]; - // First mover of the bbMinorAttacks pair (knight only for now -- - // bishop is a separate, later step; see board_representation/ - // EVAL.md section 2). "Which squares does this side's knight(s) - // attack" -- computed via g_KnightAttacksBB[c] and OR'd in per - // knight, Eval()-scoped scratch like bbPawnAttacks above, cleared - // once per Eval() call in _ClearAttackTables since (unlike - // bbPawnAttacks) it's accumulated across multiple pieces via |=, - // not assigned wholesale. Bishop still writes its own minor-bit - // contribution into the old rgSquare[c|8].bvAttacks structure for - // now -- every consumer that needs "any minor's attack," not just - // knight's, ORs both sources together until bishop converts too. + // "Which squares does this side's knight(s) or bishop(s) attack" -- + // knight ORs in g_KnightAttacksBB[c] directly (no per-square + // bit-scan needed); bishop ORs in _BishopAttacksBB(c, + // pos->bbOccupied), generate.c's magic-bitboard slider lookup. + // Eval()-scoped scratch like bbPawnAttacks above, cleared once per + // Eval() call in _ClearAttackTables since (unlike bbPawnAttacks) + // it's accumulated across multiple pieces via |=, not assigned + // wholesale. Rook/queen/king are not converted yet and still write + // their own bits into the old rgSquare[c|8].bvAttacks structure -- + // see _IsSquareAttackedByMinor (eval.c), the transitional helper + // every consumer goes through until they convert too. BITBOARD bbMinorAttacks[2]; + // Squares seen *through* a friendly bishop/queen battery partner + // or an x-rayable enemy rook/queen/king, for bishops only (knights + // never x-ray) -- see _EvalBishop's mobility loop. Same lifetime/ + // clearing discipline as bbMinorAttacks above. Nothing else writes + // this yet (rook/queen have their own xray bits still living in + // the old bvAttacks structure), so unlike bbMinorAttacks' direct- + // bit case there's no old-structure fallback to combine with -- + // see _IsSquareXrayedByMinor. + BITBOARD bbMinorXrayAttacks[2]; + ULONG uWhiteSqBishopCount[2]; // num bishops on white squares SCORE iMaterialBalance[2]; // material balance -- cgit v1.3