3 Commits

Author SHA1 Message Date
Drew T 5a9f455e60 tools(phase-35): T6 part 1 — 14 macro-era tools FROZEN (tools/frozen.py: one refusal from main(), imports never exit; the plan's 7 + 7 the guard census found, family_remap's CLI among them), 4 retired to tools/sunset/ (dedup_propagate + dedup_extend -> share_body.py with add_members_surgical and the load_sig library moved verbatim; macro_draft; test_reconcile_ledger), six lane callers repointed to share_body --apply --bucket new, validate_targets imports share_body, tool_census accepts FROZEN; dictionary + sunset README rows; kit corpus 2026-09-08 20:34:41 -06:00
Drew T a13b2a5c38 carve(main): 3-way -O0 island split of 800_b for func_8002C410
func_8002C410 MATCHES 299/299 at -O0 and DIFFs 228-vs-299 at -O2 (verified
independently with match_one --o0 vs --no-auto-o0). gcc-2.7.2 has no
per-function optimize pragma, so opt level is per FILE, and the function needs
its own object. Main had no path to one: the Makefile's -O0 wildcard covered
src/ov_*/ and src/md_*/ but NOT top-level src/*.c, and o0_subsplit.py is
overlay-shaped -- it died on config/splat.main.yaml, which does not exist.

Measured the scope first (R37): the -O0 detector flags exactly TWO open main
stubs -- this one, and func_80011380, which already lives in -O0 boot.c and is
the proved floor. So this unblocks one function, not a class.

FIVE COUPLED PIECES, which is why the carve is worth recording:
  1. splat code rows: 800_b cut 3 ways -- 800_b / 800_b_o0a / 800_b_2
  2. splat .rodata: span B SPLIT, because the 3-way cut put its two jtbl owners
     in different objects -- func_8002B0B4 into 800_b, func_800335B8 into
     800_b_2 -- and one code object may contribute exactly ONE contiguous
     .rodata run. The boundary is DERIVED, not guessed: 800_b.o's compiled
     .rodata is 0xf8 bytes, so the front run ends at 0x80072E44+0xf8. The
     build's own jtbl_rodata_pads caught the missing piece.
  3. src/800_b.c split 3 ways -- 86-line prologue duplicated, 3 defs before the
     island, 97 after
  4. Makefile -O0 glob widened to top-level src/*_o0?.c
  5. ld_interleave --order: 800_b_2.o inserted after 800_b.o. Missing this
     floated the tail rodata and shifted every data symbol by exactly its size,
     +0x204, across 704 two-byte runs -- which is how it was found.

o0_subsplit.py now REFUSES main loudly instead of dying on a missing file
(R43/R61a) and names the manual procedure.

VERIFIED BYTE-NEUTRAL BEFORE ANY BANKING: main builds
143dbb89f34491258bbc27810d0a12ec8b43a8dd with the split in place and
func_8002C410 still an INCLUDE_ASM stub.
2026-09-03 22:20:57 -06:00
Drew T d2b48b7680 feat(phase-30): tools/o0_subsplit.py — the T2 carve-within-a-carve driver; +3 banked in ov_SC03_015
Promotes the proven probe (commit:1266) into a real tool, and validates it FIRST-TRY on a
fresh overlay.

tools/o0_subsplit.py <ov> --lo <vram> --hi <vram>:
  - derives the range's contents from the SOURCE ANCHORS (overlay_src_split.parse_overlay_c:
    `asm` = unmatched stub, `define`/`def`/`nonmatch` = already matched), NEVER from an asm
    scan -- a matched fn emits no .s, which is exactly the blindness that made the range look
    like a clean contiguous run (SS126 / SS124's shape);
  - computes the -O0 bound as (address range MINUS already-matched bodies) and emits ONE
    sub-region per maximal run of unmatched anchors (K matched islands => K+1 regions);
  - names each `<ov>_o0<letter>` picking free suffixes, so the widened Makefile glob selects
    them; refuses loudly if it runs out or if the range spans >1 object or is already -O0;
  - honours the one-carve-per-region law (forces a cut at every already-banked jr in the
    object) and reuses jr_isolate_all's plan/build_new_config/ascending-unique validation
    verbatim, so carve-repoint + source-repartition stay on the proven path;
  - warns (does not refuse) when a stub in an -O0 run lacks the frame-pointer prologue --
    the byte-gate is the arbiter, not the heuristic.

VALIDATION on ov_SC03_015 (untouched by the manual probe): the tool independently derived the
SAME structure found by hand on ov_SC03_014 -- 2 matched -O2 islands (func_80184440,
func_801848E4), 2 -O0 regions (8 + 7 fns), same 5 cuts. Sub-split -> BYTE-IDENTICAL. Then 3
drafts, each global DERIVED FROM THAT OVERLAY'S OWN ASM (%hi operand) rather than copied:
3/3 match_one --o0 MATCH (22 ins), 3/3 through the whole-binary gate.

BANKED this commit: func_801846E4 / func_8018473C / func_80184794 in ov_SC03_015 (6 across
the two overlays now). The other 24 stubs in the region are undrafted -- the route makes them
DRAFTABLE (they were un-bankable at any effort before); drafting them is crack-wave work.

R22 CLEAN-FLEET: extract-all 139/139 (+main); check-all 140 passed, 0 failed of 140.
cookbook SS126 (the address-range-is-not-an-optimization-region law + the probe ladder).
2026-07-31 08:41:54 -06:00