03 — The Grey Screen
The stack had 140 bytes left.
The Symptom
The ROM built. The title screen appeared. Press Start and — grey. Solid grey screen. No crash message (GBA doesn’t have those). No sound. Just permanent, silent death.
It happened on every boot. No save file needed. The game couldn’t get past the main menu.
The Investigation
Step 1: Rule Out Save Data
The crash happened without a save file. That 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. The crash was mine — not the base code, not the toolchain.
Step 3: The Rebase Detour
Before finding the root cause, there was a necessary detour. My fork rested on a stale third-party fork — Shiny-Miner/pokefirered — years behind upstream. Claude rebased onto the official pret/pokefirered, picking up 33 new upstream commits including critical GCC 15 compatibility fixes.
The rebase was its own work. git rebase uses reversed flag semantics — --theirs means the current commit. Map constant names had changed (SEVEN_ISLAND_SEVAULT_CANYON requiring a MAP_ prefix). JSON data formats had shifted. A Python script merged my custom NPC data with upstream’s reorganized map JSON.
After the rebase, the grey screen remained. But the comparison point was clean.
Step 4: The Binary Search
Claude compared BSS sizes — zero-initialized static data — 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 is the entire fast RAM. It holds the stack, BSS, and some performance-critical code.
With 32,628 bytes of BSS, the stack had 140 bytes remaining. One function call with a few local variables and it overflowed.
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. EWRAM runs slower for access. For data used only during a UI screen, that difference doesn’t matter.
The ROM booted. Grey screen gone. Not bad for two characters.
The Lesson
GBA IWRAM is 32KB total. Every static variable in every .c file in the project competes for that space. There is no linker warning when you exceed it. The ROM crashes because the stack collides with your data.
After this fix, the rule was set: no 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 machine where RAM comes in bytes and every kilobyte earns its keep.
A Note on Debugging
GBA debugging is brutal. No stdout. No debugger in most setups. No crash logs. When something goes wrong:
- 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. The grey screen was just the most dramatic.
By the Numbers
| Metric | Value |
|---|---|
| Commits | ~7 |
| Copilot requests | ~10 |
| Tool executions | ~377 |
| Sub-agents | 0 |