2
Tutorial 12 Mini Total Conversion
bryanthaboi edited this page 2026-07-27 07:45:53 -04:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 0111; 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.

    Prefer keeping OakSpeech when you only need extra questions, sprite swaps, or answers in mod.save: wrap intro.oak_speech.build (cookbook R37, example mods/examples/example_silly_oak). Replace newGame when the whole premise is yours.

  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.