Not long ago, a friend and I started a list of games that we consider to be “compellingly
broken” - games that seem to be held together with duct tape behind the scenes but make
you want to keep playing.
And among those games was Strider for the NES.
Diving into the code for this one was an absolute treat.
Not like a treat from a candy store.
It was more like “A Nightmare on 6502 Street”-level of entertainment.
Strider has this constant sense of foreboding - not that an enemy is going to jump out at
you but rather that sprites are going to appear or disappear in random places.
What is happening?
What… indeed.
Let’s use an isolated example.
We have two enemies approaching from the left side.
Let’s let this scene roll.
There seems to be a lot of flickering and perhaps some garbage graphics appearing on
the screen.
We’ll slow it down to 3% emulation speed and watch it again in order to get an idea
of how bad things are.
Let’s name the enemies so we can differentiate them.
We’ll call the top enemy “Moe” and the lower enemy “Larry.”
Part of Moe disappears for a moment, then part of Larry.
A few frames later, Larry has mislocated sprites and an altered shape.
Moe disappears and seems to be stamped on top of Larry.
Then most of Larry disappears.
After Larry lands, most of him disappears again.
Then Moe disappears and Larry has a head for a foot.
Moe reappears.
Larry has no head, and some of that head sprite is now up here.
From this point forward, the two run to the right, and we see more flicker and garbage
sprites.
Maybe you didn’t see all of this at full speed, but slowing it down certainly showed
us just how wacky things can be.
Now some of you might think we just have a bug somewhere in the sprite logic.
But no - it is worse than that.
WAY worse.
The short explanation is - Sprite creation for a given frame may not finish before that
frame is drawn to the TV screen.
And this is not because of some bugs in a few lines of code.
It stems from a failure to either understand or respect the timing and architecture of
the NES.
Gimme a moment, and I’ll explain.
In a previous video in the Behind the Code series, we looked into Object Attribute Memory
or “OAM,” and how sprites are prioritized for both the frame and any given scanline.
You may wish to review this episode.
One component we did not examine was how the game code creates the sprites before passing
them to OAM so they can then be rendered when a given frame is drawn.
The code first writes the sprite OAM data to RAM to prepare for the next frame of video
as the current frame is being drawn.
Consider it a buffer of sorts.
Code does not write to OAM directly in the middle of the current frame being drawn - it
would destroy the sprite information for the current frame!
(And cause other problems..)
We have to finish drawing the current frame before providing the sprite information for
the next frame.
And we know when a frame is completed because the PPU signals the CPU when it is done by
using an NMI - a non-maskable interrupt.
And boy does it interrupt Strider’s code execution!
There is a very limited amount of time before the next frame begins once NMI signals the
end of the current frame.
Since time is of the essence, sprites for the next frame are built in RAM as the current
frame is drawn so they only need to be copied to OAM when the time is right.
You don’t have time to waste on building sprites after NMI.
But how much time does it really take to build the sprites, anyway?
Well…
Let’s use the playable character as an example.
This pose is built using 15 sprites - almost a quarter of the number of sprites available
for a given frame.
Behind the scenes, he has an X/Y coordinate in RAM to represent his location.
These fifteen sprites are located on the screen based on offsets from that single location.
Even if the player doesn’t provide any input, logic has to know which set of sprite tiles
to use, where to put them on the screen, and what their attributes are for any given frame.
All fifteen sprites have to be prioritized and placed in OAM, and this is an example
of just a single entity.
Enemies, a heads up display, and more exist in OAM as well.
How are they ordered for priority?
In other words, which sprites are the most important?
Strider - like many if not most NES games - shifts sprite priority across frame output.
So if we use this frame as an example, 8 sprites make up the heads up display, followed by
Strider’s 15 sprites, then Moe’s 13 sprites, then Larry’s 11 sprites, and then some other
stuff not relevant to this frame.
If we advance a single frame, the poses are the same, and the enemy positions on screen
have moved just a little bit.
In addition, Larry has higher priority in OAM now.
The sprites used for Larry occur in OAM before the sprites used for Moe.
The opposite of the previous frame.
This example has no glitches and serves as a baseline for what we want to happen - all
sprites appear in the correct place on screen when the frames are drawn.
Priority amongst the enemy sprites has changed as designed.
Happiness is only temporary.
The nightmares are about to begin.
Let’s see how long it takes to build all the sprites for a new frame.
This is the code used to do just that.
Pause and read the comments if you wish.
This subroutine is called on every frame for each of the three entities we see on screen
in our example.
Obviously if we are building a character that is made up of 15 sprites, for example, you
are looking at 15 iterations through this loop.
So let’s use this location as the start of the subroutine and this location as the
end of it.
We will tell the emulator that we want to see these moments of execution marked in our
event viewer.
If we connect the dots, it will tell us how long the subroutine took to execute after
all of that looping is complete.
The Event Viewer shows what the PPU is currently drawing on the screen but also serves as a
timeline of execution for the CPU, which is working on the next frame and calls the subroutine
three times as part of that work.
Since frame output to the television must be consistent, this gives us a pretty accurate
idea of what percentage of frame time it takes to create each set of sprites in RAM.
You may have also noticed that they are not contiguous.
Various other logic is executed between the sprite building subroutine calls.
It is also interesting..
that one instance occurs so late in the frame.
Execution completes here, and the NMI that signals that the PPU is done drawing the current
frame and it is time for the next one occurs here.
That’s pretty close.
I’ll tell you another secret - the subroutine calls vary quite a bit in terms of execution
start time relative to frame time.
In fact, the very next frame starts the third subroutine call here, but… when does it
finish?
This is the moment NMI occurs in order to say, “OK!
Gimme the next frame’s information now!”
The CPU has to follow the interrupt and jump to a special place in code at that moment
to handle NMI logic.
Funny thing about a non-maskable interrupt - it interrupts code execution.
It is going to happen regardless of if you are done with your work for the next frame
or not.
Uh….
oh….
Let’s back out of Strider for just a moment.
In a perfect world, all NES games finish working on the next frame ahead of schedule - meaning
before the PPU informs the CPU it is time for the next frame.
In fact if you step through logic in a debugger and this is the case, you may find that the
CPU is placed in an infinite loop after it completes the work for the next frame.
It is literally waiting on the PPU to finish drawing the current frame and interrupt execution.
The PPU interrupts the CPU, the CPU jumps to a specific place used for handling special
logic to perform between frames, and then returns from the interrupt.
It goes right back to what it was doing.
If the CPU was in an infinite loop, the NMI logic may set the exit condition for that
loop before returning.
There are multiple methods for how this stall/interrupt/resume logic works.
But what if the CPU never finished its work for the next frame, and the PPU is ready for
that next frame?
How does programming handle THAT scenario?
Many games are programmed to repeat the last frame rendered and defer the next frame’s
updates if they run out of time working on the next frame.
We aren’t ready to render frame B yet!
Just… just show Frame A again!
We’ll finish up frame B and send it out on the next update.
The result, from the gamer’s perspective, is called “slowdown.”
Video output framerate will always stay the same, but the game framerate drops.
The music gets “stretched” for lack of a better term due to the stalling of frame
updates.
If you have played NES games, you have no doubt experienced it.
During development, it may become clear that 60 fps with only minor instances of slowdown
is not possible or would only be achievable a small fraction of the time during gameplay.
To handle this, games like Teenage Mutant Ninja Turtles use a slower frame rate - such
as 30 fps - on a consistent and deliberate basis so they have double the amount of CPU
time available before needing to provide a new frame.
CPU frame work and NMI logic are designed around this being the rule and not an exception.
Slowdown may still occur, but at least it isn’t on MOST frames as it would be if the
game was trying to run at 60 fps.
Some games, such as Mega Man 3, may have too much work to handle during a given frame,
but still want to baseline at a higher framerate like 60 fps.
Frames are still dropped, BUT the game prioritizes sound, for example, so that the slowdown is
not as jarring to the player.
There is definitely a drop in framerate.
Performance is terrible, but at least the music keeps the tempo.
So what about Strider?
Oh boy.
Here we go…
Strider just… plows right through it.
It does not care if it is interrupted in the middle of building sprites or doing something
else to prepare the next frame.
Let’s just render a new frame!
You need the updated sprites for that next frame?
Well, here’s what I’ve got!
Let’s say you have two frames - A and B.
The sprites for A were done in time, and the frame looks good when you draw it to the screen.
During that time your logic is working on the sprites for frame B, but it doesn’t
finish before NMI occurs.
Whatever was done for sprites at that point in time is going into OAM for frame B, and
those sprites will be rendered on the screen during the next frame.
That means if sprite building code execution didn’t finish, you shove sprite data into
OAM that is part of frame B combined with sprite leftovers from frame A!
And that is what gets rendered on the screen.
Not only that, but this is not a seamless divide.
Remember - NMI doesn’t wait for code to at least finish building the current sprite.
It interrupts.
Immediately.
So you could end up with, say, 16 sprites for frame B, 22 leftover sprites from frame
A, and then one hybrid sprite that uses a new Y coordinate for frame B paired with a
leftover X coordinate from frame A!
You are looking at some combination of these traits for what we’ll call the “seam sprite”
for a given frame.
Meanwhile, what about the disappearing sprites?
The flicker?
Those sprites aren’t disappearing.
They are simply in the wrong place for that frame.
Now At first you might think, but shouldn’t the sprites still be in the general area just
a few pixels behind where they should be if they are, indeed, from the previous frame?
But remember!
The game is switching sprite priority for Larry and Moe from frame to frame.
The order of sprites in OAM is being juggled between frames!
Leftover sprites AND leftover priority order.
What a mess!
And guess what - it gets worse.
After the hybrid sprite data has been passed to OAM for the next frame and the interrupt
work is complete, normal code execution resumes as expected… and it resumes inside the sprite
building subroutine it never finished.
It picks up right where it left off and completes execution.
A total waste of time to finish building sprites that have already missed the bus for the next
frame.
(There is probably an engineering and computer science joke in there somewhere.)
How do we wake-up from the nightmare?
How do we fix it?
The simple answer is to reprogram the entire game.
This sounds like a joke.
It kinda is… but honestly, it kinda isn’t.
You could attempt to tighten up efficiency on a micro level - optimize subroutines, reduce
code redundancy, etc.
And if you are optimistic and think, “that will do it so long as everything else is OK,”
I’d ask you to grade the game’s collision detection logic to determine if “everything
else is OK.”
It is possible that making many small optimizations will still not be enough to fix a game that
is broken in this manner.
There is one really cheap way to “fix it” for the sake of computer science.
If the problem is that we don’t have enough time for execution to finish building the
sprites, let’s just manipulate time itself.
We can overclock.
We need more frame execution time for the CPU prior to NMI occurring, so let’s increase
the number of scanlines available prior to NMI.
Remember that scanlines are not just what we see on the screen but also a unit of measurement.
This has nothing to do with the video output resolution seen on screen for the NES.
With this change made, it’s possible that unintended results are produced for Strider’s
gameplay.
However, seeing as Strider was designed with unintended results…
best not to worry about it.
The event viewer illustrates the overclock by extending our virtual vertical blanking
period.
We could get into a discussion about the accuracy of this illustration…
but just roll with it.
NMI now occurs way down here.
This gives us more “time” in this emulated environment to finish code execution prior
to being interrupted by NMI.
Once again, here is an example of code execution time for the sprite building subroutine.
Each character for the screen has plenty of time to have their sprites built prior to
NMI.
No partial sprites.
No old sprites.
Accurate priority.
Each frame is “fully realized” so to speak.
Let’s sync-up the stock and overclocked footage and run them side by side.
It doesn’t appear that there are any bugs in the sprite building subroutine - at least
for this example.
The sprites appear to render as they should so long as the subroutine doesn’t run out
of time.
Speaking of out of time, that should do it for this video!
There are plenty of oddities with Strider that are worth exploring.
Perhaps we aren’t quite finished with this game just yet.
If you like these types of videos, please consider liking, commenting, and subscribing
to let me know.
I also have a Patreon available if you are interested.
Thank you so much for watching.