From f2900bb3784150e576bd77e200af01d73ea24bfb Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Thu, 1 Oct 2026 17:03:56 -0600 Subject: [PATCH] T3.c3: restruct S: canonical base needs no struct-map name; pinned bbt in apply; unjudged rows redraw --- decomp-architect/corpus/tools/P10/restruct.py | 39 ++++++++++++++----- phase-ends/current/logs/T3.md | 3 ++ tools/restruct.py | 39 ++++++++++++++----- 3 files changed, 61 insertions(+), 20 deletions(-) diff --git a/decomp-architect/corpus/tools/P10/restruct.py b/decomp-architect/corpus/tools/P10/restruct.py index 246903e554..06d72cc464 100644 --- a/decomp-architect/corpus/tools/P10/restruct.py +++ b/decomp-architect/corpus/tools/P10/restruct.py @@ -803,17 +803,19 @@ def s_body_edits(tu, fn, raw, masked, d, sites, bases, allow_local, local_types, for base_key, ent in sorted(bases.items(), key=lambda kv: -kv[1]["sites"]): tname = ent["type"] t = struct_map().get(tname) - if not t: + can = canon_types().get(tname) + # T3.c3: a canonical type needs only its field map; the map lookup gates the LOCAL path alone (was: checked first — the + # per-batch census drops the pinned names from struct_map.json, so t3_S6..S18 ledgered every pinned base NO-TYPE-IN-MAP) + if not t and not can: per_base.append((base_key, tname, "NO-TYPE-IN-MAP", [], {})) continue - can = canon_types().get(tname) if can and type_visible(tu, tname): fields = can[2] or {} edits, skips = s_edits(raw, masked, d, sites, tname, fields, base_key, fn_head_inner, why=why) per_base.append((base_key, tname, "CANONICAL", edits, skips)) elif can: per_base.append((base_key, tname, "TYPE-NOT-VISIBLE", [], {})) - elif allow_local: + elif allow_local and t: ctypes_by_off = collections.defaultdict(list) for s in sites: if s.get("bclass") + ":" + str(s.get("base")) == base_key and s.get("ctype"): @@ -1144,11 +1146,11 @@ _bbt_cache = None def typed_bases_of(tu, fn, bases): - """{base_key: reason} — the bases body_base_type.json types for this body that `bases` (the drawn ones) leaves out.""" + """{base_key: reason} — the bases body_base_type.json types for this body that `bases` (the drawn ones) leaves out. + T3.c3: read through bbt() — the same pinned merge plan_S draws from (was: the live file, unpinned).""" global _bbt_cache if _bbt_cache is None: - p = CENSUS / "body_base_type.json" - _bbt_cache = json.loads(p.read_text()) if p.exists() else {} + _bbt_cache = bbt() if (CENSUS / "body_base_type.json").exists() else {} out = {} for bk, ent in (_bbt_cache.get(f"{tu}|{fn}") or {}).items(): if bk not in bases: @@ -3293,9 +3295,13 @@ def matches_only(only, tu, unit, extra=()): def s_finished(tu, fns, latest): """True when every body in fns has a latest ("S", tu, fn) ledger row whose nhash_after is the body's CURRENT nhash (T3.c2: t3_S1–S5 - re-drew finished files — drawable stayed ~2088 after 250 files done).""" + re-drew finished files — drawable stayed ~2088 after 250 files done). + T3.c3: a latest row none of whose bases was judged (no CANONICAL/LOCAL status: every base NO-TYPE-IN-MAP in t3_S6..S14, S16..S18) + 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): + return False path = REPO / tu if not path.exists(): return False @@ -4763,12 +4769,25 @@ def selftest(real=False): plan_S(False, 50, None, set(), {}, cur_)[1] == 0 and plan_S(False, 50, None, set(), {}, mg_)[1] == 1) m_, defs_, _, _, _ = file_view(tu, text) nh_ = body_nhash(m_, next(x for x in defs_ if x["name"] == "fx_a")) - fin_ = ledger_latest([dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after=nh_)]) - old_ = ledger_latest([dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after=nh_), - dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after="stale")]) + b_can_ = [dict(base="param:a0", type="Unkstruct_TEST", status="CANONICAL")] + fin_ = ledger_latest([dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after=nh_, bases=b_can_)]) + old_ = ledger_latest([dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after=nh_, bases=b_can_), + dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after="stale", bases=b_can_)]) p_fin, p_old = plan_S(False, 50, None, set(), fin_, mg_), plan_S(False, 50, None, set(), old_, mg_) ok("plan_S: a file whose bodies' latest S rows are at the current nhash is not drawn; a stale latest hash draws it", p_fin[1] == 0 and p_fin[2]["file-finished"] == 1 and p_old[1] == 1, f"{p_fin[1:]} {p_old[1:]}") + 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) + # 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") + pb_ = s_body_edits(tu, "fx_a", text, m_, d_a_, [s_ for s_ in file_view(tu, text)[4] if s_.get("fn") == "fx_a"], + plan_S(False, 50, None, set(), {}, mg_)[0][0][1]["fx_a"], False, {}) + _map_cache = map_keep_ + ok("apply: the planned pinned base is CANONICAL with edits though the struct map lacks the name", + [(b_[0], b_[1], b_[2]) for b_ in pb_] == [("param:a0", "Unkstruct_TEST", "CANONICAL")] and len(pb_[0][3]) > 0, f"{[b_[:3] for b_ in pb_]}") ok("the selftest never touches the live inflight.json", True) ok("struct_layout selftest", sl.selftest() == 0) JUDGE_STUB = None diff --git a/phase-ends/current/logs/T3.md b/phase-ends/current/logs/T3.md index c66607c48a..0aba3c160c 100644 --- a/phase-ends/current/logs/T3.md +++ b/phase-ends/current/logs/T3.md @@ -13,3 +13,6 @@ - retriever-code (inline): --write-types is preview-only; canonical = struct defined in engine_types.h / struct_types.h; no-field = no leaf at exact off; head_types.py regenerates only its marked region; no tool adds fields to hand-authored structs. - cycle 2..60 (cycle_t3_b.log): t3_S2..S5 green (R22 218/218 each, last 69f9da8c0f); members 12384/7786/7878/6694; then t3_S6 `0 of 0 drawable … type-not-canonical 103469` → cycle exit 0 "nothing drawable". Drawable count never fell (2088→2089). Probe: current body_base_type.json 109,359 bases, top type Unkstruct_800D3888 (absent from canonical headers). Hypothesis: census rerun renames struct-map types as sites become members; plan reads drifting map; plan also re-draws finished files. - T3.c2 briefed: confirm, pin base→type (pre-sweep .run/P39/census/body_base_type.json), exclude finished files. +- T3.c2 done (d476f3c92e): cause confirmed (pinned 31,408/116,484 ov bases canonical vs current 0/103,469; ov_MAIN_012 func_8012E9C0 param_1 800B5CB8 → 800D3888); `--bbt`/RESTRUCT_BBT pin + s_finished; plan 1901 drawable. +- cycle 6..60 with pin (cycle_t3_c.log): t3_S6..S14, S16..S18 drew ~350 bodies each but 0 sites (written 7-9 files each); only t3_S15 real (5136 members, 37 kept, 1774 skipped). All R22 green. Stopped (TERM after INT ignored by bash during R22); `restruct --restore` dropped t3_S19 (8 files, 310 rows); HEAD 2f4594dce3. +- Hypothesis: apply path reads unpinned map; s_finished marks zero-site files done. T3.c3 briefed. diff --git a/tools/restruct.py b/tools/restruct.py index 246903e554..06d72cc464 100644 --- a/tools/restruct.py +++ b/tools/restruct.py @@ -803,17 +803,19 @@ def s_body_edits(tu, fn, raw, masked, d, sites, bases, allow_local, local_types, for base_key, ent in sorted(bases.items(), key=lambda kv: -kv[1]["sites"]): tname = ent["type"] t = struct_map().get(tname) - if not t: + can = canon_types().get(tname) + # T3.c3: a canonical type needs only its field map; the map lookup gates the LOCAL path alone (was: checked first — the + # per-batch census drops the pinned names from struct_map.json, so t3_S6..S18 ledgered every pinned base NO-TYPE-IN-MAP) + if not t and not can: per_base.append((base_key, tname, "NO-TYPE-IN-MAP", [], {})) continue - can = canon_types().get(tname) if can and type_visible(tu, tname): fields = can[2] or {} edits, skips = s_edits(raw, masked, d, sites, tname, fields, base_key, fn_head_inner, why=why) per_base.append((base_key, tname, "CANONICAL", edits, skips)) elif can: per_base.append((base_key, tname, "TYPE-NOT-VISIBLE", [], {})) - elif allow_local: + elif allow_local and t: ctypes_by_off = collections.defaultdict(list) for s in sites: if s.get("bclass") + ":" + str(s.get("base")) == base_key and s.get("ctype"): @@ -1144,11 +1146,11 @@ _bbt_cache = None def typed_bases_of(tu, fn, bases): - """{base_key: reason} — the bases body_base_type.json types for this body that `bases` (the drawn ones) leaves out.""" + """{base_key: reason} — the bases body_base_type.json types for this body that `bases` (the drawn ones) leaves out. + T3.c3: read through bbt() — the same pinned merge plan_S draws from (was: the live file, unpinned).""" global _bbt_cache if _bbt_cache is None: - p = CENSUS / "body_base_type.json" - _bbt_cache = json.loads(p.read_text()) if p.exists() else {} + _bbt_cache = bbt() if (CENSUS / "body_base_type.json").exists() else {} out = {} for bk, ent in (_bbt_cache.get(f"{tu}|{fn}") or {}).items(): if bk not in bases: @@ -3293,9 +3295,13 @@ def matches_only(only, tu, unit, extra=()): def s_finished(tu, fns, latest): """True when every body in fns has a latest ("S", tu, fn) ledger row whose nhash_after is the body's CURRENT nhash (T3.c2: t3_S1–S5 - re-drew finished files — drawable stayed ~2088 after 250 files done).""" + re-drew finished files — drawable stayed ~2088 after 250 files done). + T3.c3: a latest row none of whose bases was judged (no CANONICAL/LOCAL status: every base NO-TYPE-IN-MAP in t3_S6..S14, S16..S18) + 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): + return False path = REPO / tu if not path.exists(): return False @@ -4763,12 +4769,25 @@ def selftest(real=False): plan_S(False, 50, None, set(), {}, cur_)[1] == 0 and plan_S(False, 50, None, set(), {}, mg_)[1] == 1) m_, defs_, _, _, _ = file_view(tu, text) nh_ = body_nhash(m_, next(x for x in defs_ if x["name"] == "fx_a")) - fin_ = ledger_latest([dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after=nh_)]) - old_ = ledger_latest([dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after=nh_), - dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after="stale")]) + b_can_ = [dict(base="param:a0", type="Unkstruct_TEST", status="CANONICAL")] + fin_ = ledger_latest([dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after=nh_, bases=b_can_)]) + old_ = ledger_latest([dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after=nh_, bases=b_can_), + dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after="stale", bases=b_can_)]) p_fin, p_old = plan_S(False, 50, None, set(), fin_, mg_), plan_S(False, 50, None, set(), old_, mg_) ok("plan_S: a file whose bodies' latest S rows are at the current nhash is not drawn; a stale latest hash draws it", p_fin[1] == 0 and p_fin[2]["file-finished"] == 1 and p_old[1] == 1, f"{p_fin[1:]} {p_old[1:]}") + 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) + # 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") + pb_ = s_body_edits(tu, "fx_a", text, m_, d_a_, [s_ for s_ in file_view(tu, text)[4] if s_.get("fn") == "fx_a"], + plan_S(False, 50, None, set(), {}, mg_)[0][0][1]["fx_a"], False, {}) + _map_cache = map_keep_ + ok("apply: the planned pinned base is CANONICAL with edits though the struct map lacks the name", + [(b_[0], b_[1], b_[2]) for b_ in pb_] == [("param:a0", "Unkstruct_TEST", "CANONICAL")] and len(pb_[0][3]) > 0, f"{[b_[:3] for b_ in pb_]}") ok("the selftest never touches the live inflight.json", True) ok("struct_layout selftest", sl.selftest() == 0) JUDGE_STUB = None