13 — The First Impression

Custom title screen art, invisible text, and the art of layering


The Vanilla Problem

The first thing a player sees when they boot Pokémon FireRed is Charizard spreading its wings over a bold “FIRERED VERSION” banner. It’s iconic — for FireRed. For a ROM hack called Ancient Ember, with its own lore about Primal Guardians and corrupted rift zones, opening on Charizard sends exactly the wrong signal. The title screen needed to go.

Replacing it meant understanding what the GBA was actually doing — and it turns out, quite a lot. The title screen isn’t one image. It’s four background layers, each with its own tileset, tilemap, and palette, plus sprites on top. BG0 holds the Pokémon logo. BG1 holds the background art (Charizard in vanilla). BG2 holds the copyright and PRESS START text. BG3 is just border fill. Then there are OAM sprites for the version text and decorative effects.

Four layers, each competing for VRAM, each with its own palette slots. This is the GBA’s party trick: everything looks like one image, but under the hood it’s a collage of independent layers composited by hardware priority numbers.

The Background Art Saga

The background was the first piece. I had pixel art of a volcanic landscape — fire and stone, fitting the Ancient Ember theme. Converting it to GBA format meant a 16-color palette extracted from the original, dithered down and mapped to 8×8 tiles. The first attempt came out green (wrong palette slot). The second was shifted 30 pixels to the right (stale BG scroll registers from the overworld). The third had a blue channel that was entirely zeroed — a numpy uint8 overflow in the conversion script where subtracting from 0 wrapped to 255, then got clamped.

Eight commits later, the background worked. Each fix taught something about how the GBA actually handles its display pipeline.

The Pokémon Logo, Cleaned

The Pokémon logo lives in BG0 as an 8bpp tileset — 256 colors, the full luxury treatment. But it ships with “FireRed Version” baked into the same tileset, occupying rows 8–19 of the tilemap. I couldn’t just change the text — it’s rendered as tiles, not a font. So I cleaned it: zeroed all pixels with palette index ≥192 (the FireRed text colors) and blanked tilemap rows 8–19. The logo remains. The branding is gone.

In its place: three 64×64 OAM sprites spelling “ANCIENT EMBER” in a fire-gradient palette — seven colors ramping from dark red through amber to bright gold. They appear during the fade-in sequence alongside a center flame sprite, filling the space the version text used to occupy.

The Invisible Text

PRESS START was the last piece, and the hardest. It was completely invisible. The text tiles were there, the tilemap was correct, the palette was loaded — but nothing appeared on screen.

The hunt took three separate root causes to resolve:

Root cause 1: Palette colors. Palette slot 15 was loaded from the vanilla background palette — shades of purple designed to fill the Charizard scene. Against our dark volcanic background, purple-on-near-black is invisible. Fix: create a custom gold palette (sPressStartPal) with amber tones that match the Ancient Ember theme.

Root cause 2: BG priority. BG2 (text) was at priority 2, behind BG1 (background art) at priority 1. The background art is opaque — every tile filled. Text behind an opaque layer is invisible no matter what color it is. Fix: swap priorities. BG1 to priority 2 (back), BG2 to priority 1 (front).

Root cause 3: Tile 0. The copyright tileset’s tile 0 — the “empty” tile used for all the blank space — was filled solid with palette index 6. In the vanilla game this was fine (purple fill behind purple text). With BG2 now in front of BG1, every “empty” tile would cover the background art with a solid rectangle. Fix: zero the first 32 bytes of the 4bpp file, making tile 0 truly transparent.

Three bugs. Three layers of the GBA compositing system. Each one perfectly reasonable in the original game, each one broken by our changes. This is the recurring lesson of ROM hacking: you’re not building on a platform, you’re building inside someone else’s carefully balanced house of cards.

The Skip Handler

One more bug, caught during testing: pressing A during the intro flash animation skipped straight to the gameplay scene, but the flame and ANCIENT EMBER sprites never appeared. The skip handler jumped past the FADEIN sequence where those sprites are created. Two CreateSprite calls and a #if defined(FIRERED) guard later, the skip works correctly — same visual result whether you watch the full intro or mash through it.

Documentation Day

With the title screen working, I spent the rest of the session updating every documentation file in the project. The STYLEGUIDE got a full title screen architecture section — which BG layer does what, where sprites go, palette slot assignments. The AI_CONTEXT got the files added to its feature map and pitfall table. The LORE document got a new section connecting the title screen imagery to the Primal Guardians myth. Even the color array naming conventions got audited — turns out sColorPurp means different things in different files, and sColorCyan uses different TEXT_COLOR constants depending on who wrote it.

Not glamorous work. But the next time someone (human or AI) needs to touch the title screen, they won’t have to rediscover that tile 0 needs to be transparent or that BG2 must be higher priority than BG1. That’s the whole point of documentation: saving future-you from past-you’s hard-won lessons.

By the Numbers

Metric Value
Active ~8 hours
Title screen commits 18 (incl. background iterations)
Documentation commits 2
Root causes found 4 (palette, priority, tile 0, skip handler)
BG layers understood 4 (finally)
Project total commits ~230
Copilot requests 54
Tool executions ~1,520
Sub-agents 8

18 commits to replace one screen. Half of them were wrong. That’s how you learn a platform — not by reading the manual, but by watching your changes disappear and asking why.

Back to README