03 — The Grey Screen
A crash that taught me everything about GBA memory
The Symptom
The ROM built. The title screen appeared. You press Start and… grey. Solid grey screen. No crash message (GBA doesn’t have those). No sound. Just permanent, silent death.
This happened on every boot. No save file needed. The game couldn’t even get past the main menu.
The Investigation
Step 1: Rule Out Save Data
The crash happened without a save file, which immediately ruled out my SaveBlock modifications. Good — that was the scariest possibility.
Step 2: Build a Comparison ROM
Claude built a pure upstream pret/pokefirered ROM from the same commit. It booted fine. So the crash was definitively caused by my additions, not the base code or toolchain.
Step 3: The Rebase Detour
Before finding the root cause, there was a necessary detour. My fork was based on a stale third-party fork (Shiny-Miner/pokefirered) that was years behind upstream. Claude rebased onto the official pret/pokefirered, picking up 33 new upstream commits including critical GCC 15 compatibility fixes.
The rebase itself was an adventure — git rebase uses reversed flag semantics (--theirs = the current commit), map constant names had changed (SEVEN_ISLAND_SEVAULT_CANYON → requiring MAP_ prefix), and JSON data formats had shifted. A Python script was needed to merge my custom NPC data with upstream’s reorganized map JSON.
After the rebase, the grey screen persisted. But now I had a clean comparison point.
Step 4: The Binary Search
Claude compared BSS (zero-initialized static data) and COMMON symbol sizes between my build and upstream:
My build: IWRAM BSS = 32,628 bytes
Upstream: IWRAM BSS = 30,588 bytes
Difference: +2,040 bytes
IWRAM on the GBA is exactly 32,768 bytes (32KB). That’s it. That’s the entire fast RAM. IWRAM holds the stack, BSS, and some performance-critical code.
With 32,628 bytes of BSS, the stack had only 140 bytes left. A single function call with a few local variables would overflow.
Step 5: The Culprit
// gear_ui.c — before the fix
static struct ListMenuItem sGearListItems[MAX_GEAR_LIST]; // 408 bytes
static u8 sGearListStrings[MAX_GEAR_LIST][32]; // 1,632 bytes
Two static arrays in gear_ui.c. Plain static means IWRAM BSS on the GBA. Combined: 2,040 bytes. Exactly the difference.
The Fix
Two characters:
// gear_ui.c — after the fix
EWRAM_DATA static struct ListMenuItem sGearListItems[MAX_GEAR_LIST] = {0};
EWRAM_DATA static u8 sGearListStrings[MAX_GEAR_LIST][32] = {0};
EWRAM_DATA is a GCC section attribute that places the variable in EWRAM (256KB, external RAM) instead of IWRAM (32KB, internal RAM). EWRAM is slower for access, but for data that’s only used during a UI screen, the performance difference is irrelevant.
The ROM booted. The grey screen was gone.
The Lesson
GBA IWRAM is 32KB total. Every static variable in every .c file in the entire project competes for that space. There’s no linker warning when you exceed it — the ROM just crashes because the stack collides with your data.
After this fix, I established a hard rule: never add new static buffers. All large allocations use EWRAM_DATA, heap Alloc()/Free(), or direct DMA to VRAM.
Current memory status:
- IWRAM: ~92% used (2.6KB remaining for stack)
- EWRAM: ~99.7% used (less than 1KB free)
- ROM: only 28% used (plenty of code/data space)
The GBA is a system where RAM is measured in bytes and every kilobyte is precious. The grey screen taught me to respect that.
A Note on Debugging
GBA debugging is brutal. There’s no stdout, no debugger (in most setups), no crash logs. When something goes wrong, you get:
- Grey screen (stack overflow, bad jump)
- Black screen (palette/display not initialized)
- Garbled graphics (VRAM corruption, wrong palette)
- Red/orange flash (stale palette data)
- Silent wrong behavior (data corruption, off-by-one)
Every one of these happened to me. The grey screen was just the most dramatic.
By the Numbers
| Metric | Value |
|---|---|
| Commits | ~7 |
| Copilot requests | ~10 |
| Tool executions | ~377 |
| Sub-agents | 0 |