News:

11 March 2016 - Forum Rules

Main Menu

Final Fantasy II Restored

Started by redmagejoe, December 10, 2019, 03:09:14 AM

Previous topic - Next topic

abw

Quote from: redmagejoe on February 03, 2020, 09:56:52 PM
Also would the underflow fix as I suggested work, using the same sort of fix you used to avoid underflow on weapons, which I also applied to spell levels? Having an extra branch after the first SBC?
Yup, adding a BCC after the SBC to check for underflow is what I'd do to fix that issue.

Quote from: redmagejoe on February 03, 2020, 09:56:52 PM
I didn't realize Blood Sword did extra damage to the Emperor, I was only aware that it did # of hits x 1/16 of target's Max HP, ensuring that 16 hits 1-shots anything.
Ah, that must be what I was mis-remembering then - there's no bonus damage, it's just that the Emperor's Max HP is so high that hitting for 1/16 of it is over powered.

Quote from: redmagejoe on February 03, 2020, 09:56:52 PM
@abw: Did you change this following chunk for either your HP/MP cap fix or the weapon level up fix?
Yes, that section was part of the HP/MP level up routine, and I cannibalized it for checking current HP/MP against the appropriate cap.

Quote from: redmagejoe on February 03, 2020, 09:56:52 PM
Yep, I'm in the painful process of doing that as we speak.
[...]
Once I finish this time-consuming code-shifting/pointer-adjusting/optimizing/consolidating project
[..]
Finally finished... I did it the way hard way, but I double-checked every pointer, every branch, adjusted as necessary, and even had to take account for pointers from outside the scope of what was changing.
You might want to think about taking an hour to get an assembler and learn its syntax. Letting the assembler do all the pointer calculations for you avoids major headaches :P.

redmagejoe

#201
I will probably sit down and do that soon. For today I have to take a mental break. That was draining. I worked the Evasion/MDef underflow fix in, updated pointers and branch labels (and a couple offsets on the branches) again, and we still have 16 bytes to work with.

Also just finally got around to updating the Gil Cap Fix, don't know how it took me so long to get back to that. Reviewing my other fixes for consistency while I'm at it.

EDIT: All my fixes seem good. If the redone Level Up Rework patch checks out, I'll look at how to approach the Evasion/MDef delay. abw's presented solution works conceptually, but I have to think if there's still a need for a stat refresh. My save is from before a lot of the fixes, so it has old stats, so that may be affecting my view on whether a full stat refresh is needed. I guess the question is if any stats aren't already updated besides Evasion and MDef before the player goes to their stat window, since in my case it has to apply the post-fixed values to a pre-fixed save.




Quote from: abw on February 03, 2020, 09:49:25 PM
That ought to make the displayed value match the actual damage, but in this case probably the right fix is to make the damage match the displayed value.

Yeah, we want it to actually do that bonus damage.

Quote from: abw on February 03, 2020, 09:49:25 PM
For the fix, I'd probably adjust the value of Y to match the stat offset and then INC ($7A),Y when the skill level increases.

I'm just trying to think of how to do that. 30 and 32 are used for Evasion Level and MDef Level from 6200 ($7E), but they're 2A and 2C from 6100 ($7A), so at least they're both offset by 6. Would I simply...


0x016871|$05:$A861:20 6D A8 JSR $A86D  ; cap X at 15
0x016874|$05:$A864:8A      TXA       
0x016875|$05:$A865:91 7E    STA ($7E),Y ; save new Evasion/Magic Resist skill level
0x016877|$05:$A867:C8      INY        ; offset for Evasion/Magic Resist skill experience, whichever one we're dealing with at the moment
0x016878|$05:$A868:A9 00    LDA #$00    ; reset experience to zero
; control flow target (from $A85A)
0x01687A|$05:$A86A:91 7E    STA ($7E),Y ; save new Evasion/Magic Resist skill experience
; control flow target (from $A846, $A853)
0x01687C|$05:$A86C:60      RTS


Since the new code takes TXA, STA, and INY into the JSR, that's unavoidable, so maybe before the RTS, since X register is still holding a copy of our new skill level, and Y doesn't need to be preserved returning from sub, could insert (with the 16 bytes we've freed up)...


TYA          1 byte
SBC #$06     2 bytes (this sets it to 2A or 2C as needed)
TAY          1 byte
TXA          1 byte
STA ($7A),Y  2 bytes (should now, in Firion's case, be setting the new skill level to $612A or $612C)


Don't know if that works right.

BlazeHeatnix

Wait, what is/was wrong with the gil cap/level up patches that you needed to rework them?

redmagejoe

#203
Quote from: BlazeHeatnix on February 04, 2020, 06:11:35 PM
Wait, what is/was wrong with the Gil cap/level up patches that you needed to rework them?

For the Gil Cap patch, I had mistakenly NOP'd out some code that accounted for the last 128 Gil before the cap, but I've since reverted those changes with only the three branch fixes. The only thing that needed to be changed where the type of branch and number of bytes to skip in three of the branches. The version available in the first post link is now the updated version, and should work properly in all cases. If you're concerned because you've applied the previous version, the new version can be patched directly on top of your ROM with no adverse effects.

As for the level up patches, they were made with respect to available space, but as we're crunching code and optimizing it, there's a lot of otherwise unused space that, if it were side-by-side to the other free space, could be used for bug fixes that require expanding the existing routines. So that's currently what we're doing, is crunching the code, but that also involves updating pointers to ensure that we don't have code pointing to instructions they shouldn't and crashing the game. All of the fixes we've made to Weapon Levels, Spell Levels, Maxed Stat Level Ups, Capping HP/MP, as well as underflows with respect to those first 3, are in the same general vicinity, so we're consolidating the code, optimizing it using better ways using less bytes to accomplish the same things, and freeing up 16+ bytes to be used for further fixes in the future or to just give us more room to work with while improving the code.

EDIT: Just corrected a small mistake where I forgot to update an OPcode for a data table read, but it wasn't game-breaking. Just temporarily bugged the HP/MP cap fixes. I'm putting a link to the updated bytes here, and I sent one to abw for review. Assuming everything works as it should, I can bundle 4 patches into one, and also address that Evasion/MDef delay.

https://drive.google.com/open?id=1W-B-13h1aeSUdA4pLN_Nvs400gtRwjcA




My above proposed fix for the evasion/mdef delay seems to have had no effect. It seems contingent upon a stat refresh being forced somehow, and there's no way I can think to do that automatically without the player changing equipment or entering another battle on account of the different banks.

EDIT: Since we're already getting in the habit of optimizing, and we may need to free up and move around as much space as possible down the road, I've decided to update the optional Mysidian Orbs patch as per abw's suggestion. Link in first post has been updated.


; handler for bottom left orb, Fire Crystal
; indirect control flow target (via $99A5)
0x03A1BF|$0E:$A1AF:A9 10    LDA #$10    ; Stat offset for base Strength
0x03A1C1|$0E:$A1B1:D0 0A    BNE $A1BD
; handler for bottom right orb, Wind Crystal
; indirect control flow target (via $99A7)
0x03A1C3|$0E:$A1B3:A9 11    LDA #$11    ; Stat offset for base Agility
0x03A1C5|$0E:$A1B5:D0 06    BNE $A1BD
; handler for top left orb, Earth Crystal
; indirect control flow target (via $99A9)
0x03A1C7|$0E:$A1B7:A9 14    LDA #$14    ; Stat offset for base Spirit
0x03A1C9|$0E:$A1B9:D0 02    BNE $A1BD
; handler for top right orb, Water Crystal
; indirect control flow target (via $99AB)
0x03A1CB|$0E:$A1BB:A9 13    LDA #$13    ; Stat offset for base Intellect
; control flow target (from $A1B1, $A1B5, $A1B9, $A1D4)
0x03A1CD|$0E:$A1BD:AA    TAX             <<<
0x03A1CE|$0E:$A1BE:BD 00 61 LDA $6100,X ; Character #1 ID/portrait/sprite (00: Firion, 01: Maria, 02: Guy, 03: Minwu, 04: Josef, 05: Gordon, 06: Leila, 07: Ricard, 08: Leon); load A with baseline for character stats, X as offset
0x03A1D1|$0E:$A1C1:20 DF A1 JSR $A1DF           <<<
0x03A1D4|$0E:$A1C4:9D 00 61 STA $6100,X ; Character #1 ID/portrait/sprite (00: Firion, 01: Maria, 02: Guy, 03: Minwu, 04: Josef, 05: Gordon, 06: Leila, 07: Ricard, 08: Leon); store new value of stat, baseline for character stats, X as offset
0x03A1D7|$0E:$A1C7:BD 10 61 LDA $6110,X ; Character #1 Base Strength; base character stats that display in stat window (base + equipment bonuses), X as offset
0x03A1DA|$0E:$A1CA:20 DF A1 JSR $A1DF           <<<
0x03A1DD|$0E:$A1CD:9D 10 61 STA $6110,X ; Character #1 Base Strength; store new value of stat, baseline for character's window stats, X as offset
0x03A1E0|$0E:$A1D0:8A      TXA             <<<
0x03A1E1|$0E:$A1D1:18      CLC             <<<
0x03A1E2|$0E:$A1D2:69 40  ADC #$40        <<<
0x03A1E4|$0E:$A1D4:90 E7  BCC $A1BD       <<<
0x03A1E6|$0E:$A1D6:A4 A0    LDY $A0
0x03A1E8|$0E:$A1D8:20 07 99 JSR $9907
0x03A1EB|$0E:$A1DB:AD 00 7B LDA $7B00
0x03A1EE|$0E:$A1DE:60      RTS

; control flow target (from $A1C1, $A1CA)
0x03A1EF|$0E:$A1DF:18      CLC
0x03A1F0|$0E:$A1E0:69 0A    ADC #$0A    ; adds 10 to the appropriate stat
0x03A1F2|$0E:$A1E2:C9 64    CMP #$64    ; prevent stat from going above 99, compare to 100
0x03A1F4|$0E:$A1E4:90 02    BCC $A1E8  ; does not exceed cap, proceed to store new stat value
0x03A1F6|$0E:$A1E6:A9 63    LDA #$63    ; set stat to 99 if >99
0x03A1F8|$0E:$A1E8:60 RTS

0x03A1F9|$0E:$A1E9:EA      NOP
0x03A1FA|$0E:$A1EA:EA      NOP
0x03A1FB|$0E:$A1EB:EA      NOP
0x03A1FC|$0E:$A1EC:EA      NOP


@abw Is there an easy way to test for the bounds of your new RNG, or is it likely that the current form of the RNG patch is as small as it's going to get?




For Ripper, by the time the code gets to 0x033341, $9A and $9B have already been put somewhere else to be used for damage, so the code below is purely for display numbers.


; control flow target (from $B177, $B227)
0x033341|$0C:$B331:18      CLC
0x033342|$0C:$B332:A5 9A    LDA $9A ; low byte of damage dealt to target before animations
0x033344|$0C:$B334:65 04    ADC $04 ; add Ripper damage low byte
0x033346|$0C:$B336:85 9A    STA $9A ; store low byte of damage to DISPLAY
0x033348|$0C:$B338:A5 9B    LDA $9B ; high byte of damage dealt to target before animations
0x03334A|$0C:$B33A:65 05    ADC $05 ; add Ripper damage high byte
0x03334C|$0C:$B33C:85 9B    STA $9B ; store high byte of damage to DISPLAY
0x03334E|$0C:$B33E:60      RTS


I'll see if I can't figure out the moment damage is finalized and see what would have to be done to have Ripper damage checked before this. This makes me wonder if any weapons with special qualities (are there weapons that deal bonus damage to certain types like in 1?) suffer this same issue. https://strategywiki.org/wiki/Final_Fantasy_II/Weapons

Based on values in RAM, I believe that the code below is where the enemy's HP is calculated, prior to any application of Ripper damage. The LDAs and STAs from 330E6 down in RAM hold $7ED4 and $7ED5, which I'm assuming to be that particular enemy's HP in combat?


; control flow target (from $B0A0)
0x0330BF|$0C:$B0AF:A0 02    LDY #$02   
0x0330C1|$0C:$B0B1:B1 A1    LDA ($A1),Y
0x0330C3|$0C:$B0B3:85 46    STA $46   
0x0330C5|$0C:$B0B5:A9 00    LDA #$00   
0x0330C7|$0C:$B0B7:85 47    STA $47   
0x0330C9|$0C:$B0B9:A5 9C    LDA $9C   
0x0330CB|$0C:$B0BB:85 48    STA $48   
0x0330CD|$0C:$B0BD:20 88 BC JSR $BC88 
0x0330D0|$0C:$B0C0:A5 4A    LDA $4A   
0x0330D2|$0C:$B0C2:85 9A    STA $9A   
0x0330D4|$0C:$B0C4:A5 4B    LDA $4B   
0x0330D6|$0C:$B0C6:85 9B    STA $9B   
0x0330D8|$0C:$B0C8:A5 9B    LDA $9B   
0x0330DA|$0C:$B0CA:29 80    AND #$80   
0x0330DC|$0C:$B0CC:F0 06    BEQ $B0D4 
0x0330DE|$0C:$B0CE:A9 00    LDA #$00   
0x0330E0|$0C:$B0D0:85 9A    STA $9A   
0x0330E2|$0C:$B0D2:85 9B    STA $9B   
; control flow target (from $B0CC)
0x0330E4|$0C:$B0D4:A0 0A    LDY #$0A   
0x0330E6|$0C:$B0D6:B1 A1    LDA ($A1),Y
0x0330E8|$0C:$B0D8:38      SEC       
0x0330E9|$0C:$B0D9:E5 9A    SBC $9A   
0x0330EB|$0C:$B0DB:91 A1    STA ($A1),Y
0x0330ED|$0C:$B0DD:C8      INY       
0x0330EE|$0C:$B0DE:B1 A1    LDA ($A1),Y
0x0330F0|$0C:$B0E0:E5 9B    SBC $9B   
0x0330F2|$0C:$B0E2:91 A1    STA ($A1),Y
0x0330F4|$0C:$B0E4:90 08    BCC $B0EE 
0x0330F6|$0C:$B0E6:B1 A1    LDA ($A1),Y
0x0330F8|$0C:$B0E8:88      DEY       
0x0330F9|$0C:$B0E9:11 A1    ORA ($A1),Y
0x0330FB|$0C:$B0EB:D0 1F    BNE $B10C 
0x0330FD|$0C:$B0ED:C8      INY


Note that this occurs before the final break on read/write $9A/$9B, which is when Ripper damage is calculated. I assume the issue is that the final calculation should be done BEFORE ($A1) has math performed on it.

abw

Quote from: redmagejoe on February 04, 2020, 10:26:44 AM
30 and 32 are used for Evasion Level and MDef Level from 6200 ($7E), but they're 2A and 2C from 6100 ($7A), so at least they're both offset by 6.
Yeah, it would have been nicer for us if they had made the offsets line up, but alas :P.

Quote from: redmagejoe on February 04, 2020, 10:26:44 AM

TYA          1 byte
SBC #$06     2 bytes (this sets it to 2A or 2C as needed)
TAY          1 byte
TXA          1 byte
STA ($7A),Y  2 bytes (should now, in Firion's case, be setting the new skill level to $621A or $621C)


Don't know if that works right.
Quote from: redmagejoe on February 04, 2020, 06:29:13 PM
My above proposed fix for the evasion/mdef delay seems to have had no effect.
You'll also want to SEC since C could be clear or set at this point, and you'll want to INX since X has the skill level, which is 1 less than the stat level. If you just write the skill level, you're actually making things worse, since now your stat level will always be wrong after a battle :P.

Quote from: redmagejoe on February 04, 2020, 06:29:13 PM
@abw Is there an easy way to test for the bounds of your new RNG, or is it likely that the current form of the RNG patch is as small as it's going to get?
I haven't gotten around to this yet. If you want to help, it's the 2 calls to $0F:$FD11 at $0B:$B21B and $0B:$B232 that I'm concerned about - the values for X and A are the results of arithmetic operations that may or may not overflow based on the contents of RAM locations whose meaning we haven't identified yet. If we can establish that X <= A in all cases, my RNG fix can be shortened significantly.

Quote from: redmagejoe on February 04, 2020, 06:29:13 PM
This makes me wonder if any weapons with special qualities (are there weapons that deal bonus damage to certain types like in 1?) suffer this same issue.
Family and Elemental bonus damage gets added to the total before reducing the target's HP, so that looks like it's working. All the special effects come after applying damage, however, so they each need to update the applied damage according to their effect.

Quote from: redmagejoe on February 04, 2020, 06:29:13 PM
For Ripper, by the time the code gets to 0x033341, $9A and $9B have already been put somewhere else to be used for damage, so the code below is purely for display numbers.
I've updated my disassembly with more labels and comments. It looks like you're right about the Ripper's effect taking place after damage is dealt - target HP gets reduced at $0C:$B0D4 and during the drain HP special effect. Based on the code, I think I'm seeing a couple more bugs here:

  • if you manage to kill a monster with the drain bonus damage, you don't get the usual death message
  • attacking Undead with a Heal Staff has the same issue with updating the displayed damage but not the actual damage, which is more severe here since the heal effect adds between 30 and 60 bonus damage per hit compared to Ripper's +20

redmagejoe

#205
Ripper and Heal Staff can likely both be fixed with the same approach then, when I finish up my latest work project. Heal Staff is another documented bug, but it's nice to know that we may be able to address two at once. I will have to try and sort my thoughts and clear some things off my to-do list, as I've got too many things bouncing around in my head right now, like whether I can call the level up optimization "stable", the testing the RNG fix, and the Evasion/MDef delay thing, even though I opened a can of worms with chasing Ripper behavior around.

Have you had a chance to have a look at the optimized level-up fix? I'll also take a look at those calls to the RNG to see if I can't make sense of what they're doing. I will also give your suggestion regarding the update to Evasion/MDef a try. As I said before, I don't THINK that there's any other stats that need to be force-updated, and it may simply be due to my pre-fix save.




Ah, the INX was what I forgot before. I don't know what math is being done down the line on the value when a stat refresh is done, but I was afraid to increment for fear of a double increase. But I managed to increment the value and not cause it to go up twice, so... I have no idea what's been frontloaded. Is it simply comparing $6233 and $621C during equipment switching or starting a battle, and if they don't match, set 621C to 6233? Bizarre. I'll rework the optimized level up patch with a more elegant version of my current jury-rigged test fix, and that should be one less thing on the to-do list. I'll try to use up as little of the 16 bytes as possible, after all the work I went through to free it up. :)

EDIT: Google Drive link to optimization rewrite has been updated. I'm hoping that someone with sharp eyes might see where I can fit this in... In it's current state, I'd have to essentially hijack the current JSR into a new JSR to this fix, with the old JSR at the head. It's just such a headache with how many bytes are being wasted moving data and offsets around. There must be a more elegant way to apply this, but I've also pigeon-holed myself by pulling so many common elements into the previously small Cap at 15 sub. Still, that saved far more space than this is trying to take up, so... We're limited. I'll take another look at this when I'm fresh and at 100%.




I'm having a hard time even finding where $0B:$B21B and $0B:$B232 fire. Set breakpoints on execute for both of them, and haven't had a break yet in combat at least. Trying to think of all possible scenarios where RNG would be used. I'll keep trying. EDIT: Ah! Just had $B21B fire followed by $B232... Let's see what these are tied to. Huh... They seem to be RNG used to determine where the arrow sprites appear on the enemy when a bow weapon is fired. It fired 4 times before each of the 4 arrow animations on a 16x hit, so 4 sets of arrows (and 4 of them per set). So I guess those 2 calls to the RNG are used to determine where each of the up to 16 arrow sprites appears on the enemy. Maybe X position followed by Y position? That's it. Not sure that changing the RNG is going to break that somehow, but mystery solved, abw.




I'm realizing that there's a large block of free space that could be consolidated in the end of the ROM, if we were theoretically going to do code crunch to make room for enhancements down the line, where right before the "FINAL FANTASY 2" we could push those two 13-byte blocks down and adjust pointers, so that that 3-byte and 19-byte could be combined with the 194-byte block for a 216-byte contiguous block. That's just some musing that may not be relevant until later, though I may also need to make an update to Chaos Rush's translation, as it appears to use 0x33F10 to 0x33FBB, which falls right in the center of the largest block. Just needs to be shifted back about 20 bytes so we still have 196 bytes to work with, but I have no idea what it's supposed to be. It doesn't show up in his CastleFynn list, so I'm assuming it's data (possibly for spell name DTE?). Still, it's promising to see such a large chunk of available space in fixed bank.

abw

Quote from: redmagejoe on February 09, 2020, 10:36:33 PM
Ripper and Heal Staff can likely both be fixed with the same approach then, when I finish up my latest work project.
I was actually working on fixing the special effects, but it's going to take some more work to track down all the missing parts. Related to that, here's one more special effect bug:

  • HP changes from a special effect should update the target's critical HP status, but do not
I still need to test this, but it looks like another thing with the Heal Staff against a non-Undead target is that first you lose HP based on its normal damage and then you gain HP based on its normal damage + its bonus damage. So, say a hit inflicts 100 normal damage and 50 bonus damage. For a target with 300 HP, the target loses 100 HP and then gains 150 HP for a net gain of 50 HP. For a target with only 10 HP, however, the 100 normal damage kills them, setting their HP to 0, and then they gain 150 HP for a net gain of 140 HP. This should probably also be considered a bug, though it wouldn't be too hard to come up with some sort of life-force-y/more-powerful-the-closer-you-are-to-death explanation for the behaviour.

Quote from: redmagejoe on February 09, 2020, 10:36:33 PM
Have you had a chance to have a look at the optimized level-up fix?
Ah, no, sorry, not yet. I've been kind of tied up with other things lately :(.

Quote from: redmagejoe on February 09, 2020, 10:36:33 PM
Is it simply comparing $6233 and $621C during equipment switching or starting a battle, and if they don't match, set 621C to 6233?
No, it's simply always setting the stat level (1-16) to the skill level (0-15) +1.

Quote from: redmagejoe on February 09, 2020, 10:36:33 PM
Ah! Just had $B21B fire followed by $B232... Let's see what these are tied to. Huh... They seem to be RNG used to determine where the arrow sprites appear on the enemy when a bow weapon is fired. It fired 4 times before each of the 4 arrow animations on a 16x hit, so 4 sets of arrows (and 4 of them per set). So I guess those 2 calls to the RNG are used to determine where each of the up to 16 arrow sprites appears on the enemy. Maybe X position followed by Y position?
Huh, that's neat, I had no idea the arrow animations were that intricate, potentially showing each individual arrow that hits, spread out over multiple volleys, with each arrow given its own random position (possibly overlapping with other arrows) based on the location and apparent size of the target, and displayed for different distances. I didn't trace backwards through all the possible branching, but it looks like the source data for those calculations is based on numbers that are small but not too small, so while I'm still not 100% sure, I'm reasonably confident that the calculations should end up setting X <= A, which means removing the bounds checking on the RNG is probably safe. I've update the original link with the new version, which is now a total of 9 bytes shorter than the original code.

redmagejoe

#207
I've also updated the rework, seeing where I'd forgotten to incorporate the max spells outside combat still gaining experience fix. Nothing's exploding, I'm just worried that I've overlooked a branch or a JSR somewhere that's going to have subtle ramifications. Also, awesome to hear ABW! I'll add this to the latest stable ROM I'm working on and update the first post. I feel like our project is coming along nicely. :beer:

Hope you don't mind, but I NOP'd out those 9 freed up bytes as a breadcrumb for us for future code-shifting should we need it. Updated the first post.

EDIT: There's a minor graphical bug (not game-breaking and only appears for 1 or 2 frames, but still jarring) that occurs with your RNG fix, where after all party member orders are issued and the battle windows shift to prepare to execute the actions, a bunch of horizontal line fragments appear for the briefest moment. Any idea how the RNG change could be causing this? I isolated the RNG patch and tried your base version and my post-NOP version, with the same effect. Stepping through line by line to see the exact frame/instruction that the glitch appears on, though it's dealing with the RNG so... it's going to take a LONG time and probably hundreds of steps. So I set the breakpoint to execute $FD11, and after issuing orders, MANY calls to the RNG happen. However, the graphical glitch always occurs almost immediately at the 11th call to RNG, and does not clear up until the 14th call (and who knows how many more calls happen after that), but the point is that for call 11 of the RNG, and through calls 12 and 13, this fix causes weird graphic bugs. I'm guessing it has something to do with how the general-use RAM addresses ($01 - $08) are used compared to the original code, but hell if I have any idea exactly how. I'm assuming that after the 10th call to RNG and before the 11th, graphics are being updated using a value in RAM that isn't what it should be, and the graphics update again only after 4 more runs of the RNG.

Watching the same breaks and RAM values in the unmodified version, the only "wildcard" values appear to be $06 and $05. But $05 isn't used in the patched version. All I can think is that something we're doing with $06 may be an issue? I'll try to analyze the values of $06 at calls 10 in both of them and see if I can't determine a correlation. Added a read/write to $06 break after the 10th call to RNG and followed it until the moment the graphics bugged, and as much as I hate to say it, I'm wondering if it's an overflow issue. The values in the original don't get very high when LDA $06 and ADC $02 are happening, usually around $27 + $3F. In this case, $06 is going up pretty high...


> 03FD02:A5 06     LDA $0006 = #$D9      <<<
  03FD04:65 02     ADC $0002 = #$28      <<<
  03FD06:85 06     STA $0006 = #$D9
  03FD08:A5 07     LDA $0007 = #$FF
  03FD0A:65 03     ADC $0003 = #$00
  03FD0C:85 07     STA $0007 = #$FF
  03FD0E:18        CLC





Work keeps piling up, but I'm trying to find time to focus on our current items. Mostly want to be sure that I haven't overlooked anything on the Level Up Rework before I call that done, and even then, I need to find out if I've created the most efficient Evasion/MDef delay fix that uses as little of the free space as possible. I fear that the abstraction between instructions and outcome is making it difficult for me to think how to cleverly work it in other than the instructions I wrote, which rely on working around the existing framework rather than trying to cleverly make use of the behavior to fit my fix in. That has been my biggest hurdle so far, and I'm hoping that I get comfortable enough to write changes that don't throw a wrench in the current behavior. If possible, I'd like to try and come up with a way to work that fix in before it changes offsets during the level-up check, rather than after the fact. I just don't know if I can make it any more concise than its current form.

Google Drive link has been updated to reflect my current WIP test fix. I've already tested with mentioned changed JSRs and Branch offsets, I just don't know if I want to commit this change yet if someone can see a cleaner way to do this.

https://drive.google.com/open?id=1W-B-13h1aeSUdA4pLN_Nvs400gtRwjcA

Quote from: redmagejoe on December 10, 2019, 03:09:14 AM
  • Aura 8 does not grant Undead bonus attack, and Barrier 8 does not grant Ice protection. Investigate cause.

Also, maybe I'm just a fool, but isn't this an easy fix? Is this not simply a case of the offset of spell levels ($00 = Level 1) not matching up with the math required for the level 8 effects of these spells? Would not changing the CPX #$08 / LDA #$07 at $B751 onward to $09 and $08 fix these bugs?


; handler for Spell IDs #$D7: Esuna, #$D6: Basuna, #$D8: Barrier, #$D3: Aura
; indirect control flow target (via $BE9A, $BE9C, $BEA0, $BEA2)
0x0336DD|$0C:$B6CD:20 E9 BD JSR $BDE9 
0x0336E0|$0C:$B6D0:A5 5E    LDA $5E   
0x0336E2|$0C:$B6D2:C9 0A    CMP #$0A   
0x0336E4|$0C:$B6D4:90 03    BCC $B6D9 
0x0336E6|$0C:$B6D6:4C 2C B7 JMP $B72C 

; control flow target (from $B6D4)
0x0336E9|$0C:$B6D9:48      PHA       
0x0336EA|$0C:$B6DA:18      CLC       
0x0336EB|$0C:$B6DB:A0 28    LDY #$28    ; Battle stat offset for Mystery Battle Stat #$28
0x0336ED|$0C:$B6DD:B1 9F    LDA ($9F),Y ; pointer to actor's battle stats
0x0336EF|$0C:$B6DF:65 48    ADC $48   
0x0336F1|$0C:$B6E1:85 48    STA $48   
0x0336F3|$0C:$B6E3:68      PLA       
0x0336F4|$0C:$B6E4:C9 08    CMP #$08   
0x0336F6|$0C:$B6E6:D0 13    BNE $B6FB 
0x0336F8|$0C:$B6E8:A0 08    LDY #$08    ; Battle stat offset for Ailment
0x0336FA|$0C:$B6EA:84 5E    STY $5E   
0x0336FC|$0C:$B6EC:B1 A1    LDA ($A1),Y ; pointer to target's battle stats
0x0336FE|$0C:$B6EE:A0 2C    LDY #$2C    ; Battle stat offset for Ailment backup
0x033700|$0C:$B6F0:84 5F    STY $5F   
0x033702|$0C:$B6F2:91 A1    STA ($A1),Y ; pointer to target's battle stats
0x033704|$0C:$B6F4:A0 08    LDY #$08   
0x033706|$0C:$B6F6:A6 48    LDX $48   
0x033708|$0C:$B6F8:4C 0D B7 JMP $B70D 

; control flow target (from $B6E6)
0x03370B|$0C:$B6FB:A0 09    LDY #$09    ; Battle stat offset for Mystery Battle Stat #$09 (bit field; 02:critical HP)
0x03370D|$0C:$B6FD:84 5E    STY $5E   
0x03370F|$0C:$B6FF:B1 A1    LDA ($A1),Y ; pointer to target's battle stats
0x033711|$0C:$B701:A0 2D    LDY #$2D    ; Battle stat offset for Mystery Battle Stat #$2D (backup of Mystery Battle Stat #$09?)
0x033713|$0C:$B703:84 5F    STY $5F   
0x033715|$0C:$B705:91 A1    STA ($A1),Y ; pointer to target's battle stats
0x033717|$0C:$B707:A0 09    LDY #$09    ; Battle stat offset for Mystery Battle Stat #$09 (bit field; 02:critical HP)
0x033719|$0C:$B709:A6 48    LDX $48   
0x03371B|$0C:$B70B:E8      INX       
0x03371C|$0C:$B70C:E8      INX       
; control flow target (from $B6F8)
0x03371D|$0C:$B70D:E0 08    CPX #$08   
0x03371F|$0C:$B70F:90 02    BCC $B713 
0x033721|$0C:$B711:A2 07    LDX #$07   
; control flow target (from $B70F)
0x033723|$0C:$B713:B1 A1    LDA ($A1),Y ; pointer to target's battle stats
; control flow target (from $B719)
0x033725|$0C:$B715:20 0E 90 JSR $900E 
0x033728|$0C:$B718:CA      DEX       
0x033729|$0C:$B719:10 FA    BPL $B715 
0x03372B|$0C:$B71B:91 A1    STA ($A1),Y ; pointer to target's battle stats
0x03372D|$0C:$B71D:A4 5E    LDY $5E   
0x03372F|$0C:$B71F:B1 A1    LDA ($A1),Y ; pointer to target's battle stats
0x033731|$0C:$B721:85 4E    STA $4E   
0x033733|$0C:$B723:A4 5F    LDY $5F   
0x033735|$0C:$B725:B1 A1    LDA ($A1),Y ; pointer to target's battle stats
0x033737|$0C:$B727:85 4F    STA $4F   
0x033739|$0C:$B729:4C 84 B7 JMP $B784 

; control flow target (from $B6D6)
0x03373C|$0C:$B72C:48      PHA       
0x03373D|$0C:$B72D:A9 49    LDA #$49    ; Battle Message ID #$49: 'White '
0x03373F|$0C:$B72F:85 5C    STA $5C   
0x033741|$0C:$B731:A9 D1    LDA #$D1    ; Battle Message ID #$51: 'Aura'
0x033743|$0C:$B733:85 5D    STA $5D   
0x033745|$0C:$B735:68      PLA       
0x033746|$0C:$B736:C9 0C    CMP #$0C   
0x033748|$0C:$B738:F0 0D    BEQ $B747 
0x03374A|$0C:$B73A:A9 40    LDA #$40    ; Battle Message ID #$40: 'Ice'
0x03374C|$0C:$B73C:85 5C    STA $5C   
0x03374E|$0C:$B73E:A9 C8    LDA #$C8    ; Battle Message ID #$48: ' Df'
0x033750|$0C:$B740:85 5D    STA $5D   
0x033752|$0C:$B742:A0 05    LDY #$05    ; Battle stat offset for Armor Resistance bits (80:Ice, 40:Body, 20:Poison, 10:Death, 08:Lightning, 04:Mind, 02:Fire, 01:Matter)
0x033754|$0C:$B744:4C 4D B7 JMP $B74D 

; control flow target (from $B738)
0x033757|$0C:$B747:A0 1C    LDY #$1C    ; Battle stat offset for Primary Hand Weapon Monster Family Bonus (80:Undead, 40:Werebeast, 20: Dragon, 10:Spellcaster, 08:Giant, 04:Earth, 02:Aquatic, 01:Magic Beast)
0x033759|$0C:$B749:20 4D B7 JSR $B74D 
0x03375C|$0C:$B74C:60      RTS       

; control flow target (from $B744, $B749)
0x03375D|$0C:$B74D:B1 A1    LDA ($A1),Y ; pointer to target's battle stats
0x03375F|$0C:$B74F:A6 48    LDX $48   
0x033761|$0C:$B751:E0 08    CPX #$08   
0x033763|$0C:$B753:90 02    BCC $B757 
0x033765|$0C:$B755:A2 07    LDX #$07   
; control flow target (from $B753)
0x033767|$0C:$B757:CA      DEX       
0x033768|$0C:$B758:30 24    BMI $B77E 
0x03376A|$0C:$B75A:86 48    STX $48   
; control flow target (from $B760)
0x03376C|$0C:$B75C:20 0A 90 JSR $900A 
0x03376F|$0C:$B75F:CA      DEX       
0x033770|$0C:$B760:10 FA    BPL $B75C 
0x033772|$0C:$B762:91 A1    STA ($A1),Y ; pointer to target's battle stats
0x033774|$0C:$B764:A2 00    LDX #$00   
0x033776|$0C:$B766:86 5F    STX $5F   
; control flow target (from $B77C)
0x033778|$0C:$B768:A5 5C    LDA $5C   
0x03377A|$0C:$B76A:20 92 BF JSR $BF92  ; add battle message ID A to the list of battle messages to display
0x03377D|$0C:$B76D:A5 5D    LDA $5D   
0x03377F|$0C:$B76F:20 92 BF JSR $BF92  ; add battle message ID A to the list of battle messages to display
0x033782|$0C:$B772:A5 48    LDA $48   
0x033784|$0C:$B774:C5 5F    CMP $5F   
0x033786|$0C:$B776:F0 09    BEQ $B781 
0x033788|$0C:$B778:E6 5F    INC $5F   
0x03378A|$0C:$B77A:E6 5C    INC $5C   
0x03378C|$0C:$B77C:D0 EA    BNE $B768 
; control flow target (from $B758)
0x03378E|$0C:$B77E:20 73 BE JSR $BE73 
; control flow target (from $B776)
0x033791|$0C:$B781:4C 7E BE JMP $BE7E

abw

#208
More updates to the disassembly posted in the original link!

Quote from: redmagejoe on February 14, 2020, 12:06:19 AM
EDIT: There's a minor graphical bug (not game-breaking and only appears for 1 or 2 frames, but still jarring) that occurs with your RNG fix
Well, that's depressing. Comparing trace logs of the original and modified ROMs, it looks like the new RNG routine is taking too long (I knew it was longer, but didn't know how much that would matter), and when called too many times per frame it was causing code to run into vblank time. I've updated the link with a faster version that's closer to the original code; the downside is that the output is non-uniform for ranges that don't divide into 256 evenly, but the variation from uniform is never more than 1/256, so it's still far closer to uniform than the original code's distribution, which could vary from uniform by as much as 43/256. Also, the new RNG value is never more than 1 different from the old RNG value, so it's definitely a less disruptive change.

Quote from: redmagejoe on February 14, 2020, 12:06:19 AM
I'm wondering if it's an overflow issue. The values in the original don't get very high when LDA $06 and ADC $02 are happening, usually around $27 + $3F. In this case, $06 is going up pretty high...


> 03FD02:A5 06     LDA $0006 = #$D9      <<<
  03FD04:65 02     ADC $0002 = #$28      <<<
  03FD06:85 06     STA $0006 = #$D9
  03FD08:A5 07     LDA $0007 = #$FF
  03FD0A:65 03     ADC $0003 = #$00
  03FD0C:85 07     STA $0007 = #$FF
  03FD0E:18        CLC

That's part of the division routine; possible overflow there is expected and the carry needs to make its way into $07 in order for the math to work properly.

Quote from: redmagejoe on February 14, 2020, 12:06:19 AM
Mostly want to be sure that I haven't overlooked anything on the Level Up Rework before I call that done, and even then, I need to find out if I've created the most efficient Evasion/MDef delay fix that uses as little of the free space as possible.
The more I look at this, the more I feel like forcing a full stat refresh at the end of battle is the way to go. In addition to the delay on Evasion and Magic Resist attempts, it looks like none of the other calculated values are getting updated. For instance, the increases to Attack Power and Hit % when gaining Strength don't appear until the next stat refresh, nor do the increases to Hit attempts or Evasion Success % when gaining a weapon/shield level, and similarly for the other calculated stats. Probably a JSR $FAFB at the end of the base stat level up routine (i.e. somewhere around $05:$A706) should do the trick.

Quote from: redmagejoe on February 14, 2020, 12:06:19 AM
I fear that the abstraction between instructions and outcome is making it difficult for me to think how to cleverly work it in other than the instructions I wrote, which rely on working around the existing framework rather than trying to cleverly make use of the behavior to fit my fix in. That has been my biggest hurdle so far, and I'm hoping that I get comfortable enough to write changes that don't throw a wrench in the current behavior.
This is one of the hard parts about working with other people's code in general, and it's even more difficult when working with assembly than when working with some high-level language. Scoping rules make code more manageable :P.

Quote from: redmagejoe on February 14, 2020, 12:06:19 AM
Also, maybe I'm just a fool, but isn't this an easy fix? Is this not simply a case of the offset of spell levels ($00 = Level 1) not matching up with the math required for the level 8 effects of these spells? Would not changing the CPX #$08 / LDA #$07 at $B751 onward to $09 and $08 fix these bugs?
Hmm, yup, I agree. The code is checking to see if the number of successes is >= 8, and if so, setting it to 7. I'm pretty sure they meant to say LDX #$08. You can also change the CPX to #$09 if you want, but it's not necessary. Some fixes are easier than others :D.

redmagejoe

#209
So there's only a slight loss of uniform distribution with the new version, but it's significantly more uniform, isn't going to result in Maria and Guy getting 50% more focus, and isn't going to cause graphic bugs? I don't know dude, doesn't sound like much of a downside to me. :) If it's all tested and ready, I'll go ahead and update the first post and add it to stable. Also, again, looking at control flow, it's alright for me to NOP out those 19 freed up bytes after the RTS for use as free space for later, right?

As for the fix to Barrier/Aura, on Barrier at least, it seems to go through the battle messages for 7 checks, including "Freeze Resist" / "Ice Df", but isn't actually applying it, I guess? So it says you're getting ice protection, but you're not, similar to Ripper I guess. I don't know how to properly check that since I have no idea what values are being manipulated on successful applications of Barrier. That said, I don't know if there's supposed to be a message for Matter element, as it always seems to start at "Fire Resist", going all the way up to "Freeze Resist" suggesting it's doing Fire ("Fire Resist"), Mind ("Confuse Resist"), Lightning ("Thunder Resist"), Death ("Death Resist"), Poison ("Poison Resist"), Body, ("Paralyze Resist"), and Ice ("Freeze Resist") on full successes. So maybe there's an issue with the battle message, but not the actual buff for Matter defense? Maybe the counter starts 1 too high. I don't even know what the battle message for Matter is supposed to be.

And after testing, it seems that at lower levels "Freeze Resist" is the first thing to show up when no other messages do. It would help if I actually understood what this spell is meant to do in the first place. I'm not sure if Freeze means "Ice" or if it means Matter elements "Paralyze/Stop" effect. The wording in Chaos Rush's translation is confusing, and I may need to test this with the Demiforce translation. If that is the case, it would mean that it is, in essence, doing Matter first, and the final message that isn't showing up should be "Ice", which is currently not showing up. That would make more sense. I will test this with the Demiforce translation, and consider updating the messages in Chaos Rush's translation to be more clear.

Hmm, no, it does seem like it's at least trying to give you Ice Resist at lower levels. Reading the resources online, I'm now completely confused how this spell is SUPPOSED to behave versus how the game is actually executing it. Because currently it seems to behave in the exact opposite manner as outlined online. I am going to come back to this later.




Do all the branches and JSRs (and opcodes) check out in my rework? I didn't overlook anything? I'll go ahead and throw out the current Evasion/MDef fix WIP then since, as I feared, there's more things that need an update. I'll start working on a full stat refresh fix instead. If you're willing to give me the go-ahead on the Level Up Rework, I'll go ahead and add it to stable. Disregard, I have to move things around again to add this call to stat refresh at the end of everything. I need to figure out how it's handling these individual subroutines, if there's a primary control flow that does a conditional check and then JSRs to them (so the new JSR would have to be in that routine) or if it goes through each individual sub and checks within them (new JSR would have to be at the end of the last). I'll post updates here.

So looking at $05:$A553, while the comment is indeed true about leveling weapons, I'm assuming that this is the branch taken to begin the entire level up routine set. Or at least, it either levels weapons, or it invariably jumps down to the middle of level up routines. I'm just trying to chase around control flow to find when the absolute last stat level up is finished and control is handed back to any other instructions to be done before going back to the overworld.

I think I have an idea of when this stat refresh should take place, and since the existing one only seems to do one character at a time anyway, I'd need it to run with A holding the character ID. So probably would have it happen in the neighborhood of $05:$A559 ?

abw

Quote from: redmagejoe on February 18, 2020, 12:53:26 PM
So there's only a slight loss of uniform distribution with the new version, but it's significantly more uniform, isn't going to result in Maria and Guy getting 50% more focus, and isn't going to cause graphic bugs? I don't know dude, doesn't sound like much of a downside to me. :)
Yeah, it's better, just not the best. Of course, the entire thing is based on one of two static lookup tables, so it would be tough to come up with something non-trivial that was significantly worse :P. When the number of possible outputs divides evenly into 256 (as is the case when picking a random number in the range [0..3]), the distribution actually is uniform (so enemy targetting is fine), but it becomes non-uniform for other output sizes. E.g., when asking for a number between 1 and 100, there are 256 mod 100 = 56 numbers that are more likely to occur than they would in a truly uniform distribution.

Quote from: redmagejoe on February 18, 2020, 12:53:26 PM
As for the fix to Barrier/Aura, on Barrier at least, it seems to go through the battle messages for 7 checks, including "Freeze Resist" / "Ice Df", but isn't actually applying it, I guess?
It looks like the battle messages are just plain backwards. The actual resistances are in the order (using Chaos Rush's translation) 80: Freeze, 40: Paralyze, 20: Poison, 10: Death, 08: Thunder, 04: Confuse, 02: Fire, 01: Transform, but the battle messages are in the opposite order 80: Transform, 40: Fire, 20: Confuse, 10: Thunder, 08: Death, 04: Poison, 02: Paralyze, 01: Freeze, so with 1 success you actually get Transform resistance but the battle message says Freeze resistance.

For comparison, Demiforce's terms are 80: Change, 40: Fire, 20: Soul, 10: Bolt, 08: Death, 04: Poison, 02: Critical Hit!, 01: Ice, and the Japanese is 80: へんかこうげき, 40: ほのおこうげき, 20: せいしんこうげき, 10: いなづまこうげき, 08: し, 04: どくこうげき, 02: しんけいこうげき, 01: れいきこうげき. Google Translate of the Japanese is not particularly helpful, alas :(.

Quote from: redmagejoe on February 18, 2020, 12:53:26 PM
So looking at $05:$A553, while the comment is indeed true about leveling weapons, I'm assuming that this is the branch taken to begin the entire level up routine set.
Just before $05:$A553 is the check for persistent status conditions, and if you have any (except whatever bit 0 indicates), it sets $E1 to #$FF and then jumps way down to near the end of the level up routine. $A559 is on that path of no growth, so you'll definitely want to trigger the refresh at the end of the other path.

redmagejoe

#211
So wait, is this actually yet another bug? The order of the messages is reversed? I compared Neodemi's and Chaos's translations, and they're in the same memory addresses so it's not the translation like I first thought. I'm looking at the list of messages in your disassembly, and sure enough... Ugh, that's a headache, as now this change is going to conflict with translation patches that rely on changing the message offsets for changed names... Headache...


0x017325|$05:$B315:C7 B5 ; $05:$B5C7; Battle Message ID #$40: 'Ice'
0x017327|$05:$B317:CF B5 ; $05:$B5CF; Battle Message ID #$41: 'Critical Hit!'
0x017329|$05:$B319:D8 B5 ; $05:$B5D8; Battle Message ID #$42: '[Poison]'
0x01732B|$05:$B31B:DF B5 ; $05:$B5DF; Battle Message ID #$43: 'Death'
0x01732D|$05:$B31D:E1 B5 ; $05:$B5E1; Battle Message ID #$44: 'Bolt'
0x01732F|$05:$B31F:EA B5 ; $05:$B5EA; Battle Message ID #$45: 'Soul'
0x017331|$05:$B321:F3 B5 ; $05:$B5F3; Battle Message ID #$46: 'Fire'
0x017333|$05:$B323:FB B5 ; $05:$B5FB; Battle Message ID #$47: 'Change'

0x017325|$05:$B315:FB B5 ; $05:$B5FB; Battle Message ID #$40: 'れいきこうげき' (reiki(?) kogeki / cold air attack)
0x017327|$05:$B317:F3 B5 ; $05:$B5F3; Battle Message ID #$41: 'しんけいこうげき' (shinkei kogeki / nerve killing attack)
0x017329|$05:$B319:EA B5 ; $05:$B5EA; Battle Message ID #$42: 'どくこうげき' (yomi kogeki / poison attack)
0x01732B|$05:$B31B:E1 B5 ; $05:$B5E1; Battle Message ID #$43: 'し' (shin / death)
0x01732D|$05:$B31D:DF B5 ; $05:$B5DF; Battle Message ID #$44: 'いなづまこうげき' (inazuma kogeki / lightning attack)
0x01732F|$05:$B31F:D8 B5 ; $05:$B5D8; Battle Message ID #$45: 'せいしんこうげき' (seishin kogeki / soul/mind attack)
0x017331|$05:$B321:CF B5 ; $05:$B5CF; Battle Message ID #$46: 'ほのおこうげき' (ho no o kogeki / fire attack)
0x017333|$05:$B323:C7 B5 ; $05:$B5C7; Battle Message ID #$47:  'へんかこうげき' (hen ka kogeki / transform attack)


So I pulled up the same message offsets in the Japanese ROM using CastleFynn, and I have here the Japanese names of the messages. In the second block, as you can see, I reversed the pointers. Because of the natures of both messages and pointers being changed by translation patches (at least in Chaos Rush's case, the condensed words I believe are shifted around rather than filled in with blank space), making this change would demand that Chaos Rush's translation be updated again, and controversially, would render it the only "working" English translation patch from here on out. Though currently, all the translations are wrong anyway and based on improper behavior in the base ROM, so...

If I were already going to change Chaos Rush's translation again though, I'd want to address the issue with the spell names, their use of DTE (which would require graphics editing), and the 5-characters instead of 4 that causes the issue with spell levels in combat windows above level 10 I'd mentioned previously in this thread.

Chased down the values in my Chaos Rush translated version (which are of course different), flipped the offsets, and lo and behold, it displays the right messages now, starting at the highest level's resistance and working its way down, so now a success on Level 1 Barrier should yield Transform Resist rather than Freeze Resist. I'll pair this less confusing fix with testing the LDX #$08 fix and see if I can now get Freeze Resist to appear.

EDIT: Works for both Aura and Barrier. Such a ridiculously simple fix, it's almost painful to think this was a bug that made it into release. Off by one indeed. I'll roll both of these changes (the message pointers for Barrier as well as the 7 -> 8 ) into a fix. The patch will change the Japanese pointers. I've already made a separate fix for the Chaos Rush translation to my version of the IPS on my end. I'll look into fixing the spell name length issue while I'm at it and think about submitting a v1.9 / 2.0 when those get fixed.

I'd like to find someone comfortable working with graphics to redo the special DTE characters used for spell names in Chaos Rush's translation, as they look nice and are pretty tightly packed together, but again, they're based on a 5-character length instead of a 4-character length.




What do you think would be the best place to sneak in a JSR $FB00? I'll try not to go cross-eyed chasing down the control flow off of the BEQ at $A553 and see where it seems to be finished handling stat-ups for that character. There surely must be a final, always-run set of instructions post stat ups regardless of whether a certain stat goes up or not, but with all the JSRs and JMPs my eyes are going crossed. I'll do my best though.

I'm thinking after $05:$A703 ? Though we also have to LDA the character index, don't we, given that's what the comments suggest is already the case for Accumulator when going into $FB00. So I'm guessing we need an LDA $9E followed by a JSR $FB00. I'll do a test fix without shifting addresses by taking $05:$A706 and $A709 into a JSR (+NOP) that points down to our 16-byte free space, but preceding them with LDA $9E and JSR $FB00 and see if that results in Firion coming out of the battle with his new MDef. If it works and if it sounds like an appropriate fix to you, abw, I'll rewrite the Level Up Rework again. If we feel comfortable that we've made all the fixes to level up routines necessary, we can crunch the code again and move that free space (will be 11 bytes of the current 16 left after the JSR and LDA get worked in) to the end of our level up routines to be used or re-crunched towards the end of the project.

EDIT: Problem is that the JSR to $FB00 goes to $0F:$FB00, but rather than jumping to $00:$9880 as it would usually do, it's jumping to $05:$9880. Again, I don't know if there's any way within bank 05 (which I assume is used for combat) to get to bank 00 to make this stat refresh happen. Could some instructions be set to run the moment the overworld comes back up after leaving a battle? The jump goes to the correct bank at the start of a battle, yet when we're in bank 05 for stat level ups and such, I guess we're... stuck there? Shouldn't jumping over to bank 0F first allow us to properly jump to bank 00? Or am I not understanding how hand-offs between banks work?




Started looking at Dispel, and already even if it DID work, it seems to suffer the same LDX #$07 issue. Given that Basuna and Esuna have 5 and 7 status cures respectively, the LDX #$07 works there. But Dispel is supposed to act on 8 different resists, so once more I'll be changing that 7 to 8. But this may be more difficult to test, as I'm assuming (haven't tested yet) that it displays messages properly while not actually affecting any stats, so I'll have to figure out what exact RAM addresses are supposed to be affected, whether the wrong addresses are being affected, or the correct ones are simply having no change applied... The description of the bug doesn't really tell me exactly WHY it doesn't work, so going to have to do some detective work.

EDIT: Hmm, actually all 8 messages work without changing 7 to 8, so now I'm curious what makes that bug specific to Aura/Barrier. Is it the DEX immediately after the LDX? Probably. Also took this opportunity to fix a few more awkward battle messages in Chaos's translation (Like Atk.Up up!, changed Atk.Up and Def.Up to Attack and Defend), and made the janky "FireBar.fell" to "Fire ResDown".

abw

Quote from: redmagejoe on February 18, 2020, 11:48:43 PM
Ugh, that's a headache, as now this change is going to conflict with translation patches that rely on changing the message offsets for changed names...
The alternative to changing the text to match the effect is to change the effect to match the text. Instead of one success setting bit 0 (Transform) and printing the message for bit 7 (Freeze), we could flip it around so that one success sets bit 7, two successes set bit 7 and 6, and so forth. Is gaining resistances in one order better than the other order?

Quote from: redmagejoe on February 18, 2020, 11:48:43 PM
I've already made a separate fix for the Chaos Rush translation to my version of the IPS on my end. I'll look into fixing the spell name length issue while I'm at it and think about submitting a v1.9 / 2.0 when those get fixed.
If you're going to make another update to Chaos Rush's translation, it might be worth giving the entire script another pass to check for line wrapping. I've been playing through v1.8 to refresh my memory of this game and noticed the following (in addition to all of Guy's dialogue, which appears to be intentionally bad), which could easily fit into 2 lines instead of 3:
Spoiler

(After Josef stops the boulder while leaving the Snow Cavern:)
Josef: It's up to...you
now...[Firion]. My sweet
Nelly
[close]

Quote from: redmagejoe on February 18, 2020, 11:48:43 PM
What do you think would be the best place to sneak in a JSR $FB00?
Sorry, $FB00 was a typo - I meant to say $FAFB, which first swaps in bank 0 before calling $9880. I gave it a quick test by manually jumping to $FAFB in the debugger, but the game crashes at the next NMI since the NMI vector is still pointing to the $A9A2 that code in bank 5 set, and $A9A2 in bank 0 is not even code, let alone a useful NMI handler. During battle setup, the NMI handler is set to $FA3C and gets called a couple of times without issue, so at $05:$A706 I think we'd need to:

JSR $FA2A ; update the NMI vector to $FA3C
LDA $9E   ; load the current character ID
JSR $FAFB ; swap in bank 0, call the stat refresh code, then swap bank 5 back in
JSR $A992 ; set the NMI vector back to $A9A2


Quote from: redmagejoe on February 18, 2020, 11:48:43 PM
Started looking at Dispel, [...] The description of the bug doesn't really tell me exactly WHY it doesn't work, so going to have to do some detective work.
Oh, this one is a little bit hilarious. The routine loads the target's resistance bits from ($A1),Y into A, clears a bunch of them in A, and then never writes the result back to ($A1),Y. Conveniently, there are 2 useless bytes at $0C:$B807, so shifting $0C:$B809-$0C:$B81C 2 bytes earlier (making sure the BMI still branches to $B83B) and then inserting STA ($A1),Y at $0C:$B81B should do the trick.

redmagejoe

#213
I'm not really sure I want to flip the order of resistances though, since that begins to enter the territory of fiddling with game balance. Whether it's better one way or the other isn't something I'm in a position to arbitrarily decide, and I'd rather leave that alone. I'm pretty sure the specific elements are chosen based on the types of enemies you'd encounter at the point in the game where you have the spell at a high enough level to get those resists from it.

The more I think about messing up old existing translation patches, the more I realize that it's better that this overhaul patch inspire updated/new projects to account for a fully working version of the game. After a great deal of internal debate, I've decided to leave the Aura/Barrier patch as it is, as well as the Chaos Rush change I'll eventually upload.

I'll also see about fixing that Dispel issue. The thing is, I have no idea how to test whether it works or not. What addresses should I be watching in RAM? Or is it safe to assume that the code will do what it's meant to so long as that STA is inserted? Actually, come to think of it, I could set a break and watch it in the debugger. I keep forgetting that it tells you what address is being referenced as it steps through those instructions.

Quote from: abw on February 19, 2020, 10:57:56 PM
Oh, this one is a little bit hilarious. The routine loads the target's resistance bits from ($A1),Y into A, clears a bunch of them in A, and then never writes the result back to ($A1),Y. Conveniently, there are 2 useless bytes at $0C:$B807, so shifting $0C:$B809-$0C:$B81C 2 bytes earlier (making sure the BMI still branches to $B83B) and then inserting STA ($A1),Y at $0C:$B81B should do the trick.

"What does your robot do, Sam?"
"It collects data from the surrounding environment, then discards it and drives into walls."

I shouldn't need to worry about the BMI, since it's being shifted up by 2, and then 2 bytes are being added before where it branches to. It should still be $30 $30 in the end. I'm watching RAM now, using WerePanthers (with $50 resist bit, for Body and Death), and already tested that it indeed does nothing. Going to apply the test fix now and see if it gets properly set to $50 (for 4 or less successes), $40 (for 5 or 6 successes), or $00 (for 7+ successes). If that works, I may need to go looking for other monsters with different resistances to fully test this on. That said, even if it always clears all resistances regardless what the messages say, it's still working more than it would have untouched. Doesn't mean I won't be thorough in my fix though. :P

EDIT: What I assume you meant with your advisory was to ensure that the BPL at $0C:$B81B (which will become $B81D) still points back to $B817, since now there's 2 extra bytes that caused a bug with my fix. I have to change the $10 $FA to a $10 $F8 :P

Well, we've got it removing resists now, though I think there's an issue where it handles checks in the opposite way of Aura and Barrier, and it should be running the highest resist first rather than lowest... I need to test this spell on the PSP version (along with Aura and Barrier) and see how it's handling what resists are considered "higher" or "lower" relative to one another. Cool, PSP version doesn't even tell you what effects you got from the spell success. Simply says "Barrier Lv.16" does the animation, end of story. Frustrating... According to resources as relates to Dawn of Souls version, at least, Dispel and Barrier should handle resists in the same order. So I'd actually need to look at reversing the current order of Dispel's layers. It's treating Matter as the highest and Ice as the lowest. I need to figure out if this is another message issue, or actual behavior... What a headache, these layer-based spells.

EDIT: It appears, at least, that it's just another message reverse issue, as I got "Ice Res.Down" and "Body Res.Down" with level manipulation, but $44 was still set on the Bomb enemy, showing that what had actually been affected was its non-set Matter and Fire resists. Should be an easy fix at least. Since Dispel uses its own routine, in that case rather than changing the pointers, I could simply make the INC instructions for the battle message DEC, and change the first loaded offset from #$52 to #$59, so that it works backwards through the messages. Looking at the gymnastics that would have to be done, however, and the fact that I'm already putting this fix in a position to deprecate existing translations, it would really just be easier for me to change the text pointers.

First post updated with the Japanese-based Dispel fix. I've already made an adjustment to my WIP Chaos Rush translation pointers as well.




So back to Protect, I'm watching the relevant places in RAM and the steps it's taking... I think the reason that only the caster is getting the bonus is because of the fact that $B7CF calls on the target's +$27, but only the caster's +$27 is set. Following all the math it's meant to be doing, it appears that the value stored in the caster's +$27 should be caster's Spirit / 4 / # of targets * number of successes. Also even if you get 0 success at a low level, it still says "Defense up!" rather than "Not effective" which doesn't help with misleading the user into thinking they're getting no bonuses when they should. Anyway, the solution as I see it is to either ensure that we're loading the caster's +$27, or somehow load all targets' +$27 with the same value. Because right now the issue is that the multiplication is happening with a 0 going in, so obviously their bonus is going to be 0.


; handler for Spell ID #$DA: Protect
; indirect control flow target (via $BE9E)
0x0337DA|$0C:$B7CA:20 E9 BD JSR $BDE9
0x0337DD|$0C:$B7CD:A0 27    LDY #$27    ; Battle stat offset for Mystery Battle Stat #$27
0x0337DF|$0C:$B7CF:B1 A1    LDA ($A1),Y ; pointer to target's battle stats
0x0337E1|$0C:$B7D1:85 00    STA $00
0x0337E3|$0C:$B7D3:A9 00    LDA #$00
0x0337E5|$0C:$B7D5:85 01    STA $01
0x0337E7|$0C:$B7D7:A5 48    LDA $48
0x0337E9|$0C:$B7D9:85 02    STA $02
0x0337EB|$0C:$B7DB:A5 49    LDA $49
0x0337ED|$0C:$B7DD:85 03    STA $03
; call to code in a different bank ($0F:$FC98)
0x0337EF|$0C:$B7DF:20 98 FC JSR $FC98  ; 16-bit multiplication (little endian): $00-$01 * $02-$03, product stored in $04-$07; leaves X at #$00, C clear, $00-$01 and Y not changed
0x0337F2|$0C:$B7E2:A0 02    LDY #$02    ; Battle stat offset for Defense
0x0337F4|$0C:$B7E4:18      CLC
0x0337F5|$0C:$B7E5:B1 A1    LDA ($A1),Y ; pointer to target's battle stats
0x0337F7|$0C:$B7E7:65 04    ADC $04
0x0337F9|$0C:$B7E9:91 A1    STA ($A1),Y ; pointer to target's battle stats
0x0337FB|$0C:$B7EB:90 07    BCC $B7F4
0x0337FD|$0C:$B7ED:A9 FF    LDA #$FF
0x0337FF|$0C:$B7EF:91 A1    STA ($A1),Y ; pointer to target's battle stats
0x033801|$0C:$B7F1:4C 73 BE JMP $BE73


I'm going to try changing the LDA ($A1),Y to LDA ($9F),Y instead. That SHOULD be the actor's battle stats rather than the target's. EDIT: ... I CANNOT BELIEVE how simple that fix was. These guys must have been sitting around a table at 3am with no coffee when they were coding this game. Also, it appears that # of successes is calculated for each target rather than before the spell, so a Level 16 Protect from 99 Spirit Guy (99 / 24 / 4 = 6) became 3 successes on Firion (+18), 1 success on Maria (+6), 4 successes on Guy (+24), 6 successes on Char4 (+36). Give this, it seems like Protect is a spell you're much better off using 1 person per turn.




Quote from: abw on February 19, 2020, 10:57:56 PM
If you're going to make another update to Chaos Rush's translation, it might be worth giving the entire script another pass to check for line wrapping. I've been playing through v1.8 to refresh my memory of this game and noticed the following (in addition to all of Guy's dialogue, which appears to be intentionally bad), which could easily fit into 2 lines instead of 3.

I will definitely be giving it a read-over and seeing about consolidating space, though the changes I've already made to it tried to use the available space without moving so as to avoid changing pointers, so there's some excess spaces here and there. Making it use space as efficiently as possible may become its own separate project down the line.

I'll try to update, test, and then shift the Level Up routine before posting the new mock-up for review with the stat refresh code. I'd really like to get these items out of the way so I can focus 100% of my energy on the other items on our list. As always, thank you for pointing me in the right direction. If you get a chance to sit down and peer review my Level Up Rework mock-up at some point, it would be of great help. That's been kind of looming over me for a while and I'd like to commit that to my stable version if possible.

EDIT: I may have misunderstood precisely where to apply this fix. Doing some experimenting though and will report back my results here. So I turned the original JSR $FA2A into a JSR to my free block for testing, and put those instructions. The stat refresh works, BUT there's a graphical glitch that happens for a second (~60 frames) before it returns to normal. So now it's just a matter of figuring out if it has something to do with that second JSR that mentions waiting for sprite 0 and PPU / VRAM control. I'll try dragging that into the test block and/or working around it.

EDIT2: EGADS, it works! Or rather, changing the JSR $FD46 to my test block (and then having the test block start with JSR $FD46 rather than $FA2A, which already runs beforehand) results in a stat refresh, no graphics bugs... So basically to force a stat refresh, we simply insert between $05:$A711 and $05:$A714:


LDA $9E   ; load the current character ID
JSR $FAFB ; swap in bank 0, call the stat refresh code, then swap bank 5 back in
JSR $A992 ; set the NMI vector back to $A9A2


Now assuming this doesn't cause other bugs when there's multiple characters gaining level-ups... I can finally rewrite my Level Up Rework and crunch the code again (with the soon-to-be only 8 free bytes outside the level up routines). Updated the rewrite below. I'm pretty sure I didn't miss anything but I could be wrong. I'd like to commit this to stable soon.

https://drive.google.com/open?id=1W-B-13h1aeSUdA4pLN_Nvs400gtRwjcA

abw

Quote from: redmagejoe on February 19, 2020, 11:52:47 PM
I'm not really sure I want to flip the order of resistances though, since that begins to enter the territory of fiddling with game balance.
Yup, just tossing out another idea to consider. The messages for Aura are just meaningless colour names, so "White Aura" is just as good a message for gaining damage against Undead as is is for gaining damage against Magic Beasts. It does affect balance, but some of the other bugfixes have a much greater impact on balance; for instance, the RNG fix doubles the chance of losing Intellect/Stamina/Strength when gaining Strength/Intellect/Spirit, and the bugfix for enemy target-all spells increasing Firion's white magic use counter makes the entire party less susceptible to adverse status effects.

Quote from: redmagejoe on February 19, 2020, 11:52:47 PM
I shouldn't need to worry about the BMI, since it's being shifted up by 2, and then 2 bytes are being added before where it branches to.
The BMI is being shifted up by 2, but its target isn't, so it now needs to skip 2 more bytes than before. This is a total non-issue with labels and an assembler, but it's an extra thing you need to watch out for if you're manually editing hex.

Quote from: redmagejoe on February 19, 2020, 11:52:47 PM
EDIT: What I assume you meant with your advisory was to ensure that the BPL at $0C:$B81B (which will become $B81D) still points back to $B817, since now there's 2 extra bytes that caused a bug with my fix. I have to change the $10 $FA to a $10 $F8 :P
No, I meant what I said. The BPL is fine, since the distance between it and its target doesn't change when they both get shifted by the same amount. If you change it to branch to the LDA ($A1),Y, then you're resetting the current resistances to their original value every time through the loop, so only the final iteration will actually accomplish anything.

redmagejoe

#215
I had no idea that there was such a drastic change involved with the RNG fix. Should we try to adjust the ratio then for losing a stat? It's supposed to be roughly 1/5 chance to lose a stat when a stat is gained. We only have 5-8 bytes to work with post rework. I don't know if that requires changing an existing ratio somewhere, or if we'd have to make one. In the second case, I'm assuming you mean that the party is less susceptible as a result of having higher MDef. That's intended though if it had worked properly from the start, so I'm not concerned with that, but I AM concerned with the (what I can only assume to be) intended chance to lose a stat being higher than usual.

EDIT: I see in the code where in data it's calling for those chances from $AC41, $AC42, and $AC43. I'll look at the numbers, contemplate the new math (uniform distribution rather than 16/33/33/16) and figure out how they should be adjusted. Would this be as simple as increasing them to 10 ($0A) to yield the same result?


; chance to lose Intellect when gaining Strength
; data load target (from $A682)
0x016C51|$05:$AC41:05
; chance to lose Stamina when gaining Intellect
; data load target (from $A69F)
0x016C52|$05:$AC42:05
; chance to lose Strength when gaining Spirit
; data load target (from $A6BC)
0x016C53|$05:$AC43:05





Dispel Fix has been updated. Just had to change a 30 to a 32. The Protect fix, at least, was literally a single byte change. Also, first version of the complete Level Up Rework is finally up. There's a couple of questions I have regarding some SBCs, as well the necessity of a specific JSR, but this is a mostly working product at least and clears another item off our list. I can always shift code back by 3 and update all the pointers now that I have abw's version that I can do a direct byte comparison to. This has been a comprehensive effort between abw and myself (learning as I go!), and will be credited as such.

https://drive.google.com/open?id=1W-B-13h1aeSUdA4pLN_Nvs400gtRwjcA




Looking at Ripper again, I'm wondering how feasible it is to simply rearrange instructions here such that the special effects are determined before the damage is calculated. In other words, can the code from 0x03311C be run prior to 0x0330E4?


; control flow target (from $B0CC)
0x0330E4|$0C:$B0D4:A0 0A    LDY #$0A    ; Battle stat offset for Current HP low byte
0x0330E6|$0C:$B0D6:B1 A1    LDA ($A1),Y ; pointer to target's battle stats
0x0330E8|$0C:$B0D8:38      SEC       
0x0330E9|$0C:$B0D9:E5 9A    SBC $9A   
0x0330EB|$0C:$B0DB:91 A1    STA ($A1),Y ; pointer to target's battle stats
0x0330ED|$0C:$B0DD:C8      INY        ; Battle stat offset for Current HP high byte
0x0330EE|$0C:$B0DE:B1 A1    LDA ($A1),Y ; pointer to target's battle stats
0x0330F0|$0C:$B0E0:E5 9B    SBC $9B   
0x0330F2|$0C:$B0E2:91 A1    STA ($A1),Y ; pointer to target's battle stats
0x0330F4|$0C:$B0E4:90 08    BCC $B0EE 
0x0330F6|$0C:$B0E6:B1 A1    LDA ($A1),Y ; pointer to target's battle stats
0x0330F8|$0C:$B0E8:88      DEY        ; Battle stat offset for Current HP low byte
0x0330F9|$0C:$B0E9:11 A1    ORA ($A1),Y ; pointer to target's battle stats
0x0330FB|$0C:$B0EB:D0 1F    BNE $B10C 
0x0330FD|$0C:$B0ED:C8      INY        ; Battle stat offset for Current HP high byte
; control flow target (from $B0E4)
0x0330FE|$0C:$B0EE:A9 00    LDA #$00   
0x033100|$0C:$B0F0:88      DEY        ; Battle stat offset for Current HP low byte
0x033101|$0C:$B0F1:91 A1    STA ($A1),Y ; pointer to target's battle stats
0x033103|$0C:$B0F3:C8      INY        ; Battle stat offset for Current HP high byte
0x033104|$0C:$B0F4:91 A1    STA ($A1),Y ; pointer to target's battle stats
0x033106|$0C:$B0F6:20 9A BF JSR $BF9A 
0x033109|$0C:$B0F9:20 76 AF JSR $AF76  ; set A = Ailment (persistent) (80: KO, 40: Stone, 20: Toad, 10: Amnesia, 08: Curse, 04: Venom, 02: Blind, 01: ??) of entity at ($A1); leaves Y at #$08
0x03310C|$0C:$B0FC:A0 2C    LDY #$2C    ; Battle stat offset for previous Ailment (persistent) (80: KO, 40: Stone, 20: Toad, 10: Amnesia, 08: Curse, 04: Venom, 02: Blind, 01: ??)
0x03310E|$0C:$B0FE:91 A1    STA ($A1),Y ; pointer to target's battle stats
0x033110|$0C:$B100:A0 08    LDY #$08    ; Battle stat offset for Ailment (persistent) (80: KO, 40: Stone, 20: Toad, 10: Amnesia, 08: Curse, 04: Venom, 02: Blind, 01: ??)
0x033112|$0C:$B102:09 80    ORA #$80    ; KO
0x033114|$0C:$B104:91 A1    STA ($A1),Y ; pointer to target's battle stats
0x033116|$0C:$B106:A5 28    LDA $28   
0x033118|$0C:$B108:09 20    ORA #$20   
0x03311A|$0C:$B10A:85 28    STA $28   
; control flow target (from $B0EB)
0x03311C|$0C:$B10C:A0 1D    LDY #$1D    ; Battle stat offset for Primary Hand Weapon Special Effect (20:Heal, 10:Ripper, 08:Drain MP, 04:Drain HP, 02:inflict temporary Ailment based on bits 7-2, 01:inflict persistent Ailment based on bits 7-2)
0x03311E|$0C:$B10E:B1 9F    LDA ($9F),Y ; pointer to actor's battle stats
0x033120|$0C:$B110:F0 12    BEQ $B124 
0x033122|$0C:$B112:85 60    STA $60    ; Primary Hand Weapon Special Effect
0x033124|$0C:$B114:29 01    AND #$01   
0x033126|$0C:$B116:F0 03    BEQ $B11B 
0x033128|$0C:$B118:4C 51 B3 JMP $B351 

; control flow target (from $B116)
0x03312B|$0C:$B11B:A5 60    LDA $60    ; Primary Hand Weapon Special Effect
0x03312D|$0C:$B11D:29 02    AND #$02   
0x03312F|$0C:$B11F:F0 0C    BEQ $B12D 
0x033131|$0C:$B121:4C 5F B3 JMP $B35F 

; control flow target (from $B110)
0x033134|$0C:$B124:60      RTS

abw

Quote from: redmagejoe on February 22, 2020, 05:00:14 PM
I had no idea that there was such a drastic change involved with the RNG fix.
The RNG affects a lot of stuff, and the smaller the RNG output range is, the greater the impact of fixing it to be balanced is. In the case of stat loss, the game rolls a random number in the range [0..5] and you only lose a stat on 0, so the intent appears to have been to have a 1/6 chance to lose a stat. But with the original unbalanced RNG, 0 and 5 were only 50% as likely to occur as 1..4, so the actual chance was more like 1/12. If you wanted to maintain the 1/12 chance with a balanced RNG, then you'd want to roll numbers in the range [0..11], so updating the loss chances at $05:$AC41-$05:$AC43 to 0B would be the thing to do.

Quote from: redmagejoe on February 22, 2020, 05:00:14 PM
Looking at Ripper again, I'm wondering how feasible it is to simply rearrange instructions here such that the special effects are determined before the damage is calculated. In other words, can the code from 0x03311C be run prior to 0x0330E4?
I like this idea, and gave it a try. Battle poses are still not getting updated properly when the Drain effect changes your critical status; after checking, that is also another error in the original game. It'll take some more investigation to find where the battle poses are controlled from, but for now, try this.

Quote from: redmagejoe on December 10, 2019, 03:09:14 AM
  • Investigate possible overflow on Evasion % when Agility/Evasion level and Shield equipped modifier add up.
In other news, I took a peek at this one, and I think the current code is okay - it's doing 16-bit arithmetic, and a quick test with 2 Aegis Shields, level 16 Shield skill, and 99 agility still gets me Evasion % capped at 99.

redmagejoe

#217
First post has been updated with new RNG Fix (with adjusted stat loss chance values) and the final version of the Level Up Routines Rework.

Let me see if I can figure out precisely what you changed. Didn't see an ASM in the 7z, but I am nothing if not comfortable in deciphering hex now. :)

https://pastebin.com/5QxgHa19

Mostly done, but need sleep now... Still commenting this referencing the original disASM to see what's doing what, but this should be all the instructions at least.




Also, I'll need to double check, but I was definitely noticing some bizarre behavior with the value of my evasion on one of my characters. I'll do some more testing.

EDIT: Yeah, 7-99% on Firion with 99 Agility, and a Diamond Shield. I put on a second Diamond Shield, and suddenly he's 7-50%. There's SOMETHING fishy happening with shields and Evasion. Watching RAM now, and sure enough, with no shields he's at $44, one shield he's at $63, and then at two shields he's at $32.

abw

Quote from: redmagejoe on February 24, 2020, 12:51:15 AM
Let me see if I can figure out precisely what you changed. Didn't see an ASM in the 7z, but I am nothing if not comfortable in deciphering hex now. :)
Ah, sorry, I've updated the link to include the ASM file too.

Quote from: redmagejoe on February 24, 2020, 12:51:15 AM
EDIT: Yeah, 7-99% on Firion with 99 Agility, and a Diamond Shield. I put on a second Diamond Shield, and suddenly he's 7-50%. There's SOMETHING fishy happening with shields and Evasion. Watching RAM now, and sure enough, with no shields he's at $44, one shield he's at $63, and then at two shields he's at $32.
I haven't been able to reproduce this myself. What's your shield skill level and other equipment?

redmagejoe

#219
I'll grab a few screenshots, give me a moment.

https://imgur.com/a/MRR2CsR

And oddly, now it's at 85% ($68) with no shields. There's some strange math happening when equipping shields, methinks. Note that this has been happening since before I touched bug fixes, so it's at least not a side effect of the equipment stat bonus stacking patch.