I'm a bit stuck trying to figure out why my code won't scroll properly in Super Mario Bro, also the background colour is wrong ..
My code attempts to render a scanline at a time, scrolling works fine on games that don't use sprite 0 or irq tricks (like Lode Runner or Xevious).
How does SMB decide to stop scrolling, after a vblank?
The scrolling doesn't "stop", so if a program changes it mid-frame, it stays changed (even through to the next frame). Apparently your emulator is resetting it. Maybe it's incorrectly initiating a second NMI in the middle of the frame?
What I meant to ask was; in SMB, the 'status' area at the top isn't supposed to scroll - so the code must reset the scroll bits (during the vblank?), then set them again when sprite 0 hit is set?
Sounds like this happens on the vbank NMI.
Here is what happens;
1. On the first cycle of the first VBlank scanline an NMI is executed.
2. The VRAM Address is set to $0000 during VBlank.
3. When the scanline 51 (incl. vblank + dummy scanline(s)), sprite 0 is present, this triggers the PPU status register to set the sprite 0 bit.
4. The programmer reads from $2002 around this time and when it finds the sprite 0 bit set, it sets the scroll. It does this for EVERY frame throughout the game.
Horizontal scroll is reset at the end of every scanline.
Presumably once set the sprite 0 bit in the status register should remain set until the next vblank.
Horizontal scroll is reset and the end of every scanline? By the game code or the NES (in this case my emulator)?
Sprite 0 hit flag remains set until the beginning of scanline -1 (the scanline immediately before the first visible one). At that point, all bits in the status register are cleared to zero. The sprite 0 flag is set at the precise time that a pixel is drawn containing a non-transparent sprite 0 pixel and a non-transparent background pixel.
SMB clears the scroll registers during VBlank, then polls $2002 until the sprite 0 flag is set. It then counts a certain number of CPU cycles (waiting for the PPU to finish drawing the status bar), then writes to $2005 and $2000 to adjust the horizontal scroll.
Quote:
Horizontal scroll is reset and the end of every scanline?
He means that the actual horizontal scroll will be taken in account by the hardware only from the start of the next scanline, even if the write to $2005 happen in the middle of it.
To keep things accurate, the 3 lowest bits of horizontal scrolling takes effect immediately, but the 6 highest bits, including the 9th bit wich is acessed by $2000.0 instead of by $2005, will be taken in account only after the next HBlank.
Going direct into the point: re-do your loopy's PPU address "magic", plus pay attention for the palette RAM thing.
Fx3 wrote:
Going direct into the point: re-do your loopy's PPU address "magic", plus pay attention for the palette RAM thing.
I don't think he's using loopy's documents.
WedNESday wrote:
Fx3 wrote:
Going direct into the point: re-do your loopy's PPU address "magic", plus pay attention for the palette RAM thing.
I don't think he's using loopy's documents.
I am
attempting to follow the info in
http://nesdev.com/loopyppu.zip, but obviously I'm not quite there yet! Are there other docs I should be reading, by loopy or anyone else for that matter?
Disch wrote:
http://nesdev.com/bbs/viewtopic.php?t=664
Thanks, that looks like just what I need.
It leads to more questions 'tho.. you say LoopyV/2006 is effectively 15 bits, but in order to pass Nestress PPU test 'PPU $3fff-$0000 boundary' I have to wrap at 14 bits!
No, $0000-3FFF is just mirrored to $4000-7FFF (just like CPU RAM at $0000-07FF is mirrored through $0800-1FFF)
Uhh... did you try PPU_address &= 0x7FFF on reads/writes into 2007h?
Disch wrote:
No, $0000-3FFF is just mirrored to $4000-7FFF (just like CPU RAM at $0000-07FF is mirrored through $0800-1FFF)
OK, I see. While the PPU register can address a 15 bit range, in the NES half of it is just a mirror.
The problem was my code was resetting $2005 and $2006 whenever $2002 was read.
On page 18 of
http://nesdev.com/NESDoc.pdf it says:
When a read from $2002 occurs, bit 7 is reset to 0 as are $2005 and $2006.
I took this to mean the whole of $2006 and $2006 should be cleared, maybe it means clear bit 7 of $2005 and $2006 along with bit 7 of $2002?
Anyway I changed my code to clear bit 7 of $2002 only and SMB scrolls as expected.
Reading PPUSTATUS ($2002) doesn't set PPUSCROLL ($2005) and PPUADDR ($2006) to zero. It does set to zero the (1-bit) latch used to distinguish X from Y in PPUSCROLL and high byte of address from low in PPUADDR.
To elaborate more on what tepples is saying:
$2005 and $2006 use a sort of toggle, which determines which write is the "first" write, and which write is the "second" write. Reading $2002 does not change $2005 or $2006, but it does reset this toggle so that the next write will be the first write:
Code:
BIT $2002
STA $2005 ; first write
STA $2005 ; second write
STA $2005 ; first write
BIT $2002 ; reset toggle
STA $2005 ; first write (again)
Also note that $2005 and $2006
share the same toggle:
Code:
BIT $2002
STA $2006 ; first write
STA $2005 ; second write (even though it's the first to $2005)
STA $2005 ; first write
STA $2006 ; second write
Disch wrote:
Also note that $2005 and $2006 share the same toggle
Thanks for the info. I was already using toggles on $2005 and $2006 that reset on $2002 read, but I didn't realise they shared the same one.
-
Any idea why I'm getting a black background? I'm taking the background colour from the first entry (0) in the image palette.
Fx3 wrote:
Are you saying I should use image palette 0 unless vramaddress is $3Fxx, then use that?
-
Yet another problem, my sprite 0 hit isn't 100% either. When SMB scrolls a complete screen my 'is background pixel transparent' check fails.
Is this something to do with switching nametables, changing the attributes for the backround tile that sprite 0 hits?
anon wrote:
Are you saying I should use image palette 0 unless vramaddress is $3Fxx, then use that?
This is just for when rendering is disabled.
I think the problem with the black background has something to do with palette mirroring... IIRC, SMB does not write the background color to $3F00, it uses some mirror instead. Maybe you're missing that write?
Anyway, the answer for this should be simple, this problem comes up quite often.
anon wrote:
Are you saying I should use image palette 0 unless vramaddress is $3Fxx, then use that?
Only when the PPU is off. When the PPU is on ... you always use $3F00 for the background color (when both BG and sprite pixels are transparent).
However -- note that $3F10 mirrors $3F00... so when a game writes to $3F10 it will change the background color! I don't think this is an issue for SMB, but I knot it's an issue for other games (Megaman 2 comes to mind right away).
Quote:
Is this something to do with switching nametables, changing the attributes for the backround tile that sprite 0 hits?
Attributes don't matter. Sprite-0 hit is determined at render time. If whatever BG pixel you're rendering is opaque, AND the sprite 0 pixel for the same screen pixel is also opaque, sprite-0 is hit and the flag will rise.
Because it is determined by which BG pixel is rendered -- Scrolling, Nametable selection, pattern table selection, and Mirroring mode all affect if/when sprite 0 will hit.
The actual check itself is very simple (for some pseudo-code):
Code:
if( (BG_pixel & 3) && (is_this_sprite_0) && (Spr_pixel & 3) )
status_2002 |= 0x40;
The tricky part is the timing, and implimenting it efficiently. Depending on how you have your PPU set up, and how sharp your timing is, getting Sprite 0 to work 100% can be a real nightmare.
Both problems were related. As you suggested, my code to mirror writes $3F10, $3F14, $3F18 etc was broken. That meant the wrong background colour was drawn, breaking the sprite 0 check.
The background color should have zero impact on sprite 0 hit checking. What determines pixel opacity is the pattern coming from CHR and CHR alone (attributes and palette don't matter).
The CHR gives you a 4-color image (via the 2bpp bitplanes). Color 0 is transparent and should never be drawn. This is why $3Fx4, $3Fx8, and $3FxC are not displayed under normal conditions (even though they do, in fact, exist, and do not mirror $3F00) -- since they corespond to the color 0 for each attribute set. Colors 1, 2, and 3 are opaque and must be drawn.
Only when BOTH sprite and BG pixels are transparent do you fall back to drawing the background color at $3F00.
Opacity/transparency is important not only for sprite-0 hit checks, but also for sprite priority. If you fail to allow the BG to be transparent, sprites with background priority will not display properly (or at all).
Disch wrote:
What determines pixel opacity is the pattern coming from CHR and CHR alone (attributes and palette don't matter).
Well, I had that all wrong then
. I've fixed my code in that respect, now I get a few frames with the correct back colour, then it turns black and stays black.
You must be mirroring $3F04, $3F08, and $3F0C to $3F00 -- which is incorrect.
$3F04, $3F08, and $3F0C are valid palette entries. They can contain unique colors (ie: they don't mirror anything). However they are never drawn when the PPU is on.
The palette is a little funky:
$3F00 - $3F0F <--- all 16 of these are valid, unique entries
$3F10 - $3F1F <--- only 12 of these are actual entries... $3F10, $3F14, $3F18, and $3F1C mirror $3F0x.
So since $3F00 is the background color, and $3F10 is the only mirror to that color, the BG color should only change when the game writes to $3F00 or $3F10 (or any region that mirrors the whole palette -- such as $3F80 or $7FF0). Writes to $3F04, $3F08 or $3F0C should not affect the background color.
There's a C code example in the link I provided before! No bad side, but it's becoming
boring...
Patience?
A background palette index can be 00-0F, as $3F00-$3F0F. Pattern rule is 0000 AAcc (A=attribute color index, c=palette entry).
Fx3 wrote:
There's a C code example in the link I provided before! No bad side, but it's becoming
boring...
Patience?
Yeah I appreciate that, and I also appreciate the help from everyone here. It probably would have taken me forever to figure out my errors on my own.
I'm not after a step by step walkthrough, I just thought I'd keep posting my
(limited) progress. If you're not interested then that's fine.
Sooo.. it's now fixed
Now all I have to do is rewrite it all to be pixel based... well, maybe after the mappers.
Like I said, don't get me bad...