After After:

`; $05:$A7E9`

HP_MP_level_up:

LDY $44 ; offset for low byte of Max HP or MP, whichever one we're dealing with at the moment

LDA ($7A),Y ; low byte of Max

STA $00 ; low byte of Max, consumed by math routines

INY ; offset for high byte of Max HP or MP, whichever one we're dealing with at the moment

LDA ($7A),Y ; high byte of Max

STA $01 ; high byte of Max, consumed by math routines

JSR $FCC3 ; 16-bit division (little endian): $00-$01 / $02-$03, quotient in $04-$05, remainder in $06-$07

LDA $05 ; Max / damage, quotient high byte

BNE done ; if the damage ratio can't possibly be high enough, just return

; otherwise, check the damage ratio against a random number

LDA $04 ; Max / damage, quotient low byte

STA $4C ; Max / damage, quotient low byte

; $FCC3 leaves X = #$00 and nothing else has changed it, so we don't need to set it to #$00 ourselves

LDA $4A

JSR $FD11 ; given A and X, return a random value in A: X == #$FF => #$FF, A == #$00 or X => A, else X + the third byte of (A-X) * (#$80 + 256 * $7A48,$42) rounded based on its second byte

CMP $4C ; Max / damage, quotient low byte

BCC done ; if the damage ratio wasn't high enough, just return

; otherwise, check to see if Max is already at the cap

TYA ; neither $FCC3 nor $FD11 change the value of Y, so it still has the offset for the high byte of Max

LSR ; high byte offsets are #$0B and #$0F, so A is now #$05 or #$07

TAX ; this is the index into the table of 2-byte cap values

JSR CMP_max ; compare Max to its cap

BCS done ; if Max is already >= cap, just return

; otherwise, increase Max

LDY $48 ; stat offset for effective Stamina/Magic Power

ADC ($7A),Y ; effective Stamina/Magic Power

LDY $44 ; offset for low byte of Max HP or MP, whichever one we're dealing with at the moment

STA ($7A),Y ; Max HP/MP += effective Stamina/Magic Power (low byte)

INY ; offset for high byte of Max HP or MP, whichever one we're dealing with at the moment

LDA #$00

ADC ($7A),Y ; add the carry from the low byte

STA ($7A),Y ; Max HP/MP += effective Stamina/Magic Power (high byte)

JSR CMP_max ; compare Max to its cap

BCC battle_message ; if Max is already less than its cap, there's no need to cap it

; otherwise, cap Max

LDA cap_data-5,X ; low byte of cap

STA ($7A),Y ; low byte of Max

INY

LDA cap_data-4,X ; high byte of cap

STA ($7A),Y ; high byte of Max

battle_message:

TXA ; this is the index into the table of 2-byte cap values, #$05 or #$07

LSR ; A will now be #$02 or #$03, C is set

ADC #$5A ; HP/MP increase battle message IDs are #$5D and #$5E = #$5A + #$02 or #$03 + 1 for the carry

LDX $AD ; battle message index

STA $7FBA,X ; start of list of string IDs battle messages

INC $AD ; battle message index

done:

RTS

cap_data:

dw 9999 ; HP cap

dw 999 ; MP cap

...

; given an offset into ($7A) pointing to the high byte of Max in Y and an offset into the cap list (with a built-in offset of -5) in X, compare Max to its cap

; leaves Y with the offset for Max's low byte and A with the low byte of Max

; $05:$A9B6

CMP_max:

LDA ($7A),Y

CMP cap_data-4,X ; compare high byte of Max to high byte of cap

DEY

LDA ($7A),Y

BCC +

CMP cap_data-5,X ; compare low byte of Max to low byte of cap

+

RTS

Damn, you managed to optimize the code even further? Like I know it's only 2 more bytes, but given the scale of the code, that's impressive. I'll playtest this, but looking at the code, I can see that Square was definitely just doing the best it could at the time with the code. It fascinates me how much more you can fit into the code by just freeing up a couple of bytes. I was going to ask if it was going to be necessary to throw some WORDs (which I assume is used to define that it's simply data and not code) in like FF1 did for its Max HP cap. Awesome work again, abw!

By the way, have you thought about aiding Jiggers with her disassembly now that it's on GitHub? Your documentation is very thorough and would be a great boon I think to what she's trying to do.

EDIT: Ignore all previous ramblings in this section. Stamina/M.Power and HP/MP up are terribly finicky as RNG handles them. The fact that they are still occurring puts my mind at ease that both of these patches are acceptable fixes. I'll link abw's patch in the first post and mark the issue resolved. I guess the next matter, since we have it documented already, is the Evasion and Magic Defense routine. At some point in that code block quoted above, I'm assuming that an increase is being called for, but does it finish then and there and it's simply the menu display code that has the issue, or is there some incomplete instructions that's leaving the increased Evasion or MDef hanging in limbo until a second battle is completed?

EDIT2: The MP fix doesn't appear to handle the <999 situation properly. While MP doesn't ever seem to go up at 999, I changed the values of HP and MP from 9999 and 999 to 9998 and 998. At the end of a battle, Firion was dropped so low on both HP and MP that he got M.Power, Stamina, MP, and HP (in that order strangely). Stamina went from 98 to 99, HP went from 9998 to 9999 (nice!), but MP went from 998 to 9E4 (or I'm guessing 1044). 3 out of 4 of the scenarios this patch was meant to address appear to work, however, having been testing for the last few hours. HP < 9999 and increased beyond 9999 sets it to 9999, HP = 9999 prevents any HP up at all, MP = 999 prevents any MP up at all. Now all that's needed is MP < 999 increased beyond 999 sets it to 999. Looking at your code, abw, I can't figure out where the problem is. Is it possible that the comment you made before is relevant, about how the "too much" range for HP was 10000 to 10097 and used a single high byte, and MP's 1000 to 1097 does not? $03E8 to $0449 is the range for that, so perhaps the fact that the high byte could be $03 or $04 is causing problems with the math?

`; otherwise, check to see if Max is already at the cap`

TYA ; neither $FCC3 nor $FD11 change the value of Y, so it still has the offset for the high byte of Max

LSR ; high byte offsets are #$0B and #$0F, so A is now #$05 or #$07

TAX ; this is the index into the table of 2-byte cap values

JSR CMP_max ; compare Max to its cap

BCS done ; if Max is already >= cap, just return

; otherwise, increase Max

LDY $48 ; stat offset for effective Stamina/Magic Power

ADC ($7A),Y ; effective Stamina/Magic Power ;; 46 M.Power = $2E

LDY $44 ; offset for low byte of Max HP or MP, whichever one we're dealing with at the moment ;; 998 MP = $03E6 so $E6

STA ($7A),Y ; Max HP/MP += effective Stamina/Magic Power (low byte) ;; $E6 + $2E = $14 (with $01 carry?)

INY ; offset for high byte of Max HP or MP, whichever one we're dealing with at the moment ;; shifts Y up to the $03 I'm guessing

LDA #$00

ADC ($7A),Y ; add the carry from the low byte ;; I'm guessing this does $03 + $01 to get $04

STA ($7A),Y ; Max HP/MP += effective Stamina/Magic Power (high byte) ;; $04 + $00

JSR CMP_max ; compare Max to its cap ;; jumping down

...

CMP_max:

LDA ($7A),Y ;; loads A with $7A/Y which at this point should be $04

CMP cap_data-4,X ; compare high byte of Max to high byte of cap ;; should compare $04 to $03, assuming that cap_data-4 is pointing to the right address

DEY

LDA ($7A),Y ;; loads A with $7A/Y which at this point should be $14

BCC +

CMP cap_data-5,X ; compare low byte of Max to low byte of cap ;; should compare $E7 to $14 here, is this potentially the problem?

+

RTS

...

BCC battle_message ; if Max is already less than its cap, there's no need to cap it

; otherwise, cap Max

LDA cap_data-5,X ; low byte of cap

STA ($7A),Y ; low byte of Max

INY

LDA cap_data-4,X ; high byte of cap

STA ($7A),Y ; high byte of Max

cap_data:

dw 9999 ; HP cap

dw 999 ; MP cap

The more I read up on what's happening in CMP_max, the more I'm wondering if the same instructions are feasible for both HP and MP. Carry flag is 0 if the first LDA and CMP results in the cap's HB being higher than the HB of the new max. I would think the BCC could simply be moved up then, but I'm just feeling this out. In all cases in HP where it matters, the HB will be $27, so if max HP's HB is less, BCC could simply happen before the DEY, right? Same goes for MP, as $02 would mean there's no need to do further comparisons. In MP's case, however, we want to consider the possibility that MP's HB is equal to or greater than $03, so BCC would still be acceptable before the DEY I believe... Correct me if I'm wrong. However in the case that the carry flag is 1, for MP, we would want to proceed to DEY, then compare the low bytes... But the problem we run into here is that the low byte comparison's result will vary depending on whether the high byte was $03 or $04, which there doesn't appear to be any accounting for since we're only branching on Carry 0. Perhaps another CMP absolute is needed prior to the low byte comparison and to the DEY?

Correct me if I'm wrong, but it seems like the problem is that we need the program flow to behave in two different ways depending on whether the high byte is $03 (if low byte is greater than cap's, prepare to cap MP) or $04 (if low byte is... ANYTHING, prepare to cap). Right now it simply appears to be comparing the low byte once the branch fails, when a more specific comparison is needed.