From 8a0ff32fde558a4bed11464974e449811d06f6c2 Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Thu, 1 Oct 2026 17:21:02 -0600 Subject: [PATCH] T3.c3: restruct S: unjudged S rows are not done (ledger_index); selftest fixtures carry bases --- decomp-architect/corpus/tools/P10/restruct.py | 15 +++++++--- phase-ends/current/logs/T3.c3.md | 29 +++++++++++++++++++ tools/restruct.py | 15 +++++++--- 3 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 phase-ends/current/logs/T3.c3.md diff --git a/decomp-architect/corpus/tools/P10/restruct.py b/decomp-architect/corpus/tools/P10/restruct.py index 06d72cc464..37970c7332 100644 --- a/decomp-architect/corpus/tools/P10/restruct.py +++ b/decomp-architect/corpus/tools/P10/restruct.py @@ -413,11 +413,17 @@ def ledger_index(rows): """done: {(rung, tu, unit, nhash_after)}; latest: {(rung, tu, unit): verdict}.""" done = set() for r in rows: - if r.get("verdict") in DONE_VERDICTS and r.get("nhash_after"): + if r.get("verdict") in DONE_VERDICTS and r.get("nhash_after") and (r.get("rung") != "S" or s_judged(r)): done.add((r.get("rung"), r["tu"], r["unit"], r["nhash_after"])) return done, {k: r.get("verdict") for k, r in ledger_latest(rows).items()} +def s_judged(r): + """A rung-S row judged at least one base (CANONICAL/LOCAL). T3.c3: t3_S6..S14, S16..S18 ledgered every pinned base NO-TYPE-IN-MAP + (no compile): those rows are not judgements — neither done (ledger_index) nor finished (s_finished).""" + return any(b.get("status") in ("CANONICAL", "LOCAL") for b in r.get("bases") or []) + + def body_nhash(masked, d): ls = dl.line_starts(masked) s0 = ls[d["line"] - 1] @@ -3300,7 +3306,7 @@ def s_finished(tu, fns, latest): is not a judgement — the body stays drawable.""" if not all((latest.get(("S", tu, fn)) or {}).get("nhash_after") for fn in fns): return False - if not all(any(b.get("status") in ("CANONICAL", "LOCAL") for b in latest[("S", tu, fn)].get("bases") or []) for fn in fns): + if not all(s_judged(latest[("S", tu, fn)]) for fn in fns): return False path = REPO / tu if not path.exists(): @@ -4701,7 +4707,7 @@ def selftest(real=False): r3 = recipe_walked_pointer(text, tu, "fx_a", fake3) ok("R3 walks the three carved halves through one pointer (reloc flagged)", r3 and "s16 *p37w = &D_801F8870;" in r3[0][0] and "p37w[1] = p37w[0] + p37w[2];" in r3[0][0] and r3[0][2] is True, (r3[0][0][r3[0][0].find("p37w"):][:80] if r3 else "none")) # ---- ledger + inflight - rows = [dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after="h1", label="st"), dict(rung="S", tu=tu, unit="fx_a", verdict="COMBINATION-FAILED", nhash_after=None, label="st2")] + rows = [dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after="h1", label="st", bases=[dict(status="CANONICAL")]), dict(rung="S", tu=tu, unit="fx_a", verdict="COMBINATION-FAILED", nhash_after=None, label="st2")] done, latest = ledger_index(rows) ok("ledger: done keyed per unit+hash, latest verdict wins", ("S", tu, "fx_a", "h1") in done and latest[("S", tu, "fx_a")] == "COMBINATION-FAILED") # the in-flight snapshot's parser and restore core, on a SCRATCH path — never the real inflight.json (the first selftest run @@ -4779,7 +4785,8 @@ def selftest(real=False): unj_ = ledger_latest([dict(rung="S", tu=tu, unit="fx_a", verdict="NO-SITE", nhash_after=nh_, bases=[dict(base="param:a0", type="Unkstruct_TEST", status="NO-TYPE-IN-MAP")])]) ok("plan_S (T3.c3): a current-hash row with no judged base (every base NO-TYPE-IN-MAP) leaves the file drawable", - plan_S(False, 50, None, set(), unj_, mg_)[1] == 1) + plan_S(False, 50, None, set(), unj_, mg_)[1] == 1 and ledger_index(list(unj_.values()))[0] == set() + and ledger_index(list(fin_.values()))[0] == {("S", tu, "fx_a", nh_)}) # T3.c3: plan and apply agree on a pinned base even when the (drifted) struct map lacks its name map_keep_, _map_cache = _map_cache, {} d_a_ = next(x for x in defs_ if x["name"] == "fx_a") diff --git a/phase-ends/current/logs/T3.c3.md b/phase-ends/current/logs/T3.c3.md new file mode 100644 index 0000000000..0773fac145 --- /dev/null +++ b/phase-ends/current/logs/T3.c3.md @@ -0,0 +1,29 @@ +# T3.c3 — rung S apply: pinned base reaches apply; zero-site batches drawable again + +## Cause (evidence) +- Ledger t3_S6 (scratch `.run/T3c3/s6.py`): 340 rows / 50 files; verdicts NO-SITE 332, KEPT 8; every base status `NO-TYPE-IN-MAP` (397: Unkstruct_80126B58 255, Unkstruct_800B5CB8 142); sites RESIDUAL(NO-TYPE-IN-MAP) 2063, RESIDUAL(TYPE-NOT-CANONICAL) 728, KEPT(MISALIGNED) 16, KEPT(NON-STRUCT) 4. Example `src/ov_MAIN_012/ov_MAIN_012_jr_801549F8.c` func_80155580: base param:a0 Unkstruct_80126B58 NO-TYPE-IN-MAP, 0 edits. +- t3_S15 same type: status CANONICAL 1809 → 5136 members. +- `.run/P37/census/struct_map.json` (rewritten by the cycle's per-batch census) lacks both names; `.run/P39/census/struct_map.json` (the pin's) has them. +- So the brief's hypothesis was half right: plan_S's pinned bases DID reach work_file_S (bodies = plan payload), but `s_body_edits` checked `struct_map().get(tname)` FIRST and returned NO-TYPE-IN-MAP before the canonical branch, though the canonical path only needs `canon_types()` field maps. `typed_bases_of` read the live unpinned bbt (only the residual reason label; now pinned too). +- The 7-9 "written files" per batch = the KEPT rows: kept-class sites (MISALIGNED / NON-STRUCT) respelled to `CAST_*` macros by s_site_rows on the no-edit path (t3_S6: 8 files, 20 respells, e.g. ov_MAIN_012_jr_8013F350.c). No members. + +## Change (tools/restruct.py) +- `s_body_edits`: NO-TYPE-IN-MAP only when the name is neither in the map nor canonical; the LOCAL branch requires the map entry (`allow_local and t`). +- `typed_bases_of`: cache = `bbt()` (same pinned merge as plan_S). +- New `s_judged(row)`: a rung-S row with ≥1 base CANONICAL/LOCAL. `s_finished` and `ledger_index` (done set) ignore unjudged S rows. Choice over `--redraw` labels: a row with no judged base is a tool non-judgement by definition, independent of labels; ledger scan shows such rows exist only in t3_S6..S14, S16..S18 (+13 in t3_S15 on all-NO-TYPE-IN-MAP bodies) — `.run/T3c3/unj.py`. The done-set part was found by the first probe (UNCHANGED, rows 0: `done` skipped the body at its current nhash). +- Selftest: fixture S rows carry `bases` CANONICAL; new checks: unjudged row keeps the file drawable and out of `done`; s_body_edits on the planned pinned base with an empty struct map → CANONICAL with edits. + +## Commands +- `restruct.py --selftest` → 90/90 OK (both edits). +- `make kit-corpus` + `make tools-health` → OK (first edit); rerun after the ledger_index edit (see VERIFIED in return). +- commit 568de6c713 (first edit: s_body_edits, typed_bases_of, s_finished). +- `delever_oracle --snapshot-baseline` → 7428 objects at 568de6c71; `--calibrate ov_SC04_011 ov_SC03_015 md_SC07_004 main -j 16` → OK. +- `restruct --plan --rung S --bbt .run/P39/census/body_base_type.json --only "ov_*"` → 50 of 1872 drawable files · 340 bodies · 397 typed bases (= t3_S6's set); skipped file-finished 216. +- Probe `--apply --rung S --bbt … --only src/ov_MAIN_012/ov_MAIN_012_jr_801549F8.c --batch 1 --label t3_c3probe -j 12` → IDENTICAL, members 1, written 1 (`D_801801F0[((Unkstruct_80126B58 *)a0)->unk2]()`). +- `make check BINARY=ov_MAIN_012` → BYTE-IDENTICAL. +- `type_census --check-casts --quiet` → unbacked 96, NOT 0 — pre-existing: none on the probe file (0 CAST_ there); unbacked sites sit on rows of t3_S1..S10 (t3_S1 MEMBERS/KEPT NON-STRUCT 32, t3_S2 MEMBERS SIGN 11, …; top file ov_SC04_011_jr_8017D494.c 19) — `.run/T3c3/unb.py`. Earlier logs (t2c3_casts) read unbacked 0: a T3-sweep regression for the expert. + +## For the expert +- Commits after 568de6c713 make the calibration stale again: snapshot + calibrate before the next apply/cycle. +- The struct_map leverage weights in plan_S still use the drifted live map (weight 0 for pinned names → alphabetical order); harmless for correctness. +- Resuming the cycle redraws t3_S6.. files first (1872 drawable). diff --git a/tools/restruct.py b/tools/restruct.py index 06d72cc464..37970c7332 100644 --- a/tools/restruct.py +++ b/tools/restruct.py @@ -413,11 +413,17 @@ def ledger_index(rows): """done: {(rung, tu, unit, nhash_after)}; latest: {(rung, tu, unit): verdict}.""" done = set() for r in rows: - if r.get("verdict") in DONE_VERDICTS and r.get("nhash_after"): + if r.get("verdict") in DONE_VERDICTS and r.get("nhash_after") and (r.get("rung") != "S" or s_judged(r)): done.add((r.get("rung"), r["tu"], r["unit"], r["nhash_after"])) return done, {k: r.get("verdict") for k, r in ledger_latest(rows).items()} +def s_judged(r): + """A rung-S row judged at least one base (CANONICAL/LOCAL). T3.c3: t3_S6..S14, S16..S18 ledgered every pinned base NO-TYPE-IN-MAP + (no compile): those rows are not judgements — neither done (ledger_index) nor finished (s_finished).""" + return any(b.get("status") in ("CANONICAL", "LOCAL") for b in r.get("bases") or []) + + def body_nhash(masked, d): ls = dl.line_starts(masked) s0 = ls[d["line"] - 1] @@ -3300,7 +3306,7 @@ def s_finished(tu, fns, latest): is not a judgement — the body stays drawable.""" if not all((latest.get(("S", tu, fn)) or {}).get("nhash_after") for fn in fns): return False - if not all(any(b.get("status") in ("CANONICAL", "LOCAL") for b in latest[("S", tu, fn)].get("bases") or []) for fn in fns): + if not all(s_judged(latest[("S", tu, fn)]) for fn in fns): return False path = REPO / tu if not path.exists(): @@ -4701,7 +4707,7 @@ def selftest(real=False): r3 = recipe_walked_pointer(text, tu, "fx_a", fake3) ok("R3 walks the three carved halves through one pointer (reloc flagged)", r3 and "s16 *p37w = &D_801F8870;" in r3[0][0] and "p37w[1] = p37w[0] + p37w[2];" in r3[0][0] and r3[0][2] is True, (r3[0][0][r3[0][0].find("p37w"):][:80] if r3 else "none")) # ---- ledger + inflight - rows = [dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after="h1", label="st"), dict(rung="S", tu=tu, unit="fx_a", verdict="COMBINATION-FAILED", nhash_after=None, label="st2")] + rows = [dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after="h1", label="st", bases=[dict(status="CANONICAL")]), dict(rung="S", tu=tu, unit="fx_a", verdict="COMBINATION-FAILED", nhash_after=None, label="st2")] done, latest = ledger_index(rows) ok("ledger: done keyed per unit+hash, latest verdict wins", ("S", tu, "fx_a", "h1") in done and latest[("S", tu, "fx_a")] == "COMBINATION-FAILED") # the in-flight snapshot's parser and restore core, on a SCRATCH path — never the real inflight.json (the first selftest run @@ -4779,7 +4785,8 @@ def selftest(real=False): unj_ = ledger_latest([dict(rung="S", tu=tu, unit="fx_a", verdict="NO-SITE", nhash_after=nh_, bases=[dict(base="param:a0", type="Unkstruct_TEST", status="NO-TYPE-IN-MAP")])]) ok("plan_S (T3.c3): a current-hash row with no judged base (every base NO-TYPE-IN-MAP) leaves the file drawable", - plan_S(False, 50, None, set(), unj_, mg_)[1] == 1) + plan_S(False, 50, None, set(), unj_, mg_)[1] == 1 and ledger_index(list(unj_.values()))[0] == set() + and ledger_index(list(fin_.values()))[0] == {("S", tu, "fx_a", nh_)}) # T3.c3: plan and apply agree on a pinned base even when the (drifted) struct map lacks its name map_keep_, _map_cache = _map_cache, {} d_a_ = next(x for x in defs_ if x["name"] == "fx_a")