summaryrefslogtreecommitdiff
path: root/src/eval_tune/dna_diff.py
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-15 13:31:36 -0700
committerScott Gasch <[email protected]>2026-09-15 13:31:36 -0700
commit390e675849ff42c8b7e1806f75552de988741fff (patch)
tree17b8792aeaf74373bbaef3bac6b71bb5c0d74c69 /src/eval_tune/dna_diff.py
parentca73728f7dbf01c4b39c7496729f81b1fbffa00b (diff)
Rewrite _EvalKing as a linear ring-scan and promote its terms to DNA-tunable constants; retune via mate-mining regression, ElasticNet/Ridge, and logistic-vs-material refits.
Splits _EvalKing's own king-location table from CountKingSafetyDefects' search-gate copy so future eval retunes can't silently perturb extension/reduction gate frequency. Bumps KING_BACK_FLIGHT_BONUS (+1 -> +3) and reorders/nudges the ring-scan sub-terms (minor/pawn/xray/undefended) to match relative rankings that replicated across three independent calibration methods, while leaving KING_QUEEN_PROXIMITY_DANGER and KING_MISSING_ONE_CASTLE_OPTION untouched pending further discussion. Self-play (640 games) is roughly even (score 0.509, llr=+0.17, far from either SPRT bound) but consistently faster (nps candidate=1362140 vs head=1333682) at equal average depth -- landing as a wash/speed-neutral checkpoint, not a confirmed tactical improvement, for further tuning to build on. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/eval_tune/dna_diff.py')
-rwxr-xr-xsrc/eval_tune/dna_diff.py94
1 files changed, 69 insertions, 25 deletions
diff --git a/src/eval_tune/dna_diff.py b/src/eval_tune/dna_diff.py
index ecdd6bb..1b44280 100755
--- a/src/eval_tune/dna_diff.py
+++ b/src/eval_tune/dna_diff.py
@@ -1,45 +1,89 @@
#!/usr/bin/env python3
"""Human-readable diff between a baseline DNA dump and a tuned .dna
-file: names each of g_EvalDNA's 56 arrays (order taken directly from
-eval.c's g_EvalDNA[] table) and, for the 128-cell board-shaped
-location tables, renders an actual 8x8 grid diff instead of a wall of
-raw numbers."""
+file: names each of g_EvalDNA's arrays and, for the 128-cell board-
+shaped location tables, renders an actual 8x8 grid diff instead of a
+wall of raw numbers.
+
+2026-09-15: DNA_NAMES used to be a hand-maintained list "exact order
+from eval.c:604-659" -- found to have drifted badly (wrong from index
+0: eval.c's g_EvalDNA[] no longer even contains TRADE_PIECES/
+DONT_TRADE_PAWNS, the list's first two entries, and roughly a dozen
+other names had also renamed/moved/vanished over past sessions without
+this list being updated). Every consumer of this list (this script,
+dna_trend.py, tune_eval_dna.py's --freeze) had been silently mislabeling
+or misdirecting against the wrong array for a while -- plausibly the
+real explanation for at least some past "Texel tuning produced
+nonsensical results" reports, since a diff/freeze targeting the wrong
+array by name looks exactly like that from the output alone.
+
+Fixed by deriving this list from eval.c directly at import time instead
+of hand-maintaining a parallel copy that can silently drift again. Only
+falls back to the hardcoded snapshot below (kept only as a last resort
+for running this script somewhere eval.c isn't reachable) with a loud
+warning -- never silently.
+"""
+import re
import sys
+from pathlib import Path
-# Exact order from eval.c:604-659 (g_EvalDNA[] initializer).
-DNA_NAMES = [
- "TRADE_PIECES", "DONT_TRADE_PAWNS", "REDUCED_MATERIAL_DOWN_SCALER",
+# Last-resort fallback only -- see module docstring. Regenerate by
+# running _derive_dna_names() with eval.c present and pasting the result
+# here if this ever needs to be updated by hand again.
+_FALLBACK_DNA_NAMES = [
+ "REDUCED_MATERIAL_DOWN_SCALER", "CASTLE_AND_DEVELOPMENT_SCALER",
"REDUCED_MATERIAL_UP_SCALER", "PASSER_MATERIAL_UP_SCALER",
"PAWN_CENTRALITY_BONUS", "BACKWARD_SHIELDED_BY_LOCATION",
"BACKWARD_EXPOSED_BY_LOCATION", "DOUBLED_PAWN_PENALTY_BY_COUNT",
- "ISOLATED_PAWN_PENALTY_BY_COUNT", "ISOLATED_PAWN_BY_PAWNFILE",
- "ISOLATED_EXPOSED_PAWN", "ISOLATED_DOUBLED_PAWN", "PASSER_BY_RANK",
- "CANDIDATE_PASSER_BY_RANK", "CONNECTED_PASSERS_BY_RANK",
- "SUPPORTED_PASSER_BY_RANK", "OUTSIDE_PASSER_BY_DISTANCE",
+ "ISOLATED_PAWN_BY_PAWNFILE", "ISOLATED_EXPOSED_PAWN",
+ "ISOLATED_DOUBLED_PAWN", "PASSER_BY_RANK", "CANDIDATE_PASSER_BY_RANK",
+ "CONNECTED_PASSERS_BY_RANK", "SUPPORTED_PASSER_BY_RANK",
+ "KING_SUPPORTING_OWN_PASSER_BY_RANK", "OUTSIDE_PASSER_BY_DISTANCE",
"PASSER_BONUS_AS_MATERIAL_COMES_OFF", "RACER_WINS_RACE",
"UNDEVELOPED_MINORS_IN_OPENING", "BISHOP_OVER_KNIGHT_IN_ENDGAME",
- "BISHOP_PAIR", "STATIONARY_PAWN_ON_BISHOP_COLOR",
+ "BISHOP_PAIR_BONUS", "STATIONARY_PAWN_ON_BISHOP_COLOR",
"TRANSIENT_PAWN_ON_BISHOP_COLOR", "BISHOP_MOBILITY_BY_SQUARES",
- "BISHOP_MAX_MOBILITY_IN_A_ROW_BONUS",
- "BISHOP_UNASSAILABLE_BY_DIST_FROM_EKING", "BISHOP_IN_CLOSED_POSITION",
- "KNIGHT_CENTRALITY_BONUS", "KNIGHT_KING_TROPISM_BONUS",
- "KNIGHT_UNASSAILABLE_BY_DIST_FROM_EKING",
+ "BISHOP_UNASSAILABLE_BY_DIST_FROM_EKING", "KNIGHT_CENTRALITY_BONUS",
+ "KNIGHT_KING_TROPISM_BONUS", "KNIGHT_UNASSAILABLE_BY_DIST_FROM_EKING",
"KNIGHT_ON_INTERESTING_SQUARE_BY_RANK", "KNIGHT_MOBILITY_BY_COUNT",
"KNIGHT_WITH_N_PAWNS_SUPPORTING", "KNIGHT_IN_CLOSED_POSITION",
- "ROOK_ON_FULL_OPEN_BY_DIST_FROM_EKING",
- "ROOK_ON_HALF_OPEN_WITH_ENEMY_BY_DIST_FROM_EKING",
- "ROOK_ON_HALF_OPEN_WITH_FRIEND_BY_DIST_FROM_EKING",
- "ROOK_BEHIND_PASSER_BY_PASSER_RANK", "ROOK_LEADS_PASSER_BY_PASSER_RANK",
- "KING_TRAPPING_ROOK", "ROOK_TRAPPING_EKING",
+ "ROOK_ON_FULL_OPEN", "ROOK_ON_HALF_OPEN_WITH_ENEMY",
+ "ROOK_ON_HALF_OPEN_WITH_FRIEND", "ROOK_WITH_ENEMY_PASSER",
+ "ROOK_WITH_FRIEND_PASSER", "ROOK_BEHIND_PASSER_BY_PASSER_RANK",
+ "ROOK_LEADS_PASSER_BY_PASSER_RANK", "KING_TRAPPING_ROOK",
"ROOK_VALUE_AS_PAWNS_COME_OFF", "ROOK_CONNECTED_VERT",
"ROOK_CONNECTED_HORIZ", "ROOK_MOBILITY_BY_SQUARES",
- "ROOK_MAX_MOBILITY_IN_A_ROW_BONUS", "QUEEN_MOBILITY_BY_SQUARES",
- "QUEEN_OUT_EARLY", "QUEEN_KING_TROPISM",
- "QUEEN_ATTACKS_SQ_NEXT_TO_KING", "KING_INITIAL_COUNTER_BY_LOCATION",
+ "QUEEN_MOBILITY_BY_SQUARES", "QUEEN_KING_TROPISM",
+ "KING_INITIAL_COUNTER_BY_LOCATION", "KING_EVAL_INITIAL_LOCATION",
"KING_TO_CENTER", "KING_SAFETY_BY_COUNTER",
- "KING_MISSING_ONE_CASTLE_OPTION",
+ "KING_QUEEN_PROXIMITY_DANGER", "KING_MISSING_ONE_CASTLE_OPTION",
+ "KING_BACK_FLIGHT_BONUS", "KING_RING_ATTACK_MINOR",
+ "KING_RING_ATTACK_ROOK", "KING_RING_ATTACK_PAWN",
+ "KING_RING_UNDEFENDED", "KING_RING_XRAY_BONUS",
+ "KING_RING_FRONT_FLIGHT_PENALTY", "KING_INITIAL_LOCATION_SCALE",
+ "KING_ATTACK_PATTERN_SCALE", "KING_DANGER_ACCELERATOR",
]
+
+def _derive_dna_names(eval_c_path="../eval.c"):
+ """Parse g_EvalDNA[]'s initializer directly out of eval.c, in
+ order -- the only way this list can't silently drift out of sync
+ with the real array again."""
+ text = Path(eval_c_path).read_text()
+ m = re.search(r'static DNA_BASE_SIZE g_EvalDNA\[\] = \{(.*?)\n\};',
+ text, re.DOTALL)
+ if not m:
+ return None
+ return re.findall(r'DNA_(?:ARRAY|MATRIX|VAR)\(([A-Za-z0-9_]+)\)', m.group(1))
+
+
+DNA_NAMES = _derive_dna_names()
+if DNA_NAMES is None:
+ print("WARNING: could not find/parse g_EvalDNA[] in ../eval.c -- "
+ "falling back to a hardcoded DNA_NAMES snapshot that WILL "
+ "silently drift out of date. Run this from eval_tune/ with "
+ "the real eval.c reachable if at all possible.", file=sys.stderr)
+ DNA_NAMES = _FALLBACK_DNA_NAMES
+
# Arrays laid out as a 128-cell "0x88-style" board: 8 files + 8 padding
# zeros per rank, 8 ranks (see eval.c's literal formatting -- each row
# of the C initializer is one rank, padded to 16 slots). Rendered