Tuesday, November 12, 2019

Scrolling glitch

For an 8-way scrolling engine with 4 nametables (ie 4 in-memory screens for the camera to pan across), it's easy to end up with a lot of small glitches in the background. Scrolling diagonally across the seam of screens, it's easy to get some of the PPU memory address math wrong, and end up writing to the wrong spot on the screen. But it might only happen when the screen is aligned exactly wrong, so it can take awhile to find and fix all the glitches.

The gray outline shows what's actually on screen. The rest is video memory that's not shown.


Over the past couple of years of working on this project, I've gradually worked through what I thought was every single scroll glitch, and fixed them. Mesen, the NES emulator that I use, has some wonderful features for debugging this sort of thing. You can play through the game, and when you notice a rare, hard-to-reproduce bug like this, you can save your game history, then go back and replay the whole history through the debugger, stepping through what you did, rewinding if you went too far, and watching updates as they happen. It's amazing.

Despite all this, there's one scrolling glitch that, until tonight, I've never been able to reliably reproduce enough to find and fix. It only seems to happen in one particular room, and then, only rarely, even if I do the exact same movement when I enter the room.

The room that gave me all the trouble. Depending on some random numbers, you can end up with 7 enemies going at once, which has the potential to slow things down.



Luckily, tonight I caught the glitch in the emulator!  I went to quickly hit the "save history" button, and I accidentally pressed "reload save state" and jumped back to some previous point in the game, and lost it.   ARRGH!

But, I had the good fortune of being able to reproduce it again fairly soon after, and this time I managed to save the history, and replayed it over and and over and over again.

It didn't make any sense -- as I was scrolling diagonally through the room, it would just skip updating the background for a single frame. So instead of writing new tiles at the edge of the screen, garbage tiles would just scroll in instead. I couldn't figure out why it was skipping an update.

After about the 30th time I replayed it, though, I noticed something. My frame counter was jumping from $1d to $1f when I advanced a single frame. That's not right! 

To make a long story short (too late), I tie some of my scroll logic to the frame counter (to alternate between a few types of updates). In the rare case that too much is happening in a single frame, you can end up with that dreaded NES slowdown, which means that you hit vblank before you finish your frame's logic (ie you get 2 screen draws per logical frame, cutting your game to 30fps instead of 60).  During the screen draw interrupt (NMI) is when I advance my frame timer. 
You can use a grayscale bit in the video settings as a poor-man's profiler. The gray shows how much of my CPU time I'm using this frame.


So this one single room has enough enemies to very occasionally cause a slowdown (usually only for 1 or 2 frames so I never even notice), BUT during those slowdowns, my frame timer advances twice per logical frame. So my scrolling updater (which depends on that timer) fails.

It was an easy fix once I realized what was happening -- I now just use a separate counter for the background updates, and only increment it after actually doing an background update. It wastes a byte of ram, but this is the NES, which has a whopping 2k of ram! What's a wasted byte?

So now, for maybe the 100th time, my git commit message said "Fixed what was hopefully the last scroll glitch."  Maybe I'm actually right this time?

No comments:

Seeker missiles revisited

If you follow me on twitter, you saw back in February that I was attempting to get enemy-seeking missiles working correctly. At the time, I...