Skip to content

Getting Started

Pr1nted edited this page Aug 2, 2026 · 1 revision

Getting Started

One command

tools/gearbox doctor          # what toolchains do you have?
tools/gearbox new my-mod      # scaffold a mod
tools/gearbox dev my-mod      # compile, verify, pack, and install it

dev is the one to remember. It builds, checks the module imports nothing outside the ABI, packs the .odmod, and copies it into the game's mods folder — so recompiling never means hunting for a directory and copying files by hand.

Then in the game: Mod Menu → Reload modloader. Mods never load any other way, and never mid-game without a reload.

On Windows use tools\gearbox.ps1 (PowerShell), or the bash script under Git Bash / WSL. Set GEARBOX_MODS_DIR if your install lives somewhere unusual.

Installing toolchains

tools/sdk_toolchains.sh install     # Zig, TinyGo, wabt, Rust, AssemblyScript
tools/sdk_toolchains.sh clean       # remove all of it

Everything lands in .toolchains/ inside the repo — not Homebrew, not your home directory — so clean is an rm -rf that cannot break anything else. C and C++ need only a wasm32-capable clang; Apple's cannot target wasm32, but the one inside Emscripten can.

Your first mod, in full

#include "gearbox.h"
#define S(lit) lit, (uint32_t)(sizeof(lit) - 1)

static gearbox_panel g_panel;

GEARBOX_EXPORT("mod_load")
int32_t mod_load(void) {
    gearbox_env_t env; env.size = sizeof env;
    gearbox_env(&env);
    if (env.is_headless) return 0;      /* training run: no renderer */
    g_panel = gearbox_panel_register(S("Hello"), 200, 100);
    return 0;                           /* non-zero refuses the load */
}

GEARBOX_EXPORT("mod_draw_panel")
void mod_draw_panel(gearbox_panel p, uint32_t w, uint32_t h) {
    (void)w; (void)h;
    gearbox_draw_text(p, 8, 8, 0xFFFFFFFFu, S("Hello, world"));
}

Before you ship

build/odmod-check my-mod.odmod              # same checks the game runs
build/odmod-check my-mod.odmod --revoke UI  # rehearse a user saying no

The second one matters: a user can revoke any capability you asked for, and a mod that traps because of it is a bug in the mod.

Things that will bite you

  • There is no libc. No snprintf, no allocator unless you write one. Formatting an integer is nine lines and every mod needs it.
  • Strings are (ptr, len), never null-terminated.
  • Two-call sizing. country_name returns the full length and writes at most cap. A return greater than cap means truncation, not failure.
  • is_headless is real. Self-play runs thousands of turns with no renderer.
  • Fuel is finite on desktop — exceed limits.fuelPerTurn and you are terminated mid-call. On web it is not enforced, so bound your own loops.
  • Your state does not survive a reload. mod_load always runs fresh.

Clone this wiki locally