#!/usr/bin/env python3 """Turn a match_controls.py-matched king-safety CSV (mate/control pairs, fen retained -- see extract_king_features.py's 2026-09-15 fen-retention fix) into a Texel-ready (fen, result) pool: runs the same qsearch- agreement quiet filter used for the generic TWIC pool (this data was NEVER filtered for quietness at mining time by design -- see mine_king_ mates.py's is_quiet_position, added 2026-09-15 -- so this step matters here at least as much as it does for TWIC, arguably more, since "N plies before a real mate" specifically targets the moment before a real attack lands, exactly where a quiet-by-SEE-but-not-by-search position is most likely). Result-label framing (a real modeling choice, not a literal game result): label==1 rows are "this color's king got mated in this game from here" -- treated as a loss (0.0) for that color. label==0 rows are the matched sound/control side -- treated as a win (1.0) for that color. Output is always from WHITE's POV (matching eval_white_pov_batch's own convention), so a row is flipped if mated_color_or_sampled_color is black. This is an approximation (these aren't actual final game results, just "survived this specific king-safety episode or not") -- flagged explicitly rather than silently treated as equivalent to a real Texel outcome label. """ import argparse import csv import sys from tune_eval_dna import Engine, filter_by_qsearch_agreement def main(): ap = argparse.ArgumentParser() ap.add_argument("matched_csv") ap.add_argument("out_tsv") ap.add_argument("--engine", default="../typhoon") ap.add_argument("--qsearch-max-diff", type=int, default=30) ap.add_argument("--dna-scratch-dir", default="/tmp/typhoon_curated_pool") args = ap.parse_args() with open(args.matched_csv, newline="") as f: rows = list(csv.DictReader(f)) print(f"Loaded {len(rows)} matched rows from {args.matched_csv}", file=sys.stderr) fen_to_row = {} for r in rows: fen_to_row[r["fen"]] = r if len(fen_to_row) != len(rows): print(f"WARNING: {len(rows) - len(fen_to_row)} duplicate FENs " f"collapsed -- matched.csv shouldn't have dupes normally, " f"investigate if this is large.", file=sys.stderr) # (fen, result) pairs for the quiet-filter -- result value here is a # placeholder (unused by filter_by_qsearch_agreement, which only # reads/returns fens), real result labels are attached after. placeholder_positions = [(fen, 0.0) for fen in fen_to_row] with Engine(args.engine, args.dna_scratch_dir) as engine: kept = filter_by_qsearch_agreement( engine, placeholder_positions, max_diff_cp=args.qsearch_max_diff ) n_flipped = 0 out_rows = [] for fen, _ in kept: r = fen_to_row[fen] label = int(r["label"]) color = r["mated_color_or_sampled_color"] # label==1 (mate) -> loss (0.0) for that color; label==0 # (control/sound) -> win (1.0) for that color. result_for_color = 0.0 if label == 1 else 1.0 if color == "black": result_white_pov = 1.0 - result_for_color n_flipped += 1 else: result_white_pov = result_for_color out_rows.append((fen, result_white_pov)) with open(args.out_tsv, "w") as f: for fen, result in out_rows: f.write(f"{fen}\t{result}\n") print(f"Quiet-filter kept {len(kept)}/{len(fen_to_row)} " f"({len(kept)/len(fen_to_row):.1%})", file=sys.stderr) print(f"Wrote {len(out_rows)} (fen, result) rows to {args.out_tsv} " f"({n_flipped} POV-flipped from black)", file=sys.stderr) if __name__ == "__main__": main()