Maps, triggers, data overrides and mods are all plain JSON with a published schema and a strict validator. That makes them a good fit for AI assistants: a chat assistant can draft a wave script from a description, and a coding agent with a shell can write a mod, validate it and fix its own mistakes.
It does not make an assistant's output correct. Assistants invent field names, unit ids and event kinds that look plausible and do not exist. Everything on this page is built around catching that before it reaches a match.
The short version
- In The Forge, File → Copy Map as AI Context.
- Paste it into your assistant with what you want the map to do.
- Validate what comes back.
- File → Import from AI / JSON…, then Test Map (
Ctrl+F9) with the Trigger Debug panel open.
1. Give it real context
Copy Map as AI Context (File menu, available once a map is open) copies a prompt to your clipboard containing:
- the map's name, size and biome, and its seat count;
- every region, with its id and bounds;
- a summary of placed entities per player, and gold mine positions;
- the map's current trigger script and data overrides, if any;
- the rules that matter (ticks, randomness, caps, no
waitin functions, literal shorthand); - a complete reference of every trigger event, action and expression, with every field and whether it is required;
- the data override format, including the list operators and the six kinds;
- every unit and building id in the game, grouped by race;
- the output formats the importer accepts.
The reference and the id lists are generated from the game's own tables when you copy, so they match the version of the game you are running. An assistant working from them has no reason to guess a field name.
For work outside the editor, the same information is available as:
/llms.txt, a short index of these docs for AI tools, and/llms-full.txt, the long form including the node reference;- the JSON Schemas;
- the Trigger Reference.
2. Ask for something specific
Say what the map should do in game terms, and name the regions and players it should use. A good request reads like a design note:
Using the regions in the context above: every 30 seconds, spawn goblin_brawler
units for player 5 at "spawn_north" (4 in wave 1, 2 more each wave) and order
them to move (not attack-move) to "castle". Show a counter "Wave" and a counter
"Lives" starting at 20. When a player 5 unit enters "castle", remove it and
subtract a life. At 0 lives, player 0 is defeated. After wave 15 is cleared,
player 0 wins. Reply with a TriggerScript only.Name the order you mean. Units told to attack-move stop to fight whatever they pass, so waves that should walk to a goal need a plain move; see Building a Tower Defense map.
Ask for one artefact at a time: a trigger script, or an override set, or a mod. Small outputs are easier to validate and to review.
3. Validate before importing
Paste the result into /tools/validator, or save it to a file and run:
npx tsx tools/validate.ts waves.jsonEach problem comes with a JSON path and often a suggestion:
ERROR trigger $.triggers[0].actions[0].duration: unknown property 'duration' on 'showText' — did you mean 'durationTicks'?
ERROR trigger $.triggers[0].actions[1].unitTypeId: unknown unit type 'goblin_brawlr' — did you mean 'goblin_brawler'?Paste the errors back to the assistant and ask it to fix exactly those paths. Two or three rounds is typical.
4. Import and play
File → Import from AI / JSON… opens the importer. Paste the reply (a surrounding code fence is stripped for you) or use Load File…, and pick a mode:
| Mode | What it does |
|---|---|
| Merge Triggers | Merges the pasted trigger script into the map's. Triggers and regions with the same id, and variables, functions and hashtables with the same name, are replaced by the pasted ones; new ones are added; everything else in the map is kept. The dialog lists what will be added and replaced, and validates the merged script — Apply is disabled if the merge would leave errors |
| Merge Data Overrides | Merges the pasted overrides into the map's. An override for an id the map already overrides is layered on top: every field it sets wins, fields it leaves out keep the map's value (a different baseId replaces it outright). Custom sounds and models merge by key, pasted winning. The dialog lists what will be added, updated and replaced, and validates the merged set |
| Replace Full Map | Replaces the whole map with the pasted one |
| Mod Package (.sosmod) | Merges a mod's overrides and triggers into the map's, the same way, including its sounds and models — so mod:<key> model references keep resolving in the saved map, Test Map and a published map |
So the assistant only needs to return what it adds or changes. To remove
something, delete it in the Trigger or Data module. An import can be undone
with Ctrl+Z, and the importer refuses a trigger script with validation
errors.
Then Apply to Map. The Validation module (F10) checks the result against
this particular map, which catches things a standalone file cannot: a region
that exists in the assistant's head but not on your map, for example.
Finally, Test Map (Ctrl+F9) and open Trigger Debug (Ctrl+Shift+F9).
Validation proves the JSON is well formed. Only playing it proves it does what
you asked. Watch the trace for conditions that never come out true and
triggers that never fire.
Coding agents
An agent that can run commands can close the loop itself. Give it:
- the goal, in game terms;
- the context file from Copy Map as AI Context, saved to disk;
- the instruction to run
npx tsx tools/validate.ts <file> --jsonafter every change and stop only whenvalidistrue.
The --json output has a stable shape
({ valid, files: [{ file, kind, label, valid, errors, warnings }] }) and the
exit code is 0 only when there are no errors, which is a better stopping
condition than the agent's own opinion. An agent without a checkout of the
repository can POST the file to https://www.shardsofstone.com/api/validate
instead (30 requests a minute). Details are in
Validation & Tooling.
Validating a mod together with its dependencies in one command
(validate.ts base.sosmod addon.sosmod) also checks the dependency versions.
What assistants get wrong
These come up often enough to put in your request up front:
- Flat override entries. Every override needs
patch:{ "id": "dwarf_ironguard", "patch": { "hp": 700 } }, not{ "id": "dwarf_ironguard", "hp": 700 }. - Replacing a list when they meant to add to it.
"produces": ["x"]replaces the building's whole roster. Adding is{ "$append": ["x"] }. - Seconds instead of ticks. Every trigger timing is in ticks, and 20 ticks
is one second:
everyTicks,offsetTicks,durationTicks, anatTickevent'stick, and theticksofwaitandstartTimer. Cutscenes are the exception: a scene'sdurationSec, and a line's or keyframe'stand a line'shold, are in seconds. - Invented event names, such as
customEventforcustom, oronMatchStart, which does not exist (mapInitis the start of the match). - Bare strings for variables. In an expression slot,
"lives"is the text "lives". A variable is{ "k": "var", "name": "lives" }. waitinside a function. Not allowed; a function can start a timer or raise an event instead.- Sound ids that are paths.
playSoundtakes an id such asexplosion, notvoice/...or a file name.
JSON triggers and custom code
JSON triggers are directly checkable: the validator reports the exact node and field that needs fixing. For custom code, ask the assistant for sandboxed JavaScript or TypeScript using the public sos API, then test it in the Forge. Read Your first sandboxed script.
Next: Custom Units & Models.