07 — Into the Rift
Building a 5-tier endgame dungeon from scratch
The Design
Every loot game needs an endgame. Without it, you hit the credits and the gear system loses its purpose. The Rift Zone is my answer: a post-Champion gauntlet of 5 increasingly difficult dungeon floors, each with corrupted wild Pokémon, a boss trainer, and shard rewards that fuel the crafting loop.
Narratively, it’s the wound the Primal Guardians left behind. Mechanically, it’s where gear matters most.
Building Maps from Nothing
FireRed’s map system is complex. Each map needs:
- Layout — dimensions, tileset references, blockdata (the actual tile grid)
- Map data — connections, warps, events, NPC positions
- Scripts — event logic in a custom assembly-like language
- Text — dialogue strings
- Encounters — wild Pokémon tables
- Registration — entries in
layouts.json,map_groups.json, andevent_scripts.s
For 5 maps, that’s 35+ files.
Blockdata Generation
Each map tile is a u16 value:
Bits 0-9: Metatile ID (which 16×16 tile pattern to display)
Bits 10-11: Collision (0=passable, 1=impassable)
Bits 12-15: Elevation (for bridges, stairs, etc.)
A 20×15 map is 600 tiles = 1,200 bytes. Claude wrote a Python script to generate cave rooms using the CeruleanCave tileset:
- Floor: metatile
0x281(cave ground), elevation 3 →0x3281 - Walls: metatile
0x099(cave wall), collision bit set →0x0699 - Perimeter walls, open interior, staircase positions for warps
The Warp Chain
The 5 tiers are connected by warps:
Cinnabar Island → Tier 1 (bottom) → Tier 2 (bottom) → Tier 3 (bottom)
→ Tier 4 (bottom) → Tier 5 (top exits to Cinnabar)
Each tier’s bottom warp leads to the next tier’s entrance. Tier 5’s top warp returns to Cinnabar. A simple linear path — no backtracking, no map selection screen.
Boss Trainers
Five Rift Shades, each embodying a corruption theme:
| Tier | Name | Theme | Level Range | Party |
|---|---|---|---|---|
| 1 | The Dark Caller | Darkness | 50-52 | Houndoom, Murkrow, Sneasel |
| 2 | The Rotting One | Poison/Decay | 55-57 | Muk, Weezing, Crobat, Vileplume |
| 3 | The Hollow Mind | Ghost/Death | 60-62 | Gengar, Misdreavus, Dusclops, Sableye |
| 4 | The Silent Scream | Psychic/Madness | 65-67 | Alakazam, Hypno, Mr. Mime, Gardevoir, Starmie |
| 5 | The Rift Itself | Pure Corruption | 70-75 | Tyranitar, Gengar, Dragonite, Salamence, Metagross, Alakazam |
The final Shade has 6 Pokémon — the maximum. Its dialogue acknowledges the player:
“You carry the fire of the Guardians… but I AM the wound they left behind!”
Shard Rewards
Defeating a boss triggers shard rewards via a script→C bridge:
// Script:
setvar VAR_0x8000, 15
special SpecialAddGearShards
setvar VAR_0x8000, 5
special SpecialAddRadiantShards
// C:
void SpecialAddGearShards(void) {
AddGearShards(gSpecialVar_0x8000);
}
Rewards escalate:
- Tier 1: 5 Gear Shards
- Tier 2: 10 Gear Shards + 2 Radiant
- Tier 3: 15 Gear Shards + 5 Radiant
- Tier 4: 20 Gear Shards + 10 Radiant
- Tier 5: 30 Gear Shards + 15 Radiant
The Build Failures
First build attempt: 3 errors.
Error 1: TRAINER_PIC_BOSS undefined
Claude assumed a “BOSS” trainer pic existed. It doesn’t. The fix: TRAINER_PIC_CHAMPION_RIVAL — the closest existing sprite to a mysterious powerful figure.
Lesson: Always check include/constants/trainers.h for valid trainer pic IDs. Intuitive names often don’t exist.
Error 2: OBJ_EVENT_GFX_PSYCHIC_M undefined
Same problem for NPC sprites. OBJ_EVENT_GFX_PSYCHIC_M doesn’t exist in the constants. Fixed to OBJ_EVENT_GFX_GENTLEMAN.
Lesson: Always check include/constants/event_objects.h. Available sprites are a small subset of what you’d expect.
Error 3: Script labels undefined
This was the big one. All 10 script files (5 scripts.inc + 5 text.inc) compiled individually but the linker couldn’t find any of their labels.
Root cause: The map build system auto-generates header.inc, events.inc, and connections.inc from map.json. But scripts.inc and text.inc are NOT auto-included. They must be manually added as .include directives in data/event_scripts.s.
@ In data/event_scripts.s — must be added manually:
.include "data/maps/RiftZone_Tier1/scripts.inc"
.include "data/maps/RiftZone_Tier1/text.inc"
@ ... repeat for all 5 tiers
This is the kind of gotcha that wastes hours if you don’t know about it. The build system handles 90% of map integration automatically. The remaining 10% is manual and undocumented.
Wild Encounters
Each tier has 12 corruption-themed species across land, water, and fishing encounters:
- Tier 1 (Lv46-55): Zubat, Golbat, Gastly, Haunter, Grimer, Koffing
- Tier 2 (Lv50-60): Golbat, Haunter, Grimer, Muk, Koffing, Weezing
- Tier 3 (Lv55-65): Haunter, Gengar, Muk, Weezing, Sableye, Dusclops
- Tier 4 (Lv60-70): Gengar, Muk, Weezing, Sableye, Dusclops, Banette
- Tier 5 (Lv65-76): Gengar, Dusclops, Banette, Absol, Misdreavus, Sneasel
All Dark/Poison/Ghost types. The corruption theme runs deep.
Resource Budget
After the Rift Zone, the remaining resources:
- Flags: 3 remaining (0x4AD-0x4AF)
- Trainer IDs: 20 remaining (748-767)
- IWRAM: ~92% (2.6KB for stack)
- EWRAM: ~99.7%
The GBA is nearly full. Every new feature from here needs to be surgically precise about resource usage.
By the Numbers
| Metric | Value |
|---|---|
| Commits | ~10 |
| Copilot requests | ~20 |
| Tool executions | ~186 |
| Sub-agents | 6 |
Next: 08 — What’s Next