Skip to content

Tutorial 12 Mini Total Conversion

bryanthaboi edited this page Jul 19, 2026 · 1 revision

Tutorial 12 — Mini total conversion

Goal. Assemble the previous rungs into a small self-contained conversion: a new starting map, a custom boot flow, a rebalanced roster, and its own link identity — the full stack, packaged and shippable.

Prerequisites. Tutorials 01–11; Guide: Total Conversions; Guide: Publishing.

Files created.

mods/tutorial_12_tc/
├── manifest.json        -- profile = "total_conversion"
├── main.lua
├── transforms.lua
└── assets/

Steps

  1. Scaffold with the profile.

    python3 tools/modkit.py scaffold tutorial_12_tc --profile total_conversion

    The scaffold writes "profile": "total_conversion" and wires "assets_transforms": "transforms.lua". A non-content profile defaults affects_link to true, so the link fingerprint includes the mod (Concepts: Compatibility). Outcome: the manager labels the mod a total conversion.

  2. Own the new game. field.boot is the one key a conversion always writes:

    return function(mod)
      -- the world from tutorial 07, grown to taste
      mod.content.maps:register("MODTOWN", { ... })
    
      mod.content.field:patch("boot", {
        startMap = "MODTOWN", startX = 5, startY = 6, startFacing = "down",
        playerName = "ALEX", rivalName = "JESS", startMoney = 5000,
        namePresets = { player = { "ALEX", "SAM" }, rival = { "JESS", "KIT" } },
        lastHeal = { map = "MODTOWN", x = 5, y = 6 },
      })
    end

    Outcome: NEW GAME wakes up in your town with your defaults; blackout returns there.

  3. Brand the boot. The boot chain resolves its screens through the screens registry via field.boot.screens (src/core/Game.lua):

    mod.content.screens:register("ModTitle", { new = function(game) ... end })
    mod.content.screens:register("ModIntro", { new = function(game) ... end })
    mod.content.field:patch("boot", {
      screens = { title = "ModTitle", newGame = "ModIntro" },
    })

    splash, title, and newGame each name a screens id; unset keys keep the vanilla screens (IntroMovie, TitleState, OakSpeech). Your ModIntro screen owns the starter flow — a give_pokemon script row or a direct pokemon.before_give-compatible gift. Outcome: boot shows your title; NEW GAME runs your intro instead of the Oak speech.

  4. Scale the world's constants. Dex size, badges, party rules — all data:

    mod.content.constants:patch("dexSize", 160)
    mod.content.constants:override("badges", {
      { id = "MOD_BADGE_1" }, { id = "MOD_BADGE_2" },
    })

    (override on badges because a conversion supplies the whole set.) Outcome: the dex and the trainer card scale to your content.

  5. Walk the coverage checklist. A conversion is complete when every surface the player meets is yours on purpose: maps and connections, encounters, trainers and parties, map songs, text, the boot flow, and the icons/cries of every new species. Iterate with mod.content.<r>:each() in a dev script to find vanilla leftovers you meant to replace — and leave the engine itself (fonts, UI plumbing, the ROM import) alone: the import stays mandatory, conversions override on top of it.

  6. Package.

    python3 tools/modkit.py validate tutorial_12_tc
    python3 tools/modkit.py pack tutorial_12_tc

    pack runs the no-ROM-content lint at strict level and refuses cache-derived bytes — the .modpkg carries transforms and original assets only. Outcome: a shippable archive.

Checkpoint

A fresh NEW GAME boots your title, spawns in MODTOWN, runs your starter flow, and reaches a battle; the save round-trips through validation. Two installs with the mod produce matching link fingerprints and connect full; against a vanilla peer the handshake reports the difference instead of desyncing (Link Compatibility). Disable the mod: Red boots, byte-faithful, as if nothing happened.

Common pitfalls

  • Trying to skip the ROM import. It supplies the font, UI, and audio infrastructure; constraint one of the platform. Conversions overlay it, never replace it.
  • Shipping extracted content. The lint is a hard distribution gate; regenerate derived art with transforms (Art Pipeline).
  • Forgetting affects_link. The TC profile defaults it on; if you set it false while writing link-relevant registries, the loader warns and peers may desync.
  • An orphaned vanilla literal. A leftover Kanto map song or trainer party reads as a bug to your players; the coverage walk is the checklist.

Clone this wiki locally