It is currently Mon Dec 11, 2017 8:23 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 12 posts ] 
Author Message
PostPosted: Mon May 09, 2016 7:07 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1865
Location: DIGDUG
I have a bug in my Vigilante game, which I have no idea why it happens...I changed the 'death' sequence, to just freeze the frame for 2 seconds and have the hero blink. But, when I played it on real hardware, occasionally the top of the screen will jitter with the wrong scroll amount...

It's supposed to be scrolls = 0 for the top and sprite zero hit, and then set a new x scroll.

It's literally the same code that runs during the game, I cut and pasted it...several times (just to be sure). The only difference is that I'm using a NMI to time the regular game, and I have NMI off for the death sequence, and I'm using $2002 to time the death sequence.

FCEUX never shows this bug. But both Nintendulator and Nestopia do. I've stepped through the code about a dozen times, and all of the scroll values look correct and at the correct time... MAYBE, the top of the screen scroll is (apparently) still holding the scroll value from the bottom of the screen?

Does anyone have any guesses as to why this might be happening?

This is the code for the top of the screen...

Code:
DeathWait:
   lda $2002
   bpl DeathWait
   lda #$00   ;draw sprites
   sta $2003
   lda #$07
   sta $4014
   lda #$00
   sta $2006
   sta $2006
   sta $2005
   sta $2005
   lda #$14
   sta $2000
   lda #$1e
   sta $2001


Then it goes into a sprite zero wait loop. Then it sets the scroll, blinks the hero, updates hero sprites, and jumps back up to DeathWait. I've even tried replacing the sprite zero wait with timed code, it still does it. I've tried commenting out every JSR (blinking the hero), no good.

It really bothers me, because it's exactly the same code as the game (except NMIs off), yet the game never has any similar issues.


Attachments:
Bug.png
Bug.png [ 25.46 KiB | Viewed 1658 times ]

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Last edited by dougeff on Mon May 09, 2016 9:32 am, edited 1 time in total.
Top
 Profile  
 
PostPosted: Mon May 09, 2016 7:30 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1865
Location: DIGDUG
(Deleted).

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Last edited by dougeff on Mon May 09, 2016 9:21 am, edited 1 time in total.

Top
 Profile  
 
PostPosted: Mon May 09, 2016 7:47 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1865
Location: DIGDUG
And, I thought solved the bug, by inserting a slight wait between the $2002 check and the rest of the V-blank code...

Code:
DeathWait:
   lda $2002
   bpl DeathWait
   
   ldx #120
-
   dex
   bne -

   lda #$00   ;draw sprites
   sta $2003
   lda #$07
   sta $4014
   
   lda #$14
   sta $2000
   lda #$1e
   sta $2001
   lda #$00
   sta $2005
   sta $2005


But, this is just speculation. I really have NO IDEA if this was the problem. Any ideas?


EDIT: it's still doing it in Nestopia. Only fixed for FCEUX and Nintendulator. I'll have to check it on real hardware.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Last edited by dougeff on Mon May 09, 2016 9:23 am, edited 1 time in total.

Top
 Profile  
 
PostPosted: Mon May 09, 2016 8:03 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1865
Location: DIGDUG
Still happens on NES. About half the time. So disappointed.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Mon May 09, 2016 8:11 am 
Offline

Joined: Mon Apr 01, 2013 11:17 pm
Posts: 437
You will occasionally miss the start of vblank if you read $2002 to detect it.

Use NMI to detect vblank and this problem will go away.


Top
 Profile  
 
PostPosted: Mon May 09, 2016 8:33 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1865
Location: DIGDUG
{Face palm}

Are you kidding me? I spent like 2 hours stepping through the code, making small pointless corrections, checking it again and again.

Well I think we all learned something... FCEUX and Nintedulator DON'T handle 2002 vblank bits correctly!!!!

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Mon May 09, 2016 8:52 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
As mentioned in the wiki:
Nesdev wiki wrote:
Caution: Reading PPUSTATUS at the exact start of vertical blank will return 0 in bit 7 but clear the latch anyway, causing the program to miss frames.

This register should only be used at the very start of the program, during PPU warm up (and possibly during NTSC/PAL/Dendy detection, depending on how you do it), but everything else should rely exclusively on the NMI.

Instead of:
Code:
- lda #2002
  bpl :-

Use:
Code:
  lda FrameCount
- cmp FrameCount
  beq :-

And anywhere in the NMI handler:
Code:
  Inc FrameCount


Top
 Profile  
 
PostPosted: Mon May 09, 2016 9:08 am 
Offline

Joined: Mon Apr 01, 2013 11:17 pm
Posts: 437
dougeff wrote:
Well I think we all learned something... FCEUX and Nintedulator DON'T handle 2002 vblank bits correctly!!!!

I can't speak for the accuracy of FCEUX, but I know for a fact Nintendulator handles this quirk correctly.

It's much more likely that your "fixed" code happens to be timed in such a way that it sometimes doesn't miss any vblanks. You'll see the same thing happen on a real NES, although it may require you to reset a few times if it's dependent on CPU/PPU alignment.


Top
 Profile  
 
PostPosted: Mon May 09, 2016 9:43 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5886
Location: Canada
tokumaru wrote:
This register should only be used at the very start of the program, during PPU warm up (and possibly during NTSC/PAL/Dendy detection, depending on how you do it), but everything else should rely exclusively on the NMI.

???

It's quite safe and normal to read this register in many situations. Two very common examples:

1. Reading it resets the address flip/flop; a good idea if you're about to write to $2006 to set the address, or $2005 to set the scroll.

2. Polling it for sprite 0 hit.


I presume you meant "This regiser should only be used to wait for vblank at..."

Though even that I think is too strong. I've found a lot of uses for waiting for vblank via $2002, it's perfectly fine for any situation where it's acceptable to skip a frame occasionally. (By the way, if you make the wait loop longer, instead of branching right back to the read immediately, you can reduce the frequency of missed frames significantly.)


If you need stable, reliable timing though, yes, doing it with the NMI is the way to go.


Top
 Profile  
 
PostPosted: Mon May 09, 2016 10:06 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
rainwarrior wrote:
1. Reading it resets the address flip/flop; a good idea if you're about to write to $2006 to set the address, or $2005 to set the scroll.

Besides changing the horizontal scroll mid-frame, I don't see why anyone wouldn't write to $2005/6 in pairs... In most cases that'd be a bug.

Quote:
I presume you meant "This regiser should only be used to wait for vblank at..."

Yes, of course, sorry about that.

Quote:
Though even that I think is too strong.

Could be. It's just that I've come to the conclusion that leaving NMIs always on is simpler than turning them on and off during the course of the program (audio doesn't stop/pause between screen transitions, and other advantages), and I've been doing this for while, so the counter is always updated.


Top
 Profile  
 
PostPosted: Mon May 09, 2016 11:15 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5886
Location: Canada
tokumaru wrote:
rainwarrior wrote:
1. Reading it resets the address flip/flop; a good idea if you're about to write to $2006 to set the address, or $2005 to set the scroll.

Besides changing the horizontal scroll mid-frame, I don't see why anyone wouldn't write to $2005/6 in pairs... In most cases that'd be a bug.

Yes, you probably wouldn't leave writes unpaired intentionally in most cases, but unintended things do happen.

In general I always BIT $2002 before I write to $2006, because it's the difference between "the latch is ready" and "the latch should be ready as long as nothing has gone wrong since the program started". I find my own confidence in the latter case decreases the longer my program runs, and the more complicated it gets. I'd rather have the guarantee, and 4 cycles is worth spending on this peace of mind.

tokumaru wrote:
I've come to the conclusion that leaving NMIs always on is simpler than turning them on and off during the course of the program

This is my preferred approach as well, but I do find other ways of doing it useful in many situations.


Top
 Profile  
 
PostPosted: Mon May 09, 2016 12:32 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10164
Location: Rio de Janeiro - Brazil
rainwarrior wrote:
In general I always BIT $2002 before I write to $2006, because it's the difference between "the latch is ready" and "the latch should be ready as long as nothing has gone wrong since the program started". I find my own confidence in the latter case decreases the longer my program runs, and the more complicated it gets. I'd rather have the guarantee, and 4 cycles is worth spending on this peace of mind.

I get it. In my case though, my own NMI handler is pretty tight... there are a number of things that have to be done before the updates begin (backing up the registers, switching to the main PRG bank, checking whether updates are needed at all, setting up the stack pointer to access the update buffer, etc.), and doing redundant things like resetting the $2005/6 latch or the OAM address ($2003) could actually cause VRAM updates to spill into the pre-render scanline (or require the buffer to be reduced by 1 or 2 bytes - which would prevent the 2 largest types of update from being able to share the buffer!).

Anyway, I consider this approach pretty safe, since the only thing I can think of that could get in he way of a double $2005/6 write is an NMI, which in my case is programmed to not touch that latch unless the main thread was explicitly expecting the NMI.

Quote:
This is my preferred approach as well, but I do find other ways of doing it useful in many situations.

Heh, at one point I was even reading $2002 every frame a little after the NTSC vblank should have ended, and using the result to dynamically switch between NTSC and PAL modes. I eventually decided this was stupid, and console detection is now only done during startup.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 12 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 8 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group