System Architecture
Estimated reading time: 4 minutesStartup Sequence
ModLoader changes the original jQuery -> SC2 startup to jQuery -> ModLoader -> SC2:
ELK not loaded. Ensure /elk.bundled.js is loaded before the app.
Sequence:
How ModLoader Hooks Into SugarCube2
SugarCube2 is a fully synchronous engine that turns game scripts (twee, JS, CSS) into HTML and displays them. Game scripts live in the compiled HTML's tw-storydata node.
SugarCube2 startup lives inside a jQuery(() => {}) closure in sugarcube.js. ModLoader wraps the original code in a mainStart() function and inserts an async wait so ModLoader can finish loading and injecting Mods before the engine starts.
SC2 Injection Point
ModLoader's injection point is in sugarcube.js:
Startup changes from jQuery -> SC2 to:
The whole Mod load process (fetching zips, running early scripts, merging tw-storydata) finishes before SugarCube2 reads any game data.
Core Component Relationships
ELK not loaded. Ensure /elk.bundled.js is loaded before the app.
Script Stages and Data Availability
What each script stage can access:
ELK not loaded. Ensure /elk.bundled.js is loaded before the app.
Global Objects
ModLoader exposes three global objects to Mod scripts:
Core Components
SC2DataManager
Central manager that initializes internal objects and plugins. On startInit() it:
- Caches the original unmodified
tw-storydataviainitSC2DataInfoCache() - Initializes internal components (ModLoader, ModZipReader, JsPreloader, etc.)
- Starts Mod loading
ModLoader
Core loader: reads Mod zip files from sources, runs scripts, registers Addons, merges Mod data into the game.
ModZipReader
Reads Mod zip archives and parses boot.json to understand Mod structure and requirements.
JsPreloader
Runs scriptFileList_earlyload and scriptFileList_preload. It wraps JS as (async () => { return ${jsCode} })() and awaits the result.
JsPreloader.JsRunner() injects return before the first line, so only the first line or an IIFE from the first line runs.
AddonPluginManager
Manages Addon registration and dispatch. Addon Mods register via registerAddonPlugin() during EarlyLoad; regular Mods declare dependencies in boot.json via the addonPlugin field.
DependenceChecker
Runs dependency checks during Mod load, validating that dependenceInfo constraints in boot.json are satisfied.
SC2DataInfo and SC2DataInfoCache
ModLoader caches the original unmodified tw-storydata via initSC2DataInfoCache() at startup. Each SC2DataInfo exposes a read-only interface to game data.
Purpose:
- In earlyload and preload, Mod scripts can read raw or merged Passage/CSS/JS data
- Earlyload sees raw data; preload sees merged data
- Enables TweeReplacer, ReplacePatch, and similar Addons
Overall Structure
ModLoader + game relation:
Mod framework breakdown:
Packaged Structure
Build steps:
- Build the modified SC2 engine to get
format.js - Override
devTools/tweego/storyFormats/sugarcube-2/format.jswithformat.js, then compile the game - Use
insert2html.jsto inject ModLoader into the game HTML
Custom SugarCube2 Modifications
ModLoader requires a modified SC2 engine (repository). Main changes:
- Startup change: Insert ModLoader’s async wait inside the jQuery closure
- Wikifier changes: Add
_lastPassageQand related data to track compilation; seemacrolib.js,parserlib.js,wikifier.js(search forpassageObj) - Image tag interception: Intercept
imgandsvgcreation so all images can be loaded from memory (no server)
Further Reading
For a more detailed technical breakdown of the 21-step loading flow and SC2 modification points:
