February to April Gears emulator update

Fixing interrupts and starting audio !

May 02, 2023

Previously, I had a bug in my gears emulator, which I then fixed. But Sonic 2 still wasn't working, so let's see what went wrong there.

Missing interrupt behaviour

The Sonic 2 ROM wasn't starting at all, leaving an uninteresting black screen, which led me to suspect some kind of infinite loop. I said in the previous posts that I thought this loop could have been related to timings or interrupts.

So I looked at how the game behaved in Emulicious, another (closed-source) emulator with great debugging capability. And I noticed something weird on my side of the emulation: after a HALT instruction (that shuts down the CPU similarly to running an infinite number of NOPs), the VDP interrupt would wake-up the CPU, it would be handled and return. But when returning it jumped straight into the previous HALT instruction (!), meaning it looped and never moved forward. I fixed this by implementing the missing PC increment, and it set forward the startup of Sonic 2.

Sonic 2 Press start screen

Later, I went looking at another game that didn't finish starting: Global Gladiators. I figured it might be something related with an infinite loop somewhere + interrupts. I went to the frame that froze...

First of intro of Mick & Mack: Global Gladiators: showing a McDonald restaurant on top of a hill

... and I noticed something similar: a loop reading from the VDP V Counter (or line counter), waiting for it to be 0xC0. But it never happened ! Because at 0xC0, the VDP raised an interrupt and by the time the interrupt was handled, the VDP already had processed a few other lines. So the code looped, waiting for an event that never happened. Emulicious seemed to send the interrupt at 0xC1, contrary to what the official Game Gear manual said would happen; but Charles McDonald's VDP documentation from 2002 said it should happen 0xC1. So I simply fixed it by using this same value. But something tells me there might be other timing-related bugs hiding here. At least I can now play on of the greatest games of the platform 😉

Global Gladiators intro: Ronald McDonald says HOW ABOUT THIS ? to Mick & Mack.
How about this ?
Global Gladiators Press Start screen: a big yellow M from McDonalds with GLOBAL GLADIATORS written on top.
Did a brand pay for this ?

In the end, this untested interrupt code is the source of multiple bugs... I might need to find a way to write simple test for these.


This one is a quite simple and well-documented VDP feature for background patterns: there's a bit to have them use the second color palette (usually for sprites). It allows using 16 more colors for the background: background can use 32 colors, while sprites can only use the second half (16) of those colors at a given time.

Sonic Triple Trouble character select screen: impossible to see if Sonic or Tails is selected.
Bad rendering: which character is selected ?
Sonic Triple Trouble character sellect screen: Tails is highlighted and is obviously the selected character.
Palette bit is used to implement character selection

The color palette select bit was already decoded, but not used for rendering, so the fix was straightforward.

Sonic 2 level Intro: Under Ground Zone Act 1; the image of Sonic and tails seems inverted, with wrong colors.
Bad rendering with palette 0
Sonic 2 level Intro: Under Ground Zone Act 1: Sonic and Tails are sitting in wagon going into a mine.
Sonic and Tails use pallete 1

Making sounds

After fixing enough VDP bugs that many games are now playable, I started looking into making sound. I used the rust crate cpal which seemed like the most well-maintained and portable for abstraction for playing audio. After a PoC to play a simple 440 Hz sinusoid, I slowly wired things up. The programming model of cpal is to provide a callback that will be polled by an audio thread, so it forced my design to do more Send+Sync data structures, while initially everything in gears was single threaded, with the use of Rc for some simple high-level things like dynamic device registration. It forced me to use Arc and Mutex to ensure the PSG data structures would be shareable across threads.

I was inspired by the design of the VGM 'audio' files, which are just basically command dumps from the PSG writes. Audio rendering is done as late as possible to ensure being as independent as possible from the sample format. The queue of commands updates the PSG internal state, and it generates audio when cycles go forward.

Just like with the VDP, the CPU cycles are the reference, so instruction cycles need to be correctly counted or emulation can be visibly changed.

Audio is played in real time, so there is a high accuracy component to ensure what comes out sounds 'good'; and in gears, synchronization is far from done — not because of instructions: cycle accuracy is good enough to pass the fuse test suite. But because the emulator currently relies on VSYNC to do 60 frames per second: it's very convenient that all my screens are also at 60Hz, like the Game Gear's LCD!

But since frames during which the VDP is BLANK (screen "off") are not displayed, the emulation goes too fast during blank frames ! It means that PSG commands will accumulate and need to be "flushed" later on to keep the queue from growing indefinitely; this leads to weird fast music effects, then going back to normal speed, like during the beginning of the Green Level in Sonic 1:

As you can hear, in addition to proper synchronization, there are other things missing, like noise generation. Maybe for the next update ?