Retro Insectivores: Finding and Eliminating Bugs in NES Development
If you have ever engaged in programming, then you know about bugs. If bugs didn't bother us, the development process would be faster and more enjoyable. But these bugs are just waiting for the moment to corrupt our code and spoil our timelines and creative flows. Fortunately, there are many tools and strategies for eliminating bugs - even for retro programmers.
One of the best ways to debug your code is to use a debugger. Some of the FCEUX and Mesen emulators have a built-in debugger that can interrupt program execution at any time in order to check the code for operability.
It is worth saying that this way is more suitable for advanced programmers who use assembly language. But since we are newbies, we will use the C language (cc65). Of course, the compiler will play by its own rules, and it will be difficult for us to navigate the machine code compiled from the C language.
FCEUX Hex Editor
Suppose we need to watch for some variable or array. Add the following parameter to your linker option (ld65): -Ln labels.txt
When the project is compiled, you will find the file labels.txt in your project folder. Just open it with any text viewer and look for the name of the variable you need to watch.
(Note: if you declare a static variable, it will not be included in this list. Therefore use unsigned char playerX; instead of static unsigned char playerX)
Now we know the address of the required variable. Not bad. Let's find it in the debugger. Start your ROM with the FCEUX emulator. In the “Debug menu”, click on the “Hex Editor” item, in the window that opens, press ctrl + g, and enter the address of your variable:
Click OK and the cursor will be moved to the address where the variable is located. Let's look at this:
This can be useful to check if the array is filled correctly or to watch changes in specific variables. It also makes you feel a bit Big Brotherish, surveilling your code like this.
Be sure to check the FCEUX emulator Debug menu for other useful tools, like PPU Viewer, Name table Viewer, and much more.
Streamlining the Debug Process
What if you don’t want to run the debugger each time you check for a variable? An advanced method is to write a subroutine that will display any value on the screen. Let's try to use the score in the HUD to display the player's position on the Y-axis:
Works like a charm!
Doug Fraker, a retro coder and owner of the nesdoug blog, provides a similar method for using an on-screen visualization for debugging purposes. The following subroutine creates a grey line on the screen which visually indicates CPU usage:
ora #1 ;yes gray bit
ora #$e0 ;all color emphasis bits
ldx #20 ;wait
You can copy-paste this to your source, or include the nesdoug.h library in your project. Call this subroutine after your game cycle has been completed, and you will see this gray bar on the screen.
It works, but I think I got another bug! I’ll get rid of it later. For now, let’s move on.
The Power of Macros
Macros can also be a useful tool for debugging. They can help you pinpoint the spot in your code that is fending off a bug infestation.
Let's create some macros that will give us some signals at the right time, like playing a sound or highlighting a zero palette with a necessary value. Here we have a few macros that change the zero palette to red, blue and random colors, and for playing a sound:
How does it work? Suppose your project is successfully compiled, you run the emulator with your game, click the Start button, and...
It seems there is nothing here except the white screen. In addition, some emulators will tell you in the status bar: CPU jam! What do we do now?
First of all, we have to localize the code where the error occurs. This is where my sound macro comes into play.
We know for sure that at least the main menu works, let's see what happens after it:
playMainMenu();player.lives = 9;
points = 0;
gameFlags = 0;
I have a suspicion that the game crashes on the execution of the set_world subroutine. Let's check it out. I will simply write the name of the macro in the next line after the subroutine I want to check.
We start the project and ... I hear a sound! So, this subroutine completed successfully, and we need to check the next one: playCurrentLevel. Let’s move the debug macro below:
while(current_level<7 && player.lives>0)
I run the project again and do not hear the sound. This means that my subroutine is not complete, and failure is occurring inside it.
In these cases, open the listing of the subroutine in question and continue using this method until you narrow the range of where the bug could be hiding.
Macros that change the palette can also be useful for checking conditions. For example, our code performs a complex check of several conditions:
if ( (getTile(objX, objY+16) || collide16() ) || (objsOX[i] && objY>objsOX[i]))
If we switch the color of the palette here, we will see if our condition is fulfilled or not:
It seems that this chicken is fine. But if the flag does not work, then one of the conditions is not met. In this case, check each of them separately, and perhaps you will find another creepy bug.
The Nuclear Option
Recently, I noticed that one of the ghosts exhibiting some suspicious behavior. Occasionally, they refused to attack the player.
Take a look at this bug-enslaved ghost - it attacks only when the character is close to the center of the screen:
No matter how much I looked at the code of this procedure, I could not understand where the bug was hidden, so I decided to take extreme measures and test the performance of this code in a modern development environment.
I took with me everything I needed: a map of the screen, an array with the attributes of metatiles, the code of the subroutine, and just pasted all this in Visual Studio 2017:
Here, on the PC, this code worked exactly the same way. As it turned out, the bug was hiding in a procedure that fills the cache to find the obstacles between the player and the enemy. My array was not filled correctly. I'm fairly certain there should be 0 instead of 0x80.
Well, I will try to debug the code step by step to find out why this is happening.
It's funny, but it seems like I performed the actions in the wrong sequence. Let's fix it and check the array again!
It seems that now the array is filled correctly. So, I have only to fix the cc65 code and compile the NES project once more.
So, modern development tools will be able to help debug your algorithms and get rid of bugs.
Eat Bugs in Peace
Bugs are frustrating, and debugging can be too. Just stay calm, stay in control, and use every tool at your disposal to find and destroy these filthy insects. Your code, and your peace of mind, will feel much better.
Want more tips straight from the retro pros? Welcome to our Discord!
Get mad meat on The Meating page now!