mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-28 14:59:48 -04:00
46097c2339
THE GAP: a drafting agent is briefed to STOP at a plateaued permuter-class residual — right, since an agent grinding a register permutation burns tokens for nothing — so every SCHEDULE-REORDER/DELAY-SLOT/REGALLOC-PERM residual lands unattempted while the local permuter costs no tokens. In S77 the hand-off happened only when I remembered. THE CORRECTION THIS TOOL FORCED. §479 v2 claimed the predictor of a permuter win was 'prior-attempt history: all 3 winners were drafts nobody had worked'. Building the selector on that claim refuted it immediately: journal_notes reports prior attempts for ALL EIGHT known runs, winners included (2, 3, 3). What I had eyeballed was the DRAFT HEADER narrative, a different corpus — the winners came from a recovery pile whose files carry no header journal. That is provenance, not evidence. So the tool selects on the two NECESSARY conditions only (small residual, a match_one class the permuter can search), prints prior-attempt counts as information, and puts the unvalidated filter behind --skip-ground, off by default so it cannot silently discard good work (R39). AND A BUG IN THE NEW TOOL, caught by cross-checking against known-true numbers: wave_results globbed journals across EVERY session and did last-write-wins on a bare function name, so an older wave's row won and carried its stale draft_path — the sweep reported func_8002AC98 at closeness 73 and func_80015608 at 65 while both drafts measure 1 and 3. R48 inside a brand-new tool. Journals are now read newest-last and rows are kept only when the draft lives under this wave's directory. After the fix all seven cross-checkable residuals agree with what the agents independently reported (9, 8, 7, 3, 3, 1, 1). §479 now states the honest position: ~3 in 8 at <=4, no validated predictor, and a note that a yield table is evidence while a story about why is a hypothesis needing its own negative control before it goes in the cookbook.
176 lines
8.7 KiB
Python
176 lines
8.7 KiB
Python
#!/usr/bin/env python3
|
|
"""permuter_sweep.py — hand a wave's NEARs to the permuter, but ONLY the ones worth the CPU. (P31 S77)
|
|
|
|
THE GAP. A drafting agent's brief tells it to STOP at a plateaued permuter-class residual rather
|
|
than grind one — correct, because an agent grinding a register permutation burns tokens for nothing.
|
|
The consequence is that every `SCHEDULE-REORDER` / `DELAY-SLOT` / `REGALLOC-PERM` residual lands
|
|
un-attempted, and the local permuter costs no tokens at all. In S77 that hand-off happened only
|
|
when I remembered to do it.
|
|
|
|
WHAT IS ACTUALLY MEASURED, AND WHAT IS NOT. §479 went through TWO corrections in one session and
|
|
this tool lives on the far side of both. v1 said "≤4 mismatched is a permuter one-shot" on 3/3.
|
|
Five more runs made it **3 of 8** — a residual of 1 FAILED while a residual of 4 BANKED, so **the
|
|
mismatch count predicts nothing**:
|
|
|
|
banked : func_80021174 (2) · func_80040DE8 (2) · func_80024054 (4)
|
|
failed : func_8005F290 (1) · func_80015608 (3) · func_80039DEC (3)
|
|
func_8005F0C8 (3) · func_8005ECC0 (2)
|
|
|
|
v2 then claimed the predictor was "prior-attempt history". **Building this tool refuted that too.**
|
|
`journal_notes` — the same index the packs use — reports prior attempts for ALL EIGHT, winners
|
|
included (2, 3, 3 for the three that banked). The signal actually eyeballed was the DRAFT HEADER
|
|
narrative; the two are different corpora, and the winners simply came from a recovery pile whose
|
|
files carry no header journal. That is provenance, not evidence.
|
|
|
|
So: **there is no validated predictor.** The honest selector is the two NECESSARY conditions — a
|
|
small residual, and a class the permuter can actually search — with an expected yield around 3 in 8.
|
|
Prior-attempt counts are PRINTED as information, never enforced. `--skip-ground` exists as a
|
|
labelled experiment; promote it only if someone measures it discriminating over several waves.
|
|
(R14/R35: this paragraph exists because a negative control was run against a claim already written
|
|
into the cookbook.)
|
|
|
|
tools/permuter_sweep.py --wave .run/S77w --dry # what it would run, and why not
|
|
tools/permuter_sweep.py --wave .run/S77w --apply
|
|
"""
|
|
import argparse
|
|
import glob
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
REPO = os.path.dirname(HERE)
|
|
sys.path.insert(0, HERE)
|
|
PY = os.path.join(REPO, ".venv/bin/python")
|
|
if not os.path.exists(PY):
|
|
PY = sys.executable
|
|
|
|
# match_one buckets the permuter can actually search. A STRUCTURAL or LENGTH-DRIFT residual is a
|
|
# different animal and a permuter run on one is measured waste.
|
|
PERMUTER_CLASSES = ("SCHEDULE-REORDER", "DELAY-SLOT", "REGALLOC-PERM", "SCHEDULE", "REGALLOC")
|
|
|
|
|
|
def wave_results(wave):
|
|
"""{fn: result} from the workflow journals for this wave's targets."""
|
|
tg = {t["name"]: t for t in json.load(open(os.path.join(REPO, wave, "targets.json")))}
|
|
base = os.path.expanduser(
|
|
"~/.claude/projects/-home-musashi-bfm-decomp/*/subagents/workflows/*/journal.jsonl")
|
|
# SCOPE THE JOURNALS TO *THIS* WAVE (P31 S77). The glob spans every session's workflows, and a
|
|
# bare `out[fn] = x` is last-write-wins over an UNORDERED glob — so an older wave's row for the
|
|
# same function silently replaced this one, carrying its stale `draft_path`. Measured: the sweep
|
|
# reported func_8002AC98 at closeness 73 and func_80015608 at 65, while both drafts in THIS
|
|
# wave measure 1 and 3. That is R48 (never key by bare function name) inside a brand-new tool.
|
|
# Two guards: read journals NEWEST LAST so recency wins, and keep only rows whose draft is
|
|
# actually under this wave's directory.
|
|
wave_abs = os.path.abspath(os.path.join(REPO, wave))
|
|
out = {}
|
|
for j in sorted(glob.glob(base), key=lambda f: os.path.getmtime(f)):
|
|
for line in open(j, errors="replace"):
|
|
try:
|
|
r = json.loads(line)
|
|
except Exception:
|
|
continue
|
|
if r.get("type") != "result":
|
|
continue
|
|
v = r.get("value") or r.get("result") or {}
|
|
for x in (v if isinstance(v, list) else [v]):
|
|
if not (isinstance(x, dict) and x.get("fn") in tg):
|
|
continue
|
|
dp = x.get("draft_path") or ""
|
|
if dp and not os.path.abspath(dp).startswith(wave_abs):
|
|
continue # a different wave's row for the same name
|
|
out[x["fn"]] = x
|
|
return tg, out
|
|
|
|
|
|
def has_prior_attempts(binary, fn):
|
|
"""How many prior-attempt notes the journal index holds for this function.
|
|
|
|
INFORMATIONAL ONLY. It reads as a predictor and is not one: measured over the 8 known permuter
|
|
runs it reports history for all 8, winners included. Printed, never enforced (see the module
|
|
docstring)."""
|
|
try:
|
|
import journal_notes as jn
|
|
rows = jn.notes_for(jn.load(), binary, fn)
|
|
return len(rows or [])
|
|
except Exception:
|
|
return 0
|
|
|
|
|
|
def residual_class(fn, draft, asm_subdir):
|
|
"""(closeness, class) from match_one — the same oracle the wave used."""
|
|
r = subprocess.run([PY, os.path.join(REPO, "tools/match_one.py"), fn, "--c", draft,
|
|
"--asm-subdir", asm_subdir], cwd=REPO, capture_output=True, text=True)
|
|
txt = (r.stdout or "") + (r.stderr or "")
|
|
m = re.search(r"(\d+) mismatched", txt)
|
|
k = re.search(r"class:\s*(\S+)", txt)
|
|
if re.search(r"^MATCH\b", txt, re.M):
|
|
return 0, "MATCH"
|
|
return (int(m.group(1)) if m else None), (k.group(1) if k else "?")
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument("--wave", required=True)
|
|
ap.add_argument("--max-closeness", type=int, default=4,
|
|
help="cheap cap, NOT the predictor (see the docstring's 3-of-8 table)")
|
|
ap.add_argument("--cycles", type=int, default=1)
|
|
ap.add_argument("--secs", type=int, default=150)
|
|
ap.add_argument("--jobs", type=int, default=8)
|
|
ap.add_argument("--skip-ground", action="store_true",
|
|
help="UNVALIDATED EXPERIMENT: skip drafts with prior-attempt history. Measured "
|
|
"NOT to discriminate (all 8 known runs have history, winners included), so "
|
|
"it is OFF by default and cannot silently discard good work (R39)")
|
|
ap.add_argument("--apply", action="store_true")
|
|
ap.add_argument("--dry", action="store_true", help="explicit no-op alias; dry is the default")
|
|
a = ap.parse_args()
|
|
|
|
tg, res = wave_results(a.wave)
|
|
picked, skipped = [], []
|
|
for fn, r in sorted(res.items()):
|
|
if r.get("status") != "NEAR":
|
|
continue
|
|
t = tg[fn]
|
|
draft = r.get("draft_path") or os.path.join(REPO, a.wave, t.get("arm", ""), fn + ".c")
|
|
if not os.path.exists(draft):
|
|
skipped.append((fn, "no draft on disk")); continue
|
|
prior = has_prior_attempts(t.get("binary", "main"), fn)
|
|
close, klass = residual_class(fn, draft, t["sub"])
|
|
if close is None:
|
|
skipped.append((fn, "no residual from match_one")); continue
|
|
if close > a.max_closeness:
|
|
skipped.append((fn, "closeness %d > cap %d" % (close, a.max_closeness))); continue
|
|
if not any(k in klass for k in PERMUTER_CLASSES):
|
|
skipped.append((fn, "class %s is not a permuter bucket" % klass)); continue
|
|
if prior and a.skip_ground:
|
|
skipped.append((fn, "prior history %d (--skip-ground, UNVALIDATED)" % prior))
|
|
continue
|
|
picked.append({"fn": fn, "draft": draft, "sub": t["sub"], "close": close, "klass": klass,
|
|
"prior": prior})
|
|
|
|
print("permuter_sweep: %d NEAR -> %d picked, %d skipped"
|
|
% (sum(1 for r in res.values() if r.get("status") == "NEAR"), len(picked), len(skipped)))
|
|
for s in skipped:
|
|
print(" skip %-16s %s" % s)
|
|
for p in picked:
|
|
print(" RUN %-16s closeness %-3d %-18s prior=%d" % (p["fn"], p["close"], p["klass"], p["prior"]))
|
|
if not a.apply:
|
|
print("\nDRY RUN. Re-run with --apply. A winner is a CANDIDATE — gate it whole-binary before banking.")
|
|
return 0
|
|
|
|
for p in picked:
|
|
print("\n=== %s" % p["fn"], flush=True)
|
|
subprocess.run([PY, os.path.join(REPO, "tools/permuter_ils.py"), p["fn"],
|
|
"--draft", p["draft"], "--asm-subdir", p["sub"], "--klass", "SCHEDULE",
|
|
"--cycles", str(a.cycles), "--secs", str(a.secs), "--j", str(a.jobs)],
|
|
cwd=REPO)
|
|
print("\nWinners land in .run/permuter-winners/. They are CANDIDATES: gate whole-binary (G3/P9).")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|