Files
gen1recomp/docs/blue-version.md
2026-07-17 20:30:02 -04:00

5.0 KiB
Raw Permalink Blame History

Building a Blue Version of the port

pokered builds Red and Blue from one source tree: the Makefile assembles everything twice with rgbasm -D _RED or -D _BLUE, and every in-game difference sits in an IF DEF(_RED) / IF DEF(_BLUE) block. Our extraction pipeline resolves those conditionals the same way (tools/extract/util.py ASM_DEFINES), so most of a Blue build falls out of re-running the extractors. Only three things are hand-ported on the Lua side and need swapping by hand.

What differs between Red and Blue

Where (pokered) What
data/wild/maps/*.asm (34 files) Version-exclusive encounters (Red: Ekans, Oddish, Growlithe, Mankey, Scyther, Electabuzz, Blue: Sandshrew, Bellsprout, Vulpix, Meowth, Pinsir, Magmar)
data/pokemon/title_mons.asm The 16 title-screen Pokémon
engine/movie/title.asm + gfx/version.asm The "Red Version" / "Blue Version" ribbon
constants/player_constants.asm Preset names (Red: RED/ASH/JACK + BLUE/GARY/JOHN)
data/sgb/sgb_palettes.asm, sgb_border.asm Super Game Boy palettes and border
data/events/prizes.asm, prize_mon_levels.asm Game Corner prize mons, costs and levels
engine/movie/intro.asm, data/credits/credits_text.asm, engine/slots/slot_machine.asm, engine/battle/animations.asm, audio/sfx/save_3.asm Small gated tweaks (credits say "BLUE VERSION STAFF", etc.)

Step 1, flip the extraction define

tools/extract/util.py:

ASM_DEFINES = {"_RED"}   # -> {"_BLUE"}

Then regenerate everything (same as scripts/setup.sh does):

cd tools
../.venv/bin/python3 build_data.py \
    --pokered /Users/bryanbassett/Documents/development/pokered \
    --out ../data/generated --assets ../assets/generated

This alone switches the wild encounters, preset names, SGB palettes and credits text. Caveat: parse_preset_names sanity-checks in tools/extract/field.py:1193 expect "RED" in the player presets, relax that check for a Blue build (Blue's presets are BLUE/GARY/JOHN for the player and RED/ASH/JACK for the rival).

tools/extract/palettes.py uses its own raw reader with hardcoded IF DEF(_BLUE) skipping (around line 44), invert that too, or port it to read_asm so ASM_DEFINES covers it.

Step 2, title screen (hand-ported)

src/ui/TitleState.lua:

  1. Ribbon art. The extractor writes assets/generated/title/red_version.png; add blue_version.png to gfx.extract_title in tools/extract/gfx.py (source: gfx/title/blue_version.png, 64×8). In TitleState:draw(), the Red strip needs two quads (tiles 01 "Red", skip, tiles 59 "Version", title.asm VersionOnTitleScreenText). Blue's strip prints its tiles contiguously (db $61..$68), so draw the whole 64×8 image at px (56, 64) and verify with the title driver screenshot.

  2. Title mons. Replace CYCLE_SPECIES with Blue's list from data/pokemon/title_mons.asm:

    local CYCLE_SPECIES = {
      "SQUIRTLE", "CHARMANDER", "BULBASAUR", "MANKEY", "HITMONLEE",
      "VULPIX", "CHANSEY", "AERODACTYL", "JOLTEON", "SNORLAX",
      "GLOOM", "POLIWAG", "DODUO", "PORYGON", "GENGAR", "RAICHU",
    }
    

Step 3, Game Corner prizes (hand-ported)

data/scripts/story3.lua (~line 209) carries the Red prize tables. Blue's values (prizes.asm + prize_mon_levels.asm):

Prize Cost Level
ABRA 120 6
CLEFAIRY 750 12
NIDORINO 1200 17
PINSIR 2500 20
DRATINI 4600 24
PORYGON 6500 18

(TM prizes are identical in both versions.)

Step 4, verify

luajit tests/run_tests.lua

Plus two spot checks:

# every grass table must have exactly 10 slots (a conditional-parsing
# regression shows up as 1920 slots)
luajit -e 'local e=dofile("data/generated/encounters.lua")
for m,d in pairs(e) do if type(d)=="table" and d.grass and #d.grass.slots>0
  and #d.grass.slots~=10 then print("BAD",m,#d.grass.slots) end end'

# a Blue exclusive should now appear (and Growlithe should not)
grep -c VULPIX data/generated/encounters.lua
grep -c GROWLITHE data/generated/encounters.lua

Title screenshot: run the driver in tests/drivers/ style (SHOT_DIR=... POKEPORT_DRIVER=... love .) and eyeball the ribbon, title mon, and copyright row.

What you get for free / what to skip

  • Free after re-extraction: encounters (incl. Super Rod groups), SGB palettes, preset names, credits text, the gated sfx/animation tweaks.
  • Trades, gift Pokémon, story scripts, gym data: identical in Western Red/Blue, nothing to touch.
  • Save files: a Red save loads fine, but dex AREA nests and new encounters will be Blue's. Trainer parties are identical.

Making it a runtime toggle instead

If you want one build with both versions, extraction would need to emit both branches keyed by version (e.g. slots / slotsBlue) and the three hand-ported spots would read a save.version or conf.lua flag. That is a bigger change than the rebuild above, the flip-and-regenerate route needs no engine changes at all.