The captured listings made the colour computation readable, except for the
instructions that actually carry the data. LWL/LWR/SWL/SWR printed as
"op22"/"op26"/"op2a"/"op2e" and GTE ops as bare cop2 words -- in the exact
window under investigation, and covering the exact question (is the GTE
involved?) the investigation was trying to answer.
With them decoded, the per-vertex colour path reads end to end:
lwl $t6,-9($s4) unaligned load of the source RGB
lwr $t6,-12($s4)
swl $t6,67($sp) staged unaligned into stack scratch
swr $t6,64($sp)
beq $s6,$t3,skip neutral scale passes through unmodulated
lbu $v0,64($sp) per channel:
mult $v0,$s6 value * scale
sra $v0,$t6,7 >> 7
sb $v0,48($sp)
lw $v0,48($sp) reassemble the three bytes
sw $v0,4($a3) -> packet colour word
So every vertex colour is source_rgb * $s6 >> 7, and the source reaches the
packet only through LWL/LWR -> SWL/SWR -> lbu. Geometry does not go through
that path, which fits the evidence exactly: identical vertices, wrong colours.
cop2 needed splitting too -- bit 25 separates register moves from GTE commands,
and rt in lwc2/swc2 indexes a GTE register rather than a GPR, so printing "$t4"
there would send the reader after the wrong thing.
Checked the recompiler's LWL/LWR by hand while here; the keep-mask formulas are
correct in all four byte-offset cases. Noted rather than concluded: reading
code is how the last four hypotheses in this investigation went wrong.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The trace now works — a clean run separates six instructions that write ONLY
colour words (64 each) from four that write ONLY vertex words (64 each, which
is exactly 4 vertices x 64 packets), with no mixed rows. Those six are the
answer to "what computes these colours".
But reading them was still a second, separate step, and that is not safe here.
The PCs are at 0x80068xxx, above the main EXE at 0x8003F000, so they are
OVERLAY code: the instruction at a given address depends on which overlay is
resident. Disassembling after the scene moves on decodes whatever replaced it
and looks entirely plausible. So the listing is captured during the trace,
while the emulator is already parked and the right overlay is loaded, and
travels in the report.
Includes a small MIPS decoder — enough to read a store and the arithmetic
feeding it — with tests on the forms that appear around a packet write. Store
operand order is pinned because rt is the source and rs the base, and swapping
them yields a different instruction that still reads sensibly. Shifts are
pinned because taking the shift amount from rs instead of shamt is a classic
decoder bug that produces plausible nonsense. cop2 is pinned so GTE ops cannot
be silently decoded as something else.
Those tests immediately earned themselves: disasm_around mixed a virtual PC
into the offset arithmetic and compared it against a masked one, so no line
was ever marked as the target. The listing would have come back with nothing
highlighted, reading as "the instruction is not in this window".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>