19 — Playing My Own Game
A dedicated play session turned up a list of things to fix.
The Bug Report
The last few sessions were heavy on infrastructure — CI tests, offset verification, code audits. Good work, but not the kind where you play through the game. I reserved this session for that. New game, walked around, triggered battles, opened menus. Within ten minutes I had a list.
The first item: the Town Portal crashed the game. Not a soft crash — hard. The debug warp worked because that path calls SetMainCallback2(CB2_LoadMap) before fading. The EMBER submenu portal skipped that call. One-liner fix, but it only surfaces when you use the feature end to end.
Second item: a level 13 Meowth as my second wild encounter out of Pallet Town. Treasure Pokémon — a 5% chance encounter with an Epic+ gear drop — were spawning from the start. The player has no business fighting a level 13 anything on Route 1. I added a 2-badge gate. Now they show up around Vermilion, which is about right.
Third item: the EMBER stats panel was cluttered. STREAK, GEAR, and a 0/3 QUESTS counter were visible from the first step out of the lab. None of those mean anything at the start. STREAK needs battles. GEAR needs items. QUESTS shouldn’t show a counter when you have no quests. Now each element only appears when it has something to show.
Launching the Wiki
The site has had a devlog since day one, but nothing explaining how the game works. A new player (or my own future self) would have no idea what gear rarities exist, how corrupted zones work, or what the Altar of Recrafting does.
I wrote 9 wiki articles covering the core systems: gear, relics, shards, combat mechanics, the loot economy, corrupted zones, quests, merchants, and the Rift. Combined them with the devlog under a single site with unified header nav. Both sections share the same URL with tabs to switch between them.
The wiki doesn’t need to stay in sync with every commit — it’s a snapshot, not live documentation. I’ll update it when systems change.
Getting Out of the Bedroom
The vanilla FireRed opening is a seven-minute cinematic: wake up, watch TV, walk down stairs, trigger Oak on Route 1, get stopped, walk back, sit through a four-person dialogue about choosing starters. Every single time you start a new game.
I cut all of it. New game now starts in Oak’s Lab, directly in front of the three Poké Balls. One custom dialogue box — “Ancient Ember awaits” — then you pick your starter. The bedroom, the Route 1 trigger, the lab walk-in animation, Oak’s four-box speech, the rival’s interruption: all gone.
This required flag surgery. The EventScript_ResetAllMapFlags call that fires on new game sets FLAG_HIDE_OAK_IN_HIS_LAB, so Oak would be invisible without an explicit FlagClear right after. I removed the outdoor Oak coord events from map.json (source of truth, not the generated .inc file). The starter scene had two paths — the cinematic one I removed, and the national dex scene used in post-game. Those shared movement scripts, so the national dex path had to survive the gutting.
222 lines removed.
No Tutorial Battle
The first rival battle in Oak’s Lab has a special mode: BATTLE_TYPE_FIRST_BATTLE. When set, Oak narrates the fight with voiceover prompts (“Inflicting damage is key!”), crits are suppressed, accuracy checks auto-pass, and running is blocked with a Birch speech. It’s a full tutorial harness bolted onto the battle engine.
I removed it. The rival battle is now just a normal trainer battle. You fight, you win or lose, life goes on.
Removing the flag required touching eight files — battle_setup.c, battle_controller_player.c, battle_controller_opponent.c, battle_controllers.c, battle_main.c, battle_script_commands.c, battle_bg.c, and a battle script. The dead branches in battle_controller_oak_old_man.c came down in a follow-up audit.
The Dead Code Audit
With the tutorial gone and BAG moved out of the start menu, I swept for dead code.
From the BAG removal: STARTMENU_BAG enum entry, its action table row, StartMenuBagCallback, the help text string, the extern declaration. All gone.
From the tutorial removal: BATTLE_TYPE_FIRST_BATTLE branches across battle_controller_oak_old_man.c (9 dead conditionals). Then the functions those branches called: OakOldManHandleInputChooseMove, three PrintOakText_* functions, BtlCtrl_OakOldMan_TestState2Flag/SetState2Flag. Then the party menu tutorial chain: 10 Task_FirstBattleEnterParty_* functions, their helper functions, the Oak voiceover window template, two Oak speech strings. Then the declarations, the flag defines, the bit definition itself.
529 lines removed in a single commit. The BATTLE_TYPE_FIRST_BATTLE bit is now vacant — a gap between bit 3 (TRAINER) and bit 5 (LINK_IN_BATTLE).
Hold to Scroll
Every list UI in the game — gear bag, forge, altar, merchant, chest, portal, Pokédex — required a separate button press per row. On a 40-item gear bag, that’s exhausting.
GBA’s gMain.newAndRepeatedKeys fires on fresh press, then again after a 40-frame delay (≈0.7s), then every 5 frames (≈83ms) while held. I replaced JOY_NEW(DPAD_UP/DOWN) with a JOY_REPEAT macro across 8 files. Eight sed commands.
BAG in the EMBER Menu
The start menu had both BAG and EMBER sitting next to each other. BAG opened the item bag. EMBER opened a submenu with gear-specific tools. It made more sense to put BAG inside EMBER — one fewer top-level menu item, and the two bag options live together.
EMBER submenu now has five items: GEAR BAG, BAG, CODEX, PORTAL, DEBUG — all caps, consistent with GBA menu convention. The submenu window grew from 8 tiles tall to 10 to fit the extra item, and the stats panel’s VRAM baseBlock was shifted from 0x26D to 0x281 to avoid overlap.
By the Numbers
| Metric | Value |
|---|---|
| Commits | 16 |
| Copilot requests | 18 |
| Tool executions | 537 |
| Sub-agents spawned | 3 |
16 commits. Time to play it some more.
Back to README