summaryrefslogtreecommitdiff
path: root/tests/walkpv_bug_repro.txt
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-18 09:43:17 -0700
committerScott Gasch <[email protected]>2026-09-18 09:43:17 -0700
commitf698c02de213d0ed8968fb7cca5f70df51fa0763 (patch)
tree080d6443fbe62d71de092f9acde61da387dff408 /tests/walkpv_bug_repro.txt
parent4a7ec9ac91939957fb3d3e5564fc24a385b422ff (diff)
Redesign lazy/super-lazy eval margins with position-specific percentile
tables, and fix four real search/recognizer bugs found while validating it Main theme: replace the flat, heavily-padded LAZY_EVAL_BASE_MARGIN / SUPER_LAZY_MARGIN_BY_ARMY constants with per-material-bucket percentile tables (king safety by attacker army, residual by own army, separate tables for the lazy vs super-lazy baselines) derived from a 30k-position calibration pool, targeting a measured ~1-1.5% exceeded-margin rate instead of an unmeasured "add lots of headroom" floor. QSearch's FUTILITY_BASE_MARGIN_LAZY/SUPERLAZY were re-derived to match the new, now-meaningful (p50-based) iPositional this produces. Self-play confirms the working tree beats both HEAD, a more liberal (~5% miss target) variant, and a more conservative (near-zero miss) variant of the same margin mechanism. Along the way, an initial self-play regression (this margin work bundled with several changes in one pass, one of which broke things) led to a day of ZFS-snapshot bisection and then targeted DEBUG-build fuzzing against a stratified 200k-position corpus (tests/cross_sectional_200k.ep_, new), which is what actually found and fixed four distinct, real, pre-existing engine bugs -- none of them related to the margin redesign itself: - searchsup.c (CommonSearchInit): three early-return paths (timer abort, draw, too-deep bailout) left a node's own PV[] row stale instead of clearing it, letting an ancestor's UpdatePV splice a leftover move from a completely unrelated node into the displayed PV -- root cause of a WalkPV/MakeMove assert ("moving a piece to capture, but nothing was on the target square"). - dynamic.c (RecordEnprisePieceAtPly): the two-slot en-prise dedup compared (square, piece) instead of square alone, so a same-square re-record with a different piece (legitimate -- a fail-high capture's victim is restored by UnmakeMove and can differ across calls at the same ply) shifted a duplicate square into both slots, tripping ValueOfMaterialInTroubleDespiteMove's two-distinct-squares assert. - recogn.c (_RecognizeKBKP): the wrong-colored-bishop corner-fortress draw shortcut fired even when the weak side has its own pawn(s), which can hand the strong side real zugzwang resources the fortress logic doesn't account for -- produced a false hard draw bound where EGTB shows a near-forced loss. - eval.c (_EvalPasserRacesAgainstLoneKings): a rook pawn's race was credited as winning even when the strong side's own king sits exactly on its own pawn's queening square (distance 0 specifically -- adjacent is fine, the square is empty) -- a classic stalemate resource the race heuristic can't see. Only reachable through _RecognizeKPK's pawn-hash-cache-hit path, which is why testrecogn.c's TestRecognExhaustiveKPK reported "0 wrong" the whole time; extended it to cover both color assignments and the full board (no mirror-shortcut) and documented the remaining cache-hit coverage gap for a future session to close. Also: match_play.py gains a local_ebf metric (geometric-mean of depth-normalized node-growth ratios between consecutive completed iterations) alongside the existing cumulative nodes**(1/depth) ebf, which turned out to be misleading when comparing engines with different lazy-exit aggressiveness -- and probe.c/hash.c pick up two small, independently-found correctness fixes (TB root-probe promotion piece missing its color bit; ASSERT(g_pHashTable) crashing on the legitimate "--hash none" config) noticed along the way. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'tests/walkpv_bug_repro.txt')
-rw-r--r--tests/walkpv_bug_repro.txt58
1 files changed, 58 insertions, 0 deletions
diff --git a/tests/walkpv_bug_repro.txt b/tests/walkpv_bug_repro.txt
new file mode 100644
index 0000000..af77cc0
--- /dev/null
+++ b/tests/walkpv_bug_repro.txt
@@ -0,0 +1,58 @@
+Pre-existing WalkPV assertion crash (util.c:1093), found 2026-09-08
+during the board-representation-migration self-play investigation.
+
+STATUS: confirmed pre-existing on unmodified HEAD (434fa04), NOT
+introduced by any of that session's changes. Not yet root-caused.
+Reproduces intermittently depending on accumulated hash-table state,
+not on the triggering FEN alone in isolation.
+
+Repro (DEBUG build, asserts compiled in):
+
+ ./_typhoon --batch --command "force; setboard rnb1r1k1/pp5p/2pp2p1/4b3/2P1P2q/1NN1BPp1/PP1QB1P1/3RR1K1 b - -; sd 5; go"
+
+Crash happens during the *subsequent* Ponder() call after the "go"
+search completes (visible as "_[pondering ...] white(move 1)_" in the
+output right before the assertion) -- NOT during the sd 5 search
+itself. A bare "sd 5; go" against this FEN with no prior search history
+in the process did NOT reproduce it in at least one isolated attempt;
+it may depend on hash-table/PV state accumulated from prior searches
+in the same process (same category as the second crash found the same
+day, recogn.c:1359 _SanityCheckRecognizers -- also state-dependent,
+also confirmed pre-existing on 434fa04).
+
+Backtrace (gdb, DEBUG build at 434fa04):
+
+ #0 SystemDebugBreakpoint () at unix.c:405
+ #1 _assert (...) at util.c:824
+ #2 WalkPV (ctx=0x25d918 <Ponder.ctx>) at util.c:1093
+ #3 UtilPrintPV (ctx=..., iAlpha=-421, iBeta=32767, iScore=39, mv=...) at util.c:1214
+ #4 RootSearch (ctx=..., iAlpha=-32767, iBeta=32767, uDepth=80) at root.c:857
+ #5 Iterate (ctx=...) at root.c:1287
+ #6 Ponder (pos=...) at root.c:1592
+ #7 MainProgramLoop () at main.c:695
+ #8 main (argc=4, argv=...) at main.c:757
+
+At the point of the crash (frame 2, WalkPV locals):
+ u = 2, v = 1
+ ctx->sPlyInfo[v].PV[u] == HASHMOVE (uMove = 286361736,
+ cFrom = 136 (0x88, the illegal/off-board sentinel coordinate),
+ cTo = 136, pMoved = pCaptured = pPromoted = 1, bvFlags = 1)
+ ctx->sPlyInfo[v].PV[u+1] == the SAME non-zero value as PV[u]
+
+WalkPV's <TT> branch (util.c ~1089-1093) matches
+`mv.uMove == HASHMOVE.uMove`, then asserts the *next* PV slot is the
+zero-terminator:
+
+ ASSERT(ctx->sPlyInfo[v].PV[u+1].uMove == 0);
+
+but PV[u+1] holds the same non-zero "garbage-looking" value as PV[u],
+so the assert fires. HASHMOVE itself looks corrupt/never-properly-set
+here -- cFrom == cTo == the illegal-square sentinel (0x88) rather than
+a real move or a clean zero, which reads like leftover/uninitialized
+state being compared as if it were a legitimate hash move.
+
+Not yet investigated further: whether HASHMOVE is stale from a
+previous ply/search, whether it's a genuine PV-array corruption during
+Ponder specifically (pondering is the only reproduction path found so
+far), or something else. This file exists so the exact repro isn't
+lost to a later smoke_test run overwriting the same sample filename.