diff options
| author | Scott Gasch <[email protected]> | 2026-10-07 08:17:51 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-10-07 08:17:51 -0700 |
| commit | d14dbe2a50c220b2adac758c3d4f1a1fa67ece0b (patch) | |
| tree | cbf6d9928bcc41ef833b62b70479113f7d419925 /src/see.c | |
| parent | 447130db3df87b40d080dc26429d45e9213c19f7 (diff) | |
Speed up eval/move-gen/SEE; remove EvalLookForDanger, overhaul _EvalKing
SPRT-confirmed improvement (1814 games, st 4.0): candidate nps=1498118
vs head=1378448, avg depth 14.30 vs 13.87, ELO=+24.2+/-16.0,
LOWER95=0.5118, LLR=+3.126 crossing the H1 bound.
Most of the diff is non-semantic speed work across eval.c, generate.c,
movesup.c, see.c, probe.c and friends. Two real semantic changes:
removed EvalLookForDanger and overhauled _EvalKing's king-safety scoring.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/see.c')
| -rw-r--r-- | src/see.c | 111 |
1 files changed, 91 insertions, 20 deletions
@@ -206,7 +206,7 @@ Return value: // single-bit bitboard of the actual king square is either that bit or // zero, same O(1)-table-lookup shape the knight line already uses, no // DISTANCE()==1 branch needed. -BITBOARD +BITBOARD FORCEINLINE _WhoAttacksSquareBB(IN POSITION *pos, IN COOR cSquare, IN ULONG uSide, @@ -258,7 +258,7 @@ Return value: return bbAttackers; } -void CDECL +void FORCEINLINE _GetAttacksBB(IN OUT SEE_LIST *pList, IN POSITION *pos, IN COOR cSquare, @@ -319,17 +319,14 @@ Return value: // no mailbox load -- entirely replacing what iSeeDelta/pPawn[] // used to do at runtime; only the (0-2) actual hits still need a // COOR to populate the SEE_LIST. + BITBOARD bbPawnHits = g_PawnAttackOriginBB[uSide][cSquare] & + pos->bbPawns[uSide]; + ULONG uPawnBit; + while (bbPawnHits) { - BITBOARD bbPawnHits = g_PawnAttackOriginBB[uSide][cSquare] & - pos->bbPawns[uSide]; - ULONG uPawnBit; - - while (bbPawnHits) - { - uPawnBit = FastFirstBit(bbPawnHits) - 1; - bbPawnHits &= (bbPawnHits - 1); - ADD_ATTACKER(pPawn[uSide], BIT_NUMBER_TO_COOR(uPawnBit), VALUE_PAWN); - } + uPawnBit = FastFirstBit(bbPawnHits) - 1; + bbPawnHits &= (bbPawnHits - 1); + ADD_ATTACKER(pPawn[uSide], BIT_NUMBER_TO_COOR(uPawnBit), VALUE_PAWN); } // @@ -640,7 +637,6 @@ Return value: { pList->uCount--; } - #else // !SEE_HEAPS // @@ -698,7 +694,7 @@ Return value: #ifdef SEE_HEAPS -static PIECE +static PIECE FORCEINLINE _MinLegalPiece(IN POSITION *pos, IN ULONG uColor, IN SEE_LIST *pList, @@ -1008,7 +1004,7 @@ Return value: } -FLAG +FLAG FORCEINLINE SeeFastSign(IN POSITION *pos, IN MOVE mv) /** @@ -1084,15 +1080,18 @@ Return value: SCORE -SEE(IN POSITION *pos, - IN MOVE mv) +_SEEFull(IN POSITION *pos, + IN MOVE mv) /** Routine description: - Given a board and a move on the board, estimate the value of the - move by considering the friend/enemy pieces that attack the move's - destination square. + The real exchange-walk implementation -- see SEE()'s header + comment (below) for the cheap early-exit that now sits in front of + this, and _SEEFull's own non-static, un-inlined status so + testsee.c's TestSEENoDefenderFastPath can call it directly by name + for a correctness/speed comparison against SEE(), same convention + as _GenerateAllMoves/_GenerateAllMovesBB. Parameters: @@ -1244,3 +1243,75 @@ Return value: #endif return(rgiList[0]); } + +SCORE FORCEINLINE +SEE(IN POSITION *pos, + IN MOVE mv) +/** + +Routine description: + + Given a board and a move on the board, estimate the value of the + move by considering the friend/enemy pieces that attack the move's + destination square. + + Cheap early exit before paying for any of _SEEFull's work (two + GetAttacks calls, sort/heap of both, _AddXRays): if the defending + side has NO attacker on mv.cTo -- checked the same x-ray-aware way + SeeFastSign already does (mv.cFrom removed from occupancy, so a + slider the mover was itself blocking gets counted) -- the exchange + can never continue past our own capture, so the true SEE value is + exactly PIECE_VALUE(mv.pCaptured) + PIECE_VALUE(mv.pPromoted). This + is not an approximation: it is the exact value _SEEFull's own + do/while loop would compute on this input (its first + _MinLegalPiece call for the defender would find nothing and break + immediately, returning rgiList[0] unchanged) -- see + TestSEENoDefenderFastPath (testsee.c) for the exact-value + correctness gate against _SEEFull, not just a sign check. + + Unlike SeeFastSign (which only ever needs the sign and so can + throw the magnitude away), callers of SEE() need the real value -- + _ScoreAllMoves/_ScoreQSearchMoves* add an ordering bias on top of + it and DecodeMoveValueEstimate (searchsup.c) later subtracts that + bias back out to recover this exact number for real pruning + decisions in _ShouldWeConsiderThisMove. So this early exit has to + return the true value, not just resolve a sign and hand back a + placeholder -- which is exactly what it does here, no different + from what the full exchange walk would have produced. + + Pawn defenders can't be x-ray-revealed by the mover's own + departure (a pawn's attack squares are a fixed 2-square delta, not + a blockable ray), so that check alone uses the raw board, matching + SeeFastSign's identical reasoning. + +Parameters: + + POSITION *pos, + MOVE mv + +Return value: + + SCORE : the estimate of the move's score + +**/ +{ + ULONG uDefender = FLIP(GET_COLOR(mv.pMoved)); + BITBOARD bbOccupiedWithoutMover; + + if (g_PawnAttackOriginBB[uDefender][mv.cTo] & pos->bbPawns[uDefender]) + { + return _SEEFull(pos, mv); + } + bbOccupiedWithoutMover = pos->bbOccupied & ~COOR_TO_BB(mv.cFrom); + if (_WhoAttacksSquareBB(pos, mv.cTo, uDefender, bbOccupiedWithoutMover)) + { + return _SEEFull(pos, mv); + } + + // + // No defender at all -- the exchange can't continue past our own + // move, so the result is exactly the material gained (captured + + // promoted). + // + return (SCORE)(PIECE_VALUE(mv.pCaptured) + PIECE_VALUE(mv.pPromoted)); +} |
