The modkit CLI
tools/modkit.py is the mod-author command line: scaffold, validate,
gen2check, gen3check, lint, pack, bounce, translation, docs, set-github,
add-release-workflow. Python 3; the
validate path also needs luajit on your PATH (override with
MODKIT_LUAJIT).
python3 tools/modkit.py <subcommand> [args]
Global flags: --repo PATH (repo root override), --json, --quiet.
Exit codes: 0 success, 1 validation/lint failure, 2 usage error.
First mod workflow
Run these commands from the engine repository root, the directory containing
tools/modkit.py. Replace my_mod with a short id using letters, numbers,
_, or -.
python3 tools/modkit.py scaffold my_mod
python3 tools/modkit.py validate my_mod
python3 tools/modkit.py pack my_mod -o my_mod-1.0.0.zip
Edit mods/my_mod/manifest.json and mods/my_mod/main.lua between
scaffold and validate.
scaffold creates the starting files under mods/my_mod/. validate checks
the mod with the headless loader; pack writes the installable package only
after validation and linting pass. If a command reports a finding, fix the
file and line named in that finding, then rerun the same command. Do not use
--force until you have confirmed which existing file the command will
replace.
scaffold
python3 tools/modkit.py scaffold my_mod
python3 tools/modkit.py scaffold my_tc --profile total_conversion
python3 tools/modkit.py scaffold my_mod --github owner/repo --experimental
python3 tools/modkit.py scaffold my_mod --games gen1,gen2,gen3
Creates mods/<id>/ with a manifest (api: 2 by default, or the level you pass with --api; the current engine's
game_version range), a commented main.lua, a README.md, a
.luarc.json, and an assets/ directory. The total_conversion profile
adds a transforms.lua and wires assets_transforms. Flags:
--profile content|overhaul|total_conversion, --games (default gen1;
gen1, gen2, gen3, frlg, rse, all, or a comma list of version
ids such as red,gold,firered), --github owner/repo (optional; enables
launcher auto-update, see Manifest), --experimental
(off until the player confirms enable), --dest DIR, --force.
--api sets the manifest api (default 2). Use --api 3 for a
portable API 3 mod; mods/examples/portable_api/ is a
working reference.
validate
python3 tools/modkit.py validate my_mod [--strict] [--base auto|fixture|imported]
Drives the real engine loader headlessly, so a mod that passes will
not surface load errors in-game: manifest shape (MK001-MK004), permission
vocabulary (MK005) and undeclared engine requires (MK006), schema
violations, dangling cross-references, vanilla-id checks (MK1xx) and
map_scripts problems (MK201). --base auto folds over the player's
imported dataset when one exists and falls back to the ROM-free fixture
(tests/fixture_data/) otherwise, which keeps the tool runnable on a CI box
with no ROM. Rules
that need the real id space (MK103) are reported as skipped over the
fixture, never guessed.
github is optional: if the field is missing, validate prints a tip that
setting "github": "owner/repo" enables launcher auto-update. A present
but malformed value is an error.
gen2check and gen3check
python3 tools/modkit.py gen2check my_mod [more_mods...] [--strict] [--notes]
python3 tools/modkit.py gen3check my_mod [more_mods...] [--strict] [--notes]
Answer whether a mod runs on a Gen 2 (Gold, Silver, Crystal) or Gen 3 game
and how far it gets: first the manifest gate (MK400: games claims no game
of that generation; MK401: a dependency claims none), then a static read of
the mod's Lua against what src/mods/Gen2Compat.lua or
src/mods/Gen3Compat.lua backs, member by member (MK402-MK410). It is a
scan, not an interpreter: what it cannot follow is listed as unresolved.
Mods named together are read as one install set, so a mod and its
dependencies answer each other. --notes adds the adapter's note for every
backed member the mod touches. Exits non-zero on a fatal finding.
-- example_portable_api: api 3, profile content, games gen1+gen2+gen3, ...
ok example_portable_api on gen 3: will load
lint
python3 tools/modkit.py lint my_mod
The no-ROM-content distribution gate:
| Rule | Refuses |
|---|---|
| MK301 | files in (or pointing into) the generated trees |
| MK302 | byte-identical or perceptually near-duplicate assets vs the player's cache |
| MK303 | ROM images and ROM-hack patch formats (.gb, .gbc, .ips, .bps) |
| MK304 | raw chip-audio banks (ROM-derived) |
| MK305 | bulk dumps of an imported data table (register individual records instead; warns when no imported dataset exists to diff against) |
| MK306 | lang/strings.lua keys that are FireRed/LeafGreen ROM English (key them by ROM label; translation --refresh moves them) |
| MK307 | anything under baseroms/: user-supplied import files are installation state, never distributed (Publishing) |
pack
python3 tools/modkit.py pack my_mod [-o out.zip] [--base auto|fixture|imported]
Runs validate and lint at strict level (any finding, warning included,
refuses the package), then writes a reproducible zip with the mod files at
the archive root plus .modkit/pack.json. Without -o the name is
<id>-<version>.modpkg; the launcher's Import mod .zip and drag-and-drop
only take a .zip name, so pass -o <id>-<version>.zip for a file players
import directly. See Publishing.
bounce
Renders chip-program songs only to WAV headlessly: the fast iteration
loop for ChipAsm authoring without booting the game.
File-backed songs are skipped because this command exercises the chip
renderer. Run from the engine repository root; without --out, WAV files go
under bounce/ in that repository. The command needs imported/generated
data, so an error saying it needs an imported dataset means you must import a
game before using bounce. A missing id prints no such song.
python3 tools/modkit.py bounce Music_ModChip --seconds 30
python3 tools/modkit.py bounce --all --out renders/
Look for the printed wrote .../<song-id>.wav line and listen to that WAV.
The command's success means a WAV was rendered; it does not verify that a
file-backed cue, map assignment, or in-game mix sounds right.
translation
python3 tools/modkit.py translation my_lang --language "Francais"
python3 tools/modkit.py translation my_lang --refresh
Scaffolds a translation mod: manifest.json, main.lua, README.md,
TRANSLATING.md and empty lang/*.lua catalogs holding every
player-visible string the engine and the imported dataset know about.
--refresh re-harvests the catalogs and keeps existing work, --pixel-font
renders through the bundled Plain Pixel TTF instead of the tile font, and
--base, --dest, --force work as elsewhere. See
Translations.
docs
python3 tools/modkit.py docs [--out DIR]
Regenerates docs/modding/reference/registries.md from
src/mods/Schemas.lua (via tools/gen_registry_docs.lua), so the
registry reference cannot drift from the engine; --out DIR also copies it
to DIR/registries.md. To write the wiki page directly, run
luajit tools/gen_registry_docs.lua <wiki checkout>. The
wiki registry reference mirrors that file.
set-github
python3 tools/modkit.py set-github my_mod owner/repo
python3 tools/modkit.py set-github my_mod https://github.com/owner/repo
Writes or updates the optional github field on an existing
manifest.json (for mods scaffolded before the field existed). Same
normalization as the loader: owner/repo or a github.com URL.
add-release-workflow
python3 tools/modkit.py add-release-workflow my_mod
python3 tools/modkit.py add-release-workflow my_mod --force
Copies the standard GitHub Actions release workflow into
mods/<id>/.github/workflows/release.yml, stamped with the mod's id.
On each push to main (or a manual run) it packs a root-level installable
.zip and publishes a GitHub Release: the layout the launcher's
Import mod .zip / Update / Versions paths expect. Use --force to
overwrite an existing workflow. See Publishing.
Check and recover
The first workflow should leave a package at the -o path after validate and
lint pass. If a command reports a finding, use its file and line, rerun that
same command, and keep the last working mod directory; if bounce reports no
imported dataset or no song, import the dataset or correct the song id before
trying again.
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.