Correctness:
- memory({op:'readCart', cpuAddress, bank}) honors the SNES bank (bank-local
addr + bank:2 read BANK 0 silently — the disasm twin was 0.91.2). Composes
bank<<16 | cpuAddress like the disasm fix.
- frame({op:'stepInstructions'}) width no longer lies on taken branches. width
was the raw PC delta, so a 2-byte beq taken to +3 looked like a 3-byte instr,
mis-validating a 65816 immediate-width decode. Each step is classified from
its opcode into flow: seq/branch/call/jump/ret; width is emitted ONLY on
flow:'seq' (delta = size); transfers carry flow + nextPc instead.
- cpu({op:'call'}) speaks the loaded core's registers. finalRegs was hardcoded
m68k names on every core (wrong labels on 65816) — now per-CPU decode
(A/X/Y/P/DB/D/S/PC). regs accepts NAMES ({a:0x0350}) via a per-platform map
(unknown -> clear error), not just numeric ids. New callMode:'jsr'|'jsl'
sizes the 65816 return/sentinel (jsr/rts=2 vs the jsl/rtl=3 default; wrong
size 'returned' into vector-stub land).
Token cost:
- build({output:'reassemble'}) collapses regions to {count, allByteExact:true}
on full success (was ~2.5KB of identical rows on a 32-bank cart per rebuild);
lists only failed/edited regions.
- breakpoint({on:'pc'}) hits emit a terse note, not the ~600-char registersAtHit
boilerplate every time (full text is in the tool description). memory read
stops emitting note:null for note-less regions.
Verified live on real SNES; tests: stepInstructions flow classification,
disasm bank, reassemble region collapse. All suites green.