summaryrefslogtreecommitdiff
path: root/src/searchsup.c
AgeCommit message (Collapse)Author
14 daysFix QSearch stand-pat gating bug and ComputeMoveScore's SEE contamination; ↵Scott Gasch
tune singular-reply-to-check margin. QSearch stand-pat: the "deny stand pat when material is genuinely in trouble" check was gated on fCouldStandPat (has this side had a chance to stand pat earlier in this qsearch line). That's wrong -- whether an ancestor node had a moment of safety says nothing about whether *this* node's material danger is real; a hanging piece doesn't stop hanging because the position was quiet three plies ago. fCouldStandPat's legitimate uses (search.c:950, 1190/1193) are about deciding whether a *whole line* looks forcing enough to justify extra qsearch depth/ breadth, a different question from per-node stand-pat correctness. Removed the gate; the material-in-trouble check now always denies stand-pat, regardless of history. ComputeMoveScore: for winning/even captures and promotions, the value extracted from the move-ordering sort key (generate.c's _ScoreAllMoves) included a flat +120 ordering bias plus small MVV-LVA tie-break nudges (PIECE_VALUE_OVER_100 terms) baked in on top of the real SEE/ material-diff value. Harmless for its original sorting purpose (every capture gets the same treatment), but this function's callers (futility pruning, the singular-reply-to-check extension) use the result as an eval-axis quantity compared against material-scale margins -- the contamination doesn't belong there. Subtracted the ordering-only bias back out to recover pure SEE/material-diff, same axis as the raw PIECE_VALUE() fallback used when no move-stack index is available. Quiet-move and losing-capture handling were already correct (both collapse to a clean, uncontaminated value). Also bumped the singular-reply-to-check margin (225 -> 400): confirmed via direct A/B on the quick suites that this is a real, independent improvement on top of the SEE fix, not just compensating for it -- reverting to 225 measurably regressed both ecm_confident_quick.ep_ (89->88/90) and ecm_hard_quick.ep_ (10->6/90) versus keeping 400. Verified against pristine baseline (no LMR in this binary) on the three-suite protocol (ecm_ringers.ep_, ecm_confident_quick.ep_, ecm_hard_quick.ep_, sn=5M, book disabled): 11/11 ringers (matches baseline exactly), 89/90 confident (vs baseline's 90/90), 10/90 hard (vs baseline's 4/90) -- a real net improvement over baseline with zero LMR involved, considerably stronger than any state reached earlier in this session's LMR-only experimentation. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01YGSMkwjqiCk4XhbfN7ugD2
2026-08-27Baseline: uPositional data-calibrated fix, enprise/trapped hints, ↵Scott Gasch
EBF/beta-cutoff/counter-move stats, script.c FPE fix. No LMR, no counter-move-driven move ordering (both explored separately, kept out for now -- counter-move measured worse, ~655->647 solved on ecm879 @ sn=4M with a leaner tree beforehand). Futility pruning restored. Verified: 647/879 solved, EBF 4.609 @ sn=4M; 684/879 solved, EBF 3.995 @ 20s/move, 1cpu, 256m hash (typhoon_baseline.log). The counter-move table is still written and its stats still tracked (dynamic.c) for diagnostic purposes, but generate.c no longer reads it for move ordering, so it has no effect on search behavior in this commit. lmr_testing/ holds the in-flight graded-LMR + counter-move code (not applied here) with notes on what was already tried and measured, so a future session can resume without re-deriving it.
2026-08-26Replace ctx->uPositional with a data-calibrated Eval() return value.Scott Gasch
uPositional was a per-thread EWMA of abs(material - true score) used to size lazy-eval and futility margins. It was history-derived (reflecting whatever recent, unrelated positions looked like) rather than derived from the position actually being margined, and its update/consumption was tangled with EVAL_HASH (now disabled). Eval() now takes an optional SCORE *piPositional out-param and fills it in on every return path: exact (abs(material-delta)) on a full eval, or an estimate from a new EstimatePositionalScore() on a lazy exit. EstimatePositionalScore()'s two terms (king-safety-defect-bucketed, and a flat residual for mobility/passers/everything else) are calibrated from ~1.6M measured full-eval samples (p90 of the actual swing), not guessed -- an initial guessed version measurably regressed ECM solve rate (630 vs a 650 baseline at sn=4M); the recalibrated version is back at parity (649/879). search.c's qsearch futility now reads the value Eval() just computed instead of the stale/shared ctx field. Also removes QSearchInDangerNoStandPat and SideCanStandPat, dead since the danger-hash check that fed them was already commented out (e08387a) -- they depended on the same enprise/ trapped-piece data this conversation is about to move off of g_PositionHash entirely. Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-26Don't [re-]consult the SEE when generator already just did so.Scott Gasch
2026-08-25Tame check-extension compounding; verified neutral in self-play (0.4955)Scott Gasch
Fixes a real pathology: checks along a long unbroken forcing line could extend for free (net zero cost against the qsearch boundary), letting tree size blow up multiple orders of magnitude on positions like a near-all-check forced mate (ECM.089: 4.5B nodes / 31min at depth 12 before this change). - Main-search check extension: gate on SEE soundness (a losing sacrifice check gets a small consolation QUARTER_PLY instead of the full bonus a sound check gets), and flatten the sound-check bonus to a flat THREE_QUARTERS_PLY instead of a near-free ONE_PLY. - Lower the qsearch entry threshold to match (THREE_QUARTERS_PLY instead of ONE_PLY) so a lone check still buys one extra full-width ply as before; root.c trims QUARTER_PLY off the per-iteration depth budget so this doesn't add a blanket 1/4 ply to every search. - Qsearch's own check-widening (QSearchFromCheckNoStandPat) now relies on fCouldStandPat history plus a g_uIterateDepth/4 ceiling instead of an unconditional per-check grant, and QPLIES_OF_NON_CAPTURE_CHECKS moved from 1 to 2 to cover both "enter qsearch already in check" and "opponent's reply is the first real check" cases with one baseline window instead of ad hoc attacker-color tracking. Net effect on ECM.089 (sn 4M canary): ~7.5x fewer nodes and ~4x less time at depth 12 versus the original, unbounded behavior. Costs solve count on the full ECM suite (879 pos, sn 4M): 650 baseline -> 636 here -- expected and accepted, since ECM is unusually check-extension-heavy tactics and not representative of real games. Self-play vs baseline (1000 games, st 1) came back at B_SCORE=0.4955, ELO=-3.1+/-21.5 -- statistically neutral, confirming the fix costs nothing in real play. Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-25Add LMR with PV-adjacency guard (v7), verified against saved binaryScott Gasch
Late Move Reductions using a depth x movecount table (Ethereal-style formula), gated off PV nodes and the ply directly below a PV node (PLY_INFO.fIsPVNode), with magnitude-aware re-search on fail-high. Verified node-for-node identical to the previously tested-good v7 binary on a canary position (sd 10) after reconstructing from a ZFS snapshot of search.c/root.c/split.c taken just before that binary was built. Co-Authored-By: Claude Sonnet 5 <[email protected]>
2016-06-01Initial checkin for typhoon chess engine.Scott Gasch