Mod lifecycle
How a mod moves through the engine's boot sequence, and what it may legally do in each phase.
When your file runs
Your mod entry file runs during load, before the engine combines mod
content with the game's imported data. Put registry calls such as
mod.content.moves:patch(...) there. They describe the content the game will
use after the merge; they do not change a running game immediately.
The live game exists later. Use the game.ready event when an action needs the
live Game object, and touch mod.world only after that event. Events and
hooks are different: you may subscribe or wrap them in the entry file and they
run when their documented game moment occurs. Do not put every runtime action
inside game.ready; use the event or hook that describes the moment you need.
For a first content change, see Tutorial 00.
For the exact capabilities on mod, see The mod object.
Phases
boot -> import -> discover -> load -> merge -> play -> save
- Boot.
conf.luasets the window; the engine version and mod API number come fromsrc/core/Version.lua, the single home of every compatibility-relevant number (engine semver, mod API major, link protocol, save format, cache generation). The launcher then boots one game:Gamefor Gen 1,Game2for Gold, Silver and Crystal,Game3for the Game Boy Advance games; each builds its own loader with the booted generation and version, which routes every registry (see Data Model). - Import. The game's
loadruns the ROM importer's cached data throughData:load()(Gen 3 binds its generated tables withSchemas.bindGen3). Mods never participate in extraction; they see the world only after it exists. - Discover. The loader scans the virtual
mods/root (source tree and save directory fused) and validates eachmanifest.json. A bad manifest skips that mod with a logged reason; boot continues. The engine then registers its own vanilla records into the behavioral registries under the ownerengine(src/mods/Builtins.lua) - before any mod runs, so a mod'sregisteron a vanilla id collides and must sayoverride. - Load. Manifest enforcement runs first: the manifest
apiagainst the engine'smodApi, enginegame_versionranges, thegameslist against the booted game (a mod that does not claim it is skipped, not failed), hard dependencies (present, enabled, in range, and not scoped to another game), conflict declarations, missingrequired_imports, and dependency cycles, each failing only the mods involved. Entry chunks then run in dependency order (dependencies and optional dependencies first; then a custom cart'sload_order, the player's launcher load order,priorityascending, and finally id), each inside its own sandbox (src/mods/Sandbox.lua): a private_G, compat stand-ins for the removed globals, and no reach into the engine's namespace. Each chunk runs under a rollback journal: if it errors, every registration it made and every subscription it took are undone, so a broken mod leaves zero residue. - Merge. Registered content folds over the imported base data, one
registry at a time, shallowest target first (
Loader:load, merge loop). Each registry lands in the table its generation routes it to. A cross-reference pass then resolves id-typed fields against the merged world and reports dangling references. After the merge the content registries freeze:register/override/patch/removeraise from then on, including from inside amods.loadedlistener. Register content at entry-chunk time. Asset transforms run here too, and the merged mod set becomes the asset search path. - Play.
game.readyfires with{ game = Game }once every service is up: readingev.gameis the sanctioned way to reach the live game object (Game,Game2orGame3;mod.gameresolves the same one). From here the game runs; mods observe it through events and hooks. - Save. Saving and loading pass through the save model: meta stamping, per-mod namespaces, migrations, and the validation/quarantine pass.
Unsealed buses
The event and hook buses are never sealed. A mod may subscribe during its
entry chunk, inside a mods.loaded or game.ready listener, or at any
point later - from inside a battle callback if it wants. Only content
freezes at the merge boundary; behavior stays open for the life of the
process.
Every listener and hook link runs under pcall. A callback that raises is
logged against the mod that subscribed it and skipped; the engine path that
emitted the event or called the hook always completes, and a failing hook
link degrades to "not installed for this call"
(Events and Hooks).
Lifecycle events
| Event | Fires | Payload |
|---|---|---|
mods.loaded |
end of the merge phase | { loader, data } |
game.ready |
after services init, before the first screen | { game } |
Both fire exactly once per boot, with or without mods installed. Under
POKEPORT_DEV=1, a hot reload re-emits game.ready after re-merging
(src/dev/HotReload.lua).
What runs when - quick table
| You want to | Do it |
|---|---|
| Register/override/patch/remove content | entry chunk only |
| Subscribe to events, wrap hooks | any time |
Reach the live Game |
game.ready payload |
Touch the overworld (mod.world) |
after game.ready |
| Read another mod's exports | mod.find(id) - after that mod loaded |
Poll mod.fetch / mod.job handles |
any time; poll never blocks, and handles are released on unload |
Start here
Playing
Tutorials
- Choose a tutorial
- 01 Sprite and Text Tweak
- 02 Balance Patch
- 03 New Species
- 04 New Item and Ball
- 05 New Move
- 06 NPC and Dialogue
- 07 New Map
- 08 Quest
- 09 Custom Music
- 10 New Mechanic
- 11 Custom UI Screen
- 12 Mini Total Conversion
Creative guides
Concepts
Recipes and reference
Author and developer tools
Home · Start Playing · Glossary · Troubleshooting · Repository · Community
Game data is imported locally from a supported ROM supplied by the player. Shared mods contain original work or transform recipes, never extracted game content. Check each guide's target game before using its examples.