summaryrefslogtreecommitdiff
path: root/src/searchsup.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/searchsup.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/searchsup.c')
-rw-r--r--src/searchsup.c39
1 files changed, 13 insertions, 26 deletions
diff --git a/src/searchsup.c b/src/searchsup.c
index 64dc991..7c69d0a 100644
--- a/src/searchsup.c
+++ b/src/searchsup.c
@@ -2631,32 +2631,19 @@ Return value:
INC(ctx->sCounters.tree.u64LeafCount);
*piScore = g_iDrawScore[ctx->sPosition.uToMove];
// 2026-09-18: pi->PV[ctx->uPly] has the exact same staleness
- // hazard as pi->mvBest above, but was only ever cleared
- // conditionally, via UpdatePV(ctx, DRAWMOVE) -- and only when
- // the draw score actually falls in the open (alpha, beta)
- // window. When it doesn't, this ply's PV[] row is left
- // whatever an unrelated earlier node last wrote there.
- // sPlyInfo[] rows are shared/reused across every node that
- // ever occupies a given ply (see UpdatePV's own sentinel-case
- // comment), so a later ancestor's UpdatePV can blindly copy
- // that stale row forward via `this->PV[u] = next->PV[u]`,
- // trusting its zero-terminator as proof of freshness --
- // splicing a leftover move from a completely different
- // subtree onto the real line. This is the root cause of a
- // WalkPV/MakeMove assertion failure (move.c:881,
- // "ASSERT(!IS_EMPTY(pCaptured))" -- a move that's real
- // *somewhere* but whose capture target is empty on the
- // actual board) reproduced against tests/cross_sectional_
- // 200k.ep_. Clear unconditionally so a non-window draw score
- // can't leave this row stale.
- if ((*piAlpha < *piScore) && (*piScore < *piBeta))
- {
- UpdatePV(ctx, DRAWMOVE);
- }
- else
- {
- pi->PV[ctx->uPly].uMove = 0;
- }
+ // hazard as pi->mvBest above. That was fixed defensively back
+ // then (clear the row when the draw score misses the ambient
+ // (alpha, beta) window, rather than leave whatever an
+ // unrelated earlier node last wrote in this shared sPlyInfo[]
+ // slot) rather than at the root: a repetition/50-move draw is
+ // a known, exact fact about the position, not a bound, so
+ // there's no reason its PV record should depend on the
+ // window at all -- unconditionally recording it here (same
+ // reasoning now applied to MATEMOVE/RECOGNMOVE/EGTBMOVE in
+ // search.c) makes the staleness hazard this comment used to
+ // describe structurally unreachable from this site, not just
+ // defended against.
+ UpdatePV(ctx, DRAWMOVE);
DTTRACE("<i bold=\"draw\"/>");
goto end;
}