{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://www.shardsofstone.com/schemas/v1/map-format.json",
  "$comment": "GENERATED by tools/generate_modding_reference.ts. Do not edit by hand.",
  "title": "SerializedMap",
  "description": "A Shards of Stone map in play format (.play.json / published scenario maps).",
  "type": "object",
  "required": [
    "v",
    "meta",
    "terrain",
    "goldMines",
    "oilPatches",
    "spawns"
  ],
  "properties": {
    "v": {
      "type": "integer",
      "enum": [
        1,
        2
      ]
    },
    "meta": {
      "type": "object",
      "required": [
        "name",
        "mapType",
        "size"
      ],
      "properties": {
        "name": {
          "type": "string"
        },
        "desc": {
          "type": "string"
        },
        "mapType": {
          "$ref": "#/definitions/MapType"
        },
        "size": {
          "type": "number"
        },
        "biome": {
          "type": "string"
        },
        "rockStyle": {
          "description": "Optional vista hint: rock-formation style the map was generated with. Old saves lack it — consumers fall back to 'random'.",
          "$ref": "#/definitions/RockFormationStyle"
        },
        "rockDensity": {
          "description": "Optional vista hint matching the generator's formation density.",
          "$ref": "#/definitions/RockFormationDensity"
        },
        "vistaSeed": {
          "description": "Generator seed for flat-map vista continuity. Elevated maps normally use `heightSeed`, but a deliberately flat map has no height layer.",
          "type": "number"
        },
        "heightSeed": {
          "description": "Seed for the deterministic render-only height recipe. Written only for elevated maps; old/flat maps keep their byte-identical metadata.",
          "type": "number"
        },
        "loadingScreen": {
          "description": "Custom loading screen artwork path (e.g. '/assets/artwork/splash_mountain_hold.png').",
          "type": "string"
        },
        "splashArt": {
          "description": "Alias for loadingScreen.",
          "type": "string"
        },
        "sounds": {
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        },
        "models": {
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        }
      },
      "additionalProperties": false
    },
    "terrain": {
      "type": "string"
    },
    "baseTerrain": {
      "type": "string"
    },
    "height": {
      "description": "Per-tile integer cliff level 0..7, same RLE format as `terrain`.",
      "type": "string"
    },
    "ramps": {
      "description": "Optional ramp mask, same RLE shape as `height`, omitted when the map has none — which is every flat map, and therefore all 230 classic maps, every campaign mission and every existing save.",
      "type": "string"
    },
    "resOverrides": {
      "description": "Only non-default amounts: `[x, y, amount][]` per resource. Sparse, and omitted entirely when a map authors none — see `resourceAmounts.ts`, which owns both the encoding and the defaults it is measured against.",
      "$ref": "#/definitions/ResourceOverrides"
    },
    "goldMines": {
      "type": "array",
      "items": {
        "type": "array",
        "items": {
          "type": "number"
        }
      }
    },
    "goldMineVariants": {
      "description": "Parallel array to goldMines: variant id per mine (by index). Missing → default picker.",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "oilPatches": {
      "type": "array",
      "items": {
        "type": "array",
        "items": {
          "type": "number"
        }
      }
    },
    "spawns": {
      "type": "array",
      "items": {
        "type": "array",
        "items": {
          "type": "number"
        }
      }
    },
    "buildings": {
      "description": "Pre-placed buildings.",
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "t",
          "x",
          "y",
          "p"
        ],
        "properties": {
          "t": {
            "type": "string"
          },
          "x": {
            "type": "number"
          },
          "y": {
            "type": "number"
          },
          "p": {
            "type": "number"
          },
          "c": {
            "type": "boolean"
          },
          "aid": {
            "description": "Stable authored id — triggers reference THIS, never ECS ids.",
            "type": "string"
          },
          "tag": {
            "description": "Group tag; many entities may share one — that IS a unit group.",
            "type": "string"
          },
          "nm": {
            "description": "Custom display name.",
            "type": "string"
          },
          "hp": {
            "description": "Starting health as a FRACTION 0..1 of the final max.",
            "type": "number"
          },
          "mhp": {
            "description": "Absolute max-health override, applied before everything else.",
            "type": "number"
          },
          "mp": {
            "description": "Starting mana as a fraction 0..1 of the final max.",
            "type": "number"
          },
          "rk": {
            "description": "Veterancy rank (2..UNIT_RANK_CAP); rank bonuses are baked in.",
            "type": "number"
          },
          "fc": {
            "description": "Facing, degrees clockwise from \"down\" (0 = down, 90 = left).",
            "type": "number"
          },
          "rl": {
            "description": "Rally point [x, y] for a producing building.",
            "type": "array",
            "items": [
              {
                "type": "number"
              },
              {
                "type": "number"
              }
            ],
            "minItems": 2,
            "maxItems": 2,
            "additionalItems": false
          },
          "bp": {
            "description": "Build progress 0..1. Anything < 1 spawns the building incomplete.",
            "type": "number"
          },
          "inv": {
            "description": "Backpack contents, by slot. `null` = empty slot.",
            "type": "array",
            "items": {
              "type": [
                "object",
                "null"
              ],
              "required": [
                "i"
              ],
              "properties": {
                "i": {
                  "type": "string"
                },
                "n": {
                  "type": "number"
                }
              },
              "additionalProperties": false
            }
          },
          "eq": {
            "description": "Equipped gear: equip-slot key → item id.",
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "hero": {
            "description": "Hero state (ignored on an entity with no `hero` component).",
            "$ref": "#/definitions/AuthoredHeroProps"
          },
          "gar": {
            "description": "Garrison contents, as unit type ids.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "stock": {
            "description": "Shop stock override, as item ids.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "spawner": {
            "description": "Data-driven spawner (D4).",
            "$ref": "#/definitions/SpawnerSpec"
          },
          "kv": {
            "description": "Trigger-readable scalars.",
            "type": "object",
            "additionalProperties": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                },
                {
                  "type": "number"
                }
              ]
            }
          }
        },
        "additionalProperties": false
      }
    },
    "units": {
      "description": "Pre-placed units. Same inline-props rule as `buildings`. `hero_*` type ids route through the hero spawn path.",
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "t",
          "x",
          "y",
          "p"
        ],
        "properties": {
          "t": {
            "type": "string"
          },
          "x": {
            "type": "number"
          },
          "y": {
            "type": "number"
          },
          "p": {
            "type": "number"
          },
          "aid": {
            "description": "Stable authored id — triggers reference THIS, never ECS ids.",
            "type": "string"
          },
          "tag": {
            "description": "Group tag; many entities may share one — that IS a unit group.",
            "type": "string"
          },
          "nm": {
            "description": "Custom display name.",
            "type": "string"
          },
          "hp": {
            "description": "Starting health as a FRACTION 0..1 of the final max.",
            "type": "number"
          },
          "mhp": {
            "description": "Absolute max-health override, applied before everything else.",
            "type": "number"
          },
          "mp": {
            "description": "Starting mana as a fraction 0..1 of the final max.",
            "type": "number"
          },
          "rk": {
            "description": "Veterancy rank (2..UNIT_RANK_CAP); rank bonuses are baked in.",
            "type": "number"
          },
          "fc": {
            "description": "Facing, degrees clockwise from \"down\" (0 = down, 90 = left).",
            "type": "number"
          },
          "rl": {
            "description": "Rally point [x, y] for a producing building.",
            "type": "array",
            "items": [
              {
                "type": "number"
              },
              {
                "type": "number"
              }
            ],
            "minItems": 2,
            "maxItems": 2,
            "additionalItems": false
          },
          "bp": {
            "description": "Build progress 0..1. Anything < 1 spawns the building incomplete.",
            "type": "number"
          },
          "inv": {
            "description": "Backpack contents, by slot. `null` = empty slot.",
            "type": "array",
            "items": {
              "type": [
                "object",
                "null"
              ],
              "required": [
                "i"
              ],
              "properties": {
                "i": {
                  "type": "string"
                },
                "n": {
                  "type": "number"
                }
              },
              "additionalProperties": false
            }
          },
          "eq": {
            "description": "Equipped gear: equip-slot key → item id.",
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "hero": {
            "description": "Hero state (ignored on an entity with no `hero` component).",
            "$ref": "#/definitions/AuthoredHeroProps"
          },
          "gar": {
            "description": "Garrison contents, as unit type ids.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "stock": {
            "description": "Shop stock override, as item ids.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "spawner": {
            "description": "Data-driven spawner (D4).",
            "$ref": "#/definitions/SpawnerSpec"
          },
          "kv": {
            "description": "Trigger-readable scalars.",
            "type": "object",
            "additionalProperties": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                },
                {
                  "type": "number"
                }
              ]
            }
          }
        },
        "additionalProperties": false
      }
    },
    "pools": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "x",
          "y",
          "type"
        ],
        "properties": {
          "x": {
            "type": "number"
          },
          "y": {
            "type": "number"
          },
          "type": {
            "type": "string",
            "enum": [
              "health",
              "mana"
            ]
          }
        },
        "additionalProperties": false
      }
    },
    "players": {
      "description": "Explicit seat roster (D3). Absent = derive the roster from the map bytes exactly as before, which is what keeps all 230 classic maps, both stress fixtures and every existing custom map booting unchanged.",
      "type": "array",
      "items": {
        "$ref": "#/definitions/MapPlayerSlot"
      }
    },
    "rules": {
      "description": "Per-map rules (D3). Absent = shipped behaviour in every respect.",
      "$ref": "#/definitions/MapRules"
    },
    "groundItems": {
      "description": "Items lying on the ground at map start (D8).",
      "type": "array",
      "items": {
        "$ref": "#/definitions/AuthoredGroundItem"
      }
    },
    "doodads": {
      "description": "Decorative props placed by the map editor's Doodad tool. Cosmetic ONLY — no occupancy, no collision, no gameplay effect.",
      "type": "array",
      "items": {
        "$ref": "#/definitions/AuthoredDoodad"
      }
    },
    "triggers": {
      "description": "The authored trigger script (E1/E2). Absent = the map runs no triggers, which is every map that exists today; `TriggerSystem` then costs nothing and never even materialises its state singleton.",
      "$ref": "trigger-script.json"
    },
    "scenes": {
      "description": "Authored in-game cutscenes, named by the `playCutscene` trigger action. Absent on every map that authors none (omit-when-empty, so those maps stay byte-identical). Passed through STRUCTURALLY here — `Game` validates each scene with `parseInGameScene` at install time and drops (with a warning) any that fail, so one bad scene degrades a map rather than refusing to boot it. The sim reads only each scene's `durationSec`; everything else is presentation.",
      "type": "array",
      "items": {
        "$ref": "#/definitions/InGameScene"
      }
    },
    "defOverrides": {
      "description": "Per-match data-editor overrides (E5). Installed by `Game.init` immediately after this map is deserialized and BEFORE any spawn — unit HP/damage is baked into components at spawn time — and cleared in `Game.destroy()`, because the def tables are module singletons shared by every `Game` in the tab and a leak would corrupt the next match and the campaign.",
      "$ref": "def-overrides.json"
    },
    "mercCamps": {
      "description": "[x, y] per mercenary camp. Merc camps MUST be authored here, never in `buildings`: the `Mercenary` stock component is attached only by `Game.spawnMercBuilding`, which this field routes through. A merc camp written into `buildings` goes through the plain `spawnBuilding` path instead and spawns as an inert, invulnerable prop with no hireable stock — it looks right and does nothing.",
      "type": "array",
      "items": {
        "$ref": "#/definitions/MercCampEntry"
      }
    },
    "critters": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "x",
          "y",
          "type"
        ],
        "properties": {
          "x": {
            "type": "number"
          },
          "y": {
            "type": "number"
          },
          "type": {
            "type": "string"
          }
        },
        "additionalProperties": false
      }
    },
    "bridges": {
      "description": "Strategic crossing centres used by the AI on generated river maps.",
      "type": "array",
      "items": {
        "type": "array",
        "items": {
          "type": "number"
        }
      }
    },
    "sounds": {
      "type": "object",
      "additionalProperties": {
        "type": "string"
      }
    },
    "models": {
      "type": "object",
      "additionalProperties": {
        "type": "string"
      }
    },
    "$schema": {
      "type": "string",
      "description": "Optional JSON Schema URL; ignored by the loader."
    }
  },
  "additionalProperties": false,
  "definitions": {
    "AuthoredDoodad": {
      "description": "A decorative prop placed directly on the map.",
      "type": "object",
      "required": [
        "typeId",
        "x",
        "y"
      ],
      "properties": {
        "typeId": {
          "description": "Catalog id from `data/campaign/doodadCatalog.ts`.",
          "type": "string"
        },
        "x": {
          "type": "number"
        },
        "y": {
          "type": "number"
        }
      },
      "additionalProperties": false
    },
    "AuthoredGroundItem": {
      "description": "A ground item placed directly on the map (D8).",
      "type": "object",
      "required": [
        "x",
        "y",
        "i"
      ],
      "properties": {
        "x": {
          "type": "number"
        },
        "y": {
          "type": "number"
        },
        "i": {
          "description": "Item id.",
          "type": "string"
        },
        "inst": {
          "description": "Optional rolled instance (affixes/procs/element), as `ItemInstance`.",
          "$ref": "#/definitions/ItemInstance"
        }
      },
      "additionalProperties": false
    },
    "AuthoredHeroProps": {
      "description": "Authored hero state. `persist` picks the progress bucket — see Hero.persistKey and assignHeroPersistKeys.",
      "type": "object",
      "properties": {
        "lvl": {
          "description": "Starting level (1..cap). Per-level stat growth is applied.",
          "type": "number"
        },
        "xp": {
          "description": "XP banked toward the next level.",
          "type": "number"
        },
        "ab": {
          "description": "Learned ability ids, applied in authored order.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "st": {
          "description": "Stat points already spent per stat.",
          "type": "object",
          "properties": {
            "might": {
              "type": "number"
            },
            "swiftness": {
              "type": "number"
            },
            "focus": {
              "type": "number"
            }
          },
          "additionalProperties": false
        },
        "sp": {
          "description": "UNSPENT stat points.",
          "type": "number"
        },
        "persist": {
          "description": "Progress bucket. Omitted ⇒ derived deterministically from the map bytes (first instance of a heroId persists, later ones don't). Explicit `null` ⇒ progress dies with the entity.",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "additionalProperties": false
    },
    "Cam2DKeyframe": {
      "description": "One 2D camera keyframe (for renderMode '2d' scenes).",
      "type": "object",
      "required": [
        "t",
        "x",
        "y",
        "zoom"
      ],
      "properties": {
        "t": {
          "description": "Seconds from frame 0.",
          "type": "number"
        },
        "x": {
          "description": "Center tile X.",
          "type": "number"
        },
        "y": {
          "description": "Center tile Y.",
          "type": "number"
        },
        "zoom": {
          "description": "2D zoom factor (1 = 100%). Applied directly (not snapped to the discrete gameplay zoom levels).",
          "type": "number"
        }
      },
      "additionalProperties": false
    },
    "CamEase": {
      "type": "string",
      "enum": [
        "linear",
        "smooth",
        "inOut",
        "catmullRom"
      ]
    },
    "CamKeyframe": {
      "description": "One 3D camera keyframe. Either an explicit target (targetX/targetY in tiles) or `followGroup` (target = live centroid of a spawn group).",
      "type": "object",
      "required": [
        "t",
        "yaw",
        "pitch",
        "distance"
      ],
      "properties": {
        "t": {
          "description": "Seconds from frame 0.",
          "type": "number"
        },
        "targetX": {
          "description": "Target tile X (fractional ok). Required unless followGroup is set.",
          "type": "number"
        },
        "targetY": {
          "description": "Target tile Y (fractional ok). Required unless followGroup is set.",
          "type": "number"
        },
        "followGroup": {
          "description": "Follow a spawn group: the orbit target tracks the group centroid.",
          "type": "string"
        },
        "targetHeight": {
          "description": "Look-at elevation in world pixels, default 0. Useful for aircraft and tall buildings.",
          "type": "number"
        },
        "yaw": {
          "description": "Camera yaw in degrees (0 = camera south of target looking north).",
          "type": "number"
        },
        "pitch": {
          "description": "Pitch from vertical in degrees (0 = top-down, 90 = horizon).",
          "type": "number"
        },
        "distance": {
          "description": "Orbit distance in world pixels.",
          "type": "number"
        },
        "ease": {
          "description": "Easing INTO this keyframe (i.e. for the segment that ends here). Default 'smooth' (smoothstep).",
          "$ref": "#/definitions/CamEase"
        }
      },
      "additionalProperties": false
    },
    "CtfRules": {
      "type": "object",
      "required": [
        "enabled",
        "capturesToWin",
        "respawnSeconds",
        "carrierSlowdown",
        "eyeOfTheStormBonus"
      ],
      "properties": {
        "enabled": {
          "type": "boolean"
        },
        "capturesToWin": {
          "type": "number"
        },
        "respawnSeconds": {
          "type": "number"
        },
        "carrierSlowdown": {
          "type": "number"
        },
        "eyeOfTheStormBonus": {
          "type": "boolean"
        }
      },
      "additionalProperties": false
    },
    "DamageType": {
      "type": "string",
      "enum": [
        "siege",
        "slash",
        "pierce",
        "blunt",
        "fire",
        "frost",
        "lightning",
        "arcane",
        "shadow",
        "nature",
        "holy",
        "water"
      ]
    },
    "DominationRules": {
      "type": "object",
      "required": [
        "enabled",
        "targetScore",
        "pointsCount",
        "scorePerSecPerPoint"
      ],
      "properties": {
        "enabled": {
          "type": "boolean"
        },
        "targetScore": {
          "type": "number"
        },
        "pointsCount": {
          "type": "integer",
          "enum": [
            3,
            5
          ]
        },
        "scorePerSecPerPoint": {
          "type": "number"
        }
      },
      "additionalProperties": false
    },
    "HillRules": {
      "type": "object",
      "required": [
        "enabled",
        "hold",
        "radius",
        "reveal"
      ],
      "properties": {
        "enabled": {
          "type": "boolean"
        },
        "hold": {
          "$ref": "#/definitions/HoldLength"
        },
        "radius": {
          "description": "Tiles from the Monument centre that count toward control.",
          "type": "number"
        },
        "reveal": {
          "description": "Mark the Monument on the minimap (white until captured, then the controller's colour). Off makes it a landmark you must scout.",
          "type": "boolean"
        }
      },
      "additionalProperties": false
    },
    "HoldLength": {
      "description": "How long a Wonder / Relic set / Monument must be HELD before it wins.",
      "type": "string",
      "enum": [
        "standard",
        "short",
        "long"
      ]
    },
    "InGameScene": {
      "description": "A cutscene that plays INSIDE a running match, triggered by `Action{k:'playCutscene'}`.",
      "type": "object",
      "required": [
        "id",
        "durationSec",
        "camera"
      ],
      "properties": {
        "id": {
          "description": "Stable authored id — what `Action{k:'playCutscene'}` names.",
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "durationSec": {
          "type": "number"
        },
        "fps": {
          "type": "integer",
          "enum": [
            24,
            30,
            60
          ]
        },
        "hud": {
          "description": "true = HUD stays visible. Default false (HUD hidden for the duration).",
          "type": "boolean"
        },
        "lockInput": {
          "description": "Swallow player input for the duration. Default true.",
          "type": "boolean"
        },
        "letterbox": {
          "description": "Draw cinematic bars. Default true.",
          "type": "boolean"
        },
        "dayNight": {
          "description": "Time-of-day override for the shot. PRESENTATION ONLY: it must not reach `DayNightCycle`, which is sim state that feeds fog radii.",
          "type": "object",
          "properties": {
            "timeOfDay": {
              "type": "number"
            },
            "speedMultiplier": {
              "type": "number"
            }
          },
          "additionalProperties": false
        },
        "lines": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/SceneLine"
          }
        },
        "sounds": {
          "description": "Timed sound cues. Presentation only — see SceneSoundCue.",
          "type": "array",
          "items": {
            "$ref": "#/definitions/SceneSoundCue"
          }
        },
        "camera": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/CamKeyframe"
          }
        },
        "camera2d": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Cam2DKeyframe"
          }
        }
      },
      "additionalProperties": false
    },
    "ItemAffix": {
      "description": "A single random bonus rolled onto an item instance. Applied as a Buff with id 'item:<slot>:<defId>:affix:<n>'.",
      "type": "object",
      "required": [
        "id",
        "stat",
        "value",
        "label"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "stat": {
          "type": "string"
        },
        "value": {
          "type": "number"
        },
        "label": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "ItemInstance": {
      "description": "The optional per-instance data layer carried by an inventory/equipment slot or a ground item. Round-trips through EntitySerialization.",
      "type": "object",
      "required": [
        "defId",
        "itemLevel",
        "rarity",
        "source",
        "rolledStats",
        "affixes",
        "procs"
      ],
      "properties": {
        "defId": {
          "type": "string"
        },
        "itemLevel": {
          "type": "number"
        },
        "rarity": {
          "$ref": "#/definitions/ItemRarity"
        },
        "source": {
          "type": "string",
          "enum": [
            "preset",
            "drop",
            "gambling"
          ]
        },
        "rolledStats": {
          "description": "Base def stats scaled by itemLevel.",
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "stat",
              "value"
            ],
            "properties": {
              "stat": {
                "type": "string"
              },
              "value": {
                "type": "number"
              }
            },
            "additionalProperties": false
          }
        },
        "affixes": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/ItemAffix"
          }
        },
        "procs": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/ItemProc"
          }
        },
        "grantsDamageType": {
          "description": "Elemental weapon → ADDS this element as a bonus rider to the hero's attacks while equipped (does NOT swap the primary Combat.damageType). The per-hit amount applied lives in ItemSystem (see elementRiderAmount).",
          "$ref": "#/definitions/DamageType"
        },
        "elementAmount": {
          "description": "Optional explicit per-hit elemental rider amount. When set it overrides ItemSystem's rarity/itemLevel-derived default.",
          "type": "number"
        }
      },
      "additionalProperties": false
    },
    "ItemProc": {
      "description": "An on-hit proc registered while the item is equipped. The proc system fires it on the 'attack-landed' event. Either an elemental damage burst (element+damage) or a short self-buff aura (auraStat+auraValue+durationTicks).",
      "type": "object",
      "required": [
        "id",
        "trigger",
        "chance",
        "label"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "trigger": {
          "type": "string",
          "const": "on_hit"
        },
        "chance": {
          "description": "0..1 chance to fire per landed attack.",
          "type": "number"
        },
        "element": {
          "description": "Bonus elemental damage burst.",
          "$ref": "#/definitions/DamageType"
        },
        "damage": {
          "type": "number"
        },
        "auraStat": {
          "description": "Temporary self-buff applied on proc.",
          "type": "string"
        },
        "auraValue": {
          "type": "number"
        },
        "durationTicks": {
          "type": "number"
        },
        "label": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "ItemRarity": {
      "type": "string",
      "enum": [
        "common",
        "uncommon",
        "rare",
        "epic",
        "legendary"
      ]
    },
    "KingReveal": {
      "description": "Who sees a King on the minimap.",
      "type": "string",
      "enum": [
        "always",
        "owner",
        "never"
      ]
    },
    "MapPlayerSlot": {
      "description": "One authored seat. Everything except `id` is optional; an omitted field means \"whatever the engine would have done\", so a partially-filled roster is safe.",
      "type": "object",
      "required": [
        "id"
      ],
      "properties": {
        "id": {
          "description": "Seat index (0..MAX_PLAYERS-1). Matches the `p` on entities.",
          "type": "number"
        },
        "controller": {
          "$ref": "#/definitions/MapSeatController"
        },
        "race": {
          "description": "The seat's faction. THE reason this roster exists: nothing in the format carried a race before, so `inferPlayerRacesFromMap` tallied entity id prefixes and took the majority — which mislabels any seat whose army is mixed, mercenary-heavy, or empty.",
          "type": "string",
          "enum": [
            "dwarf",
            "goblin",
            "ratmen",
            "lizardmen",
            "wood_elf",
            "high_elf",
            "dark_elf",
            "human",
            "undead",
            "random"
          ]
        },
        "team": {
          "description": "Allies share a team. Absent ⇒ own team (free-for-all), as today.",
          "type": "number"
        },
        "color": {
          "description": "Minimap/unit colour index override.",
          "type": "number"
        },
        "res": {
          "description": "ABSOLUTE starting resources for this seat — overrides `STARTING_RESOURCE_PRESETS`, which is a global, all-seats setting.",
          "type": "object",
          "properties": {
            "gold": {
              "type": "number"
            },
            "lumber": {
              "type": "number"
            },
            "stone": {
              "type": "number"
            },
            "oil": {
              "type": "number"
            }
          },
          "additionalProperties": false
        },
        "workers": {
          "description": "Starting worker count override (default `STARTING_WORKERS`).",
          "type": "number"
        },
        "tech": {
          "description": "Upgrade / building ids this seat starts having researched or unlocked.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "ai": {
          "description": "Per-seat AI configuration.",
          "type": "object",
          "properties": {
            "difficulty": {
              "type": "string",
              "enum": [
                "medium",
                "easy",
                "hard",
                "llm"
              ]
            },
            "personality": {
              "description": "Forced AI personality (otherwise drawn from `gameRandom`).",
              "type": "string"
            },
            "firstAttackTick": {
              "description": "First tick this seat is allowed to attack.",
              "type": "number"
            },
            "passive": {
              "description": "Never attacks.",
              "type": "boolean"
            }
          },
          "additionalProperties": false
        },
        "camera": {
          "description": "Where this seat's CAMERA starts, if not at its town hall.",
          "type": "array",
          "items": [
            {
              "type": "number"
            },
            {
              "type": "number"
            }
          ],
          "minItems": 2,
          "maxItems": 2,
          "additionalItems": false
        }
      },
      "additionalProperties": false
    },
    "MapRules": {
      "description": "Per-map rule overrides. Every field absent ⇒ shipped behaviour.",
      "type": "object",
      "properties": {
        "heroes": {
          "description": "Hero uniqueness/cap rules — see `players/heroRules.ts`.",
          "type": "object",
          "properties": {
            "uniquePerPlayer": {
              "description": "Reject a train when this player already has that heroId alive or queued. `true` = shipped behaviour.",
              "type": "boolean"
            },
            "uniqueGlobal": {
              "description": "Reject a train when ANY player already has that heroId alive or queued. `false` = shipped behaviour (heroes are unique per player, not per match).",
              "type": "boolean"
            },
            "capOverride": {
              "description": "Absolute cap on living+queued heroes per player, replacing the town-hall tier cap. `null` = shipped behaviour (tier 1 → 1 hero, tier 2 → 2, tier 3+ → unlimited). A number applies at every tier.",
              "type": [
                "number",
                "null"
              ]
            }
          },
          "additionalProperties": false
        },
        "standardVictory": {
          "description": "`false` disables the last-team-standing win check entirely.",
          "type": "boolean"
        },
        "minPlayers": {
          "type": "number"
        },
        "maxPlayers": {
          "type": "number"
        },
        "noStartingUnits": {
          "description": "`true` suppresses the automatic town-hall + workers fallback.",
          "type": "boolean"
        },
        "fog": {
          "description": "Fog mode override.",
          "type": "string",
          "enum": [
            "normal",
            "explored",
            "off"
          ]
        },
        "victory": {
          "description": "Victory ruleset pinned BY THE MAP — see `players/victoryRules.ts`.",
          "type": "object",
          "properties": {
            "preset": {
              "description": "The name the player picked. Presentation and telemetry only — no system branches on it, they branch on the flags below.",
              "type": "string",
              "enum": [
                "standard",
                "conquest",
                "wonder",
                "relics",
                "regicide",
                "score",
                "domination",
                "king_of_hill",
                "capture_the_flag"
              ]
            },
            "conquest": {
              "description": "Last-team-standing is armed.",
              "type": "boolean"
            },
            "wonder": {
              "$ref": "#/definitions/WonderRules"
            },
            "relics": {
              "$ref": "#/definitions/RelicRules"
            },
            "regicide": {
              "$ref": "#/definitions/RegicideRules"
            },
            "hill": {
              "$ref": "#/definitions/HillRules"
            },
            "score": {
              "$ref": "#/definitions/ScoreRules"
            },
            "domination": {
              "$ref": "#/definitions/DominationRules"
            },
            "ctf": {
              "$ref": "#/definitions/CtfRules"
            }
          },
          "additionalProperties": false
        },
        "disableAi": {
          "description": "`true` prevents AISystem and LLMAISystem from running macro/attack logic for AI players. Useful for scripted Tower Defense, RPGs, or boss encounter maps where creeps/invaders are controlled strictly by triggers.",
          "type": "boolean"
        },
        "allowedBuildings": {
          "description": "Optional whitelist of building IDs the player's builders can construct. If set, only these buildings appear in the build menu and can be constructed. Ideal for Tower Defense maps where builders should only build towers.",
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "additionalProperties": false
    },
    "MapSeatController": {
      "description": "How a seat is driven. `user`/`computer` are the two that produce a `Player`; the rest exist so an author can reserve or disable a slot. `creep` and `hostile` map onto the neutral-hostile owner, `neutral` onto the `p < 0` prop owner.",
      "type": "string",
      "enum": [
        "neutral",
        "creep",
        "none",
        "user",
        "computer",
        "hostile"
      ]
    },
    "MapType": {
      "description": "The map's TOPOLOGY. Biome is the separate material axis — every map type is available in every biome, and nothing here names a climate.",
      "type": "string",
      "enum": [
        "standard",
        "river",
        "islands",
        "continents",
        "coastal",
        "arena",
        "dungeon",
        "skylands"
      ]
    },
    "MercCampEntry": {
      "description": "One neutral hire building, as a saved map stores it.",
      "anyOf": [
        {
          "type": "array",
          "items": [
            {
              "type": "number"
            },
            {
              "type": "number"
            }
          ],
          "minItems": 2,
          "maxItems": 2,
          "additionalItems": false
        },
        {
          "type": "array",
          "items": [
            {
              "type": "number"
            },
            {
              "type": "number"
            },
            {
              "type": "string"
            }
          ],
          "minItems": 3,
          "maxItems": 3,
          "additionalItems": false
        }
      ]
    },
    "RegicideRules": {
      "type": "object",
      "required": [
        "enabled",
        "reveal"
      ],
      "properties": {
        "enabled": {
          "type": "boolean"
        },
        "reveal": {
          "$ref": "#/definitions/KingReveal"
        }
      },
      "additionalProperties": false
    },
    "RelicCount": {
      "description": "How many Relics a map carries.",
      "anyOf": [
        {
          "type": "string",
          "const": "auto"
        },
        {
          "type": "integer",
          "enum": [
            3,
            5,
            7
          ]
        }
      ]
    },
    "RelicRules": {
      "type": "object",
      "required": [
        "enabled",
        "hold",
        "count",
        "reveal"
      ],
      "properties": {
        "enabled": {
          "type": "boolean"
        },
        "hold": {
          "$ref": "#/definitions/HoldLength"
        },
        "count": {
          "$ref": "#/definitions/RelicCount"
        },
        "reveal": {
          "description": "Mark un-banked relics on the minimap.",
          "type": "boolean"
        }
      },
      "additionalProperties": false
    },
    "ResourceOverrides": {
      "description": "Sparse per-tile amounts, `[x, y, amount]` per entry, keyed by resource.",
      "type": "object",
      "properties": {
        "tree": {
          "type": "array",
          "items": {
            "type": "array",
            "items": {
              "type": "number"
            }
          }
        },
        "gold": {
          "type": "array",
          "items": {
            "type": "array",
            "items": {
              "type": "number"
            }
          }
        },
        "stone": {
          "type": "array",
          "items": {
            "type": "array",
            "items": {
              "type": "number"
            }
          }
        },
        "oil": {
          "type": "array",
          "items": {
            "type": "array",
            "items": {
              "type": "number"
            }
          }
        }
      },
      "additionalProperties": false
    },
    "RockFormationDensity": {
      "type": "string",
      "enum": [
        "sparse",
        "moderate",
        "dense"
      ]
    },
    "RockFormationStyle": {
      "type": "string",
      "enum": [
        "none",
        "ridges",
        "mountains",
        "ravines",
        "boulders",
        "outcrop",
        "wallcluster",
        "mixed",
        "random"
      ]
    },
    "SceneLine": {
      "description": "One timed subtitle line inside an InGameScene.",
      "type": "object",
      "required": [
        "t",
        "text"
      ],
      "properties": {
        "t": {
          "description": "Seconds from scene start.",
          "type": "number"
        },
        "who": {
          "description": "Speaker name shown above the line. Optional.",
          "type": "string"
        },
        "text": {
          "type": "string"
        },
        "hold": {
          "description": "Seconds the line stays up. Default 3.",
          "type": "number"
        }
      },
      "additionalProperties": false
    },
    "SceneSoundCue": {
      "description": "One timed sound cue inside an InGameScene. PRESENTATION ONLY: it is played by the client watching the scene, never by the sim, so a client that skipped the scene simply does not hear it.",
      "type": "object",
      "required": [
        "t",
        "soundId"
      ],
      "properties": {
        "t": {
          "description": "Seconds from scene start.",
          "type": "number"
        },
        "soundId": {
          "description": "A SoundManager id (same namespace as the `playSound` trigger action).",
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "ScoreRules": {
      "type": "object",
      "required": [
        "enabled",
        "limitMinutes"
      ],
      "properties": {
        "enabled": {
          "type": "boolean"
        },
        "limitMinutes": {
          "type": "number"
        }
      },
      "additionalProperties": false
    },
    "SpawnerSpec": {
      "description": "A data-driven spawner attached to ANY building (D4). See `CreepSpawner`.",
      "type": "object",
      "required": [
        "squad"
      ],
      "properties": {
        "squad": {
          "description": "ANY unit type ids — cross-faction is fine, and always was: `squad` is an arbitrary `string[]` and `spawnCreepUnit` accepts any id with a `UnitDef`.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "respawn": {
          "description": "[min, max] ticks between respawn attempts. Default 900/1800 (45-90s).",
          "type": "array",
          "items": [
            {
              "type": "number"
            },
            {
              "type": "number"
            }
          ],
          "minItems": 2,
          "maxItems": 2,
          "additionalItems": false
        },
        "leash": {
          "description": "Soft leash radius. Default CREEP_LEASH_RADIUS.",
          "type": "number"
        },
        "hardLeash": {
          "description": "Hard leash radius. Default CREEP_HARD_LEASH_RADIUS.",
          "type": "number"
        },
        "activation": {
          "description": "Wake radius. Default CREEP_CAMP_ACTIVATION_RADIUS.",
          "type": "number"
        },
        "owner": {
          "description": "Seat the spawned units belong to. Default CREEP_PLAYER_ID; a real seat makes this a FRIENDLY spawner.",
          "type": "number"
        },
        "hostile": {
          "description": "`false` ⇒ no `Creep` component: no leash, no bounty, no dormancy. Default true. NOTE: this path charges no population either way.",
          "type": "boolean"
        }
      },
      "additionalProperties": false
    },
    "WonderProgress": {
      "description": "How much of an ENEMY Wonder's construction the rest of the lobby is told.",
      "type": "string",
      "enum": [
        "off",
        "quarters",
        "exact"
      ]
    },
    "WonderRules": {
      "type": "object",
      "required": [
        "enabled",
        "hold",
        "progress"
      ],
      "properties": {
        "enabled": {
          "type": "boolean"
        },
        "hold": {
          "$ref": "#/definitions/HoldLength"
        },
        "progress": {
          "description": "Construction-progress visibility. See WonderProgress.",
          "$ref": "#/definitions/WonderProgress"
        }
      },
      "additionalProperties": false
    }
  }
}
