Map Editor Documentation

Validation & Tooling

One validator, four front ends: the Validation module, the web validator, POST /api/validate and the command line. Plus the JSON Schemas, the Trigger Debug panel and a write, validate, fix loop for people and AI agents.

Page 14 of 22

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.

SeverityEffect
ErrorBlocks saving, Test Map and publishing. At runtime the offending part degrades rather than crashing, which is exactly why it is caught here.
WarningBlocks 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/json

No 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.

StatusBodyWhen
200the report aboveValidation 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 --json

Text 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:

SchemaFor
/schemas/v1/map-format.jsonA playable map
/schemas/v1/trigger-script.jsonA trigger script
/schemas/v1/def-overrides.jsonA set of data overrides
/schemas/v1/mod-package.jsonA .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.

  1. 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.
  2. Write the trigger script, override set or mod as a file.
  3. Validate it: npx tsx tools/validate.ts file.json --json, or POST it to /api/validate.
  4. Fix exactly what is reported. Each issue's path points at the offending field, and suggestion, when present, is usually the fix. Do not rewrite the parts that passed.
  5. Repeat until valid is true, then read the warnings.
  6. Optionally, run it headlessly. With a checkout of the repository, npx tsx tools/ai_loop/playMap.ts map.json --json plays 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.
  7. 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.