summaryrefslogtreecommitdiff
path: root/src/see.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-10-07 08:17:51 -0700
committerScott Gasch <[email protected]>2026-10-07 08:17:51 -0700
commitd14dbe2a50c220b2adac758c3d4f1a1fa67ece0b (patch)
treecbf6d9928bcc41ef833b62b70479113f7d419925 /src/see.c
parent447130db3df87b40d080dc26429d45e9213c19f7 (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.c111
1 files changed, 91 insertions, 20 deletions
diff --git a/src/see.c b/src/see.c
index dd60537..fa8e8db 100644
--- a/src/see.c
+++ b/src/see.c
@@ -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));
+}