Compatibility
Four separate compatibility questions, four separate answers: which mod API a mod speaks, which engine it needs, which other mods it can sit beside, and whether two linked games agree.
What a new mod needs now
Start with "api": 2 in a new manifest, or "api": 3 for a mod that runs
on Gen 1, Gen 2 and Gen 3 from one entry file. The other compatibility fields
solve specific later problems:
| If your mod does this | Read / declare |
|---|---|
| Uses the current documented API | api: 2 |
| Runs one source on every claimed game | api: 3 and games (Portable API 3) |
| Targets particular games | games (gen1, gen2, gen3, a version id, or all) |
| Needs an engine feature unavailable in older releases | game_version |
| Calls another mod's public exports | dependencies or optional_dependencies |
| Cannot coexist with another mod | conflicts |
| Will be used during player link play | package/select a sealed custom cart and use launcher link setup |
You can leave out a field that does not describe a real need. The sections
below give the exact rules and version grammar. affects_link is low-level
metadata, not a way to make a freely enabled mod eligible for player link play.
API level
api in the manifest picks the contract. Absent means 1, which is what
every mod written before v2 says by omission.
api |
Meaning |
|---|---|
| 1 | Lenient. The whole mod object, but a value that violates a registry schema, an unknown profile or permission, or an unknown games token is an attributed warning, not an error. |
| 2 | Strict. The same surface; those violations are load errors. Declaring api: 2 is how you ask for the errors. |
| 3 | Portable opt-in. Strict like 2, and mod.content, mod.events, mod.hooks, mod.world and mod.battle become portable services that share names, record shapes and units across Gen 1, 2 and 3. See Portable API 3. |
An api 1 or 2 mod behaves exactly as before on an engine that provides 3.
api higher than the engine provides is refused outright with
requires mod API <n>; this engine provides <m>. The engine's number
(modApi, currently 3) lives in src/core/Version.lua and moves only on a
breaking change to the mod surface.
Which games
games in the manifest lists where the mod runs: generations (gen1, gen2,
gen3), the Hoenn family (rse), version ids (red, blue, yellow,
gold, silver, crystal, firered, leafgreen, ruby, sapphire,
emerald) or all. A manifest with no games is Gen 1 only; the legacy
gen2compat: true adds the Gen 2 games and never claims Gen 3. A mod that
does not claim the booted game is skipped there with For <games>, not <game>, not failed. Claim a game only after testing on it; the per-game
availability of every registry, hook and event is in the
Gen 2 compatibility notes,
Reference: Registries, Reference: Hooks
and Reference: Events.
The sandbox holds to the same promise: a mod written before it landed
keeps loading, because the globals it took away answer through compat
stand-ins (src/mods/LegacyCompat.lua) that reroute into mod.storage
and a private per-mod overlay. Each stand-in call logs one warning naming
its replacement, and loader:legacyReport(modId) lists them with call
counts. The stand-ins are a compatibility floor, not a second storage
system; migrate to the mod object surface.
Engine version
game_version is a semver range against the engine release, checked
before the entry chunk runs - a mod that needs a newer engine is refused
with needs game version <range>, engine is <version> instead of loading
and breaking in the middle. Leave it out if you have no reason to pin.
The engine release is deliberately not a compatibility wall for durable
data: a mod.checkpoints checkpoint records the engine that captured it
as metadata (identity.engineVersion), and restore treats that as
metadata rather than runtime state, so a checkpoint captured on an older
release restores on a newer one (src/core/Checkpoint.lua).
Other mods
dependencies, optional_dependencies and conflicts are enforced, and
each entry may carry a version range ("colorlib@^1.2"), a GitHub repo
hint the launcher can download from ("colorlib@^1.2#owner/repo"), and,
in the object form, a games list that scopes enforcement to the games
that need it. Failure is per-mod: whatever cannot be satisfied is refused
with a reason the manager shows, and everything else still loads. See the
manifest reference for
entry formats, resolution rules and the range grammar.
Reaching another mod's code goes through mod.exports and mod.find:
return function(mod)
-- hard dependency: guaranteed loaded before this mod runs
local color = mod.find("colorlib").exports
-- optional dependency: declare it for the ordering, then branch
local radio = mod.find("radio")
if radio then radio.exports.addStation("NIGHT_FM") end
mod.exports = { hour = function() return 12 end }
end
find returns { id, version, exports }, or nil when the other mod is
absent, disabled or failed. Treat another mod's exports as a versioned
public API: range-check handle.version (src/mods/Semver.lua is a
supported require) before relying on its shape.
Link play
Set up player link play in the launcher. Choose either a vanilla cart or a sealed custom cart. A sealed cart runs its fixed, pinned mod set; it is the supported way for a link session to use modded content. Freely enabled loose mods - including cosmetic and translation mods - are not a supported link setup.
Do not break a cart seal to make a link session work. Return to the launcher,
select the intended vanilla or sealed cart, and start the link there.
affects_link, a matching fingerprint, or a shared loose mod set does not
turn arbitrary enabled mods into a supported player link configuration.
Cart protocol note
The engine computes a fingerprint and handshake data for the selected cart.
The low-level metadata includes affects_link and link_fields; engine code
currently names outcomes including full, vanilla_peer, subset, and
refused. Those values describe the cart protocol, not a player-facing choice
to link with freely enabled mods. Treat link_fields and the
link.fingerprint hook as low-level compatibility surfaces.
Your mod's own ledger
The base game keeps three parity ledgers, and
docs/known-differences.md reads "None currently." - mods must not
invalidate that. A mod's divergences are the mod's, documented by the mod:
ship a DIFFERENCES.md enumerating what you change from vanilla
(mirroring docs/known-differences.md's format), and let the manifest
profile (content / overhaul / total_conversion) be the
machine-readable summary of how far you depart. The engine's promise stays
auditable precisely because disabling your mod restores vanilla exactly.
Deprecation
A name that is superseded keeps working for the life of the mod API major
and logs one attributed warning the first time a given mod touches it -
scripts and ui are the current examples, renamed to map_scripts and
screens, and the v1 audio registry is superseded by the granular audio
registries. Nothing a v1 mod does stops working inside API 2, and
pokemon.before_give fires forever.
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.