Super Mario Bros (SMB1) and sprite 0 hit

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Super Mario Bros (SMB1) and sprite 0 hit
by on (#138293)
I am attempting to write an NES emulator and I finally got SMB1 running. I have yet to implement all the PPU registers discussed in Loopy's "The skinny on NES scrolling" document. Rather, the emulator simply updates the base nametable address during PPUCTRL ($2000) writes and it updates scrollX and scrollY offsets during PPUSCROLL ($2005) writes.

From Loopy's doc:

Quote:
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.


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.

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.
Re: Super Mario Bros (SMB1) and sprite 0 hit
by on (#138297)
zeroone wrote:
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.

If I do this, it'll still set the nametable base to 1.
Code:
  lda #$00  ; disable rendering
  sta $2001
  lda #$81  ; enable NMI on line 241; set nametable base to $81
  sta $2000
  lda #$1E  ; enable rendering of background and sprites
  sta PPUMASK


So long as background or sprite rendering is enabled at x=304 of the line before the first line of picture, the nametable base will take effect when PPU copies t (top left address) to v (current video memory address).* What SMB1 does that might be tripping you up is set the VRAM address directly through writes to $2006. These affect both v and t.


* The first line (and no other lines) may be glitched if rendering is enabled after 256 but before 304.
Re: Super Mario Bros (SMB1) and sprite 0 hit
by on (#138302)
Quote:
What SMB1 does that might be tripping you up is set the VRAM address directly through writes to $2006. These affect both v and t.


That is certainly possible. And, unfortunately, that would mean I would have to do a full implementation of Loopy's doc to get this working correctly :/
Re: Super Mario Bros (SMB1) and sprite 0 hit
by on (#138305)
Eh, not really. Emulators had SMB running loooong before scrolling was completely figured out. You should be able to get it working just by updating the scroll position when $2006 is changed. You don't need to deal with all the weirdness with interleaved 2005/6 writes.
Re: Super Mario Bros (SMB1) and sprite 0 hit
by on (#138313)
True. I can get it working using the hack mentioned in the original post. But, I might as well shoot for a higher goal.

Looking at this stuff makes me wonder how the developers of SMB1 ended up using these facilities. The Famicom came out a few years earlier and none of the launch titles resemble anything like SMB1. During those first few years, devs must have recognized the shortcomings of the system such as a lack of a scanline interrupts forcing them to invent the sprite 0 hit test. The scroll offsets were probably only designed for being modified between frames until someone discovered how to get around that. Sigh. Long story short, emulating this is going to be challenge.
Re: Super Mario Bros (SMB1) and sprite 0 hit
by on (#138400)
I implemented everything in Loopy's "The skinny on NES scrolling" document and now it works perfectly :) Amazing!

I suspect that a full implementation probably was not necessary; the writes to $2005 and $2006 suggest that the SMB1 devs really did not intend to fully exploit the PPU registers. Rather, they simply needed the base nametable address reset to $2000 at the start of each frame and they must have noticed at some point that were doing that indirectly, saving them the extra code to do it directly. I could have sorted out the values written to $2005 and $2006 and made this work, but what's the point. Loopy's solution is a far more general solution and it certainly works well.
Re: Super Mario Bros (SMB1) and sprite 0 hit
by on (#138726)
Now, I'm running into a similar issue with Castlevania. It detects a sprite 0 hit on scanline 38, but it fails to split the screen properly. The status bar scrolls along with the rest of the background. It's almost as if the code waits for the vblank before responding to the sprite 0 hit. Suggestions to debug this are welcome. Thanks.
Re: Super Mario Bros (SMB1) and sprite 0 hit
by on (#138813)
Make sure the sprite 0 hit flag turns off when the screen starts rendering. The sprite 0 flag stays on through the entirety of vblank time, but turns off when rendering starts.
Re: Super Mario Bros (SMB1) and sprite 0 hit
by on (#138830)
I managed to resolve my Castlevania issue. It appears to have been timing related.