Alpha Draft from Claude Opus 4.5
;=====================================================================
; NEW CODE - Place at E8:5280 (file offset: $340280 for headered ROM)
;=====================================================================
org $E85280
;---------------------------------------------------------------------
; Lookup table: Hero index (0-4) → Base address within bank E8
; Each entry is 2 bytes (16-bit address)
;---------------------------------------------------------------------
HeroBaseTable:
dw BARTZ_BASE ; $0000 - Hero 0 (Bartz: $00 & $07 = 0)
dw LENNA_BASE ; $1080 - Hero 1 (Lenna: $09 & $07 = 1)
dw GALUF_BASE ; $2100 - Hero 2 (Galuf: $02 & $07 = 2)
dw FARIS_BASE ; $3180 - Hero 3 (Faris: $0B & $07 = 3)
dw KRILE_BASE ; $4200 - Hero 4 (Krile: $0C & $07 = 4)
; Hex bytes: 00 00 80 10 40 20 C0 30 40 41
; (little-endian 16-bit values)
;---------------------------------------------------------------------
; Lookup table: Slot index (0-3) → WRAM base address
; Used to find the hero/job bytes for each party slot
;---------------------------------------------------------------------
SlotWRAMTable:
dw $2000 ; Slot 0 - party position 1
dw $2080 ; Slot 1 - party position 2
dw $2100 ; Slot 2 - party position 3
dw $2180 ; Slot 3 - party position 4
; Hex bytes: 00 20 80 20 00 21 80 21
;---------------------------------------------------------------------
; Main routine: ComputeWWAddress
;---------------------------------------------------------------------
; Entry conditions (ASSUMED - VERIFY!):
; X = slot × 8 (so $00, $08, $10, $18 for slots 0-3)
; DB = ?? (we'll set it ourselves)
; Processor likely in 8-bit mode from prior code
;
; Exit conditions:
; $76-$77 = sprite address within bank (computed)
; $78 = $E8 (bank byte)
; All other registers preserved
;---------------------------------------------------------------------
ComputeWWAddress:
PHP ; Save processor status
PHB ; Save data bank
PHX ; Save X (we'll need it)
PHY ; Save Y
; Set data bank to $7E for WRAM access
PHA
LDA #$7E
PHA
PLB ; DB = $7E
PLA
;-------------------------------------------------
; Step 1: Convert X to slot number (0-3)
; REVISED: X = $08, $10, $18, $20 for slots 0-3
;-------------------------------------------------
REP #$20 ; 16-bit accumulator
TXA ; A = X value ($08-$20)
SEC
SBC #$0008 ; Subtract 8: now $00, $08, $10, $18
LSR A ; ÷2
LSR A ; ÷4
LSR A ; ÷8 → slot number 0-3
AND #$0003 ; Safety mask
ASL A ; ×2 for word table index
TAX ; X = table index for SlotWRAMTable
;-------------------------------------------------
; Step 2: Get WRAM base address for this slot
;-------------------------------------------------
; We need to access our table in bank E8
; Using long addressing to reach it
LDA $E8528A,X ; Load from SlotWRAMTable (E85280 + $0A)
TAY ; Y = WRAM address ($2000, $2080, etc.)
;-------------------------------------------------
; Step 3: Read hero byte, compute hero base addr
;-------------------------------------------------
SEP #$20 ; 8-bit accumulator
LDA $0000,Y ; Read hero byte from $7E:20x0
AND #$07 ; Mask to get hero index (0-4)
ASL A ; ×2 for word table index
TAX ; X = hero table index
REP #$20 ; 16-bit accumulator
LDA $E85280,X ; Load from HeroBaseTable
STA $76 ; Store base address to $76-$77
;-------------------------------------------------
; Step 4: Read job byte, multiply by $C0, add to base
;-------------------------------------------------
SEP #$20 ; 8-bit accumulator
INY ; Y now points to job byte ($7E:20x1)
LDA $0000,Y ; Read job byte
; Multiply job by $C0 (192 decimal)
; $C0 = $80 + $40 = 128 + 64
; job × $C0 = (job × $80) + (job × $40)
; = (job << 7) + (job << 6)
REP #$20 ; 16-bit accumulator
AND #$00FF ; Clear high byte
STA $00 ; Temp storage: job value
ASL A ; ×2
ASL A ; ×4
ASL A ; ×8
ASL A ; ×16
ASL A ; ×32
ASL A ; ×64
STA $02 ; Temp: job × $40
ASL A ; ×128
CLC
ADC $02 ; A = job × $C0
; Add offset to base address
CLC
ADC $76
STA $76 ; $76-$77 = final sprite address
;-------------------------------------------------
; Step 5: Set bank byte to $E8
;-------------------------------------------------
SEP #$20 ; 8-bit accumulator
LDA #WW_BANK ; $E8
STA $78 ; $78 = bank byte
;-------------------------------------------------
; Restore and return
;-------------------------------------------------
PLY ; Restore Y
PLX ; Restore X
PLB ; Restore data bank
PLP ; Restore processor status
RTL ; Return from long subroutine
; End of routine - next free byte is at approximately E8:52E0
;=====================================================================
; HOOK PATCH - Apply at C1:2517
;=====================================================================
; Original 8 bytes at C1:2517:
; BF A7 24 C1 LDA $C124A7,X
; BF A9 24 C1 LDA $C124A9,X
;
; Replace with:
; 22 92 52 E8 JSL $E85292 ; Jump to ComputeWWAddress
; EA NOP ; \
; EA NOP ; } Pad remaining 4 bytes
; EA NOP ; }
; EA NOP ; /
org $C12517
JSL ComputeWWAddress ; 22 92 52 E8
NOP ; EA
NOP ; EA
NOP ; EA
NOP ; EA
; HEX SUMMARY FOR MANUAL PATCHING
;================================================================
;
; == At file offset $285280 (E8:5280) - New tables and routine ==
;
; HeroBaseTable (10 bytes):
; 00 00 80 10 40 20 C0 30 40 41
;
; SlotWRAMTable (8 bytes):
; 00 20 80 20 00 21 80 21
; @ E8:5292
; ComputeWWAddress routine (~70 bytes):
; REVISED: X = $08, $10, $18, $20 for slots 0-3
; 08 8B DA 5A 48 A9 7E 48 AB 68 C2 20 8A 38 FD 08
; 00 4A 4A 4A 29 03 00 0A AA BF 8A 52 E8 A8 E2 20
; B9 00 00 29 07 0A AA C2 20 BF 80 52 E8 85 76 E2
; 20 C8 B9 00 08 8B DA 5A 48 A9 7E 48 AB 68 C2 20
; 8A 4A 4A 4A 29 03 00 0A AA BF 8A 52 E8 A8 E2 20
; B9 00 00 29 07 0A AA C2 20 BF 80 52 E8 85 76 E2
; 20 C8 B9 00 00 C2 20 29 FF 00 85 00 0A 0A 0A 0A
; 0A 0A 85 02 0A 18 65 02 18 65 76 85 76 E2 20 A9
; E8 85 78 7A FA AB 28 6B
;
; === At file offset $012517 (C1:2517) - Hook patch ===
; 22 92 52 E8 EA EA EA EA
;
;================================================================
Once you have those observations, we can refine. The routine is ~70 bytes, well within the space after your sprites end at E8:527F.
;-------------------------------------------------
; Step 1: Convert X to slot number (0-3)
; REVISED: X = $08, $10, $18, $20 for slots 0-3
;-------------------------------------------------
REP #$20 ; 16-bit accumulator
TXA ; A = X value ($08-$20)
38 SEC
E9 08 00 SBC #$0008 ; Subtract 8: now $00, $08, $10, $18
LSR A ; ÷2
LSR A ; ÷4
LSR A ; ÷8 → slot number 0-3
AND #$0003 ; Safety mask
ASL A ; ×2 for word table index
TAX ; X = table index for SlotWRAMTable

Working hack, with incorrect sprites!
Finding which tiles are actually appearing is straightforward: the Mesen Sprite Viewer (Ctrl + 3).
Choosing to “View in Memory Viewer” on right click pulls up the first tile’s data in VRAM; copying these $20 bytes and then searching in ROM will locate them.

Nice offset, $140000 aka #D4:0000.

To quickly recap why a single tile is $20 bytes… we know from other measurements that $C0 is 6 tiles; $20 = 32, and 32 bytes is enough to cover 64 pixels at 4 bits per pixel (64 x 4 / 8 = 32).
So hero #4 is landing at $140000 using the alpha hack logic, and checking the other 3 battle positions will yield other data to help adjust things.
Surprise— all 4 battle positions load these tiles!

So the loop to change which address is loaded from per battle position is not yet firing, or somehow avoiding the hook.
Tables Look Correct Now
E8:5280 00 00 HeroBase[0] = $0000 (Bartz) ✓
E8:5282 80 10 HeroBase[1] = $1080 (Lenna) ✓
E8:5284 00 21 HeroBase[2] = $2100 (Galuf) ✓
E8:5286 80 31 HeroBase[3] = $3180 (Faris) ✓
E8:5288 00 42 HeroBase[4] = $4200 (Krile) ✓
E8:528A 00 20 SlotWRAM[0] = $2000 ✓
E8:528C 80 20 SlotWRAM[1] = $2080 ✓
E8:528E 00 21 SlotWRAM[2] = $2100 ✓
E8:5290 80 21 SlotWRAM[3] = $2180 ✓
Bug #1
E8:52C4 B9 00 08 LDA $0800,Y ; ← BUG! Should be LDA $0000,Y
The byte at file offset $3402C6 is 08 but should be 00.
That confirms the routine isn't computing addresses correctly yet — it's falling back to the original D4:0000 instead
of the new E8:xxxx addresses.
*Looking at your bytecode dump* more carefully, I found two more bugs beyond the one we fixed:
Bug #2: Fall-Through into Duplicate Code
After Version 1 reads the job byte at E8:52C4, it falls straight through into a second copy of the routine's prologue
at E8:52C7 (8B DA 5A 48...). This pushes registers again, corrupts the stack, and re-runs the calculations with a
different formula.
Fix: Insert a branch to skip over V2's redundant setup and jump directly to the job multiplication code.
At E8:52C7-52C8 (file offset $3402C7-C8):
- Current: 8B DA (PHD, PHX)
- Change to: 80 32 (BRA +$32 → jumps to E8:52FB)
Bug #3: Wrong Opcode in Epilogue
The epilogue uses AB (PLB - Pull Data Bank, 8-bit) but the prologue pushed with 8B (PHD - Push Direct Page, 16-bit).
These aren't symmetric!
At E8:531B (file offset $34031B):
- Current: AB (PLB)
- Change to: 2B (PLD)
Summary of All Fixes
┌─────────────┬─────────┬───────────┬──────────────────────────┐
│ File Offset │ Current │ Change To │ Purpose │
├─────────────┼─────────┼───────────┼──────────────────────────┤
│ $3402C6 │ 08 │ 00 │ Fix job byte read (DONE) │
├─────────────┼─────────┼───────────┼──────────────────────────┤
│ $3402C7 │ 8B │ 80 │ BRA opcode │
├─────────────┼─────────┼───────────┼──────────────────────────┤
│ $3402C8 │ DA │ 32 │ BRA offset (+$32) │
├─────────────┼─────────┼───────────┼──────────────────────────┤
│ $340317 │ AB │ 2B │ PLB → PLD in epilogue │
└─────────────┴─────────┴───────────┴──────────────────────────┘