I'm having a really strange thing happen with my attempt to use sprite 0 hit to cause a scroll split at a variable screen position. My code runs fine in every emulator I've tried except Nintendulator, and also appears to run correctly on my NTSC NES. The ROM also autodetects and compensates for PAL but I do not have a PAL NES to run a hardware test on.
In the attached ROM, a two-pixel wide sprite is placed on pixels 253/254 to trigger a sprite 0 hit. $2002 is polled for the hit, and once detected the scroll split is timed to be written during hblank on the following scanline. To demonstrate the variable position, the sprite will be moved from Y=41 to Y=239, one pixel per frame.
For some reason in Nintendulator the $2002 test appears to fail randomly (maybe on 3% of frames?), causing the scroll split to be missed in overlay_test_1.nes. I can't figure out why.
Does Nintendulator emulate something unusual about sprite 0 hits that other emulators do not? Since it's not happening on my hardware test, I'm wondering if it is a bug in Nintendulator, but I'm worried that it's emulating some rare case that I should really be looking out for.
This is the code that waits for the sprite 0 hit and sets the scroll, you can find it at $801F in memory when the last 32k bank is selected (this uses an oversized 256k BNROM mapper).
If you need the NMI routine, or other source, I can post it.
In overlay_test_2.nes, the fail-safe line bmi @overlay_fail is commented out, which doesn't make a difference, except in Nintendulator. In Nintendulator the overlay position appears to freeze on frames where the test fails, which I don't quite understand. If it's catching the sprite on the next frame, the scroll should not have been executed. Very puzzling...
I apologize that this isn't a simple test program with full source. I made an attempt to produce a "simpler" test ROM, but so far I have been unable to do so. This is excerpted from a larger program I'm working on.
In the attached ROM, a two-pixel wide sprite is placed on pixels 253/254 to trigger a sprite 0 hit. $2002 is polled for the hit, and once detected the scroll split is timed to be written during hblank on the following scanline. To demonstrate the variable position, the sprite will be moved from Y=41 to Y=239, one pixel per frame.
For some reason in Nintendulator the $2002 test appears to fail randomly (maybe on 3% of frames?), causing the scroll split to be missed in overlay_test_1.nes. I can't figure out why.
Does Nintendulator emulate something unusual about sprite 0 hits that other emulators do not? Since it's not happening on my hardware test, I'm wondering if it is a bug in Nintendulator, but I'm worried that it's emulating some rare case that I should really be looking out for.
This is the code that waits for the sprite 0 hit and sets the scroll, you can find it at $801F in memory when the last 32k bank is selected (this uses an oversized 256k BNROM mapper).
Code:
render_overlay:
lda #1
sta nmi_lock ; disables NMI temporarily in case sprite 0 hit fails
:
; sprite 0 hit doesn't clear until the end of vblank, make sure that's happened
bit $2002
bvs :-
:
bit $2002 ; will hit after dot 253
bmi @overlay_fail ; if vblank is detected, we completely missed the sprite 0 hit
bvc :-
; [ 4 cycles elapsed since sprite 0 hit ]
; wait for hblank alignment
; add 3 dots to get from 254 sprite hit to 257 hblank (1 cycle)
; a line is 341 dots long (113.6 cycles NTSC, 106.6 cycles PAL)
; thus, this wait needs: NTSC 84 cycles, PAL 77 cycles (+6 cycles above, +27 cycles below)
lda player_pal ; [ 3 ]
bne :+ ; [ +2 if NTSC, +3 if PAL ]
nop
nop
nop
nop
: ; [ 13 NTSC, 6 PAL ]
; 71 cycles to go
lda player_pal ; [ +3 ]
.repeat 34
nop
.endrepeat ; [ +68 ]
; [ NTSC 84, PAL 77 cycles waited ]
; wait finished
lda #%00001010
sta $2001 ; disable sprite rendering
lda #%00000100
sta $2006 ; 2006.1 > nametable %01
lda overlay_scroll_y
sta $2005 ; 2005.2 > Y scroll
and #%00111000
asl
asl
ldx #0
; [27 cycles elapsed since wait finished]
; the last two write should fall in hblank (after dot 256, before dot 320)
stx $2005 ; 2005.1 > X scroll = 0
sta $2006 ; 2006.2 > Y scroll bits 3-5, X scroll low bits = 0
lda #0
sta nmi_lock
rts
@overlay_fail:
lda #0
sta nmi_lock
rts
lda #1
sta nmi_lock ; disables NMI temporarily in case sprite 0 hit fails
:
; sprite 0 hit doesn't clear until the end of vblank, make sure that's happened
bit $2002
bvs :-
:
bit $2002 ; will hit after dot 253
bmi @overlay_fail ; if vblank is detected, we completely missed the sprite 0 hit
bvc :-
; [ 4 cycles elapsed since sprite 0 hit ]
; wait for hblank alignment
; add 3 dots to get from 254 sprite hit to 257 hblank (1 cycle)
; a line is 341 dots long (113.6 cycles NTSC, 106.6 cycles PAL)
; thus, this wait needs: NTSC 84 cycles, PAL 77 cycles (+6 cycles above, +27 cycles below)
lda player_pal ; [ 3 ]
bne :+ ; [ +2 if NTSC, +3 if PAL ]
nop
nop
nop
nop
: ; [ 13 NTSC, 6 PAL ]
; 71 cycles to go
lda player_pal ; [ +3 ]
.repeat 34
nop
.endrepeat ; [ +68 ]
; [ NTSC 84, PAL 77 cycles waited ]
; wait finished
lda #%00001010
sta $2001 ; disable sprite rendering
lda #%00000100
sta $2006 ; 2006.1 > nametable %01
lda overlay_scroll_y
sta $2005 ; 2005.2 > Y scroll
and #%00111000
asl
asl
ldx #0
; [27 cycles elapsed since wait finished]
; the last two write should fall in hblank (after dot 256, before dot 320)
stx $2005 ; 2005.1 > X scroll = 0
sta $2006 ; 2006.2 > Y scroll bits 3-5, X scroll low bits = 0
lda #0
sta nmi_lock
rts
@overlay_fail:
lda #0
sta nmi_lock
rts
If you need the NMI routine, or other source, I can post it.
In overlay_test_2.nes, the fail-safe line bmi @overlay_fail is commented out, which doesn't make a difference, except in Nintendulator. In Nintendulator the overlay position appears to freeze on frames where the test fails, which I don't quite understand. If it's catching the sprite on the next frame, the scroll should not have been executed. Very puzzling...
I apologize that this isn't a simple test program with full source. I made an attempt to produce a "simpler" test ROM, but so far I have been unable to do so. This is excerpted from a larger program I'm working on.