From Loopy's doc:
Unfortunately, SMB1 does split the screen. A stationary status bar occupies the top 3 tile rows on all screens and below that, Mario's world scrolls horizontally. The status bar is part of the background; it's just a segment of background that does not scroll. That's why clouds, pipes, flag poles, castles, etc. never appear in front or behind the status bar. But, sprites, such as Mario, can appear in front or behind the status bar.If you aren't trying to split the screen, scrolling the background is as easy as writing the X and Y coordinates to $2005 and writing the high bit of both coordinates to $2000. Programming or emulating a game that uses complex raster effects, on the other hand, requires a complete understanding of how the various address registers inside the PPU work.
To split the screen, the lower 2 pixel rows of the blinking coin in the status bar, which protrude below the rest of the status bar, are duplicated in sprite 0. Sprite 0 is rendered behind the background (to ensure that is always below any other sprites) directly behind the blinking coin of the status bar. And, as long as the status bar is rendered properly, a sprite 0 hit will occur enabling the code to modify the scrollX offset for the subsequent scan lines.
The stationary status bar only exists in nametable $2000. The horizontally scrolling background below it exists between nametables $2000 and $2400.
Now, my emulator would freeze when Mario walked one screen to the right at the moment the base nametable changed from $2000 to $2400. I noticed this change took place during the vertical blanking period, which did not make any sense. It should always set the base nametable to $2000 for the new frame since that's where the stationary status bar was located. Consequentially, as soon as Mario crosses the nametable boundary, the status bar was not rendered, which in turn failed to trigger the sprite 0 hit, freezing the emulator.
But, as mentioned, I managed to get it to work. Though, I am really not sure if my solution is a hack or accurate, which is why I am writing up this post
Since, the base nametable is set via PPUCTRL ($2000) during VBlank, according to Loopy's doc, a full implementation of all the PPU registers and their associated behaviors should not be necessary for the game to work. In addition, logging the calls indicated that after the sprite 0 hit, it was simply setting the base nametable to $2000 or $2400 appropriately. It was never doing strange sequences of $2005 and $2006, etc. that would suggest that something more complicated needed to be implemented.
From my logging, I noticed that showing the background and showing the sprites were disabled during the VBlank via PPUMASK ($2001). While they were disabled, the base nametable was changed to $2400. Then, showing the background and showing the sprites were enabled. So, to fix the issue, I added a check that only allowed the base nametable address to be set when showing the sprites or showing the background were enabled. With that check in place, the base nametable is stuck at $2000 at the start of each new frame.
Is that the way it is supposed to work? Or, is this just as bad as simply forcing the base nametable address to $2000?
Edit: Well, that check apparently broke other games. So, I'm guessing that I'm not supposed to do that. Will implementing all the PPU registers somehow take care of this issue? Suggestions are welcome. Thanks.