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
This is a complete, valid package with the fields you will normally set, and the smallest content that does something.
{
"$schema": "https://www.shardsofstone.com/schemas/v1/mod-package.json",
"formatVersion": 2,
"id": "my_balance_patch",
"name": "My Balance Patch",
"version": "1.0.0",
"author": "Your Name",
"description": "Cheaper goblin brawlers.",
"minEngineVersion": "0.3.0",
"dependencies": [],
"defOverrides": {
"units": [{ "id": "goblin_brawler", "patch": { "cost": { "gold": 55 } } }]
},
"triggers": { "version": 1, "vars": [], "regions": [], "triggers": [] },
"sounds": {},
"models": {}
}A triggers value is a whole trigger script, so
even an empty one needs version, vars, regions and triggers.
| 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 4 MB, and a single embedded data: URI in
the top-level sounds or models at most 256 KB. Host larger models and
sounds on an https:// server and reference them by URL instead.
Sounds and models. Each value in the top-level sounds and models is an
https:// URL or a data: URI; an http:// URL is an error. Keys are letters,
digits, _ and -, and never __proto__, constructor or prototype.
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. Amodvalue that is not a valid mod id is ignored without fetching anything. - 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, and so does the same problem in a
dependency's own dependencies. The validator reports all of these before you
ship, as long as it has the packages involved (see below).
Using what a dependency defines
Dependencies apply first, so a mod can build on their content: copy a
dependency's unit ("baseId": "their_unit"), train it, cast its spell,
research its upgrade, use a model from its models as mod:<key>, or create
its unit and play its sound from a trigger. A dependency's dependencies count
too. Copies of copies work across packages with the same limits as inside one
package: no loops, and at most 16 copies deep.
The validator checks each such name against the dependency itself when it has that package, merged exactly as the match merges them. When it does not, it says what it could not check:
| The name is… | Result |
|---|---|
| defined by a dependency the validator has | Checked for real, like anything in the mod itself |
| not found, and a declared dependency is not available | Warning: "provided by dependency 'x' — not checked" |
| not found, and no declared dependency is missing | Error: the id is unknown. If another package the validator has defines it, the suggestion is the dependencies entry to add |
A warning means the mod may be fine, but nothing about that name was checked.
To check it, give the validator the dependency: name it on the command line
with your mod (npx tsx tools/validate.ts my_mod.sosmod their_mod.sosmod), put
their_mod.sosmod beside your file, add it in the web validator, or send it in
dependencies to the HTTP endpoint. Mods hosted in the game's /mods/ folder
are found automatically by the command line and the web validator. See
Validation & Tooling.
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.