A map is a playable scenario: terrain, placed units, players, triggers. A mod is a portable package of changes that applies on top of a map: data overrides, triggers, custom sounds and 3D model references, with no terrain and nothing placed. One balance mod can be played on every map; one map can be played with or without it.
A mod is a single JSON file with the extension .sosmod.
What is in a .sosmod
{
"$schema": "https://shardsofstone.com/schemas/v1/mod-package.json",
"formatVersion": 2,
"id": "sos_balance_ironclad",
"name": "Ironclad Balance Patch",
"version": "1.0.0",
"author": "Shards of Stone",
"description": "…",
"minEngineVersion": "0.3.0",
"dependencies": [],
"defOverrides": { },
"triggers": { },
"sounds": { },
"models": { }
}| Field | Required | Meaning |
|---|---|---|
id | Yes | 1–64 characters: lowercase letters, digits, _ and -, starting with a letter or digit |
name | Yes | Display name |
version | Yes | MAJOR.MINOR.PATCH. Anything else is a warning, and breaks version ranges in dependencies |
author | Yes | Who made it |
description | No | Shown with the mod |
formatVersion | No | Which version of this file format it uses. Absent means 1; current is 2 |
minEngineVersion | No | The oldest game version that can load it |
dependencies | No | Other mods that must be applied first, see below |
defOverrides | No | Data overrides |
triggers | No | A trigger script |
sounds | No | { "id": "https://…" or "data:…" } for playSound |
models | No | { "key": "https://…" or "data:…" }, referenced as "modelUrl": "mod:<key>" |
tags, category, homepage, created | No | Metadata |
Unknown top-level fields are rejected by the JSON Schema. A package with no overrides, triggers, sounds or models is valid but gets a warning, since it does nothing.
Size. A package can be at most 20 MB, and a single embedded data: URI at
most 5 MB. Host larger models and sounds on an https:// server and reference
them by URL instead.
The two sample mods
Both ship with the game in public/mods/, so you can read them, validate them
and play them.
sos_balance_ironclad
A small balance patch that shows each kind of change:
- Patches
dwarf_ironguardto 70 HP and 4 armour, andgoblin_brawlerto cost 55 gold. The brawler patch is{ "cost": { "gold": 55 } }, and because objects merge deeply, its lumber, stone and oil costs are untouched. - Clones a new upgrade,
dwarf_tempered_plate, fromdwarf_attack_1: +1 armour for ironguard and royal guard, researched at the barracks for 125 gold and 25 stone. - Appends that upgrade to the barracks' research list with
{ "researches": { "$append": ["dwarf_tempered_plate"] } }, so it appears without replacing the barracks' existing research. - Patches a hero:
boringetsstatOverrides.hpof 100.
sos_frost_warden
A content mod that adds a unit, and depends on the first mod:
"dependencies": [{ "id": "sos_balance_ironclad", "version": "^1.0.0" }].- Models: two entries in
models,frost_warden_bodyandfrost_warden_anims. (They point at the runesmith's own model files on the game's CDN, so the sample works anywhere; your mod would point at your own.) - A sound:
frost_warden_cry, a URL to an.mp3. - A spell clone:
frost_warden_glacial_boltfromrune_blast, 45 damage, 30 mana, on theqhotkey. - A unit clone:
frost_wardenfromdwarf_runesmith, 60 HP, 180 gold, with"modelUrl": "mod:frost_warden_body"and aspellspatch that removes the runesmith's spells and prepends the glacial bolt. - Trainable: the runic library's
producesgets{ "$append": ["frost_warden"] }. - A trigger: on map initialisation, once, show "The Frost Wardens have
answered the call…" and play
frost_warden_cry.
Applying a mod
A mod is applied when a custom map is launched. It is never baked into a map you save.
- The Mod picker. The map browser and the multiplayer lobby's custom-map panel have a Mod control: Load .sosmod to pick a file, Clear to remove it. The chosen mod stays active for the browser tab.
- A link.
/play?customMap=<map>&mod=<id>or/play?communityMap=<id>&mod=<id>loads the mod from/mods/<id>.sosmodon the site. That only works for mods hosted on the site, such as the two samples:/play?customMap=tower-defense-showcase&mod=sos_balance_ironclad. - The validator. /tools/validator can test a mod directly: paste it, and Test in Game launches it on a blank map.
- An embed. A page embedding the game can send a mod with its map; see Web APIs.
Mods only apply to custom maps. The campaign and ordinary skirmish are never modded.
In multiplayer
Only the host needs the mod. The host's game resolves the mod and its dependencies, merges them into the map, and sends the merged map to everyone along with a checksum of the map and the mod's id and version. Every player's game checks that checksum before the match starts, and again at the start signal. If anything differs, the match does not start, and the message says Map integrity check failed with both checksums, rather than the match desyncing later.
Guests see a read-only Mod: name vversion line in the lobby.
How a mod merges into a map
Mods apply in order: dependencies first, then the mod itself, each on top of the result so far.
- defOverrides. Entries are matched by
idwithin each kind. A mod entry for an id the map already overrides is applied after the map's patch, as if both patches ran in sequence. If the mod gives that id a differentbaseId, the mod's entry replaces the map's entirely. - triggers. Triggers and regions are matched by
id; variables (vars), functions and hashtables by name. A mod entry with the same id or name replaces the map's whole entry; anything else is added, and the map's other entries are kept. - sounds and models merge by key, the mod winning.
Dependencies and versions
A dependency is { "id": "...", "version": "..." }. The version accepts:
| Requirement | Matches |
|---|---|
1.2.3 | Exactly 1.2.3 |
^1.2.0 | 1.x.x, at least 1.2.0 (for 0.x, the same minor) |
~1.2.0 | 1.2.x, at least 1.2.0 |
>=1.2.0 | 1.2.0 or anything newer |
* | Any version |
When a mod launches, its dependencies are loaded from /mods/<id>.sosmod. A
missing dependency, a version that does not match, or a cycle refuses the
launch with a message naming the problem. The validator reports the same
things before you ship. Run it over several files at once
(npx tsx tools/validate.ts a.sosmod b.sosmod) and it checks them against each
other.
minEngineVersion is compared with the game's version. An older game refuses
the package with "This package needs game version X or newer".
See Compatibility & Versioning for how versions should move when you change a mod.
Making a mod in The Forge
- Build the changes on a test map: data overrides in the Data module (
F7), triggers in the Trigger module (F6). - File → Export Standalone Mod (.sosmod)… writes a package of the map's
data overrides, triggers, sounds and models. The id comes from the map name
(lowercased, other characters turned into
_), with version1.0.0and authorForge Modder. - Open the file in a text editor and set
id,name,version,authoranddescriptionproperly. AdddependenciesandminEngineVersionif you need them. - Validate it:
npx tsx tools/validate.ts my_mod.sosmod, or paste it into /tools/validator.
The export takes the map's whole trigger script, regions included, because triggers reference regions by id. Region coordinates come from your test map, so a mod whose triggers use regions only makes sense on maps laid out the same way. When the mod is applied, a region with the same id on the target map is replaced by the mod's copy. (The trigger script is only exported when it has at least one trigger or variable.)
To bring a mod's changes into a map you are editing, use File → Import
Standalone Mod (.sosmod)…. It merges the package's data overrides, triggers,
sounds and models into the map by the same rules, so mod:<key> model
references keep working in the saved map, in Test Map and when published.
Sharing a mod
There is no mod upload or mod gallery yet. Share the .sosmod file however you
share files; players load it with Load .sosmod. Mods hosted in the game's
own /mods/ folder, which today means the two samples, also work with
?mod=<id> links.
Next: Compatibility & Versioning.