mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
feat(phase-30 S38/S3): the whale banks in 3 of the 4 SC07 overlays — 137/138, not 138/138
func_80144B9C (770 ins) was matched in 134/138 overlays and open in the four SC07s — 3,080
instructions of code BYTE-IDENTICAL (h_exact, reloc payloads included) to what was already banked,
blocked by a missing file boundary. Now banked in ov_SC07_006 / _007 / _011 for ~0 agent tokens.
R22 clean-fleet 140/140.
THE PLAN'S FRAMING WAS WRONG. This was recorded as "the SC07 carve defect (T2 Arm-A %lo +0x20)".
The carve was never broken: o0_subsplit reported `split byte-neutral` on the FIRST attempt in all
four. The real blocker is that carving the whale out of a jr file makes jr_isolate_all hoist the
parent's file-scope decls into the new region as its `ambient` set — so for the first time the
fleet's loose-typed spellings share a TU with the shared header's (`extern void *D_801274CC` vs
`extern s32 D_801274CC`). In the 134 working overlays the whale sits in a CLEAN -O0 file (common.h +
the header, nothing else) and the two never meet. Fix: drop, in that one file only, the ambient
decls the header already declares — the header being the byte-proven side.
Three iterations, each exposing the next layer of the ambient set, every one a DECLARATION:
1. data symbols (D_80126B58, D_801274CC, D_801274D0)
2. function symbols (func_801336E8, func_8005C324)
3. the alias form terminated by a trailing COMMENT, which an endswith(';') test skipped —
the §134 comment-blindness shape for the THIRD time today.
ov_SC07_010 REVERTED and left open (hence 137/138). Its split landed in an EXISTING _o0c file
rather than a fresh _o0d, producing region _jr_801457A4 whose asm dir splat never generated. It
passed its per-binary build and FAILED the clean-tree R22 — the first R22 failure of the session,
and precisely why a per-binary pass is not a fleet byte claim (§61). Committing on that per-binary
"BANKED" would have shipped a broken overlay.
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python3
|
||||
"""S3 — unblock the whale (func_80144B9C) in the 4 SC07 overlays.
|
||||
|
||||
The whale is matched in 134/138 overlays via the §38 shared-header route: a per-overlay `-O0` split
|
||||
file (`<ov>_o0b.c`) whose entire content is `#include "../shared/func_80144B9C.h"`. gcc-2.7.2 has no
|
||||
per-function optimize pragma, so the opt level is per FILE (§116) — the whale MUST live in its own
|
||||
`-O0` object or it recompiles at -O2 and stops matching.
|
||||
|
||||
The 4 SC07 overlays never got that region: they have `_o0c` but no `_o0b`, and the whale sits in
|
||||
`<ov>_jr_80140608.c` (an -O2 jr split). So this is not a codegen problem at all — it is 3,080
|
||||
instructions of code that is BYTE-IDENTICAL to what is already banked elsewhere (h_exact, relocation
|
||||
payloads included), blocked purely by a missing file boundary.
|
||||
|
||||
Per overlay: carve the range into its own -O0 region (o0_subsplit, which reuses jr_isolate_all's
|
||||
proven plan/config/validation path), re-extract, prove the split alone is BYTE-NEUTRAL, then swap
|
||||
the whale's INCLUDE_ASM for the shared header and prove it BANKS. Revert that overlay on any failure.
|
||||
The whole-binary byte-gate is the sole arbiter (G3/P9); a full R22 follows because config changed.
|
||||
|
||||
Usage: s3_sc07_whale.py [--dry]
|
||||
"""
|
||||
import sys, os, re, subprocess, json
|
||||
|
||||
LO, HI = 0x80144B9C, 0x801457A4
|
||||
OVS = ['ov_SC07_006', 'ov_SC07_007', 'ov_SC07_010', 'ov_SC07_011']
|
||||
PY = '.venv/bin/python'
|
||||
DRY = '--dry' in sys.argv
|
||||
sys.path.insert(0, 'tools')
|
||||
|
||||
|
||||
def sh(*cmd):
|
||||
return subprocess.run(list(cmd), capture_output=True, text=True)
|
||||
|
||||
|
||||
def good_sha(ov):
|
||||
return open(f'config/check.{ov}.sha').read().split()[0]
|
||||
|
||||
|
||||
def build_ok(ov):
|
||||
r = sh('make', '--no-print-directory', 'build', f'BINARY={ov}')
|
||||
return r.returncode == 0 and '[ OK ]' in (r.stdout or ''), (r.stdout or '') + (r.stderr or '')
|
||||
|
||||
|
||||
def revert(ov):
|
||||
sh('git', 'checkout', '--', f'src/{ov}', 'config')
|
||||
sh('git', 'clean', '-fdq', f'src/{ov}')
|
||||
sh('make', '--no-print-directory', 'extract', f'BINARY={ov}')
|
||||
|
||||
|
||||
results = {}
|
||||
for ov in OVS:
|
||||
print(f"\n===== {ov} =====", flush=True)
|
||||
import importlib, corpus
|
||||
for f in (corpus.stubs, corpus.sig, corpus.symbols, corpus.src_files):
|
||||
if hasattr(f, 'cache_clear'):
|
||||
f.cache_clear()
|
||||
if LO not in corpus.stubs(ov):
|
||||
print(" whale is not a live stub here — skipping"); results[ov] = 'not-stub'; continue
|
||||
|
||||
r = sh(PY, 'tools/o0_subsplit.py', ov, '--lo', hex(LO), '--hi', hex(HI),
|
||||
*(['--dry-run'] if DRY else []))
|
||||
out = (r.stdout or '') + (r.stderr or '')
|
||||
print(' [subsplit]', out.strip().splitlines()[-1][:150] if out.strip() else f'rc={r.returncode}')
|
||||
if r.returncode:
|
||||
results[ov] = 'subsplit-fail'; continue
|
||||
if DRY:
|
||||
results[ov] = 'dry'; continue
|
||||
|
||||
if sh('make', '--no-print-directory', 'extract', f'BINARY={ov}').returncode:
|
||||
print(" extract FAILED"); revert(ov); results[ov] = 'extract-fail'; continue
|
||||
|
||||
# The split alone must be BYTE-NEUTRAL — it only moves code between objects.
|
||||
ok, log = build_ok(ov)
|
||||
if not ok:
|
||||
hard = [l for l in log.splitlines() if 'undefined reference' in l or 'error' in l.lower()][:2]
|
||||
print(" split is NOT byte-neutral — reverting:", *hard)
|
||||
revert(ov); results[ov] = 'split-not-neutral'; continue
|
||||
print(" split byte-neutral ✓")
|
||||
|
||||
# Find the new -O0 region file holding the whale and swap the stub for the shared header.
|
||||
for f in (corpus.stubs, corpus.sig, corpus.symbols, corpus.src_files):
|
||||
if hasattr(f, 'cache_clear'):
|
||||
f.cache_clear()
|
||||
rec = corpus.stubs(ov).get(LO)
|
||||
if rec is None:
|
||||
print(" whale vanished from the corpus after the split"); revert(ov); results[ov] = 'lost'; continue
|
||||
tu = rec.path
|
||||
txt = open(tu).read()
|
||||
stub = f'INCLUDE_ASM("{rec.asm_dir}", {rec.symbol});'
|
||||
if stub not in txt:
|
||||
print(f" stub line not found verbatim in {tu}"); revert(ov); results[ov] = 'no-stub-line'; continue
|
||||
print(f" whale now in {os.path.basename(tu)} — swapping in the shared header")
|
||||
new_txt = txt.replace(stub, '#include "../shared/func_80144B9C.h"', 1)
|
||||
# DROP THE AMBIENT DECLS THE HEADER ALSO DECLARES (§8b/§8d).
|
||||
# In the 134 working overlays the whale sits in a CLEAN -O0 file (`common.h` + the header only),
|
||||
# so the TU's own decls of D_80126B58 / D_801274CC / D_801274D0 live in OTHER TUs and never meet
|
||||
# the header's. Here the region was carved out of a jr file, and jr_isolate_all hoists the
|
||||
# parent's file-scope decls into every new region as its `ambient` set — so the same TU now
|
||||
# declares those symbols twice, with the loose-typed spellings the fleet uses
|
||||
# (`extern void *D_801274CC` vs the header's `extern s32 D_801274CC`). The header is byte-proven
|
||||
# across 134 overlays, so the AMBIENT copy is the one to drop, and only in this file.
|
||||
# BOTH axes: the header declares DATA (D_*) and FUNCTION (func_*) symbols, and the ambient set
|
||||
# carried in by the carve can collide on either. The first pass only dropped D_*, and the three
|
||||
# remaining overlays then failed on func_801336E8 / func_8005C324 (the memcpy alias) instead.
|
||||
hdr_syms = set(re.findall(r'\b(?:D_|func_)[0-9A-Fa-f]{8}\b',
|
||||
open('src/shared/func_80144B9C.h').read()))
|
||||
kept, dropped = [], 0
|
||||
for line in new_txt.split('\n'):
|
||||
st = line.strip()
|
||||
# STRIP A TRAILING COMMENT BEFORE THE `;` TEST. The last holdout was
|
||||
# extern void func_8005C324(int,int,int) __asm__("memcpy"); /* Phase-24: ... */
|
||||
# which ends with `*/`, so an endswith(';') test skipped it — the §134 shape yet again.
|
||||
# Safe to drop HERE specifically: after the carve this file holds only the whale, so its
|
||||
# call now emits `func_8005C324` exactly as it does in the 134 overlays where the whale
|
||||
# sits in a clean -O0 file with no alias decl in scope. The parent jr file keeps its alias.
|
||||
st = re.sub(r'/\*.*?\*/\s*$', '', st).strip()
|
||||
st = re.sub(r'//.*$', '', st).strip()
|
||||
if st.startswith('extern ') and st.endswith(';') and '{' not in st:
|
||||
named = set(re.findall(r'\b(?:D_|func_)[0-9A-Fa-f]{8}\b', st))
|
||||
if named and named <= hdr_syms: # every symbol on this line is header-declared
|
||||
dropped += 1
|
||||
continue
|
||||
kept.append(line)
|
||||
if dropped:
|
||||
print(f" dropped {dropped} ambient extern line(s) the shared header already declares")
|
||||
open(tu, 'w').write('\n'.join(kept))
|
||||
|
||||
ok, log = build_ok(ov)
|
||||
if ok:
|
||||
print(f" ✅ BANKED — {ov} byte-identical with the whale from source")
|
||||
results[ov] = 'BANKED'
|
||||
else:
|
||||
hard = [l for l in log.splitlines()
|
||||
if 'undefined reference' in l or 'conflicting' in l or 'error' in l.lower()][:2]
|
||||
print(" gate refused:", *[h.strip()[:150] for h in hard])
|
||||
revert(ov); results[ov] = 'gate-fail'
|
||||
|
||||
print("\n=== S3 SUMMARY ===")
|
||||
for ov, v in results.items():
|
||||
print(f" {ov:<14} {v}")
|
||||
print("\nNEXT: R22 (config changed => full blast radius): make clean && make extract-all && make check-all")
|
||||
@@ -104,6 +104,8 @@ segments:
|
||||
- [0x171f8, c, ov_SC07_006_jr_8013F350]
|
||||
- [0x181d4, c, ov_SC07_006_jr_8014032C]
|
||||
- [0x184b0, c, ov_SC07_006_jr_80140608]
|
||||
- [0x1ca44, c, ov_SC07_006_o0d]
|
||||
- [0x1d64c, c, ov_SC07_006_jr_801457A4]
|
||||
- [0x2c2f4, c, ov_SC07_006_jr_8015444C]
|
||||
- [0x2cacc, c, ov_SC07_006_jr_80154C24]
|
||||
- [0x30774, c, ov_SC07_006_jr_801588CC]
|
||||
|
||||
@@ -104,6 +104,8 @@ segments:
|
||||
- [0x171f8, c, ov_SC07_007_jr_8013F350]
|
||||
- [0x181d4, c, ov_SC07_007_jr_8014032C]
|
||||
- [0x184b0, c, ov_SC07_007_jr_80140608]
|
||||
- [0x1ca44, c, ov_SC07_007_o0d]
|
||||
- [0x1d64c, c, ov_SC07_007_jr_801457A4]
|
||||
- [0x2c2f4, c, ov_SC07_007_jr_8015444C]
|
||||
- [0x2cacc, c, ov_SC07_007_jr_80154C24]
|
||||
- [0x30774, c, ov_SC07_007_jr_801588CC]
|
||||
|
||||
@@ -104,6 +104,8 @@ segments:
|
||||
- [0x171f8, c, ov_SC07_011_jr_8013F350]
|
||||
- [0x181d4, c, ov_SC07_011_jr_8014032C]
|
||||
- [0x184b0, c, ov_SC07_011_jr_80140608]
|
||||
- [0x1ca44, c, ov_SC07_011_o0d]
|
||||
- [0x1d64c, c, ov_SC07_011_jr_801457A4]
|
||||
- [0x2c2f4, c, ov_SC07_011_jr_8015444C]
|
||||
- [0x2cacc, c, ov_SC07_011_jr_80154C24]
|
||||
- [0x30774, c, ov_SC07_011_jr_801588CC]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user