Everything you author (a map, a trigger script, a set of data overrides, a
.sosmod package) goes through one validator. The editor, the web
validator, the HTTP endpoint, the command-line tool and the server's publish
check all call the same function, so they cannot disagree about whether
something is valid.
The validator does not judge your design. It reports things that would fail silently at runtime: a misspelled field the game would ignore, a unit type that does not exist, a region a trigger names but the map never declares, a mercenary camp that would spawn as scenery. Each problem has a severity, a JSON path to the exact spot and, where something is close, a suggestion.
| Severity | Effect |
|---|---|
| Error | Blocks saving, Test Map and publishing. At runtime the offending part degrades rather than crashing, which is exactly why it is caught here. |
| Warning | Blocks nothing. Worth reading, because most warnings are mistakes most of the time. |
In The Forge: the Validation module
Press F10. Every problem in the map is listed, with a jump-to for anything
that has a location. The count in the status bar is the same list. See
Testing for how to read it and
Publishing for the rules that only apply to
publishing.
In the browser: /tools/validator
/tools/validator runs the validator entirely in your
browser. Paste or drop a file (.json, .sosmap, .sosmod), or start from a
sample (Tower Defense, Custom Units, Sample Mod). The mode tabs force
a kind if detection guesses wrong: Auto-Detect, Full Map, Trigger
Script, Data Overrides, Mod Package.
When the content is valid, Test in Game launches it: a map as itself, a trigger script or override set wrapped in a blank template map, a mod on a blank map. Copy Clean JSON and Download File give you back the normalised file.
Over HTTP: POST /api/validate
POST https://www.shardsofstone.com/api/validate
Content-Type: application/jsonNo account or key is needed. The body is either the artefact itself, or a wrapper:
{ "kind": "triggers", "content": { "version": 1, "vars": [], "regions": [], "triggers": [] } }kind is auto (the default), map, triggers, defOverrides or mod, and
can also be passed as ?kind= in the URL. content may be an object or a JSON
string. The wrapper is recognised only when its keys are exactly content and
optionally kind.
The response
A completed validation is always HTTP 200, whether the content is valid or not: the request succeeded, the content did not.
{
"kind": "triggers",
"label": "Trigger Script (TriggerAST)",
"valid": false,
"errors": [
{
"severity": "error",
"code": "trigger",
"path": "$.triggers[0].actions[0].duration",
"message": "unknown property 'duration' on 'showText' — did you mean 'durationTicks'?",
"suggestion": "durationTicks"
}
],
"warnings": [],
"engineVersion": "0.3.4",
"formatVersion": 2
}That error is real output for a trigger script with a typo in it.
engineVersion is the game version that validated it, and formatVersion is
the newest .sosmod format it reads. Issues may also carry at: { x, y }, a
tile position, when the problem has one.
| Status | Body | When |
|---|---|---|
| 200 | the report above | Validation ran |
| 400 | { "error": "bad_kind" | "bad_content", "message" } | Unknown kind, or content that is not JSON |
| 413 | { "error": "payload_too_large", "message" } | Body over 21 MB |
| 429 | { "error": "rate_limited", "message" } | More than 30 requests in a minute from one IP address |
| 500 | { "error": "internal", "message" } | A bug on our side |
The endpoint sends Access-Control-Allow-Origin: *, so a web page on any site
can call it.
How the kind is detected
Without kind, the validator looks at the shape: a $schema naming one of the
schemas below decides outright; otherwise terrain or meta means a map; id
plus version plus author or formatVersion means a mod; version with a
triggers, vars or regions array means a trigger script; and any of
units, buildings, items, spells, upgrades or heroes means data
overrides. If nothing matches, the error code is unknown-format. Set kind
when you know it.
On the command line: tools/validate.ts
In a checkout of the game's repository:
npx tsx tools/validate.ts my_map.json
npx tsx tools/validate.ts public/mods/*.sosmod
npx tsx tools/validate.ts wave_triggers.json --kind triggers --jsonText output is one PASS or FAIL line per file followed by its issues:
FAIL wave_triggers.json [Trigger Script (TriggerAST)] 3 error(s), 0 warning(s)
ERROR trigger $.triggers[0].events[0].region: unknown region 'gaet' — did you mean 'gate'? — gate
ERROR trigger $.triggers[0].actions[0].duration: unknown property 'duration' on 'showText' — did you mean 'durationTicks'? — durationTicks
ERROR trigger $.triggers[0].actions[1].unitTypeId: unknown unit type 'goblin_brawlr' — did you mean 'goblin_brawler'? — goblin_brawler--json prints { "valid", "files": [{ "file", "kind", "label", "valid", "errors", "warnings" }] }.
Exit codes: 0 when no file has an error (warnings are fine), 1 when any
file has an error, 2 for bad usage or a file it could not read. That makes it
usable as a CI check.
Every .sosmod named in one run is available to the others as a dependency,
and a <id>.sosmod sitting next to the file is found too, so validating a mod
together with its dependencies checks that they exist and that the versions
match.
JSON Schemas
The formats are published as JSON Schema, for editor autocompletion and for tools that want a structural check without running the game's validator:
| Schema | For |
|---|---|
/schemas/v1/map-format.json | A playable map |
/schemas/v1/trigger-script.json | A trigger script |
/schemas/v1/def-overrides.json | A set of data overrides |
/schemas/v1/mod-package.json | A .sosmod package |
Put "$schema": "https://shardsofstone.com/schemas/v1/mod-package.json" at the
top of a file and most code editors will complete and check field names as you
type.
A schema is a structural check. It cannot know that a region id exists, that a unit type is real, or that a variable was declared, so passing the schema is not the same as passing the validator. Use the schema while typing and the validator before shipping.
The node-by-node documentation for triggers, generated from the same catalogue as the trigger schema, is the Trigger Reference.
While a map runs: Trigger Debug
The validator checks shape. Whether your triggers do what you meant is a
runtime question, and the Trigger Debug panel answers it. It exists only in
a Test Map match (Ctrl+F9 from the editor); open it with Ctrl+Shift+F9 or
its button at the lower left.
- Trace lists triggers that fired, conditions that came out false, actions that ran, waits, cap hits (a loop or cascade that hit its limit), cutscenes starting and ending, and every read of something that was not there. Filter by trigger name or id, or by entry kind. Clear empties it.
- Variables shows every global variable's current value.
The trace is recorded only in Test Map and never affects the match.
The write, validate, fix loop
This works the same for a person editing JSON and for an AI agent with a shell.
- Start from real context. In The Forge, File → Copy Map as AI Context copies the map's regions, placed entities, current triggers and overrides, and a full reference of every trigger node, override kind and unit id, all generated from the game's own tables. Or start from the JSON Schemas and the Trigger Reference.
- Write the trigger script, override set or mod as a file.
- Validate it:
npx tsx tools/validate.ts file.json --json, orPOSTit to/api/validate. - Fix exactly what is reported. Each issue's
pathpoints at the offending field, andsuggestion, when present, is usually the fix. Do not rewrite the parts that passed. - Repeat until
validistrue, then read the warnings. - Optionally, run it headlessly. With a checkout of the repository,
npx tsx tools/ai_loop/playMap.ts map.json --jsonplays the whole map in the real simulation, with human seats idle, and exits non-zero if a trigger throws, hits a cap or reads a broken reference. See Web APIs. - Import and play. File → Import from AI / JSON… in The Forge, then
Test Map (
Ctrl+F9) with Trigger Debug open. Validation proves the script is well formed; only playing it proves it is right.
For an agent, the useful stopping rule is "exit code 0 and no new warnings", not "the output looks plausible". The validator rejects the most common invented names outright, so a loop that feeds errors back usually converges in two or three rounds.
Next: Publishing.