Files
BFM-decomp/.run/giants/c730_side.py
T
Drew T 109ce6a2c3 feat(phase-29): BEHEMOTH #6 func_8017C730 BANKED (1,061 ins) + §82 two source-shape oracles
- CRACKED at xHigh and VERIFIED INDEPENDENTLY: match_one MATCH (1061 ins); agent re-matched 3x
  from clean runs (100% register-masked AND register-kept, all 10 regions, frame 0x270 exact).
  §81 carve chain clean first try: jr_isolate_all --only -> byte-identical cacaf7c2 -> jtbl_carve
  (43-piece set) -> byte-identical -> bank -> R22 clean-fleet 140/140, tools-health OK.
  instr 80.6%; distinct-code 3,844,100 -> 3,845,161.
- WHAT IT IS: the matched base func_8017CA80 + camera height-band cull + distance-driven CLUT
  fade. func_8004974C (TransposeMatrix) sits in a 36-ins prologue deriving a Y band; the
  part-level `lim >= g.otz` cull is GONE; flat arms gain an `sz < lim` near-plane cull. The
  base+one-extra-callee fingerprint predicted this exactly.
- §82 ORACLE 1 -- A DUPLICATED `addiu $aN,$sp,K` ACROSS A `jal` MEANS THE BLOCK WAS INLINED.
  `&X` on any non-first local always creates a pseudo and CSE always merges two of them
  (expr.c:6260 ADDR_EXPR -> force_operand(..., NULL); exception: virtual-stack-vars offset 0).
  So the same stack address re-materialised at two sites separated by a jal means CSE was
  PREVENTED from merging => not the same function body. 17 non-inline spellings failed; a
  `static inline` helper reproduced the prologue BYTE-FOR-BYTE first try. Reusable probe: scan
  the ~1,200 built objects for that signature in NON-INCLUDE_ASM functions.
- §82 ORACLE 2 -- SCALAR vs AGGREGATE DECIDES *WHEN* A STACK SLOT IS ALLOCATED: lazily at first
  `&` for a scalar, AT DECLARATION for an aggregate. Six GTE result words had to be six separate
  longs, not a struct, or they don't land after the inlined helper's temps and the frame isn't
  0x270. Second-order: it also flips MEM_IN_STRUCT_P (§30's /s) -- with one word a fixed-address
  scalar, ((PolyF3*)pkt)->rgbc stops aliasing it, so a store needed respelling to keep the
  target's nop. A scalar-vs-struct choice is simultaneously a frame-layout AND an aliasing
  decision.
- BANKING FOOTNOTE (§75a class A): first bank rejected `conflicting types for ApplyMatrixSV` --
  draft (MATRIX2*, SVECTOR2*, SVECTOR2*) vs the TU/fleet canon (void*, void*, void*), 2,286 of
  2,835 sites. Conforming the decl is byte-neutral and banked first try. On a jr function expect
  BOTH gates to speak: the carve chain answers the jump table, §75a answers the declarations.
- Also reproduced: §78 (reuse a busy variable), §80(i) (a lever went -8 -> exactly neutral as the
  base moved), §72 (a register pin made it worse).
- AGENT'S OWN CAVEAT, recorded not hidden: one zero-byte __asm__ keeps a vestigial `mnc = hmid`
  alive that flow.c would delete (costing 10 ins + the 0x130 spill slot). Emits nothing, compile
  is 1061 exact, but it is a documented stand-in -- 12 natural spellings measured, all DCE'd.
2026-07-25 23:24:11 -06:00

33 lines
1.5 KiB
Python

#!/usr/bin/env python3
"""Side-by-side index-wise listing of a compiled object vs target .s over a range.
usage: c954_side.py <obj.o> <lo> <hi> [fn]"""
import sys, os, re, subprocess, shutil
sys.path.insert(0, '/home/musashi/bfm-decomp/tools')
import masked_diff
OBJ = sys.argv[1]; LO = int(sys.argv[2]); HI = int(sys.argv[3])
FN = sys.argv[4] if len(sys.argv) > 4 else 'func_8017C730'
TGT = '/home/musashi/bfm-decomp/asm/ov_SC03_010/nonmatchings/ov_SC03_010_jr_8017AE2C/%s.s' % FN
OD = [c for c in ["mips-linux-gnu-objdump", "mipsel-linux-gnu-objdump"] if shutil.which(c)][0]
out = subprocess.run([OD, "-drz", OBJ], capture_output=True, text=True).stdout
mt = []
inside = False
for line in out.splitlines():
if re.match(r'^[0-9a-f]+ <%s>:' % FN, line): inside = True; continue
if inside:
if re.match(r'^[0-9a-f]+ <', line): break
m = re.match(r'\s*[0-9a-f]+:\s+[0-9a-f]{8}\s+(.*)', line)
if m: mt.append(re.sub(r'\s+', ' ', m.group(1).strip()))
tt = []
for line in open(TGT):
m = re.match(r'\s*/\* \w+ [0-9A-F]{8} [0-9A-F]{8} \*/\s+(.*)', line)
if m: tt.append(re.sub(r'\s+', ' ', m.group(1).strip().rstrip()))
mine = masked_diff.insns_from_object(OBJ, FN)
tgt = masked_diff.insns_from_s(TGT)
diffs = {i for i, a, b in masked_diff.structured_diff(mine, tgt)}
for k in range(LO, min(HI, max(len(mt), len(tt)))):
flag = '*' if k in diffs else ' '
print('%s %4d | %-38s | %s' % (flag, k, mt[k] if k < len(mt) else '', tt[k] if k < len(tt) else ''))