RSS About
A screenshot of a terminal window showing ROMs having been built. On top, two emulator windows: mednafen showing a blank screen, and Goholint showing concentric rectangles in four colors.

Funny bugs: the missing bit

So, just a couple days ago, I published an article along with a few test ROMs that were so simple I genuinely believed they didn’t contain any kind of serious bug1.

I mean, they ran just fine on Goholint, and Goholint is now capable of running many actual games. Showing a few tiles and changing a palette felt like no big deal.

Hell, I even tested those ROMs on actual — if not original — hardware, and to my greatest pleasure, they appeared to run just fine!

One of the ROMs from the last article running on an Analogue Pocket handheld console.
One of the ROMs from the last article running on an Analogue Pocket handheld console.

 

The same ROM as above, this time running on a Game Boy Advance SP.

I thought I was done, I went to sleep happy and all was well.

It worked for me!

The following morning, I got a message from my friend and fellow nerd Hiro Lynx, who contacted me to explain that he had tried those ROMs on other emulators, even on an actual Game Boy Pocket, and had “mitigated results”.

One of the test ROMs running on a Game Boy Pocket. There should be visible tiles, but the screen is blank.

Not only did my ROMs not produce the expected result on real hardware, but the same bug occurred in three other distinct emulators.

So I did what I really should have done from the very beginning: I tested my ROMs on one of those. I downloaded and built mednafen.

And sure enough:

One of the test ROMs running on mednafen. There should be visible tiles, but the screen is blank.

Damn!

How did it ever work?

That kind of bug really hits hard, because I was so sure displaying tiles from the background map was a solved problem. I know that my emulator still doesn’t properly handle some subtler features, like the Background/Sprite priority bits, but by now, graphics in most games look how they are supposed to. Goholint can even run Gargoyle’s Quest!

That threw me off for a little while.

Usually when I have not implemented something properly (or at all), stuff tends to not work on Goholint, but otherwise looks fine in other emulators. The opposite happening was a bit surprising.

However, that wasn’t quite the first time…

In the last article, I discreetly mentioned in a footnote that Goholint used to not care about ROM code turning off the display or waiting for VBlank before writing to VRAM.

In fact, early enough in development, I wrote a few test ROMs, specifically to test my PPU implementation at the time. One thing I remember is that one of those seemed to work as expected in Goholint, but when I tried it in another emulator, my tiles would look bad — because I had been writing them to video RAM without caring about what state the PPU was in, and most of those bytes never actually got written.

That was one case where things seemed to work fine for me, when they shouldn’t have. Just out of curiosity, I threw a couple of these old ROMs at mednafen.

You know what? They worked just fine!

Problem Exists Between Keyboard And Chair

None of these test ROMs are very complicated, so I could easily look at the old, working ones side-by-side with the newer, not-working ones.

By and large, they look very similar: turn display off. Copy data to video RAM. Set up a VBlank interrupt handler. Turn display back on. Wait for VBlank. Do things. Lather. Rinse. Repeat.

I still spotted one subtle difference, though: the value I wrote to LCDC when I wanted to turn the display back on.

The old test ROMs all used the same value:

; Turn LCD back on
LD A, $93
LD ($FF00+$40), A

The new ones all used another value:

; Turn PPU back on (bit 7), read tile data from 0x8000 (bit 4).
LD A, $90
LDH ($FF00+$40), A

Before even trying to understand, I quickly tested replacing that $90 by $93, and ran the resulting ROM on mednafen again.

And sure enough…

One of the hastily fixed test ROMs now running correctly on mednafen.

[Insert loud profanity here]

Above all, it was a relief to realize it wasn’t more difficult to fix. However it also meant that Goholint blissfully ignored some bits that clearly meant something to full-fledged emulators.

But what was broken exactly?

I haven’t looked at the PPU logic in ages, so I had to spend some time going over the code again. Fortunately, it is pretty heavily commented. I even wrote dedicated constants for each bit of the LCDC register:

// LCDC flags.
const (
    // Bit 0 - BG/Window Display/Priority     (0=Off, 1=On)
    LCDCBGDisplay uint8 = 1 << iota
    // Bit 1 - OBJ (Sprite) Display Enable    (0=Off, 1=On)
    LCDCSpriteDisplayEnable
    // Bit 2 - OBJ (Sprite) Size              (0=8x8, 1=8x16)
    LCDCSpriteSize
    // Bit 3 - BG Tile Map Display Select     (0=9800-9BFF, 1=9C00-9FFF)
    LCDCBGTileMapDisplayeSelect
    // Bit 4 - BG & Window Tile Data Select   (0=8800-97FF, 1=8000-8FFF)
    LCDCBGWindowTileDataSelect
    // Bit 5 - Window Display Enable          (0=Off, 1=On)
    LCDCWindowDisplayEnable
    // Bit 6 - Window Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
    LCDCWindowTileMapDisplayeSelect
    // Bit 7 - LCD Display Enable             (0=Off, 1=On)
    LCDCDisplayEnable
)

Those code comments were copied straight from the old GBDev wiki. In fact, that page I just linked was the main documentation I had at the time.

I remember that “BG/Windows Display/Priority” bit sounded confusing, and in retrospect I am almost positive I read it as “select priority between Window and Background” or something of the kind. Which I probably decided I would implement later.

Worse yet, on the old wiki, there is no further detailed mention of what that bit does, except on a Game Boy Color, where it has a different meaning, and I wasn’t going to implement that either. In the end, I just left it alone, and until today that bit’s value was never checked anywhere in Goholint.

I assume that the $93 value I used in my older test ROMs must have been copied from example code I saw somewhere.

So I went hunting for the actual meaning of that bit.

Good old Pan Docs

Yes, of course I went to the Pan Docs right away — I pretty much have those permanently open in a tab somewhere by now.

Their current documentation is a lot clearer:

LCDC.0 — BG and Window enable/priority

LCDC.0 has different meanings depending on Game Boy type and Mode:

Non-CGB Mode (DMG, SGB and CGB in compatibility mode): BG and Window display

When Bit 0 is cleared, both background and window become blank (white), and the Window Display Bit is ignored in that case. Only objects may still be displayed (if enabled in Bit 1).

Oh dang! Now, that sounds like something important I should have implemented.

So I did! And then I fixed the assembly code from my last article. While I’m definitely miffed to have overlooked something like that for so long, knowing my emulator works a little better today than it did yesterday makes me happy.

I’ll take that as a win!

But it did work for me!

I’ll admit I’m not 100% certain why my ROMs ran fine on an Analog Pocket and a Game Boy Advance, but not on a proper emulator or a real Game Boy.

The best explanation I have is that, when running Game Boy cartridges, those handhelds internally behave as if they were a Game Boy Color — which they are supposed to be able to emulate as well, and which is also compatible with original Game Boy games anyway. The Game Boy Color will not disable the background if that bit is unset, so the ROMs would run like they did on Goholint.

You would think that this bit having different meanings between those two systems might be an issue, but I suspect there weren’t a lot of games that actually needed to turn off the background map entirely? I don’t know.

The main thing I care about is that the bug is now fixed. Whew!

A million thanks to Hiro for testing my stuff better than I did. At least now I have another readily available emulator to try out my homemade ROMs.

Thank you for reading!


  1. I have been writing sofware for literal decades, I definitely should know better by now. ↩︎