Files
gen1recomp/docs/architecture.md
T
2026-08-28 10:14:44 -04:00

7.7 KiB

Architecture

packaged first boot
user-provided Pokemon Red ROM
        |
        v
RomImporter + RomExtractor (Lua)
        |
        +--> private data/generated/*.lua
        +--> private assets/generated/**/*.png
        +--> private assets/generated/audio/programs.bin
        |
        v
LÖVE2D engine + ChipAudio

The importer validates the ROM SHA-1, decodes tables and graphics using bundled address/name metadata, and writes a private cache. It releases the ROM after import and does not copy it into the cache. Normal gameplay reads only the generated files.

tools/build_data.py is a separate Python/Pillow developer path that writes the same core data and graphics into the source tree for verification.

Runtime layout

Area Files Role
import src/import/RomImporter.lua first-boot UI, ROM validation, cache ownership
src/import/RomExtractor.lua ROM tables, text, pictures, PNGs, audio programs
core src/core/Game.lua service owner: data, input, renderer, stack, save
src/core/Data.lua loads data/generated/*, resolves TEXT_* pointers
src/core/ChipAudio.lua streams ROM music programs and synthesizes SFX/cries
src/core/FixedStep.lua 60 Hz fixed-step loop
src/core/Input.lua GB button abstraction, per-step edge detection
src/core/StateStack.lua stack of states; top updates, draws bottom-up from the last opaque state
src/core/SaveData.lua Lua-serialized save in the LÖVE save dir
render src/render/Renderer.lua 160x144 canvas, integer nearest scaling
src/render/TileRenderer.lua one SpriteBatch per map (8x8 quads) + border-block ring
src/render/SpriteRenderer.lua variable-size anchored sprite sheets, 6-frame walkers and flipped right facing
src/render/Font.lua glyph rendering via charmap (greedy longest match)
src/render/TextBox.lua dialogue box: typewriter, \n line, \v scroll, \f page
src/render/Camera.lua, Transition.lua follow camera, warp fades
world src/world/Map.lua cell queries: walkable/grass/door/warp/sign (bottom-left-tile rule)
src/world/MapLoader.lua generated def -> runtime Map, cached
src/world/Player.lua, NPC.lua grid movement, walk animation, wander AI
src/world/Collision.lua tile + entity + bounds checks
src/world/Warp.lua arrive-on-door and walk-off-edge warp rules, LAST_MAP
src/world/Encounter.lua Gen 1 encounter rate + slot buckets
src/world/OverworldController.lua the overworld state: input, interactions, connections, encounters
script src/script/ScriptRunner.lua coroutine executor for command lists
src/script/Commands.lua show_text, flags, battles, warps, movement, objects...
src/script/Flags.lua named event flags in the save
pokemon src/pokemon/* instances, Gen 1 stat calc, growth curves, party
battle src/battle/BattleState.lua battle flow + menus + message queue
src/battle/Damage.lua Gen 1 damage/crit/accuracy formulas
src/battle/TypeChart.lua, TurnOrder.lua, Status.lua, MoveEffects.lua subsystems
src/battle/Experience.lua, Catching.lua, TrainerAI.lua exp/levels, Gen 1 catch algorithm, AI
src/battle/rulesets/ gen1_faithful (default) vs modern_clean
ui src/ui/* start menu, generic menu, yes/no box, party/bag lists
tools/save-editor/ Save editor: shipped in every build, opened from the launcher's Edit button or standalone with love . --editor

Online play

Online play is owned by the launcher, not by a running game. src/online/ holds one persistent relay connection for the life of the process (main.lua pumps it every frame, whether the launcher or a game is on screen), and a battle is run by arena booting the game: no splash, no title, no overworld, just the lockstep battle, then straight back to the launcher with the room still selected. In-game link (src/link/LinkState.lua) stays as it was and is LAN only. The relay lives in its own repo, ../pokeserver; docs/link-security.md describes protocol v2 and what it does and does not guarantee.

  • src/online/Client.lua - process singleton: connection, heartbeat, reconnect with session resume, the inbox, and the local model (presence, room, match, tournament). Client.roomSession() hands LinkBattle the same shape a LAN Session does.
  • src/online/Protocol2.lua - relay protocol v2 message builders; the matching schemas live with the v1 ones in src/link/Wire.lua.
  • src/online/ArenaData.lua - computes an ArenaProfile (engine, version, engine/api version, fingerprint, ruleset, vanilla or sealed cart) headless by mounting a version's cache, and compares two profiles (equal, describeMismatch).
  • src/online/ArenaBoot.lua - the ArenaSpec: profile, role, slot, team, seed, parties, session, onDone; plus the battle options a spec turns into.
  • src/online/TeamPick.lua - headless slot read, rule validation and party packing. src/online/Convert.lua - Gen 1 <-> Gen 2 mon conversion with Time Capsule refusals. src/online/Trade.lua - launcher-side trade with a two-file commit. src/online/OnlineSprites.lua - the cached party icons and front sprites those pickers draw.
  • Boot path: main.lua bootGame(version, cartId, { arena = spec }) -> Game:load / Game2:load skip the intro and push src/ui/ArenaState.lua or src/ui/gen2/ArenaState.lua, which build src/link/LinkBattle.lua (Gen 1) or src/link/LinkBattle2.lua (Gen 2), host, guest or spectator, and return the result.
  • Mods in an arena: Loader:load(data, { mode = ... }) runs disableAll (verified translations only) for a vanilla arena and cartOnly for a sealed-cart one, without touching the player's saved enable state.
  • src/import/OnlinePanel.lua plus src/import/online/ are the launcher's ONLINE tab: a small stack of screens (home, play, setup, room, watch, tournaments, trade) drawn from Client, ArenaData and TeamPick.

Map scripts

Map-specific behavior lives in data/scripts/<map>.lua, keyed by the TEXT_* constants from the map's object events. The engine dispatches a talk interaction to (in order):

  1. a hand-ported script in data/scripts/ ({ talk = { TEXT_X = {...} } }),
  2. the generic trainer path (object has trainer args from object_event),
  3. the extracted plain text via the map's text pointer table.

Scripts are arrays of { "command", args... } rows executed by a coroutine so show_text, ask, start_battle, warp, wait block naturally. Every hand-ported script cites its pokered source file.

Coordinates

  • block: 32x32 px, the unit of .blk layouts (map.width/height)
  • cell: 16x16 px walk grid, the unit of all object/warp coordinates
  • tile: 8x8 px graphics; a cell is 2x2 tiles, a block 4x4

A cell's behavior (collision, grass, door, warp tile) is decided by its bottom-left 8x8 tile, matching the original engine's "tile at the sprite's feet" checks.

Verification

  • luajit tests/run_tests.lua - headless behavior suite over real generated data (collision, warps, text, stats, damage, growth, type chart, encounters, a full scripted battle, save round-trip) using a love API stub.
  • luajit tests/run_save_editor_tests.lua (plus the task-specific suites)
    • save editor pure logic and panel click tests.
  • POKEPORT_AUTOPILOT=1 love . - scripted end-to-end run (walk Pallet Town, read the sign, enter Oak's Lab, take a starter, beat the rival, exit, cross into Route 1, win a wild battle) that captures screenshots.
  • POKEPORT_DRIVER=tests/drivers/audio_runtime_test.lua love . - imports and queues title music, a sound effect, and a Pokemon cry.