{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://www.shardsofstone.com/schemas/v1/trigger-script.json",
  "$comment": "GENERATED by tools/generate_modding_reference.ts. Do not edit by hand.",
  "title": "TriggerScript",
  "description": "A Shards of Stone trigger script: global variables, regions, functions, hashtables and triggers. Every node is discriminated on `k`.",
  "type": "object",
  "required": [
    "version",
    "vars",
    "regions",
    "triggers"
  ],
  "properties": {
    "version": {
      "type": "integer",
      "minimum": 1,
      "maximum": 1,
      "description": "Always 1."
    },
    "vars": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/VarDecl"
      },
      "description": "Global variables, initialised in order on map init."
    },
    "regions": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/RegionDef"
      },
      "description": "Named tile rectangles (RegionDef[])."
    },
    "triggers": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/TriggerDef"
      },
      "description": "Triggers (TriggerDef[]), evaluated in array order."
    },
    "saveCodeSalt": {
      "type": "string",
      "description": "Binds save codes to this map; defaults to the map name."
    },
    "functions": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/FunctionDef"
      },
      "description": "User-defined functions (FunctionDef[])."
    },
    "hashtables": {
      "type": "array",
      "maxItems": 64,
      "items": {
        "$ref": "#/definitions/HashtableDecl"
      },
      "description": "Declared hashtables (HashtableDecl[])."
    }
  },
  "additionalProperties": false,
  "definitions": {
    "Action": {
      "description": "An action, discriminated on `k`.",
      "oneOf": [
        {
          "$ref": "#/definitions/Action.if"
        },
        {
          "$ref": "#/definitions/Action.while"
        },
        {
          "$ref": "#/definitions/Action.forEachUnit"
        },
        {
          "$ref": "#/definitions/Action.forEachInt"
        },
        {
          "$ref": "#/definitions/Action.wait"
        },
        {
          "$ref": "#/definitions/Action.setVar"
        },
        {
          "$ref": "#/definitions/Action.addVar"
        },
        {
          "$ref": "#/definitions/Action.raiseEvent"
        },
        {
          "$ref": "#/definitions/Action.enableTrigger"
        },
        {
          "$ref": "#/definitions/Action.runTrigger"
        },
        {
          "$ref": "#/definitions/Action.stopTrigger"
        },
        {
          "$ref": "#/definitions/Action.comment"
        },
        {
          "$ref": "#/definitions/Action.callFunction"
        },
        {
          "$ref": "#/definitions/Action.return"
        },
        {
          "$ref": "#/definitions/Action.hashtableSet"
        },
        {
          "$ref": "#/definitions/Action.hashtableRemove"
        },
        {
          "$ref": "#/definitions/Action.hashtableClear"
        },
        {
          "$ref": "#/definitions/Action.createUnits"
        },
        {
          "$ref": "#/definitions/Action.removeUnit"
        },
        {
          "$ref": "#/definitions/Action.killUnit"
        },
        {
          "$ref": "#/definitions/Action.setUnitHp"
        },
        {
          "$ref": "#/definitions/Action.setUnitMana"
        },
        {
          "$ref": "#/definitions/Action.setUnitOwner"
        },
        {
          "$ref": "#/definitions/Action.setUnitInvulnerable"
        },
        {
          "$ref": "#/definitions/Action.moveUnit"
        },
        {
          "$ref": "#/definitions/Action.addUnitToGroup"
        },
        {
          "$ref": "#/definitions/Action.removeUnitFromGroup"
        },
        {
          "$ref": "#/definitions/Action.clearGroup"
        },
        {
          "$ref": "#/definitions/Action.order"
        },
        {
          "$ref": "#/definitions/Action.setResource"
        },
        {
          "$ref": "#/definitions/Action.addResource"
        },
        {
          "$ref": "#/definitions/Action.setAlliance"
        },
        {
          "$ref": "#/definitions/Action.setBuildableRegion"
        },
        {
          "$ref": "#/definitions/Action.victory"
        },
        {
          "$ref": "#/definitions/Action.defeat"
        },
        {
          "$ref": "#/definitions/Action.startTimer"
        },
        {
          "$ref": "#/definitions/Action.pauseTimer"
        },
        {
          "$ref": "#/definitions/Action.resumeTimer"
        },
        {
          "$ref": "#/definitions/Action.stopTimer"
        },
        {
          "$ref": "#/definitions/Action.showText"
        },
        {
          "$ref": "#/definitions/Action.clearText"
        },
        {
          "$ref": "#/definitions/Action.setCounter"
        },
        {
          "$ref": "#/definitions/Action.removeCounter"
        },
        {
          "$ref": "#/definitions/Action.setLeaderboardTitle"
        },
        {
          "$ref": "#/definitions/Action.setLeaderboardRow"
        },
        {
          "$ref": "#/definitions/Action.removeLeaderboardRow"
        },
        {
          "$ref": "#/definitions/Action.clearLeaderboard"
        },
        {
          "$ref": "#/definitions/Action.showDialog"
        },
        {
          "$ref": "#/definitions/Action.hideDialog"
        },
        {
          "$ref": "#/definitions/Action.ping"
        },
        {
          "$ref": "#/definitions/Action.playSound"
        },
        {
          "$ref": "#/definitions/Action.playCutscene"
        },
        {
          "$ref": "#/definitions/Action.stopCutscene"
        },
        {
          "$ref": "#/definitions/Action.cameraPan"
        },
        {
          "$ref": "#/definitions/Action.cameraSetZoom"
        },
        {
          "$ref": "#/definitions/Action.cameraLock"
        },
        {
          "$ref": "#/definitions/Action.cameraUnlock"
        },
        {
          "$ref": "#/definitions/Action.cameraSetBounds"
        },
        {
          "$ref": "#/definitions/Action.cameraShake"
        },
        {
          "$ref": "#/definitions/Action.cameraReset"
        },
        {
          "$ref": "#/definitions/Action.applyImpulse"
        },
        {
          "$ref": "#/definitions/Action.setUnitKinematics"
        },
        {
          "$ref": "#/definitions/Action.generateSaveCode"
        },
        {
          "$ref": "#/definitions/Action.createUIFrame"
        },
        {
          "$ref": "#/definitions/Action.updateUIFrame"
        },
        {
          "$ref": "#/definitions/Action.destroyUIFrame"
        },
        {
          "$ref": "#/definitions/Action.clearUIFrames"
        },
        {
          "$ref": "#/definitions/Action.setQuest"
        },
        {
          "$ref": "#/definitions/Action.clearQuests"
        }
      ]
    },
    "Action.addResource": {
      "title": "Add resource",
      "description": "Add to a player's resource stock (rounded, ≥ 0).",
      "type": "object",
      "required": [
        "k",
        "player",
        "resource",
        "delta"
      ],
      "properties": {
        "k": {
          "const": "addResource"
        },
        "player": {
          "$ref": "#/definitions/Expr",
          "description": "Seat id (0-based) of the player."
        },
        "resource": {
          "type": "string",
          "enum": [
            "gold",
            "lumber",
            "stone",
            "oil"
          ],
          "description": "Resource kind."
        },
        "delta": {
          "$ref": "#/definitions/Expr",
          "description": "Amount to add (negative subtracts)."
        }
      },
      "additionalProperties": false
    },
    "Action.addUnitToGroup": {
      "title": "Add unit to group",
      "description": "Add a unit to a unitGroup variable.",
      "type": "object",
      "required": [
        "k",
        "varName",
        "unit"
      ],
      "properties": {
        "k": {
          "const": "addUnitToGroup"
        },
        "varName": {
          "type": "string",
          "minLength": 1,
          "description": "unitGroup variable."
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "The unit (entity id). A dead or missing unit is harmless: reads give zero values, writes do nothing."
        }
      },
      "additionalProperties": false
    },
    "Action.addVar": {
      "title": "Add to variable",
      "description": "Add a number to a numeric variable.",
      "type": "object",
      "required": [
        "k",
        "name",
        "delta"
      ],
      "properties": {
        "k": {
          "const": "addVar"
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "description": "Variable name."
        },
        "index": {
          "$ref": "#/definitions/Expr",
          "description": "Element index for an array."
        },
        "delta": {
          "$ref": "#/definitions/Expr",
          "description": "Amount to add."
        }
      },
      "additionalProperties": false
    },
    "Action.applyImpulse": {
      "title": "Apply impulse",
      "description": "Give a unit a kinematic velocity.",
      "type": "object",
      "required": [
        "k",
        "unit",
        "vx",
        "vy"
      ],
      "properties": {
        "k": {
          "const": "applyImpulse"
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "The unit (entity id). A dead or missing unit is harmless: reads give zero values, writes do nothing."
        },
        "vx": {
          "$ref": "#/definitions/Expr",
          "description": "Velocity X (tiles/tick)."
        },
        "vy": {
          "$ref": "#/definitions/Expr",
          "description": "Velocity Y (tiles/tick)."
        },
        "friction": {
          "$ref": "#/definitions/Expr",
          "description": "Per-tick velocity multiplier."
        },
        "bounce": {
          "$ref": "#/definitions/Expr",
          "description": "Restitution on collision."
        },
        "ticks": {
          "$ref": "#/definitions/Expr",
          "description": "Maximum duration in ticks."
        }
      },
      "additionalProperties": false
    },
    "Action.callFunction": {
      "title": "Call function",
      "description": "Call a user-defined function as a statement, optionally storing its return value.",
      "type": "object",
      "required": [
        "k",
        "fn"
      ],
      "properties": {
        "k": {
          "const": "callFunction"
        },
        "fn": {
          "type": "string",
          "minLength": 1,
          "description": "Function name."
        },
        "args": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Expr"
          },
          "description": "Arguments, one per parameter, in order."
        },
        "intoVar": {
          "type": "string",
          "minLength": 1,
          "description": "Variable receiving the return value (function must declare `returns`)."
        },
        "intoIndex": {
          "$ref": "#/definitions/Expr",
          "description": "Element index when intoVar is an array."
        }
      },
      "additionalProperties": false
    },
    "Action.cameraLock": {
      "title": "Lock camera",
      "description": "Lock the camera to a unit or a point.",
      "type": "object",
      "required": [
        "k",
        "players"
      ],
      "properties": {
        "k": {
          "const": "cameraLock"
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null` for everyone."
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "Unit to follow."
        },
        "at": {
          "$ref": "#/definitions/Expr",
          "description": "Point to hold."
        }
      },
      "additionalProperties": false
    },
    "Action.cameraPan": {
      "title": "Pan camera",
      "description": "Pan the camera to a point over a duration.",
      "type": "object",
      "required": [
        "k",
        "players",
        "to"
      ],
      "properties": {
        "k": {
          "const": "cameraPan"
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null` for everyone."
        },
        "to": {
          "$ref": "#/definitions/Expr",
          "description": "Target tile."
        },
        "durationTicks": {
          "type": "integer",
          "minimum": 0,
          "description": "Pan duration in ticks."
        }
      },
      "additionalProperties": false
    },
    "Action.cameraReset": {
      "title": "Reset camera",
      "description": "Clear locks, bounds and zoom overrides.",
      "type": "object",
      "required": [
        "k",
        "players"
      ],
      "properties": {
        "k": {
          "const": "cameraReset"
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null` for everyone."
        }
      },
      "additionalProperties": false
    },
    "Action.cameraSetBounds": {
      "title": "Camera bounds",
      "description": "Confine the camera to a region, or null to clear.",
      "type": "object",
      "required": [
        "k",
        "players",
        "region"
      ],
      "properties": {
        "k": {
          "const": "cameraSetBounds"
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null` for everyone."
        },
        "region": {
          "type": [
            "string",
            "null"
          ],
          "description": "Region id, or null."
        }
      },
      "additionalProperties": false
    },
    "Action.cameraSetZoom": {
      "title": "Set camera zoom",
      "description": "Change the camera zoom.",
      "type": "object",
      "required": [
        "k",
        "players",
        "zoom"
      ],
      "properties": {
        "k": {
          "const": "cameraSetZoom"
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null` for everyone."
        },
        "zoom": {
          "$ref": "#/definitions/Expr",
          "description": "Zoom factor."
        },
        "durationTicks": {
          "type": "integer",
          "minimum": 0,
          "description": "Transition in ticks."
        }
      },
      "additionalProperties": false
    },
    "Action.cameraShake": {
      "title": "Shake camera",
      "description": "Shake the camera.",
      "type": "object",
      "required": [
        "k",
        "players",
        "intensity"
      ],
      "properties": {
        "k": {
          "const": "cameraShake"
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null` for everyone."
        },
        "intensity": {
          "$ref": "#/definitions/Expr",
          "description": "Shake intensity."
        },
        "durationTicks": {
          "type": "integer",
          "minimum": 0,
          "description": "Duration in ticks."
        }
      },
      "additionalProperties": false
    },
    "Action.cameraUnlock": {
      "title": "Unlock camera",
      "description": "Release a camera lock.",
      "type": "object",
      "required": [
        "k",
        "players"
      ],
      "properties": {
        "k": {
          "const": "cameraUnlock"
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null` for everyone."
        }
      },
      "additionalProperties": false
    },
    "Action.clearGroup": {
      "title": "Clear group",
      "description": "Empty a unitGroup variable.",
      "type": "object",
      "required": [
        "k",
        "varName"
      ],
      "properties": {
        "k": {
          "const": "clearGroup"
        },
        "varName": {
          "type": "string",
          "minLength": 1,
          "description": "unitGroup variable."
        }
      },
      "additionalProperties": false
    },
    "Action.clearLeaderboard": {
      "title": "Clear leaderboard",
      "description": "Remove every row and the title.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "clearLeaderboard"
        }
      },
      "additionalProperties": false
    },
    "Action.clearQuests": {
      "title": "Clear quests",
      "description": "Remove every quest.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "clearQuests"
        }
      },
      "additionalProperties": false
    },
    "Action.clearText": {
      "title": "Clear text",
      "description": "Clear banners for these seats (null = all).",
      "type": "object",
      "required": [
        "k",
        "players"
      ],
      "properties": {
        "k": {
          "const": "clearText"
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null` for everyone."
        }
      },
      "additionalProperties": false
    },
    "Action.clearUIFrames": {
      "title": "Clear UI frames",
      "description": "Remove every frame.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "clearUIFrames"
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null`/absent for everyone."
        }
      },
      "additionalProperties": false
    },
    "Action.comment": {
      "title": "Comment",
      "description": "Does nothing; documents the script.",
      "type": "object",
      "required": [
        "k",
        "text"
      ],
      "properties": {
        "k": {
          "const": "comment"
        },
        "text": {
          "type": "string",
          "description": "Comment text."
        }
      },
      "additionalProperties": false
    },
    "Action.createUIFrame": {
      "title": "Create UI frame",
      "description": "Create or replace a declarative HUD frame.",
      "type": "object",
      "required": [
        "k",
        "frameId",
        "anchor",
        "x",
        "y",
        "width",
        "height"
      ],
      "properties": {
        "k": {
          "const": "createUIFrame"
        },
        "frameId": {
          "type": "string",
          "minLength": 1,
          "description": "Frame id."
        },
        "anchor": {
          "type": "string",
          "enum": [
            "top_left",
            "top_center",
            "top_right",
            "center_left",
            "center",
            "center_right",
            "bottom_left",
            "bottom_center",
            "bottom_right"
          ],
          "description": "Screen anchor."
        },
        "x": {
          "$ref": "#/definitions/Expr",
          "description": "X offset from the anchor (px)."
        },
        "y": {
          "$ref": "#/definitions/Expr",
          "description": "Y offset from the anchor (px)."
        },
        "width": {
          "$ref": "#/definitions/Expr",
          "description": "Width (px)."
        },
        "height": {
          "$ref": "#/definitions/Expr",
          "description": "Height (px)."
        },
        "title": {
          "$ref": "#/definitions/Text",
          "description": "Title template."
        },
        "text": {
          "$ref": "#/definitions/Text",
          "description": "Body template."
        },
        "progress": {
          "$ref": "#/definitions/Expr",
          "description": "Progress bar fill 0..1."
        },
        "progressColor": {
          "type": "string",
          "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
          "description": "Progress bar colour."
        },
        "style": {
          "$ref": "#/definitions/UIFrameStyle",
          "description": "Visual style overrides."
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null`/absent for everyone."
        },
        "visible": {
          "type": "boolean",
          "description": "Visible (default true)."
        }
      },
      "additionalProperties": false
    },
    "Action.createUnits": {
      "title": "Create units",
      "description": "Spawn N units (max 500) for a player at a point.",
      "type": "object",
      "required": [
        "k",
        "unitTypeId",
        "count",
        "player",
        "at"
      ],
      "properties": {
        "k": {
          "const": "createUnits"
        },
        "unitTypeId": {
          "type": "string",
          "description": "Unit type id (base game or a defOverrides clone)."
        },
        "count": {
          "$ref": "#/definitions/Expr",
          "description": "How many."
        },
        "player": {
          "$ref": "#/definitions/Expr",
          "description": "Seat id (0-based) of the player."
        },
        "at": {
          "$ref": "#/definitions/Expr",
          "description": "Spawn tile."
        },
        "facing": {
          "$ref": "#/definitions/Expr",
          "description": "Facing in degrees."
        },
        "intoVar": {
          "type": "string",
          "minLength": 1,
          "description": "unitGroup variable receiving the created ids."
        }
      },
      "additionalProperties": false
    },
    "Action.defeat": {
      "title": "Defeat",
      "description": "Declare a seat defeated.",
      "type": "object",
      "required": [
        "k",
        "player"
      ],
      "properties": {
        "k": {
          "const": "defeat"
        },
        "player": {
          "$ref": "#/definitions/Expr",
          "description": "Seat id (0-based) of the player."
        }
      },
      "additionalProperties": false
    },
    "Action.destroyUIFrame": {
      "title": "Destroy UI frame",
      "description": "Remove a frame.",
      "type": "object",
      "required": [
        "k",
        "frameId"
      ],
      "properties": {
        "k": {
          "const": "destroyUIFrame"
        },
        "frameId": {
          "type": "string",
          "minLength": 1,
          "description": "Frame id."
        }
      },
      "additionalProperties": false
    },
    "Action.enableTrigger": {
      "title": "Enable/disable trigger",
      "description": "Turn a trigger on or off.",
      "type": "object",
      "required": [
        "k",
        "triggerId",
        "on"
      ],
      "properties": {
        "k": {
          "const": "enableTrigger"
        },
        "triggerId": {
          "type": "string",
          "minLength": 1,
          "description": "Trigger id."
        },
        "on": {
          "type": "boolean",
          "description": "Enabled."
        }
      },
      "additionalProperties": false
    },
    "Action.forEachInt": {
      "title": "For each integer",
      "description": "Run the body for each integer from..to inclusive (at most 10000).",
      "type": "object",
      "required": [
        "k",
        "varName",
        "from",
        "to",
        "body"
      ],
      "properties": {
        "k": {
          "const": "forEachInt"
        },
        "varName": {
          "type": "string",
          "pattern": "^[A-Za-z_][A-Za-z0-9_]*$",
          "description": "Loop variable; an undeclared name becomes a local."
        },
        "from": {
          "$ref": "#/definitions/Expr",
          "description": "First value."
        },
        "to": {
          "$ref": "#/definitions/Expr",
          "description": "Last value (inclusive)."
        },
        "body": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Action"
          },
          "description": "Loop body."
        }
      },
      "additionalProperties": false
    },
    "Action.forEachUnit": {
      "title": "For each unit",
      "description": "Run the body once per unit of a group (snapshotted and sorted before the first iteration).",
      "type": "object",
      "required": [
        "k",
        "group",
        "varName",
        "body"
      ],
      "properties": {
        "k": {
          "const": "forEachUnit"
        },
        "group": {
          "$ref": "#/definitions/Expr",
          "description": "Group to iterate."
        },
        "varName": {
          "type": "string",
          "pattern": "^[A-Za-z_][A-Za-z0-9_]*$",
          "description": "Loop variable receiving each unit id; an undeclared name becomes a local."
        },
        "body": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Action"
          },
          "description": "Loop body."
        }
      },
      "additionalProperties": false
    },
    "Action.generateSaveCode": {
      "title": "Generate save code",
      "description": "Encode named values into a tamper-evident code bound to this map, stored in a string variable.",
      "type": "object",
      "required": [
        "k",
        "player",
        "toVar",
        "data"
      ],
      "properties": {
        "k": {
          "const": "generateSaveCode"
        },
        "player": {
          "$ref": "#/definitions/Expr",
          "description": "Seat id (0-based) of the player."
        },
        "toVar": {
          "type": "string",
          "minLength": 1,
          "description": "String variable receiving the code."
        },
        "data": {
          "type": "object",
          "propertyNames": {
            "minLength": 1
          },
          "additionalProperties": {
            "$ref": "#/definitions/Expr"
          },
          "description": "Field name → value expression."
        }
      },
      "additionalProperties": false
    },
    "Action.hashtableClear": {
      "title": "Hashtable clear",
      "description": "Remove every entry, or only those under key1.",
      "type": "object",
      "required": [
        "k",
        "table"
      ],
      "properties": {
        "k": {
          "const": "hashtableClear"
        },
        "table": {
          "type": "string",
          "minLength": 1,
          "description": "Hashtable name."
        },
        "key1": {
          "$ref": "#/definitions/Expr",
          "description": "Parent key to flush; absent = whole table."
        }
      },
      "additionalProperties": false
    },
    "Action.hashtableRemove": {
      "title": "Hashtable remove",
      "description": "Remove the value under (key1, key2).",
      "type": "object",
      "required": [
        "k",
        "table",
        "key1",
        "key2"
      ],
      "properties": {
        "k": {
          "const": "hashtableRemove"
        },
        "table": {
          "type": "string",
          "minLength": 1,
          "description": "Hashtable name."
        },
        "key1": {
          "$ref": "#/definitions/Expr",
          "description": "Parent key."
        },
        "key2": {
          "$ref": "#/definitions/Expr",
          "description": "Child key."
        }
      },
      "additionalProperties": false
    },
    "Action.hashtableSet": {
      "title": "Hashtable set",
      "description": "Store a value under (key1, key2). New keys past the table cap are dropped.",
      "type": "object",
      "required": [
        "k",
        "table",
        "key1",
        "key2",
        "value"
      ],
      "properties": {
        "k": {
          "const": "hashtableSet"
        },
        "table": {
          "type": "string",
          "minLength": 1,
          "description": "Hashtable name."
        },
        "key1": {
          "$ref": "#/definitions/Expr",
          "description": "Parent key (int or string)."
        },
        "key2": {
          "$ref": "#/definitions/Expr",
          "description": "Child key (int or string)."
        },
        "value": {
          "$ref": "#/definitions/Expr",
          "description": "Value (coerced to the table type)."
        }
      },
      "additionalProperties": false
    },
    "Action.hideDialog": {
      "title": "Hide dialog",
      "description": "Close a dialog.",
      "type": "object",
      "required": [
        "k",
        "dialogId"
      ],
      "properties": {
        "k": {
          "const": "hideDialog"
        },
        "dialogId": {
          "type": "string",
          "minLength": 1,
          "description": "Dialog id."
        }
      },
      "additionalProperties": false
    },
    "Action.if": {
      "title": "If / then / else",
      "description": "Run `then` when cond is true, else `else`.",
      "type": "object",
      "required": [
        "k",
        "cond",
        "then"
      ],
      "properties": {
        "k": {
          "const": "if"
        },
        "cond": {
          "$ref": "#/definitions/Expr",
          "description": "Condition."
        },
        "then": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Action"
          },
          "description": "Actions when true."
        },
        "else": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Action"
          },
          "description": "Actions when false."
        }
      },
      "additionalProperties": false
    },
    "Action.killUnit": {
      "title": "Kill unit",
      "description": "Kill a unit through the normal death path.",
      "type": "object",
      "required": [
        "k",
        "unit"
      ],
      "properties": {
        "k": {
          "const": "killUnit"
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "The unit (entity id). A dead or missing unit is harmless: reads give zero values, writes do nothing."
        }
      },
      "additionalProperties": false
    },
    "Action.moveUnit": {
      "title": "Move unit instantly",
      "description": "Teleport a unit to a tile.",
      "type": "object",
      "required": [
        "k",
        "unit",
        "to"
      ],
      "properties": {
        "k": {
          "const": "moveUnit"
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "The unit (entity id). A dead or missing unit is harmless: reads give zero values, writes do nothing."
        },
        "to": {
          "$ref": "#/definitions/Expr",
          "description": "Destination tile."
        }
      },
      "additionalProperties": false
    },
    "Action.order": {
      "title": "Order units",
      "description": "Issue an order to a group, applied inside the tick (never through lockstep).",
      "type": "object",
      "required": [
        "k",
        "units",
        "order"
      ],
      "properties": {
        "k": {
          "const": "order"
        },
        "units": {
          "$ref": "#/definitions/Expr",
          "description": "Units to order (a single unit also works)."
        },
        "order": {
          "type": "string",
          "enum": [
            "move",
            "attack_move",
            "attack",
            "stop",
            "hold",
            "patrol",
            "follow"
          ],
          "description": "Order kind."
        },
        "to": {
          "$ref": "#/definitions/Expr",
          "description": "Target tile for move / attack_move / patrol."
        },
        "target": {
          "$ref": "#/definitions/Expr",
          "description": "Target unit for attack / follow."
        },
        "queue": {
          "type": "boolean",
          "description": "Queue after current orders."
        }
      },
      "additionalProperties": false
    },
    "Action.pauseTimer": {
      "title": "Pause timer",
      "description": "Pause a timer.",
      "type": "object",
      "required": [
        "k",
        "timer"
      ],
      "properties": {
        "k": {
          "const": "pauseTimer"
        },
        "timer": {
          "type": "string",
          "minLength": 1,
          "description": "Timer name."
        }
      },
      "additionalProperties": false
    },
    "Action.ping": {
      "title": "Ping minimap",
      "description": "Ping a tile on the minimap.",
      "type": "object",
      "required": [
        "k",
        "players",
        "at"
      ],
      "properties": {
        "k": {
          "const": "ping"
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null` for everyone."
        },
        "at": {
          "$ref": "#/definitions/Expr",
          "description": "Tile to ping."
        },
        "colorHex": {
          "type": "string",
          "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
          "description": "Colour (default #ffcc00)."
        },
        "durationTicks": {
          "type": "integer",
          "minimum": 1,
          "description": "Duration in ticks (default 60)."
        }
      },
      "additionalProperties": false
    },
    "Action.playCutscene": {
      "title": "Play cutscene",
      "description": "Start a map scene for these seats; the sim records only the start.",
      "type": "object",
      "required": [
        "k",
        "sceneId",
        "players"
      ],
      "properties": {
        "k": {
          "const": "playCutscene"
        },
        "sceneId": {
          "type": "string",
          "description": "Scene id."
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null` for everyone."
        }
      },
      "additionalProperties": false
    },
    "Action.playSound": {
      "title": "Play sound",
      "description": "Play a sound effect (base game or map custom sound).",
      "type": "object",
      "required": [
        "k",
        "soundId",
        "players"
      ],
      "properties": {
        "k": {
          "const": "playSound"
        },
        "soundId": {
          "type": "string",
          "description": "Sound id."
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null` for everyone."
        }
      },
      "additionalProperties": false
    },
    "Action.raiseEvent": {
      "title": "Raise custom event",
      "description": "Fire the `custom` event of this name later in the same tick.",
      "type": "object",
      "required": [
        "k",
        "name"
      ],
      "properties": {
        "k": {
          "const": "raiseEvent"
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "description": "Event name."
        },
        "value": {
          "$ref": "#/definitions/Expr",
          "description": "Event value payload."
        },
        "text": {
          "$ref": "#/definitions/Expr",
          "description": "Event text payload (default: the name)."
        }
      },
      "additionalProperties": false
    },
    "Action.removeCounter": {
      "title": "Remove counter",
      "description": "Remove a counter.",
      "type": "object",
      "required": [
        "k",
        "id"
      ],
      "properties": {
        "k": {
          "const": "removeCounter"
        },
        "id": {
          "type": "string",
          "minLength": 1,
          "description": "Counter id."
        }
      },
      "additionalProperties": false
    },
    "Action.removeLeaderboardRow": {
      "title": "Remove leaderboard row",
      "description": "Remove a leaderboard row.",
      "type": "object",
      "required": [
        "k",
        "rowId"
      ],
      "properties": {
        "k": {
          "const": "removeLeaderboardRow"
        },
        "rowId": {
          "type": "string",
          "minLength": 1,
          "description": "Row id."
        }
      },
      "additionalProperties": false
    },
    "Action.removeUnit": {
      "title": "Remove unit",
      "description": "Remove a unit outright — no death, corpse or bounty.",
      "type": "object",
      "required": [
        "k",
        "unit"
      ],
      "properties": {
        "k": {
          "const": "removeUnit"
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "The unit (entity id). A dead or missing unit is harmless: reads give zero values, writes do nothing."
        }
      },
      "additionalProperties": false
    },
    "Action.removeUnitFromGroup": {
      "title": "Remove unit from group",
      "description": "Remove a unit from a unitGroup variable.",
      "type": "object",
      "required": [
        "k",
        "varName",
        "unit"
      ],
      "properties": {
        "k": {
          "const": "removeUnitFromGroup"
        },
        "varName": {
          "type": "string",
          "minLength": 1,
          "description": "unitGroup variable."
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "The unit (entity id). A dead or missing unit is harmless: reads give zero values, writes do nothing."
        }
      },
      "additionalProperties": false
    },
    "Action.resumeTimer": {
      "title": "Resume timer",
      "description": "Resume a paused timer.",
      "type": "object",
      "required": [
        "k",
        "timer"
      ],
      "properties": {
        "k": {
          "const": "resumeTimer"
        },
        "timer": {
          "type": "string",
          "minLength": 1,
          "description": "Timer name."
        }
      },
      "additionalProperties": false
    },
    "Action.return": {
      "title": "Return",
      "description": "Leave the current function, yielding `value`. At trigger level (no value allowed) it stops the trigger.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "return"
        },
        "value": {
          "$ref": "#/definitions/Expr",
          "description": "Return value (functions with a return type)."
        }
      },
      "additionalProperties": false
    },
    "Action.runTrigger": {
      "title": "Run trigger",
      "description": "Run another trigger's actions inline in this event context.",
      "type": "object",
      "required": [
        "k",
        "triggerId"
      ],
      "properties": {
        "k": {
          "const": "runTrigger"
        },
        "triggerId": {
          "type": "string",
          "minLength": 1,
          "description": "Trigger id."
        },
        "checkConditions": {
          "type": "boolean",
          "description": "Evaluate its conditions first (and count it as fired)."
        }
      },
      "additionalProperties": false
    },
    "Action.setAlliance": {
      "title": "Set alliance",
      "description": "Ally B to A's team, or put B back on its own team.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b",
        "allied"
      ],
      "properties": {
        "k": {
          "const": "setAlliance"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        },
        "allied": {
          "type": "boolean",
          "description": "Allied."
        }
      },
      "additionalProperties": false
    },
    "Action.setBuildableRegion": {
      "title": "Restrict building",
      "description": "Limit where a seat may build: a list of regions, [] for nowhere, or null to restore the default.",
      "type": "object",
      "required": [
        "k",
        "player",
        "regions"
      ],
      "properties": {
        "k": {
          "const": "setBuildableRegion"
        },
        "player": {
          "$ref": "#/definitions/Expr",
          "description": "Seat id (0-based) of the player."
        },
        "regions": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string",
            "minLength": 1
          },
          "description": "Region ids, or null."
        }
      },
      "additionalProperties": false
    },
    "Action.setCounter": {
      "title": "Set counter",
      "description": "Create or update an on-screen counter.",
      "type": "object",
      "required": [
        "k",
        "id",
        "label",
        "value"
      ],
      "properties": {
        "k": {
          "const": "setCounter"
        },
        "id": {
          "type": "string",
          "minLength": 1,
          "description": "Counter id."
        },
        "label": {
          "$ref": "#/definitions/Text",
          "description": "Label template."
        },
        "value": {
          "$ref": "#/definitions/Expr",
          "description": "Value."
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null`/absent for everyone."
        },
        "order": {
          "type": "integer",
          "description": "Sort position."
        }
      },
      "additionalProperties": false
    },
    "Action.setLeaderboardRow": {
      "title": "Leaderboard row",
      "description": "Create or update a leaderboard row.",
      "type": "object",
      "required": [
        "k",
        "rowId",
        "label",
        "value"
      ],
      "properties": {
        "k": {
          "const": "setLeaderboardRow"
        },
        "rowId": {
          "type": "string",
          "minLength": 1,
          "description": "Row id."
        },
        "label": {
          "$ref": "#/definitions/Text",
          "description": "Label template."
        },
        "value": {
          "$ref": "#/definitions/Expr",
          "description": "Value."
        },
        "order": {
          "type": "integer",
          "description": "Sort position."
        }
      },
      "additionalProperties": false
    },
    "Action.setLeaderboardTitle": {
      "title": "Leaderboard title",
      "description": "Set the leaderboard title (\"\" hides it).",
      "type": "object",
      "required": [
        "k",
        "title"
      ],
      "properties": {
        "k": {
          "const": "setLeaderboardTitle"
        },
        "title": {
          "$ref": "#/definitions/Text",
          "description": "Title template."
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null`/absent for everyone."
        }
      },
      "additionalProperties": false
    },
    "Action.setQuest": {
      "title": "Set quest",
      "description": "Create or update a quest-log entry.",
      "type": "object",
      "required": [
        "k",
        "questId",
        "title",
        "description",
        "state"
      ],
      "properties": {
        "k": {
          "const": "setQuest"
        },
        "questId": {
          "type": "string",
          "minLength": 1,
          "description": "Quest id."
        },
        "title": {
          "$ref": "#/definitions/Text",
          "description": "Title template."
        },
        "description": {
          "$ref": "#/definitions/Text",
          "description": "Description template."
        },
        "state": {
          "type": "string",
          "enum": [
            "active",
            "completed",
            "failed"
          ],
          "description": "Quest state."
        },
        "icon": {
          "type": "string",
          "description": "Icon key."
        },
        "order": {
          "type": "integer",
          "description": "Sort position."
        }
      },
      "additionalProperties": false
    },
    "Action.setResource": {
      "title": "Set resource",
      "description": "Set a player's resource stock (rounded, ≥ 0).",
      "type": "object",
      "required": [
        "k",
        "player",
        "resource",
        "value"
      ],
      "properties": {
        "k": {
          "const": "setResource"
        },
        "player": {
          "$ref": "#/definitions/Expr",
          "description": "Seat id (0-based) of the player."
        },
        "resource": {
          "type": "string",
          "enum": [
            "gold",
            "lumber",
            "stone",
            "oil"
          ],
          "description": "Resource kind."
        },
        "value": {
          "$ref": "#/definitions/Expr",
          "description": "New amount."
        }
      },
      "additionalProperties": false
    },
    "Action.setUnitHp": {
      "title": "Set unit HP",
      "description": "Set HP (clamped to 0..max).",
      "type": "object",
      "required": [
        "k",
        "unit",
        "value"
      ],
      "properties": {
        "k": {
          "const": "setUnitHp"
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "The unit (entity id). A dead or missing unit is harmless: reads give zero values, writes do nothing."
        },
        "value": {
          "$ref": "#/definitions/Expr",
          "description": "HP or percentage."
        },
        "percent": {
          "type": "boolean",
          "description": "Treat value as a percentage of max HP."
        }
      },
      "additionalProperties": false
    },
    "Action.setUnitInvulnerable": {
      "title": "Set invulnerable",
      "description": "Toggle invulnerability.",
      "type": "object",
      "required": [
        "k",
        "unit",
        "on"
      ],
      "properties": {
        "k": {
          "const": "setUnitInvulnerable"
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "The unit (entity id). A dead or missing unit is harmless: reads give zero values, writes do nothing."
        },
        "on": {
          "type": "boolean",
          "description": "Invulnerable."
        }
      },
      "additionalProperties": false
    },
    "Action.setUnitKinematics": {
      "title": "Set kinematics",
      "description": "Tune kinematic parameters of a unit.",
      "type": "object",
      "required": [
        "k",
        "unit"
      ],
      "properties": {
        "k": {
          "const": "setUnitKinematics"
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "The unit (entity id). A dead or missing unit is harmless: reads give zero values, writes do nothing."
        },
        "friction": {
          "$ref": "#/definitions/Expr",
          "description": "Per-tick velocity multiplier."
        },
        "bounce": {
          "$ref": "#/definitions/Expr",
          "description": "Restitution on collision."
        },
        "minSpeedCutoff": {
          "$ref": "#/definitions/Expr",
          "description": "Speed below which motion stops."
        }
      },
      "additionalProperties": false
    },
    "Action.setUnitMana": {
      "title": "Set unit mana",
      "description": "Set mana (clamped).",
      "type": "object",
      "required": [
        "k",
        "unit",
        "value"
      ],
      "properties": {
        "k": {
          "const": "setUnitMana"
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "The unit (entity id). A dead or missing unit is harmless: reads give zero values, writes do nothing."
        },
        "value": {
          "$ref": "#/definitions/Expr",
          "description": "Mana."
        }
      },
      "additionalProperties": false
    },
    "Action.setUnitOwner": {
      "title": "Change owner",
      "description": "Give a unit to another seat.",
      "type": "object",
      "required": [
        "k",
        "unit",
        "player"
      ],
      "properties": {
        "k": {
          "const": "setUnitOwner"
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "The unit (entity id). A dead or missing unit is harmless: reads give zero values, writes do nothing."
        },
        "player": {
          "$ref": "#/definitions/Expr",
          "description": "Seat id (0-based) of the player."
        }
      },
      "additionalProperties": false
    },
    "Action.setVar": {
      "title": "Set variable",
      "description": "Write a variable (local, parameter, loop or global). Writing a global with a new value raises variableChanges.",
      "type": "object",
      "required": [
        "k",
        "name",
        "value"
      ],
      "properties": {
        "k": {
          "const": "setVar"
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "description": "Variable name."
        },
        "index": {
          "$ref": "#/definitions/Expr",
          "description": "Element index for an array."
        },
        "value": {
          "$ref": "#/definitions/Expr",
          "description": "New value (coerced to the variable type)."
        }
      },
      "additionalProperties": false
    },
    "Action.showDialog": {
      "title": "Show dialog",
      "description": "Open a modal dialog; clicks fire dialogButton.",
      "type": "object",
      "required": [
        "k",
        "dialogId",
        "title",
        "body",
        "buttons",
        "players"
      ],
      "properties": {
        "k": {
          "const": "showDialog"
        },
        "dialogId": {
          "type": "string",
          "minLength": 1,
          "description": "Dialog id."
        },
        "title": {
          "$ref": "#/definitions/Text",
          "description": "Title template."
        },
        "body": {
          "$ref": "#/definitions/Text",
          "description": "Body template."
        },
        "buttons": {
          "type": "array",
          "minItems": 1,
          "items": {
            "$ref": "#/definitions/Text"
          },
          "description": "Button labels (at least one)."
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null` for everyone."
        }
      },
      "additionalProperties": false
    },
    "Action.showText": {
      "title": "Show text",
      "description": "Show a text banner, resolved in the sim.",
      "type": "object",
      "required": [
        "k",
        "players",
        "text"
      ],
      "properties": {
        "k": {
          "const": "showText"
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null` for everyone."
        },
        "text": {
          "$ref": "#/definitions/Text",
          "description": "Text template (a bare string is accepted)."
        },
        "durationTicks": {
          "type": "integer",
          "minimum": 1,
          "description": "Display time in ticks (default 100)."
        }
      },
      "additionalProperties": false
    },
    "Action.startTimer": {
      "title": "Start timer",
      "description": "Start (or restart) a named timer.",
      "type": "object",
      "required": [
        "k",
        "timer",
        "ticks"
      ],
      "properties": {
        "k": {
          "const": "startTimer"
        },
        "timer": {
          "type": "string",
          "minLength": 1,
          "description": "Timer name."
        },
        "ticks": {
          "$ref": "#/definitions/Expr",
          "description": "Duration in ticks (≥ 1)."
        },
        "periodic": {
          "type": "boolean",
          "description": "Restart on expiry."
        }
      },
      "additionalProperties": false
    },
    "Action.stopCutscene": {
      "title": "Stop cutscene",
      "description": "End running cutscenes for these seats.",
      "type": "object",
      "required": [
        "k",
        "players"
      ],
      "properties": {
        "k": {
          "const": "stopCutscene"
        },
        "players": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer"
          },
          "description": "Seats that see this, or `null` for everyone."
        }
      },
      "additionalProperties": false
    },
    "Action.stopTimer": {
      "title": "Stop timer",
      "description": "Stop and delete a timer.",
      "type": "object",
      "required": [
        "k",
        "timer"
      ],
      "properties": {
        "k": {
          "const": "stopTimer"
        },
        "timer": {
          "type": "string",
          "minLength": 1,
          "description": "Timer name."
        }
      },
      "additionalProperties": false
    },
    "Action.stopTrigger": {
      "title": "Stop trigger",
      "description": "Abandon the rest of this trigger. Not allowed inside functions (use return).",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "stopTrigger"
        }
      },
      "additionalProperties": false
    },
    "Action.updateUIFrame": {
      "title": "Update UI frame",
      "description": "Update fields of an existing frame.",
      "type": "object",
      "required": [
        "k",
        "frameId"
      ],
      "properties": {
        "k": {
          "const": "updateUIFrame"
        },
        "frameId": {
          "type": "string",
          "minLength": 1,
          "description": "Frame id."
        },
        "title": {
          "$ref": "#/definitions/Text",
          "description": "Title template."
        },
        "text": {
          "$ref": "#/definitions/Text",
          "description": "Body template."
        },
        "progress": {
          "$ref": "#/definitions/Expr",
          "description": "Progress 0..1."
        },
        "progressColor": {
          "type": "string",
          "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
          "description": "Progress bar colour."
        },
        "visible": {
          "type": "boolean",
          "description": "Visible."
        }
      },
      "additionalProperties": false
    },
    "Action.victory": {
      "title": "Victory",
      "description": "Declare a seat victorious.",
      "type": "object",
      "required": [
        "k",
        "player"
      ],
      "properties": {
        "k": {
          "const": "victory"
        },
        "player": {
          "$ref": "#/definitions/Expr",
          "description": "Seat id (0-based) of the player."
        }
      },
      "additionalProperties": false
    },
    "Action.wait": {
      "title": "Wait",
      "description": "Suspend this trigger for N ticks (min 1) and resume at the next action. Locals and loop state survive, including across save/load. Not allowed inside functions.",
      "type": "object",
      "required": [
        "k",
        "ticks"
      ],
      "properties": {
        "k": {
          "const": "wait"
        },
        "ticks": {
          "$ref": "#/definitions/Expr",
          "description": "Ticks to wait."
        }
      },
      "additionalProperties": false
    },
    "Action.while": {
      "title": "While",
      "description": "Repeat the body while cond holds, at most maxIterations (hard cap 10000) times per run.",
      "type": "object",
      "required": [
        "k",
        "cond",
        "body"
      ],
      "properties": {
        "k": {
          "const": "while"
        },
        "cond": {
          "$ref": "#/definitions/Expr",
          "description": "Loop condition, checked before each iteration."
        },
        "body": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Action"
          },
          "description": "Loop body."
        },
        "maxIterations": {
          "type": "integer",
          "minimum": 1,
          "maximum": 10000,
          "description": "Iteration cap (≤ 10000)."
        }
      },
      "additionalProperties": false
    },
    "Event": {
      "description": "A trigger event, discriminated on `k`.",
      "oneOf": [
        {
          "$ref": "#/definitions/Event.mapInit"
        },
        {
          "$ref": "#/definitions/Event.periodic"
        },
        {
          "$ref": "#/definitions/Event.atTick"
        },
        {
          "$ref": "#/definitions/Event.timerExpires"
        },
        {
          "$ref": "#/definitions/Event.unitEntersRegion"
        },
        {
          "$ref": "#/definitions/Event.unitLeavesRegion"
        },
        {
          "$ref": "#/definitions/Event.unitDies"
        },
        {
          "$ref": "#/definitions/Event.unitTrained"
        },
        {
          "$ref": "#/definitions/Event.buildingComplete"
        },
        {
          "$ref": "#/definitions/Event.unitAttacked"
        },
        {
          "$ref": "#/definitions/Event.heroLevels"
        },
        {
          "$ref": "#/definitions/Event.itemPickedUp"
        },
        {
          "$ref": "#/definitions/Event.playerEliminated"
        },
        {
          "$ref": "#/definitions/Event.upgradeComplete"
        },
        {
          "$ref": "#/definitions/Event.variableChanges"
        },
        {
          "$ref": "#/definitions/Event.dialogButton"
        },
        {
          "$ref": "#/definitions/Event.playerChats"
        },
        {
          "$ref": "#/definitions/Event.playerKeyPress"
        },
        {
          "$ref": "#/definitions/Event.projectileHits"
        },
        {
          "$ref": "#/definitions/Event.custom"
        },
        {
          "$ref": "#/definitions/Event.cutsceneFinished"
        }
      ]
    },
    "Event.atTick": {
      "title": "At tick",
      "description": "Fires once on an exact tick.",
      "type": "object",
      "required": [
        "k",
        "tick"
      ],
      "properties": {
        "k": {
          "const": "atTick"
        },
        "tick": {
          "type": "integer",
          "minimum": 0,
          "description": "Tick number."
        }
      },
      "additionalProperties": false
    },
    "Event.buildingComplete": {
      "title": "Building completes",
      "description": "Fires when construction finishes.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "buildingComplete"
        },
        "filter": {
          "$ref": "#/definitions/UnitFilter",
          "description": "Optional unit filter; every set field is ANDed. `aliveOnly` defaults to true."
        }
      },
      "additionalProperties": false
    },
    "Event.custom": {
      "title": "Custom event",
      "description": "Fires when `raiseEvent` raises this name (same tick, capped cascade).",
      "type": "object",
      "required": [
        "k",
        "name"
      ],
      "properties": {
        "k": {
          "const": "custom"
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "description": "Event name."
        }
      },
      "additionalProperties": false
    },
    "Event.cutsceneFinished": {
      "title": "Cutscene finished",
      "description": "Fires when a cutscene ends. Event value: 0 played out, 1 skipped by every human viewer, 2 replaced by another scene, 3 stopped by a trigger.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "cutsceneFinished"
        },
        "sceneId": {
          "type": "string",
          "description": "Only this scene."
        }
      },
      "additionalProperties": false
    },
    "Event.dialogButton": {
      "title": "Dialog button clicked",
      "description": "Fires when a player clicks a trigger dialog button (arrives through lockstep); event value = button index.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "dialogButton"
        },
        "dialogId": {
          "type": "string",
          "minLength": 1,
          "description": "Only this dialog."
        },
        "buttonIndex": {
          "type": "integer",
          "minimum": 0,
          "description": "Only this 0-based button."
        }
      },
      "additionalProperties": false
    },
    "Event.heroLevels": {
      "title": "Hero levels up",
      "description": "Fires when a hero gains a level; event value = new level.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "heroLevels"
        },
        "filter": {
          "$ref": "#/definitions/UnitFilter",
          "description": "Optional unit filter; every set field is ANDed. `aliveOnly` defaults to true."
        }
      },
      "additionalProperties": false
    },
    "Event.itemPickedUp": {
      "title": "Item picked up",
      "description": "Fires when a hero picks up an item.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "itemPickedUp"
        },
        "itemId": {
          "type": "string",
          "description": "Only this item id; absent = any."
        }
      },
      "additionalProperties": false
    },
    "Event.mapInit": {
      "title": "Map initialisation",
      "description": "Fires once on the first tick, after every global initialiser.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "mapInit"
        }
      },
      "additionalProperties": false
    },
    "Event.periodic": {
      "title": "Every N ticks",
      "description": "Fires when (tick − offset) is a multiple of everyTicks, from tick `offsetTicks` on.",
      "type": "object",
      "required": [
        "k",
        "everyTicks"
      ],
      "properties": {
        "k": {
          "const": "periodic"
        },
        "everyTicks": {
          "type": "integer",
          "minimum": 1,
          "description": "Period in ticks (20 = 1 second)."
        },
        "offsetTicks": {
          "type": "integer",
          "description": "Phase offset in ticks."
        }
      },
      "additionalProperties": false
    },
    "Event.playerChats": {
      "title": "Player chats",
      "description": "Fires when a player sends a chat line; event text = the line.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "playerChats"
        },
        "pattern": {
          "type": "string",
          "description": "Text to match; absent = any message."
        },
        "matchMode": {
          "type": "string",
          "enum": [
            "exact",
            "prefix",
            "contains"
          ],
          "description": "How `pattern` matches (default exact)."
        },
        "player": {
          "type": "integer",
          "description": "Only this seat."
        }
      },
      "additionalProperties": false
    },
    "Event.playerEliminated": {
      "title": "Player eliminated",
      "description": "Fires when a seat is eliminated.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "playerEliminated"
        },
        "player": {
          "type": "integer",
          "description": "Only this seat; absent = any."
        }
      },
      "additionalProperties": false
    },
    "Event.playerKeyPress": {
      "title": "Player presses key",
      "description": "Fires when a player presses or releases a key (through lockstep); event text = key, value = 1 down / 0 up.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "playerKeyPress"
        },
        "key": {
          "type": "string",
          "description": "Key name, e.g. 'w', 'Space', 'ArrowUp'."
        },
        "state": {
          "type": "string",
          "enum": [
            "down",
            "up"
          ],
          "description": "down or up (absent = both)."
        },
        "player": {
          "type": "integer",
          "description": "Only this seat."
        }
      },
      "additionalProperties": false
    },
    "Event.projectileHits": {
      "title": "Projectile hits",
      "description": "Fires when a projectile strikes its target or a bomb detonates. Event unit = target, other unit = shooter, value = damage.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "projectileHits"
        },
        "tag": {
          "type": "string",
          "description": "Match the shooter's authored tag, the spell id, the shooter's unit type or the projectile type."
        }
      },
      "additionalProperties": false
    },
    "Event.timerExpires": {
      "title": "Timer expires",
      "description": "Fires when a named timer reaches zero.",
      "type": "object",
      "required": [
        "k",
        "timer"
      ],
      "properties": {
        "k": {
          "const": "timerExpires"
        },
        "timer": {
          "type": "string",
          "minLength": 1,
          "description": "Timer name."
        }
      },
      "additionalProperties": false
    },
    "Event.unitAttacked": {
      "title": "Unit attacked",
      "description": "Fires when a unit takes an attack; event value = damage, other unit = attacker.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "unitAttacked"
        },
        "filter": {
          "$ref": "#/definitions/UnitFilter",
          "description": "Optional unit filter; every set field is ANDed. `aliveOnly` defaults to true."
        }
      },
      "additionalProperties": false
    },
    "Event.unitDies": {
      "title": "Unit dies",
      "description": "Fires when a unit or building dies. The filter is matched against the unit as it was at death (`aliveOnly` is ignored); event unit = the dead unit, other unit = killer.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "unitDies"
        },
        "filter": {
          "$ref": "#/definitions/UnitFilter",
          "description": "Optional unit filter; every set field is ANDed. `aliveOnly` defaults to true."
        }
      },
      "additionalProperties": false
    },
    "Event.unitEntersRegion": {
      "title": "Unit enters region",
      "description": "Fires for each unit whose tile moved into the region this tick. Units that start inside do not count.",
      "type": "object",
      "required": [
        "k",
        "region"
      ],
      "properties": {
        "k": {
          "const": "unitEntersRegion"
        },
        "region": {
          "type": "string",
          "minLength": 1,
          "description": "Region id."
        },
        "filter": {
          "$ref": "#/definitions/UnitFilter",
          "description": "Optional unit filter; every set field is ANDed. `aliveOnly` defaults to true."
        }
      },
      "additionalProperties": false
    },
    "Event.unitLeavesRegion": {
      "title": "Unit leaves region",
      "description": "Fires for each unit that left the region this tick — including by dying or being removed (matched against its last snapshot).",
      "type": "object",
      "required": [
        "k",
        "region"
      ],
      "properties": {
        "k": {
          "const": "unitLeavesRegion"
        },
        "region": {
          "type": "string",
          "minLength": 1,
          "description": "Region id."
        },
        "filter": {
          "$ref": "#/definitions/UnitFilter",
          "description": "Optional unit filter; every set field is ANDed. `aliveOnly` defaults to true."
        }
      },
      "additionalProperties": false
    },
    "Event.unitTrained": {
      "title": "Unit trained",
      "description": "Fires when production completes a unit.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "unitTrained"
        },
        "filter": {
          "$ref": "#/definitions/UnitFilter",
          "description": "Optional unit filter; every set field is ANDed. `aliveOnly` defaults to true."
        }
      },
      "additionalProperties": false
    },
    "Event.upgradeComplete": {
      "title": "Upgrade completes",
      "description": "Fires when research completes; event text = upgrade id.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "upgradeComplete"
        },
        "upgradeId": {
          "type": "string",
          "description": "Only this upgrade; absent = any."
        }
      },
      "additionalProperties": false
    },
    "Event.variableChanges": {
      "title": "Variable changes",
      "description": "Fires (in the cascade phase) when a GLOBAL variable is written with a different value.",
      "type": "object",
      "required": [
        "k",
        "name"
      ],
      "properties": {
        "k": {
          "const": "variableChanges"
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "description": "Global variable name."
        }
      },
      "additionalProperties": false
    },
    "Expr": {
      "description": "An expression node, discriminated on `k`. A bare number, string or boolean is accepted wherever an expression is expected.",
      "oneOf": [
        {
          "type": "number",
          "description": "Literal shorthand: an integer becomes {\"k\":\"int\"}, any other number {\"k\":\"real\"}."
        },
        {
          "type": "string",
          "description": "Literal shorthand: always a text literal {\"k\":\"str\"} — use {\"k\":\"var\",\"name\":…} for a variable."
        },
        {
          "type": "boolean",
          "description": "Literal shorthand for {\"k\":\"bool\"}."
        },
        {
          "$ref": "#/definitions/Expr.int"
        },
        {
          "$ref": "#/definitions/Expr.real"
        },
        {
          "$ref": "#/definitions/Expr.bool"
        },
        {
          "$ref": "#/definitions/Expr.str"
        },
        {
          "$ref": "#/definitions/Expr.point"
        },
        {
          "$ref": "#/definitions/Expr.nullUnit"
        },
        {
          "$ref": "#/definitions/Expr.var"
        },
        {
          "$ref": "#/definitions/Expr.add"
        },
        {
          "$ref": "#/definitions/Expr.sub"
        },
        {
          "$ref": "#/definitions/Expr.mul"
        },
        {
          "$ref": "#/definitions/Expr.div"
        },
        {
          "$ref": "#/definitions/Expr.mod"
        },
        {
          "$ref": "#/definitions/Expr.neg"
        },
        {
          "$ref": "#/definitions/Expr.abs"
        },
        {
          "$ref": "#/definitions/Expr.min"
        },
        {
          "$ref": "#/definitions/Expr.max"
        },
        {
          "$ref": "#/definitions/Expr.clamp"
        },
        {
          "$ref": "#/definitions/Expr.trunc"
        },
        {
          "$ref": "#/definitions/Expr.eq"
        },
        {
          "$ref": "#/definitions/Expr.ne"
        },
        {
          "$ref": "#/definitions/Expr.lt"
        },
        {
          "$ref": "#/definitions/Expr.le"
        },
        {
          "$ref": "#/definitions/Expr.gt"
        },
        {
          "$ref": "#/definitions/Expr.ge"
        },
        {
          "$ref": "#/definitions/Expr.and"
        },
        {
          "$ref": "#/definitions/Expr.or"
        },
        {
          "$ref": "#/definitions/Expr.not"
        },
        {
          "$ref": "#/definitions/Expr.tick"
        },
        {
          "$ref": "#/definitions/Expr.rand"
        },
        {
          "$ref": "#/definitions/Expr.playerResource"
        },
        {
          "$ref": "#/definitions/Expr.playerPop"
        },
        {
          "$ref": "#/definitions/Expr.playerMaxPop"
        },
        {
          "$ref": "#/definitions/Expr.playerAlive"
        },
        {
          "$ref": "#/definitions/Expr.playerTeam"
        },
        {
          "$ref": "#/definitions/Expr.playerHasUpgrade"
        },
        {
          "$ref": "#/definitions/Expr.isAllied"
        },
        {
          "$ref": "#/definitions/Expr.isEnemy"
        },
        {
          "$ref": "#/definitions/Expr.unitProp"
        },
        {
          "$ref": "#/definitions/Expr.unitPoint"
        },
        {
          "$ref": "#/definitions/Expr.entityByAid"
        },
        {
          "$ref": "#/definitions/Expr.unitsInRegion"
        },
        {
          "$ref": "#/definitions/Expr.unitsWithTag"
        },
        {
          "$ref": "#/definitions/Expr.unitsOfPlayer"
        },
        {
          "$ref": "#/definitions/Expr.unitsOfType"
        },
        {
          "$ref": "#/definitions/Expr.groupCount"
        },
        {
          "$ref": "#/definitions/Expr.groupAt"
        },
        {
          "$ref": "#/definitions/Expr.groupRandom"
        },
        {
          "$ref": "#/definitions/Expr.groupUnion"
        },
        {
          "$ref": "#/definitions/Expr.groupDiff"
        },
        {
          "$ref": "#/definitions/Expr.groupHas"
        },
        {
          "$ref": "#/definitions/Expr.groupEmpty"
        },
        {
          "$ref": "#/definitions/Expr.distance"
        },
        {
          "$ref": "#/definitions/Expr.pointX"
        },
        {
          "$ref": "#/definitions/Expr.pointY"
        },
        {
          "$ref": "#/definitions/Expr.regionCenter"
        },
        {
          "$ref": "#/definitions/Expr.randomPointInRegion"
        },
        {
          "$ref": "#/definitions/Expr.pointInRegion"
        },
        {
          "$ref": "#/definitions/Expr.offsetPoint"
        },
        {
          "$ref": "#/definitions/Expr.eventUnit"
        },
        {
          "$ref": "#/definitions/Expr.eventOtherUnit"
        },
        {
          "$ref": "#/definitions/Expr.eventPlayer"
        },
        {
          "$ref": "#/definitions/Expr.eventValue"
        },
        {
          "$ref": "#/definitions/Expr.eventString"
        },
        {
          "$ref": "#/definitions/Expr.counter"
        },
        {
          "$ref": "#/definitions/Expr.timerRemaining"
        },
        {
          "$ref": "#/definitions/Expr.timerRunning"
        },
        {
          "$ref": "#/definitions/Expr.triggerFireCount"
        },
        {
          "$ref": "#/definitions/Expr.concat"
        },
        {
          "$ref": "#/definitions/Expr.toStr"
        },
        {
          "$ref": "#/definitions/Expr.subStr"
        },
        {
          "$ref": "#/definitions/Expr.strLen"
        },
        {
          "$ref": "#/definitions/Expr.unitVelocity"
        },
        {
          "$ref": "#/definitions/Expr.hashString"
        },
        {
          "$ref": "#/definitions/Expr.verifySaveCode"
        },
        {
          "$ref": "#/definitions/Expr.call"
        },
        {
          "$ref": "#/definitions/Expr.hashtableGet"
        },
        {
          "$ref": "#/definitions/Expr.hashtableHas"
        },
        {
          "$ref": "#/definitions/Expr.hashtableCount"
        }
      ]
    },
    "Expr.abs": {
      "title": "Absolute value",
      "description": "Absolute value. Returns number.",
      "type": "object",
      "required": [
        "k",
        "a"
      ],
      "properties": {
        "k": {
          "const": "abs"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.add": {
      "title": "A + B",
      "description": "Sum. If either side is a string the two are concatenated. Returns number.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "add"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.and": {
      "title": "A and B",
      "description": "Logical AND. Short-circuits: B is not evaluated when A is false. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "and"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.bool": {
      "title": "Boolean",
      "description": "true or false. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "v"
      ],
      "properties": {
        "k": {
          "const": "bool"
        },
        "v": {
          "type": "boolean",
          "description": "The value."
        }
      },
      "additionalProperties": false
    },
    "Expr.call": {
      "title": "Call function",
      "description": "Call a user-defined function that declares a return type and yield its return value. Arguments are evaluated left to right and coerced to the parameter types. Recursion past the depth cap yields the zero value. Returns any.",
      "type": "object",
      "required": [
        "k",
        "fn"
      ],
      "properties": {
        "k": {
          "const": "call"
        },
        "fn": {
          "type": "string",
          "minLength": 1,
          "description": "Function name."
        },
        "args": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Expr"
          },
          "description": "Arguments, one per parameter, in order."
        }
      },
      "additionalProperties": false
    },
    "Expr.clamp": {
      "title": "Clamp",
      "description": "A limited to [lo, hi]; reversed bounds are swapped. Returns number.",
      "type": "object",
      "required": [
        "k",
        "a",
        "lo",
        "hi"
      ],
      "properties": {
        "k": {
          "const": "clamp"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Value to clamp."
        },
        "lo": {
          "$ref": "#/definitions/Expr",
          "description": "Lower bound."
        },
        "hi": {
          "$ref": "#/definitions/Expr",
          "description": "Upper bound."
        }
      },
      "additionalProperties": false
    },
    "Expr.concat": {
      "title": "Concatenate",
      "description": "A joined with B as strings. Returns string.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "concat"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.counter": {
      "title": "Counter value",
      "description": "Current value of an on-screen counter. Returns number.",
      "type": "object",
      "required": [
        "k",
        "id"
      ],
      "properties": {
        "k": {
          "const": "counter"
        },
        "id": {
          "type": "string",
          "minLength": 1,
          "description": "Counter id."
        }
      },
      "additionalProperties": false
    },
    "Expr.distance": {
      "title": "Distance",
      "description": "Euclidean tile distance; either side may be a unit or a point. Returns real.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "distance"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.div": {
      "title": "A ÷ B",
      "description": "Quotient. Division by zero yields 0 — never Infinity or NaN. Returns number.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "div"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.entityByAid": {
      "title": "Unit by authored id",
      "description": "The placed entity carrying this authored id, or no unit. Returns unit.",
      "type": "object",
      "required": [
        "k",
        "aid"
      ],
      "properties": {
        "k": {
          "const": "entityByAid"
        },
        "aid": {
          "type": "string",
          "description": "Authored id from the entity inspector."
        }
      },
      "additionalProperties": false
    },
    "Expr.eq": {
      "title": "A = B",
      "description": "Equality. Strings compare by code unit; groups element-wise; points by x then y. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "eq"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.eventOtherUnit": {
      "title": "Other unit",
      "description": "The killer for unitDies, the attacker for unitAttacked, the shooter for projectileHits. Returns unit.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "eventOtherUnit"
        }
      },
      "additionalProperties": false
    },
    "Expr.eventPlayer": {
      "title": "Triggering player",
      "description": "The seat the event is about, or -1. Returns player.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "eventPlayer"
        }
      },
      "additionalProperties": false
    },
    "Expr.eventString": {
      "title": "Event text",
      "description": "String payload: upgrade id, item id, chat text, custom event name. Returns string.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "eventString"
        }
      },
      "additionalProperties": false
    },
    "Expr.eventUnit": {
      "title": "Triggering unit",
      "description": "The unit the event is about (entered, died, trained, was attacked…). No unit when the event has none. Returns unit.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "eventUnit"
        }
      },
      "additionalProperties": false
    },
    "Expr.eventValue": {
      "title": "Event value",
      "description": "Numeric payload: dialog button index, hero level, damage, raised value. Returns number.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "eventValue"
        }
      },
      "additionalProperties": false
    },
    "Expr.ge": {
      "title": "A ≥ B",
      "description": "Greater than or equal. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "ge"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.groupAt": {
      "title": "Unit at index",
      "description": "0-based element of the sorted group; out of range gives no unit. Returns unit.",
      "type": "object",
      "required": [
        "k",
        "group",
        "index"
      ],
      "properties": {
        "k": {
          "const": "groupAt"
        },
        "group": {
          "$ref": "#/definitions/Expr",
          "description": "The group."
        },
        "index": {
          "$ref": "#/definitions/Expr",
          "description": "0-based index."
        }
      },
      "additionalProperties": false
    },
    "Expr.groupCount": {
      "title": "Group size",
      "description": "Number of units in a group. Returns int.",
      "type": "object",
      "required": [
        "k",
        "group"
      ],
      "properties": {
        "k": {
          "const": "groupCount"
        },
        "group": {
          "$ref": "#/definitions/Expr",
          "description": "The group."
        }
      },
      "additionalProperties": false
    },
    "Expr.groupDiff": {
      "title": "Group difference",
      "description": "Members of A not in B. Returns unitGroup.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "groupDiff"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.groupEmpty": {
      "title": "Empty group",
      "description": "A group with no units. Returns unitGroup.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "groupEmpty"
        }
      },
      "additionalProperties": false
    },
    "Expr.groupHas": {
      "title": "Group contains",
      "description": "True when the unit is in the group. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "group",
        "unit"
      ],
      "properties": {
        "k": {
          "const": "groupHas"
        },
        "group": {
          "$ref": "#/definitions/Expr",
          "description": "The group."
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "The unit (entity id). A dead or missing unit is harmless: reads give zero values, writes do nothing."
        }
      },
      "additionalProperties": false
    },
    "Expr.groupRandom": {
      "title": "Random unit of group",
      "description": "A random member (one PRNG draw, even for an empty group). Returns unit.",
      "type": "object",
      "required": [
        "k",
        "group"
      ],
      "properties": {
        "k": {
          "const": "groupRandom"
        },
        "group": {
          "$ref": "#/definitions/Expr",
          "description": "The group."
        }
      },
      "additionalProperties": false
    },
    "Expr.groupUnion": {
      "title": "Group union",
      "description": "Members of A or B. Returns unitGroup.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "groupUnion"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.gt": {
      "title": "A > B",
      "description": "Greater than. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "gt"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.hashString": {
      "title": "Hash text",
      "description": "Deterministic 32-bit FNV-1a hash. Returns int.",
      "type": "object",
      "required": [
        "k",
        "text"
      ],
      "properties": {
        "k": {
          "const": "hashString"
        },
        "text": {
          "$ref": "#/definitions/Expr",
          "description": "The text."
        }
      },
      "additionalProperties": false
    },
    "Expr.hashtableCount": {
      "title": "Hashtable size",
      "description": "Number of entries in the table, or under one parent key when key1 is set. Returns int.",
      "type": "object",
      "required": [
        "k",
        "table"
      ],
      "properties": {
        "k": {
          "const": "hashtableCount"
        },
        "table": {
          "type": "string",
          "minLength": 1,
          "description": "Hashtable name."
        },
        "key1": {
          "$ref": "#/definitions/Expr",
          "description": "Parent key to count children of."
        }
      },
      "additionalProperties": false
    },
    "Expr.hashtableGet": {
      "title": "Hashtable value",
      "description": "Value stored under (key1, key2), or the zero value of the table type. Keys are integers or strings. Returns any.",
      "type": "object",
      "required": [
        "k",
        "table",
        "key1",
        "key2"
      ],
      "properties": {
        "k": {
          "const": "hashtableGet"
        },
        "table": {
          "type": "string",
          "minLength": 1,
          "description": "Hashtable name."
        },
        "key1": {
          "$ref": "#/definitions/Expr",
          "description": "Parent key (int or string)."
        },
        "key2": {
          "$ref": "#/definitions/Expr",
          "description": "Child key (int or string)."
        }
      },
      "additionalProperties": false
    },
    "Expr.hashtableHas": {
      "title": "Hashtable has key",
      "description": "True when a value is stored under (key1, key2). Returns bool.",
      "type": "object",
      "required": [
        "k",
        "table",
        "key1",
        "key2"
      ],
      "properties": {
        "k": {
          "const": "hashtableHas"
        },
        "table": {
          "type": "string",
          "minLength": 1,
          "description": "Hashtable name."
        },
        "key1": {
          "$ref": "#/definitions/Expr",
          "description": "Parent key."
        },
        "key2": {
          "$ref": "#/definitions/Expr",
          "description": "Child key."
        }
      },
      "additionalProperties": false
    },
    "Expr.int": {
      "title": "Integer",
      "description": "An integer literal. Non-integers are truncated. Returns int.",
      "type": "object",
      "required": [
        "k",
        "v"
      ],
      "properties": {
        "k": {
          "const": "int"
        },
        "v": {
          "type": "number",
          "description": "The value."
        }
      },
      "additionalProperties": false
    },
    "Expr.isAllied": {
      "title": "Players are allied",
      "description": "True when seats A and B are on the same team. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "isAllied"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.isEnemy": {
      "title": "Players are enemies",
      "description": "True when seats A and B are not allied. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "isEnemy"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.le": {
      "title": "A ≤ B",
      "description": "Less than or equal. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "le"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.lt": {
      "title": "A < B",
      "description": "Less than. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "lt"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.max": {
      "title": "Maximum",
      "description": "The larger of A and B. Returns number.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "max"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.min": {
      "title": "Minimum",
      "description": "The smaller of A and B. Returns number.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "min"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.mod": {
      "title": "A mod B",
      "description": "Remainder (sign of A). Modulo by zero yields 0. Returns number.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "mod"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.mul": {
      "title": "A × B",
      "description": "Product. Returns number.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "mul"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.ne": {
      "title": "A ≠ B",
      "description": "Inequality. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "ne"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.neg": {
      "title": "Negate",
      "description": "Arithmetic negation. Returns number.",
      "type": "object",
      "required": [
        "k",
        "a"
      ],
      "properties": {
        "k": {
          "const": "neg"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.not": {
      "title": "Not A",
      "description": "Logical negation. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "a"
      ],
      "properties": {
        "k": {
          "const": "not"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.nullUnit": {
      "title": "No unit",
      "description": "Entity id 0. Every property of it reads as a zero value. Returns unit.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "nullUnit"
        }
      },
      "additionalProperties": false
    },
    "Expr.offsetPoint": {
      "title": "Offset point",
      "description": "A point moved by (dx, dy) tiles. Returns point.",
      "type": "object",
      "required": [
        "k",
        "p",
        "dx",
        "dy"
      ],
      "properties": {
        "k": {
          "const": "offsetPoint"
        },
        "p": {
          "$ref": "#/definitions/Expr",
          "description": "Point or unit."
        },
        "dx": {
          "$ref": "#/definitions/Expr",
          "description": "Tiles X."
        },
        "dy": {
          "$ref": "#/definitions/Expr",
          "description": "Tiles Y."
        }
      },
      "additionalProperties": false
    },
    "Expr.or": {
      "title": "A or B",
      "description": "Logical OR. Short-circuits: B is not evaluated when A is true. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "or"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.playerAlive": {
      "title": "Player is alive",
      "description": "True while the seat has not been eliminated or defeated. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "player"
      ],
      "properties": {
        "k": {
          "const": "playerAlive"
        },
        "player": {
          "$ref": "#/definitions/Expr",
          "description": "Seat id (0-based) of the player."
        }
      },
      "additionalProperties": false
    },
    "Expr.playerHasUpgrade": {
      "title": "Player has upgrade",
      "description": "True once the seat has completed the research. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "player",
        "upgradeId"
      ],
      "properties": {
        "k": {
          "const": "playerHasUpgrade"
        },
        "player": {
          "$ref": "#/definitions/Expr",
          "description": "Seat id (0-based) of the player."
        },
        "upgradeId": {
          "type": "string",
          "description": "Upgrade id."
        }
      },
      "additionalProperties": false
    },
    "Expr.playerMaxPop": {
      "title": "Player's population cap",
      "description": "Population cap. Returns int.",
      "type": "object",
      "required": [
        "k",
        "player"
      ],
      "properties": {
        "k": {
          "const": "playerMaxPop"
        },
        "player": {
          "$ref": "#/definitions/Expr",
          "description": "Seat id (0-based) of the player."
        }
      },
      "additionalProperties": false
    },
    "Expr.playerPop": {
      "title": "Player's population",
      "description": "Population in use. Returns int.",
      "type": "object",
      "required": [
        "k",
        "player"
      ],
      "properties": {
        "k": {
          "const": "playerPop"
        },
        "player": {
          "$ref": "#/definitions/Expr",
          "description": "Seat id (0-based) of the player."
        }
      },
      "additionalProperties": false
    },
    "Expr.playerResource": {
      "title": "Player's resource",
      "description": "Current stock of one resource. Returns int.",
      "type": "object",
      "required": [
        "k",
        "player",
        "resource"
      ],
      "properties": {
        "k": {
          "const": "playerResource"
        },
        "player": {
          "$ref": "#/definitions/Expr",
          "description": "Seat id (0-based) of the player."
        },
        "resource": {
          "type": "string",
          "enum": [
            "gold",
            "lumber",
            "stone",
            "oil"
          ],
          "description": "Resource kind."
        }
      },
      "additionalProperties": false
    },
    "Expr.playerTeam": {
      "title": "Player's team",
      "description": "Team id of the seat, or -1. Returns int.",
      "type": "object",
      "required": [
        "k",
        "player"
      ],
      "properties": {
        "k": {
          "const": "playerTeam"
        },
        "player": {
          "$ref": "#/definitions/Expr",
          "description": "Seat id (0-based) of the player."
        }
      },
      "additionalProperties": false
    },
    "Expr.point": {
      "title": "Point",
      "description": "A tile coordinate built from two numbers. Returns point.",
      "type": "object",
      "required": [
        "k",
        "x",
        "y"
      ],
      "properties": {
        "k": {
          "const": "point"
        },
        "x": {
          "$ref": "#/definitions/Expr",
          "description": "Tile X."
        },
        "y": {
          "$ref": "#/definitions/Expr",
          "description": "Tile Y."
        }
      },
      "additionalProperties": false
    },
    "Expr.pointInRegion": {
      "title": "Point in region",
      "description": "True when the point (or unit) lies inside the region. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "p",
        "region"
      ],
      "properties": {
        "k": {
          "const": "pointInRegion"
        },
        "p": {
          "$ref": "#/definitions/Expr",
          "description": "Point or unit."
        },
        "region": {
          "type": "string",
          "minLength": 1,
          "description": "Region id."
        }
      },
      "additionalProperties": false
    },
    "Expr.pointX": {
      "title": "Point X",
      "description": "X of a point. Returns number.",
      "type": "object",
      "required": [
        "k",
        "p"
      ],
      "properties": {
        "k": {
          "const": "pointX"
        },
        "p": {
          "$ref": "#/definitions/Expr",
          "description": "The point."
        }
      },
      "additionalProperties": false
    },
    "Expr.pointY": {
      "title": "Point Y",
      "description": "Y of a point. Returns number.",
      "type": "object",
      "required": [
        "k",
        "p"
      ],
      "properties": {
        "k": {
          "const": "pointY"
        },
        "p": {
          "$ref": "#/definitions/Expr",
          "description": "The point."
        }
      },
      "additionalProperties": false
    },
    "Expr.rand": {
      "title": "Random integer",
      "description": "Inclusive random integer from the synchronised game PRNG. Always consumes exactly one draw. Returns int.",
      "type": "object",
      "required": [
        "k",
        "min",
        "max"
      ],
      "properties": {
        "k": {
          "const": "rand"
        },
        "min": {
          "$ref": "#/definitions/Expr",
          "description": "Lower bound (inclusive)."
        },
        "max": {
          "$ref": "#/definitions/Expr",
          "description": "Upper bound (inclusive)."
        }
      },
      "additionalProperties": false
    },
    "Expr.randomPointInRegion": {
      "title": "Random point in region",
      "description": "Random tile inside the region (always two PRNG draws). Returns point.",
      "type": "object",
      "required": [
        "k",
        "region"
      ],
      "properties": {
        "k": {
          "const": "randomPointInRegion"
        },
        "region": {
          "type": "string",
          "minLength": 1,
          "description": "Region id."
        }
      },
      "additionalProperties": false
    },
    "Expr.real": {
      "title": "Real number",
      "description": "A real-number literal. Returns real.",
      "type": "object",
      "required": [
        "k",
        "v"
      ],
      "properties": {
        "k": {
          "const": "real"
        },
        "v": {
          "type": "number",
          "description": "The value."
        }
      },
      "additionalProperties": false
    },
    "Expr.regionCenter": {
      "title": "Region centre",
      "description": "Integer centre tile of a region. Returns point.",
      "type": "object",
      "required": [
        "k",
        "region"
      ],
      "properties": {
        "k": {
          "const": "regionCenter"
        },
        "region": {
          "type": "string",
          "minLength": 1,
          "description": "Region id."
        }
      },
      "additionalProperties": false
    },
    "Expr.str": {
      "title": "Text",
      "description": "A string literal. Returns string.",
      "type": "object",
      "required": [
        "k",
        "v"
      ],
      "properties": {
        "k": {
          "const": "str"
        },
        "v": {
          "type": "string",
          "description": "The value."
        }
      },
      "additionalProperties": false
    },
    "Expr.strLen": {
      "title": "Text length",
      "description": "Length in UTF-16 code units. Returns int.",
      "type": "object",
      "required": [
        "k",
        "text"
      ],
      "properties": {
        "k": {
          "const": "strLen"
        },
        "text": {
          "$ref": "#/definitions/Expr",
          "description": "The text."
        }
      },
      "additionalProperties": false
    },
    "Expr.sub": {
      "title": "A − B",
      "description": "Difference. Returns number.",
      "type": "object",
      "required": [
        "k",
        "a",
        "b"
      ],
      "properties": {
        "k": {
          "const": "sub"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "b": {
          "$ref": "#/definitions/Expr",
          "description": "Right operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.subStr": {
      "title": "Substring",
      "description": "0-based slice; out-of-range start gives empty text. Returns string.",
      "type": "object",
      "required": [
        "k",
        "text",
        "start"
      ],
      "properties": {
        "k": {
          "const": "subStr"
        },
        "text": {
          "$ref": "#/definitions/Expr",
          "description": "Source text."
        },
        "start": {
          "$ref": "#/definitions/Expr",
          "description": "Start index."
        },
        "length": {
          "$ref": "#/definitions/Expr",
          "description": "Maximum length; absent = to the end."
        }
      },
      "additionalProperties": false
    },
    "Expr.tick": {
      "title": "Game tick",
      "description": "The current simulation tick (20 per second) — the only clock in the language. Returns int.",
      "type": "object",
      "required": [
        "k"
      ],
      "properties": {
        "k": {
          "const": "tick"
        }
      },
      "additionalProperties": false
    },
    "Expr.timerRemaining": {
      "title": "Timer ticks left",
      "description": "Ticks until the timer expires (0 if not running). Returns int.",
      "type": "object",
      "required": [
        "k",
        "timer"
      ],
      "properties": {
        "k": {
          "const": "timerRemaining"
        },
        "timer": {
          "type": "string",
          "minLength": 1,
          "description": "Timer name."
        }
      },
      "additionalProperties": false
    },
    "Expr.timerRunning": {
      "title": "Timer is running",
      "description": "True while the timer runs and is not paused. Returns bool.",
      "type": "object",
      "required": [
        "k",
        "timer"
      ],
      "properties": {
        "k": {
          "const": "timerRunning"
        },
        "timer": {
          "type": "string",
          "minLength": 1,
          "description": "Timer name."
        }
      },
      "additionalProperties": false
    },
    "Expr.toStr": {
      "title": "To text",
      "description": "Value as text. Non-integers print with `decimals` places (default 2). Returns string.",
      "type": "object",
      "required": [
        "k",
        "a"
      ],
      "properties": {
        "k": {
          "const": "toStr"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        },
        "decimals": {
          "type": "integer",
          "minimum": 0,
          "maximum": 6,
          "description": "Fixed decimal places, 0..6."
        }
      },
      "additionalProperties": false
    },
    "Expr.triggerFireCount": {
      "title": "Trigger fire count",
      "description": "How many times a trigger has fired. Returns int.",
      "type": "object",
      "required": [
        "k",
        "triggerId"
      ],
      "properties": {
        "k": {
          "const": "triggerFireCount"
        },
        "triggerId": {
          "type": "string",
          "minLength": 1,
          "description": "Trigger id."
        }
      },
      "additionalProperties": false
    },
    "Expr.trunc": {
      "title": "Truncate",
      "description": "Real to integer toward zero (`Math.trunc`) — the one real→int rule in the language. Returns int.",
      "type": "object",
      "required": [
        "k",
        "a"
      ],
      "properties": {
        "k": {
          "const": "trunc"
        },
        "a": {
          "$ref": "#/definitions/Expr",
          "description": "Left operand."
        }
      },
      "additionalProperties": false
    },
    "Expr.unitPoint": {
      "title": "Unit position",
      "description": "Tile position of a unit. Returns point.",
      "type": "object",
      "required": [
        "k",
        "unit"
      ],
      "properties": {
        "k": {
          "const": "unitPoint"
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "The unit (entity id). A dead or missing unit is harmless: reads give zero values, writes do nothing."
        }
      },
      "additionalProperties": false
    },
    "Expr.unitProp": {
      "title": "Unit property",
      "description": "Read a property of a unit. A unit that died THIS tick answers from its death snapshot; otherwise a missing unit gives the zero value. Returns any.",
      "type": "object",
      "required": [
        "k",
        "unit",
        "prop"
      ],
      "properties": {
        "k": {
          "const": "unitProp"
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "The unit (entity id). A dead or missing unit is harmless: reads give zero values, writes do nothing."
        },
        "prop": {
          "type": "string",
          "enum": [
            "hp",
            "maxHp",
            "hpPercent",
            "mana",
            "maxMana",
            "x",
            "y",
            "level",
            "xp",
            "kills",
            "owner",
            "alive",
            "isHero",
            "isBuilding",
            "isFlying",
            "typeId"
          ],
          "description": "Property to read."
        }
      },
      "additionalProperties": false
    },
    "Expr.unitVelocity": {
      "title": "Unit velocity",
      "description": "Kinematic velocity component or speed (tiles/tick); 0 without kinematics. Returns real.",
      "type": "object",
      "required": [
        "k",
        "unit",
        "axis"
      ],
      "properties": {
        "k": {
          "const": "unitVelocity"
        },
        "unit": {
          "$ref": "#/definitions/Expr",
          "description": "The unit (entity id). A dead or missing unit is harmless: reads give zero values, writes do nothing."
        },
        "axis": {
          "type": "string",
          "enum": [
            "x",
            "y",
            "speed"
          ],
          "description": "Component to read."
        }
      },
      "additionalProperties": false
    },
    "Expr.unitsInRegion": {
      "title": "Units in region",
      "description": "Sorted group of units whose tile is inside the region. Returns unitGroup.",
      "type": "object",
      "required": [
        "k",
        "region"
      ],
      "properties": {
        "k": {
          "const": "unitsInRegion"
        },
        "region": {
          "type": "string",
          "minLength": 1,
          "description": "Region id."
        },
        "filter": {
          "$ref": "#/definitions/UnitFilter",
          "description": "Optional unit filter; every set field is ANDed. `aliveOnly` defaults to true."
        }
      },
      "additionalProperties": false
    },
    "Expr.unitsOfPlayer": {
      "title": "Units of player",
      "description": "Sorted group of units owned by the seat. Returns unitGroup.",
      "type": "object",
      "required": [
        "k",
        "player"
      ],
      "properties": {
        "k": {
          "const": "unitsOfPlayer"
        },
        "player": {
          "$ref": "#/definitions/Expr",
          "description": "Seat id (0-based) of the player."
        },
        "filter": {
          "$ref": "#/definitions/UnitFilter",
          "description": "Optional unit filter; every set field is ANDed. `aliveOnly` defaults to true."
        }
      },
      "additionalProperties": false
    },
    "Expr.unitsOfType": {
      "title": "Units of type",
      "description": "Sorted group of units or buildings of one type id. Returns unitGroup.",
      "type": "object",
      "required": [
        "k",
        "unitTypeId"
      ],
      "properties": {
        "k": {
          "const": "unitsOfType"
        },
        "unitTypeId": {
          "type": "string",
          "description": "Unit or building type id."
        },
        "filter": {
          "$ref": "#/definitions/UnitFilter",
          "description": "Optional unit filter; every set field is ANDed. `aliveOnly` defaults to true."
        }
      },
      "additionalProperties": false
    },
    "Expr.unitsWithTag": {
      "title": "Units with tag",
      "description": "Sorted group of placed entities sharing an authored tag. Returns unitGroup.",
      "type": "object",
      "required": [
        "k",
        "tag"
      ],
      "properties": {
        "k": {
          "const": "unitsWithTag"
        },
        "tag": {
          "type": "string",
          "description": "Authored tag."
        },
        "filter": {
          "$ref": "#/definitions/UnitFilter",
          "description": "Optional unit filter; every set field is ANDed. `aliveOnly` defaults to true."
        }
      },
      "additionalProperties": false
    },
    "Expr.var": {
      "title": "Variable",
      "description": "Read a variable: a trigger/function local, a function parameter, a loop variable or a global (resolved in that order). Returns any.",
      "type": "object",
      "required": [
        "k",
        "name"
      ],
      "properties": {
        "k": {
          "const": "var"
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "description": "Variable name."
        },
        "index": {
          "$ref": "#/definitions/Expr",
          "description": "Element index for an array variable. Out-of-range reads yield the zero value."
        }
      },
      "additionalProperties": false
    },
    "Expr.verifySaveCode": {
      "title": "Read save code",
      "description": "Validate a save code for this map. With `field`, returns that field (or 0); without, returns 1 for a valid code and 0 otherwise. Returns any.",
      "type": "object",
      "required": [
        "k",
        "code"
      ],
      "properties": {
        "k": {
          "const": "verifySaveCode"
        },
        "code": {
          "$ref": "#/definitions/Expr",
          "description": "The code text."
        },
        "field": {
          "type": "string",
          "description": "Field to extract; `playerId` returns the issuing seat."
        }
      },
      "additionalProperties": false
    },
    "FunctionDef": {
      "type": "object",
      "required": [
        "name",
        "actions"
      ],
      "properties": {
        "name": {
          "type": "string",
          "description": "Function identifier.",
          "pattern": "^(?!__)[A-Za-z_][A-Za-z0-9_]*$"
        },
        "description": {
          "type": "string",
          "description": "Documentation."
        },
        "params": {
          "type": "array",
          "maxItems": 16,
          "items": {
            "$ref": "#/definitions/FunctionParam"
          },
          "description": "Typed parameters (≤ 16, scalars only)."
        },
        "returns": {
          "type": "string",
          "enum": [
            "int",
            "real",
            "bool",
            "string",
            "unit",
            "unitGroup",
            "point",
            "player",
            "region",
            "timer"
          ],
          "description": "Return type; absent = returns nothing."
        },
        "locals": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/VarDecl"
          },
          "description": "Per-call locals."
        },
        "actions": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Action"
          },
          "description": "Body. May not contain wait or stopTrigger."
        }
      },
      "additionalProperties": false
    },
    "FunctionParam": {
      "type": "object",
      "required": [
        "name",
        "type"
      ],
      "properties": {
        "name": {
          "type": "string",
          "description": "Parameter identifier.",
          "pattern": "^(?!__)[A-Za-z_][A-Za-z0-9_]*$"
        },
        "type": {
          "type": "string",
          "enum": [
            "int",
            "real",
            "bool",
            "string",
            "unit",
            "unitGroup",
            "point",
            "player",
            "region",
            "timer"
          ],
          "description": "Parameter type."
        },
        "description": {
          "type": "string",
          "description": "Documentation."
        }
      },
      "additionalProperties": false
    },
    "HashtableDecl": {
      "type": "object",
      "required": [
        "name",
        "valueType"
      ],
      "properties": {
        "name": {
          "type": "string",
          "description": "Hashtable identifier.",
          "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
        },
        "valueType": {
          "type": "string",
          "enum": [
            "int",
            "real",
            "bool",
            "string",
            "unit",
            "unitGroup",
            "point",
            "player",
            "region",
            "timer"
          ],
          "description": "Type every stored value is coerced to."
        },
        "maxEntries": {
          "type": "integer",
          "minimum": 1,
          "maximum": 16384,
          "description": "Entry cap (≤ 16384)."
        },
        "description": {
          "type": "string",
          "description": "Documentation."
        }
      },
      "additionalProperties": false
    },
    "RegionDef": {
      "type": "object",
      "required": [
        "id",
        "x",
        "y",
        "w",
        "h"
      ],
      "properties": {
        "id": {
          "type": "string",
          "description": "Region id.",
          "minLength": 1
        },
        "x": {
          "type": "integer",
          "description": "Top-left tile X."
        },
        "y": {
          "type": "integer",
          "description": "Top-left tile Y."
        },
        "w": {
          "type": "integer",
          "minimum": 1,
          "description": "Width in tiles (≥ 1)."
        },
        "h": {
          "type": "integer",
          "minimum": 1,
          "description": "Height in tiles (≥ 1)."
        },
        "name": {
          "type": "string",
          "description": "Editor label."
        }
      },
      "additionalProperties": false
    },
    "Text": {
      "description": "A text template, or a bare string (shorthand for one literal part).",
      "oneOf": [
        {
          "type": "string"
        },
        {
          "$ref": "#/definitions/TextTemplate"
        }
      ]
    },
    "TextTemplate": {
      "type": "object",
      "required": [
        "parts"
      ],
      "properties": {
        "parts": {
          "type": "array",
          "items": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "t",
                  "s"
                ],
                "additionalProperties": false,
                "properties": {
                  "t": {
                    "const": "lit"
                  },
                  "s": {
                    "type": "string",
                    "description": "Literal text."
                  }
                }
              },
              {
                "type": "object",
                "required": [
                  "t",
                  "e"
                ],
                "additionalProperties": false,
                "properties": {
                  "t": {
                    "const": "expr"
                  },
                  "e": {
                    "$ref": "#/definitions/Expr",
                    "description": "Expression whose value is printed."
                  },
                  "decimals": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 6,
                    "description": "Fixed decimal places for non-integers."
                  }
                }
              }
            ]
          }
        }
      },
      "additionalProperties": false
    },
    "TriggerDef": {
      "type": "object",
      "required": [
        "id",
        "events",
        "actions"
      ],
      "properties": {
        "id": {
          "type": "string",
          "description": "Stable trigger id.",
          "minLength": 1
        },
        "name": {
          "type": "string",
          "description": "Editor label."
        },
        "enabled": {
          "type": "boolean",
          "description": "Initially enabled (default true)."
        },
        "runOnce": {
          "type": "boolean",
          "description": "Fire at most once."
        },
        "events": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Event"
          },
          "description": "Events (any one fires the trigger)."
        },
        "conditions": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Expr"
          },
          "description": "Conditions, all of which must hold. They see globals only."
        },
        "locals": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/VarDecl"
          },
          "description": "Per-execution local variables; survive wait and save/load."
        },
        "actions": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Action"
          },
          "description": "Actions, run top to bottom."
        }
      },
      "additionalProperties": false
    },
    "UIFrameStyle": {
      "type": "object",
      "properties": {
        "backgroundColor": {
          "type": "string",
          "description": "CSS colour."
        },
        "borderColor": {
          "type": "string",
          "description": "CSS colour."
        },
        "textColor": {
          "type": "string",
          "description": "CSS colour."
        },
        "fontSize": {
          "type": "number",
          "description": "Font size (px)."
        },
        "borderRadius": {
          "type": "number",
          "description": "Corner radius (px)."
        },
        "padding": {
          "type": "number",
          "description": "Padding (px)."
        }
      },
      "additionalProperties": false
    },
    "UnitFilter": {
      "type": "object",
      "properties": {
        "players": {
          "type": "array",
          "items": {
            "type": "integer"
          },
          "description": "Owner must be one of these seats."
        },
        "ofPlayer": {
          "$ref": "#/definitions/Expr",
          "description": "Owner must equal this seat."
        },
        "alliedTo": {
          "$ref": "#/definitions/Expr",
          "description": "Owner must be allied to this seat."
        },
        "enemyOf": {
          "$ref": "#/definitions/Expr",
          "description": "Owner must not be allied to this seat."
        },
        "unitTypeIds": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Unit/building type must be one of these."
        },
        "tags": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Authored tag must be one of these."
        },
        "aliveOnly": {
          "type": "boolean",
          "description": "Only living units (default true)."
        },
        "heroesOnly": {
          "type": "boolean",
          "description": "Only heroes."
        },
        "buildingsOnly": {
          "type": "boolean",
          "description": "Only buildings."
        },
        "excludeBuildings": {
          "type": "boolean",
          "description": "No buildings."
        },
        "flyingOnly": {
          "type": "boolean",
          "description": "Only flyers."
        },
        "excludeFlying": {
          "type": "boolean",
          "description": "No flyers."
        },
        "excluding": {
          "$ref": "#/definitions/Expr",
          "description": "Drop these units."
        }
      },
      "additionalProperties": false
    },
    "VarDecl": {
      "type": "object",
      "required": [
        "name",
        "type"
      ],
      "properties": {
        "name": {
          "type": "string",
          "description": "Identifier: letters, digits and _, not starting with a digit or `__`.",
          "pattern": "^(?!__)[A-Za-z_][A-Za-z0-9_]*$"
        },
        "type": {
          "type": "string",
          "enum": [
            "int",
            "real",
            "bool",
            "string",
            "unit",
            "unitGroup",
            "point",
            "player",
            "region",
            "timer"
          ],
          "description": "Value type."
        },
        "array": {
          "type": "boolean",
          "description": "Fixed-length array."
        },
        "arraySize": {
          "type": "integer",
          "minimum": 1,
          "description": "Array length (globals ≤ 8192, locals ≤ 1024).",
          "maximum": 8192
        },
        "init": {
          "$ref": "#/definitions/Expr",
          "description": "Initial value (globals: at map init; locals: at each execution start)."
        }
      },
      "additionalProperties": false,
      "if": {
        "required": [
          "array"
        ],
        "properties": {
          "array": {
            "const": true
          }
        }
      },
      "then": {
        "required": [
          "arraySize"
        ]
      }
    }
  }
}
