what's $A9? is that ever added to anything? when's it first read after this?
at first, i was thinking that they wanted to have "Immune" guarantee the maximum Evasion value of 99 (because they knew that setting $AA to 128+ would later get Evasion capped to 99). now for this, they could've skipped the "ORA $AA", and still accomplished what they wanted.
and i figured the bottom 7 bits of $AD,X were just a normal integer. however, the $A9 code is suggesting they aren't, because it's being treated like a bitfield as well.. except i don't anticipate it ensuring 99 of anything, because $A9 will be under 128.
at this point, i'd say the bottom 7 bits of $AD,X are a list of unknown properties, and the top bit is a switch to control what variable(s) these properties will be applied to.
however, the whole idea of adding a bitfield to an integer, which is what 03/9A00 does, seems wrong. FF6 does mix and match the two a little (by having a 16-bit value where bits 0-7 are a character's normal HP or MP, then bits 8-9 are flags that select a certain percentage bonus to give to the stat), but FF6 actually has code to interpret this rather than just add things willy nilly.
this makes me think that the FF4 makers didn't want to use $AA in 03/9A00, but some other variable, though that's with the caveat that i know nothing whatsoever about this game. :P i wonder if you'll find some "complementary" code, where they read some other variable and treat it like a bitfield to do certain things, except the variable was never set in the first place (or maybe it was set as an integer). then two things would be resolved in a nice, tidy basket. if only.
that's a bit of speculation. anyway, i'm with you that something seems suspect here.
A9 looks to, oddly be enough, Critical Hit Bonus and before that is A8 in Critical Hit Chance. Which are also stored in character data, so I could easily verify that. What a strange order...
A8 - Critical Hit Chance (A set value held by character to character. Equipping a weapon of any sort doubles the value and equipping a Bow and Arrow triples the value. Noticeably when you have two weapons equipped the game will reset the Critical Hit chance to default)
A9 - Critical Hit Bonus - (A set value held by character to character. Equipping a weapon will add half of the weapon's attack power to the possible highest damage of a Critical Hit.)
AA - ??
That is what it reads by default, but in this routine they may be different...
In the Routine A8 and A9 are set as 00's with only AA being used as a temporary Evasion store-point.
The other 7 bits of AD are Elemental Resistance, Fire, Ice, Bolt, Holy, Dark, Air, Absorb and the game carries them as normal until it reaches the Branch if Minus portion, once it sees that it isn't 80 it leaves that value behind. Though I do have to wonder why it is then saved to A9... instead of AA. Maybe it is an off-by-one error? It wouldn't be the first time that Square had this happen (In this very game!)
Now A9 in this routine's purpose ends here...
$03/999E A5 A9 LDA $A9 [$00:00A9] A:0001 X:0005 Y:0025 P:envMxdIzC
$03/99A0 91 80 STA ($80),y[$7E:2125] A:0001 X:0005 Y:0025 P:envMxdIzC
25 elemental defense (if immunity bit wasn't set)***
Why it has to jump through loops to put the value into 7E2125, I'm not really sure. More interestingly is what happens to the value in AA...
$03/99A3 A5 AA LDA $AA [$00:00AA] A:0001 X:0005 Y:0026 P:envMxdIzC
$03/99A5 91 80 STA ($80),y[$7E:2126] A:0000 X:0005 Y:0026 P:envMxdIZC
26 elemental defense (if immunity bit was set)***
AA is not only a Temporary Evasion Store but is also where the Immunity Byte value is stored. I think I'm starting to see the picture here... maybe Square did screw up and a haphazard programmer used both AA as an Elemental Immune and Evade byte.
The real issue is that the Elemental Immunity byte is never zeroed out. as the other values in the routine appear to be before going onto their next function.
$03/99AC 64 A9 STZ $A9 [$00:00A9] A:0000 X:0000 Y:0005 P:envMxdIZC
This may be the issue. By default in the routine A9 seems to be Zero at this point anyhow. It is a possibility that this is supposed to be STZ AA, which would clear the problematic Immunity=Evade Byte.
I was not thorough enough before. Those bytes I said to null are necessary for applying Elemental Immunity. I think now the object is to find where these are fed into Evasion.
Hmm...
$03/99C2 A5 A9 LDA $A9 [$00:00A9] A:0026 X:0003 Y:0027 P:envMxdIzC
$03/99C4 0D 74 39 ORA $3974 [$7E:3974] A:0000 X:0003 Y:0027 P:envMxdIZC
$03/99C7 91 80 STA ($80),y[$7E:2127] A:0000 X:0003 Y:0027 P:envMxdIZC
27 is defense vs. Creature Types, so little surprise that it wouldn't generate a value in plain equipment.
I really may just have to disassemble this section of data as it is quite sequential.
$03/99DC AD 67 39 LDA $3967 [$7E:3967] A:0000 X:0000 Y:0028 P:envMxdIZc
$03/99DF 20 85 84 JSR $8485 [$03:8485] A:0007 X:0000 Y:0028 P:envMxdIzc
$03/8485 4A LSR A A:0007 X:0000 Y:0028 P:envMxdIzc
$03/8486 4A LSR A A:0003 X:0000 Y:0028 P:envMxdIzC
$03/8487 4A LSR A A:0001 X:0000 Y:0028 P:envMxdIzC
$03/8488 60 RTS A:0000 X:0000 Y:0028 P:envMxdIZC
$03/99E2 18 CLC A:0000 X:0000 Y:0028 P:envMxdIZC
$03/99E3 65 E3 ADC $E3 [$00:00E3] A:0000 X:0000 Y:0028 P:envMxdIZc
$03/99E5 91 80 STA ($80),y[$7E:2128] A:0000 X:0000 Y:0028 P:envMxdIZc
28 - Defense Multiplier
Now Evasion is calculated in this manner...
$03/9A00 A0 29 00 LDY #$0029 A:0023 X:0003 Y:0023 P:envMxdIZC
$03/9A03 18 CLC A:0023 X:0003 Y:0029 P:envMxdIzC
$03/9A04 A5 AA LDA $AA [$00:00AA] A:0023 X:0003 Y:0029 P:envMxdIzc
$03/9A06 6D 6C 39 ADC $396C [$7E:396C] A:00D1 X:0003 Y:0029 P:eNvMxdIzc
$03/9A09 20 2A 9E JSR $9E2A [$03:9E2A] A:00D1 X:0003 Y:0029 P:eNvMxdIzc
$03/9E2A C9 63 CMP #$63 A:00D1 X:0003 Y:0029 P:eNvMxdIzc
$03/9E2C 90 02 BCC $02 [$9E30] A:00D1 X:0003 Y:0029 P:envMxdIzC
$03/9E2E A9 63 LDA #$63 A:00D1 X:0003 Y:0029 P:envMxdIzC
$03/9E30 60 RTS A:0063 X:0003 Y:0029 P:envMxdIzC
It is all to do with AA's dual purposes which causes this noticeable problem.
Read one byte of Equipment (Evasion), store it in AA, move on to the next piece of equipment. (Shields are not read here for some reason however)
The real issue may be is that the AA register is never "cleaned" so to speak after putting in the data where it Should (Elemental Immunity) it should then be zeroed and allowed to calculate Evasion on its own merit.
$03/99AC 64 A9 STZ $A9 [$00:00A9] A:0000 X:0000 Y:0005 P:envMxdIZC
$03/99AE B1 82 LDA ($82),y[$7E:27F3] A:0000 X:0000 Y:0005 P:envMxdIZC
$03/99B0 05 A9 ORA $A9 [$00:00A9] A:0000 X:0000 Y:0005 P:envMxdIZC
$03/99B2 85 A9 STA $A9 [$00:00A9] A:0000 X:0000 Y:0005 P:envMxdIZC
This set of bytes here seem unnecessarily exact. Why would you set a value to Zero, load a new value in, check to see if the value is already there (which it plainly cannot be because you Just Zeroed It) and Store that value. Would it not be as easy to do this...?
$03/99AE B1 82 LDA ($82),y[$7E:27F3] A:0000 X:0000 Y:0005 P:envMxdIZC
$03/99B2 85 A9 STA $A9 [$00:00A9] A:0000 X:0000 Y:0005 P:envMxdIZC
Since the value will be overwritten anyhow. This may give use the space we need to fix the Immunity/Evasion byte without impacting anything else.
And finally, truly fixed!
Change this...
$03/99AC 64 A9 STZ $A9 [$00:00A9] A:0000 X:0000 Y:0005 P:envMxdIZC
$03/99AE B1 82 LDA ($82),y[$7E:27F3] A:0000 X:0000 Y:0005 P:envMxdIZC
$03/99B0 05 A9 ORA $A9 [$00:00A9] A:0000 X:0000 Y:0005 P:envMxdIZC
$03/99B2 85 A9 STA $A9 [$00:00A9] A:0000 X:0000 Y:0005 P:envMxdIZC
To this...
$03/99AC 64 A9 STZ $A9 [$00:00A9] A:0000 X:0000 Y:0005 P:envMxdIZC
$03/99AE B1 82 LDA ($82),y[$7E:27F3] A:0000 X:0000 Y:0005 P:envMxdIZC
$03/99B0 64 AA STZ $AA [$00:00AA] A:0000 X:0000 Y:0005 P:envMxdIZC
$03/99B2 85 A9 STA $A9 [$00:00A9] A:0000 X:0000 Y:0005 P:envMxdIZC
The reason the game continues to read evasion into it is because it is never properly reset like every other value I've seen thus far in the code (I wonder if this lack of 00 is part of the problem with the Immunities turning into weaknesses when the Immune equipment is removed...)
Now a new verdict...
An oversight, created likely by a hasty programmer that didn't double check their work, tying both Evasion and Elemental Immunity to one byte is fine in practice, but when you don't include the proper cleaning to old values they can cause issues like this.

Hmm, it seems as if that set of bytes isn't so useless after all...
$03/99AC 64 A9 STZ $A9 [$00:00A9] A:0000 X:0000 Y:0005 P:envMxdIZC
-----------------------------------------------------(Looping Point)------------------------------------------------
$03/99AE B1 82 LDA ($82),y[$7E:27F3] A:0000 X:0000 Y:0005 P:envMxdIZC
$03/99B0 05 A9 ORA $A9 [$00:00A9] A:0000 X:0000 Y:0005 P:envMxdIZC
$03/99B2 85 A9 STA $A9 [$00:00A9] A:0000 X:0000 Y:0005 P:envMxdIZC
Only for the start of this portion is the STZ A9 called to make the value 00, which it could be something else due to earlier variables. Then what these are doing are looking at each instance of armor, checking for Creature Resistance Bytes and adding them to A9 to eventually put into Byte 27 of character data. So I can't use that. I'm not really sure what I can replace to put an STZ AA anywhere.
$03/99EC 18 CLC A:0000 X:0000 Y:0002 P:envMxdIZc
$03/99ED B1 82 LDA ($82),y[$7E:27F0] A:0000 X:0000 Y:0002 P:envMxdIZc
$03/99EF 29 7F AND #$7F A:0000 X:0000 Y:0002 P:envMxdIZc
$03/99F1 65 AA ADC $AA [$00:00AA] A:0000 X:0000 Y:0002 P:envMxdIZc
$03/99F3 85 AA STA $AA [$00:00AA] A:0081 X:0000 Y:0002 P:eNvMxdIzc
$03/99F5 98 TYA A:0081 X:0000 Y:0002 P:eNvMxdIzc
$03/99F6 18 CLC A:0002 X:0000 Y:0002 P:envMxdIzc
$03/99F7 69 0B ADC #$0B A:0002 X:0000 Y:0002 P:envMxdIzc
$03/99F9 A8 TAY A:000D X:0000 Y:0002 P:envMxdIzc
$03/99FA E8 INX A:000D X:0000 Y:000D P:envMxdIzc
$03/99FB E0 03 00 CPX #$0003 A:000D X:0001 Y:000D P:envMxdIzc
$03/99FE D0 EC BNE $EC [$99EC] A:000D X:0001 Y:000D P:eNvMxdIzc
A smart programmer would have put an STZ AA here at the start as they did with other sections before the looping point. But the lack of room and available bytes makes it a bit of a risky game to do so. All it takes is an STZ AA before this point, but I'm not sure where it could go.