summaryrefslogtreecommitdiff
path: root/src/eval_tune/test_vs_head.sh
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-10-04 08:17:52 -0700
committerScott Gasch <[email protected]>2026-10-04 08:17:52 -0700
commitbbdf1bda8f410df6aa9be0a38e71b7db04982198 (patch)
treee8315247f700ee94875cd6d2eb5dd81aef71395d /src/eval_tune/test_vs_head.sh
parent9b28080f8b3dca4588cb2aaaf748451f45e3c626 (diff)
Dynamic move-ordering/LMR session: honest FH tracking, decoupled admission/magnitude, bugfixes, tooling
Lands the 2026-09-28..10-03 move-ordering investigation (full history in lmr_testing/*.md). Summary of what ships: LMR/move-ordering redesign (searchsup.c, dynamic.c, data.c, chess.h): - GetLMRReduction's admission decision (do we reduce at all) is now decoupled from magnitude (how much): LMR_STATIC_ADMIT_THOUSANDTHS=500 gates admission via the 4D joint fail-high table (data.c) plus a countermove-match veto; magnitude is a flat Crafty/Ethereal-shaped log(depth)*log(moves) table (g_iLMRQuietReduction), which measured as good or better than the old per-tier joint-table payout while being structurally immune to the per-bucket miscalibration that sank the tiered approach. g_FailHighs honesty fix (real 0/0 init, not a fake prior) underlies the dynamic-danger signal. - Retired NumLeftoverMovesToSelect's depth-indexed selection budget -- evidence showed the leftover pool has real findable signal a bailout was discarding. - ShouldLatePrune (overt late-move pruning) implemented but disabled at its call site -- measured net-negative at the tested threshold; kept for reference and as a base for a future hang-bucket-9-targeted retry. - PRUNE_VERIFY, PRUNE_LIVE, DISABLE_LMR: opt-in diagnostic build flags (GNUmakefile), off in the standard profile. Bug fixes found and fixed while exercising this work: - JOINT_EXTRA_TIER_COUNT 4->6 (chess.h): fixed an out-of-bounds counter write reachable at remaining depth >= 14 (unreachable at sd10, routine in real search/self-play). - Node-type instrumentation (search.c, root.c, searchsup.c): fixed four compounding defects that produced impossible percentages (e.g. 106.2% of PV nodes) -- wrong denominator, qsearch misclassified as main search, untyped early-exit nodes, and fail-low tests using the in-place-raised alpha instead of the initial one. - FH-rate unit unification (search.c, searchsup.c, dynamic.c, chess.h): GetMoveFailHigh* returned per-mille while g_uJointFHThousandths is thousandths-of-a-percent, two different units sharing a "_THOUSANDTHS" suffix. Unified to one scale, renamed to "_PCT_THOUSANDTHS". RateBucket's band edges fixed to match. - QSearch option B (search.c): waives the opponent-stand-pat requirement at NODE_PV only (sound there since the exact score propagates; not sound at null-window nodes). iExtensionAmount unit fix (was 0/2 into a ONE_PLY-scaled field). pi->iEval now published when Eval() runs. - eval_tune/match_play.py local_ebf: was reporting 0.000 in every match since 2026-09-18 (regex only matched the trailing-stats parser, not the live move-scan loop; engines were also spawned with "nopost" so root.c never emitted the lines at all). True per-ply EBF is ~2.0, not the ~3.9 every prior match report implied. - eval_tune/test_vs_head.sh: positional-arg shift ordering bug caused every no-extra-args run to silently rebuild ./typhoon with a non-standard profile and, separately, could feed a flag's value in as the candidate binary path. - PERF_COUNTERS cleanup (eval.c, search.c): moved 3 leaked rdtsc sites to the existing EVAL_TIME guard, deleted two concluded one-off diagnostics (u64HeadStyle*, u64FHJointHist/RateBucket/AttemptsBucket) and their report plumbing, and re-gated a per-move hang-material estimate behind FH_TABLE_COLLECTION instead of running it unconditionally. Infrastructure: - match_play.py time-odds support (--odds/--st-head/--st-candidate), validated against the engine seat rather than color. - Wilson-upper-bound re-bake of the joint table (data.c): cells store a confidence-aware bound instead of a raw point estimate, so a consumer can threshold on the value alone; monotone, so every consumer became strictly more conservative. Verified behavior-neutral for LMR. Known-open items (not resolved here, see lmr_testing/GET_LMR_REDUCTION.md section 10 and 2026-10-03_UNCOMMITTED_TRIAGE.md): the self-play gate on this candidate is underpowered (+5 Elo, 95% CI roughly [-6, +17]); the curated-suite solve-count deltas used earlier in the investigation turned out to be noise-dominated cancellation when diffed per position, not the clean signal they first appeared to be. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/eval_tune/test_vs_head.sh')
-rwxr-xr-xsrc/eval_tune/test_vs_head.sh73
1 files changed, 66 insertions, 7 deletions
diff --git a/src/eval_tune/test_vs_head.sh b/src/eval_tune/test_vs_head.sh
index d6d9fba..592ecf1 100755
--- a/src/eval_tune/test_vs_head.sh
+++ b/src/eval_tune/test_vs_head.sh
@@ -24,17 +24,75 @@
# matches can run concurrently without colliding.
# Defaults to "match".
#
+# ...and any further arguments are passed straight through to
+# match_play.py, so a one-off flag no longer needs this file edited.
+#
+# Three env vars override the baked-in defaults (2026-10-03), so the
+# common knobs are reachable without touching the invocation below:
+#
+# TC_ARGS time control. Default: "--st 4"
+# SPRT_ARGS hypothesis test. Default: "--sprt --elo0 0 --elo1 5"
+# GAMES game cap. Default: 10000
+#
+# Why these became overridable: the baked-in "--sprt --elo0 0 --elo1 5"
+# cannot resolve the effect sizes this project's LMR work actually
+# produces. At the observed draw rate the standard error in Elo is about
+# 249/sqrt(n), so separating 0 from 5 Elo at even 2 sigma needs ~10,000
+# games (~53h here) -- and every LMR match run to date has instead
+# wandered around llr 0 for 2,000+ games and been abandoned, which is
+# exactly what a true effect sitting BETWEEN the two hypotheses looks
+# like. For a change you would only ship at >= 15 Elo, use
+# SPRT_ARGS="--sprt --elo0 0 --elo1 15": it rejects a +2 effect in hours
+# instead of never.
+#
# Examples:
# test_vs_head.sh # old default behavior
# test_vs_head.sh /tmp/typhoon/filestorm_only /tmp/typhoon/candidate queenprox_backflight
+#
+# # Faster-rejecting bounds for a change only worth shipping at >=15 Elo:
+# SPRT_ARGS="--sprt --elo0 0 --elo1 15" test_vs_head.sh
+#
+# # TIME-ODDS CALIBRATION: same binary both sides, candidate gets 2x the
+# # time. Measures what ~1.6 extra plies (at ebf 3.6) is worth in Elo in
+# # THIS engine, which bounds what any depth-buying search change
+# # (LMR/EFP/LMP) can ever deliver. No SPRT -- this measures a quantity
+# # rather than testing a hypothesis, and ELO=+/- prints either way.
+# # ~600-1000 games is plenty: the expected effect is large, so +/-10 Elo
+# # of precision already separates "a ply is worth 25" from "worth 65".
+# SPRT_ARGS="" TC_ARGS="--st 4 --odds 2" GAMES=1000 \
+# test_vs_head.sh ../../head_reference/typhoon \
+# ../../head_reference/typhoon timeodds
set -e
HEAD="${1:-../../head_reference/typhoon}"
+CANDIDATE_ARG="${2:-}"
LABEL="${3:-match}"
+# Note the `-` (not `:-`) for the three env overrides below: an
+# explicitly EMPTY value must be honored, not replaced by the default.
+# SPRT_ARGS="" is the documented way to run without a hypothesis test
+# (e.g. the time-odds calibration above), and `:-` would silently put
+# --sprt back.
+TC_ARGS="${TC_ARGS---st 4}"
+SPRT_ARGS="${SPRT_ARGS---sprt --elo0 0 --elo1 5}"
+GAMES="${GAMES-10000}"
+
+# Drop the (up to) three positional args so "$@" holds only extra
+# pass-through flags for match_play.py. This MUST come after the three
+# positionals have been captured into HEAD/CANDIDATE_ARG/LABEL above:
+# an earlier version of this shifted first and then tested "$2" to
+# decide whether to rebuild, which (a) made every no-extra-args run
+# take the rebuild branch and silently clobber ../typhoon with a
+# different make profile, and (b) made the first pass-through FLAG VALUE
+# land in "$2" and get used as the candidate BINARY PATH -- producing a
+# baffling "PermissionError: ... Permission denied: <scratch dir>" from
+# Popen trying to execute a directory.
+for _ in 1 2 3; do
+ if [ $# -gt 0 ]; then shift; fi
+done
-if [ -n "$2" ]; then
- CANDIDATE="$2"
+if [ -n "$CANDIDATE_ARG" ]; then
+ CANDIDATE="$CANDIDATE_ARG"
else
cd ..
gmake clean
@@ -48,10 +106,11 @@ python3 ./match_play.py \
${HEAD} \
${CANDIDATE} \
--pgn ../../pgn/twic_filtered.pgn \
- --games 10000 \
- --workers 12 \
- --st 4 \
- --sprt --elo0 0 --elo1 5 \
+ --games ${GAMES} \
+ --workers 18 \
+ ${TC_ARGS} \
+ ${SPRT_ARGS} \
--scratch /usr/local/tmp/typhoon_match_${LABEL} \
--log ${LABEL}.log \
- --pgn-out ../${LABEL}_games.pgn
+ --pgn-out ../${LABEL}_games.pgn \
+ "$@"