13 — The First Impression
One screen. Four layers. Eight commits before anything looked right.
The Vanilla Problem
The first thing a player sees booting Pokémon FireRed is Charizard spreading its wings over a bold “FIRERED VERSION” banner. First-rate for FireRed. For a ROM hack called Ancient Ember — with its own lore about Primal Guardians and corrupted rift zones — opening on Charizard sends the wrong signal. The title screen had to go.
Replacing it meant understanding what the GBA was doing. The title screen is not one image. It is 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 border fill. On top sit OAM sprites for the version text and decorative effects.
Four layers, each competing for VRAM, each with its own palette slots. Everything looks like one image. Under the hood it is a collage of independent layers composited by hardware priority numbers.
The Background Art Saga
The background came first. 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 zeroed out — a numpy uint8 overflow in the conversion script where subtracting from 0 wrapped to 255, then clamped.
Eight commits. Each one taught something about how the GBA handles its display pipeline.
The Pokémon Logo, Cleaned
The Pokémon logo lives in BG0 as an 8bpp tileset — 256 colors, the full treatment. But it ships with “FireRed Version” baked into the same tileset, occupying rows 8–19 of the tilemap. The text is rendered as tiles, not a font. I could not just change it. 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 invisible. The text tiles were there, the tilemap was correct, the palette was loaded — but nothing appeared on screen.
Three separate root causes to track down.
Root cause 1: Palette colors. Palette slot 15 was loaded from the vanilla background palette — shades of purple designed for the Charizard scene. Against a dark volcanic background, purple-on-near-black is invisible. Fix: a custom gold palette (sPressStartPal) with amber tones that match the Ancient Ember theme.
Root cause 2: BG priority. BG2 (text) sat 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 regardless of color. 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 for all blank space — was filled solid with palette index 6. In vanilla, fine (purple fill behind purple text). With BG2 now in front of BG1, every “empty” tile covered the background art with a solid rectangle. Fix: zero the first 32 bytes of the 4bpp file, making tile 0 transparent.
Three bugs. Three layers of the GBA compositing system. Each one right in the original game, each one broken by the new arrangement. This is the recurring lesson of ROM hacking: you are not building on a platform, you are building inside someone else’s carefully balanced house of cards.
The Skip Handler
One more, caught during testing: pressing A during the intro flash animation skipped to the gameplay scene, but the flame and ANCIENT EMBER sprites never showed up. The skip handler jumped past the FADEIN sequence where those sprites get created. Two CreateSprite calls and a #if defined(FIRERED) guard later, the skip runs correctly — same 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 went under audit — 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 needs to touch the title screen, they will not have to rediscover that tile 0 needs to be transparent or that BG2 must be higher priority than BG1. That is the whole point of documentation.
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 | 175 |
| Copilot requests | 54 |
| Tool executions | ~1,520 |
| Sub-agents | 8 |
18 commits to replace one screen. Half of them were wrong. That is how you learn a platform — not by reading the manual, but by watching your changes disappear and asking why.
Back to README