25 — Buried Three Deep
Three separate bugs, same symptom. The last one had been hiding since the project began.
Still Yellow
Devlog 24 concluded the fix was in. mGBA was just stale. File → Load ROM.
The next screenshot came back yellow.
That is a hard thing to read after two sessions. When the evidence says it is fixed and the screen says otherwise, one of them is wrong. The screen does not lie.
Started again from the beginning.
The Second Root Cause
The Python compositor from the prior session had rendered everything correctly: black backdrop, Pokémon logo, box art flame, press start text. No yellow. But the compositor was reading the wrong files. The ROM had been rebuilt. The ROM had new files. The compositor was reading the current disk state. So was the emulator.
That meant the compositor was also wrong. Or missing something.
Went deeper into BG2. Not the tilemap entry count — the tile pixel data itself.
copyright_press_start.4bpp: 64 tiles, palette slot 15. Decompressed and read every nibble. All 64 tiles had pixel value 6 as their background fill. Not zero. Six.
In 4bpp mode, pixel value 0 is transparent. Pixel value 6 is whatever palette slot 0 entry 6 contains. In the FireRed logo palette, entry 6 is 0x17DE — yellow.
The BG2 tilemap covers all 32×20 screen positions. Every entry that references tile 0 renders that tile using palette slot 0. Tile 0 was entirely pixel-6. BG2 was painting the entire screen solid yellow — on top of everything, BG1 and BG3 included.
The palette[0] fix set the backdrop to black. BG2 did not care. BG2 does not use the backdrop. BG2 rendered its own yellow tiles directly.
Two separate bugs. Two separate yellow sources. Both needed fixing.
The Fix
Wrote a Python script. Read every byte of copyright_press_start.4bpp. For each nibble that was 6, wrote 0. Zero is transparent. The text and separator pixels — values 1–5 and 7–15 — stayed untouched. Background pixels became invisible. BG2 now shows only the text it is supposed to show.
Recompressed. Force-tracked in git. Added a protection rule in graphics_file_rules.mk.
Added a CI test: tile 0 must be all-zero; no pixel value 6 anywhere in the tileset. Regression coverage. Won’t happen again.
Loaded the ROM. Black background. Logo. Ancient Ember text. Press Start. All correct.
Not Correct
The background was jumbled. A 96×96 scrambled image in the center where the rift background should be. Wrong colors — blues and teals where there should be dark purples and that orange rift glow.
The yellow had been hiding a third problem the whole time.
The Third Root Cause
box_art_mon.4bpp.lz contained 135 tiles. The custom rift background needs 512. 135 tiles is 4320 bytes — exactly what grit outputs from a 96×96 PNG with deduplication. That is the vanilla Charizard tile count. Not the rift.
Traced it back. Commit b3b5c2675, message “fix”, author AI, timestamp March 6. That commit shrank box_art_mon.4bpp from 16384 bytes to 4320 bytes. It regenerated the tile data from the vanilla Charizard box art PNG that was sitting in the graphics folder — a leftover source file, never meant to be compiled. The active Makefile rule had -num_tiles 135. That rule was wrong. The regeneration ran. The rift background was replaced.
Nobody noticed because the title screen had been yellow since before that commit ran.
Restoring the Rift
First attempt: extracted box_art_mon.4bpp from commit ec3b04b56 — the last commit before b3b5c2675. Got 512 tiles, correct count. Recompressed. Built. Loaded.
Colors were wrong. Washed out. Blues where there should be warm fire.
The palette had not changed. The palette in box_art_mon.gbapal was from a later commit. The 4bpp tiles and the palette were written for different color arrangements. Tiles from one commit, palette from another — neither is wrong independently, but together they do not match.
Checked commit 010586c6f — “Custom title screen: ANCIENT EMBER sprites, gold PRESS START, BG priority fix.” That commit changed both box_art_mon.4bpp and box_art_mon.gbapal together. Those two files are a matched pair. They were designed together and must be deployed together.
Extracted both from 010586c6f. But the palette was already current — only the tiles needed replacing. Extracted the .4bpp and .bin from 010586c6f. Recompressed. Built. Loaded.
The rift is back. Dark center. Purple tendrils. Mountain in the corner. Sky in the upper left. Correct.
Locking It Down
This was three sessions of the same symptom. The right response to that is hardware.
Added check_box_art_mon_integrity() to the CI suite: SHA256 hash check against box_art_mon.4bpp, .bin, and .gbapal. If any of those three files change — whether from a bad make run, a wrong restoration, or a “fix” commit — the test fails immediately with a restore command pointing to 010586c6f.
Changed the active Makefile rule for box_art_mon.4bpp from -num_tiles 135 to a no-op. Same pattern as game_title_logo.8bpp, border_bg.4bpp, copyright_press_start.4bpp. The Makefile cannot regenerate any of them. git add -f is the only path to update.
Updated the CI tile count check: expected tiles 135 → 512. The old number was the symptom count from the bug. The right number is 512.
18 CI checks now. Five of them guard the title screen specifically.
The Lesson
Three bugs behind the same yellow screen. Each one masked by the one on top.
Bug 1 was caught in session 22: game_title_logo.gbapal[0] was yellow. Fixed. Session 23 shipped.
Bug 2 was hiding behind bug 1: BG2 tile 0 had solid yellow fill. Without bug 1 fixed, bug 2 was invisible — both rendered yellow, same result. With bug 1 fixed, bug 2 became the only yellow source. Found and fixed in this session.
Bug 3 was hiding behind bugs 1 and 2: the rift background had been replaced with Charizard tiles. The whole title screen was yellow — the rift problem was invisible. After both yellow bugs were fixed, the rift appeared. Scrambled. Restored.
The pattern is clear. A visible defect can mask deeper ones. A fix that makes a screen look wrong — rather than fully right — is still a fix. It exposed the next layer.
The SHA256 test is the post. Without it, the next “fix” commit could restore from the wrong source again and nobody would know until the title screen turned wrong colors in a future session.
Docs
Updated docs/AI_CONTEXT.md:
- Box art mon entry: 135 tiles → 512 tiles, matched-pair warning, restore command
- Lint check list: 16 → 18, added BG layer architecture table and 4bpp transparency rule note
- Yellow troubleshooting entry: single root cause expanded to three-layer breakdown
Updated CLAUDE.md constraint #18: box_art_mon.4bpp added to protected file list, matched-pair note added.
Added STYLEGUIDE.md §13 — Title Screen Architecture: BG layer table, hand-crafted binary rules, matched-pair restore snippet, 4bpp pixel transparency pitfall, mGBA reload note.
By the Numbers
| Metric | Value |
|---|---|
| Commits | 5 |
| Copilot requests | ~12 |
| Tool executions | ~95 |
| Sub-agents spawned | 2 |
When the screen is wrong and the fix says it should be right, trust the screen.
Back to README