diff options
| author | Scott Gasch <[email protected]> | 2026-10-23 13:19:17 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-10-23 13:19:17 -0700 |
| commit | 69525eb2f42ea9cca8255f6cc3b85e6b1312fd05 (patch) | |
| tree | d78420b6c45d584a3f09b4dcbaf4f1edb36e8d5d /src/chess.h | |
| parent | 6b883e83935f5e8110788565cfcb77cd6fbf8950 (diff) | |
NNUE search integration: FH% tables, NODE_TYPE refinement, TREEDUMP rewrite
- data.c/chess.h: widen the joint FH% table's late axis 16->17, dedicating
the new bucket 0 to a whole-node ("will something here fail high at
all") sample rather than sharing it with the first-leftover-move curve.
Add an NNUE-eval counterpart table (g_uJointFHThousandths_NNUE) and an
NNUE counterpart of the beta-side table, selected at runtime based on
whether NNUE eval is active.
- search.c/searchsup.c: add _RefineNodeTypeGuess, which uses the FH%
tables (via g_NodeTypeGuessTable) to refine a node's NODE_TYPE guess
instead of the old heuristic; thread uParentMoveFHEstimate through
PLY_INFO so a node can see its parent's prediction for the move that
led to it.
- generate.c: initialize sKillers[2]/[3].uBonus unconditionally (an
uninitialized-stack read caught by valgrind under LTO; harmless today
but a real bug).
- dumptree.c/chess.h/GNUmakefile: replace the long-dead DUMP_TREE XML
tree dump (written for a GUI this project no longer has) with TREEDUMP,
a flat, path-keyed move/result trace meant to diff against an
identically-instrumented Stockfish build -- see docs/TREEDUMP_FORMAT.md.
- docs/DEEPER_PRUNING.md: notes on closing the search-efficiency gap vs
Stockfish.
- crafty_egtb/stockfish_xboard/typhoon_nnue: wrapper scripts so
match_play.py's Engine class can drive Crafty, Stockfish (via
PolyGlot), and an NNUE-enabled typhoon the same way it drives HCE
typhoon.
- tests/lop: rename lop.txt -> lop.ep_; update compare_lop.cpp.
- annotate_pgns: add aggregate_marginsafety.py, dedup_pgn.py,
fit_lazy_probe.py; update annotate.py/run_nnue_pipeline.py for the NNUE
training pipeline.
- .gitignore: ignore compile_commands.json (clangd build DB) and
eval.nnue (a symlink into the untracked head_reference/ comparison
baseline), neither of which belongs in the repo.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/chess.h')
| -rwxr-xr-x | src/chess.h | 156 |
1 files changed, 130 insertions, 26 deletions
diff --git a/src/chess.h b/src/chess.h index 8c5d86c..189e0a4 100755 --- a/src/chess.h +++ b/src/chess.h @@ -1537,6 +1537,40 @@ typedef enum } NODE_TYPE; +// Sentinels for PLY_INFO.uParentMoveFHEstimate (below). A node's +// PARENT stashes, before recursing into it, its own prediction of +// "how likely is this specific move to fail high" -- which is +// identically this node's own predicted FAIL-LOW probability (for mv +// to fail high at the parent, every move here, including mv's +// continuation, must resolve to a fail-low). Two cases produce no +// real number: +// - MOVE_FH_ESTIMATE_NONLEFTOVER: mv was NOT a leftover move (hash +// move, good capture, killer, etc.) -- the dynamic-ordering/joint +// tables are only ever consulted for leftover moves +// (searchsup.c's SearchOneMove), so there is no table lookup to +// report. This is NOT "no information": trying a move specifically +// BECAUSE it's expected to be strong is itself evidence the parent +// expects a HIGHER fail-high chance than even the best leftover +// move would get, so this sentinel should bucket as a +// high-confidence band, not as "unknown". +// - MOVE_FH_ESTIMATE_NO_DATA: mv WAS a leftover move and the table +// was consulted, but the cell was below the sample floor (same +// value as searchsup.c's file-local JOINT_TABLE_SENTINEL -- kept +// numerically identical on purpose, see that macro's own comment). +// This case really is "no information". +#define MOVE_FH_ESTIMATE_NONLEFTOVER (0xFFFFFFFE) +#define MOVE_FH_ESTIMATE_NO_DATA (0xFFFF) + +// Band count for FHThousandthsToBand (searchsup.c) and +// g_NodeTypeGuessTable (data.c) -- see both for the banding scheme +// and how the table was baked. +#define NODE_TYPE_FH_BANDS (7) + +ULONG +FHThousandthsToBand(IN ULONG uFHThousandths); + +extern const NODE_TYPE g_NodeTypeGuessTable[2][NODE_TYPE_FH_BANDS][NODE_TYPE_FH_BANDS]; + // ---------------------------------------------------------------------- // // Searcher thread record @@ -1704,6 +1738,19 @@ typedef struct _PLY_INFO FLAG fInQsearch; NODE_TYPE eNodeType; // classic PVS node typing -- // see the enum's own comment + + // 2026-10-22: the PARENT's own predicted FH% for the move that + // led to this node (identically this node's predicted FAIL-LOW + // probability -- see the sentinel macros' comment above). Written + // by searchsup.c's SearchOneMove right after MakeMove succeeds, + // i.e. for THIS ply, by the ply ABOVE, before recursing in; read + // back from here once this node's own Search()/CommonSearchInit + // runs. One slot per ply, same convention as every other + // per-ply-indexed field in this struct (iExtensionAmount, etc) -- + // a child overwrites it on its own next move, so it must be read + // before this ply recurses again itself. + ULONG uParentMoveFHEstimate; + MOVE_SCORING_SCHEME eMoveScoringScheme; // which convention scored // sMoveStack[uPly] -- see // the enum's own comment @@ -3694,9 +3741,32 @@ extern BITBOARD g_KingAttacksBB[128]; // (thousandths of a percent), 0xFFFF sentinel = below this run's // 500-try sample floor, don't trust yet. [depth][dumb][gap][late], // depth outermost for cache locality -- a whole node's move loop shares -// one depth-slice (11*11*15 = 1,815 cells, ~3.5KB). Not yet consumed by +// one depth-slice (11*11*17 = 2,057 cells, ~4KB). Not yet consumed by // search.c -- phase 2 (LMR replacement) is a separate, later step. -extern USHORT g_uJointFHThousandths[12][11][11][16][11]; +// +// 2026-10-21: late axis widened 16 -> 17. Shipped late bucket 0 is now +// dedicated SOLELY to the whole-node sample (raw late == 0, "will +// something at this node fail high" -- no specific move), not shared +// with the real first-leftover-move curve the way the old 16-bucket +// shape coincidentally did. See searchsup.c's _ShipLateBucket and +// dual_table_run_nnue/README.md's "whole-node alpha sample" section. +// g_uJointFHThousandths (HCE) carries a PLACEHOLDER in that bucket, +// duplicated from g_uJointFHThousandths_NNUE's real measurement, since +// this table's own collection build predates the whole-node +// instrumentation -- see data.c's header comment on this array, and +// dual_table_run/README.md, for how/when that gets replaced with a +// real HCE-side measurement. +extern USHORT g_uJointFHThousandths[12][11][11][17][11]; + +// 2026-10-21: NNUE-eval counterpart of g_uJointFHThousandths above -- +// same shape, collected against the NNUE evaluator instead of HCE +// (see ../dual_table_run_nnue/). Both tables are compiled in; which +// one a lookup reads is chosen at runtime by +// _JointTablePredictedFHThousandths (searchsup.c) based on +// (g_fUseNnueEval && g_fNnueWeightsLoaded) -- NOT a build-time +// #ifdef, since NNUE is itself already a runtime switch in this +// binary (see eval.c, command.c's "nnue on"/"nnue off"). +extern USHORT g_uJointFHThousandths_NNUE[12][11][11][17][11]; // 2026-10-10: beta-side (static-null-move) 4D FH% table -- see data.c's // header. [depth][gapBeta][ownImprove][enemyImprove]. Cell stores the @@ -3704,6 +3774,12 @@ extern USHORT g_uJointFHThousandths[12][11][11][16][11]; // itself -- see _BetaTablePredictedFHThousandths's comment for why. extern USHORT g_uBetaFHThousandths[12][21][11][11]; +// 2026-10-21: NNUE-eval counterpart of g_uBetaFHThousandths above -- +// same shape/encoding, collected against the NNUE evaluator. Runtime- +// selected the same way as g_uJointFHThousandths_NNUE above (see +// _BetaTablePredictedFHThousandths, searchsup.c). +extern USHORT g_uBetaFHThousandths_NNUE[12][21][11][11]; + // 2026-09-15: king-ring bitboard split for _EvalKing's linear-SCORE // rewrite -- front/back mirror EmptyAttackedSquare's old color-dependent // asymmetry and eval_tune/train_king_safety_model.py's BUCKET_SLOTS @@ -4329,6 +4405,15 @@ _BetaTablePredictedFHThousandths(IN ULONG uRawDepthBucket, IN ULONG uOwnImproveBucket, IN ULONG uEnemyImproveBucket); +ULONG +_RefineNodeTypeGuess(IN SEARCHER_THREAD_CONTEXT *ctx, + IN SCORE iEval, + IN SCORE iAlpha, + IN SCORE iBeta, + IN ULONG uDepth, + IN SCORE iEnemyImprovement, + IN ULONG uRawImproveB); + FLAG WeShouldTryNullmovePruning(IN SEARCHER_THREAD_CONTEXT *ctx, IN SCORE iAlpha, @@ -5407,45 +5492,64 @@ void CleanupEGTB(void); // -// dumptree.c +// dumptree.c: TREEDUMP -- flat, line-oriented, path-keyed node/move +// trace of the search tree, meant to be diffed against an +// identically-instrumented Stockfish build on the same FEN -- see +// TREEDUMP_FORMAT.md. Build-time-only ("gmake TREEDUMP=1 ..."), +// always-on-when-built. There is no runtime toggle/filter -- keep a +// dump small by using a shallow "sd N"/"go depth N", not by +// restricting what gets written. // -#ifdef DUMP_TREE +// Replaces a 2004-era, long-unused XML tree dump written for a Windows +// GUI (typhoonui.exe) that this project no longer has; that mechanism +// (DUMP_TREE, InitializeTreeDump/CleanupTreeDump/DTEnterNode/ +// DTLeaveNode/DTTrace) has been removed outright rather than kept +// alongside this one, on the assumption that a mechanism nothing has +// exercised in two decades is broken. +// +// TDMove logs one decision about a move already made at the CURRENT +// (child) ply -- i.e. call it AFTER MakeMove and BEFORE UnmakeMove, so +// the path builder (which walks ctx->sPlyInfo[0..ctx->uPly-1].mv) picks +// up this move as the path's last element for free. TDResult logs the +// node's own final outcome and must be called at the node's OWN ply +// (e.g. search.c's "end:"), before any further move has been made. +#ifdef TREEDUMP void -InitializeTreeDump(void); +TDOpen(void); void -CleanupTreeDump(void); +TDClose(void); void -DTEnterNode(SEARCHER_THREAD_CONTEXT *ctx, - ULONG uDepth, - FLAG fIsQNode, - SCORE iAlpha, - SCORE iBeta); +TDMove(SEARCHER_THREAD_CONTEXT *ctx, + ULONG uDepth, + SCORE iAlpha, + SCORE iBeta, + CHAR *szKind, + CHAR *szAct, + double dReducedPlies, + double dExtendedPlies, + SCORE iScore, + FLAG fSkipped); void -DTLeaveNode(SEARCHER_THREAD_CONTEXT *ctx, - FLAG fQNode, - SCORE iBestScore, - MOVE mvBestMove); - -void CDECL -DTTrace(ULONG uPly, CHAR *szMessage, ...); - -#define DTTRACE(...) DTTrace(ctx->uPly, __VA_ARGS__) +TDResult(SEARCHER_THREAD_CONTEXT *ctx, + ULONG uDepth, + SCORE iAlpha, + SCORE iBeta, + SCORE iEval, + SCORE iBestScore); #else -#define DTTRACE(...) - -#define DTEnterNode(...) +#define TDOpen(...) -#define DTLeaveNode(...) +#define TDClose(...) -#define InitializeTreeDump(...) +#define TDMove(...) -#define CleanupTreeDump(...) +#define TDResult(...) #endif |
