summaryrefslogtreecommitdiff
path: root/src/search.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-10-15 11:12:37 -0700
committerScott Gasch <[email protected]>2026-10-15 11:12:37 -0700
commitef2c2f667c4fc2c0303f56ea487183916f82fb67 (patch)
treed781a71ea958ca421521b21c93eb9c2ce59df65c /src/search.c
parentd9419c104e9fe193649931cee167b819044c6ca3 (diff)
Fix several latent search/move-generation bugs found via NNUE annotation tooling
- probe.c: EGTB win/loss scores now render as +-MATE100 (literal +-NMATE) instead of a misleading float, matching how a real forced mate prints. - hash.c: fix a sign error in StoreExactScore's loss-side hash bound (iValue +NMATE paired with HASH_FLAG_UPPER, should be -NMATE) -- dormant until the probe.c fix made it reachable every EGTB loss. - san.c: a checking castle move (O-O+/O-O-O+) printed as +-O/+-O-O -- the check-suffix pointer was captured before strcpy filled the buffer, so '+' overwrote the leading 'O' instead of appending. - search.c/searchsup.c: MATEMOVE/DRAWMOVE/RECOGNMOVE/EGTBMOVE now record into the PV unconditionally instead of only when the ambient alpha-beta window happens to strictly contain the score -- these are exact, game-theoretic results, not heuristic bounds, so a PVS scout-window tie (common once a line's value is already known) shouldn't suppress them. This is why <#>/<=>/<REC>/<EGTB> almost never showed up in a printed PV. - util.c: WalkPV now reconstructs a still-missing final move/sentinel directly from the board when the recorded PV runs out one ply short of (or exactly at) a forced mate/stalemate/draw, instead of leaving the printed PV silently truncated. - gamelist.c/board.c/testsup.c: SetRootPosition now parses into a local staging POSITION and validates IsBoardLegal + VerifyPositionConsistency (both moved/enabled for all build profiles, not just TEST/EVAL_DUMP) before committing to g_RootPosition. Previously a rejected setboard (e.g. a hand-built FEN leaving the side not on move in check) still left the partially-parsed, illegal position in g_RootPosition for the next `go` to search -- GetRootPosition() hands out a stable pointer straight to it with no defensive copy -- causing a crash instead of the clean "Error (malformed FEN)" rejection already printed. All verified via precommit_check.sh (TEST=1 self-test suite + DEBUG smoke test) in addition to targeted manual repros for each bug. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/search.c')
-rw-r--r--src/search.c58
1 files changed, 32 insertions, 26 deletions
diff --git a/src/search.c b/src/search.c
index 9350fad..8404d9d 100644
--- a/src/search.c
+++ b/src/search.c
@@ -544,17 +544,20 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
case UNRECOGNIZED:
break;
case RECOGN_EXACT:
- if ((iAlpha < iScore) && (iScore < iBeta))
- {
- UpdatePV(ctx, RECOGNMOVE);
- }
+ // Unconditional: a recognizer-proven exact result is a
+ // game-theoretic fact about this position, not a bound --
+ // unlike an ordinary heuristic score, it can't be "wrong"
+ // for having been reached outside the ambient (alpha,
+ // beta) window, so it deserves to be recorded regardless
+ // (see CLAUDE.md's MATEMOVE/DRAWMOVE/RECOGNMOVE/EGTBMOVE
+ // writeup -- all five sentinel cases shared this same
+ // gate, which is why <REC>/<EGTB>/<#>/<=> almost never
+ // showed up in a printed PV).
+ UpdatePV(ctx, RECOGNMOVE);
iBestScore = iScore;
goto end;
case RECOGN_EGTB:
- if ((iAlpha < iScore) && (iScore < iBeta))
- {
- UpdatePV(ctx, EGTBMOVE);
- }
+ UpdatePV(ctx, EGTBMOVE);
iBestScore = iScore;
goto end;
case RECOGN_LOWER:
@@ -1203,22 +1206,22 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
ASSERT(IS_CHECKING_MOVE(mvLast));
ASSERT(InCheck(pos, pos->uToMove));
iBestScore = MATED_SCORE(ctx->uPly);
- if ((iAlpha < iBestScore) && (iBestScore < iBeta))
- {
- INC(ctx->sCounters.tree.u64LeafCount);
- UpdatePV(ctx, MATEMOVE);
- }
+ // Unconditional -- see the RecognLookup sites above: a
+ // checkmate is an exact, game-theoretic fact about this
+ // position, not a bound, so it can't be "wrong" for
+ // having been reached outside the ambient window.
+ INC(ctx->sCounters.tree.u64LeafCount);
+ UpdatePV(ctx, MATEMOVE);
ASSERT(iBestScore <= -NMATE);
goto end;
}
else
{
iBestScore = g_iDrawScore[pos->uToMove];
- if ((iAlpha < iBestScore) && (iBestScore < iBeta))
- {
- INC(ctx->sCounters.tree.u64LeafCount);
- UpdatePV(ctx, DRAWMOVE);
- }
+ // Unconditional -- same reasoning as the MATEMOVE case
+ // just above: stalemate is exact, not a bound.
+ INC(ctx->sCounters.tree.u64LeafCount);
+ UpdatePV(ctx, DRAWMOVE);
goto end;
}
}
@@ -2167,17 +2170,20 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx,
case UNRECOGNIZED:
break;
case RECOGN_EXACT:
- if ((iAlpha < iScore) && (iScore < iBeta))
- {
- UpdatePV(ctx, RECOGNMOVE);
- }
+ // Unconditional: a recognizer-proven exact result is a
+ // game-theoretic fact about this position, not a bound --
+ // unlike an ordinary heuristic score, it can't be "wrong"
+ // for having been reached outside the ambient (alpha,
+ // beta) window, so it deserves to be recorded regardless
+ // (see CLAUDE.md's MATEMOVE/DRAWMOVE/RECOGNMOVE/EGTBMOVE
+ // writeup -- all five sentinel cases shared this same
+ // gate, which is why <REC>/<EGTB>/<#>/<=> almost never
+ // showed up in a printed PV).
+ UpdatePV(ctx, RECOGNMOVE);
iBestScore = iScore;
goto end;
case RECOGN_EGTB:
- if ((iAlpha < iScore) && (iScore < iBeta))
- {
- UpdatePV(ctx, EGTBMOVE);
- }
+ UpdatePV(ctx, EGTBMOVE);
iBestScore = iScore;
goto end;
case RECOGN_LOWER: