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()handsLinkBattlethe same shape a LANSessiondoes.src/online/Protocol2.lua- relay protocol v2 message builders; the matching schemas live with the v1 ones insrc/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.luabootGame(version, cartId, { arena = spec })->Game:load/Game2:loadskip the intro and pushsrc/ui/ArenaState.luaorsrc/ui/gen2/ArenaState.lua, which buildsrc/link/LinkBattle.lua(Gen 1) orsrc/link/LinkBattle2.lua(Gen 2), host, guest or spectator, and return the result. - Mods in an arena:
Loader:load(data, { mode = ... })runsdisableAll(verified translations only) for a vanilla arena andcartOnlyfor a sealed-cart one, without touching the player's saved enable state. src/import/OnlinePanel.luaplussrc/import/online/are the launcher's ONLINE tab: a small stack of screens (home, play, setup, room, watch, tournaments, trade) drawn fromClient,ArenaDataandTeamPick.
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):
- a hand-ported script in
data/scripts/({ talk = { TEXT_X = {...} } }), - the generic trainer path (object has trainer args from
object_event), - 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
.blklayouts (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 aloveAPI 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.