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:

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:

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

Next: 04 — The String Encoding Mystery