mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-10-02 16:00:27 -04:00
fa2ccea3ea3a2ebd9dbf4e8efbe40d38f52fbbbc
15 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
186a8b1548 |
fix(match_one): model the reorder island, not maspsx, for its four TUs
REORDER_TUS := 800c2 800c2_2 800c2_3 800c3 are piped through reorder_passthrough.py into as -O2 by the Makefile — the mode that fills delay slots and emits the jr/addiu epilogue. That island landed 2026-09-01 and banked 20 functions. match_one, the oracle every drafting agent scores against, still compiled those TUs through maspsx + as -O1, so it reported a phantom LENGTH-DRIFT in the epilogue and an extra instruction. Measured on one plain-C draft of func_8005ECC0: maspsx + as -O1 closeness 5, 36 ins vs 35 'the §188 wall' reorder + as -O2 closeness 2, 35 ins vs 35 epilogue identical Cost, in the S76w wave alone: seven of eleven main agents produced correct C, saw the phantom tail, correctly identified the §182/§188 shape, consulted oracle_reorder.py — which told them 'file IMMOVABLE, no C-level work can ever close it' — and each submitted a §265 verbatim-asm body instead. They all reasoned correctly from a false premise the knowledge base gave them. The TU list is DERIVED from the Makefile, never a second copy (R51 — a derived property stored as config goes stale, which is this defect exactly). oracle_reorder.py's docstring is corrected and the cookbook carries the §182/§188 correction with the byte evidence. |
||
|
|
c4380c19e4 |
feat(p31 s67): stranded-draft census + honest jtbl probe — the frontier's biggest class is carve plumbing
MEASURED (denominators in .run/S67_findings.md): * 193 of the 530 open functions ALREADY have a draft on disk (1,885 wave targets seen, 1,521 banked, 171 open-no-draft, 166 never drawn). Classified in their real TUs: 37 MATCH / 67 NEAR / 89 CC1-FAIL. * 159 open functions (30% of the frontier) reference a jump table; 96 are PLAN-REFUSED by build_carve (non-contiguous same-subseg .rodata), 75 non-main across 38 subsegs. Not a codegen wall and not a decl wall — carve plumbing. NEW * tools/strand_census.py — coverage-asserted census + rtu_match classifier + draft staging. Keys binary:fn (R48); classifies each pair once after merging every manifest's view. * tools/o0_detect.py — the -O0 prologue tell extracted from match_one (which parses argv at import and therefore cannot be imported). match_one re-exports it; ONE definition (R33). Wiring it into the classifier turned md_MAIN_003 from 8 NEAR (7 of them >20) into 6 MATCH. Negative-controlled both directions. * tools/scope_demote_drafts.py — §8d as an _xform-contract gate rung. NOT yet exercised. FIXED * jtbl_carve --probe now runs build_carve (a pure planner) and reports plan-refused. It previously called only island_probe, which answers a necessary-not-sufficient question — every blocked function probed "carveable", and S66 priced 32 of them as free on that. * blocker_probe.macro_scope selects the LAST #define per macro name, matching cpp. engine_core.h has 1,037 duplicate DEFINE_func_ names and 4 with DIFFERENT bodies. NOT VALIDATED — DO NOT SCALE * jr_isolate_all: two real defects fixed (carried types deduped by name; header-provided types no longer re-emitted) but ov_SC02_000 STILL fails the byte gate after them. Open lead: file_scope_types carries a block without its enclosing #if guard. 20 of 35 blocked overlays dry-run clean and that number means nothing until one round-trips. 0 functions banked this session. tools-health has ONE pre-existing cdecl defect (1 of 74,749 declarations, func_8017EE08_p55352/struct ZnRec) — cdecl.py and its inputs are byte-identical to HEAD, so it is not from this change. Knowledge banked: cookbook §322/§323/§323a/§323b, decision-log pivot, accelerators #13/#14. |
||
|
|
5c0b2969d7 |
tools(P31 S66): match_one --json answers on toolchain failure; packs render it; wave_args + draw_waves
- match_one.py: a CPP/CC1/MASPSX/AS failure printed bare text and exited 1 even under --json, so every programmatic caller got json.loads of a non-JSON line. claude_wave_packs swallowed 4 of 19 prior drafts as 'residual not measured: Expecting value' — the most actionable datum a pack can carry (the draft does not COMPILE, here is the error) was the one it dropped. Negative control: a near draft still measures identically (closeness 2, same residual rows); human mode unchanged. - claude_wave_packs.py: renders that verdict, pointing the agent at the card's decl_prior block. Coverage on wave r1 went 15/19 -> 19/19 packs carrying a measured verdict. - wave_args.py (new): emits the claude_wave_draft.js args from <wave>/targets.json, asserting the .s exists, that sub is exactly its parent dir, and that the pack exists. Written because I hand-typed sub as 'ov_SC03_112/jr_80181D08' when the truth is 'asm/ov_SC03_112/nonmatchings/ov_SC03_112_jr_801817E0' (a stub's asm dir is named for its jr-carve block, not itself) — all 19 agent oracles would have failed identically and read as a model failure. Negative control: that exact string is REFUSED. - draw_waves.py (new): draws waves off corpus.stubs cheapest-first, ledger-filtered, with name-collision deferral (packs are name-keyed and refuse a duplicate). Re-measured the frontier: the S65 tier map's '~557 cheap singletons (3-17 ins)' conflated one-member FAMILIES with small functions — only 28 undrawn non-main stubs are <=17 ins; the bulk is 51-120 (258) and >120 (245). |
||
|
|
3a4de071d9 |
fix(match_one): warn when --asm-subdir is defaulted — the default targets resident
A distill reviewer reported "match_one resolves targets by bare symbol name, not (binary, address)" from 6+ observed target-confusion instances. VERIFIED AND THE CLAIM DOES NOT HOLD as stated: match_one resolves '%s/%s.s' % (asm_subdir, fn) — an explicit path — and the drafting path is safe because api_draft.match_one() always passes dirname(card['asm']). The 675 "match_one MATCH but the whole-binary gate rejected" rows today keep their real explanation: they landed in the window when config/overlays.mk was empty and NOTHING could build. The narrower hazard behind the report is real. --asm-subdir defaults to asm/resident/nonmatchings/resident, function names are ADDRESS-DERIVED, and overlays share the address space — so the same name is routinely a DIFFERENT function in another binary (§238 homonym trap). Any caller that omits the flag gets a confident verdict about the wrong target, and the failure is silent because the file exists. It now warns loudly on stderr when the default is used, naming the fn and the directory, and stays silent when the flag is passed (controlled both ways). A warning rather than a refusal: resident-era callers legitimately rely on the default, and R43's "refuse what you cannot handle" does not apply to a tool that CAN handle the input — it applies to one that cannot tell whether the input is what the caller meant. |
||
|
|
05a4aeaf41 |
feat(o0): md_MAIN_011 compiles -O0 — 21 functions / 4,321 ins unlocked, byte-proven
Its entire code subseg is the -O0 run and the .c is stub-only, so this is the boot
precedent: a whole-object CC1FLAGS override, no splat change, no carve, and none of
the 18-P29 re-disassembly risk. Proven byte-neutral by a CLEAN per-binary rebuild —
build dirs deleted, re-extract, rebuild:
sha1 80731bac0ddd6b3e354f43b2c179582b12590752 == config/check.md_MAIN_011.sha
Landed with the coupling fix it requires, or the 21 would have stayed invisible.
Three tools decided -O0-ness from the subseg NAME ('_o0' in it, or 'boot'); this
object keeps its plain name, so match_one would have warned 'cannot bank' about
functions that now bank and the wave draw would have kept refusing to draw them. All
three now ask corpus.o0_subseg(), which derives the answer from the Makefile itself
(R33: a name is a convention, the Makefile is ground truth).
Verified end to end: match_one compiles md_MAIN_011 targets at -O0 with the
cannot-bank warning correctly gone, the wave draw emits cards for them, and
test_o0_detect still passes 167/167 coverage with 0 false positives.
|
||
|
|
f574f3c80e |
feat(match_one): derive the opt level from the target — the -O0 oracle was never wired up
--o0 has existed since P29 and NOTHING ever passed it: api_draft.match_one(), the oracle every wave agent iterates against, builds a fixed argv without it. Every agent handed an -O0 target was shown an -O2 compile of its own C and a mismatch on every instruction — feedback that cannot converge, for a reason invisible in the diff, and it hit even the 51 functions already sitting in -O0 objects. match_one now decides from the target (R33), with two oracles because neither alone is sufficient (R34): the -O0 frame-pointer prologue in the target's own bytes, OR the subseg being an object the Makefile compiles -O0. boot/start.s is built -O0 with no ordinary prologue; an -O0 function stranded in an -O2 subseg has the prologue but cannot bank — and match_one now says so instead of letting an agent chase it. The prologue scan is anchored at glabel, not the top of the file: two md_MAIN_011 .s files open with a migrated jump table / .asciz blob and a naive scan read table words as the prologue. tools/test_o0_detect.py: 14,400 .s scanned, 167/167 signature files covered, 0 false positives outside -O0 objects, both rodata-first files detected. Census correction in the design doc: the handoff's refutation of md_MAIN_003/011 is itself wrong (16 and 21 -O0 functions, byte-verified), and the 311 '$fp mentions' figure over-counts because $fp is $s8, allocatable at -O2. True population: 167 files, of which 116 / 14,148 ins are -O0 functions the build compiles -O2 — double the atlas estimate, with x2/x3/x4 sibling replication. |
||
|
|
594a87e173 |
feat(phase-31): reloc_identity.py — the oracle that disagrees with match_one about SYMBOL IDENTITY (S52)
match_one masks relocations (26-bit jal field, HI16/LO16 immediates), so it verifies instruction SHAPE and is structurally blind to WHICH symbol each relocation names: a draft calling the wrong function or touching the wrong global reports a clean MATCH (§174 law 1c; wave K burned 5 gate attempts on two swapped globals). Until now only the whole-binary gate caught it, and it reports a hash, not a cause. But the target .s comment column carries the FINAL LINKED WORD, so the true address behind every masked field is recoverable arithmetically, and config/symbols*.txt maps it back to a name. This tool resolves what the DRAFT names, computes what the TARGET references, and compares -- /bin/bash, no rebuild, and it names the fix instead of reporting a mismatch. match_one --emit-streams additionally carries mine_relocs (kind/operand per index); existing consumers read mine/tgt only. THREE INSTRUMENT BUGS FOUND BY ITS OWN NEGATIVE CONTROLS, each fixed before any verdict was believed (R35/R39): - splat-derived func_/D_/jtbl_ names are not in the symbol FILES (their address IS their name), so the first run checked ZERO relocations on a draft whose every callee was correct -- a checker that looks clean while checking nothing (R32). - the nearest-symbol fallback used a 0x4000 window and labelled func_8001C9D0 as 'SsGetMute+0xC50'; a wrong label is worse than none. Tightened to 0x200 with an explicit splat-derived fallback. - MIPS o32 uses REL relocations: THE ADDEND IS IN THE INSTRUCTION, not the reloc entry. Reading it off the operand string reported 0 for every struct-field/array access and fabricated mismatches (the +1/+2/+3 signature on func_801F0734 was a byte-array walk, not three symbol errors). Also refuses to answer confidently when the streams are not index-aligned (shape differs), since a single inserted instruction shifts every later index -- my own NC produced exactly that phantom. NC: known-good drafts AGREE (3 and 5 relocs actually checked); a synthetic callee 4 bytes off is caught while match_one still says MATCH; a misaligned mutant is downgraded to advisory. |
||
|
|
0840eda5bb |
feat(phase-31 T8): LEN+N lane — 587 near-misses routed; 345 wrong-drafts reclassified; detectors live
- match_one --emit-streams (additive; stdout-identity NC'd): word streams for the len lane - family_align.addr_true_rel: reloc-vs-constant range discriminator — FULL conservative set kept for pair semantics (NC-1 157/157 regression), address- true subset for indel eligibility only (a constant li-cluster must not read as reloc-in-indel); synthetic probes green both directions - tools/len_tells.py: aligned classification + §172b tell tagging (EXTPAIR/ SELECT/NOP) on target-side indels; detectors imported from atlas_features (R33); cookbook text embedded in cards - tools/lenmiss_route.py: pool-parallel (A8) — 587 audit LEN rows re-verified live + routed in 24s: redraft 345 (frac>0.35, APPEND-ONLY backlog reclassification — near-miss metrics stop lying) / permuter-length 49 (grinder fuel) / cards 192 incl 14 tell-tagged (the audit's own detectors had emitted ZERO) / mechanical 0 — an HONEST NULL: stored drafts rarely get constants wrong; LEN drift is shape, family_align's value here is classifier/detector - R32 accounting 587/587 |
||
|
|
b3ab5c2253 |
feat(phase-29 Task-13A): deterministic residual classifier — the permuter's problem is TARGETING
The autopsy (hindsight-study §7) assumed the permuter loses for want of a mutation. Measured over the whole open backlog, it loses because it is aimed at work a search-closer provably cannot close. - NEW tools/residual_class.py: decide a near-miss's class FROM THE BYTES. Decodes each mismatching MIPS word -> (op-skeleton, register-fields, immediate); drift FIRST (one inserted insn inflates `closeness` by the tail length), then consistent-injective register map -> REGALLOC-PERM (§31 S11/RC-3), same-multiset-reorder -> SCHEDULE-REORDER, DELAY-SLOT, WIDTH/BRANCH-POLARITY/STRENGTH/ADDRESSING/IMM-OFFSET/IMM-VALUE. Every class routes to a BUCKET = which tool the failure wants. Uncovered opcode -> UNKNOWN, COUNTED (R32). 16 synthetic unit tests (test_residual_class.py). - NEW tools/autopsy.py: `collect` materialises the corpus Task-12's telemetry never filled (1 of 6,169 records had a residual) by recompiling every open draft through the EXISTING match_one path (R33) — 1,752 drafts in 21s at -j12. `report` -> docs/autopsy.md. - NEW corpus.o0_sources()/is_o0(): the opt-level oracle DERIVED from the Makefile's own -O0 rules, coverage-asserted. Scoring an -O0 target at -O2 makes the residual 100% artefact (the trap this phase hit four times). - R34 cross-check baked in: residual_class's closeness vs masked_diff.structured_diff's, asserted per row; 1,673/1,673 agree, 0 classifier errors. FINDING: of the 972 records the grinder's own filter admits, only 75 (7.7%) are permuter-shaped; 547 are structural and 348 are drafts that are not the function at all. ~92% of the daemon's CPU went where it could not win — the byte-grounded explanation of "7 banks all-time, all Phase 21, 0 since" (Phase-22 audit). grinder.candidates() now filters on the measured bucket (1,303 -> 78) and takes its directed profile from the measured class, not the logged label (91% carry none -> it ran on gcc defaults). Degrades to undirected if uncollected and says so; --no-targeting A/Bs it. Two measured corollaries (R14, not projections): - 699 records rank as near-misses at closeness up to 278 purely from a length artefact: un-attempted work misfiled as a backlog of hard functions -> new `redraft` bucket. - a 12-draft gate probe of the `integration` bucket banked 1/12 (11 PLUMBING), so the 306 prices Task 14's reconcile ladder rather than promising free banks. func_80167714 (104 ins, reach-134) banked x1, un-propagated by design (§55b). Two defects fixed forward: - masked_diff._common_typedefs() used ONE shared probe path, so parallel match_one processes clobbered each other: 14 of 1,752 drafts lost in a single 12-way run (0.8%), silently, in every parallel wave ever run. Now per-PID. - gate_stage.match_one_closeness never passed --o0 -> phantom residuals for every -O0 function, written straight into the backlog this autopsy reads. R22 clean-fleet: check-all 140 passed, 0 failed of 140; tools-health OK (dedup 1847/0, C1 234343/234343); 0 NON_MATCHING (G4). Flywheel captured in-session (R30/R31): cookbook §60, decision-log entry, SETUP.md inventory. |
||
|
|
eec96f76a4 |
feat(phase-29 Task-12): structured failure telemetry + durable permuter-winner save
The permuter-autopsy prerequisite (Drew-directed). Captures the WHAT/WHERE of every
non-match so the classifier/autopsy (Task-13) can mine it, not just a scalar closeness:
- masked_diff.structured_diff(): the per-instruction masked residual [(idx,mine,tgt),...]
- match_one --json: emits {status,closeness,nins,residual} (refactored to share the primitive)
- backlog FIELDS += residual, passes_tried
- gate_stage.match_one_closeness returns + logs the residual on every near/fail (verified
end-to-end: a near-miss's structured residual now lands in backlog.jsonl)
- grinder: durably save the winning C to .run/permuter-winners/ BEFORE gate_stage banks
(this session's lesson — 3 permuter wins were lost to a mid-flight revert)
|
||
|
|
876dc7f053 |
feat(phase-28 T5): resident 21 -> 14 stubs (7 banked, 90.34%) + fix match_one's fake isolation
Ultracode wave: 16 isolated drafters over the resident's non-jtbl stubs (the 5 jtbl deferred —
they need the rodata-island carve, §53). Drafts only; the whole-binary byte-gate arbitrated after.
- BANKED 7/16, byte-gated: func_800CEFD0(77) func_800D0D7C(45) func_800D1B80(22) func_800D1E28(37)
func_800D1FC8(62) func_800D29F8(172) func_800D2D10(39).
Resident REAL 122 -> 129, stubs 21 -> 14, byte-ident 124/145 (85.52%) -> 131/145 (90.34%).
FLEET instr 9017152 -> 9017606 (+454 ins). R22 make clean && extract-all && check-all ->
140 passed, 0 failed of 140 (the first R22 was killed by a terminal crash and RE-RUN, not assumed).
Ground truth on 14 agrees 3 ways: source grep, splat-emitted stub .s count, progress.py.
- §52b's LAW, MEASURED AGAIN INDEPENDENTLY: the agents self-reported 11 match_one MATCH; the
whole-binary gate banked 7 (64%). All 4 blocked MATCHes died on `conflicting types`
(D_8010EDEC / D_80115110 / func_800D1984 / cdFileLocTable) — the loose-typing def-side wall, NOT
codegen. gate_stage's recovery banked 0/5 on them. A match_one MATCH is a CANDIDATE (G3/P9).
- FIX — match_one's isolation was FAKE, and its own docstring was the false spec. It promises
"Fully isolated (own temp dir) so many run in PARALLEL with no shared build -- a real asm-differ
loop for an agent to iterate against", while `--work` defaulted to the SHARED '.run/match': every
concurrent caller compiled into the same t.c/t.o. FOUND BY AN AGENT MID-WAVE, the only way it can
be found — it read another agent's function out of its own scratch ("found another agent's
func_800D2650 in my t.c") and reported it. Every other agent steered by a loop that could hand it
someone else's compile: a CONFIDENT WRONG verdict, worse than a crash. Default is now a private
.run/match/<fn>.<pid>; the default IS the promise. (Some agents had already worked around it by
passing --work themselves.) The byte-gate was never at risk — it is the sole arbiter — but the
iteration loop the agents steer by absolutely was.
- The 14 remaining: 5 PLUMBING (loose-typing) + 4 DIFF (genuine codegen: func_800D2650 close=4,
func_800CFAD0 close=5, func_800D0E30 close=12, func_800D27DC close=48) + 5 jtbl deferred.
Dossier next (T5b) — the agents' per-function residual analyses are the durable asset (R30).
|
||
|
|
e0a0becfaa |
feat(phase-27 T4): one cdecl typedef-strip primitive (was six regexes) + surface cc1 stderr
The plan named two defective regexes; the tree had SIX with complementary holes, each silently recording the resulting compile failure as "not a match" — a plumbing error wearing a compiler wall's clothes, the exact class the 26-A audit exists to end (R32). - cdecl.py: the canonical primitive — typedef_names(tu_path) + strip_provided_typedefs (draft, provided). Built on tu_statements (robust) NOT tu_scope (which coverage-asserts -> would crash the byte-gate on any unrelated unparseable file-scope statement). Splits multi-typedef lines (split_statements, depth-aware); covers scalar AND struct typedefs; keeps draft-local types. lru_cached. - harvest_verify.py: strips PER-TU (cdecl.typedef_names of the draft's real target TU) -> unblocks the 39 struct-typedef drafts the scalar-only _TD dropped. And SURFACES cc1 stderr: build() stashes it; a single-draft failure is classified DIFF / PLUMBING:… / CC1-FAIL / SKIP -> .run/harvest_failed.classified.txt. A `redefinition` is no longer recorded byte-identically to a codegen miss. - masked_diff.py: strip_scalar_typedefs() (common.h set derived from the header once, R33, cached) replaces SCALAR_TYPEDEF_RE.sub for the ISOLATED compile; wired into match_one + p16_permute. Fixes the multi-typedef-LINE skip that discarded 42 masked-MATCH drafts over whitespace. Unblocks B4's func_8015C32C (redefinition of 's16'). - canon_sig_reconcile / eval_lora / format_finetune keep their own copies — migrate per-bank, byte-gated (the audit-prescribed cadence, not a big-bang swap). VERIFIED: - HEADLINE known-answer: func_8015C030 -> MATCH (23 ins) UNEDITED via match_one (was CC1-FAIL; the multi-line typedef split alone fixes it — a live x134-family draft that was being discarded over whitespace). - unit: 7/7 scalars stripped; a local struct KEPT; a TU-provided Blk16 stripped. - classifier unit: DIFF / PLUMBING:… / CC1-FAIL / SKIP all label correctly. - all 5 edited tools import + AST-parse clean. - R22 clean-fleet: check-all 136/136; main clean-rebuild 143dbb89. (A mid-test c4546248 "mismatch" was a stale-incremental artifact from concurrent compiles, cleared by a clean rebuild — the R22 lesson; edits touch only tools/, src/ stayed git-clean.) - SAFETY: a strip bug can only fail-to-bank, never falsely bank (INCLUDE_ASM pastes the original asm; a wrong draft always changes bytes -> always fails SHA1). |
||
|
|
d572cc4d2b |
chore(phase-25): T5b batch-3 (_o0 measured) + T6 Fable5 brief + match_one --o0 (fleet 72.29%, handoff)
- batch-3: the 3 _o0 giants 3/3 isolation-MATCH at -O0 (new tools/match_one.py --o0 flag); 0 banked — an -O0 in-context byte-diff (canon_sig_reconcile's void->s32 is NOT byte-neutral at -O0) -> deferred to T7. Frontier 125/127 draftable exemplars measured. - R14 correction: the mechanical reconcile-sweep is NOT the clean 51-target x134 win first hoped. The frontier "match" status carried un-verified agent claims (5-sample spot-check = 3/5 genuine); ~19/51 have clean engine_core.h canonicals (the reliable canon_sig_reconcile tier), the rest hit VARIED walls (callee-sig conflicts, non-identical types Vec3/SVEC, macro-local data) -> this is the genuine Fable5/T6 residual, not a mechanical sweep. - docs/phase25-t6-fable-brief.md: the grounded Step-B input package for the fresh Fable5Max session to author the crack curriculum (95-stub worklist by wall-class + tools + 5 swing questions). CURRENT_PHASE: T6 hand-off (fresh session, Fable5Max, read the brief). Phase OPEN. |
||
|
|
5984749421 |
feat(phase-24): T2 — floor-free relocation-masked permuter scorer (-drz)
- tools/masked_diff.py: shared objdump -drz masking oracle (jal/j 26-bit + HI16/LO16 immediate mask; -z keeps nop runs -> no GTE under-count). object-vs-object (permuter, +reloc-operand equality) and object-vs-.s (match_one) modes. - match_one.py refactored onto masked_diff (-dr -> -drz); regression-clean on count-exact seeds (35/52/72/110, leaf-MATCHes), output format unchanged (gate_stage/grinder compatible) - tools/masked_scorer.py MaskedScorer: drop-in for decomp-permuter's Scorer; scores masked .text closeness (bottoms out at 0) not the stock mnemonic-diff floor - tools/permuter/run_masked.py: rebinds src.main.Scorer -> MaskedScorer (NO submodule edit, R3/R20); p16_permute.run_permuter wired to it - VALIDATED: masked_self=0; masked_cand~match_one; STOCK floor 1750-2100 vs masked 36-77 (the wander cause); live permuter base score = masked 77 (not stock 1930), descends to 74 - R14 SELF-CORRECTION: -drz confirms func_80132784 is 4/400 count-exact (T1's -dr 204 was a 16-nop-collapse artifact); backlog re-logged close 4. The flagship IS 4 ins away. - no build-input changed (136/136 untouched); 2 compile FAILs surfaced -> T3 (GTE asm, typedefs) |
||
|
|
4c7cf48ebf |
feat(phase-12): T3 — +2 (typedef-strip recovery) + per-function match tool (REAL 87->89)
- tools/match_one.py (new): compile ONE function's C standalone (pinned triple), mask relocations (jal/HI16/LO16 like psyq_identify), compare to the target bytes in its .s. Isolated temp dir -> parallelizable; a real per-function asm-differ loop for agents to iterate against without a shared build. Validated: a matched fn -> MATCH, residuals -> informative diffs. - harvest_verify.py + match_one.py: strip inline scalar-typedef redefinitions (some drafts inlined `typedef unsigned char u8;` -> C89 redefinition error vs common.h = a COMPILE fail, not a byte mismatch). Re-gate recovered func_800CFBCC + func_800D1938. - REAL 89 (62.76% byte-identical); resident 8e17e02f, main 143dbb89 (R22 clean). - 54 stubs remain (scheduling/regalloc residuals + big GPU/dispatch fns) -> match_one-driven iteration pass next. |