Skip to content

Getting Started

bryanthaboi edited this page Jul 19, 2026 · 1 revision

Getting started

Install the game, make a mod directory, enable it, restart. This page is the durable reference for that loop; the tutorial ladder builds on it.

Run the game first

Open the packaged desktop app and, on first boot, choose your legally obtained Pokemon Red .gb file (only the canonical 1 MiB US ROM is accepted; the importer verifies SHA-1 ea9bcae617fdf159b045185467ae58b2e4a48b9a). Import takes a few seconds and the game starts. From a source checkout, love . after setup — see Developer Setup.

Where mods live

The loader scans one virtual mods/ root. LÖVE fuses two real locations into it (src/mods/Loader.lua:_discover):

  • mods/ inside the game folder (a source checkout or an unpacked app)
  • mods/ inside the LÖVE save directory — the same place save.lua lives:
    • macOS: ~/Library/Application Support/LOVE/pokemon-love2d/mods/ (without the LOVE/ segment in a packaged build)
    • Linux: ~/.local/share/love/pokemon-love2d/mods/
    • Windows: %APPDATA%\love\pokemon-love2d\mods\

Either location works; the save directory is the one that survives app updates.

The minimal mod

mods/my_first_mod/
├── manifest.json
└── main.lua

manifest.json:

{
  "id": "my_first_mod",
  "name": "My First Mod",
  "version": "1.0.0",
  "entry": "main.lua",
  "api": 2
}

api: 2 opts into strict schema validation — typos become load errors with suggestions instead of silent misses. Every other field is optional; the full list is in the manifest reference.

main.lua returns an entry function that receives the mod object:

return function(mod)
  mod.log:info("hello from a native mod")

  -- patch deep-merges onto the imported record; only speed changes
  mod.content.pokemon:patch("GROWLITHE", {
    baseStats = { speed = 90 },
  })

  -- react to the one gift-mon seam every story gift passes through
  mod.events:on("pokemon.before_give", function(gift)
    mod.log:info("giving %s at level %d", gift.species, gift.level)
  end)
end

Record fields are camelCase (baseStats, catchRate, spriteFront) — they come from the importer, not from JSON. Writing base_stats is the classic mistake; under api = 2 the loader rejects it with did you mean "baseStats"?. See Data Model.

Scaffold instead of hand-writing

The modkit CLI seeds the same layout with a valid manifest and a commented entry file:

python3 tools/modkit.py scaffold my_first_mod
python3 tools/modkit.py validate my_first_mod

validate drives the real engine loader headlessly, so a mod that passes will not surface load errors in-game. See Modkit CLI.

Enabling, disabling, and restarting

Mods load enabled by default. Enablement persists in the normal options.lua beside audio and display settings — starting a new game never resets it. options.mods[id] = false means disabled; missing means enabled. Toggle in-game from the mod manager (Options menu), or edit the file. Content changes take effect on the next launch: content registries freeze after the boot merge (Lifecycle).

While developing, run with POKEPORT_DEV=1: F5 hot-reloads mod content without restarting, and the loader warns when a mod requires private engine modules without declaring the matching permission.

When something breaks

A broken mod never takes the game down. Load failures — bad manifest, schema violations, missing dependencies, an entry chunk that throws — are reported in the mod manager against the failing mod, and every registration that mod made is rolled back. Fix the reported field and restart.

The rule that has no exceptions

Do not put ROM-derived content in a mod. Ship original assets, or an asset transform that regenerates derived art from the player's own imported cache (Art Pipeline). The loader never imports or executes ROM-hack patches; the supported content source remains the verified base ROM plus native mods. modkit pack refuses to package cache-derived bytes.

Where to next

Goal Page
See a change on screen in ten minutes Tutorial 01
Understand what loads when Lifecycle
Learn the five registry verbs Registries
Look up one task Cookbook
Look up a record's fields Reference: Registries

Clone this wiki locally