summaryrefslogtreecommitdiff
path: root/src/unix.c
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/unix.c
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/unix.c')
-rw-r--r--src/unix.c93
1 files changed, 92 insertions, 1 deletions
diff --git a/src/unix.c b/src/unix.c
index 2c90f7f..f400ebb 100644
--- a/src/unix.c
+++ b/src/unix.c
@@ -1003,6 +1003,79 @@ SystemObtainSemaphoreResource(ULONG u)
}
+//
+// Set the first time SIGINT asks a search to abort, and cleared once
+// we next observe the engine idle. Lets a second ^C -- sent before the
+// first one's TIMER_STOPPING has actually unwound the search -- force
+// an immediate exit instead of silently collapsing into a no-op repeat
+// of the first request.
+//
+static volatile sig_atomic_t g_fSigintAbortPending = 0;
+
+static void
+_AbortSearchOrTerminate(int iSig)
+/**
+
+Routine description:
+
+ Signal handler shared by SIGHUP and SIGINT. If a search is in
+ progress, abort just the current search the same way the engine
+ already unrolls on a timer/user-input stop (TIMER_STOPPING) --
+ control returns to whatever is driving the engine (interactively,
+ or the next line of a "script" test suite), rather than killing the
+ process. This is what a stuck ECM/EPD batch position needs: a way
+ to bail out of one pathological search without losing the rest of
+ the run.
+
+ If nothing is being searched right now there's nothing to abort.
+ For SIGINT specifically, fall back to the normal terminate-the-
+ process behavior in that case (restore the default disposition and
+ re-raise) so Ctrl-C still works as expected when the engine is
+ idle. SIGHUP while idle is just ignored.
+
+ A second ^C sent while the first one's abort is still pending (the
+ search hasn't unwound to idle yet -- it can take a moment, e.g. if
+ we're deep inside a long leaf-node burst between TIMER_STOPPING
+ checks) is treated as "I mean it" and kills the process immediately,
+ matching the usual double-^C idiom instead of silently eating the
+ second press.
+
+Parameters:
+
+ int iSig
+
+Return value:
+
+ void
+
+**/
+{
+ if (FALSE == g_Options.fThinking)
+ {
+ g_fSigintAbortPending = 0;
+ if (SIGINT == iSig)
+ {
+ signal(SIGINT, SIG_DFL);
+ raise(SIGINT);
+ }
+ return;
+ }
+
+ if (SIGINT == iSig)
+ {
+ if (0 != g_fSigintAbortPending)
+ {
+ signal(SIGINT, SIG_DFL);
+ raise(SIGINT);
+ return;
+ }
+ g_fSigintAbortPending = 1;
+ }
+ Trace("SIGINT/HUP -- STOP SEARCHING NOW");
+ g_MoveTimer.bvFlags |= TIMER_STOPPING;
+}
+
+
FLAG
SystemDependentInitialization(void)
/**
@@ -1043,5 +1116,23 @@ Return value:
}
InitializeListHead(&g_SystemThreadList);
pthread_mutex_init(&g_SystemLock, NULL);
+
+ //
+ // SIGHUP/SIGINT: abort just the in-progress search (see
+ // _AbortSearchOrTerminate's comment) instead of the default
+ // terminate-the-process disposition. SA_RESTART so a blocking
+ // syscall elsewhere in the process (e.g. the input thread's read())
+ // doesn't get kicked out with EINTR by a signal that has nothing to
+ // do with it.
+ //
+ {
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = _AbortSearchOrTerminate;
+ sa.sa_flags = SA_RESTART;
+ sigemptyset(&sa.sa_mask);
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+ }
return(TRUE);
-}
+}