It's nice to see somebody working on this - I'll be looking forward to the TAS!
I investigated some of the mechanics of the game when I played it half a year ago or so. One of the things I did was to dump the
monster information table. Sadly, it seems I didn't dump the associated monster names, but that should be possible to infer based on the stats of the monster. Pherhaps it will come in handy. I didn't figure out what all the columns mean, but I think the two bytes between the Holy and Air resistance are individual spell immunities (one bit per spell).
A monster can have 5 possible reactions to an element, indicated by the number in the column for that element:
0: Normal damage
1: Double damage
2: Half damage
3: Zero damage
>=4: Negative damage
The hit formula seems to be:
- If attacker and defender face the same way, the attack is a hit (and there is a chance of some extra stuff happening, it seems).
- Otherwise, the chance of hitting is (hit - evade - agl/4)/256.
The hit chance is capped to [0:1], like any probability should be.
The melee damage formula seems to be
x = (attack - def*4)*mul*33/32
Here, mul is the element multiplier as described above, with the first element of the weapon that is something else than normal being used, if any. I don't see the point of the multiplication by 33/32, but that seems to be what the game does.
I haven't tested this formula, though. It is unsual that it doesn't seem to include any element of randomness, for example. So I may be missing something. Damage is capped to [-9999:9999], and def cannot reduce damage below 0. I haven't checked how def is applied for negative damage, but it would make sense that it isn't applied in this case.
The damage formula shows that one strong attack is much better than many weak ones. This is also my experience in the game. This means that attacks like shinkuuhazan (the one with the long delay, but 3 times normal damage, and long reach) can do much more damage than other attacks when you are weak. This meshes with my experience. I have sometimes done 0 damage with my normal attacks, but more than 1000 with shinkuuhazan. On the other hand, when you are strong enough to easily penetrate the enemy's defense, it is better to have 2 hits at double damage than 1 slow hit at triple damage.
There is some partially commented assembly
here, but I'm not sure how useful it will be - it was written at various stages in my investigation, and may contain incorrect or contradictory hypotheses.
Edit: While doing this, I found some RNG code too, though I don't know if this the only RNG or not. The RNG code is at C3E793. I didn't fully disassemble it, only trace it, so I don't have all its code paths
rng = table[((state++)&0xff)<<1]
if(state >= 0x38) do something
where state is the byte at address 0x85 and the table is at address
0x10740x103c (in ram, so it is probably getting overwritten in the "do something" branch. I don't have the rom and emulator at hand at the moment (I'm using my work computer now), but perhaps somebody else can disassemble the funciton now that its location is known.
Edit2: I have now disassembled the whole RNG. Here is a C equivalent of what it does (word here means 2 bytes. Remember that indexing an array of N byte data types increases the address by N times the offset. So this is equivalent to the disassembly below):
byte * state = 0x85;
word * table = 0x103c;
word rng() {
if(++*state >= 0x38) {
word x;
for(x=0;x!=0x19;x++)
table[x] -= table[x+0x1f];
for(;x!=0x38;x++)
table[x] -= table[x-0x18];
*state = 1;
}
return table[state];
}
The disassembly itself is here:
rng:
$C3/E793 08 PHP P:...Mx....
$C3/E794 8B PHB P:...Mx....
$C3/E795 DA PHX P:...Mx....
8 bit mode;
if(++$85 >= 38) {
shuffle();
$85 = 1;
}
$C3/E796 E2 20 SEP #$20 P:...Mx....
$C3/E798 A5 85 LDA $85 P:...Mx....
$C3/E79A 1A INC A P:...Mx....
$C3/E79B 85 85 STA $85 P:...Mx....
$C3/E79D C9 38 CMP #$38 P:...Mx....
$C3/E79F 90 07 BCC $07 [$E7A8] P:...Mx....
$C3/E7A1 20 B7 E7 JSR $E7B7 [$C3:E7B7] P:...Mx....
$C3/E7A4 A9 01 LDA #$01 P:...Mx....
$C3/E7A6 85 85 STA $85 P:...Mx....
16 bit mode;
return $103c[($85&0xff)<<1];
$C3/E7A8 C2 20 REP #$20 P:...Mx....
$C3/E7AA 29 FF 00 AND #$00FF P:...mx....
$C3/E7AD 0A ASL A P:...mx....
$C3/E7AE AA TAX P:...mx....
$C3/E7AF BF 3C 10 00 LDA $00103C,x P:...mx....
$C3/E7B3 FA PLX P:...mx....
$C3/E7B4 AB PLB P:...mx....
$C3/E7B5 28 PLP P:...mx....
$C3/E7B6 6B RTL P:...mx....
shuffle:
16 bit mode;
for(x=0;x!=32;x+=2)
$103c[x] -= $103c[x+3e];
$C3/E7B7 C2 20 REP #$20 P:...mx....
$C3/E7B9 A2 00 00 LDX #$0000 P:...mx....
$C3/E7BC BF 3C 10 00 LDA $00103C,x P:...mx....
$C3/E7C0 38 SEC P:...mx....
$C3/E7C1 FF 7A 10 00 SBC $00107A,x P:...mx....
$C3/E7C5 9F 3C 10 00 STA $00103C,x P:...mx....
$C3/E7C9 E8 INX P:...mx....
$C3/E7CA E8 INX P:...mx....
$C3/E7CB E0 32 00 CPX #$0032 P:...mx....
$C3/E7CE D0 EC BNE $EC [$E7BC] P:...mx....
for(x=32;x!=70;x+=2)
$103c[x] -= $103c[x-30];
$C3/E7D0 BF 3C 10 00 LDA $00103C,x P:...mx....
$C3/E7D4 38 SEC P:...mx....
$C3/E7D5 FF 0C 10 00 SBC $00100C,x P:...mx....
$C3/E7D9 9F 3C 10 00 STA $00103C,x P:...mx....
$C3/E7DD E8 INX P:...mx....
$C3/E7DE E8 INX P:...mx....
$C3/E7DF E0 70 00 CPX #$0070 P:...mx....
$C3/E7E2 D0 EC BNE $EC [$E7D0] P:...mx....
8 bit mode;
return;
$C3/E7E4 E2 20 SEP #$20 P:...mx....
$C3/E7E6 60 RTS P:...Mx....
Here is a lua script which uses this formula to predict the next few values of the RNG:
emu = snes9x
-- Generate the next N random numbers
function step_rng(state, table)
state = state+1
if state >= 0x38 then
for x = 0, 0x18 do
table[x] = AND(table[x]-table[x+0x1f],0xffff)
end
for x = 0x19, 0x37 do
table[x] = AND(table[x]-table[x-0x18],0xffff)
end
state = 1
end
return table[state], state
end
function top_rng(n)
-- Copy initial rng state
state = memory.readbyte(0x85)
tsize = 0x38
table = {}
for i = 0, tsize-1 do
table[i] = memory.readword(0x103c+i*2)
end
-- generate the requested number of numbers
res = {}
for i = 1, n do
res[i], state = step_rng(state, table)
end
return res
end
gui.register(function()
nums = top_rng(27)
for i = 1, #nums do
gui.text(10,8*i,string.format("%04x",nums[i]))
end
end)
while true do
emu.frameadvance()
end
Edit: Small fitx to C code.