23 — The Yellow Screen

Three bugs walked into the title screen. None of them knocked.


Something Wrong

The title screen was yellow.

Not a little yellow. Not a tint. Bright yellow-green filling the upper two-thirds of the frame, bleeding through wherever the background art should have been. ANCIENT EMBER floated over a smear of color that had no business being there.

Pressed A. Game ran fine. The title screen was wrong and everything else was not.

That combination — isolated to one screen, not a crash — means root cause is rendering. Palette, tilemap, or a layer that isn’t drawing. Time to go looking.


Three Bugs at Once

Pulled the palette out of the ROM. The first entry in game_title_logo.gbapal read 0x17E0. That is bright green in GBA BGR555.

On the GBA, BG0 runs in 8bpp mode on the title screen. In 8bpp mode, palette entry 0 is not just a color — it is the hardware backdrop color. Any pixel on any layer that is transparent shows palette entry 0 straight through to the display. Set it to green and every transparent pixel in the entire scene goes green. No exception.

That explained the color. It did not explain why so much of the screen was transparent.

Wrote a small Python renderer. Fed it the tilemap (box_art_mon.bin.lz), the tile data (box_art_mon.4bpp.lz), and the palette. Asked it to draw what the hardware would actually draw. The output showed 427 of 640 screen tiles rendering blank — transparent.

The tilemap contained indices up to 511. The tile data held 135 tiles. Any index above 134 is out of range on this hardware. Out of range means transparent. Sixty-seven percent of the background art was falling through to the backdrop color.

That is three bugs in one screen:

  1. gbapal[0] = 0x17E0 — wrong backdrop color
  2. Tilemap references 512 tiles, tile data has 135 — 427 tiles transparent
  3. No defensive guard — a future palette load could corrupt entry 0 again

Could have patched the color and moved on. The other two would have bitten later.


Finding the Break

git log --oneline pointed at commit b3b5c2675. It changed box_art_mon.4bpp from 16384 bytes down to 4320 bytes — a new tighter asset, 135 tiles. The tilemap was never regenerated. It still referenced the old layout.

That is how you get a haunted tilemap. The tile data changes shape and the map keeps pointing at where the old tiles used to be.

The Python renderer confirmed it. Running it against the old tile count showed a full image. Running it against 135 tiles showed most of the screen blank. Mighty good diagnostic tool for a platform with no GPU debugger.


The Fixes

Three problems, three fixes.

First: game_title_logo.gbapal[0] set to 0x0000. Black. A hex editor and a two-byte change.

Second: rebuilt box_art_mon.bin. The background art is a 96×96 pixel PNG — 12 tiles wide, 12 tiles tall. Centered on a 30×20 screen that puts it at column 9, row 4. Wrote the tilemap with 0-indexed tile references starting at 0. Compressed it. Force-tracked the binary.

Third: added gPlttBufferUnfaded[0] = 0 in both title screen init paths — the normal path and the restart path — immediately after LoadPalette. One line each. If a future palette load sets entry 0 wrong, the guard overwrites it before the frame renders.

Builds clean. Title screen is black where it should be black, and the background art fills where it should fill.

Not bad.


Locking It Down

Eight lint checks in the CI suite before this. After, sixteen.

Added check_title_screen_backdrop() with three sub-checks: palette entry 0 must be 0x0000 in the title palette binary, the tilemap must not reference any tile index above the tile count, and the defensive guard must be present in title_screen.c on both init paths.

Added check_box_art_mon_tile_count(): exactly 135 tiles in box_art_mon.4bpp.lz. If someone replaces the asset, this check fires before the broken tilemap ships.

Expanded check_palette_faded_restore() — previously only scanned start_menu.c. Now it covers all custom UI files. The pattern it checks: every full-screen UI must restore gPlttBufferUnfaded[0] after loading its palette. Twelve files. All pass.

The test suite does not run the emulator. It reads the binary assets and parses C source. Fast enough that there is no reason not to run it on every build.


The Documentation

Two notes added to AI_CONTEXT.md and STYLEGUIDE.md:

In 8bpp mode, palette entry 0 is the hardware backdrop color. Any transparent pixel on any layer shows this color. Always set it to black (0x0000) and guard it after every palette load.

The box_art_mon tilemap and tile data must be regenerated together. Changing one without the other produces out-of-range tile references that render transparent.

Short notes. The kind that save an hour next time.


By the Numbers

Metric Value
Commits 0 (pending)
Copilot requests 4
Tool executions 127
Sub-agents spawned 1

Three bugs is not unusual. Finding all three before pushing is the work.

Back to README