SCML Mod Development Skill

Estimated reading time: 3 minutes

This skill helps AI assistants create and debug mods for SugarCube-2 ModLoader (SCML) games. It applies to all SC2-based games, primarily targeting Degrees of Lewdity (DoL).

Trigger Scenarios

This skill activates automatically when you need to:

  • Create a new mod and generate boot.json
  • Choose the appropriate script loading stage
  • Use AddonPlugin to depend on other mods
  • Use ModUtils API for inter-mod communication
  • Replace Passage or image content
  • Package .mod.zip files
  • Troubleshoot mod loading issues

Quick Start

1. Create Mod Directory Structure

MyMod/
├── boot.json
├── readme.txt
├── MyMod_style.css          (styles)
├── MyMod_script.js          (main script)
├── MyMod_passage.twee       (Passage)
└── MyMod_Image/             (images)
    └── character/
        └── avatar.png

2. Write boot.json

{
  "name": "MyMod",
  "version": "1.0.0",
  "styleFileList": ["MyMod_style.css"],
  "scriptFileList": ["MyMod_script.js"],
  "tweeFileList": ["MyMod_passage.twee"],
  "imgFileList": ["MyMod_Image/character/avatar.png"],
  "additionFile": ["readme.txt"],
  "dependenceInfo": [],
  "addonPlugin": []
}

3. Package

Compress all files (without the outer folder) into .mod.zip format. boot.json must be at the zip root.

Tip

See Creating Mods for the full tutorial.

Script Stage Selection

NeedStageboot.json Field
Register addon, expose modRef, sync initinject_earlyscriptFileList_inject_early
Async init, read raw data, decryptearlyloadscriptFileList_earlyload
Read merged data, modify passagespreloadscriptFileList_preload
Normal game scripts, macros, game logicMainscriptFileList
Warning

earlyload and preload scripts use IIFE format:

(async () => {
  // Your code
})();

See Script Loading Stages.

boot.json Essentials

Required fields (must exist even if empty):

FieldTypeDescription
namestringMod name
versionstringSemver version
styleFileListstring[]CSS file list
scriptFileListstring[]Main script file list
tweeFileListstring[]Twee file list
imgFileListstring[]Image file list

AddonPlugin dependency:

{
  "addonPlugin": [
    {
      "modName": "TweeReplacer",
      "addonName": "tweeReplacer",
      "modVersion": "1.0.0",
      "params": []
    }
  ]
}

See boot.json Complete Reference.

Bundled Mods & Addons

AddonPurposeboot.json Config
TweeReplacerPassage replacementaddonName: "tweeReplacer"
ReplacePatchJS/CSS/Passage string replacementaddonName: "replacePatch"
ImageLoaderHookImage replacementaddonName: "imgLoaderHook"
BeautySelectorAddonMultiple beauty image setsaddonName: "bsaAddon"

See Bundled Mods.

API Quick Reference

ModUtils (window.modUtils)

// Query mods
window.modUtils.getModListName(); // All loaded mod names
window.modUtils.getMod("MyMod"); // Get mod info
window.modUtils.getNowRunningModName(); // Current running mod name

// Expose API
const info = window.modUtils.getMod(myName);
info.modRef = { doThing: () => {} };

Lifecycle Hooks

HookTiming
afterInjectEarlyLoadAfter inject_early script
afterEarlyLoadAfter all earlyload complete
afterRegisterMod2AddonAfter mod-to-addon registration
beforePatchModToGameBefore data merge to game
afterPatchModToGameAfter data merge to game
ModLoaderLoadEndModLoader fully loaded

See ModUtils Reference, Lifecycle Hooks, AddonPlugin System.

Twee File Format

:: PassageName [tag1 tag2]
Passage content here.
Links use [[display text->Target Passage]] syntax.

Same-name Passages override base game content. Multiple mods with the same passage name: later-loaded mod wins.

Common Pitfalls

  • boot.json not at zip root — Package files directly, not the folder containing them
  • Path case mismatch — Paths in boot.json must exactly match zip layout (case-sensitive)
  • Missing IIFE for earlyload/preload — Must use (async () => { ... })() format
  • Async operations in inject_early — This stage doesn't support async, operations won't be awaited
  • Calling other mod's modRef too early — Use preload or later for inter-mod calls
  • Image path collisions — Use unique, namespaced paths