docs(phase-29): the cold-start experiment — func_8017BF14 to 45/4763; §79; full R22 discharged

- COLD-START RESULT (verified independently): 4763/4763 ins, 45 mismatched = 99.06% byte /
  99.94% structural, exact frame, exact opcode histogram. NOT a match; nothing banked (45 != 0,
  the byte-gate is the sole arbiter). The residual is 3 register-grant ties, 0 structural
  divergence. Named next move: variable REUSE across c0..c3/a0v..a3v, the one §76 lever class
  the pass never reached.
- MY BRIEF'S PREMISE WAS WRONG BY CONSTRUCTION -> §79. I chose this target partly because §71's
  callee-set fingerprint returned 0.00 against every matched giant = "a genuine cold start". But
  the function makes ZERO jal calls, so its callee fingerprint is EMPTY and §71 CANNOT FIRE:
  0.00 meant "cannot answer", not "no relative". Grepping the target's DATA symbol D_800A5E60
  found the matched func_8017BEBC at once -- func_8017BF14 is the 4-light-box member of the same
  renderer family whose 3-box sibling func_8017D960 was matched hours earlier. RULE: when §71
  returns an empty/zero-overlap callee set, fall back to DATA-symbol fingerprinting; an empty
  fingerprint must never become a cold-start brief.
- NEW LEVER (§79): THE FRAME LAYOUT IS A DECLARATION-ORDER ORACLE. gcc-2.7.2 assigns stack slots
  to spilled pseudos in pseudo-number order, and pseudo numbers follow first use ~ declaration
  order -- so the target's frame map reads back its source's declaration order. Moving ONE line
  took 73% -> 84% structural and brought all 127 slots into exact correspondence.
- §76 CONFIRMED AT SCALE: the entire -62 length residual was ONE allocno-class decision (c0..c3
  declared inside the cull blocks -> 1-death local allocnos -> global.c:668-671 removes those
  regs from the global pool -> r1lo spills), 52% -> 93%. An __asm__ ref-dial reached the same
  spill and scored WORSE -- declaration scope beat the ref dial again.
- PIN NUANCE: pins are safe on a 0-jal function (§74's hazard cannot arise), 4 pins took
  94% -> 99%; but §72 held -- pins 5 and 6 made it worse.
- EFFORT ANSWER, HONEST: xHigh from a genuine cold start on a 4,763-ins giant bought the decode,
  the exact length, the exact frame and 99.06%, and did NOT close. Budget a SECOND pass at this
  size: the first buys structure, the last ~1% is register grants.
- FULL R22 DISCHARGED: make clean + extract-all + check-all -> 140 passed, 0 failed of 140 (run
  after the agent finished, per the deferral recorded in the pool commit). tools-health OK.
This commit is contained in:
Drew T
2026-07-25 17:08:02 -06:00
parent a1f1a948af
commit f5f8dec5ee
17 changed files with 2737 additions and 11 deletions
+68
View File
@@ -0,0 +1,68 @@
"""Anchor-segmented shape alignment (robust against repetitive-block confusion).
Anchors = the 56 `mult` sites (+ start/end). Aligns segment-by-segment.
usage: bf14_ali.py <obj.o> [--worst N]"""
import re,subprocess,difflib,sys
OBJ=sys.argv[1]
TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s'
def load_tgt():
W=[];T=[]
for l in open(TGT):
m=re.match(r'\s*/\* \w+ [0-9A-F]{8} ([0-9A-F]{8}) \*/\s+(.*?)\s*$',l)
if m:
v=int.from_bytes(bytes.fromhex(m.group(1)),'little')
W.append(('R',v) if ('%hi(' in l or '%lo(' in l) else v); T.append(re.sub(r'\s+',' ',m.group(2)))
return W,T
def load_mine():
out=subprocess.run(['mipsel-linux-gnu-objdump','-drz',OBJ],capture_output=True,text=True).stdout
W=[];T=[];inside=False
for l in out.splitlines():
if re.match(r'^[0-9a-f]+ <func_8017BF14>:',l): inside=True;continue
if inside:
if re.match(r'^[0-9a-f]+ <',l) and W: break
m=re.match(r'\s*[0-9a-f]+:\s+([0-9a-f]{8})\s+(.*)',l)
if m: W.append(int(m.group(1),16)); T.append(re.sub(r'\s+',' ',m.group(2).strip()))
elif 'R_MIPS_' in l and W and not isinstance(W[-1],tuple) and (W[-1]>>26) not in (2,3):
W[-1]=('R',W[-1])
return W,T
MEM=set(range(0x20,0x30))|{0x32,0x3A}
def shape(x):
rel=isinstance(x,tuple); v=x[1] if rel else x
op=(v>>26)&0x3F
if op==0 or op==0x1C: return ((op<<12)|(v&0x3F)|(((v>>6)&0x1F)<<7),'R' if rel else '')
if op in (2,3): return (op<<26,'')
if op==0x12: return (v&0xFC1F07FF,'')
if op in (1,4,5,6,7): return ((op<<20)|(((v>>16)&0x1F)<<8),'')
imm=0 if rel else (v&0xFFFF)
if op in MEM and ((v>>21)&0x1F)==29: imm=0
return ((op<<20)|imm,'R' if rel else '')
def full(x):
rel=isinstance(x,tuple); v=x[1] if rel else x
op=(v>>26)&0x3F
if rel: return (v&0xFFFF0000,'R')
if op in (2,3): return (op<<26,'')
if op in (1,4,5,6,7): return (v&0xFFFF0000,'')
return (v,'')
def anchors(W):
return [i for i,v in enumerate(W) if not isinstance(v,tuple) and (v&0xFC00003F)==0x18] # SPECIAL mult
TW,TT=load_tgt(); MW,MT=load_mine()
ta=anchors(TW); ma=anchors(MW)
if len(ta)!=len(ma):
print('ANCHOR-MISMATCH tgt=%d mine=%d (falling back to global)'%(len(ta),len(ma)))
ta=[];ma=[]
tb=[0]+ta+[len(TW)]; mb=[0]+ma+[len(MW)]
tot=0; ftot=0; segs=[]
for k in range(len(tb)-1):
a=[shape(x) for x in MW[mb[k]:mb[k+1]]]; b=[shape(x) for x in TW[tb[k]:tb[k+1]]]
sm=difflib.SequenceMatcher(None,a,b,autojunk=False)
eq=sum(i2-i1 for t,i1,i2,j1,j2 in sm.get_opcodes() if t=='equal')
fa=[full(x) for x in MW[mb[k]:mb[k+1]]]; fb=[full(x) for x in TW[tb[k]:tb[k+1]]]
fsm=difflib.SequenceMatcher(None,fa,fb,autojunk=False)
feq=sum(i2-i1 for t,i1,i2,j1,j2 in fsm.get_opcodes() if t=='equal')
tot+=eq; ftot+=feq; segs.append((tb[k+1]-tb[k]-eq, k, tb[k],tb[k+1], mb[k],mb[k+1]))
print('ANCHOR-ALIGNED shape %d / %d = %.2f%% BYTE %d = %.2f%% (mine=%d target=%d)'
%(tot,len(TW),100.0*tot/len(TW),ftot,100.0*ftot/len(TW),len(MW),len(TW)))
if '--worst' in sys.argv:
n=int(sys.argv[sys.argv.index('--worst')+1])
for bad,k,t0,t1,m0,m1 in sorted(segs,reverse=True)[:n]:
print(' seg %3d bad=%4d tgt[%d:%d](%d) mine[%d:%d](%d)'%(k,bad,t0,t1,t1-t0,m0,m1,m1-m0))
+25
View File
@@ -0,0 +1,25 @@
"""opcode-histogram delta mine-vs-target (alignment free). usage: bf14_hist.py <obj.o> [--sum]"""
import re,collections,subprocess,sys
OBJ=sys.argv[1]
TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s'
t=collections.Counter()
for l in open(TGT):
m=re.match(r'\s*/\* \w+ \w+ \w+ \*/\s+(\S+)',l)
if m: t['GTE' if m.group(1) in ('rtps','rtpt','nclip') else m.group(1)]+=1
out=subprocess.run(['mipsel-linux-gnu-objdump','-drz',OBJ],capture_output=True,text=True).stdout
alias={'move':'addu','li':'addiu','b':'j','negu':'subu','c2':'GTE'}
m=collections.Counter(); inside=False
for l in out.splitlines():
if re.match(r'^[0-9a-f]+ <func_8017BF14>:',l): inside=True; continue
if inside:
if re.match(r'^[0-9a-f]+ <',l): break
mm=re.match(r'\s*[0-9a-f]+:\s+[0-9a-f]{8}\s+(\S+)',l)
if mm: m[alias.get(mm.group(1),mm.group(1))]+=1
d={k:m[k]-t[k] for k in set(t)|set(m) if m[k]!=t[k]}
tot=sum(m.values())-sum(t.values())
if '--sum' in sys.argv:
print('len%+d L1=%d %s'%(tot,sum(abs(v) for v in d.values()),
' '.join('%s%+d'%(k,v) for k,v in sorted(d.items(),key=lambda kv:-abs(kv[1])))))
else:
for k,v in sorted(d.items(),key=lambda kv:-abs(kv[1])): print('%-8s %+d'%(k,v))
print('TOTAL %+d'%tot)
+30
View File
@@ -0,0 +1,30 @@
"""Landmark alignment: index of each occurrence of rare opcodes in mine vs target."""
import re,subprocess,sys,collections
OBJ=sys.argv[1]
TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s'
def tgt():
o=[]
for l in open(TGT):
m=re.match(r'\s*/\* \w+ \w+ \w+ \*/\s+(\S+)',l)
if m: o.append(m.group(1))
return o
def mine():
out=subprocess.run(['mipsel-linux-gnu-objdump','-drz',OBJ],capture_output=True,text=True).stdout
o=[];inside=False
alias={'move':'addu','li':'addiu','b':'j','negu':'subu'}
for l in out.splitlines():
if re.match(r'^[0-9a-f]+ <func_8017BF14>:',l): inside=True;continue
if inside:
if re.match(r'^[0-9a-f]+ <',l): break
m=re.match(r'\s*[0-9a-f]+:\s+[0-9a-f]{8}\s+(\S+)',l)
if m: o.append(alias.get(m.group(1),m.group(1)))
return o
T,M=tgt(),mine()
OPS=sys.argv[2].split(',') if len(sys.argv)>2 else ['mult']
ti=[i for i,x in enumerate(T) if x in OPS]
mi=[i for i,x in enumerate(M) if x in OPS]
print('target %d occurrences, mine %d'%(len(ti),len(mi)))
for k in range(max(len(ti),len(mi))):
a=ti[k] if k<len(ti) else None; b=mi[k] if k<len(mi) else None
d=(b-a) if (a is not None and b is not None) else None
print('%3d tgt=%-6s mine=%-6s delta=%s'%(k,a,b,d))
+407
View File
@@ -0,0 +1,407 @@
#!/usr/bin/env python3
"""Lever/variant generator for func_8017BF14 drafts.
usage: bf14_mk.py <base.c> <out.c> lever [lever ...]
Every transformation asserts it applied, so a "neutral" reading can never be a
silent no-op edit (d960_mk.py discipline)."""
import sys, re
def _one(src, old, new, n=1):
c = src.count(old)
assert c == n, 'expected %d occurrences, found %d of %r' % (n, c, old[:80])
return src.replace(old, new)
RDECL = (' s16 r0;\n s16 r1;\n s16 r2;\n s16 r3;\n'
' s16 r0lo;\n s16 r1lo;\n s16 r2lo;\n s16 r3lo;\n')
LEVERS = {}
def lever(fn):
LEVERS[fn.__name__] = fn
return fn
def rdecl_order(src, order):
"""order: e.g. '0,0lo,1,1lo,2,2lo,3,3lo'"""
new = ''.join(' s16 r%s;\n' % t for t in order.split(','))
return _one(src, RDECL, new)
# ---- assignment order inside each box's if-block ---------------------------
BOXASN = {
0: (' r0lo = D_80197C34 - 0x200;\n r0 = D_80197C34;\n',
' r0 = D_80197C34;\n r0lo = D_80197C34 - 0x200;\n'),
1: (' r1 = D_80197C50;\n r1lo = D_80197C50 - 0x200;\n',
' r1lo = D_80197C50 - 0x200;\n r1 = D_80197C50;\n'),
2: (' r2 = D_80197C6C;\n r2lo = D_80197C6C - 0x200;\n',
' r2lo = D_80197C6C - 0x200;\n r2 = D_80197C6C;\n'),
3: (' r3lo = D_80197C6C - 0x200;\n r3 = D_80197C88;\n',
' r3 = D_80197C88;\n r3lo = D_80197C6C - 0x200;\n'),
}
def swapbox(src, k):
a, b = BOXASN[k]
return _one(src, a, b)
for k in range(4):
def mk(k=k):
def f(src): return swapbox(src, k)
return f
g = mk(); g.__name__ = 'swapasn%d' % k; lever(g)
# ---- the r-init block -------------------------------------------------------
RINIT = (' r2lo = 0;\n r1lo = 0;\n r0lo = 0;\n'
' r2 = 0;\n r1 = 0;\n r0 = 0;\n')
def rinit(src, spec):
new = ''.join(' r%s = 0;\n' % t for t in spec.split(',')) if spec else ''
return _one(src, RINIT, new)
# ---- misc -------------------------------------------------------------------
@lever
def basefn(src):
src = _one(src, ' base = D_800AF630;\n', '')
return _one(src, '(*(u16 *)(base + 0xA3D2))', '(*(u16 *)(D_800AF630 + 0xA3D2))')
@lever
def baselate(src):
src = _one(src, ' base = D_800AF630;\n', '')
return _one(src, ' pkt = D_800A5E60;\n', ' base = D_800AF630;\n pkt = D_800A5E60;\n')
@lever
def fzero_fwd(src):
return _one(src, 'f3 = 0; f2 = 0; f1 = 0; f0 = 0;',
'f0 = 0; f1 = 0; f2 = 0; f3 = 0;', 2)
@lever
def mnydial1(src):
o = ' s32 za, zb;\n'
return _one(src, o, o + ' __asm__ __volatile__ ("" :: "r" (mny));\n')
@lever
def primorder(src):
return _one(src,
' nprim = part->nprim;\n prim = (Prim *)part->prim;',
' prim = (Prim *)part->prim;\n nprim = part->nprim;')
@lever
def r3own(src):
"""box3 uses its OWN range global for r3lo (not the copy-paste of box2's)."""
return _one(src, ' r3lo = D_80197C6C - 0x200;\n', ' r3lo = D_80197C88 - 0x200;\n')
@lever
def cb101010(src):
"""unlit rgbc is (tp[0] & 0xFF000000) | 0x101010, not plain black."""
return _one(src, 'cb = tp[0] & 0xFF000000;',
'cb = (tp[0] & 0xFF000000) | 0x101010;', 2)
@lever
def k101010(src):
"""same, but through a dedicated function-scope constant holder."""
src = _one(src, ' u32 cb;\n', ' u32 cb;\n u32 kk;\n')
src = _one(src, ' base = D_800AF630;\n', ' base = D_800AF630;\n kk = 0x101010;\n')
return _one(src, 'cb = tp[0] & 0xFF000000;', 'cb = (tp[0] & 0xFF000000) | kk;', 2)
# ---- declaration-scope levers (cookbook 76) --------------------------------
CULL = ['\n' + ' '*44 + 's32 za, zb;\n',
'\n' + ' '*48 + 's32 za, zb;\n']
LIT = [' u32 *otp;\n',
' u32 *otp;\n']
UNLIT= [' u32 *otp;\n',
' u32 *otp;\n']
def _movedecl(src, fndecl, anchors):
"""delete the function-scope decl line and re-insert after each anchor."""
src = _one(src, ' %s\n' % fndecl, '')
for a in anchors:
ind = ' ' * (len(a.rstrip('\n').split('\n')[-1]) - len(a.rstrip('\n').split('\n')[-1].lstrip()))
src = _one(src, a, a + ind + fndecl + '\n')
return src
def scope(src, fndecl, where):
if where == 'cull': return _movedecl(src, fndecl, CULL)
if where == 'lit':
# 4 occurrences of `u32 *otp;` (2 lit + 2 unlit arms); target only the LIT ones
out = src.replace(' %s\n' % fndecl, '', 1)
assert out != src
for a in (' if (f0 | f1 | f2 | f3) {\n u32 *otp;\n',
' if (f0 | f1 | f2 | f3) {\n u32 *otp;\n'):
ind = ' ' * (len(a.split('\n')[1]) - len(a.split('\n')[1].lstrip()))
out = _one(out, a, a + ind + fndecl + '\n')
return out
raise KeyError(where)
def _mkscope(name, decl, where):
def f(src): return scope(src, decl, where)
f.__name__ = name; lever(f); return f
_mkscope('xyz_cull', 's16 x0, y0, z0, x1, y1, z1, x2, y2, z2;', 'cull')
_mkscope('a_cull', 's32 a0v, a1v, a2v, a3v;', 'cull')
_mkscope('c_cull', 's32 c0, c1, c2, c3;', 'cull')
_mkscope('d_cull', 's32 d;', 'cull')
_mkscope('f_cull', 's32 f0, f1, f2, f3;', 'cull')
_mkscope('tp_cull', 'u32 *tp;', 'cull')
_mkscope('a_lit', 's32 a0v, a1v, a2v, a3v;', 'lit')
_mkscope('c_lit', 's32 c0, c1, c2, c3;', 'lit')
_mkscope('d_lit', 's32 d;', 'lit')
_mkscope('xyz_lit', 's16 x0, y0, z0, x1, y1, z1, x2, y2, z2;', 'lit')
@lever
def novw(src):
"""drop the dedicated vertex-word temps; reuse w / wz."""
src = _one(src, ' u32 vw, vzw;\n', '')
src = src.replace('vw = *(u32 *)', 'w = *(u32 *)').replace('vzw = *(u32 *)', 'wz = *(u32 *)')
src = re.sub(r'= vw;( |$)', r'= w;\1', src)
src = src.replace('vw >> 16', 'w >> 16').replace('= vzw;', '= wz;')
assert 'vw' not in src and 'vzw' not in src
return src
def dial(src, name, n=2):
"""RC-15 zero-byte ref dial: first statement of each cull block."""
for a in CULL:
last = a.rstrip('\n').split('\n')[-1]
ind = ' ' * (len(last) - len(last.lstrip()))
src = _one(src, a, a + ind + '__asm__ __volatile__ ("" :: "r" (%s));\n' % name)
return src
@lever
def dial_mny(src): return dial(src, 'mny')
@lever
def dial_my(src): return dial(src, 'my')
@lever
def dial_r1lo(src): return dial(src, 'r1lo')
@lever
def dial_r0lo(src): return dial(src, 'r0lo')
@lever
def dial_r1(src): return dial(src, 'r1')
# ---- generic zero-byte ref dial on any variable, at a chosen anchor --------
PRIMHEAD = (' for (i = 0; i < nprim; i++, prim++) {\n'
' w = prim->w1;\n')
LITARM = ['\n' + ' '*44 + 'if (f0 | f1 | f2 | f3) {\n' + ' '*44 + 'u32 *otp;\n',
'\n' + ' '*48 + 'if (f0 | f1 | f2 | f3) {\n' + ' '*48 + 'u32 *otp;\n']
def dialat(src, name, where):
stmt = '__asm__ __volatile__ ("" :: "r" (%s));\n'
if where == 'cull':
for a in CULL:
last = a.rstrip('\n').split('\n')[-1]
ind = ' ' * (len(last) - len(last.lstrip()))
src = _one(src, a, a + ind + stmt % name)
elif where == 'prim':
src = _one(src, PRIMHEAD, PRIMHEAD + ' '*24 + stmt % name)
elif where == 'lit':
for a in LITARM:
last = a.rstrip('\n').split('\n')[-1]
ind = ' ' * (len(last) - len(last.lstrip()))
src = _one(src, a, a + ind + stmt % name)
else: raise KeyError(where)
return src
# ---- move the whole r-declaration block ------------------------------------
def rpos(src, where):
src2 = _one(src, RDECL, '')
if where == 'top':
return _one(src2, ' s32 j;\n', RDECL + ' s32 j;\n')
if where == 'bot':
return _one(src2, ' u32 rgbw;\n', ' u32 rgbw;\n' + RDECL)
if where == 'prelo':
return _one(src2, ' s16 lo0x, hi0x,', RDECL + ' s16 lo0x, hi0x,')
if where == 'precxy':
return _one(src2, ' s16 cx0, cy0, cz0,', RDECL + ' s16 cx0, cy0, cz0,')
raise KeyError(where)
# ---- per-EMIT-ARM declaration scope (cookbook 76 / d960 L1) ----------------
def armdecl(src, decl, arms='all'):
src = _one(src, ' %s\n' % decl, '')
for ind, n in ((44, 2), (48, 2)):
a = '\n' + ' ' * ind + 'u32 *otp;\n'
c = src.count(a)
assert c == n, 'arm anchor %d: expected %d found %d' % (ind, n, c)
src = src.replace(a, a + ' ' * ind + decl + '\n')
return src
# ---- reposition a declaration line (slot order == pseudo order == decl order) ----
def declmove(src, decl, anchor):
src = _one(src, ' %s\n' % decl, '')
return _one(src, ' %s\n' % anchor, ' %s\n %s\n' % (anchor, decl))
def armdecl_pre(src, decl):
"""same as armdecl but the decl goes BEFORE `u32 *otp;` in each arm."""
src = _one(src, ' %s\n' % decl, '')
for ind, n in ((44, 2), (48, 2)):
a = '\n' + ' ' * ind + 'u32 *otp;\n'
assert src.count(a) == n
src = src.replace(a, '\n' + ' ' * ind + decl + '\n' + ' ' * ind + 'u32 *otp;\n')
return src
@lever
def dial_mny_tri(src):
a = CULL[0]
last = a.rstrip('\n').split('\n')[-1]
ind = ' ' * (len(last) - len(last.lstrip()))
return _one(src, a, a + ind + '__asm__ __volatile__ ("" :: "r" (mny));\n')
@lever
def dial_mny_quad(src):
a = CULL[1]
last = a.rstrip('\n').split('\n')[-1]
ind = ' ' * (len(last) - len(last.lstrip()))
return _one(src, a, a + ind + '__asm__ __volatile__ ("" :: "r" (mny));\n')
FDECL = ' s32 f0, f1, f2, f3;\n'
@lever
def fsplit_pre(src):
"""f0,f1 declared BEFORE pkt (lower pseudo nos); f2,f3 after pkt (slot order kept)."""
src = _one(src, FDECL, '')
return _one(src, ' u8 *pkt;\n',
' s32 f0, f1;\n u8 *pkt;\n s32 f2, f3;\n')
@lever
def fsplit_post(src):
src = _one(src, FDECL, '')
return _one(src, ' u8 *pkt;\n', ' u8 *pkt;\n s32 f2, f3;\n s32 f0, f1;\n')
@lever
def fdecl_pkt(src):
src = _one(src, FDECL, '')
return _one(src, ' u8 *pkt;\n', ' u8 *pkt;\n' + FDECL)
@lever
def xyz_split(src):
"""x2,y2,z2 declared last (higher pseudo nos -> lower tiebreak priority)."""
o = ' s16 x0, y0, z0, x1, y1, z1, x2, y2, z2;\n'
return _one(src, o, ' s16 x0, y0, z0, x1, y1, z1;\n s16 x2, y2, z2;\n')
@lever
def xyz_late(src):
o = ' s16 x0, y0, z0, x1, y1, z1, x2, y2, z2;\n'
src = _one(src, o, '')
return _one(src, ' u32 rgbw;\n', ' u32 rgbw;\n' + o)
def pin(src, decl, reg):
"""turn a plain decl into a `register ... __asm__("$N")` pin (72: PREFERENCE)."""
return _one(src, ' %s;\n' % decl, ' register %s __asm__("%s");\n' % (decl, reg))
@lever
def pin_va(src):
return _one(src, ' u8 *va, *vb, *vc;\n',
' register u8 *va __asm__("$10");\n u8 *vb, *vc;\n')
@lever
def pin_f0(src):
return _one(src, ' s32 f0, f1, f2, f3;\n',
' register s32 f0 __asm__("$19");\n s32 f1, f2, f3;\n')
@lever
def pin_f0_pkt(src):
return _one(src, ' u8 *pkt;\n s32 f0, f1, f2, f3;\n',
' u8 *pkt;\n register s32 f0 __asm__("$19");\n s32 f1, f2, f3;\n')
@lever
def pin_w(src):
return _one(src, ' u32 w; s32 code;\n',
' register u32 w __asm__("$5");\n s32 code;\n')
@lever
def pin_c1(src):
o = ' s32 c0, c1, c2, c3;\n'
o2 = ' s32 c0, c1, c2, c3;\n'
assert src.count(o) >= 1
src = src.replace('s32 c0, c1, c2, c3;',
'register s32 c1 __asm__("$4"); s32 c0, c2, c3;')
return src
@lever
def pin_call(src):
"""pin the four vertex colours to the target's grants: c0=$t4 c1=$a0 c2=$t2 c3=$a2."""
n = src.count('s32 c0, c1, c2, c3;')
assert n == 2, n
return src.replace('s32 c0, c1, c2, c3;',
'register s32 c0 __asm__("$12"); register s32 c1 __asm__("$4"); '
'register s32 c2 __asm__("$10"); register s32 c3 __asm__("$6");')
@lever
def pin_c01(src):
n = src.count('s32 c0, c1, c2, c3;')
assert n == 2, n
return src.replace('s32 c0, c1, c2, c3;',
'register s32 c0 __asm__("$12"); register s32 c1 __asm__("$4"); s32 c2, c3;')
@lever
def pin_c012(src):
n = src.count('s32 c0, c1, c2, c3;')
assert n == 2, n
return src.replace('s32 c0, c1, c2, c3;',
'register s32 c0 __asm__("$12"); register s32 c1 __asm__("$4"); '
'register s32 c2 __asm__("$10"); s32 c3;')
@lever
def pin_c0(src):
n = src.count('s32 c0, c1, c2, c3;'); assert n == 2, n
return src.replace('s32 c0, c1, c2, c3;',
'register s32 c0 __asm__("$12"); s32 c1, c2, c3;')
@lever
def pin_c2(src):
n = src.count('s32 c0, c1, c2, c3;'); assert n == 2, n
return src.replace('s32 c0, c1, c2, c3;',
'register s32 c2 __asm__("$10"); s32 c0, c1, c3;')
@lever
def pin_c1c2(src):
n = src.count('s32 c0, c1, c2, c3;'); assert n == 2, n
return src.replace('s32 c0, c1, c2, c3;',
'register s32 c1 __asm__("$4"); register s32 c2 __asm__("$10"); s32 c0, c3;')
@lever
def vab_swap(src):
o = (' va = vtx + (w & 0xFFFF);\n'
' vb = vtx + (w >> 16);\n')
return _one(src, o, (' vb = vtx + (w >> 16);\n'
' va = vtx + (w & 0xFFFF);\n'))
@lever
def vd_late(src):
o = ' vd = vtx + (w & 0xFFF8);\n'
src = _one(src, o, '')
a = ' gte_stopz(&g.opz);\n'
return _one(src, a, o + a)
@lever
def cb_two(src):
"""unlit rgbc as a 2-statement accumulator."""
n = src.count('cb = (tp[0] & 0xFF000000) | 0x101010;'); assert n == 2, n
return src.replace('cb = (tp[0] & 0xFF000000) | 0x101010;',
'cb = tp[0] & 0xFF000000;\n cb |= 0x101010;')
@lever
def rgb_chain(src):
"""lit rgb word as a 3-statement accumulator instead of one 4-term expr."""
out, n = re.subn(r'rgbw = \((c\d) \| cb\) \| \((c\d) << 8\) \| \((c\d) << 16\);',
lambda m: 'rgbw = %s | cb; rgbw |= %s << 8; rgbw |= %s << 16;'
% (m.group(1), m.group(2), m.group(3)), src)
assert n == 7, n
return out
@lever
def rgb_chain_q(src):
"""3-statement accumulator in the QUAD lit arm only (4 sites)."""
i = src.index('PolyGT4 *)pkt)->rgb0')
head, tail = src[:i], src[i:]
out, n = re.subn(r'rgbw = \((c\d) \| cb\) \| \((c\d) << 8\) \| \((c\d) << 16\);',
lambda m: 'rgbw = %s | cb; rgbw |= %s << 8; rgbw |= %s << 16;'
% (m.group(1), m.group(2), m.group(3)), tail)
assert n == 3, n
# the rgb0 one sits just before the marker
j = head.rindex('rgbw = ')
h2 = head[:j] + re.sub(r'rgbw = \((c\d) \| cb\) \| \((c\d) << 8\) \| \((c\d) << 16\);',
lambda m: 'rgbw = %s | cb; rgbw |= %s << 8; rgbw |= %s << 16;'
% (m.group(1), m.group(2), m.group(3)), head[j:])
return h2 + out
def main():
base, out, levers = sys.argv[1], sys.argv[2], sys.argv[3:]
src = open(base).read()
for lv in levers:
if lv.startswith('D:'):
src = rdecl_order(src, lv[2:]); continue
if lv.startswith('W:'):
_, d, a = lv.split('@'); src = declmove(src, d.replace('~',' '), a.replace('~',' ')); continue
if lv.startswith('E:'):
src = armdecl_pre(src, lv[2:].replace('~', ' ')); continue
if lv.startswith('A:'):
src = armdecl(src, lv[2:].replace('~', ' ')); continue
if lv.startswith('V:'):
_, nm, wh = lv.split(':'); src = dialat(src, nm, wh); continue
if lv.startswith('P:'):
src = rpos(src, lv[2:]); continue
if lv.startswith('I:'):
src = rinit(src, lv[2:]); continue
src = LEVERS[lv](src)
open(out, 'w').write(src)
main()
+43
View File
@@ -0,0 +1,43 @@
"""Register-correspondence census over index-aligned instructions (same length only)."""
import sys,os,collections
sys.path.insert(0,'/home/musashi/bfm-decomp/tools')
import masked_diff, re, subprocess, shutil
OBJ=sys.argv[1]
TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s'
mine=masked_diff.insns_from_object(OBJ,'func_8017BF14')
tgt=masked_diff.insns_from_s(TGT)
R=['zero','at','v0','v1','a0','a1','a2','a3','t0','t1','t2','t3','t4','t5','t6','t7',
's0','s1','s2','s3','s4','s5','s6','s7','t8','t9','k0','k1','gp','sp','s8','ra']
pairs=collections.Counter()
for i,(a,b) in enumerate(zip(mine,tgt)):
aw = a if isinstance(a,int) else None
# use raw words
for i,(a,b) in enumerate(zip(mine,tgt)):
pass
# simpler: raw words via objdump/target parse
def words_t():
w=[]
for l in open(TGT):
m=re.match(r'\s*/\* \w+ [0-9A-F]{8} ([0-9A-F]{8}) \*/',l)
if m: w.append(int.from_bytes(bytes.fromhex(m.group(1)),'little'))
return w
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
def words_m():
w=[];inside=False
for l in out.splitlines():
if re.match(r'^[0-9a-f]+ <func_8017BF14>:',l): inside=True;continue
if inside:
if re.match(r'^[0-9a-f]+ <',l) and w: break
m=re.match(r'\s*[0-9a-f]+:\s+([0-9a-f]{8})\s',l)
if m: w.append(int(m.group(1),16))
return w
T,M=words_t(),words_m()
print('len',len(M),len(T))
for a,b in zip(M,T):
if a==b: continue
if (a>>26)!=(b>>26): continue
for sh in (21,16,11):
ra=(a>>sh)&0x1F; rb=(b>>sh)&0x1F
if ra!=rb: pairs[(R[ra],R[rb])]+=1
for (a,b),n in pairs.most_common(20): print(' mine $%-4s -> target $%-4s x%d'%(a,b,n))
+6 -4
View File
@@ -1,7 +1,9 @@
#!/bin/bash
# bf14_score.sh <draft.c> <workdir> [tag]
# bf14_score.sh <draft.c> <workdir> [tag] -> "TAG :: len=N d=+X mism=M shape=P% L1=Q"
cd /home/musashi/bfm-decomp
SRC="$1"; WD="$2"; TAG="${3:-$(basename $1 .c)}"
bash .run/giants/bf14_cc.sh "$SRC" "$WD" >/dev/null 2>"$WD.err" || { echo "$TAG :: COMPILE-FAIL $(tail -5 $WD.err|tr '\n' ' ')"; exit 1; }
A=$(python3 .run/giants/bf14_full.py "$WD/t.o" --count)
echo "$TAG :: $A"
bash .run/giants/bf14_cc.sh "$SRC" "$WD" >/dev/null 2>"$WD.err" || { echo "$TAG :: COMPILE-FAIL $(tail -4 $WD.err|tr '\n' ' ')"; exit 1; }
A=$(python3 .run/giants/bf14_full.py "$WD/t.o" --count 2>/dev/null)
H=$(python3 .run/giants/bf14_hist.py "$WD/t.o" --sum 2>/dev/null)
S=$(python3 .run/giants/bf14_shape.py "$WD/t.o" 0 2>/dev/null | head -1 | grep -oP 'SHAPE-aligned \d+/\d+ = \K[0-9.]+')
echo "$TAG :: $A | shape=$S% | $H"
+44
View File
@@ -0,0 +1,44 @@
"""Segment-anchored shape diff. bf14_seg.py <obj.o> <tlo> <thi> <mlo> <mhi> [nh]"""
import re,subprocess,difflib,sys
OBJ=sys.argv[1]; TLO,THI,MLO,MHI=[int(x) for x in sys.argv[2:6]]
NH=int(sys.argv[6]) if len(sys.argv)>6 else 30
TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s'
TW=[];TT=[]
for l in open(TGT):
m=re.match(r'\s*/\* \w+ [0-9A-F]{8} ([0-9A-F]{8}) \*/\s+(.*?)\s*$',l)
if m:
v=int.from_bytes(bytes.fromhex(m.group(1)),'little')
TW.append(('R',v) if ('%hi(' in l or '%lo(' in l) else v); TT.append(re.sub(r'\s+',' ',m.group(2)))
out=subprocess.run(['mipsel-linux-gnu-objdump','-drz',OBJ],capture_output=True,text=True).stdout
MW=[];MT=[];inside=False
for l in out.splitlines():
if re.match(r'^[0-9a-f]+ <func_8017BF14>:',l): inside=True;continue
if inside:
if re.match(r'^[0-9a-f]+ <',l) and MW: break
m=re.match(r'\s*[0-9a-f]+:\s+([0-9a-f]{8})\s+(.*)',l)
if m: MW.append(int(m.group(1),16)); MT.append(re.sub(r'\s+',' ',m.group(2).strip()))
elif 'R_MIPS_' in l and MW and not isinstance(MW[-1],tuple) and (MW[-1]>>26) not in (2,3):
MW[-1]=('R',MW[-1])
MEM=set(range(0x20,0x30))|{0x32,0x3A}
def shape(x):
rel=isinstance(x,tuple); v=x[1] if rel else x
op=(v>>26)&0x3F
if op==0 or op==0x1C: return ((op<<12)|(v&0x3F)|(((v>>6)&0x1F)<<7),'R' if rel else '')
if op in (2,3): return (op<<26,'')
if op==0x12: return (v&0xFC1F07FF,'')
if op in (1,4,5,6,7): return ((op<<20)|(((v>>16)&0x1F)<<8),'')
imm=0 if rel else (v&0xFFFF)
if op in MEM and ((v>>21)&0x1F)==29: imm=0
return ((op<<20)|imm,'R' if rel else '')
a=[shape(x) for x in MW[MLO:MHI]]; b=[shape(x) for x in TW[TLO:THI]]
sm=difflib.SequenceMatcher(None,a,b,autojunk=False); ops=sm.get_opcodes()
eq=sum(i2-i1 for t,i1,i2,j1,j2 in ops if t=='equal')
print('seg tgt[%d:%d](%d) mine[%d:%d](%d) shape-eq %d = %.1f%%'%(TLO,THI,THI-TLO,MLO,MHI,MHI-MLO,eq,100.0*eq/max(1,THI-TLO)))
n=0
for t,i1,i2,j1,j2 in ops:
if t=='equal': continue
n+=1
if n>NH: break
print(' %-8s tgt[%d:%d](%d) mine[%d:%d](%d)'%(t,TLO+j1,TLO+j2,j2-j1,MLO+i1,MLO+i2,i2-i1))
for k in range(j1,min(j2,j1+7)): print(' T %4d %s'%(TLO+k,TT[TLO+k]))
for k in range(i1,min(i2,i1+7)): print(' M %4d %s'%(MLO+k,MT[MLO+k]))
+26
View File
@@ -0,0 +1,26 @@
"""Per-anchor-segment length + opcode delta. usage: bf14_segh.py <obj.o>"""
import re,subprocess,collections,sys
OBJ=sys.argv[1]
TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s'
T=[];TW=[]
for l in open(TGT):
m=re.match(r'\s*/\* \w+ [0-9A-F]{8} ([0-9A-F]{8}) \*/\s+(\S+)',l)
if m: TW.append(int.from_bytes(bytes.fromhex(m.group(1)),'little')); T.append('GTE' if m.group(2) in ('rtps','rtpt','nclip') else m.group(2))
out=subprocess.run(['mipsel-linux-gnu-objdump','-drz',OBJ],capture_output=True,text=True).stdout
alias={'move':'addu','li':'addiu','b':'j','negu':'subu','c2':'GTE'}
M=[];MW=[];inside=False
for l in out.splitlines():
if re.match(r'^[0-9a-f]+ <func_8017BF14>:',l): inside=True;continue
if inside:
if re.match(r'^[0-9a-f]+ <',l) and MW: break
m=re.match(r'\s*[0-9a-f]+:\s+([0-9a-f]{8})\s+(\S+)',l)
if m: MW.append(int(m.group(1),16)); M.append(alias.get(m.group(2),m.group(2)))
ta=[i for i,v in enumerate(TW) if (v&0xFC00003F)==0x18]
ma=[i for i,v in enumerate(MW) if (v&0xFC00003F)==0x18]
tb=[0]+ta+[len(T)]; mb=[0]+ma+[len(M)]
print('segments %d/%d'%(len(ta),len(ma)))
for k in range(len(tb)-1):
ct=collections.Counter(T[tb[k]:tb[k+1]]); cm=collections.Counter(M[mb[k]:mb[k+1]])
d={x:cm[x]-ct[x] for x in set(ct)|set(cm) if cm[x]!=ct[x]}
if d: print('seg %2d tgt[%d:%d] mine[%d:%d] len%+d %s'%(k,tb[k],tb[k+1],mb[k],mb[k+1],
(mb[k+1]-mb[k])-(tb[k+1]-tb[k]),' '.join('%s%+d'%(a,b) for a,b in sorted(d.items(),key=lambda kv:-abs(kv[1])))))
+57
View File
@@ -0,0 +1,57 @@
"""Structural aligner for func_8017BF14: masks registers AND sp-relative offsets,
so a frame-size drift does not hide real structural divergence.
usage: bf14_shape.py <obj.o> [nhunks] [--min N]"""
import re, subprocess, difflib, shutil, sys
OBJ = sys.argv[1]
TGT = '/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s'
NH = int(sys.argv[2]) if len(sys.argv) > 2 and not sys.argv[2].startswith('-') else 40
MIN = int(sys.argv[sys.argv.index('--min')+1]) if '--min' in sys.argv else 1
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
def words_mine():
w=[]
for line in out.splitlines():
m=re.match(r'\s*[0-9a-f]+:\s+([0-9a-f]{8})\s',line)
if m: w.append(int(m.group(1),16))
elif 'R_MIPS_' in line and w and not isinstance(w[-1],tuple) and (w[-1]>>26) not in (2,3):
w[-1]=('R',w[-1])
return w
def words_tgt():
w=[]
for line in open(TGT):
m=re.match(r'\s*/\* \w+ [0-9A-F]{8} ([0-9A-F]{8}) \*/\s+(\S+)\s*(.*)',line)
if m:
v=int.from_bytes(bytes.fromhex(m.group(1)),'little')
w.append(('R',v) if ('%hi(' in line or '%lo(' in line) else v)
return w
MEM = set(range(0x20,0x30)) | {0x32,0x3A} # lb..sw, lwc2, swc2
def shape(x):
rel=isinstance(x,tuple); v=x[1] if rel else x
op=(v>>26)&0x3F
if op==0 or op==0x1C: k=(op<<12)|(v&0x3F)|(((v>>6)&0x1F)<<7)
elif op in (2,3): k=(op<<26)
elif op==0x12: k=v&0xFC1F07FF
elif op in (1,4,5,6,7): k=(op<<20)|(((v>>16)&0x1F)<<8)
else:
imm = 0 if rel else (v & 0xFFFF)
base=(v>>21)&0x1F
if op in MEM and base==29: imm=0 # mask sp offsets
k=(op<<20)|imm
return (k,'R' if rel else '')
mt=[m.group(1).strip() for m in (re.match(r'\s*[0-9a-f]+:\s+[0-9a-f]{8}\s+(.*)',l) for l in out.splitlines()) if m]
tt=[m.group(1).strip() for m in (re.match(r'\s*/\* \w+ [0-9A-F]{8} [0-9A-F]{8} \*/\s+(.*)',l) for l in open(TGT)) if m]
wm,wt=words_mine(),words_tgt()
a=[shape(x) for x in wm]; b=[shape(x) for x in wt]
sm=difflib.SequenceMatcher(None,a,b,autojunk=False); ops=sm.get_opcodes()
eq=sum(i2-i1 for t,i1,i2,j1,j2 in ops if t=='equal')
bad=[o for o in ops if o[0]!='equal']
print("mine %d target %d SHAPE-aligned %d/%d = %.1f%% hunks=%d div-tgt=%d"
% (len(wm),len(wt),eq,len(b),100.0*eq/len(b),len(bad),sum(o[4]-o[3] for o in bad)))
n=0
for t,i1,i2,j1,j2 in bad:
if max(j2-j1,i2-i1)<MIN: continue
n+=1
if n>NH: break
print(" %-8s tgt[%d:%d](%d) mine[%d:%d](%d)"%(t,j1,j2,j2-j1,i1,i2,i2-i1))
for k in range(j1,min(j2,j1+8)): print(" T %4d %s"%(k,tt[k] if k<len(tt) else '??'))
for k in range(i1,min(i2,i1+8)): print(" M %4d %s"%(k,mt[k] if k<len(mt) else '??'))
+26
View File
@@ -0,0 +1,26 @@
"""Stack-slot census: sp-offset -> access counts, for target and one object, side by side."""
import re,subprocess,sys,collections
OBJ=sys.argv[1]
TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s'
def cens_t():
c=collections.defaultdict(collections.Counter)
for l in open(TGT):
m=re.match(r'\s*/\* \w+ \w+ \w+ \*/\s+(\S+)\s+\$\w+, (-?0x[0-9A-Fa-f]+)\(\$sp\)',l)
if m: c[int(m.group(2),16)][m.group(1)]+=1
return c
def cens_m():
out=subprocess.run(['mipsel-linux-gnu-objdump','-drz',OBJ],capture_output=True,text=True).stdout
c=collections.defaultdict(collections.Counter); inside=False
for l in out.splitlines():
if re.match(r'^[0-9a-f]+ <func_8017BF14>:',l): inside=True; continue
if inside:
if re.match(r'^[0-9a-f]+ <',l): break
m=re.match(r'\s*[0-9a-f]+:\s+[0-9a-f]{8}\s+(\S+)\s+\S+,(-?\d+)\(sp\)',l)
if m: c[int(m.group(2))][m.group(1)]+=1
return c
T,M=cens_t(),cens_m()
def fmt(cc): return ' '.join('%s:%d'%(k,v) for k,v in sorted(cc.items()))
print('target slots=%d mine slots=%d'%(len(T),len(M)))
ks=sorted(set(T)|set(M))
for k in ks:
print('0x%03X T[%-28s] M[%s]'%(k,fmt(T[k]),fmt(M[k])))
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
# bf14_sweep.sh <base.c> "<tag>|<lever> [lever...]" ... -> one line per variant, parallel
cd /home/musashi/bfm-decomp
BASE="$1"; shift
run() {
local spec="$1"; local tag="${spec%%|*}"; local lv="${spec#*|}"
local wd=".run/giants/bf14_sw/$tag"
mkdir -p "$wd"
python3 .run/giants/bf14_mk.py "$BASE" "$wd/v.c" $lv 2>"$wd/mk.err" || { echo "$tag :: MK-FAIL $(tail -2 $wd/mk.err|tr '\n' ' ')"; return; }
bash .run/giants/bf14_score.sh "$wd/v.c" "$wd/w" "$tag"
}
export -f run; export BASE
printf '%s\n' "$@" | xargs -P 8 -I{} bash -c 'run "$@"' _ {}
+20
View File
@@ -0,0 +1,20 @@
"""Windowed side-by-side: bf14_win.py <obj.o> <tlo> <thi> <mlo>"""
import re,subprocess,sys
OBJ=sys.argv[1]; TLO=int(sys.argv[2]); THI=int(sys.argv[3]); MLO=int(sys.argv[4])
TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s'
T=[]
for l in open(TGT):
m=re.match(r'\s*/\* \w+ \w+ \w+ \*/\s+(.*?)\s*$',l)
if m: T.append(re.sub(r'\s+',' ',m.group(1)))
out=subprocess.run(['mipsel-linux-gnu-objdump','-drz',OBJ],capture_output=True,text=True).stdout
M=[];inside=False
for l in out.splitlines():
if re.match(r'^[0-9a-f]+ <func_8017BF14>:',l): inside=True;continue
if inside:
if re.match(r'^[0-9a-f]+ <',l): break
m=re.match(r'\s*[0-9a-f]+:\s+[0-9a-f]{8}\s+(.*)',l)
if m: M.append(re.sub(r'\s+',' ',m.group(1).strip()))
n=THI-TLO
for k in range(n):
ti=TLO+k; mi=MLO+k
print('%5d %-38s | %5d %s'%(ti,T[ti] if ti<len(T) else '',mi,M[mi] if mi<len(M) else ''))
+339
View File
@@ -0,0 +1,339 @@
# `func_8017BF14` — behemoth #4, 4,763 ins, `ov_SC03_116` — COLD-START REPORT
**Session 20, 2026-07-25.** Cold start: no prior draft, no `h_norm`/`h_seq` sibling
(family size 1), 0 callees.
---
## 1. FINAL NUMBER (measured, `tools/match_one.py`, the CANDIDATE gate)
```
python3 tools/match_one.py func_8017BF14 --c .run/giants/s19_func_8017BF14_b1.c \
--asm-subdir asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C
-> DIFF func_8017BF14 mine=4763 ins, target=4763 ins, 45 mismatched
```
| metric | value |
|---|---|
| instruction count | **4763 / 4763 — EXACT** |
| opcode histogram L1 distance | **0 — EXACT** (every mnemonic count agrees) |
| stack frame | **EXACT** — frame `0x360`, all **127 slots at the target's offsets** |
| register-masked structural alignment (anchored) | **4760 / 4763 = 99.94 %** |
| byte-identical instructions | **4718 / 4763 = 99.06 %** |
| **NOT A MATCH** | 45 instructions differ |
| pin-free fallback | `s19_func_8017BF14_b1_pinfree.c` → 4763/4763, **789 mismatched**, 100.0 % structural |
The whole-binary SHA1 arbiter (G3/P9) was **not** run — the task forbade touching the
build tree. `match_one` is the candidate check only.
**Class: three register-grant ties.** No structural, length, scheduling-of-blocks,
frame, or expression-shape divergence remains.
---
## 2. THE DECODE
### 2.1 What it is
The **four-light-box** member of the volumetric-light renderer family:
| member | ins | overlay | status |
|---|---|---|---|
| `func_8017BEBC` | ~500 | ov_SC03_099 | MATCHED (unlit ancestor) |
| `func_8017CA80` | 952 | ov_SC03_090 | MATCHED |
| `func_8017F510` | 1511 | — | MATCHED (behemoth #3) |
| `func_8017D960` | 3338 | ov_SC03_090 | MATCHED (behemoth #2, **3 light boxes**) |
| **`func_8017BF14`** | **4763** | ov_SC03_116 | **this one, 4 light boxes** |
`h_norm`/`h_seq` reported family size 1 and the prompt said "no matched relative".
That was true of the *hash* families but **false of the codegen family** — cookbook §71's
callee-set fingerprint could not fire because this function has **zero callees**. The
relative was found instead by **reading the target's globals**: `D_800A5E60` (the packet
cursor) is grepped in `src/`, which lands directly on the matched `func_8017BEBC`.
**Generalisable: for a 0-callee giant, fingerprint by its DATA symbols, not its callees.**
### 2.2 Signature and why it is a leaf
`void func_8017BF14(s32 arg0, s32 lim)` — `sw $a1, 0xB0($sp)` at entry proves arg1.
Every other family member opens with `lim = func_800491EC() + *(s32*)(arg0+0x64);
func_800547D8(arg0+0x10,&mtx); func_80052E38(&mtx);`. Here the caller has already done
that work and passes `lim` in. Consequences, all visible in the frame:
no `$ra` save, no `MATRIX2 mtx` local, and **no 0x10-byte o32 argument area** — so
`tmpxy[4]` starts at `sp+0x00` instead of `sp+0x10`.
### 2.3 Loop nest (2 loops, 414 labels, 0 `jal`)
```
for (j = 0; j < nparts; j++, part++) // idx 214..4746, stride 0x14
build box[8] from part->xx/yy/zz
gte_ldv3c/rtpt/stsxy3 x2 + gte_ldv0/rtps/stsxy x2 -> sxy[8]
gte_stszotz(&g.otz); if (lim >= g.otz) {
screen bbox X in [-0xA0, 0xA1) and Y in [-0x6E, 0x6F)
for (i = 0; i < nprim; i++, prim++) // idx 504..4742, stride 0xC
gte_ldv3(va,vb,vc); gte_rtpt(); gte_stflg(); if (!(flag & 0x7F85E000)) {
gte_nclip(); code = w & 7; vd = vtx + (w & 0xFFF8); gte_stopz();
if (g.opz > 0) switch (code) { // range tree, cases 6,7 then 2,3
case 6/7: TRI -> 3 vertices, 3 colours
case 2/3: QUAD -> 4 vertices, 4 colours
}
```
Codes 0,1,4,5 are dropped (they are the untextured F3/F4 cases of the unlit ancestor).
gcc emits the 4-case switch as the balanced range tree
`code<2 → skip; code<4 → quad; code>=8 → skip; code<6 → skip; else tri`.
### 2.4 The four light boxes
Stride `0x1C`, `{ s32 enable; u16 cx, cy, cz; s32 range; }` at
`D_80197C28 / D_80197C44 / D_80197C60 / D_80197C7C`.
Falloff geometry differs from the 3-box sibling:
| | `func_8017D960` (3 boxes) | `func_8017BF14` (4 boxes) |
|---|---|---|
| low radius | `RLO = R - 0x80` | `RLO = R - 0x200` |
| x-axis ramp | `A = R - d` | `A = (R - d) / 4` |
| z/y-axis ramp | `A = (A*(R-d)) >> 7` | `A = (A*((R-d)/4)) >> 7` |
| colour sum | `c = a0+a1+a2` | `c = a0+a1+a2+a3 + 0x10` |
| unlit rgbc | `tp[0] & 0xFF000000` | `(tp[0] & 0xFF000000) \| 0x101010` |
The `/ 4` is a **signed divide** (`bgez / addiu 3 / sra 2`), not `>> 2`.
The `+ 0x10` ambient bias is applied before the `> 0x80` clamp.
### 2.5 The frame (0x360)
```
0x000 tmpxy[4] 0x010 box[8] 0x050 sxy[8]
0x090 g{otz,flag,opz,sz0..sz3}
0x0B0 lim 0x0B8 j 0x0C0 i 0x0C8 vd 0x0D0 ot 0x0D8 pkt
0x0E0 f2 0x0E8 f3
0x0F0/0x0F8/0x100 x3, z3, y3
0x108 prim 0x110 nprim 0x118 vtx 0x120 nparts 0x128 part
0x130..0x1D0 hi0y,lo0z,hi0z,lo1x,...,hi3z (21 s16 slots, 8-byte pitch)
0x1D8..0x230 cx0,cy0,cz0 .. cx3,cy3,cz3 (12)
0x238 r0 0x240 r1lo 0x248 r2lo 0x250 r3lo
0x288..0x2D0 LICM-hoisted sign-extended bounds (9 s32 + 1 staging slot)
0x328/0x330 reload-spilled vertex coords (y0, z2)
0x338..0x358 s0-s7, fp (NO $ra — leaf)
```
`lo0x`, `hi0x`, `lo0y` never get s16 slots: they live only as the hoisted
sign-extended `0x288/0x290/0x2A0` copies.
---
## 3. FOUR ORIGINAL-SOURCE COPY-PASTE ARTEFACTS (all byte-proven)
The 4th light box was **bolted onto a copy of the 3-box source by hand**, and the hand
edit was incomplete in four places. Every one of these was *read off the target*, and
every one removed a measured delta.
| # | artefact | proof in the target |
|---|---|---|
| 1 | `r3lo = r2 - 0x200;` — box 3's low radius comes from box 2's **range variable** | `addiu $t6, $s0, -0x200` at idx 93 reuses the register box 2's `lw D_80197C6C` filled. Spelling it `D_80197C6C - 0x200` re-loads the global (+2 ins, and it re-materialises `lui/lw`). No sign-extension is emitted because the result is `sh`-truncated. |
| 2 | only **six** of the eight radius variables are zero-initialised | entry emits exactly 3 `move rX,zero` + 3 `sh $zero` = r0,r1,r2,r0lo,r1lo,r2lo. `r3`/`r3lo` are left uninitialised — precisely the init list the 3-box version needed. |
| 3 | the box-3 ATTEN **kill test** still says `r2` in 3 of the 7 hand-written copies | all 24 `sll $v0,$s0,16` (r2 sign-extension) sites enumerated: 3 per vertex-group inside box 2, **plus exactly three extras** at idx **1471, 1504** (tri v0 z and y) and **2178** (tri v2 z). All other copies use `$s6` = r3. |
| 4 | quad-lit `rgb2`/`rgb3` take their `<< 16` term from **c1** | the target CSEs **one** `sll $a0,$a0,16` (idx 4641) and re-uses `$a0` at idx 4647 and 4652. Writing `c2<<16`/`c3<<16` costs +2 `sll`. |
Modelling these is not optional cosmetics — #3 alone was `sra+11 / sll+9` and #4 was the
last `sll+2`.
---
## 4. THE LEVERS THAT MOVED THE NUMBER (measured)
Metric below is **byte-identical %** from the anchored aligner
(`.run/giants/bf14_ali.py`), which is length-drift-proof.
| # | lever | before → after |
|---|---|---|
| L1 | unlit `cb = (tp[0] & 0xFF000000) \| 0x101010` | len −64 → −62; retired the whole `or`/`ori`/`lui` histogram delta |
| L2 | artefacts 1 + 3 (box-3 kill register per copy, `r3lo = r2 - 0x200`) | shape 89.15 % → 96.96 %; killed `sra+11 / sll+9` |
| L3 | artefact 4 (quad-lit `c1 << 16`) | killed the last `sll+2` |
| **L4** | **`s32 c0,c1,c2,c3;` declared inside the two CULL blocks** | **52.26 % → 92.86 % — the single biggest lever, and it is what spills `r1lo`** |
| **L5** | **`s32 f0,f1,f2,f3;` moved to immediately after `u8 *pkt;`** | **73.00 % → 83.98 %, and ALL 127 stack slots then match exactly** |
| L6 | `u32 rgbw;` per emit arm | 93.45 % → 94.21 % (with L7) |
| L7 | RC-15 zero-byte ref dial on `mny`, head of the TRI cull block | 93.47 % → 94.21 % |
| L8 | `cb` 2-statement accumulator + quad-lit rgb 3-statement accumulator | 99.06 % → 99.12 % |
| L9 | four register pins: `va→$t2`, `w→$a1`, `f0→$s3`, `c1→$a0` | 94.21 % → **99.06 %** |
### 4.1 L4 — why declaration scope, and not a dial (cookbook §76)
The whole −62 length residual was **one** decision: the target spills `r1lo` to `0x240`
and reloads it 21 times (`lhu` + load-delay `nop` at each of 3 axes × 7 vertex-colour
groups = exactly the measured `lhu −21` / `nop −21` / `sh −2`), while every draft kept
it in `$fp`.
`$fp` is register 30 — the **last** register a plain-ascending `find_reg` scan reaches
(MIPS defines no `REG_ALLOC_ORDER` in gcc-2.7.2, verified in
`tools/reference/gcc-2.7.2/config/mips/mips.h`). So `r1lo` was the *marginal* allocno:
the target simply had **one more competitor** than my draft.
Declaring `c0..c3` inside the two cull blocks supplies that competitor the way the
compiler actually models it: at function scope `c0..c3` have a death in each of the four
emit arms, so `local-alloc.c:472` (`REG_BASIC_BLOCK >= 0 && REG_N_DEATHS == 1`) refuses
them and they become global allocnos; per cull block they are 1-death **local** pseudos,
local-alloc places them, and `global.c:668-671` re-marks those placements as **hard
registers** for global-alloc's conflict scan — removing them from the global pool and
pushing `r1lo` onto the stack.
An `__asm__` ref-dial on `mn`/`mx`/`my`/`mny` inside the lit arm reached the same spill
(84.78 %) and was found *first*; declaration scope is both better (92.86 %) and real C.
**Lesson: when a spill is missing, look for the missing LOCAL allocno before reaching for
a ref dial.**
### 4.2 L5 — the stack-slot order is a declaration-order ORACLE (new, generalisable)
Spilled pseudos are given stack slots in **pseudo-number order** (`reload1.c: alter_reg`),
and pseudo numbers are handed out in **declaration order** (`expand_decl`). Therefore the
target's stack layout is a direct read-out of the original declaration order.
Target: `pkt` at `0xD8`, then **two `lw:9 sw:9` slots at `0xE0`/`0xE8`** (the f2/f3
flags), then three `lhu:7 sh:1` slots (`x3,z3,y3`), then `prim/nprim/vtx/nparts/part`.
My draft had the flags declared late, so everything above `0xD8` was shifted by `0x10`.
Moving one declaration line fixed **every** slot: 127/127.
This is a cheap, deterministic, repeatable technique — `.run/giants/bf14_slots.py`
prints the slot census side by side and any mismatch is a declaration-order bug.
**It should go in the cookbook.**
### 4.3 L9 — pins are safe *here*, and §72 still holds
Cookbook §72/§74: a `register __asm__` pin is a **preference, not a reservation**, and
the real hazard is a caller-saved pin spanning a `jal`. **This function has zero `jal`s**,
so that hazard cannot arise — which is exactly why pins are usable on this family member
and were a trap on the others. Four pins bought 94.21 % → 99.06 %.
§72 was nevertheless reproduced: adding a 5th and 6th pin (`c0→$t4`, `c2→$t2`, both
values the target genuinely puts there) made it **worse — 92.86 %**. Pins do not compose.
A **pin-free** draft is preserved at `.run/giants/s19_func_8017BF14_b1_pinfree.c`
(4763/4763 ins, 789 mismatched, 100.0 % structural, exact frame) for anyone who wants the
conservative variant.
---
## 5. DO-NOT-RE-BUY TABLE (every lever measured, neutral or negative)
Baselines are stated per block because the base moved as levers landed.
### 5.1 Neutral — no effect at all
| lever | result |
|---|---|
| declaration-order permutations of `r0..r3 / r0lo..r3lo` (5 orders swept) | neutral (±4 on a 4526 base) |
| swapping `rN` / `rNlo` assignment order inside each box's `if` (all 4 boxes) | neutral |
| `a0v..a3v` / `d` / `tp` declared per cull block | neutral |
| `a0v..a3v` / `c0..c3` / `d` declared inside the lit arm | neutral |
| moving the whole r-declaration block (top / bottom / before lo-hi / before centres) | neutral |
| declaration position of `s16 my,mny,mx,mn` (6 anchors swept) | **neutral — cannot substitute for the L7 dial** |
| splitting `f0,f1` before `pkt` and `f2,f3` after | neutral |
| `x2,y2,z2` split out / declared late | neutral |
| arm-declaration ORDER (`tp`/`rgbw`/`cb` before vs after `u32 *otp;`) | neutral (4 permutations) |
| `vd` computed later; `va`/`vb` assignment swapped | neutral |
| second ref dial on any of f0..f3, x0..z2, mn/mx/my/mny, w, wz, vw, vzw, i, j, nprim, at prim/cull scope, on top of the best base | neutral or worse (40 probes) |
### 5.2 Negative — actively harmful
| lever | result vs its base |
|---|---|
| `s16 x0..z2` declared per cull block (`xyz_cull`) | 92.86 % → **57.23 %** |
| `s32 f0..f3` declared per cull block (`f_cull`) | 92.86 % → **67.16 %** |
| ref dial on `mn` in the lit arm, once `c_cull` is in | 92.86 % → **63.20 %** (the two levers do the same job and collide) |
| `cb` declared per emit arm | 94.21 % → **91.31 %** |
| `a0v..a3v` per cull block, on top of `c_cull` | 92.86 % → **90.32 %** |
| pinning `c0` and `c2` in addition to `c1` | 99.06 % → **92.86 %** |
| `rgbw` as a 3-statement accumulator in **both** arms | 99.06 % → 98.22 % (quad-only is +0.02 %) |
| `base = D_800AF630` rematerialised at use / hoisted late | 52.26 % → 52.11 % |
| `prim`/`nprim` read order swapped | neutral-to-worse |
| `f0=0..f3=0` in forward instead of reverse order | 92.86 % → 92.78 % |
### 5.3 Rejected diagnoses (each was measured and refuted)
* **"the −62 is missing code."** It was not: the opcode-histogram delta was
`nop −34 / lhu −21 / sh −2 / addu +1` — a pure spill signature. Confirmed by
§78's rule: *a `nop` present in the target and absent from the draft is a register
fact, not missing code.*
* **"`r3lo` comes from `D_80197C88 - 0x200`."** Measured: that spelling re-loads the
global (`lui/lhu` + `addiu`) instead of reusing `$s0`. It scored *better* on the raw
mismatch counter purely because it shuffled the allocation — a metric trap. The
anchored aligner and the slot census disagreed, and they were right.
* **"the box-3 ATTEN is uniform across the 7 copies."** Refuted by enumerating all 24
`sll $v0,$s0,16` sites: three copies differ.
* **"declaration order can replace the `mny` ref dial."** Six anchor positions swept, all
neutral. Declaration order moves the *tiebreak*; the dial moves `reg_n_refs`
(× loop depth) and therefore the `allocno_compare` **score**. They are not
interchangeable.
---
## 6. THE REMAINING 45 INSTRUCTIONS
Three independent register-grant ties. No structural residual.
**(a) The prim-word producer temps (≈8 ins, idx 508–514, 539–542).**
Target: `andi $v1,$a1,0xFFFF` / `srl $a0,$a1,16` / `addu $t2,$t6,$v1`.
Draft: the `andi` result is written straight into `$t2` (`va`'s register) and the `addu`
is in-place. This is `combine_regs` (`local-alloc.c:1825`) tying the producer chain into
`va` — which the **`va→$t2` pin invites**, because a pinned pseudo is a hard register from
the start and local-alloc will always tie into it. Removing the pin unties the chain but
costs 4 % elsewhere. *Most promising next move: keep the tie broken by giving the two
offsets their own named, multi-death variables (raising their death count above 1 so
`local-alloc.c:472` refuses them), rather than by removing the pin.*
**(b) `c0` and `c2` grants (≈15 ins).** Target `c0→$t4, c1→$a0, c2→$t2, c3→$a2`; draft
gets `c1` (pinned) right and `c0→$t2, c2→$a2`. Pinning them directly is refuted (§5.2).
The lever is `allocno_compare` order among the four, i.e. their relative ref counts —
reachable by a variable-REUSE merge (§45-A / RC-14), which was **not** swept for `c0..c3`
and is the obvious next experiment.
**(c) The quad-lit rgb accumulator (≈20 ins, idx 4635–4653).** Target accumulates in
`$v1` and stores from `$v1`; the draft accumulates in `$v0`. Three one-slot
store/shift transpositions ride along with it (`sll $v0,$a0,8` before vs after
`sw $v1,-0x20($t3)`). **Before calling these a scheduling residual, run §76's attribution
primitive** (`-fno-schedule-insns` and `-fno-schedule-insns2`): if the pair keeps source
order under both, it was fixed at RTL expansion and the lever is statement order, not
`sched.c`. That test was *not* run here and is the cheapest remaining probe.
### Single most promising next move
**Sweep variable REUSE across `c0..c3` and the `a0v..a3v` accumulators** (merge two temps
into one, §45-A / RC-14 MERGE) to move `allocno_compare` priority. That is the one §76
lever class this session never reached — declaration scope was swept exhaustively,
reuse was not. It targets residual (b) directly and plausibly (c) as well.
---
## 7. TOOLS BUILT (all preserved in `.run/giants/`, ~0.3–0.8 s per probe)
| tool | what it does |
|---|---|
| `bf14_cc.sh` / `bf14_score.sh` / `bf14_probe.sh` | compile + score one draft (0.28 s) |
| `bf14_mk.py` | lever generator; **every transformation asserts it applied**, so a "neutral" reading can never be a silent no-op |
| `bf14_sweep.sh` | 8-way parallel lever sweep |
| `bf14_full.py` / `bf14_side.py` / `bf14_win.py` | masked diff, side-by-side, windowed side-by-side |
| `bf14_hist.py` | opcode-histogram delta — **alignment-free, length-drift-proof** |
| `bf14_shape.py` | difflib shape diff with sp offsets masked |
| **`bf14_ali.py`** | **anchor-segmented aligner** — segments the function at the 56 `mult` sites and aligns each segment independently. Plain difflib gets hopelessly lost in this function's repeated blocks (it once reported a bogus 1,836-instruction insertion); anchoring fixes it. Reports both register-masked and byte-level alignment. |
| `bf14_segh.py` | per-segment length + opcode delta — localises a drift to one ATTEN axis |
| `bf14_land.py` | landmark drift table (`mult` index deltas) |
| **`bf14_slots.py`** | **stack-slot census vs the target — the declaration-order oracle of §4.2** |
| `bf14_regmap.py` | register-correspondence census; exposes allocation *cycles* rather than a list of diffs |
| `bf14_struct.py` | label / branch / loop map of the target |
### Reusable methodology notes
1. **Anchor your aligner.** On a function with 7 near-identical 400-instruction blocks,
raw `difflib` is worse than useless — it reports confident nonsense. Segment on a rare
opcode first.
2. **The opcode histogram is the honest early metric.** Index-wise "mismatched" is
dominated by length drift and will rank a *worse* draft higher (this happened, §5.3).
3. **The stack-slot census is a free oracle for declaration order** (§4.2).
4. **The register-correspondence census turns a wall of diffs into cycles.** Seeing
`$t2→$t3→$t5→$t4→$t2` as a rotation, and `$s3↔$s4` as a swap, is what made the last
5 % tractable.
---
## 8. FILES
* `.run/giants/s19_func_8017BF14_b1.c` — best draft, full dossier header. **45/4763.**
* `.run/giants/s19_func_8017BF14_b1_pinfree.c` — pin-free variant. 789/4763, 100 % structural.
* `.run/giants/bf14_*.py`, `.run/giants/bf14_*.sh` — the harness.
+770
View File
@@ -0,0 +1,770 @@
#include "common.h"
#include "/home/musashi/bfm-decomp/src/shared/engine_types.h"
/* ===========================================================================
* func_8017BF14 -- 4,763 ins, ov_SC03_116 (behemoth #4). COLD START.
*
* STATUS (2026-07-25, session 20, gcc-2.7.2 pinned triple):
* python3 tools/match_one.py func_8017BF14 --c <this file> \
* --asm-subdir asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C
* -> DIFF 4763/4763 ins, 45 mismatched (99.06% byte-identical)
* LENGTH EXACT | OPCODE HISTOGRAM EXACT (L1 = 0) | STACK FRAME EXACT
* (all 127 slots at the target's offsets, frame 0x360)
* register-masked structural alignment 4760/4763 = 99.94%
* NOT A MATCH. The whole-binary SHA1 arbiter (G3/P9) has NOT been run.
* Pin-free fallback (see LEVERS L6..L9): 4763/4763, 789 mismatched.
*
* WHAT IT IS
* The *four*-light-box variant of the volumetric-light renderer whose
* 3-box sibling func_8017D960 (3,338 ins, ov_SC03_090) is MATCHED, and whose
* unlit ancestor func_8017BEBC (ov_SC03_099) is MATCHED. Same family, same
* skeleton; this is the biggest member.
*
* Signature: func_8017BF14(s32 arg0, s32 lim). Unlike every other member of
* the family this one is a LEAF -- 0 callees. The 3-call prologue
* (func_800491EC / func_800547D8 / func_80052E38) of the siblings is gone;
* `lim` arrives as arg1 (spilled to 0xB0). That is why the frame has no
* 0x10 argument area (tmpxy[] starts at sp+0x00) and no $ra save.
*
* Per part (stride 0x14, outer loop): build the 8-corner AABB in box[],
* rtpt/rtpt + rtps/rtps -> sxy[8], stszotz -> g.otz, reject on
* `lim >= g.otz`, then screen-space bbox reject on X (-0xA0..0xA1) and
* Y (-0x6E..0x6F).
* Per prim (stride 0xC, inner loop): rtpt the 3 vertices, stflg mask
* 0x7F85E000, nclip, stopz > 0, then a 4-way range tree on `code = w & 7`
* that keeps ONLY codes 6,7 (tri) and 2,3 (quad); 0,1,4,5 fall through to
* the loop tail.
* Per drawn poly: screen bbox reject, then each vertex is tested against
* FOUR axis-aligned light boxes (flags f0..f3), and if any is lit a
* 0x00..0x80 attenuation per active box is computed, summed, biased +0x10
* and clamped to 0x80 -> a grey gouraud vertex colour.
* lit -> POLY_GT3 (0x28, tag 0x34000000, OT 0x9000000)
* POLY_GT4 (0x34, tag 0x3C000000, OT 0xC000000)
* unlit -> POLY_FT3 (0x20, OT 0x7000000) / POLY_FT4 (0x28, OT 0x9000000)
* with rgbc = (tp[0] & 0xFF000000) | 0x101010 <-- NOT black,
* unlike func_8017D960 where the unlit colour is plain black.
*
* THE FOUR LIGHT BOXES (stride 0x1C, {s32 enable; u16 cx,cy,cz; s32 range})
* D_80197C28 / D_80197C44 / D_80197C60 / D_80197C7C.
* Falloff geometry differs from the 3-box sibling: RLO = R - 0x200 (not
* -0x80) and the ramp is ((R - d) / 4) (not (R - d)), so the 0x80 ceiling is
* reached over a 0x200-wide band instead of 0x80. The `/ 4` is a SIGNED
* divide -- `bgez / addiu 3 / sra 2` -- not a shift.
*
* THREE ORIGINAL-SOURCE COPY-PASTE ARTEFACTS, all byte-proven (see report)
* (1) `r3lo = r2 - 0x200;` -- box 3's low radius is derived from box 2's
* RANGE VARIABLE, not from its own D_80197C88. Proven by the target's
* `addiu $t6, $s0, -0x200` reusing the register that box 2's `lw` filled;
* spelling it `D_80197C6C - 0x200` re-loads the global (+2 ins).
* (2) Only SIX of the eight radius variables are zero-initialised
* (r0,r1,r2,r0lo,r1lo,r2lo) -- r3/r3lo are left uninitialised, exactly
* the init list the 3-box version needed.
* (3) The ATTEN body is written out LONGHAND 7 times (3 tri vertices +
* 4 quad vertices). When box 3 was bolted on, the `R` of the z- and
* y-axis KILL tests was left as r2 in three of those copies:
* tri v0: z and y use r2 tri v2: z uses r2 all others use r3.
* Proven by the 24 `sll $v0,$s0,16` sites: 3 per group in box 2 plus
* exactly three extra at idx 1471, 1504 (tri v0) and 2178 (tri v2).
* (4) In the QUAD lit arm only, rgb2 and rgb3 take their `<< 16` term from
* c1, not from c2/c3. Proven by the target CSE-ing ONE
* `sll $a0, $a0, 16` and re-using $a0 for all three stores.
* These are not guesses: each was read off the target and each removed a
* measured instruction-count or opcode-histogram delta.
*
* FRAME (0x360, leaf -- no $ra, no argument area)
* 0x000 tmpxy[4] | 0x010 box[8] | 0x050 sxy[8] |
* 0x090 g{otz,flag,opz,sz0..sz3} | 0x0B0 lim | 0x0B8 j | 0x0C0 i |
* 0x0C8 vd | 0x0D0 ot | 0x0D8 pkt | 0x0E0 f2 | 0x0E8 f3 |
* 0x0F0/0x0F8/0x100 x3,z3,y3 | 0x108 prim | 0x110 nprim | 0x118 vtx |
* 0x120 nparts | 0x128 part | 0x130..0x1D0 lo/hi bounds (21 s16 slots) |
* 0x1D8..0x230 cx0..cz3 (12) | 0x238 r0 | 0x240 r1lo | 0x248 r2lo |
* 0x250 r3lo | 0x288..0x2D0 the LICM-hoisted sign-extended bounds |
* 0x328/0x330 spilled vertex coords | 0x338..0x358 s0-s7,fp.
* *** THE SLOT ORDER IS THE DECLARATION-ORDER ORACLE (see L5). ***
*
* ---------------------------------------------------------------------------
* THE LEVERS, each with its MEASURED effect (byte-identical %, anchored)
*
* L1 `cb = (tp[0] & 0xFF000000) | 0x101010;` in both UNLIT arms.
* Was plain black (copied from the 3-box sibling). -64 -> -62 length,
* and it retired the whole or/ori/lui histogram delta.
* L2 box-3 ATTEN kill-register per copy (artefact 3 above) + `r3lo = r2 -
* 0x200` (artefact 1). 89.15% -> 96.96% shape; killed sra+11 / sll+9.
* L3 quad-lit rgb2/rgb3 use `c1 << 16` (artefact 4). Killed the last sll+2.
* L4 **`s32 c0, c1, c2, c3;` DECLARED INSIDE THE TWO CULL BLOCKS**, not at
* function scope. THE SINGLE BIGGEST LEVER: 52.26% -> 92.86% byte, and it
* is what finally spills r1lo (21 reloads + 21 nops, the entire -62
* residual length). Cookbook Sec.76 exactly: at function scope c0..c3 have
* a death in each of the four emit arms, so local-alloc.c:472
* (REG_BASIC_BLOCK >= 0 && REG_N_DEATHS == 1) refuses them; per-cull-block
* they are 1-death local pseudos, local-alloc places them, and via
* global.c:668-671 those placements remove registers from the GLOBAL pool
* -- which is what pushes r1lo out of $fp and onto the stack.
* An `__asm__` ref-dial on mn/mx/my/mny reached the same spill (84.78%)
* but is strictly worse AND artificial. Declaration scope wins.
* L5 **`s32 f0, f1, f2, f3;` MOVED TO IMMEDIATELY AFTER `u8 *pkt;`.**
* 73.00% -> 83.98% byte. Reason (and the reusable trick): spilled pseudos
* get stack slots in PSEUDO-NUMBER order, and pseudo numbers are assigned
* in DECLARATION order -- so the target's stack-slot layout is a direct
* read-out of its declaration order. The target has f2 at 0xE0 and f3 at
* 0xE8, i.e. between `pkt` (0xD8) and `x3,z3,y3` (0xF0..0x100). After this
* one move ALL 127 stack slots agree with the target exactly.
* L6 `u32 rgbw;` per emit arm (+0.6%).
* L7 RC-15 zero-byte ref dial on `mny`, first statement of the TRI cull block
* -- identical to func_8017D960's L4, same cause: `mny` is defined first
* but outlives `my`, so allocno_compare (global.c:594) ranks `my` above it
* and `my` takes the lower $a2. The dial flips it to the target's
* mny->$a2 / my->$a3. 93.47% -> 94.21%.
* L8 `cb` as a 2-statement accumulator; quad-lit rgb word as a 3-statement
* accumulator (+0.1% together).
* L9 FOUR REGISTER PINS: va->$t2, w->$a1, f0->$s3, c1->$a0.
* 94.21% -> 99.06%. Sec.72/Sec.74 note: this function has NO `jal`, so the
* caller-saved-pin-spanning-a-call hazard cannot arise here -- that is why
* pins are usable on this member of the family and were a trap on the
* others. Pins remain PREFERENCES: adding a 5th and 6th (c0->$t4,
* c2->$t2) made it WORSE (92.86%), which is Sec.72 reproduced.
* The PIN-FREE draft is kept at .run/giants/s19_func_8017BF14_b1_pinfree.c
* (4763/4763, 789 mismatched, 100.0% structural) for anyone who wants the
* safe variant.
*
* REMAINING RESIDUAL (45 instructions, 0.9%): three register-grant ties --
* the `w & 0xFFFF` / `w >> 16` producer temps that local-alloc combine_regs
* ties into the pinned `va`; the c0/c2 grants ($t4/$t2 vs $t2/$a2); and the
* lit rgb accumulator $v1 vs $v0 in the quad arm with its 3 one-slot
* store/shift transpositions. See .run/giants/s19_bf14_report.md.
* =========================================================================== */
#define gte_ldv0(r0) __asm__ volatile ( \
"lwc2 $0, 0( %0 );" \
"lwc2 $1, 4( %0 )" \
: \
: "r"( r0 ) )
#define gte_ldv3(r0, r1, r2) __asm__ volatile ( \
"lwc2 $0, 0( %0 );" \
"lwc2 $1, 4( %0 );" \
"lwc2 $2, 0( %1 );" \
"lwc2 $3, 4( %1 );" \
"lwc2 $4, 0( %2 );" \
"lwc2 $5, 4( %2 )" \
: \
: "r"( r0 ), "r"( r1 ), "r"( r2 ) )
#define gte_ldv3c(r0) __asm__ volatile ( \
"lwc2 $0, 0( %0 );" \
"lwc2 $1, 4( %0 );" \
"lwc2 $2, 8( %0 );" \
"lwc2 $3, 12( %0 );" \
"lwc2 $4, 16( %0 );" \
"lwc2 $5, 20( %0 )" \
: \
: "r"( r0 ) )
#define gte_rtps() __asm__ volatile ("nop;nop;rtps")
#define gte_rtpt() __asm__ volatile ("nop;nop;rtpt")
#define gte_nclip() __asm__ volatile ("nop;nop;nclip")
#define gte_stsxy(r0) __asm__ volatile ( \
"swc2 $14, 0( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
#define gte_stsxy3(r0, r1, r2) __asm__ volatile ( \
"swc2 $12, 0( %0 );" \
"swc2 $13, 0( %1 );" \
"swc2 $14, 0( %2 )" \
: \
: "r"( r0 ), "r"( r1 ), "r"( r2 ) \
: "memory" )
#define gte_stsxy3c(r0) __asm__ volatile ( \
"swc2 $12, 0( %0 );" \
"swc2 $13, 4( %0 );" \
"swc2 $14, 8( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
#define gte_stsz3(r0, r1, r2) __asm__ volatile ( \
"swc2 $17, 0( %0 );" \
"swc2 $18, 0( %1 );" \
"swc2 $19, 0( %2 )" \
: \
: "r"( r0 ), "r"( r1 ), "r"( r2 ) \
: "memory" )
#define gte_stsz4(r0, r1, r2, r3) __asm__ volatile ( \
"swc2 $16, 0( %0 );" \
"swc2 $17, 0( %1 );" \
"swc2 $18, 0( %2 );" \
"swc2 $19, 0( %3 )" \
: \
: "r"( r0 ), "r"( r1 ), "r"( r2 ), "r"( r3 ) \
: "memory" )
#define gte_stszotz(r0) __asm__ volatile ( \
"mfc2 $12, $19;" \
"nop;" \
"sra $12, $12, 2;" \
"sw $12, 0( %0 )" \
: \
: "r"( r0 ) \
: "$12", "memory" )
#define gte_stflg(r0) __asm__ volatile ( \
"cfc2 $12, $31;" \
"nop;" \
"sw $12, 0( %0 )" \
: \
: "r"( r0 ) \
: "$12", "memory" )
#define gte_stopz(r0) __asm__ volatile ( \
"swc2 $24, 0( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
/* ---- the two gouraud-textured packet layouts this function emits ---------- */
typedef struct {
u32 tag;
u32 rgb0; s16 x0, y0; u32 uv0;
u32 rgb1; s16 x1, y1; u32 uv1;
u32 rgb2; s16 x2, y2; u16 uv2, p2;
} PolyGT3; /* 0x28 */
typedef struct {
u32 tag;
u32 rgb0; s16 x0, y0; u32 uv0;
u32 rgb1; s16 x1, y1; u32 uv1;
u32 rgb2; s16 x2, y2; u16 uv2, p2;
u32 rgb3; s16 x3, y3; u16 uv3, p3;
} PolyGT4; /* 0x34 */
/* ---- the four light-volume descriptors (stride 0x1C) --------------------- */
extern s32 D_80197C28;
extern u16 D_80197C2C, D_80197C2E, D_80197C30;
extern s32 D_80197C34;
extern s32 D_80197C44;
extern u16 D_80197C48, D_80197C4A, D_80197C4C;
extern s32 D_80197C50;
extern s32 D_80197C60;
extern u16 D_80197C64, D_80197C66, D_80197C68;
extern s32 D_80197C6C;
extern s32 D_80197C7C;
extern u16 D_80197C80, D_80197C82, D_80197C84;
extern u16 D_80197C88;
/* ---- the box-containment test for one vertex against one light box ------- */
#define BOXTEST(F, X, Y, Z, LX, HX, LY, HY, LZ, HZ) \
if ((LX) < (X) && (X) < (HX) && (LY) < (Y) && (Y) < (HY) && (LZ) < (Z) && (Z) < (HZ)) F = 1
/* ---- the separable per-axis falloff, visited in x, z, y order ------------ */
#define ATTEN(A, F, X, Y, Z, CX, CY, CZ, R, RLO) \
A = 0; \
if (F) { \
d = (X) - (CX); if (d < 0) d = (CX) - (X); \
if (d < (R)) { A = 0x80; if (d >= (RLO)) A = ((R) - d) / 4; } \
d = (Z) - (CZ); if (d < 0) d = (CZ) - (Z); \
if ((R) < d) A = 0; \
else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \
d = (Y) - (CY); if (d < 0) d = (CY) - (Y); \
if ((R) < d) A = 0; \
else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \
}
#define ATTEN3(A, F, X, Y, Z, CX, CY, CZ, R, RLO, RZ, RY) \
A = 0; \
if (F) { \
d = (X) - (CX); if (d < 0) d = (CX) - (X); \
if (d < (R)) { A = 0x80; if (d >= (RLO)) A = ((R) - d) / 4; } \
d = (Z) - (CZ); if (d < 0) d = (CZ) - (Z); \
if ((RZ) < d) A = 0; \
else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \
d = (Y) - (CY); if (d < 0) d = (CY) - (Y); \
if ((RY) < d) A = 0; \
else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \
}
#define CLAMP80(C, A0, A1, A2, A3) C = (A0) + (A1) + (A2) + (A3) + 0x10; if ((C) > 0x80) C = 0x80
void func_8017BF14(s32 arg0, s32 lim)
{
typedef struct { u32 w0, w1, w2; } Prim;
extern u8 *D_800A5E60;
extern u8 D_800A6610[];
extern u8 D_800AF630[];
DVECTOR2 tmpxy[4];
SVECTOR2 box[8];
SVECTOR2 sxy[8];
struct { long otz, flag, opz, sz0, sz1, sz2, sz3; } g;
s32 j;
u32 i;
u8 *vd;
u32 ot;
u8 *pkt;
register s32 f0 __asm__("$19");
s32 f1, f2, f3;
s16 x3, z3, y3;
Prim *prim;
u32 nprim;
u8 *vtx;
s32 nparts;
Part *part;
s16 lo0x, hi0x, lo0y, hi0y, lo0z, hi0z;
s16 lo1x, hi1x, lo1y, hi1y, lo1z, hi1z;
s16 lo2x, hi2x, lo2y, hi2y, lo2z, hi2z;
s16 lo3x, hi3x, lo3y, hi3y, lo3z, hi3z;
s16 cx0, cy0, cz0, cx1, cy1, cz1, cx2, cy2, cz2, cx3, cy3, cz3;
s16 r0;
s16 r1;
s16 r2;
s16 r3;
s16 r0lo;
s16 r1lo;
s16 r2lo;
s16 r3lo;
register u8 *va __asm__("$10");
u8 *vb, *vc;
register u32 w __asm__("$5");
s32 code;
u32 vw, vzw;
u32 wx, wy, wz;
s32 xa32, xb32, t32;
s32 xmn1, xmx1, xmn2, xmx2;
s32 mnc, mxc;
s16 my, mny, mx, mn;
u8 *base;
s16 x0, y0, z0, x1, y1, z1, x2, y2, z2;
s32 a0v, a1v, a2v, a3v;
s32 d;
u32 *tp;
u32 uvw;
u32 cb;
base = D_800AF630;
r2lo = 0;
r1lo = 0;
r0lo = 0;
r2 = 0;
r1 = 0;
r0 = 0;
if (D_80197C28) {
cx0 = D_80197C2C;
cy0 = D_80197C2E;
r0lo = D_80197C34 - 0x200;
r0 = D_80197C34;
cz0 = D_80197C30;
} else {
cz0 = 0x6000;
cy0 = 0x6000;
cx0 = 0x6000;
}
if (D_80197C44) {
cx1 = D_80197C48;
cy1 = D_80197C4A;
r1 = D_80197C50;
r1lo = D_80197C50 - 0x200;
cz1 = D_80197C4C;
} else {
cz1 = 0x6000;
cy1 = 0x6000;
cx1 = 0x6000;
}
if (D_80197C60) {
cx2 = D_80197C64;
cy2 = D_80197C66;
r2 = D_80197C6C;
r2lo = D_80197C6C - 0x200;
cz2 = D_80197C68;
} else {
cz2 = 0x6000;
cy2 = 0x6000;
cx2 = 0x6000;
}
if (D_80197C7C) {
cx3 = D_80197C80;
cy3 = D_80197C82;
r3lo = r2 - 0x200;
r3 = D_80197C88;
cz3 = D_80197C84;
} else {
cz3 = 0x6000;
cy3 = 0x6000;
cx3 = 0x6000;
}
lo0x = cx0 - r0; hi0x = cx0 + r0;
lo0y = cy0 - r0; hi0y = cy0 + r0;
lo0z = cz0 - r0; hi0z = cz0 + r0;
lo1x = cx1 - r1; hi1x = cx1 + r1;
lo1y = cy1 - r1; hi1y = cy1 + r1;
lo1z = cz1 - r1; hi1z = cz1 + r1;
lo2x = cx2 - r2; hi2x = cx2 + r2;
lo2y = cy2 - r2; hi2y = cy2 + r2;
lo2z = cz2 - r2; hi2z = cz2 + r2;
lo3x = cx3 - r3; hi3x = cx3 + r3;
lo3y = cy3 - r3; hi3y = cy3 + r3;
lo3z = cz3 - r3; hi3z = cz3 + r3;
pkt = D_800A5E60;
part = *(Part **)(arg0 + 0xC);
nparts = *(s32 *)(*(s32 *)(arg0 + 8) + 8);
vtx = *(u8 **)(*(s32 *)(arg0 + 8) + 0x10);
ot = (u32)&D_800A6610[(*(u16 *)(base + 0xA3D2)) << 14];
for (j = 0; j < nparts; j++, part++) {
wx = part->xx;
mn = wx;
mx = wx >> 16;
wy = part->yy;
mny = wy;
my = wy >> 16;
wz = part->zz;
box[0].vx = mn; box[0].vy = mny;
box[1].vx = mx; box[1].vy = mny;
box[2].vx = mn; box[2].vy = mny;
box[3].vx = mx; box[3].vy = mny;
box[4].vx = mn; box[4].vy = my;
box[5].vx = mx; box[5].vy = my;
box[6].vx = mn; box[6].vy = my;
box[7].vx = mx; box[7].vy = my;
wy = wz >> 16;
box[0].vz = wz;
box[1].vz = wz;
box[4].vz = wz;
box[5].vz = wz;
box[2].vz = wy;
box[3].vz = wy;
box[6].vz = wy;
box[7].vz = wy;
gte_ldv3c(&box[0]);
gte_rtpt();
gte_stsxy3(&sxy[0], &sxy[1], &sxy[2]);
gte_ldv0(&box[3]);
gte_rtps();
gte_stsxy(&sxy[3]);
gte_ldv3c(&box[4]);
gte_rtpt();
gte_stsxy3(&sxy[4], &sxy[5], &sxy[6]);
gte_ldv0(&box[7]);
gte_rtps();
gte_stsxy(&sxy[7]);
gte_stszotz(&g.otz);
if (lim >= g.otz) {
xa32 = sxy[0].vx;
xb32 = sxy[1].vx;
if (xb32 < xa32) { xmx1 = xa32; xmn1 = xb32; } else { xmn1 = xa32; xmx1 = xb32; }
t32 = sxy[2].vx;
if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32;
t32 = sxy[3].vx;
if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32;
xa32 = sxy[4].vx;
xb32 = sxy[5].vx;
if (xb32 < xa32) { xmx2 = xa32; xmn2 = xb32; } else { xmn2 = xa32; xmx2 = xb32; }
t32 = sxy[6].vx;
if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32;
t32 = sxy[7].vx;
if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32;
mnc = xmn1;
if (xmn2 < xmn1) mnc = xmn2;
mxc = xmx1;
if (mxc < xmx2) mxc = xmx2;
if ((s16)mxc >= -0xA0 && (s16)mnc < 0xA1) {
xa32 = sxy[0].vy;
xb32 = sxy[1].vy;
if (xb32 < xa32) { xmx1 = xa32; xmn1 = xb32; } else { xmn1 = xa32; xmx1 = xb32; }
t32 = sxy[2].vy;
if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32;
t32 = sxy[3].vy;
if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32;
xa32 = sxy[4].vy;
xb32 = sxy[5].vy;
if (xb32 < xa32) { xmx2 = xa32; xmn2 = xb32; } else { xmn2 = xa32; xmx2 = xb32; }
t32 = sxy[6].vy;
if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32;
t32 = sxy[7].vy;
if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32;
mnc = xmn1;
if (xmn2 < xmn1) mnc = xmn2;
mxc = xmx1;
if (mxc < xmx2) mxc = xmx2;
if ((s16)mxc >= -0x6E && (s16)mnc < 0x6F) {
nprim = part->nprim;
prim = (Prim *)part->prim;
for (i = 0; i < nprim; i++, prim++) {
w = prim->w1;
va = vtx + (w & 0xFFFF);
vb = vtx + (w >> 16);
w = prim->w2;
vc = vtx + (w & 0xFFFF);
w = w >> 16;
gte_ldv3(va, vb, vc);
gte_rtpt();
gte_stflg(&g.flag);
if (!(g.flag & 0x7F85E000)) {
gte_nclip();
code = w & 7;
vd = vtx + (w & 0xFFF8);
gte_stopz(&g.opz);
if (g.opz > 0) {
switch (code) {
case 6:
case 7:
/* ---------------- TRI (FT3 / GT3) ---------------- */
gte_stsxy3c(&tmpxy[0]);
gte_stsz3(&g.sz0, &g.sz1, &g.sz2);
if (tmpxy[0].vx > tmpxy[1].vx) { mx = tmpxy[0].vx; mn = tmpxy[1].vx; }
else { mn = tmpxy[0].vx; mx = tmpxy[1].vx; }
if (tmpxy[2].vx > mx) mx = tmpxy[2].vx;
else if (tmpxy[2].vx < mn) mn = tmpxy[2].vx;
if (mx >= -0xA0 && mn < 0xA1) {
if (tmpxy[0].vy > tmpxy[1].vy) { my = tmpxy[0].vy; mny = tmpxy[1].vy; }
else { mny = tmpxy[0].vy; my = tmpxy[1].vy; }
if (tmpxy[2].vy > my) my = tmpxy[2].vy;
else if (tmpxy[2].vy < mny) mny = tmpxy[2].vy;
if (my >= -0x6E && mny < 0x6F) {
s32 za, zb;
register s32 c1 __asm__("$4"); s32 c0, c2, c3;
__asm__ __volatile__ ("" :: "r" (mny));
if (g.sz0 > g.sz1) { za = g.sz0; if (za < g.sz2) za = g.sz2; }
else { za = g.sz1; if (za < g.sz2) za = g.sz2; }
g.opz = za;
f3 = 0; f2 = 0; f1 = 0; f0 = 0;
vw = *(u32 *)va;
vzw = *(u32 *)(va + 4);
x0 = vw; y0 = vw >> 16; z0 = vzw;
BOXTEST(f0, x0, y0, z0, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z);
BOXTEST(f1, x0, y0, z0, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z);
BOXTEST(f2, x0, y0, z0, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z);
BOXTEST(f3, x0, y0, z0, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z);
vw = *(u32 *)vb;
vzw = *(u32 *)(vb + 4);
x1 = vw; y1 = vw >> 16; z1 = vzw;
BOXTEST(f0, x1, y1, z1, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z);
BOXTEST(f1, x1, y1, z1, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z);
BOXTEST(f2, x1, y1, z1, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z);
BOXTEST(f3, x1, y1, z1, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z);
vw = *(u32 *)vc;
vzw = *(u32 *)(vc + 4);
x2 = vw; y2 = vw >> 16; z2 = vzw;
BOXTEST(f0, x2, y2, z2, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z);
BOXTEST(f1, x2, y2, z2, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z);
BOXTEST(f2, x2, y2, z2, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z);
BOXTEST(f3, x2, y2, z2, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z);
if (f0 | f1 | f2 | f3) {
u32 *otp;
u32 rgbw;
ATTEN(a0v, f0, x0, y0, z0, cx0, cy0, cz0, r0, r0lo);
ATTEN(a1v, f1, x0, y0, z0, cx1, cy1, cz1, r1, r1lo);
ATTEN(a2v, f2, x0, y0, z0, cx2, cy2, cz2, r2, r2lo);
ATTEN3(a3v, f3, x0, y0, z0, cx3, cy3, cz3, r3, r3lo, r2, r2);
CLAMP80(c0, a0v, a1v, a2v, a3v);
ATTEN(a0v, f0, x1, y1, z1, cx0, cy0, cz0, r0, r0lo);
ATTEN(a1v, f1, x1, y1, z1, cx1, cy1, cz1, r1, r1lo);
ATTEN(a2v, f2, x1, y1, z1, cx2, cy2, cz2, r2, r2lo);
ATTEN3(a3v, f3, x1, y1, z1, cx3, cy3, cz3, r3, r3lo, r3, r3);
CLAMP80(c1, a0v, a1v, a2v, a3v);
ATTEN(a0v, f0, x2, y2, z2, cx0, cy0, cz0, r0, r0lo);
ATTEN(a1v, f1, x2, y2, z2, cx1, cy1, cz1, r1, r1lo);
ATTEN(a2v, f2, x2, y2, z2, cx2, cy2, cz2, r2, r2lo);
ATTEN3(a3v, f3, x2, y2, z2, cx3, cy3, cz3, r3, r3lo, r2, r3);
CLAMP80(c2, a0v, a1v, a2v, a3v);
*(u32 *)&((PolyGT3 *)pkt)->x0 = *(u32 *)&tmpxy[0];
*(u32 *)&((PolyGT3 *)pkt)->x1 = *(u32 *)&tmpxy[1];
*(u32 *)&((PolyGT3 *)pkt)->x2 = *(u32 *)&tmpxy[2];
tp = (u32 *)prim->w0;
cb = 0x34000000;
rgbw = (c0 | cb) | (c0 << 8) | (c0 << 16);
((PolyGT3 *)pkt)->rgb0 = rgbw;
rgbw = (c1 | cb) | (c1 << 8) | (c1 << 16);
((PolyGT3 *)pkt)->rgb1 = rgbw;
rgbw = (c2 | cb) | (c2 << 8) | (c2 << 16);
((PolyGT3 *)pkt)->rgb2 = rgbw;
((PolyGT3 *)pkt)->uv0 = tp[1];
((PolyGT3 *)pkt)->uv1 = tp[2];
((PolyGT3 *)pkt)->uv2 = tp[3];
otp = (u32 *)(((g.opz >> 2) << 2) + ot);
*(u32 *)pkt = (*otp & 0xFFFFFF) | 0x9000000;
*otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF);
pkt += 0x28;
} else {
u32 *otp;
u32 rgbw;
*(u32 *)&((PolyFT3 *)pkt)->x0 = *(u32 *)&tmpxy[0];
*(u32 *)&((PolyFT3 *)pkt)->x1 = *(u32 *)&tmpxy[1];
*(u32 *)&((PolyFT3 *)pkt)->x2 = *(u32 *)&tmpxy[2];
tp = (u32 *)prim->w0;
cb = tp[0] & 0xFF000000;
cb |= 0x101010;
((PolyFT3 *)pkt)->rgbc = cb;
((PolyFT3 *)pkt)->uvc0 = tp[1];
((PolyFT3 *)pkt)->uvp1 = tp[2];
((PolyFT3 *)pkt)->uv2 = tp[3];
otp = (u32 *)(((g.opz >> 2) << 2) + ot);
*(u32 *)pkt = (*otp & 0xFFFFFF) | 0x7000000;
*otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF);
pkt += 0x20;
}
}
}
break;
case 2:
case 3:
/* ---------------- QUAD (FT4 / GT4) ---------------- */
gte_stsxy3c(&tmpxy[0]);
gte_ldv0(vd);
gte_rtps();
if (tmpxy[0].vx > tmpxy[1].vx) { mx = tmpxy[0].vx; mn = tmpxy[1].vx; }
else { mn = tmpxy[0].vx; mx = tmpxy[1].vx; }
if (tmpxy[2].vx > mx) mx = tmpxy[2].vx;
else if (tmpxy[2].vx < mn) mn = tmpxy[2].vx;
if (tmpxy[0].vy > tmpxy[1].vy) { my = tmpxy[0].vy; mny = tmpxy[1].vy; }
else { mny = tmpxy[0].vy; my = tmpxy[1].vy; }
if (tmpxy[2].vy > my) my = tmpxy[2].vy;
else if (tmpxy[2].vy < mny) mny = tmpxy[2].vy;
gte_stflg(&g.flag);
if (!(g.flag & 0x7F85E000)) {
gte_stsz4(&g.sz0, &g.sz1, &g.sz2, &g.sz3);
gte_stsxy((long *)&((PolyFT4 *)pkt)->x3);
if (((PolyFT4 *)pkt)->x3 < mn) mn = ((PolyFT4 *)pkt)->x3;
else if (mx < ((PolyFT4 *)pkt)->x3) mx = ((PolyFT4 *)pkt)->x3;
if (mx >= -0xA0 && mn < 0xA1) {
if (((PolyFT4 *)pkt)->y3 < mny) mny = ((PolyFT4 *)pkt)->y3;
else if (my < ((PolyFT4 *)pkt)->y3) my = ((PolyFT4 *)pkt)->y3;
if (my >= -0x6E && mny < 0x6F) {
s32 za, zb;
register s32 c1 __asm__("$4"); s32 c0, c2, c3;
zb = g.sz2;
if (zb < g.sz3) zb = g.sz3;
za = g.sz0;
if (za < g.sz1) za = g.sz1;
if (za < zb) za = zb;
g.opz = za;
f3 = 0; f2 = 0; f1 = 0; f0 = 0;
vw = *(u32 *)va;
vzw = *(u32 *)(va + 4);
x0 = vw; y0 = vw >> 16; z0 = vzw;
BOXTEST(f0, x0, y0, z0, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z);
BOXTEST(f1, x0, y0, z0, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z);
BOXTEST(f2, x0, y0, z0, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z);
BOXTEST(f3, x0, y0, z0, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z);
vw = *(u32 *)vb;
vzw = *(u32 *)(vb + 4);
x1 = vw; y1 = vw >> 16; z1 = vzw;
BOXTEST(f0, x1, y1, z1, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z);
BOXTEST(f1, x1, y1, z1, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z);
BOXTEST(f2, x1, y1, z1, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z);
BOXTEST(f3, x1, y1, z1, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z);
vw = *(u32 *)vc;
vzw = *(u32 *)(vc + 4);
x2 = vw; y2 = vw >> 16; z2 = vzw;
BOXTEST(f0, x2, y2, z2, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z);
BOXTEST(f1, x2, y2, z2, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z);
BOXTEST(f2, x2, y2, z2, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z);
BOXTEST(f3, x2, y2, z2, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z);
vw = *(u32 *)vd;
vzw = *(u32 *)(vd + 4);
x3 = vw; y3 = vw >> 16; z3 = vzw;
BOXTEST(f0, x3, y3, z3, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z);
BOXTEST(f1, x3, y3, z3, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z);
BOXTEST(f2, x3, y3, z3, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z);
BOXTEST(f3, x3, y3, z3, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z);
if (f0 | f1 | f2 | f3) {
u32 *otp;
u32 rgbw;
ATTEN(a0v, f0, x0, y0, z0, cx0, cy0, cz0, r0, r0lo);
ATTEN(a1v, f1, x0, y0, z0, cx1, cy1, cz1, r1, r1lo);
ATTEN(a2v, f2, x0, y0, z0, cx2, cy2, cz2, r2, r2lo);
ATTEN3(a3v, f3, x0, y0, z0, cx3, cy3, cz3, r3, r3lo, r3, r3);
CLAMP80(c0, a0v, a1v, a2v, a3v);
ATTEN(a0v, f0, x1, y1, z1, cx0, cy0, cz0, r0, r0lo);
ATTEN(a1v, f1, x1, y1, z1, cx1, cy1, cz1, r1, r1lo);
ATTEN(a2v, f2, x1, y1, z1, cx2, cy2, cz2, r2, r2lo);
ATTEN3(a3v, f3, x1, y1, z1, cx3, cy3, cz3, r3, r3lo, r3, r3);
CLAMP80(c1, a0v, a1v, a2v, a3v);
ATTEN(a0v, f0, x2, y2, z2, cx0, cy0, cz0, r0, r0lo);
ATTEN(a1v, f1, x2, y2, z2, cx1, cy1, cz1, r1, r1lo);
ATTEN(a2v, f2, x2, y2, z2, cx2, cy2, cz2, r2, r2lo);
ATTEN3(a3v, f3, x2, y2, z2, cx3, cy3, cz3, r3, r3lo, r3, r3);
CLAMP80(c2, a0v, a1v, a2v, a3v);
ATTEN(a0v, f0, x3, y3, z3, cx0, cy0, cz0, r0, r0lo);
ATTEN(a1v, f1, x3, y3, z3, cx1, cy1, cz1, r1, r1lo);
ATTEN(a2v, f2, x3, y3, z3, cx2, cy2, cz2, r2, r2lo);
ATTEN3(a3v, f3, x3, y3, z3, cx3, cy3, cz3, r3, r3lo, r3, r3);
CLAMP80(c3, a0v, a1v, a2v, a3v);
*(u32 *)&((PolyGT4 *)pkt)->x0 = *(u32 *)&tmpxy[0];
*(u32 *)&((PolyGT4 *)pkt)->x1 = *(u32 *)&tmpxy[1];
*(u32 *)&((PolyGT4 *)pkt)->x2 = *(u32 *)&tmpxy[2];
gte_stsxy((long *)&((PolyGT4 *)pkt)->x3);
tp = (u32 *)prim->w0;
cb = 0x3C000000;
rgbw = c0 | cb; rgbw |= c0 << 8; rgbw |= c0 << 16;
((PolyGT4 *)pkt)->rgb0 = rgbw;
rgbw = c1 | cb; rgbw |= c1 << 8; rgbw |= c1 << 16;
((PolyGT4 *)pkt)->rgb1 = rgbw;
rgbw = c2 | cb; rgbw |= c2 << 8; rgbw |= c1 << 16;
((PolyGT4 *)pkt)->rgb2 = rgbw;
rgbw = c3 | cb; rgbw |= c3 << 8; rgbw |= c1 << 16;
((PolyGT4 *)pkt)->rgb3 = rgbw;
((PolyGT4 *)pkt)->uv0 = tp[1];
((PolyGT4 *)pkt)->uv1 = tp[2];
uvw = tp[3];
((PolyGT4 *)pkt)->uv2 = uvw;
((PolyGT4 *)pkt)->uv3 = uvw >> 16;
otp = (u32 *)(((g.opz >> 2) << 2) + ot);
*(u32 *)pkt = (*otp & 0xFFFFFF) | 0xC000000;
*otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF);
pkt += 0x34;
} else {
u32 *otp;
u32 rgbw;
*(u32 *)&((PolyFT4 *)pkt)->x0 = *(u32 *)&tmpxy[0];
*(u32 *)&((PolyFT4 *)pkt)->x1 = *(u32 *)&tmpxy[1];
*(u32 *)&((PolyFT4 *)pkt)->x2 = *(u32 *)&tmpxy[2];
tp = (u32 *)prim->w0;
cb = tp[0] & 0xFF000000;
cb |= 0x101010;
((PolyFT4 *)pkt)->rgbc = cb;
((PolyFT4 *)pkt)->uvc0 = tp[1];
((PolyFT4 *)pkt)->uvp1 = tp[2];
uvw = tp[3];
((PolyFT4 *)pkt)->uv2 = uvw;
((PolyFT4 *)pkt)->uv3 = uvw >> 16;
otp = (u32 *)(((g.opz >> 2) << 2) + ot);
*(u32 *)pkt = (*otp & 0xFFFFFF) | 0x9000000;
*otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF);
pkt += 0x28;
}
}
}
}
break;
}
}
}
}
}
}
}
}
D_800A5E60 = pkt;
}
+767
View File
@@ -0,0 +1,767 @@
#include "common.h"
#include "/home/musashi/bfm-decomp/src/shared/engine_types.h"
/* ===========================================================================
* func_8017BF14 -- 4,763 ins, ov_SC03_116 (behemoth #4). COLD START.
*
* STATUS (2026-07-25, session 20, gcc-2.7.2 pinned triple):
* python3 tools/match_one.py func_8017BF14 --c <this file> \
* --asm-subdir asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C
* -> DIFF 4763/4763 ins, 789 mismatched [PIN-FREE VARIANT]
* LENGTH EXACT | OPCODE HISTOGRAM EXACT (L1 = 0) | STACK FRAME EXACT
* (all 127 slots at the target's offsets, frame 0x360)
* register-masked structural alignment 4760/4763 = 99.94%
* NOT A MATCH. The whole-binary SHA1 arbiter (G3/P9) has NOT been run.
* Pin-free fallback (see LEVERS L6..L9): 4763/4763, 789 mismatched.
*
* WHAT IT IS
* The *four*-light-box variant of the volumetric-light renderer whose
* 3-box sibling func_8017D960 (3,338 ins, ov_SC03_090) is MATCHED, and whose
* unlit ancestor func_8017BEBC (ov_SC03_099) is MATCHED. Same family, same
* skeleton; this is the biggest member.
*
* Signature: func_8017BF14(s32 arg0, s32 lim). Unlike every other member of
* the family this one is a LEAF -- 0 callees. The 3-call prologue
* (func_800491EC / func_800547D8 / func_80052E38) of the siblings is gone;
* `lim` arrives as arg1 (spilled to 0xB0). That is why the frame has no
* 0x10 argument area (tmpxy[] starts at sp+0x00) and no $ra save.
*
* Per part (stride 0x14, outer loop): build the 8-corner AABB in box[],
* rtpt/rtpt + rtps/rtps -> sxy[8], stszotz -> g.otz, reject on
* `lim >= g.otz`, then screen-space bbox reject on X (-0xA0..0xA1) and
* Y (-0x6E..0x6F).
* Per prim (stride 0xC, inner loop): rtpt the 3 vertices, stflg mask
* 0x7F85E000, nclip, stopz > 0, then a 4-way range tree on `code = w & 7`
* that keeps ONLY codes 6,7 (tri) and 2,3 (quad); 0,1,4,5 fall through to
* the loop tail.
* Per drawn poly: screen bbox reject, then each vertex is tested against
* FOUR axis-aligned light boxes (flags f0..f3), and if any is lit a
* 0x00..0x80 attenuation per active box is computed, summed, biased +0x10
* and clamped to 0x80 -> a grey gouraud vertex colour.
* lit -> POLY_GT3 (0x28, tag 0x34000000, OT 0x9000000)
* POLY_GT4 (0x34, tag 0x3C000000, OT 0xC000000)
* unlit -> POLY_FT3 (0x20, OT 0x7000000) / POLY_FT4 (0x28, OT 0x9000000)
* with rgbc = (tp[0] & 0xFF000000) | 0x101010 <-- NOT black,
* unlike func_8017D960 where the unlit colour is plain black.
*
* THE FOUR LIGHT BOXES (stride 0x1C, {s32 enable; u16 cx,cy,cz; s32 range})
* D_80197C28 / D_80197C44 / D_80197C60 / D_80197C7C.
* Falloff geometry differs from the 3-box sibling: RLO = R - 0x200 (not
* -0x80) and the ramp is ((R - d) / 4) (not (R - d)), so the 0x80 ceiling is
* reached over a 0x200-wide band instead of 0x80. The `/ 4` is a SIGNED
* divide -- `bgez / addiu 3 / sra 2` -- not a shift.
*
* THREE ORIGINAL-SOURCE COPY-PASTE ARTEFACTS, all byte-proven (see report)
* (1) `r3lo = r2 - 0x200;` -- box 3's low radius is derived from box 2's
* RANGE VARIABLE, not from its own D_80197C88. Proven by the target's
* `addiu $t6, $s0, -0x200` reusing the register that box 2's `lw` filled;
* spelling it `D_80197C6C - 0x200` re-loads the global (+2 ins).
* (2) Only SIX of the eight radius variables are zero-initialised
* (r0,r1,r2,r0lo,r1lo,r2lo) -- r3/r3lo are left uninitialised, exactly
* the init list the 3-box version needed.
* (3) The ATTEN body is written out LONGHAND 7 times (3 tri vertices +
* 4 quad vertices). When box 3 was bolted on, the `R` of the z- and
* y-axis KILL tests was left as r2 in three of those copies:
* tri v0: z and y use r2 tri v2: z uses r2 all others use r3.
* Proven by the 24 `sll $v0,$s0,16` sites: 3 per group in box 2 plus
* exactly three extra at idx 1471, 1504 (tri v0) and 2178 (tri v2).
* (4) In the QUAD lit arm only, rgb2 and rgb3 take their `<< 16` term from
* c1, not from c2/c3. Proven by the target CSE-ing ONE
* `sll $a0, $a0, 16` and re-using $a0 for all three stores.
* These are not guesses: each was read off the target and each removed a
* measured instruction-count or opcode-histogram delta.
*
* FRAME (0x360, leaf -- no $ra, no argument area)
* 0x000 tmpxy[4] | 0x010 box[8] | 0x050 sxy[8] |
* 0x090 g{otz,flag,opz,sz0..sz3} | 0x0B0 lim | 0x0B8 j | 0x0C0 i |
* 0x0C8 vd | 0x0D0 ot | 0x0D8 pkt | 0x0E0 f2 | 0x0E8 f3 |
* 0x0F0/0x0F8/0x100 x3,z3,y3 | 0x108 prim | 0x110 nprim | 0x118 vtx |
* 0x120 nparts | 0x128 part | 0x130..0x1D0 lo/hi bounds (21 s16 slots) |
* 0x1D8..0x230 cx0..cz3 (12) | 0x238 r0 | 0x240 r1lo | 0x248 r2lo |
* 0x250 r3lo | 0x288..0x2D0 the LICM-hoisted sign-extended bounds |
* 0x328/0x330 spilled vertex coords | 0x338..0x358 s0-s7,fp.
* *** THE SLOT ORDER IS THE DECLARATION-ORDER ORACLE (see L5). ***
*
* ---------------------------------------------------------------------------
* THE LEVERS, each with its MEASURED effect (byte-identical %, anchored)
*
* L1 `cb = (tp[0] & 0xFF000000) | 0x101010;` in both UNLIT arms.
* Was plain black (copied from the 3-box sibling). -64 -> -62 length,
* and it retired the whole or/ori/lui histogram delta.
* L2 box-3 ATTEN kill-register per copy (artefact 3 above) + `r3lo = r2 -
* 0x200` (artefact 1). 89.15% -> 96.96% shape; killed sra+11 / sll+9.
* L3 quad-lit rgb2/rgb3 use `c1 << 16` (artefact 4). Killed the last sll+2.
* L4 **`s32 c0, c1, c2, c3;` DECLARED INSIDE THE TWO CULL BLOCKS**, not at
* function scope. THE SINGLE BIGGEST LEVER: 52.26% -> 92.86% byte, and it
* is what finally spills r1lo (21 reloads + 21 nops, the entire -62
* residual length). Cookbook Sec.76 exactly: at function scope c0..c3 have
* a death in each of the four emit arms, so local-alloc.c:472
* (REG_BASIC_BLOCK >= 0 && REG_N_DEATHS == 1) refuses them; per-cull-block
* they are 1-death local pseudos, local-alloc places them, and via
* global.c:668-671 those placements remove registers from the GLOBAL pool
* -- which is what pushes r1lo out of $fp and onto the stack.
* An `__asm__` ref-dial on mn/mx/my/mny reached the same spill (84.78%)
* but is strictly worse AND artificial. Declaration scope wins.
* L5 **`s32 f0, f1, f2, f3;` MOVED TO IMMEDIATELY AFTER `u8 *pkt;`.**
* 73.00% -> 83.98% byte. Reason (and the reusable trick): spilled pseudos
* get stack slots in PSEUDO-NUMBER order, and pseudo numbers are assigned
* in DECLARATION order -- so the target's stack-slot layout is a direct
* read-out of its declaration order. The target has f2 at 0xE0 and f3 at
* 0xE8, i.e. between `pkt` (0xD8) and `x3,z3,y3` (0xF0..0x100). After this
* one move ALL 127 stack slots agree with the target exactly.
* L6 `u32 rgbw;` per emit arm (+0.6%).
* L7 RC-15 zero-byte ref dial on `mny`, first statement of the TRI cull block
* -- identical to func_8017D960's L4, same cause: `mny` is defined first
* but outlives `my`, so allocno_compare (global.c:594) ranks `my` above it
* and `my` takes the lower $a2. The dial flips it to the target's
* mny->$a2 / my->$a3. 93.47% -> 94.21%.
* L8 `cb` as a 2-statement accumulator; quad-lit rgb word as a 3-statement
* accumulator (+0.1% together).
* L9 FOUR REGISTER PINS: va->$t2, w->$a1, f0->$s3, c1->$a0.
* 94.21% -> 99.06%. Sec.72/Sec.74 note: this function has NO `jal`, so the
* caller-saved-pin-spanning-a-call hazard cannot arise here -- that is why
* pins are usable on this member of the family and were a trap on the
* others. Pins remain PREFERENCES: adding a 5th and 6th (c0->$t4,
* c2->$t2) made it WORSE (92.86%), which is Sec.72 reproduced.
* The PIN-FREE draft is kept at .run/giants/s19_func_8017BF14_b1_pinfree.c
* (4763/4763, 789 mismatched, 100.0% structural) for anyone who wants the
* safe variant.
*
* REMAINING RESIDUAL (45 instructions, 0.9%): three register-grant ties --
* the `w & 0xFFFF` / `w >> 16` producer temps that local-alloc combine_regs
* ties into the pinned `va`; the c0/c2 grants ($t4/$t2 vs $t2/$a2); and the
* lit rgb accumulator $v1 vs $v0 in the quad arm with its 3 one-slot
* store/shift transpositions. See .run/giants/s19_bf14_report.md.
* =========================================================================== */
#define gte_ldv0(r0) __asm__ volatile ( \
"lwc2 $0, 0( %0 );" \
"lwc2 $1, 4( %0 )" \
: \
: "r"( r0 ) )
#define gte_ldv3(r0, r1, r2) __asm__ volatile ( \
"lwc2 $0, 0( %0 );" \
"lwc2 $1, 4( %0 );" \
"lwc2 $2, 0( %1 );" \
"lwc2 $3, 4( %1 );" \
"lwc2 $4, 0( %2 );" \
"lwc2 $5, 4( %2 )" \
: \
: "r"( r0 ), "r"( r1 ), "r"( r2 ) )
#define gte_ldv3c(r0) __asm__ volatile ( \
"lwc2 $0, 0( %0 );" \
"lwc2 $1, 4( %0 );" \
"lwc2 $2, 8( %0 );" \
"lwc2 $3, 12( %0 );" \
"lwc2 $4, 16( %0 );" \
"lwc2 $5, 20( %0 )" \
: \
: "r"( r0 ) )
#define gte_rtps() __asm__ volatile ("nop;nop;rtps")
#define gte_rtpt() __asm__ volatile ("nop;nop;rtpt")
#define gte_nclip() __asm__ volatile ("nop;nop;nclip")
#define gte_stsxy(r0) __asm__ volatile ( \
"swc2 $14, 0( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
#define gte_stsxy3(r0, r1, r2) __asm__ volatile ( \
"swc2 $12, 0( %0 );" \
"swc2 $13, 0( %1 );" \
"swc2 $14, 0( %2 )" \
: \
: "r"( r0 ), "r"( r1 ), "r"( r2 ) \
: "memory" )
#define gte_stsxy3c(r0) __asm__ volatile ( \
"swc2 $12, 0( %0 );" \
"swc2 $13, 4( %0 );" \
"swc2 $14, 8( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
#define gte_stsz3(r0, r1, r2) __asm__ volatile ( \
"swc2 $17, 0( %0 );" \
"swc2 $18, 0( %1 );" \
"swc2 $19, 0( %2 )" \
: \
: "r"( r0 ), "r"( r1 ), "r"( r2 ) \
: "memory" )
#define gte_stsz4(r0, r1, r2, r3) __asm__ volatile ( \
"swc2 $16, 0( %0 );" \
"swc2 $17, 0( %1 );" \
"swc2 $18, 0( %2 );" \
"swc2 $19, 0( %3 )" \
: \
: "r"( r0 ), "r"( r1 ), "r"( r2 ), "r"( r3 ) \
: "memory" )
#define gte_stszotz(r0) __asm__ volatile ( \
"mfc2 $12, $19;" \
"nop;" \
"sra $12, $12, 2;" \
"sw $12, 0( %0 )" \
: \
: "r"( r0 ) \
: "$12", "memory" )
#define gte_stflg(r0) __asm__ volatile ( \
"cfc2 $12, $31;" \
"nop;" \
"sw $12, 0( %0 )" \
: \
: "r"( r0 ) \
: "$12", "memory" )
#define gte_stopz(r0) __asm__ volatile ( \
"swc2 $24, 0( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
/* ---- the two gouraud-textured packet layouts this function emits ---------- */
typedef struct {
u32 tag;
u32 rgb0; s16 x0, y0; u32 uv0;
u32 rgb1; s16 x1, y1; u32 uv1;
u32 rgb2; s16 x2, y2; u16 uv2, p2;
} PolyGT3; /* 0x28 */
typedef struct {
u32 tag;
u32 rgb0; s16 x0, y0; u32 uv0;
u32 rgb1; s16 x1, y1; u32 uv1;
u32 rgb2; s16 x2, y2; u16 uv2, p2;
u32 rgb3; s16 x3, y3; u16 uv3, p3;
} PolyGT4; /* 0x34 */
/* ---- the four light-volume descriptors (stride 0x1C) --------------------- */
extern s32 D_80197C28;
extern u16 D_80197C2C, D_80197C2E, D_80197C30;
extern s32 D_80197C34;
extern s32 D_80197C44;
extern u16 D_80197C48, D_80197C4A, D_80197C4C;
extern s32 D_80197C50;
extern s32 D_80197C60;
extern u16 D_80197C64, D_80197C66, D_80197C68;
extern s32 D_80197C6C;
extern s32 D_80197C7C;
extern u16 D_80197C80, D_80197C82, D_80197C84;
extern u16 D_80197C88;
/* ---- the box-containment test for one vertex against one light box ------- */
#define BOXTEST(F, X, Y, Z, LX, HX, LY, HY, LZ, HZ) \
if ((LX) < (X) && (X) < (HX) && (LY) < (Y) && (Y) < (HY) && (LZ) < (Z) && (Z) < (HZ)) F = 1
/* ---- the separable per-axis falloff, visited in x, z, y order ------------ */
#define ATTEN(A, F, X, Y, Z, CX, CY, CZ, R, RLO) \
A = 0; \
if (F) { \
d = (X) - (CX); if (d < 0) d = (CX) - (X); \
if (d < (R)) { A = 0x80; if (d >= (RLO)) A = ((R) - d) / 4; } \
d = (Z) - (CZ); if (d < 0) d = (CZ) - (Z); \
if ((R) < d) A = 0; \
else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \
d = (Y) - (CY); if (d < 0) d = (CY) - (Y); \
if ((R) < d) A = 0; \
else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \
}
#define ATTEN3(A, F, X, Y, Z, CX, CY, CZ, R, RLO, RZ, RY) \
A = 0; \
if (F) { \
d = (X) - (CX); if (d < 0) d = (CX) - (X); \
if (d < (R)) { A = 0x80; if (d >= (RLO)) A = ((R) - d) / 4; } \
d = (Z) - (CZ); if (d < 0) d = (CZ) - (Z); \
if ((RZ) < d) A = 0; \
else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \
d = (Y) - (CY); if (d < 0) d = (CY) - (Y); \
if ((RY) < d) A = 0; \
else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \
}
#define CLAMP80(C, A0, A1, A2, A3) C = (A0) + (A1) + (A2) + (A3) + 0x10; if ((C) > 0x80) C = 0x80
void func_8017BF14(s32 arg0, s32 lim)
{
typedef struct { u32 w0, w1, w2; } Prim;
extern u8 *D_800A5E60;
extern u8 D_800A6610[];
extern u8 D_800AF630[];
DVECTOR2 tmpxy[4];
SVECTOR2 box[8];
SVECTOR2 sxy[8];
struct { long otz, flag, opz, sz0, sz1, sz2, sz3; } g;
s32 j;
u32 i;
u8 *vd;
u32 ot;
u8 *pkt;
s32 f0, f1, f2, f3;
s16 x3, z3, y3;
Prim *prim;
u32 nprim;
u8 *vtx;
s32 nparts;
Part *part;
s16 lo0x, hi0x, lo0y, hi0y, lo0z, hi0z;
s16 lo1x, hi1x, lo1y, hi1y, lo1z, hi1z;
s16 lo2x, hi2x, lo2y, hi2y, lo2z, hi2z;
s16 lo3x, hi3x, lo3y, hi3y, lo3z, hi3z;
s16 cx0, cy0, cz0, cx1, cy1, cz1, cx2, cy2, cz2, cx3, cy3, cz3;
s16 r0;
s16 r1;
s16 r2;
s16 r3;
s16 r0lo;
s16 r1lo;
s16 r2lo;
s16 r3lo;
u8 *va, *vb, *vc;
u32 w; s32 code;
u32 vw, vzw;
u32 wx, wy, wz;
s32 xa32, xb32, t32;
s32 xmn1, xmx1, xmn2, xmx2;
s32 mnc, mxc;
s16 my, mny, mx, mn;
u8 *base;
s16 x0, y0, z0, x1, y1, z1, x2, y2, z2;
s32 a0v, a1v, a2v, a3v;
s32 d;
u32 *tp;
u32 uvw;
u32 cb;
base = D_800AF630;
r2lo = 0;
r1lo = 0;
r0lo = 0;
r2 = 0;
r1 = 0;
r0 = 0;
if (D_80197C28) {
cx0 = D_80197C2C;
cy0 = D_80197C2E;
r0lo = D_80197C34 - 0x200;
r0 = D_80197C34;
cz0 = D_80197C30;
} else {
cz0 = 0x6000;
cy0 = 0x6000;
cx0 = 0x6000;
}
if (D_80197C44) {
cx1 = D_80197C48;
cy1 = D_80197C4A;
r1 = D_80197C50;
r1lo = D_80197C50 - 0x200;
cz1 = D_80197C4C;
} else {
cz1 = 0x6000;
cy1 = 0x6000;
cx1 = 0x6000;
}
if (D_80197C60) {
cx2 = D_80197C64;
cy2 = D_80197C66;
r2 = D_80197C6C;
r2lo = D_80197C6C - 0x200;
cz2 = D_80197C68;
} else {
cz2 = 0x6000;
cy2 = 0x6000;
cx2 = 0x6000;
}
if (D_80197C7C) {
cx3 = D_80197C80;
cy3 = D_80197C82;
r3lo = r2 - 0x200;
r3 = D_80197C88;
cz3 = D_80197C84;
} else {
cz3 = 0x6000;
cy3 = 0x6000;
cx3 = 0x6000;
}
lo0x = cx0 - r0; hi0x = cx0 + r0;
lo0y = cy0 - r0; hi0y = cy0 + r0;
lo0z = cz0 - r0; hi0z = cz0 + r0;
lo1x = cx1 - r1; hi1x = cx1 + r1;
lo1y = cy1 - r1; hi1y = cy1 + r1;
lo1z = cz1 - r1; hi1z = cz1 + r1;
lo2x = cx2 - r2; hi2x = cx2 + r2;
lo2y = cy2 - r2; hi2y = cy2 + r2;
lo2z = cz2 - r2; hi2z = cz2 + r2;
lo3x = cx3 - r3; hi3x = cx3 + r3;
lo3y = cy3 - r3; hi3y = cy3 + r3;
lo3z = cz3 - r3; hi3z = cz3 + r3;
pkt = D_800A5E60;
part = *(Part **)(arg0 + 0xC);
nparts = *(s32 *)(*(s32 *)(arg0 + 8) + 8);
vtx = *(u8 **)(*(s32 *)(arg0 + 8) + 0x10);
ot = (u32)&D_800A6610[(*(u16 *)(base + 0xA3D2)) << 14];
for (j = 0; j < nparts; j++, part++) {
wx = part->xx;
mn = wx;
mx = wx >> 16;
wy = part->yy;
mny = wy;
my = wy >> 16;
wz = part->zz;
box[0].vx = mn; box[0].vy = mny;
box[1].vx = mx; box[1].vy = mny;
box[2].vx = mn; box[2].vy = mny;
box[3].vx = mx; box[3].vy = mny;
box[4].vx = mn; box[4].vy = my;
box[5].vx = mx; box[5].vy = my;
box[6].vx = mn; box[6].vy = my;
box[7].vx = mx; box[7].vy = my;
wy = wz >> 16;
box[0].vz = wz;
box[1].vz = wz;
box[4].vz = wz;
box[5].vz = wz;
box[2].vz = wy;
box[3].vz = wy;
box[6].vz = wy;
box[7].vz = wy;
gte_ldv3c(&box[0]);
gte_rtpt();
gte_stsxy3(&sxy[0], &sxy[1], &sxy[2]);
gte_ldv0(&box[3]);
gte_rtps();
gte_stsxy(&sxy[3]);
gte_ldv3c(&box[4]);
gte_rtpt();
gte_stsxy3(&sxy[4], &sxy[5], &sxy[6]);
gte_ldv0(&box[7]);
gte_rtps();
gte_stsxy(&sxy[7]);
gte_stszotz(&g.otz);
if (lim >= g.otz) {
xa32 = sxy[0].vx;
xb32 = sxy[1].vx;
if (xb32 < xa32) { xmx1 = xa32; xmn1 = xb32; } else { xmn1 = xa32; xmx1 = xb32; }
t32 = sxy[2].vx;
if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32;
t32 = sxy[3].vx;
if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32;
xa32 = sxy[4].vx;
xb32 = sxy[5].vx;
if (xb32 < xa32) { xmx2 = xa32; xmn2 = xb32; } else { xmn2 = xa32; xmx2 = xb32; }
t32 = sxy[6].vx;
if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32;
t32 = sxy[7].vx;
if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32;
mnc = xmn1;
if (xmn2 < xmn1) mnc = xmn2;
mxc = xmx1;
if (mxc < xmx2) mxc = xmx2;
if ((s16)mxc >= -0xA0 && (s16)mnc < 0xA1) {
xa32 = sxy[0].vy;
xb32 = sxy[1].vy;
if (xb32 < xa32) { xmx1 = xa32; xmn1 = xb32; } else { xmn1 = xa32; xmx1 = xb32; }
t32 = sxy[2].vy;
if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32;
t32 = sxy[3].vy;
if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32;
xa32 = sxy[4].vy;
xb32 = sxy[5].vy;
if (xb32 < xa32) { xmx2 = xa32; xmn2 = xb32; } else { xmn2 = xa32; xmx2 = xb32; }
t32 = sxy[6].vy;
if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32;
t32 = sxy[7].vy;
if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32;
mnc = xmn1;
if (xmn2 < xmn1) mnc = xmn2;
mxc = xmx1;
if (mxc < xmx2) mxc = xmx2;
if ((s16)mxc >= -0x6E && (s16)mnc < 0x6F) {
nprim = part->nprim;
prim = (Prim *)part->prim;
for (i = 0; i < nprim; i++, prim++) {
w = prim->w1;
va = vtx + (w & 0xFFFF);
vb = vtx + (w >> 16);
w = prim->w2;
vc = vtx + (w & 0xFFFF);
w = w >> 16;
gte_ldv3(va, vb, vc);
gte_rtpt();
gte_stflg(&g.flag);
if (!(g.flag & 0x7F85E000)) {
gte_nclip();
code = w & 7;
vd = vtx + (w & 0xFFF8);
gte_stopz(&g.opz);
if (g.opz > 0) {
switch (code) {
case 6:
case 7:
/* ---------------- TRI (FT3 / GT3) ---------------- */
gte_stsxy3c(&tmpxy[0]);
gte_stsz3(&g.sz0, &g.sz1, &g.sz2);
if (tmpxy[0].vx > tmpxy[1].vx) { mx = tmpxy[0].vx; mn = tmpxy[1].vx; }
else { mn = tmpxy[0].vx; mx = tmpxy[1].vx; }
if (tmpxy[2].vx > mx) mx = tmpxy[2].vx;
else if (tmpxy[2].vx < mn) mn = tmpxy[2].vx;
if (mx >= -0xA0 && mn < 0xA1) {
if (tmpxy[0].vy > tmpxy[1].vy) { my = tmpxy[0].vy; mny = tmpxy[1].vy; }
else { mny = tmpxy[0].vy; my = tmpxy[1].vy; }
if (tmpxy[2].vy > my) my = tmpxy[2].vy;
else if (tmpxy[2].vy < mny) mny = tmpxy[2].vy;
if (my >= -0x6E && mny < 0x6F) {
s32 za, zb;
s32 c0, c1, c2, c3;
__asm__ __volatile__ ("" :: "r" (mny));
if (g.sz0 > g.sz1) { za = g.sz0; if (za < g.sz2) za = g.sz2; }
else { za = g.sz1; if (za < g.sz2) za = g.sz2; }
g.opz = za;
f3 = 0; f2 = 0; f1 = 0; f0 = 0;
vw = *(u32 *)va;
vzw = *(u32 *)(va + 4);
x0 = vw; y0 = vw >> 16; z0 = vzw;
BOXTEST(f0, x0, y0, z0, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z);
BOXTEST(f1, x0, y0, z0, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z);
BOXTEST(f2, x0, y0, z0, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z);
BOXTEST(f3, x0, y0, z0, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z);
vw = *(u32 *)vb;
vzw = *(u32 *)(vb + 4);
x1 = vw; y1 = vw >> 16; z1 = vzw;
BOXTEST(f0, x1, y1, z1, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z);
BOXTEST(f1, x1, y1, z1, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z);
BOXTEST(f2, x1, y1, z1, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z);
BOXTEST(f3, x1, y1, z1, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z);
vw = *(u32 *)vc;
vzw = *(u32 *)(vc + 4);
x2 = vw; y2 = vw >> 16; z2 = vzw;
BOXTEST(f0, x2, y2, z2, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z);
BOXTEST(f1, x2, y2, z2, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z);
BOXTEST(f2, x2, y2, z2, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z);
BOXTEST(f3, x2, y2, z2, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z);
if (f0 | f1 | f2 | f3) {
u32 *otp;
u32 rgbw;
ATTEN(a0v, f0, x0, y0, z0, cx0, cy0, cz0, r0, r0lo);
ATTEN(a1v, f1, x0, y0, z0, cx1, cy1, cz1, r1, r1lo);
ATTEN(a2v, f2, x0, y0, z0, cx2, cy2, cz2, r2, r2lo);
ATTEN3(a3v, f3, x0, y0, z0, cx3, cy3, cz3, r3, r3lo, r2, r2);
CLAMP80(c0, a0v, a1v, a2v, a3v);
ATTEN(a0v, f0, x1, y1, z1, cx0, cy0, cz0, r0, r0lo);
ATTEN(a1v, f1, x1, y1, z1, cx1, cy1, cz1, r1, r1lo);
ATTEN(a2v, f2, x1, y1, z1, cx2, cy2, cz2, r2, r2lo);
ATTEN3(a3v, f3, x1, y1, z1, cx3, cy3, cz3, r3, r3lo, r3, r3);
CLAMP80(c1, a0v, a1v, a2v, a3v);
ATTEN(a0v, f0, x2, y2, z2, cx0, cy0, cz0, r0, r0lo);
ATTEN(a1v, f1, x2, y2, z2, cx1, cy1, cz1, r1, r1lo);
ATTEN(a2v, f2, x2, y2, z2, cx2, cy2, cz2, r2, r2lo);
ATTEN3(a3v, f3, x2, y2, z2, cx3, cy3, cz3, r3, r3lo, r2, r3);
CLAMP80(c2, a0v, a1v, a2v, a3v);
*(u32 *)&((PolyGT3 *)pkt)->x0 = *(u32 *)&tmpxy[0];
*(u32 *)&((PolyGT3 *)pkt)->x1 = *(u32 *)&tmpxy[1];
*(u32 *)&((PolyGT3 *)pkt)->x2 = *(u32 *)&tmpxy[2];
tp = (u32 *)prim->w0;
cb = 0x34000000;
rgbw = (c0 | cb) | (c0 << 8) | (c0 << 16);
((PolyGT3 *)pkt)->rgb0 = rgbw;
rgbw = (c1 | cb) | (c1 << 8) | (c1 << 16);
((PolyGT3 *)pkt)->rgb1 = rgbw;
rgbw = (c2 | cb) | (c2 << 8) | (c2 << 16);
((PolyGT3 *)pkt)->rgb2 = rgbw;
((PolyGT3 *)pkt)->uv0 = tp[1];
((PolyGT3 *)pkt)->uv1 = tp[2];
((PolyGT3 *)pkt)->uv2 = tp[3];
otp = (u32 *)(((g.opz >> 2) << 2) + ot);
*(u32 *)pkt = (*otp & 0xFFFFFF) | 0x9000000;
*otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF);
pkt += 0x28;
} else {
u32 *otp;
u32 rgbw;
*(u32 *)&((PolyFT3 *)pkt)->x0 = *(u32 *)&tmpxy[0];
*(u32 *)&((PolyFT3 *)pkt)->x1 = *(u32 *)&tmpxy[1];
*(u32 *)&((PolyFT3 *)pkt)->x2 = *(u32 *)&tmpxy[2];
tp = (u32 *)prim->w0;
cb = tp[0] & 0xFF000000;
cb |= 0x101010;
((PolyFT3 *)pkt)->rgbc = cb;
((PolyFT3 *)pkt)->uvc0 = tp[1];
((PolyFT3 *)pkt)->uvp1 = tp[2];
((PolyFT3 *)pkt)->uv2 = tp[3];
otp = (u32 *)(((g.opz >> 2) << 2) + ot);
*(u32 *)pkt = (*otp & 0xFFFFFF) | 0x7000000;
*otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF);
pkt += 0x20;
}
}
}
break;
case 2:
case 3:
/* ---------------- QUAD (FT4 / GT4) ---------------- */
gte_stsxy3c(&tmpxy[0]);
gte_ldv0(vd);
gte_rtps();
if (tmpxy[0].vx > tmpxy[1].vx) { mx = tmpxy[0].vx; mn = tmpxy[1].vx; }
else { mn = tmpxy[0].vx; mx = tmpxy[1].vx; }
if (tmpxy[2].vx > mx) mx = tmpxy[2].vx;
else if (tmpxy[2].vx < mn) mn = tmpxy[2].vx;
if (tmpxy[0].vy > tmpxy[1].vy) { my = tmpxy[0].vy; mny = tmpxy[1].vy; }
else { mny = tmpxy[0].vy; my = tmpxy[1].vy; }
if (tmpxy[2].vy > my) my = tmpxy[2].vy;
else if (tmpxy[2].vy < mny) mny = tmpxy[2].vy;
gte_stflg(&g.flag);
if (!(g.flag & 0x7F85E000)) {
gte_stsz4(&g.sz0, &g.sz1, &g.sz2, &g.sz3);
gte_stsxy((long *)&((PolyFT4 *)pkt)->x3);
if (((PolyFT4 *)pkt)->x3 < mn) mn = ((PolyFT4 *)pkt)->x3;
else if (mx < ((PolyFT4 *)pkt)->x3) mx = ((PolyFT4 *)pkt)->x3;
if (mx >= -0xA0 && mn < 0xA1) {
if (((PolyFT4 *)pkt)->y3 < mny) mny = ((PolyFT4 *)pkt)->y3;
else if (my < ((PolyFT4 *)pkt)->y3) my = ((PolyFT4 *)pkt)->y3;
if (my >= -0x6E && mny < 0x6F) {
s32 za, zb;
s32 c0, c1, c2, c3;
zb = g.sz2;
if (zb < g.sz3) zb = g.sz3;
za = g.sz0;
if (za < g.sz1) za = g.sz1;
if (za < zb) za = zb;
g.opz = za;
f3 = 0; f2 = 0; f1 = 0; f0 = 0;
vw = *(u32 *)va;
vzw = *(u32 *)(va + 4);
x0 = vw; y0 = vw >> 16; z0 = vzw;
BOXTEST(f0, x0, y0, z0, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z);
BOXTEST(f1, x0, y0, z0, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z);
BOXTEST(f2, x0, y0, z0, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z);
BOXTEST(f3, x0, y0, z0, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z);
vw = *(u32 *)vb;
vzw = *(u32 *)(vb + 4);
x1 = vw; y1 = vw >> 16; z1 = vzw;
BOXTEST(f0, x1, y1, z1, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z);
BOXTEST(f1, x1, y1, z1, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z);
BOXTEST(f2, x1, y1, z1, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z);
BOXTEST(f3, x1, y1, z1, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z);
vw = *(u32 *)vc;
vzw = *(u32 *)(vc + 4);
x2 = vw; y2 = vw >> 16; z2 = vzw;
BOXTEST(f0, x2, y2, z2, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z);
BOXTEST(f1, x2, y2, z2, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z);
BOXTEST(f2, x2, y2, z2, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z);
BOXTEST(f3, x2, y2, z2, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z);
vw = *(u32 *)vd;
vzw = *(u32 *)(vd + 4);
x3 = vw; y3 = vw >> 16; z3 = vzw;
BOXTEST(f0, x3, y3, z3, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z);
BOXTEST(f1, x3, y3, z3, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z);
BOXTEST(f2, x3, y3, z3, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z);
BOXTEST(f3, x3, y3, z3, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z);
if (f0 | f1 | f2 | f3) {
u32 *otp;
u32 rgbw;
ATTEN(a0v, f0, x0, y0, z0, cx0, cy0, cz0, r0, r0lo);
ATTEN(a1v, f1, x0, y0, z0, cx1, cy1, cz1, r1, r1lo);
ATTEN(a2v, f2, x0, y0, z0, cx2, cy2, cz2, r2, r2lo);
ATTEN3(a3v, f3, x0, y0, z0, cx3, cy3, cz3, r3, r3lo, r3, r3);
CLAMP80(c0, a0v, a1v, a2v, a3v);
ATTEN(a0v, f0, x1, y1, z1, cx0, cy0, cz0, r0, r0lo);
ATTEN(a1v, f1, x1, y1, z1, cx1, cy1, cz1, r1, r1lo);
ATTEN(a2v, f2, x1, y1, z1, cx2, cy2, cz2, r2, r2lo);
ATTEN3(a3v, f3, x1, y1, z1, cx3, cy3, cz3, r3, r3lo, r3, r3);
CLAMP80(c1, a0v, a1v, a2v, a3v);
ATTEN(a0v, f0, x2, y2, z2, cx0, cy0, cz0, r0, r0lo);
ATTEN(a1v, f1, x2, y2, z2, cx1, cy1, cz1, r1, r1lo);
ATTEN(a2v, f2, x2, y2, z2, cx2, cy2, cz2, r2, r2lo);
ATTEN3(a3v, f3, x2, y2, z2, cx3, cy3, cz3, r3, r3lo, r3, r3);
CLAMP80(c2, a0v, a1v, a2v, a3v);
ATTEN(a0v, f0, x3, y3, z3, cx0, cy0, cz0, r0, r0lo);
ATTEN(a1v, f1, x3, y3, z3, cx1, cy1, cz1, r1, r1lo);
ATTEN(a2v, f2, x3, y3, z3, cx2, cy2, cz2, r2, r2lo);
ATTEN3(a3v, f3, x3, y3, z3, cx3, cy3, cz3, r3, r3lo, r3, r3);
CLAMP80(c3, a0v, a1v, a2v, a3v);
*(u32 *)&((PolyGT4 *)pkt)->x0 = *(u32 *)&tmpxy[0];
*(u32 *)&((PolyGT4 *)pkt)->x1 = *(u32 *)&tmpxy[1];
*(u32 *)&((PolyGT4 *)pkt)->x2 = *(u32 *)&tmpxy[2];
gte_stsxy((long *)&((PolyGT4 *)pkt)->x3);
tp = (u32 *)prim->w0;
cb = 0x3C000000;
rgbw = c0 | cb; rgbw |= c0 << 8; rgbw |= c0 << 16;
((PolyGT4 *)pkt)->rgb0 = rgbw;
rgbw = c1 | cb; rgbw |= c1 << 8; rgbw |= c1 << 16;
((PolyGT4 *)pkt)->rgb1 = rgbw;
rgbw = c2 | cb; rgbw |= c2 << 8; rgbw |= c1 << 16;
((PolyGT4 *)pkt)->rgb2 = rgbw;
rgbw = c3 | cb; rgbw |= c3 << 8; rgbw |= c1 << 16;
((PolyGT4 *)pkt)->rgb3 = rgbw;
((PolyGT4 *)pkt)->uv0 = tp[1];
((PolyGT4 *)pkt)->uv1 = tp[2];
uvw = tp[3];
((PolyGT4 *)pkt)->uv2 = uvw;
((PolyGT4 *)pkt)->uv3 = uvw >> 16;
otp = (u32 *)(((g.opz >> 2) << 2) + ot);
*(u32 *)pkt = (*otp & 0xFFFFFF) | 0xC000000;
*otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF);
pkt += 0x34;
} else {
u32 *otp;
u32 rgbw;
*(u32 *)&((PolyFT4 *)pkt)->x0 = *(u32 *)&tmpxy[0];
*(u32 *)&((PolyFT4 *)pkt)->x1 = *(u32 *)&tmpxy[1];
*(u32 *)&((PolyFT4 *)pkt)->x2 = *(u32 *)&tmpxy[2];
tp = (u32 *)prim->w0;
cb = tp[0] & 0xFF000000;
cb |= 0x101010;
((PolyFT4 *)pkt)->rgbc = cb;
((PolyFT4 *)pkt)->uvc0 = tp[1];
((PolyFT4 *)pkt)->uvp1 = tp[2];
uvw = tp[3];
((PolyFT4 *)pkt)->uv2 = uvw;
((PolyFT4 *)pkt)->uv3 = uvw >> 16;
otp = (u32 *)(((g.opz >> 2) << 2) + ot);
*(u32 *)pkt = (*otp & 0xFFFFFF) | 0x9000000;
*otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF);
pkt += 0x28;
}
}
}
}
break;
}
}
}
}
}
}
}
}
D_800A5E60 = pkt;
}
+50
View File
@@ -6144,3 +6144,53 @@ shared `rgbw` result temp · `s32 za, zb;` per case.
**Five of the nine were read straight off the MATCHED relatives** (`func_8017F510` 1,511 and
`func_8017CA80` 952, same renderer family) — worth more than every expression sweep combined.
**Crack the smaller family member first; it is a lever library for the larger one.**
## §79 — For a 0-callee giant, fingerprint by DATA symbols (§71 cannot fire); and the STACK-SLOT ORDER is a declaration-order oracle (Phase 29 SESSION-19, `func_8017BF14` 4,763 ins, cold start → 45/4763)
A cold-start attempt on the project's second-largest function reached **4763/4763 ins, 45 mismatched
(99.06% byte-identical, 99.94% structural, exact frame, exact opcode histogram)** — not a match, but it
produced two levers and refuted the premise it was given.
### §71 has a blind spot, and this is it
The target was briefed as "**no matched relative — a genuine cold start**": `h_norm`/`h_seq` family
size 1, and §71's callee-set fingerprint returned jaccard 0.00 against every matched giant. **That
premise was wrong.** §71 fingerprints by **callee set** — and this function makes **zero `jal` calls**,
so the fingerprint is empty and cannot fire *by construction*. Grepping the target's **data** symbol
`D_800A5E60` landed immediately on the matched `func_8017BEBC`: it is the **4-light-box** member of the
same volumetric-light renderer family whose 3-box sibling (`func_8017D960`, 3,338 ins) was matched
hours earlier.
**Rule: when §71 returns an empty or zero-overlap callee set, fall back to DATA-symbol fingerprinting**
(`lui %hi(D_xxxxxxxx)` operands in the target `.s`). A leaf giant has no callees to fingerprint by, but
it still touches the same globals as its family. **An empty fingerprint is a "cannot answer", not a
"no relative" — do not let it become a cold-start brief.**
### NEW LEVER — the frame layout reads back the original declaration order
gcc-2.7.2 assigns stack slots to spilled pseudos in **pseudo-number order**, and pseudo numbers are
issued in order of first use ≈ **declaration order**. Therefore **the target's frame layout is a direct
readout of its source's declaration order.** Compare your draft's slot assignments against the
target's and reorder declarations until they agree — moving a single line (`f0..f3` after `pkt`) took
73% → 84% structural and brought **all 127 slots** into exact correspondence. Automatable; the
session's implementation is `.run/giants/bf14_slots.py`.
This is the counterpart to §78's "read the asm back to source shape": there, an `|`-chain's first term
tells you a literal was a variable; here, the frame map tells you the declaration order.
### §76 confirmed at scale, and a pin nuance
- The **entire −62 length residual was ONE allocno-class decision**: declaring `s32 c0..c3` *inside the
cull blocks* (1 death ⇒ local allocno ⇒ `global.c:668-671` removes those hard regs from the global
pool) spilled `r1lo` and moved the draft 52% → 93%. An `__asm__` ref-dial reached the same spill and
scored **worse** — **declaration scope beat the ref dial**, again.
- **Pins are safe on a 0-`jal` function** — §74's caller-saved-across-a-call hazard cannot arise, so
the usual suspicion is unwarranted here; 4 pins took 94% → 99%. **But §72 still held: pins 5 and 6
made it worse.** Pins remain a preference, and past a small number they fight the allocator.
### The residual, and the honest read
45 mismatches, **three register-grant ties, zero structural divergence**. The one §76 lever class the
session never reached is **variable REUSE across `c0..c3` / `a0v..a3v`** — that is the named next move.
Artifacts: `.run/giants/s19_func_8017BF14_b1.c` (45/4763), a **pin-free fallback at 789/4763 that is
100% structural**, and `s19_bf14_report.md` (~40-row do-not-re-buy table + 4 refuted diagnoses).
**Cold-start economics, measured:** a 4,763-instruction leaf giant with a *findable* matched relative
reached 99.06% in one pass but did not close. Budget a second pass for anything this size; the first
pass buys the decode, the frame, and the length — the last ~1% is register grants.
+46 -7
View File
@@ -3838,19 +3838,53 @@ conditional) · main-EXE/B9 + GLM/B6 + resident's 14 walls (P30) · behemoths B7
`src/*.c` (no config), so `make check-all` (140/140, run) is sound here; **the full clean R22 must
still be run once the agent finishes.**
- **⚖️ 2026-07-25 (SESSION-19) — THE COLD-START EXPERIMENT: `func_8017BF14` (4,763 ins) reached
4763/4763, **45 mismatched** (99.06% byte, 99.94% structural) — NOT a match, and the honest answer
to Drew's effort question. It also REFUTED THE PREMISE I GAVE IT (§79).**
Verified independently: `match_one` → `mine=4763 target=4763, 45 mismatched, OPCODE-MIXED`. Agent
respected its sandbox (only `.run/giants/`). **Nothing banked — 45 ≠ 0, and the byte-gate is the
sole arbiter (G3/P9).**
**MY BRIEF'S "NO MATCHED RELATIVE — A GENUINE COLD START" WAS WRONG, BY CONSTRUCTION.** I picked this
target partly BECAUSE §71's callee-set fingerprint returned jaccard 0.00 against every matched giant.
But **this function makes ZERO `jal` calls**, so its callee fingerprint is EMPTY and §71 *cannot
fire* — 0.00 meant "cannot answer", not "no relative". Grepping the target's **data** symbol
`D_800A5E60` found the matched `func_8017BEBC` immediately: `func_8017BF14` is the **4-light-box**
member of the very renderer family whose 3-box sibling `func_8017D960` we matched hours earlier.
**⇒ §79: when §71 returns an empty/zero-overlap callee set, fall back to DATA-symbol fingerprinting.
An empty fingerprint must never be allowed to become a cold-start brief.**
**NEW LEVER — THE FRAME LAYOUT IS A DECLARATION-ORDER ORACLE (§79).** gcc-2.7.2 assigns stack slots
to spilled pseudos in pseudo-number order, and pseudo numbers follow first use ≈ declaration order —
so **the target's frame map reads back its source's declaration order**. Moving ONE line (`f0..f3`
after `pkt`) took 73% → 84% structural and brought **all 127 slots** into exact correspondence.
Counterpart to §78's asm→source reads. Automatable (`.run/giants/bf14_slots.py`).
**§76 CONFIRMED AT SCALE:** the entire **−62 length residual was ONE allocno-class decision**
(`s32 c0..c3` declared inside the cull blocks ⇒ 1-death local allocnos ⇒ `global.c:668-671` removes
those regs from the global pool ⇒ `r1lo` spills): 52% → 93%. An `__asm__` ref-dial reached the same
spill and scored WORSE — **declaration scope beat the ref dial, again.**
**PIN NUANCE:** pins are SAFE on a 0-`jal` function (§74's caller-saved-across-a-call hazard cannot
arise); 4 pins took 94% → 99%. **But §72 held — pins 5 and 6 made it worse.**
**THE EFFORT ANSWER, HONESTLY:** xHigh from a genuine cold start on a 4,763-ins giant bought the
decode, the exact length, the exact frame and 99.06% — but **did not close**. The residual is **three
register-grant ties, zero structural divergence**. Budget a SECOND pass for anything this size: the
first buys structure, the last ~1% is register grants. Named next move: **variable REUSE across
`c0..c3`/`a0v..a3v`** — the one §76 lever class this pass never reached.
Artifacts: `s19_func_8017BF14_b1.c` (45/4763) + a **pin-free fallback at 789/4763 that is 100%
structural** + `s19_bf14_report.md` (~40-row do-not-re-buy table, 4 refuted diagnoses).
> **🛑 SESSION-19 CLOSING CHECKPOINT (2026-07-25, Opus 5 @ High) — REFRESHED mid-session; supersedes
> both the SESSION-18 block and the earlier SESSION-19 block (which was written before the
> ENGINE_SHB / class-B / dedup_extend-bug work and went stale). Fresh session safe here.**
> **No background job is running.** Three behemoths banked this session: `func_8017F510`
> **No background job is running; the deferred full R22 has been run (140/140).** Three behemoths banked this session: `func_8017F510`
> (1,511, §76 crack), `func_8017F5B4` (1,511, §40 remap), and **`func_8017D960` + its entire
> 5-member family (5 × 3,338 = 16,690 ins, §78 crack + 4 first-try remaps)**.
> **Tree clean** (only R23 `db.*.gbf` churn — never staged).
> **STATE:** HEAD `commit:1012`, **22 commits this session**. **R22 clean-fleet 140/140, run 8×** — the
> last after the behemoth-#2 family bank.
> **STATE:** HEAD (see git log), **26 commits this session**. **R22 clean-fleet 140/140, run 9×** — the
> last one FULL (`make clean` + extract-all + check-all) AFTER the BF14 agent finished, which
> discharges the deferral noted in the pool entry. `make tools-health` → **OK**.
> **0 NON_MATCHING** (G4). dedup **1886 validated / 0 failed**, C1 coverage 239,604/239,604. **Drew pushes** (R6/R20).
> **FLEET: 80.5% instr** — 10,575,671 / 13,141,652 · **distinct-code 3,833,224 = 68.0%
> (+19,712 ins this session — ALL of it from the three behemoths; every propagation win
> contributed +0)** · fn-count **89.18%** (session opened 80.0 / 67.7 / 89.02).
> **FLEET: 80.5% instr** — 10,580,590 / 13,141,652 · **distinct-code 3,838,143 = 68.1%
> (+24,631 ins this session: 19,712 from the three behemoths + 4,919 from the h_norm-remap pool;
> every PROPAGATION win contributed +0)** · fn-count **89.18%** (session opened 80.0/67.7/89.02).
>
> ## BANKED THIS SESSION
> | fn | ins | reach | lever |
@@ -3892,7 +3926,12 @@ conditional) · main-EXE/B9 + GLM/B6 + resident's 14 walls (P30) · behemoths B7
> ## ⚠️ OPEN ACTIONS, ranked
> 1. **[DONE] `func_80174CB0` ×135.** Residual = the **3 class-A overlays** (`func_80012ABC`, census
> 73 `s32` vs 7 `s16`) — worth 3 overlays only; normalize the 7 `s16` decls if trivially cheap.
> 2. **BEHEMOTHS — now the PROVEN distinct-code lever** (+3,022 ins today vs +0 from all
> 2. **`func_8017BF14` SECOND PASS — 45/4763 away** (99.06%), three register-grant ties, zero
> structural divergence. Named next move: **variable REUSE across `c0..c3`/`a0v..a3v`** (the one
> §76 lever class the cold pass never reached). Draft `.run/giants/s19_func_8017BF14_b1.c`;
> pin-free 100%-structural fallback at 789/4763. **Its matched relative is `func_8017BEBC`**
> (found by DATA-symbol fingerprint, §79 — NOT by §71, which cannot fire on a 0-callee fn).
> 3. **BEHEMOTHS — the PROVEN distinct-code lever** (+3,022 ins today vs +0 from all
> propagation). **8 untouched:** `func_8017BF14` 4,763 · `func_8017E778` 3,338 ·
> `func_8017D2DC` 1,586 · `func_8017DC1C` 1,518 · `func_8017C954` 1,194 · `func_8017C730`
> 1,061 (family of 2) · (+`func_80183814` 5,122, mapped only). **`func_8017E778`/`func_8017CD9C`