The Super Mario Bros. level loading routine processes tile objects on the fly, and the order they are written to the tile buffer depends on where those objects show up in the level data, and where they show up on the screen. This makes it non-trivial to overlap certain objects with others, since you cannot explicitly define the z-order of each object.
For a measly payment of one coin, you can have your bricks overwrite your blocks!
A lot of times, a ? block or other block with a powerup will be placed inside of a larger row of brick blocks. To reduce the number of objects and ultimately save ROM space, you can have just a single row of bricks, and just have those ? blocks in the same location as some of the bricks, overwriting them.
The classic Mario block setup consists of one row of 5 bricks, 3 ? blocks with a coin, and a ? block with a powerup.
However, this won't always work! In fact in the original game, this is only done at the very start of 1-1. Every other row of bricks is split into two or more objects, with those ? blocks or what have you in between.
In this case, the overlapping problem can be avoided by creating separate objects, but this is not possible with larger objects, most notably the tree and mushroom platforms. So let's see what was done to address this issue!
The mushroom in the upper middle ends up in a higher queue slot (and therefore higher tile priority), yet it falls underneath the mushroom under it!Since objects are processed in a fixed order, the only way to have an object fall 'behind' another object, would be to check what tiles are already written in the tile buffer, and decide whether or not to actually write the tile for the new object based on which tile is there. This is exactly what is done in the game.
; $9B7D: write lower priority tiles to the tile buffer
; Y = number of tiles to draw
; X = Y position of the first tile to draw
; A = tile number to draw
RenderUnderPart:
STY TileObjectHeight ; temp location of counter
LDY TileBuffer,X ; see what is already written here
BEQ .write ; if there's nothing, we can write
CPY #$17
BEQ .skip ; if it's a tree platform, don't write
CPY #$1A
BEQ .skip ; if it's a mushroom platform, don't write
CPY #$C0
BEQ .write ; if it's a ? block with coin, overwrite it
CPY #$C0 ; if it's any other tile that uses the brown
BCS .skip ; palette (coin, ? block, axe), don't write
CPY #$54
BNE .write ; if it's anything but dirt, overwrite it
CMP #$50 ; if we are trying to write a mushroom stem
BEQ .skip ; middle part, don't write it
.write:
STA TileBuffer,X
.skip:
INX
CPX #13 ; don't go past the end of the buffer
BCS .done
LDY TileObjectHeight
DEY ; decrement the length counter
BPL RenderUnderPart ; and loop if there's more to go
.done:
RTS
Here is a routine called RenderUnderPart which is called by certain objects (not all of them!) when they want to draw a column of similar tiles that are considered lower priority. Some of these objects include the mushroom and tree platforms, pipes, the flagpole, and the Bullet Bill cannon.
Not pictured: holes (with and without water), unused flag balls object, vertical lift ropes
If a tile is not included in this image above, it does not even check the underlying tile and overwrites no matter what.
The first comparison LDY : BEQ, will write the tile in question if there is no underlying tile. That's pretty self-explanatory. This means that the rest of the code will be able to assume that there is something already there.
The next two comparisons CPY #$17 : BEQ : CPY #$1A : BEQ, will skip writing this tile to the buffer if the underlying tile is the center section of the tree or mushroom platforms. These lines are here to make placing these platforms in a level easier. The tree trunk and mushroom stem will always extend to the bottom of the screen, so if you want two of these platforms stacked on top of each other, they will overlap. Interestingly, this only prevents the center tile of the platform from being overwritten--the left and right edges will still get clobbered.
Higher priority tree's trunks will overwrite lower priority tree's ends, but not their middle sections.
This is contrary to what I show in one of my videos, because I hadn't looked into this routine extensively yet at the time.
The next comparison CPY #$C0 : BEQ, is a mystery to me. This will make it so that if the underlying tile is a ? block specifically with a coin, it will always get overwritten. I have a feeling this used to be something else and was changed at a later point in time. My evidence is the next line...
The next comparison CPY #$C0 : BCS, will prevent any tile (other than the ? block with coin) that uses the shiny brown palette from being overwritten. (The upper two bits of the tile number essentially encodes its palette.) This includes coins, ? blocks, and the axe. It makes sense that these objects should have a higher priority, but I don't know why the coin ? block is left out here. A BCS acts like a 'greater or equal' comparison here, so it would include the coin ? block here, but the previous comparison nullifies it.
A pipe in queue slot 3 (the highest priority) will overwrite ? blocks with coins, but not ? blocks with powerups (or poison mushrooms)!
The next comparison CPY #$54 : BNE, will prevent an underlying dirt tile from being overwritten just yet. The branch if not equal here means that any other tile will end up being overwritten. Essentially that means that this is the default operation if no exceptions are caught by any of the other comparisons.
The final comparison CMP #$50 : BEQ, actually compares to the tile we are writing rather than the underlying tile (which is presumed to be dirt due to the previous comparison). It checks for the smooth stem of the mushroom platform, so if we are trying to write a mushroom stem on top of a dirt tile, it won't go through. These two comparisons were probably put in place specifically for the warp zone room in 4-2 with the mushroom platforms so that their stems don't eat into the dirt blocks below. This means that although dirt is a lower priority tile, it still has priority specifically over the mushroom stem.
Can't let those mushrooms eat through the ground now! Funny enough, these last two comparisons are absent in Lost Levels, since the mushroom platform doesn't exist--it was replaced with a cloud platform that has no stem. The middle section of the cloud still has higher priority though.
Then the tile gets written to the buffer (or not), the counter for this column of tiles is decremented, and we loop back up to the top again until we're finished. There is a bounds check on the size of the tile buffer (which is 13 tiles big) since any objects that extend "to the bottom of the screen" really just extend downwards 13 tiles.
But notice that this bounds check is at the bottom of the loop instead of the top! This means that if the Y position of the tile to draw is out of bounds when this routine is called, it will run this loop once before checking and realizing that the X register is way larger than 13.
This means that a big chunk of memory is susceptible to being written to if an object tries to draw tiles way off the bottom of the screen. This is exactly what I was referring to in the last article with the staircases. The maximum staircase can write to the memory locations that set the second quest flag, as well as change the background scenery into clouds without needing a special scenery change object!
A lovely change of scenery.