Map Editor Documentation

Working with AI Assistants

Using a chat assistant or a coding agent to draft triggers, data overrides and mods: what to give it, how to bring its output into The Forge, and how to check the result before you play.

AI & automation · Page 18 of 46

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

  1. In The Forge, File → Copy Map as AI Context.
  2. Paste it into your assistant with what you want the map to do.
  3. Validate what comes back.
  4. 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 wait in 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:

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

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

ModeWhat it does
Merge TriggersMerges 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 OverridesMerges 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 MapReplaces 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> --json after every change and stop only when valid is true.

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, an atTick event's tick, and the ticks of wait and startTimer. Cutscenes are the exception: a scene's durationSec, and a line's or keyframe's t and a line's hold, are in seconds.
  • Invented event names, such as customEvent for custom, or onMatchStart, which does not exist (mapInit is 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" }.
  • wait inside a function. Not allowed; a function can start a timer or raise an event instead.
  • Sound ids that are paths. playSound takes an id such as explosion, not voice/... 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.