Help understanding sprite-0

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

pwnskar
Posts: 119
Joined: Tue Oct 16, 2018 5:46 am
Location: Gothenburg, Sweden

Help understanding sprite-0

Post by pwnskar »

Hey! Sorry if this is a redundant post. I've found some other posts covering this topic but I was not able to dissect an answer to my problem.

So I'm trying my hand at sprite-0 hit detection but I instantly run into a problem. It seems my loop for detecting the hit only branches on the correct scanline (32, though the OAM Y value is set to 28) the very first frame I test for it. After that it always branches when on scanline 246.

What am I missing? I'm testing this on a "clean" slate so to say. That is I'm not doing this in my game project, as it would make solving the issue even harder.

Here's the source for my NMI routine. I've attached my test rom as well.

Setting a break point at C116 should show you where the branch happens.

Thanks for reading this!

Code: Select all

NMI:
	
	pha
	txa
	pha
	tya
	pha
	
	ldx #0
	stx PPUMASK		; disable rendering
	
	lda #<(OAM_buffer)   ; load OAM buffer into PPU
	sta PPUOAMADDR
	lda #>(OAM_buffer)
	sta OAMDMA
	
	lda #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
	sta PPUCTRL
	lda #%00011110   ; enable sprites, enable background, no clipping on left side, emphasis on blue
	sta PPUMASK
	
	@wait_sprite0:
		lda PPUSTATUS
		and #%01000000
		beq @wait_sprite0
		
		; TODO: We've hit sprite 0. now do something.
		; for some reason, this only seems to hit the right scanline the very first time.
		; after that, it's always at around 246. what am I doing wrong?
	
	inc frame_cnt	; inc frame_cnt to get out of waitNMI loop.
	
	pla
	tay
	pla
	tax
	pla
	
	rti
Attachments
sprite-0_test.nes
(24.02 KiB) Downloaded 179 times
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Help understanding sprite-0

Post by tokumaru »

A hit only happens when an opaque sprite pixel overlaps an opaque background pixel. That might explain the scanlines between the top of the sprite and the actual hit. I haven't looked at your ROM because I'm on my phone, so I don't know what the graphics look like.

As for the premature hit at scanline 246, that happens because once set, the flag will remain set until the end of vblank (scanline 262 or so), when the PPU automatically clears it. If you start testing too soon, you'll detect the old hit before the PPU has a chance to clear the flag. The solution for that is to first wait for the flag to be cleared, and then wait for it to get set.
pwnskar
Posts: 119
Joined: Tue Oct 16, 2018 5:46 am
Location: Gothenburg, Sweden

Re: Help understanding sprite-0

Post by pwnskar »

tokumaru wrote:A hit only happens when an opaque sprite pixel overlaps an opaque background pixel. That might explain the scanlines between the top of the sprite and the actual hit. I haven't looked at your ROM because I'm on my phone, so I don't know what the graphics look like.
I've made sure the sprite is always positioned on opaque pixels, so that part should be OK?
tokumaru wrote:As for the premature hit at scanline 246, that happens because once set, the flag will remain set until the end of vblank (scanline 262 or so), when the PPU automatically clears it. If you start testing too soon, you'll detect the old hit before the PPU has a chance to clear the flag. The solution for that is to first wait for the flag to be cleared, and then wait for it to get set.
OK.. hmm.. Am i correct to assume the start of vblank triggers the NMI and that they run in parallel? Would I then solve the issue by just doing something that takes up a certain amount of cycles for the vblank to end? Or is there any way I can test and wait for the end of vblank before I test for my sprite-0 hit?
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Help understanding sprite-0

Post by tokumaru »

Also, since the sprite hit flag is located in bit 6, you can take advantage of the BIT instruction to test it, which results in a tighter wait loop. A faster wait loop helps reduce jitter, because it will detect hits sooner.

Here's what the wait itself could look like:

Code: Select all

@wait_no_sprite0:
  bit PPUSTATUS ;copy bit 6 to V flag
  bvs @wait_no_sprite0 ;test again if set
@wait_sprite0:
  bit PPUSTATUS ;copy bit 6 to V flag
  bvc @wait_sprite0 ;test again if clear
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Help understanding sprite-0

Post by tokumaru »

pwnskar wrote:I've made sure the sprite is always positioned on opaque pixels, so that part should be OK?
But you said the OAM Y value is 28, and the hit only happens at scanline 32. The PPU has a one scanline delay when rendering sprites, so the sooner a hit would happen in this case is scanline 29. Are your graphics designed for the hit to happen at the top of the sprite or does the opaque overlap only happens further down?
OK.. hmm.. Am i correct to assume the start of vblank triggers the NMI and that they run in parallel?
Yes. The purpose of the NMI is to let you know that vblank started, so you can make use of that time.
Would I then solve the issue by just doing something that takes up a certain amount of cycles for the vblank to end?
Yes, but that takes more code and is harder to manage (if you modify your vblank handler you may have to adjust this wait period). It's much simpler and safer to wait for the flag itself to be cleared.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Help understanding sprite-0

Post by tepples »

And to ensure your game doesn't completely crash if some oversight causes the background in this area not to be opaque, you can set a timeout by including the vblank bit in your test.

Code: Select all

@wait_no_sprite0:
  bit PPUSTATUS ;copy bit 6 to V flag
  bvs @wait_no_sprite0 ;test again if V set

  lda #%11000000 ;test for at least one of bit 7 or 6
@wait_sprite0:
  bit PPUSTATUS ;copy bit 7 to N flag, bit 6 to V flag, and NOT(A AND status) to Z flag
  beq @wait_sprite0 ;test again if Z set

  bmi @sprite_0_was_missed
This means a sprite 0 miss is more likely to cause a momentary graphical glitch instead of a crash.

On the other hand, when debugging, you may prefer a hang to a momentary glitch to encourage you to investigate the root cause of the sprite 0 miss and thereby reduce momentary glitches in the release build. You can switch between hard failure for debug builds and recovery behavior for release builds by using conditional assembly, a feature of the major 6502 assemblers to determine what to assemble based on the value of a constant defined earlier in the program. I don't see what assembler you're using, but in ca65, it'd look something like this:

Code: Select all

HANG_ON_S0_MISS = 1

@wait_no_sprite0:
  bit PPUSTATUS ;copy bit 6 to V flag
  bvs @wait_no_sprite0 ;test again if V set

  .if HANG_ON_S0_MISS
    lda #%01000000 ;test only bit 6
  .else
    lda #%11000000 ;test for at least one of bit 7 or 6
  .endif
@wait_sprite0:
  bit PPUSTATUS ;copy bit 7 to N flag, bit 6 to V flag, and NOT(A AND status) to Z flag
  beq @wait_sprite0 ;test again if Z set

  bmi @sprite_0_was_missed
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Help understanding sprite-0

Post by lidnariq »

Quickly looking in Mesen ...

The problem is not that your sprite 0 handler isn't running. The bit is being set, &c.

The problem is that you aren't waiting for vertical blanking to end before you start polling the bit in the busy-wait in NMI, and the bit isn't cleared until vertical blanking ends.
pwnskar
Posts: 119
Joined: Tue Oct 16, 2018 5:46 am
Location: Gothenburg, Sweden

Re: Help understanding sprite-0

Post by pwnskar »

Thanks for the help, everyone! I now have the sprite-0 detection branching consistently on the same scanline. Now I have another problem and that is that the scroll gets messed up after I follow my sprite-0 with some palette updates. I try to correct it by writing twice to $2005 and once to $2000 but I can't seem to get it to work. Here's my updated NMI and a rom.

Code: Select all

NMI:
	
	pha
	txa
	pha
	tya
	pha
	
	ldx #0
	stx PPUMASK		; disable rendering
	
	bit PPUSTATUS
	lda #$3f
	sta PPUADDR
	lda #$00
	sta PPUADDR
	
	lda #$0f		; undoing the writes I do after sprite-0 here.
	sta PPUDATA
	lda #$00
	sta PPUDATA
	lda #$10
	sta PPUDATA
	lda #$20
	sta PPUDATA
	
	lda #<(OAM_buffer)   ; load OAM buffer into PPU
	sta PPUOAMADDR
	lda #>(OAM_buffer)
	sta OAMDMA
	
	ldx #0
	stx PPUSCROLL
	stx PPUSCROLL
	
	lda #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
	sta PPUCTRL
	lda #%00011110   ; enable sprites, enable background, no clipping on left side, emphasis on blue
	sta PPUMASK
	
	@wait_no_sprite0:
		bit PPUSTATUS ;copy bit 6 to V flag
		bvs @wait_no_sprite0 ;test again if set
	@wait_sprite0:
		bit PPUSTATUS ;copy bit 6 to V flag
		bvc @wait_sprite0 ;test again if clear
		
		; TODO: We've hit sprite 0. now do something.
		
		ldx #0
		stx PPUMASK
		bit PPUSTATUS
		lda #$3f
		sta PPUADDR
		lda #$00
		sta PPUADDR
		
		lda #$0a		; writing to the palette
		sta PPUDATA
		lda #$1a
		sta PPUDATA
		lda #$2a
		sta PPUDATA
		lda #$3a
		sta PPUDATA
		
		;bit PPUSTATUS
		ldx #0
		stx PPUSCROLL
		stx PPUSCROLL
		
		lda #%10010000   ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
		sta PPUCTRL
		lda #%00011110   ; enable sprites, enable background, no clipping on left side, emphasis on blue
		sta PPUMASK
		
	inc frame_cnt	; inc frame_cnt to get out of waitNMI loop.
	
	pla
	tay
	pla
	tax
	pla
	
	rti
EDIT: I forgot to mention that I'm incrementing the y value of the sprite-0 every frame now, incase the rom seems confusing.
Attachments
sprite-0_test_02.nes
(24.02 KiB) Downloaded 176 times
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Help understanding sprite-0

Post by tepples »

Whenever you update the palettes or anything else that uses $2006/$2007, the NES PPU forgets what the scroll position was. This means you have to rewrite the scroll position in $2005 (twice) and $2000.
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Help understanding sprite-0

Post by lidnariq »

pwnskar wrote: ;bit PPUSTATUS
ldx #0
stx PPUSCROLL
stx PPUSCROLL
PPUSCROLL only sets the Y coordinate at the very top of the screen. Afterwards you have to use PPUADDR (or both together) to set Y scrolling.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Help understanding sprite-0

Post by tokumaru »

Setting the scroll mid-screen works differently from setting it during vblank. To have full control of the scroll mid-screen, you need to mix writes to $2005 and $2006. This is usually considered complicated by people who don't understand how scrolling really works on the NES. Here's the relevant information: https://wiki.nesdev.com/w/index.php/PPU ... r_controls

Things get significantly simpler if you don't need to set the fine Y scroll (i.e. the Y scroll is a multiple of 8). In this case you might get away with just writing the NT address of the tile where you want the scroll to resume to register $2006.

Since you're messing with mid-screen palette changes, let me warn you this is a notoriously hard thing to get right on the NES for a few reasons:

1- VRAM can only be accessed if rendering is turned off, and turning rendering of mid-screen can corrupt OAM on the *next* frame depending on whether there are sprites in the scanline where rendering is turned off;

2- When rendering is off and the VRAM address is pointing to the palette region, the color at that address is displayed on the screen, meaning that if you want to update colors without causing jittery rainbows to show up on the screen, you can really only do it during hblank, 1 or 2 colors per scanline;

3- Once rendering is turned off, the PPU's rendering logic gets a bit messed up, and sprites for example may need an entire scanline to fully recover, meaning that you may need a few scanlines of "nothing" during which you can do the palette updates without disturbing the visible picture;

4- After you're done messing with VRAM, you need to restore the scroll, which can be a little complex if you have a free-moving camera.

These are the main things you have to watch for when changing the palette mid-frame. Not surprisingly, not many games do it.
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Help understanding sprite-0

Post by lidnariq »

tokumaru wrote:2- When rendering is off and the VRAM address is pointing to the palette region, the color at that address is displayed on the screen, meaning that if you want to update colors without causing jittery rainbows to show up on the screen, you can really only do it during hblank, 1 or 2 colors per scanline;
Just in case this didn't sound bad enough, it's actually a little worse:
* 1 color, with a noticeable hole on the left of the previous scanline or right of the following scanline, if you want no empty scanlines at all. (Example: Taito's Indiana Jones)
* 3 colors per scanline completely disabled, displaying some random fixed color during the blanked scanline
* 10-ish colors per scanline completely disabled, if the jittery rainbow is acceptable. (Example: ccovell's "Sayoonara!" demo)
3- Once rendering is turned off, the PPU's rendering logic gets a bit messed up, and sprites for example may need an entire scanline to fully recover
Not may, do. Sprites are loaded on the previous scanline, and if anything goes wrong during sprite evaluation they'll be wrong—at best "missing"—for the following scanline.
pwnskar
Posts: 119
Joined: Tue Oct 16, 2018 5:46 am
Location: Gothenburg, Sweden

Re: Help understanding sprite-0

Post by pwnskar »

tokumaru wrote:Since you're messing with mid-screen palette changes, let me warn you this is a notoriously hard thing to get right on the NES for a few reasons:
...
My goal is to use this to change the palette for a top-screen status bar in a non-scrolling (so far) 2 player death match game. I have an idea of introducing scrolling later but for now, just changing the palette would do.

However, given your warning I realize I might have to abandon the idea in the end. But for now though, I'd like to just try to get it working one thing at a time in this little test rom.
Unfortunately I won't have a lot of time to play around with this today but maybe tomorrow. Until then, how do I set the y-scroll with writes to 2006?

Thanks for all the help guys! I really appreciate you all taking your time to help me understand all this.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Help understanding sprite-0

Post by tokumaru »

I really don't advise you do mid-screen palette changes in your first project. It's harder to pull off correctly than most other things on the NES. Even most experienced programmers prefer not to do it due to the inherent complications.

As for setting the scroll mid-frame, it can mostly be done with 2 PPUADDR ($2006) writes, in the following format:

Code: Select all

1st:     2nd:
0yyyNNYY YYYXXXXX

XXXXX: coarse X scroll (0 to 31);
YYYYY: coarse Y scroll (0 to 29);
NN: name table (0 to 3);
yyy: fine Y scroll (0 to 7);
This is almost the exact same format used when updating tiles on the name tables, with exception of the fine Y scroll bits that now go at the top.

Anyway, the problem is that there are 2 catches when using this register for scrolling purposes:

1- the top fine Y bit gets cleared on the first $2006 write, making it impossible to change the scroll to anywhere in the bottom half of a tile (i.e. only values 1 to 3 are possible);

2- $2006 doesn't affect the fine X scroll at all. Only a $2005 write can do that;

A carefully planned $2006/5/5/6 sequence can circumvent those problems, but if you're only using tile-aligned scroll values, those problems shouldn't affect you, and you can just do the regular $2006 writes.

EDIT: fixed the maximum coarse Y value, which is 29, not 30. It is in fact possible to set the scroll to rows 30 and 31, but that will cause the PPU to interpret attribute table data as if it were name table data.
Last edited by tokumaru on Sat Jan 05, 2019 1:46 am, edited 1 time in total.
pwnskar
Posts: 119
Joined: Tue Oct 16, 2018 5:46 am
Location: Gothenburg, Sweden

Re: Help understanding sprite-0

Post by pwnskar »

tokumaru wrote:I really don't advise you do mid-screen palette changes in your first project. It's harder to pull off correctly than most other things on the NES. Even most experienced programmers prefer not to do it due to the inherent complications.
You're probably right, but I must say the idea is still tempting. Perhaps it will be something I attempt in some sort of polish phase. If I opt for not having any scrolling at all, perhaps this could be a feature I would be able to pull off?

Whether I'll do something with this in my game or not, I've been learning from what you all have been writing so far and I'm still playing around with my test rom. I've managed to set the coarse Y scroll right by using the sprite-0 Y value divided by 8, though I seem to have to adjust it a bit by subtracting #2 before dividing. Otherwise the seam is never able to line up perfectly.

I also tried applying some fine Y scroll and it worked just as you explained. How would one go about solving the issue of that missing bit? Is it impossible on a standard UNROM mapper?

Oh, and how do I know when I'm in h-blank, so that I can make sure to only do my palette updates at that time?

I've added an updated test rom but with the fine y scroll turned off, as it seems to mess up the consistency of when the coarse scroll manages to line up seamlessly. You can use the d-pad to test x scroll and sprite-0 y position. Hold A for turbo speed. :beer:
Attachments
sprite-0_test_03.nes
(24.02 KiB) Downloaded 176 times
Post Reply