BNE/BEQ, BCC/BCS, BPL/BMI

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

Post Reply
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

BNE/BEQ, BCC/BCS, BPL/BMI

Post by SMB2J-2Q »

Is it true that when correctly assembling code built around these opcodes (BNE/BEQ, BCC/BCS and BPL/BMI), that after these mnemonics are read, that the next few lines of code are skipped over until reaching the label called for by these mnemonics?

I used Easy 6502 to test these out, using the debugger console and pressing "Step" to read each line. If the code skips over any line of code following these particular mnemonics, then it should work correctly. But if it reads all the lines in this code, which it did when I tried it in Easy 6502, then I believe (part of) it may not work correctly.

Example 1:

Code: Select all

NYSpd:  ldy #$01           ; set player's vertical speed to nullify
        lda AreaType       ; nullify player's vertical speed if underwater
        bne NYSpd2         ; branch if not underwater
        dey                ; if so, decrement value in Y
NYSpd2: sty Player_Y_Speed ; store current value in Y
What these lines of code do in Super Mario Bros. is to check to make sure the player's vertical speed is intact (the LDY #$01) if the level is either one of these three types (1=above ground; 2=underground; or 3=inside castle), determined by the LDA instruction denoting current level type. The BNE branch will be taken if Mario is presently in one of these three areas, but not so if underwater; if underwater, the branch is skipped over and DEY is then used to subtract the current value in Y to #$00.

But when I changed it to this...

Code: Select all

NYSpd:  ldy #$01           ; set player's vertical speed to nullify
        lda AreaType       
        cmp #$00           ; check world type for underwater stage
        bcc NYSpd2         ; branch if not underwater
        dey                ; if so, decrement value in Y
NYSpd2: sty Player_Y_Speed ; store current value in Y
... it then behaved correctly when I assembled it in Easy 6502. What I did was to add a CMP instruction after the LDA denoting the level type to further denote if Mario is specifically in an underwater stage (#$00). If not underwater, the BCC tells the code to skip over the DEY instruction in order to preserve #$01 in Y.

~Ben
Last edited by SMB2J-2Q on Fri Oct 21, 2022 7:15 pm, edited 1 time in total.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: BNE/BEQ, BCC/BCS, BPL/BMI

Post by tokumaru »

The first version looks correct to me - it loads the area type, and if it's not 0, the branch is taken and Y retains the value of 1. If the area type is 0, the branch is not taken and Y is decremented from 1 to 0.

The second version, however, doesn't make any sense - when testing the Z flag (BEQ/BNE), comparing against 0 is generally redundant, because simply loading a value already sets or clears the Z flag accordingly. You're not testing the Z flag though, you're testing the C flag (BCC). The problem with that is that CMP #$00 ALWAYS leaves the carry set, so that branch is never taken. This code will always set Player_Y_Speed to 0, no matter the area type.

The value of the carry after a subtraction (or a comparison, which is essentially a subtraction that discards the result) indicates whether you subtracted more than the original value. For example: #5 CMP #4 leaves the carry set, since 4 can be subtracted from 5 without problems. But if you do #5 CMP #6, then the carry is cleared, because subtracting 6 from 5 causes an underflow. The thing with CMP #0 is that you can ALWAYS subtract 0 without causing an underflow, regardless of what the other number is, so the carry is always set after that.

I wonder why you though that the second piece of code worked... Have you tried it area types other than 0?

EDIT: Also, keep in mind that assemblers don't skip any lines because of branch instructions - they will calculate the number of bytes that must be skipped when the branches are taken and will output that alongside the branch instruction itself, but it will not skip the assembling of any lines of source code. It's the CPU, at the time it's running the program, that will decide whether to skip or go back the specified number of bytes according to the flag being tested.
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: BNE/BEQ, BCC/BCS, BPL/BMI

Post by SMB2J-2Q »

tokumaru wrote: Fri Oct 21, 2022 7:12 pm The first version looks correct to me - it loads the area type, and if it's not 0, the branch is taken and Y retains the value of 1. If the area type is 0, the branch is not taken and Y is decremented from 1 to 0.

The second version, however, doesn't make any sense - when testing the Z flag (BEQ/BNE), comparing against 0 is generally redundant, because simply loading a value already sets or clears the Z flag accordingly. You're not testing the Z flag though, you're testing the C flag (BCC). The problem with that is that CMP #$00 ALWAYS leaves the carry set, so that branch is never taken. This code will always set Player_Y_Speed to 0, no matter the area type.

The value of the carry after a subtraction (or a comparison, which is essentially a subtraction that discards the result) indicates whether you subtracted more than the original value. For example: #5 CMP #4 leaves the carry set, 4 can be subtracted from 5. But if you do #5 CMP #6, the carry is cleared, because subtracting 6 from 5 results in an underflow. The thing with CMP #0 is that you can ALWAYS subtract 0 without causing an underflow, so the carry is always set after that.

I wonder why you though that the second piece of code worked... Have you tried it area types other than 0?
Yes, the game begins on an above ground level (#$01 as denoted in AreaType, or $074e).

No such check was ever accounted for in the NTSC version (neither the original 8-bit NES release nor the SNES version part of Super Mario All-Stars), which was written only as loading A with #$01 and storing it in RAM $009F.

Here's another version for you to check out:

Code: Select all

NYSpd:  ldy #$00           ; set player's vertical speed to nullify
        lda AreaType       
        beq NYSpd2         ; branch ahead if we are underwater
        iny                ; if not, increment value in Y to #$01 to impose gravity
NYSpd2: sty Player_Y_Speed ; store current value in Y
What this version does, it starts off the routine loading Y with 0. As such, the BNE is replaced by BEQ and the DEY with INY. The BEQ after the LDA determines if Mario is underwater or not, and if he's not, then INY is loaded to increase the value in Y to #$01 in order to impose gravity.

~Ben
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: BNE/BEQ, BCC/BCS, BPL/BMI

Post by tokumaru »

This one should be functionally identical to the first one you posted. In both cases, Player_Y_Speed will be 0 if AreaType is 0, or 1 if AreaType is anything else. Both of these should be working. What are the results you're getting?
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: BNE/BEQ, BCC/BCS, BPL/BMI

Post by SMB2J-2Q »

Results so far:

World 2-1, PAL
Image

World 1-1, PAL (inside coin room):
Image

World 2-2, PAL
Image

World 1-1, NTSC with code modification (outside):
Image

World 1-1, NTSC with code modification (inside coin room):
Image

World 2-2, NTSC with code modification:
Image

PAL version:
In underwater levels, the value in STY $009F is given as #$00
In above ground levels, the value in STY $009F is given as #$00
In the underground levels, the value in STY $009F is given as #$00

NTSC with code modification:
In underwater levels, the value in STY $009F is given as #$02
In above ground levels, the value in STY $009F is given as #$03
In underground levels, the value in STY $009F is given as #$04

~Ben
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: BNE/BEQ, BCC/BCS, BPL/BMI

Post by SMB2J-2Q »

tokumaru wrote: Fri Oct 21, 2022 7:29 pm This one should be functionally identical to the first one you posted. In both cases, Player_Y_Speed will be 0 if AreaType is 0, or 1 if AreaType is anything else. Both of these should be working. What are the results you're getting?
My new question is this: why does the value within RAM $009F constantly change? I know with the PAL updated instruction, this value never changes from $00, per the results I sent here.

I wanted to share with you the result of RAM $009F, per the unaltered NTSC swimming code:

Code: Select all

NYSpd: lda #$01           ; set player's vertical speed to nullify
       sta Player_Y_Speed ; jump or swim
Above ground (current value in $009F = #$03):
Image

Underground (current value in $009F = #$04):
Image

Underwater (current value in $009F = #$FF):
Image
What is really happening in $009F?

I also wanted you to check out this modification I did:

Code: Select all

NYSpd:  ldy Player_Y_Speed ; check player's vertical speed
        lda AreaType       ; check area type for dry land level (1=above ground; 2=underground; or 3=castle)
        cmp #$01
        bcc NYSpd2         ; if underwater (#$00), skip this check
        ldy #$01           ; load Y with #$01 if valid
NYSpd2: sty Player_Y_Speed ; jump or swim
Line by line analysis: the first LDY checks the player's present vertical speed (we want to fetch the Y speed's address first, without changing anything yet). Then we come to LDA to determine which area the player is in, to see if the vertical speed check is valid. The CMP #$01 tells the LDA to specifically check if the player is on any dry land level (1=above ground; 2=underground; or 3=castle), and if valid, the branch is skipped over and Y is set to #$01 by default, but if the player is in any underwater stage (#$00 as determined by $074e), then the branch (BCC) is taken in order to store #$00 as the player's present vertical speed.

Results:

Above ground
Image

Underground
Image

Underwater
Image

~Ben
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: BNE/BEQ, BCC/BCS, BPL/BMI

Post by SMB2J-2Q »

I think I have a more airtight solution as to this logic. Please review it:

Code: Select all

NYSpd:  ldy #$00           ;set to nullify player's vertical speed
        lda AreaType,y     ;check the current world type and offset this value into Y
        cmp #$00           ;check for underwater level
        beq NYSpd2         ;branch if found
        iny                ;otherwise, increment Y with 1
NYSpd2: sty Player_Y_Speed ;store #$01 (jump) or #$00 (swim) depending on terrain type
Analysis: The LDY stores the default offset for the player's vertical speed if underwater, which is #$00. The LDA AreaType,Y tells the game to check the current world type (00=underwater; 01=above ground; 02=underground; or 03=castle) and then offsets by loading A's current value into Y. The CMP then tells the game to specifically check for any underwater levels, and the BEQ branch is taken if valid. If the player is not underwater, which occurs most of the time, the BEQ is not taken and the game increments the present LDY value to 01 via the INY instruction. Either check will store the present value in Y via STY which finishes the process.

~Ben
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: BNE/BEQ, BCC/BCS, BPL/BMI

Post by tokumaru »

Looks good, but you don't need the CMP #$00. Simply loading a value of 0 already sets the Z flag accordingly, so that CMP #$00 is completely redundant.
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: BNE/BEQ, BCC/BCS, BPL/BMI

Post by SMB2J-2Q »

I am revisiting this topic to ask you all about this, what Shane M. did when he was active.

Anyway, this is about his modification to the PlayerEnemyCollision routine; his additions included these three instructions:

Code: Select all

lda Player_Y_Position
cmp #$b1
bcs NoPECol
The additional check he devised was to determine if Mario's vertical position was exactly one pixel below ground level (the CMP #$b1) and, if it was, to void the collision (the BCS). However, since #$b1 is a value for which the negative flag is set (most significant bit on, so also -79), then shouldn't this BCS really be a BPL? Also, for PAL, wouldn't the #$b1 value be #$c1 due to the vertical rez being slightly wider (256x240 vs. NTSC's 256x224)?

~Ben
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: BNE/BEQ, BCC/BCS, BPL/BMI

Post by tepples »

BPL branches if the last ALU result was 0 to 127 ($00 to $7F) mod 256. Doing BPL would cause collisions in the top 48 pixels of the screen (status bar and top row of blocks, $00 to $30) not to register, as the result would be $4F-$7F. BCS/BCC is the most often appropriate test for a comparison or subtraction of unsigned numbers.

As I understand it, SMB draws the floor and actors at the same height (relative to the top of the nominal picture) regardless of TV system. The vertical blanking is not included in coordinates. It's not like MD or SNES games that might use 224-line mode on NTSC or 240-line mode on PAL. So no, it wouldn't change.
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: BNE/BEQ, BCC/BCS, BPL/BMI

Post by SMB2J-2Q »

tepples wrote: Fri Dec 16, 2022 7:08 am BPL branches if the last ALU result was 0 to 127 ($00 to $7F) mod 256. Doing BPL would cause collisions in the top 48 pixels of the screen (status bar and top row of blocks, $00 to $30) not to register, as the result would be $4F-$7F. BCS/BCC is the most often appropriate test for a comparison or subtraction of unsigned numbers.

As I understand it, SMB draws the floor and actors at the same height (relative to the top of the nominal picture) regardless of TV system. The vertical blanking is not included in coordinates. It's not like MD or SNES games that might use 224-line mode on NTSC or 240-line mode on PAL. So no, it wouldn't change.
Thank you!

I also wanted to let you know that he originally typed the CMP as $B1, meaning it would compare the pixel state to whatever value is in address $00B1, meaning he forgot to add the # before the $B1.

~Ben (SMB2J-2Q)
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: BNE/BEQ, BCC/BCS, BPL/BMI

Post by SMB2J-2Q »

I am also wondering if, I could add these three instructions on top of the modified Y speed cancellation code:

Code: Select all

ldy Player_Y_Speed ;get player's vertical speed
cpy #$02           ;check if it's above 1
bcc NYSpd2         ;if not, branch ahead and store speed
ldy #$00           ;otherwise load default Y speed for water type levels
lda AreaType       ;check if we're in such a level
beq NYSpd2         ;if we are, branch ahead to store speed
iny                ;otherwise increment Y by 1 to impose gravity for dry land levels
NYSpd2:
sty Player_Y_Speed ;store 1 or 0 as present speed according to terrain type
Breakdown:
LDY $9F gets whatever value of the player's Y speed is in memory.
CPY #2 caps the value stored in $009F at 2.
BCC NYSPD2 tells it to branch to store whatever value in $009F is present, so long as it isn't 2.
LDY #0 is the default Y speed for water type levels.
LDA $074E (AREATYPE) determines which area type applies to the two speed values stored in $009F.
BEQ NYSPD2 tells it to branch ahead to store the value of 0 to $009F if indeed we're in a water level.
INY tells the program to increment $9F up to 1 if we're not in a water level (cf. ground, underground or castle).
STY $9F (NYSPD2) stores whatever value is present in $009F (0 if water type level, or 1 for all other levels)

~Ben
Post Reply