thibautbus 934a4c55ca Route a handful of pokered dialogue labels through game.data.text
Several hand-ported scripts carry pokered dialogue as inline English
literals instead of reading game.data.text, because the real ROM
label was never reachable from data/generated/text.lua:
ViridianCityYoungster2OkThenText/CaterpieAndWeedleDescriptionText,
TMNotebookText, the SS Anne kitchen cook's three dish lines, and the
Viridian fisher's pre-gift line (data/scripts/story5.lua's gift()
already read t[label] here, just had a stale comment and a missing
fallback).

Traced the actual cause carefully -- there are two independent,
differently-behaved label scanners in this codebase:

- tools/extract/text.py's parse_text_file() requires a label to
  start with "_" to be collected. This is a real bug (confirmed
  against a real pret/pokered checkout), but this function has no
  callers anywhere in the tree and no __main__ entry point -- it
  looks like dead code left over from an earlier version of the
  pipeline.
- The function that actually produces the shipped label list is
  text_metadata() in tools/make_rom_manifest.py, which feeds
  manifest["text"]["labels"], which build_rom_data.py's
  extract_text() iterates to decode each label straight from the
  ROM. text_metadata() already uses the permissive regex (no "_"
  requirement) since commit 0f581e2f.

So the actual blocker is that the committed tools/rom_manifest.json
was stale relative to text_metadata()'s current code, not a source
bug. Verified by rebuilding pret/pokered from source with RGBDS
(reproducible -- the resulting pokered.gbc/pokeblue.gbc hash to the
same canonical SHA-1s gen1recomp already pins, so no cartridge dump
was involved anywhere here) and running the real, unmodified
make_rom_manifest.py against it: 2595 labels against the committed
manifest's 2585, a clean superset containing everything these
scripts need. SilphCo2FSilphWorkerFPleaseTakeThisText is the one
exception already in the manifest -- confirmed by commit 0f581e2f
("so many bugs i cannot even breathe") that it was hand-patched in
exactly this same targeted way, for issue #393.

Fix:
- tools/extract/text.py: relaxed parse_text_file()'s regex to match
  text_metadata()'s, for consistency (no effect on what ships, since
  nothing calls this function, but no reason to leave a legacy copy
  of the same scanner out of sync).
- Four scripts read the real label first (t[label] or fallback, the
  established pattern): celadon_eevee.lua, ss_anne_kitchen.lua,
  viridian_city.lua, story5.lua (comment/fallback only, lookup was
  already correct).
- tools/rom_manifest.json and tools/rom_manifest_blue.json:
  regenerated for real -- both files are the direct, unedited output
  of running make_rom_manifest.py/make_blue_manifest.py against a
  real pret/pokered checkout at POKERED_REVISION, not hand-assembled
  or reverse-engineered to match. Only safe because of the two fixes
  below, which exist specifically so a real run doesn't regress
  anything the previously-committed files had. Diffing a real
  make_rom_manifest.py run against that previous file (1143 lines
  out of 45253) found exactly what a naive "just regenerate against
  whatever pokered HEAD is handy" would have silently broken:
  - pret/pokered commit 079d1cc92fc3b0ec82bc1418c2b4045bfca84620
    (PR #596, 2026-08-06) renamed
    _SilphCo10FGiovanniILostAgainText/_SilphCo10FPorygonText to
    _SilphCo11F... (they live in text/SilphCo11F.asm, Giovanni's
    floor). data/scripts/victories.lua:183 still hardcodes the old
    name, and extracting under pokered's new name would silently
    blank Giovanni's "I lost again!?" rematch line. Fix is the pin
    below: POKERED_REVISION is pinned to the last commit before this
    rename, so today's generator output matches victories.lua
    natively, no engine code touched, no generator-side workaround
    either. Advancing the pin past this commit is a real, welcome
    future upgrade -- it just needs victories.lua's labels (and
    anything else's) fixed up in the same change.
  - trainerPartyOverrides.OPP_CHIEF (Giovanni's Celadon gym team)
    wasn't produced by any code under tools/ at all. Traced why:
    pret/pokered's data/trainers/parties.asm has "ChiefData: ; none"
    -- the Celadon Chief's battle is unused/cut content in the
    original game, and RomExtractor.lua's own comment confirms
    gen1recomp reimplements it as a real fight using a hand-authored
    party for exactly that reason -- no pokered commit, old or new,
    will ever produce this data. Added a TRAINER_PARTY_OVERRIDES
    constant to make_rom_manifest.py so this survives every future
    regeneration automatically; verified it reproduces the committed
    value byte-for-byte and flows through to Blue/Yellow for free
    via their existing derive-from-Red path.
  - trainerHeaders.MtMoonB2F's Super Nerd slot is pre-existing
    fabricated data, not pokered drift: his event name,
    EVENT_BEAT_MT_MOON_3_SUPER_NERD, has never existed in pokered at
    any point in its history (the real name,
    EVENT_BEAT_MT_MOON_EXIT_SUPER_NERD, has been stable since 2015),
    nor in gen1recomp's own event_flags.lua; trainerDefeated()
    checks defeatedTrainers[npc.id] first, the same pattern already
    used for the Fighting Dojo's Karate Master, so this entry is
    very likely already inert. field.seafoam also differs from a
    fresh regeneration (two showObject boulder-toggle IDs in the B3F
    puzzle, a live gameplay system nobody has verified either value
    against), and field.tradeArt is new content a fresh extraction
    produces that was never shipped. None of those three are this
    PR's problem to fix, but a real generator run has to do
    something with them regardless -- so make_rom_manifest.py gets a
    new apply_known_nonreproducible_overrides(), called right after
    text_metadata()/field_metadata(), that pins MtMoonB2F and
    seafoam back to what was already shipped and drops tradeArt,
    each with a comment explaining why and what the real fix looks
    like (MtMoonB2F needs a Data:seedMtMoonB2FSuperNerd()-style
    engine seed, not manifest data). Verified this override function
    was complete and correct -- diffed a real run against the
    previously-committed file first, empty -- before trusting it to
    write tools/rom_manifest.json/_blue.json directly; both are now
    literally that generator's output, not hand-assembled.
    tools/rom_manifest_yellow.json isn't touched by this PR at all:
    it already had all ten labels, and make_yellow_manifest.py has
    no matching override yet for its own field.oldManBattle outlier,
    so regenerating it for real isn't safe the same way yet.
- tools/make_rom_manifest.py: added a POKERED_REVISION pin --
  cf621a76d4941c93c078eb38e0880fe8db48ef40, the last pret/pokered
  commit before the Silph Co rename above, chosen deliberately
  rather than current HEAD -- and a check_pokered_revision() guard
  main() calls before generating: fails loudly if --pokered isn't
  at that commit instead of silently absorbing whatever upstream
  renames or restructures since, with an explicit
  --allow-revision-mismatch escape hatch for a deliberate pin bump.
  That's the intended way this pin moves forward: diff a fresh run
  against the committed manifest, fix up whatever engine code
  depends on by exact name, and bump POKERED_REVISION in the same
  change -- a conscious, reviewable decision instead of a silent
  contributor default. Wired the same guard into
  make_blue_manifest.py and make_yellow_manifest.py for their own
  --pokered/--pokeyellow checkouts; make_yellow_manifest.py gets its
  own POKEYELLOW_REVISION (e6ba56989b0f2694f393e6924820be11dcc1fbb8,
  verified here). The pin and its guard live entirely in the
  generator source, not also embedded as a field in the shipped
  manifests -- that would be redundant with the .py constant next to
  it in the same commit, for no protection the guard doesn't already
  give.
- tools/make_rom_manifest.py, make_blue_manifest.py,
  make_yellow_manifest.py: switched json.dump(..., ensure_ascii=
  False, ...) to ensure_ascii=True to match how the committed
  manifests were actually encoded (escaped \uXXXX rather than
  literal UTF-8). Purely cosmetic -- json.load parses both
  identically -- but needed so that a fresh make_rom_manifest.py run
  at POKERED_REVISION now produces tools/rom_manifest.json
  byte-for-byte (plain diff empty), not just content-equal. One
  small, pre-existing cosmetic mismatch remains and wasn't chased: a
  single nested dict, field.cardKeyDoors.doors, has its
  SILPH_CO_10F/11F keys in natural floor order in the committed file
  instead of the sort_keys=True lexicographic order everything else
  in the file uses -- same content either way.
- tests/rom_manifest_generator_test.py: new ROM-free unit tests (same
  style as the existing tests/build_rom_data_cli_test.py, wired into
  scripts/test.sh as a T0 tier) for check_pokered_revision() and
  apply_known_nonreproducible_overrides() -- matching/mismatched/
  bypassed/unresolvable-checkout revision cases, and that the
  MtMoonB2F/seafoam/tradeArt overrides land correctly (including
  alongside a populated map entry, and without erroring when
  tradeArt is already absent). Doesn't replace the manual real-ROM
  verification above, which needs an actual pokered/RGBDS toolchain
  -- but a future typo or logic slip in either function now fails
  immediately instead of only surfacing next time someone happens to
  redo that manual check.
- Left data/scripts/flavor/silph_co_9f.lua's nurse dialogue (labels
  also added to both manifests here) untouched code-wise: static
  command table, not a function, needs its
  face_player/heal_party/fade state machine restructured to use
  t[label] safely, and show_text's un-resolved-label fallback prints
  the label name literally rather than English -- not safe without
  interactive testing.

Checked Yellow's equivalent case (Melanie's House) since it looked
like the same shape: it isn't actually broken.
tools/make_yellow_manifest.py's YELLOW_EXTRA_TEXT_LABELS already
force-includes those eight labels, and a real built
dialogue_yellow.lua already has correct French translations for
them. tools/rom_manifest_yellow.json also already carries all ten
labels this PR adds to Red/Blue. No label changes needed there.

Tested: patched parse_text_file() against a real pret/pokered
checkout (+11 labels, 0 removed, all clearly dialogue-shaped);
rebuilt pokered.gbc/pokeblue.gbc with RGBDS at current pret/pokered
HEAD (confirmed hashes to gen1recomp's own canonical SHA-1s) and
diffed a fresh make_rom_manifest.py run there against the committed
manifest to map out what a full regen at HEAD would need; re-checked
out the same pokered checkout at cf621a76 (POKERED_REVISION),
rebuilt both ROMs again (same canonical hashes); with
apply_known_nonreproducible_overrides() and the ensure_ascii fix in
place, ran both generators to a scratch path first and diffed
against the then-committed manifests -- empty -- before running them
again writing tools/rom_manifest.json/_blue.json directly, so both
files are now the generator's literal, unedited output (git diff on
that final write: 18 lines moved in rom_manifest.json, exactly the
pre-existing SILPH_CO_10F/11F ordering quirk; zero lines changed in
rom_manifest_blue.json); rebuilt pokeyellow.gbc the same way, confirmed its
canonical hash, and confirmed make_yellow_manifest.py's symbols/
text/trainerHeaders/trainerPartyOverrides also come out byte-for-
byte identical to the committed tools/rom_manifest_yellow.json; ran
the real build_rom_data.py --only text against all three rebuilt
ROMs and confirmed every added label decodes from real ROM bytes
matching the English fallback literals exactly; verified
check_pokered_revision() actually raises on a deliberate mismatch
before relying on it to gate the runs above; python3
tests/rom_manifest_generator_test.py: 9/9 pass; luajit
tests/run_engine.lua: 250/250 suites pass.
2026-08-20 09:42:20 +02:00
2026-08-19 15:59:42 +02:00
2026-07-31 14:35:40 +00:00
2026-08-17 13:06:09 -04:00
2026-08-15 10:44:20 -04:00

Gen1Recomp

A native LÖVE2D recreation of Poke Red, Blue and Yellow. The engine and map behavior are hand-written Lua; game data and graphics are decoded from a ROM supplied by the player.

And before you say, "that's not a recomp", you're wrong. Recomp is an acronym. Reverse Engineering Causes Obsessive Mental Problems

Click Here for the AI Use Disclosure!

Caution

We are NOT affiliated with the website gen1recomp[.]com That website is not run by this project, was not authorized by us, and we have no idea who operates it. It is impersonating this project; do not download anything from it, and treat anything it hosts or claims as untrustworthy. Even if the site currently links back to this repository, the people behind it can change its content at any time, so nothing on it should ever be trusted. This GitHub repository and the Discord linked below are the only official sources for this project. Also, as I assumed would eventually happen, the idiot that made that website now pumped it full of adware. Please stay away from that website.

SUPPORT / ANNOUNCEMENTS / MODS: Discord

YouTube TikTok X Bluesky Instagram

As seen on Polygon As seen on KOTAKU As seen on Digital Foundry As seen on Android Authority As seen on XDA Developers

Watch the latest update video

Watch the latest update video

This project does not include a ROM, emulate the Game Boy, transpile assembly, or download a disassembly. A canonical US Poke Red, Blue, Yellow, or Gold ROM is the only game content input.

The ROM is verified, used during import, and then released from memory. It is not copied into the cache. Later launches load the private generated cache and do not ask for the ROM again. Red, Blue, Yellow, and Gold can all be imported side by side. Gold is Gen 2 Phase 1 (import + launcher; see docs/gold-phase1.md): the Gen 2 engine is still under construction.

Quick Start

Open the desktop app. On first boot, choose your legally obtained .gb / .gbc file or drop it onto the window. Import takes a few seconds and the game starts automatically.

Only the canonical US Red, Blue, Yellow (1 MiB), and Gold (2 MiB) ROMs are accepted. The importer verifies SHA-1 before creating any game data:

  • Red: ea9bcae617fdf159b045185467ae58b2e4a48b9a
  • Blue: d7037c83e1ae5b39bde3c30787637ba1d4c48ce2
  • Yellow: cc7d03262ebfaf2f06772c1a480c7d9d5f4a38e1
  • Gold: d8b8a3600a465308c9953dfa04f0081c05bdcb94

The packaged app contains neither a ROM nor pre-extracted game data. Music, sound effects, and cries are synthesized while the game runs from compact audio channel programs copied out of the verified ROM.

A note on Windows Defender warnings

Windows Defender sometimes flags the Windows build with a generic machine-learning detection such as Trojan:Win32/Wacatac!ml (#621). This is a known false positive: the exe is the official LÖVE runtime with the game archive appended (the standard way LÖVE games ship), and Defender's heuristics distrust unsigned executables with appended data. Every release publishes SHA-256 checksums (sha256sums.txt) so you can verify your download, and you can confirm a flagged file yourself on VirusTotal, where these builds come back clean on every engine except Defender's heuristic. False positives are reported to Microsoft as they come up.

Controls

Action Keyboard Controller
Move Arrow keys / WASD D-pad / left stick
A Z / Enter / Space A
B X / Backspace B
Start Escape Start
Select Tab / Shift Back / Select

Rebind any of these in-game under OPTIONS → CONTROLS. Controllers are supported out of the box.

Hotkeys

Key What it does
- / = Zoom out / in (overworld; also mouse wheel)
1 Cycle GAME SPEED up (controller: R2 faster, L2 slower)
2 Cycle COLORS
3 Cycle TILT (free-roam overworld)
4 Cycle ZOOM through every level (free-roam overworld)
5 Cycle GBC FX
F1 Save
F2 Load
F10 Open / close the mod manager

COLORS, TILT, ZOOM, GBC FX, GAME SPEED, and VOID FILL are also in the Options menu and persist in options.lua.

Low-end devices

OPTIONS → PERFORMANCE scales the port's optional extras for weaker hardware: HIGH (everything on), BALANCED (no 3D tilt or GBC FX), LOW (also no survey zoom, FPS capped), or AUTO — the default, which picks a tier from your device (ARM handhelds → LOW, phones → BALANCED, normal desktops → HIGH, unchanged). It only scales presentation; the fixed-step game logic is identical on every tier, and a lower tier hides your tilt/zoom/GBC-FX preferences without forgetting them. Details in docs/new-features.md.

Rulesets

OPTIONS → RULESET picks which set of Gen 1 battle behaviors to run. Both rulesets share the same damage formulas; they differ only in whether the original's quirks are kept. The setting persists in options.lua, and mods can register their own.

gen1_faithful is the default and reproduces the original cartridge, famous bugs included:

Rule Behavior
oneIn256Miss A 100%-accurate move still misses on a roll of 255
critUsesBaseSpeed Crit rate reads base speed, not the current stat
critIgnoresStages Crit rate ignores stat stages
focusEnergyBug FOCUS ENERGY quarters the crit rate instead of x4
enemyUnlimitedPP Enemies never spend PP, so they never Struggle
hyperBeamSkipRechargeOnKO HYPER BEAM skips its recharge when the target faints
randMin / randMax Damage random factor 217-255

modern_clean keeps the formulas but removes the notorious quirks:

Rule Behavior
oneIn256Miss Off: a 100%-accurate move always hits
critUsesBaseSpeed Unchanged: crit rate still reads base speed
critIgnoresStages Off: stat stages count toward the crit rate
focusEnergyBug Off: FOCUS ENERGY raises the crit rate as intended
enemyUnlimitedPP Off: enemies deplete PP and Struggle when empty
hyperBeamSkipRechargeOnKO Off: HYPER BEAM always recharges, like Gen 2+
randMin / randMax Damage random factor 217-255, same as faithful

Running From Source

Requires LÖVE 11.x. Place a Red, Blue, or Yellow ROM in the project folder and double-click Play-Mac.command or Play-Windows.bat, or run:

scripts/setup.sh --rom "/path/to/Poke Red.gb"   # or Blue.gb / Yellow.gbc
scripts/run.sh

then love . for later launches. Windows PowerShell scripts, the optional developer data build, test suites, and cache management are covered in Developer Setup.

Portable Mode

By default the game keeps your save, options, and the private ROM-derived data cache in your OS's normal per-user app data folder. To keep everything next to the game instead (handy for a USB stick or portable drive you carry between computers), drop an empty file named portable.txt next to the app (next to gen1recomp.app/.exe, or next to main.lua/conf.lua when running from source), then launch the game. Portable mode is desktop-only (Windows, Linux, macOS); it has no effect on Android or iOS, where the app runs from a read-only package.

With portable.txt present:

  • save.lua, save.lua.bak, and options.lua are read from and written to that same folder instead of the OS save directory.
  • A ROM import writes the generated data/generated and assets/generated cache straight into that folder too (nothing is left in the OS save directory), so a later launch reuses it without asking for the ROM again even on a different computer, as long as the same folder comes along.
  • Deleting portable.txt switches back to the normal OS save directory; nothing already written to either location is touched automatically, so copy files over yourself if you want to carry existing progress across the switch.

Launch Options

By default the app opens the launcher so you can pick a game. Launch options skip it and start one game directly, which is what you want for a one-click entry: a desktop shortcut per game, a Steam entry, or a handheld frontend.

Option Effect
--game=red boot Red, skipping the launcher (blue and yellow too, or just r / b / y)
--slot=2 load that save slot; takes a slot number or a slot id
--launcher open the launcher anyway, so you can edit a shortcut you already made

Linux on arm64 (Raspberry Pi)

Alongside the x86_64 gen1recomp-*-linux.zip, every release ships gen1recomp-*-linux-arm64.AppImage for 64-bit ARM desktop Linux — Raspberry Pi 4/5, Armbian and other SBC distros, and arm64 VMs on Apple Silicon:

chmod +x gen1recomp-*-linux-arm64.AppImage
./gen1recomp-*-linux-arm64.AppImage

LÖVE publishes no aarch64 binary of any kind, so this artifact compiles the engine — and SDL2, OpenAL and the codecs — from source inside a Debian bullseye arm64 container. It needs only glibc 2.29+, libstdc++, freetype and zlib on the host; OpenGL, X11, Wayland, KMSDRM, ALSA and PulseAudio are all dlopened, so the same image runs on a full desktop, a Wayland-only session or a KMSDRM handheld with no X server. Build instructions and the reasoning are in docs/linux-arm64-build.md.

iOS

Every release ships gen1recomp++-*-ios.ipa. Sideload it with AltStore (Windows or Mac) — see docs/ios-sideload.md. To build and install from source on a Mac instead, see docs/ios-install.md.

Add to SideStore   Add to Feather   Add to AltStore   Download from GitHub

Xbox Dev Mode

Every release ships gen1recomp-*-xbox-uwp.zip for Xbox One and Xbox Series consoles in Developer Mode. It cannot be installed in retail mode.

Extract the archive, then use Xbox Device Portal to install the .msix and the x64 package under Dependencies.

External setup

  1. Put your legally obtained Red, Blue, or Yellow ROMs on an external drive. Mod ZIPs can go on the same drive.
  2. Connect the drive to the Xbox and open Gen1Recomp.
  3. Select Import ROM or Import Mod, then choose the file with the Xbox file picker.
  4. Repeat the ROM import for each version you want to use.

Internal setup

  1. Create a folder named baseroms on your PC and place your legally obtained Red, Blue, or Yellow ROMs inside it.
  2. ZIP the folder, keeping baseroms at the top level of the archive.
  3. Launch Gen1Recomp once, then close it.
  4. Open Xbox Device Portal and upload the ZIP to Gen1Recomp/LocalState/pokemon-love2d/.
  5. Choose Yes when Device Portal asks whether to extract the archive.
  6. Open Gen1Recomp. The launcher checks baseroms once at startup. When it finds a compatible ROM, that games tab shows ROM FOUND and an Import detected ROM button.

ROMs, generated game data, saves, and mods remain in LocalState and are not included in the app.

Source builds and package details are covered in the Xbox UWP build notes.

Handhelds

A PortMaster-style port for the Anbernic RG34XXSP on Stock OS 64-bit MOD ships with every release as gen1recomp-*-rg34xxsp-stockos64-mod.zip. Install steps, controls, and troubleshooting live in docs/anbernic-rg34xxsp.md.

Nintendo Switch

Releases ship an SD-ready gen1recomp-*-switch.zip. Runtime target is pinned love-nx 11.5-nx1. Requires a console that can run Switch homebrew.

  • Players: docs/switch-install.md. Download the zip, extract at the microSD root (install or update), title-override launch, import your own legal ROM, Joy-Con controls and shortcuts.
  • Builders: docs/switch-build.md. --fetch / --loose / --fused, toolchain, Docker fallback, and CI vs release (path-gated ubuntu selftest, fused PR artifact on the main repo, release hard-fail).
  • File transfer (MTP / SD / FTP): docs/switch-transfer.md.

Modding

The game ships a native mod platform: content registries, events and hooks, per-mod saves and options, and an in-game manager. The full modding book — getting started, a twelve-rung tutorial ladder, a cookbook, and the generated reference — lives on the project wiki.

Shipped example mods, one per kind of author, live in [mods/](mods/).

Maps can be edited in our own build of Tiled, bryanthaboi/tiled_gen1recomp, and exported back out as a mod; see docs/tiled-map-editing.md.

Bugs

Found a bug? A warp dropping you somewhere it shouldn't, a battle doing math that looks wrong, text in the wrong box, anything that does not match the original game. Open a bug report. Attach a screenshot if you can. It saves a lot of back and forth, and if you can't get one, the form asks you to describe what you saw instead.

More

  • Link play — START > LINK connects two copies directly over UDP.
  • Save editor — edit party, boxes, items, events, and Pokédex flags outside the game.
  • docs/architecture.md — runtime details; docs/behavior-porting-notes.md — formula provenance; docs/link-security.md — what link play defends against, and what it doesn't.

Special Thanks

This project would not be possible without pret > the pret band of decompiling maniacs > and their pokered disassembly.

S
Description
No description provided
Readme 91 MiB
Languages
C 45.9%
Lua 25%
C++ 18.7%
Shell 2.4%
Makefile 1.5%
Other 6%