Skip to content

Making mods

New units, buildings and upgrade levels in one JSON file: the format, how to test a mod in the game, and how to publish it to the registry.

Steel Tide takes mods. A mod adds units, buildings and upgrade levels — described in one JSON file, with sprite sheets beside it if you draw some — and the game lists them under Settings → Mods, next to the official registry where everyone's mods are published. A mod cannot change the rules, the interface or the units the game ships with: it adds, and everything it adds is switched off with it. That is what keeps a mod safe to install and a save safe to load.

The whole reference is on this page, generated from the same tables the game validates a mod against. If you would rather hand the job to a coding agent, the brief at the bottom tells it everything it needs.

What a mod is

A folder:

my-mod/
  mod.json          the manifest: the mod's identity, its defs, and the sheets they draw with
  sprites/*.png     optional art
  README.md         optional

mod.json names the mod, then lists its defs — each a unit, a building or an upgrade level — and, if the mod draws its own art, the sheets. Every field a def can carry is a field of the game's own unit definition, so anything you can read off a wiki page here is something a mod can set.

Your first mod in five minutes

The dead-simple path is extends: start from a vanilla def and override what you like. This is a complete, working mod — a heavier Bison for the late game:

{
  "format": "steel-tide-mod",
  "v": 1,
  "id": "bison-ii",
  "name": [
    "Bison II",
    "野牛 II"
  ],
  "version": "1.0.0",
  "author": "you",
  "description": [
    "A heavier Bison for the late game.",
    "后期用的重型野牛。"
  ],
  "defs": [
    {
      "id": "bison2",
      "extends": "mbt",
      "name": [
        "Bison II",
        "野牛 II"
      ],
      "desc": [
        "Thicker plate, a bigger gun, and a bigger bill.",
        "更厚的装甲、更大的炮,也更贵。"
      ],
      "tier": 3,
      "cost": 520,
      "hp": 1100,
      "speed": 52,
      "requires": [
        "radar"
      ],
      "weapons": [
        {
          "id": "cannon",
          "cls": "cannon",
          "dmg": 90,
          "reload": 2,
          "range": 5,
          "splash": 12,
          "turret": true,
          "muzzleOffset": 23
        }
      ],
      "aiWeight": 2
    }
  ]
}
  1. Save that as mod.json inside a folder called bison-ii.
  2. In the game, open Settings → Mods and press Open folder… (or zip the folder and press Upload file…). The game says what it loaded — or lists what is wrong, with the path into mod.json for each thing.
  3. Start a skirmish. The Bison II is in the war factory's list from level 2, because that is where the Bison is; it draws with the Bison's art; and the AI fields it too, since the def carries aiWeight.

extends copies everything — art, weapons, cost, where it is built — and each field you name replaces the copy. Leave weapons out to keep the base's guns; write your own list to replace them.

Testing

  • Open folder… is the edit loop. In Chrome and Edge the game keeps the folder open, so after you edit mod.json you press Reload on the mod's row and the change is in. Elsewhere, pick the folder again.
  • Upload file… takes a zip of the folder or a .steel-tide-mod file (the manifest with the sheets embedded — what the converter hands out).
  • Add from URL… and ?mod= take a mod served over the web: npx serve --cors my-mod and then open https://play.steelti.de/?mod=http://localhost:3000/ — the mod is fetched again at every boot, so a page reload picks up an edit.
  • Developer Tools → Showcase (developer mode: five taps on the version line) places every unit and building on one map, yours included, so you can see the art and watch the guns without building anything.
  • The console (backquote) has give <id> for any def.

Mods apply to skirmish and the campaign. A networked match is always unmodded — the server's roster is the vanilla one. A save remembers the mods it was made with and will not load without them.

Going deeper

A mod with its own art, a defended building, and a fourth level for the power plant line:

{
  "format": "steel-tide-mod",
  "v": 1,
  "id": "ironworks",
  "name": [
    "Ironworks",
    "铁工厂"
  ],
  "version": "1.0.0",
  "author": "you",
  "description": [
    "A hover tank, a bunker and a fusion plant.",
    "一辆悬浮坦克、一座碉堡和一座聚变电站。"
  ],
  "license": "CC-BY-4.0",
  "defs": [
    {
      "id": "ironworks-hover",
      "name": [
        "Skimmer Hover Tank",
        "掠行悬浮坦克"
      ],
      "desc": [
        "Fast, thin-skinned, rockets.",
        "快、皮薄、打火箭。"
      ],
      "kind": "unit",
      "domain": "ground",
      "tier": 2,
      "cost": 260,
      "hp": 320,
      "armor": "light",
      "speed": 110,
      "turnRate": 5,
      "radius": 9,
      "trail": "tire",
      "weapons": [
        {
          "id": "pods",
          "cls": "rocket",
          "dmg": 16,
          "reload": 2.2,
          "range": 4.5,
          "burst": 4,
          "burstDelay": 0.1,
          "splash": 10,
          "spread": 14
        }
      ],
      "producedBy": [
        "factory2",
        "factory3"
      ],
      "aiWeight": 1
    },
    {
      "id": "ironworks-bunker",
      "name": [
        "Bunker",
        "碉堡"
      ],
      "desc": [
        "A gun pit that takes a beating.",
        "扛打的火力点。"
      ],
      "kind": "building",
      "cost": 380,
      "hp": 1600,
      "fw": 2,
      "fh": 2,
      "power": -3,
      "vision": 8,
      "weapons": [
        {
          "id": "gun",
          "cls": "autocannon",
          "dmg": 22,
          "reload": 0.5,
          "range": 5,
          "turret": false,
          "targets": [
            "ground",
            "ship"
          ]
        }
      ]
    },
    {
      "id": "ironworks-fusion",
      "name": [
        "Fusion Plant",
        "聚变电站"
      ],
      "desc": [
        "The plant line's fourth level.",
        "发电厂线的第四级。"
      ],
      "extends": "power3",
      "upgradeOf": "power3",
      "cost": 2200,
      "hp": 2600,
      "power": 500,
      "upgradeCost": 1100,
      "upgradeTime": 60,
      "requires": [
        "radar",
        "reactor"
      ]
    }
  ],
  "sprites": [
    {
      "key": "u.ironworks-hover",
      "file": "sprites/u.ironworks-hover.png",
      "frames": 1,
      "rotated": true,
      "fw": 24,
      "fh": 26
    },
    {
      "key": "u.ironworks-bunker",
      "file": "sprites/u.ironworks-bunker.png",
      "frames": 1
    }
  ]
}

Units of measure

A tile is 32 world pixels. speed and projectile speed are world px/s; range, minRange, vision, sonar, stealth, detect, repairRange and interceptRange are tiles; radius, splash, spread and muzzleOffset are world px; every time is seconds. For scale, the Bison Battle Tank is cost 280, hp 620, speed 60, radius 10, with a cannon of dmg 60 every 1.8 s at range 4.6; the Recon Buggy is cost 60, hp 150, speed 120; a War Factory is 3×3 tiles, hp 1500, power −8.

Where things are built

  • A unit joins the production list of the buildings in producedBy. Left out, it joins its domain's line from its tier up: a tier-2 ground unit appears at the level-2 and level-3 war factory, a tier-1 ship at every naval yard. With extends, it is built wherever the base is.
  • A building is placed by the units in builtBy — the engineer, unless you say otherwise. A building with a produces list is a factory; a unit that names a plain building in producedBy turns it into one.
  • An upgrade level is a building with upgradeOf: the named building gains an Upgrade button that turns it into this def, at upgradeCost over upgradeTime, and this def is never placed directly. A building has one next level, so upgradeOf may name a vanilla building at the end of its line (power3, factory3, extractor3, gatling, cannonturret2, samsite, interceptor2, radar, repairtower, reactor, nukesilo) or another def of your mod.
  • requires lists buildings that must stand before something can be built, the way Tier 3 needs the Radar Station.

Art

A def draws with the atlas key in sprite (its body) and turretSprite (a rotating part). Three ways to fill them:

  1. Nothing. A def with no sheet is drawn as a plain placeholder in the faction's colour, sized to the def — a mod plays before its art exists.
  2. Borrowed. "sprite": "u.mbt", "turretSprite": "tur.mbt" draws with the Bison's art; extends inherits the base's.
  3. Your own. A sprites entry per sheet: a PNG (WebP and JPEG work too), frames animation frames left to right in one strip, evenly spaced, no gaps. The key is u.<id> for a body and tur.<id> for a turret, and a mod may not take a vanilla key.

Hulls, turrets and everything that turns are one image facing up with "rotated": true — the game bakes the 24 headings. fw/fh are the in-game size of that image in world px (a tank hull is about 24×24; draw it at 2–4× that and the loader resamples). pivotX/pivotY put the pivot on the turret ring.

Buildings are a strip of frames, not rotated, drawn top-down with a slight southern tilt: the footprint is the bottom fw×32 by fh×32 px of the frame and anything above overhangs the terrain behind. Width, height and anchor are sized from the def's footprint for you.

Team colour is magenta: paint faction-coloured parts in #FF66FF, #FF00FF and #990099 and nothing else in magenta; the game recolours them per player. Generated sheets are cleaned on load — backgrounds removed, frames registered, static pixels frozen — so a transparent background is best but not required.

The AI

AI opponents build a modded unit at its aiWeight, on the scale the vanilla roster is weighed on (a Bison is 3, a scout car 1), once its requires are met. Leave it out and they never build it. Buildings and upgrade levels are yours alone.

Reference

Anything not in these tables is ignored with a warning; a number outside its range is an error.

mod.json

fieldtyperequireddefaultmeaning
formatstringyesalways "steel-tide-mod"
vinteger 1–1yesformat version, 1
ididyesthe mod's id: lower case, letters, digits, `-` — its folder in the registry
nametextyesthe name shown in the mod list
versionstringyese.g. `1.0.0`; the game offers an update when the registry's is newer
authorstringwho made it
descriptiontextone or two sentences for the list
homepagestringa link: a repository, a thread
licensestringCC-BY-4.0the mod's licence (SPDX id)
minGamestringthe oldest game version it is written for
defsdef[]yesthe units, buildings and upgrade levels
spritessheet[]the sheets the defs draw with (see below)
files{ path: dataURL }single-file form only: the sheets, embedded as data URLs by path

name, description and a def's name/desc are text: a string (used for both languages), ["English", "中文"], or { "en": "…", "zh": "…" }.

A def (defs[])

fieldtyperequireddefaultmeaning
ididyesunique across every mod and the vanilla roster; prefix a generic word with your mod's id
nametextyesas the HUD shows it
desctext""the tooltip line
extendsida vanilla def id (or an earlier def of this mod) to copy, then override field by field; inherits its art and where it is built
kindunit | buildingyesa unit or a building (required unless `extends` says)
domainground | ship | airunits onlygroundwhere it moves; a submarine is a `ship` with `underwater`
tierinteger 1–31the factory level it appears at, and the badge
costnumber 0–99999yesmetal
buildTimenumber 0–3600cost ÷ 14seconds at full power
popinteger 0–501 for a unit, 0 for a buildingpopulation it counts for
hpnumber 1–1000000yeshit points
armorlight | medium | heavy | ship | sub | air | structureby domainthe armour class weapons are multiplied against
speednumber 0–1000units only60world px/s (a tile is 32)
turnRatenumber 0–50units only3.5rad/s
visionnumber 0–648sight, in tiles
radiusnumber 1–2009, or the footprintcollision radius, world px
weaponsweapon[][]the weapons (see below); an empty list is unarmed
fwinteger 1–8buildings only2footprint width, tiles
fhinteger 1–8buildings only2footprint height, tiles
producedByid[]units onlythe line for its domain, from its tier upthe buildings whose production list it joins (vanilla or this mod's)
producesid[]buildings onlya factory: the units it builds
builtByid[]buildings only["engineer"]the builder units that may place it
buildsid[]units onlya builder unit: the buildings it can construct
buildRatenumber 0–10000units onlya builder unit: hp of work per second
powernumber -10000–100000positive produces, negative draws
metalRatenumber 0–1000buildings onlymetal per second (an extractor)
needsDepositbooleanbuildings onlymust stand on a deposit
repairRangenumber 0–64buildings onlya repair aura, tiles
repairRatenumber 0–10000buildings onlyhp per second per target
repairTargetsinteger 1–50buildings onlytargets served at once
upgradeOfidbuildings onlythe building this is the next level of; that one gains the upgrade button
upgradeCostnumber 0–99999buildings onlycost − the source's costwith `upgradeOf`: the upgrade's price
upgradeTimenumber 0–3600buildings onlybuildTimewith `upgradeOf`: seconds
requiresid[]building ids that must stand before it can be built
nukeCapacityinteger 0–10a launcher: warheads it holds
nukeCostnumber 0–99999a launcher: metal per warhead
nukeTimenumber 0–3600a launcher: seconds per warhead
interceptRangenumber 0–64point defence: reach in tiles
interceptMaginteger 1–200point defence: rounds ready
interceptReloadnumber 0.05–600point defence: seconds per round replaced
interceptMuzzleOffsetnumber 0–200point defence: launcher length, world px
transportCapinteger 1–50units onlya transport: hold, in cargo weight
landsForCargobooleanunits onlya cargo plane that touches down to load
cargoWeightnumber 0–50units onlypophow much of a hold it takes
underwaterbooleanunits onlya submarine: seen only by sonar
sonarnumber 0–64units onlysonar range, tiles
stealthnumber 0–64units onlyseen only within this many tiles of an enemy
detectnumber 0–64buildings onlyreveals stealth within this many tiles
hoversbooleanunits onlyan aircraft that hovers instead of orbiting
altitudenumber 0–64units only12an aircraft's drawn height, px
fireOnMovebooleanunits onlykeeps shooting on a plain move
trailtread | tire | wakeunits onlyby domainthe mark it leaves
spritestringthis mod's u.<id> sheet, else the base's artthe body's atlas key: one of this mod's sheets, or a vanilla key to borrow its art
turretSpritestringthis mod's tur.<id> sheet, else the base's (when its art is kept)the rotating part's key, if any
aliasesstring[]other names the console's `give` accepts
aiWeightnumber 0–10units only0how readily the AI builds it — a Bison is 3, a scout car 1; 0 never

Defaults with no extends: a unit is domain: "ground", tier: 1, pop: 1, speed: 60, turnRate: 3.5, vision: 8, radius: 9, armour by domain (ground medium, ship ship, air air), a tread trail on land and a wake at sea; a building is fw: 2, fh: 2, armor: "structure", power: 0, pop: 0. buildTime is cost ÷ 14 seconds.

A weapon (defs[].weapons[])

fieldtyperequireddefaultmeaning
idstringw1, w2…a name for the weapon
clsmg | autocannon | cannon | at | he | rocket | navgun | ashm | torpedo | aayeswhat it was built to kill — picks its row of the armour matrix
dmgnumber 0–100000yesdamage per hit
reloadnumber 0.05–600yesseconds between shots or bursts
rangenumber 0.5–64yestiles
minRangenumber 0–64tiles it cannot fire inside
projectilebullet | shell | missile | rocket | bomb | torpedo | flakby classthe round drawn
speednumber 1–5000by projectileround speed, world px/s
targets(ground | ship | sub | air)[]by classwhat it may fire at: ground, ship, sub, air
mult{ armour: number }overrides of the class row, by armour class
splashnumber 0–500blast radius, world px
burstinteger 1–32shots per burst
burstDelaynumber 0–5seconds between the shots of a burst
homingbooleanthe round tracks its target
interceptablebooleanpoint defence may shoot it down
arcbooleana ballistic arc (artillery)
turretbooleantrue when the def has a turretSpritefired from the rotating part
muzzleOffsetnumber 0–200pivot to muzzle, world px
spreadnumber 0–200inaccuracy at full range, world px
friendlyFirebooleanthe blast hurts your own side too
soundmg | autocannon | cannon | missile | flak | arty | rocket | torpedo | bombby classthe firing sound

Damage is dmg × the armour matrix cell for the weapon's class against the target's armour, with mult overriding single cells. The counter web explains the classes; the matrix:

WeaponLightMediumHeavyStructureShipSubmarineAircraft
Recon Buggy
×1.50×0.80×0.40×0.35
Wolf Light Tank
×1.50×1×0.50×0.50×0.70
Bison Battle Tank
×0.60×1.30×1×0.80×0.90
Mammoth Heavy Tank
×0.60×1.30×1.10×1×0.90
Viper Tank Destroyer
×0.40×1.10×1.80×0.70×1
Flak Track
×1
Hawk SAM Launcher
×1
Thunder Howitzer
×1.20×1×0.75×1.60×1
Tempest MLRS
×1.20×1×0.75×1.60×1
Salamander Thermobaric Mortar
×1.20×1×0.60×1.60×1
Gunboat
×1.50×1×0.50×0.50×0.80
Missile Boat
×1.60
Aegis Frigate
×1×1×0.75×0.75×1×1
Orca Destroyer
×1×1.10×0.80×0.80×1×1.90
Barracuda Submarine
×1.30×1.20
Sovereign Battleship
×1.20×1×1×1.60×1.20
Kraken Cruise-Missile Submarine
×1.20×1×0.75×1.60
Falcon Fighter
×1
Cobra Attack Helicopter
×0.60×1.10×1.80×0.80×1
Thunderbolt Strike Jet
×1.40×1.40×0.90×0.70×0.90
Albatross Naval Striker
×1.70
Vulture Bomber
×1.20×1.20×1×1.60×1.10
Spectre Gunship
×1.20×1.20×1.10×1×1
Wraith Stealth Bomber
×1×0.90×0.60×1.60×1
Cormorant ASW Helicopter
×0.90×1.80
MG Turret
×1.50×0.80×0.40×0.30×0.60×0.70
Gatling Turret
×1.50×0.90×0.50×0.30×0.70×0.80
Cannon Turret
×0.60×1.30×1.10×0.60×1
Bastion Cannon
×0.60×1.30×1.20×0.60×1.10
AA Turret
×1
SAM Site
×1

A sheet (sprites[])

fieldtyperequireddefaultmeaning
keystringyes`u.<id>` for a body, `tur.<id>` for a rotating part; never a vanilla key
filestringyesthe image, relative to mod.json (PNG, WebP or JPEG)
framesinteger 1–641animation frames, left to right in one strip
fwnumber 4–512the footprint (a building) or the imagein-game frame width, world px
fhnumber 4–512in-game frame height, world px
rotatedbooleanone up-facing image; the game bakes the 24 headings (hulls, turrets)
pivotXnumber 0–10.5rotation pivot, as a fraction of the frame
pivotYnumber 0–10.5rotation pivot, as a fraction of the frame
anchorYnumber 0–512px from the top to the footprint centre (tall buildings)
mount[x, y]a body: where its turret sits, `[fx, fy]`
fpsnumber 0–60animation speed
teamsbooleantruerecolour magenta per faction
ssinteger 1–4supersample factor; omit to let the game choose
fitFootprintbooleanscale the drawn content to fill the frame
animRegion[x0, y0, x1, y1]where the animation lives, `[x0, y0, x1, y1]` fractions; the rest is frozen
stabilizebooleantruere-align drifting frames
freezeStaticbooleanmedian-freeze pixels that barely change
stripBgbooleanforce background removal on or off
bgMinLumanumber 0–255lightest colour still taken as background
artifactCleanupbooleansweep specks left by background removal

Publishing

The registry is the public repository rivertwilight/steel-tide-mods: every mod is a folder under mods/<id>/, and the game and this site list what is there.

  1. Fork it and add your folder as mods/<id>/ — the folder name is the mod's id.
  2. Run node tools/check.mjs mods/<id>. It prints every error the game would, and checks the folder against every other published mod: ids are global, so prefix a generic word with your mod's id (ironworks-bunker, not bunker).
  3. Open a pull request. CI runs the same check; once merged, the index is rebuilt and the mod appears in the game's registry list and on the mods page.
  4. Bump version for every change. The game offers the update to whoever has the mod.

A mod is published under the licence its manifest names (CC-BY-4.0 unless you say otherwise). Only ship art you have the right to.

Converting a Rusted Warfare mod

The converter takes a Rusted Warfare custom-unit mod — a .rwmod, a zip, or the unit folder — and gives back a Steel Tide mod: the .ini files become defs, the images become sheets, green team colour becomes magenta. Prices, hit points, speeds and ranges are scaled so a Rusted Warfare tank lands near a Bison (or converted unit for unit, if you prefer). It runs in the browser; nothing is uploaded. Look the manifest over afterwards: effects, animations, shields and energy have no counterpart here.

For coding agents

The registry ships a skill — the whole format, how to test a mod and how to publish it, in the form Claude Code, Cursor, Codex and the other agents read. Install it with skills:

npx skills add rivertwilight/steel-tide-mods --skill steel-tide-mods-guideline

Then ask your agent for a mod. Working inside a clone of the registry, an agent finds the same brief as AGENTS.md, and it is served as plain text at steelti.de/mods/AGENTS.md for anything that reads a URL.