• She/Her

Queer. Left. Writes code and plays games. ΘΔ. Fiancee of
@KiyoneScarlet


rgmechex
@rgmechex

(This week's article comes from a question asked in the RGMechEx Discord server. You can join it here!)

So here's a thing that me and a bunch of other people have noticed before: the breakable bricks in the Super Mario All-Stars version of Super Mario Bros. seem less firm than in the original NES release. That is, they don't seem to pack as big of a punch to Mario's noggin when he hits one from below. In fact, if you just barely bump a block at the apex of your jump in the Super NES version, it will send Mario upwards for some reason.

Mario hitting a brick block in Super Mario All-Stars, and he pops upward a tiny bit after shattering it Mario's head hitting bricks in this game is like a hot knife cutting through butter.

I've heard that the developer's accidentally reversed Mario's impulse velocity when breaking a brick somehow, but that doesn't seem right. We've known that most of the code for the All-Stars releases of games was just a copy-paste version of the NES versions, so it sounds unlikely that a random byte would be changed like this. Time to investigate!


I'll walk you through my process here. First, we can check if the velocity of Mario was inverted or not. We need to find the code that runs when a brick is broken, and where Mario's velocity gets set.

I'm going to go to this Super Mario Bros. disassembly by doppelganger and Ctrl+F for things like 'brick', 'break', 'shatter', etc. This looks promising:

BrickShatter:
    jsr CheckTopOfBlock    ;check to see if there's a coin directly above this block
    lda #Sfx_BrickShatter
    sta Block_RepFlag,x    ;set flag for block object to immediately replace metatile
    sta NoiseSoundQueue    ;load brick shatter sound
    jsr SpawnBrickChunks   ;create brick chunk objects
    lda #$fe
    sta Player_Y_Speed     ;set vertical speed for player
    lda #$05
    sta DigitModifier+5    ;set digit modifier to give player 50 points
    jsr AddToScore         ;do sub to update the score
    ldx SprDataOffset_Ctrl ;load control bit and leave
    rts

Okay, so there it is! Mario's velocity gets set to #$FE, or -2 when he breaks a brick. Positive velocities point downwards, so this means... Mario is set to move upwards. Well that's interesting! Because that's exactly what happens in the All-Stars version, but it doesn't feel like it in the NES version. Let's check the SNES version real quick.

The function BBKINT from the Super Mario Bros. source code This is from leaked source code so I will only be posting images instead of text.

Yeah, this routine is here, with only a small change regarding the sound effect that plays. Mario's velocity is set to -2 in both games. So that disproves that! The date on this file within the Super Mario All-Stars source is August 12, 1985, so I would be surprised if there were any tiny changes like that.

So, what's the deal then? Why do the two games differ? And why is Mario's velocity being set in the upwards direction when he should be moving downwards? More investigation needed...


I'll put the two instances of the game next to each other and step frame by frame as I break a block, and see when the difference occurs.

Frame by frame side-by-side of Mario breaking a brick in Super Mario Bros. and Super Mario All-Stars The brick breaks on the same frame in the same exact way, but Mario hits an invisible force in the NES version.

It looks like the change in velocity gets applied way after the block breaks, so I don't think this will help much. However, I did notice something when I did this. In the NES version, there are two sound effects that get played when you break a break--the brick shattering sound, and the bump sound that plays when Mario hits his head on any ceiling. In the Super NES version however, there is only the brick shattering sound. It's quite hollow sounding because there is no thud sound (and the bonk sound in this game has some oomf to it too). That gives me an idea.

Instead of looking at the game, I'm going to watch the memory. The memory region from $0500 - $069F (in both versions--the code was copy-pasted afterall) holds the tile IDs of all the blocks currently loaded. Let's see what happens as I step frame by frame again.

Hex data of the games' level layouts per frame NES version on the left, SNES on the right. The orange byte is the tile of the brick block.

There it is, just as I suspected! In the NES version, it takes two frames for the brick block to disappear. Mario actually bonks his head on this mystery block that appears after the brick disappears for a single frame. This mystery block does not appear in the Super NES version, so Mario has nothing to bonk his head against. When Mario bonks his head on a ceiling, his velocity is set to positive 1. So that means after breaking a brick, he will have a velocity of +1 in the NES version, but -2 in the Super NES version. Solved!


But what is this mystery block?? Well, the mystery block is just the invisible solid block that appears after hitting any bumpable block. When a block plays its little animation of being hit, it turns into a sprite object temporarily. The background becomes blank in that time, but that tile still has to act solid during the animation. This is the purpose of the mystery block.

A ? block being bumped from below Sometimes this animation can glitch out which will break the illusion that the blocks are always a single entity.

But why does it not appear in the Super NES version? Let's find out. I'm going to set a break point in my emulator (which is Mesen2, I highly recommend, link here) to pause when this specific memory location gets written to in both versions.

The debugger screen from an emulator showing nearly identical code from each game where the mystery block ID is being written to memory The ID of the mystery block (and a lot of blocks) changed, but the code is identical.

Okay wait, the mystery block is there in the Super NES version too? Huh. If I let the games run again I should get the blank tile next.

The debugger screen from an emulator showing nearly identical code from each game where the mystery block ID is being cleared from memory You can see the location of the code in the ROM is different too, which changes all the jump and call targets.

Yep, there they are. But something happened that gives us another clue! In between these break points, the NES version of the game advanced a frame. So on one frame, the mystery block was written, and then cleared on the next frame. In the Super NES version, these writes happened on the same frame. This would explain why Mario never interacts with the mystery block--it appears and disappears before any physics code runs! But why??


Let's look at that function that runs to clear the mystery block. If I set a break point at the start of this function on the NES version, I find that it runs once per frame.

The debugger screen from an emulator showing a function responsible for drawing tiles to the screen I need to get a symbols file so that all these memory addresses have labels...

If I go to the disassembly, it looks like this is responsible for drawing various blocks to the screen.

BlockObjMT_Updater:
    ldx #$01                  ;set offset to start with second block object
UpdateLoop:
    stx ObjectOffset          ;set offset here
    lda VRAM_Buffer1          ;if vram buffer already being used here,
    bne NextBUpd              ;branch to move onto next block object
    lda Block_RepFlag,x       ;if flag for block object already clear,
    beq NextBUpd              ;branch to move onto next block object
    lda Block_BBuf_Low,x      ;get low byte of block buffer
    sta $06                   ;store into block buffer address
    lda #$05
    sta $07                   ;set high byte of block buffer address
    lda Block_Orig_YPos,x     ;get original vertical coordinate of block object
    sta $02                   ;store here and use as offset to block buffer
    tay
    lda Block_Metatile,x      ;get metatile to be written
    sta ($06),y               ;write it to the block buffer
    jsr ReplaceBlockMetatile  ;do sub to replace metatile where block object is
    lda #$00
    sta Block_RepFlag,x       ;clear block object flag
NextBUpd:
    dex                       ;decrement block object offset
    bpl UpdateLoop            ;do this until both block objects are dealt with
    rts                       ;then leave

Every frame it makes its way through the entire function. However, I notice that on the frame that the brick is hit, the branch at $BED8 is taken, which basically skips an entry in the buffer for that frame. From the comments on this code here, it looks like maybe that the VRAM buffer is being used. It looks like maybe the game can only draw one kind of block at a time. If more than one is queued up, then they are drawn on successive frames instead. This lines up with what we saw here. What about the Super NES version?

Well this function exists over here as well, and it runs once per frame too. However, it runs through on every frame without a hiccup. Interesting. Just curious, what does the original source for this function look like?

The function AGBLOK from the Super Mario Bros. source code I honestly think the comments from the disassembly are better than the original source.

Oh! Well look at that, I didn't even notice. The branch that the NES was taking was commented out in the Super NES version! I presume maybe because the Super NES has the capability to be able to draw more tiles at once without causing graphical glitches or whatnot. The entire VRAM buffer that the NES references here is completely absent from the Super NES version of the game. This seems to be the root cause for why Mario interacts differently with bricks between the two versions. There's even proof with the commented out lines!


Just out of curiosity, what would happen if we commented out these lines in the NES version? Maybe we'll see why that check was put there in the first place.

Well I tried it and nothing bad seems to happen. I'm sure with more testing or a deeper analysis of the code would reveal why that check was done, but at a first glance it seems like everything works just fine. We can even see Mario bounce upwards when he just barely grazes a brick block:

Mario breaking a brick block and gaining a small bit of height from it in Super Mario Bros. This just feels wrong.

So why is Mario's velocity set to be an upwards direction when he hits a block? Who knows. Maybe it was an accident, and the temporary solid block just prevented him from moving upwards anyway and so it was never noticed. Or maybe this was done on purpose to get the 'bump' sound to play too? Or perhaps this was originally the intended way that Mario would break bricks. It will forever be a mystery.


You can support Retro Game Mechanics Explained on Patreon here! Any and all support is greatly appreciated!


You must log in to comment.

in reply to @rgmechex's post:

What happens if you comment out the -2 forced upward velocity, but do not remove the phantom brick? What I'd guess is with the phantom brick in place Mario bounced downward in an unsatisfying way, so they (possibly without rigorously investigating the root cause) added the -2 to cancel it out. (Though this theory doesn't seem to mesh well with your description of what the phantom block does…)

If I had to guess, it's to make sure Mario moves up into the block in the first place. Maybe there was some circumstance where Mario would get close enough to break the brick, but not close enough to bump off of it?

finally something i actually knew already because of living with rachel, who fixed this bug twice iirc (once the "wrong" way, then once again years later when we knew what was actually going on)

What approach does the "Brick Fix" patch on RHDN use? The description of the patch implies it only saw the -2 and decided that was the issue, but then it also mentions the bump sound being in, which just setting the -2 to 2 wouldn't fix per this write-up. Does it do the "wrong" fix and then hack the "bump" in, instead of more accurately reproducing the NES behavior which generates the bump naturally, or is the patch description just out of date?