{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://supertextadventure.com/contract/world.schema.json",
  "title": "Super Text Adventure world",
  "description": "The shape of a Classic Game Engine world document. Cross-references between ids are described separately in world.refs.json. Unknown keys are allowed everywhere except attack_condition, so worlds keep validating when new fields land.",
  "type": "object",
  "required": ["meta", "rooms"],
  "properties": {
    "meta": { "$ref": "#/$defs/meta" },
    "rooms": { "type": "object", "additionalProperties": { "$ref": "#/$defs/room" } },
    "items": { "type": "object", "additionalProperties": { "$ref": "#/$defs/item" } },
    "npcs": { "type": "object", "additionalProperties": { "$ref": "#/$defs/npc" } },
    "creatures": { "type": "object", "additionalProperties": { "$ref": "#/$defs/creature" } }
  },
  "$defs": {
    "id": { "type": "string", "minLength": 1 },
    "flag": { "type": "string", "minLength": 1 },
    "text": { "type": "string" },
    "idList": { "type": "array", "items": { "$ref": "#/$defs/id" } },
    "stringList": { "type": "array", "items": { "type": "string" } },

    "meta": {
      "type": "object",
      "required": ["name", "description", "starting_room"],
      "properties": {
        "name": { "type": "string", "minLength": 1 },
        "description": { "$ref": "#/$defs/text" },
        "starting_room": { "$ref": "#/$defs/id" },
        "version": { "type": "string" },
        "author": { "type": "string" },
        "editor": { "description": "Builder-only visual metadata (map positions, sizes, glyphs, decor). Never validated further and ignored by the engine." }
      }
    },

    "exit": {
      "oneOf": [
        { "$ref": "#/$defs/id" },
        { "$ref": "#/$defs/exitObject" }
      ]
    },
    "exitObject": {
      "type": "object",
      "required": ["to"],
      "properties": {
        "to": { "$ref": "#/$defs/id" },
        "keywords": { "$ref": "#/$defs/stringList" },
        "requires": { "$ref": "#/$defs/id" },
        "requires_flag": { "$ref": "#/$defs/flag" },
        "locked_msg": { "$ref": "#/$defs/text" },
        "unlocked_msg": { "$ref": "#/$defs/text" },
        "use_item": { "$ref": "#/$defs/id" },
        "on_unlock": { "$ref": "#/$defs/text" },
        "permanently_unlock": { "type": "boolean", "default": false },
        "consume_item": { "type": "boolean", "default": false },
        "sets_flag": { "$ref": "#/$defs/flag" },
        "hidden": { "type": "boolean", "default": false },
        "reveal_msg": { "$ref": "#/$defs/text" }
      }
    },

    "room": {
      "type": "object",
      "required": ["name", "description"],
      "properties": {
        "name": { "type": "string", "minLength": 1 },
        "description": { "$ref": "#/$defs/text" },
        "exits": { "type": "object", "additionalProperties": { "$ref": "#/$defs/exit" } },
        "items": { "$ref": "#/$defs/idList" },
        "npcs": { "$ref": "#/$defs/idList" },
        "creatures": { "$ref": "#/$defs/idList" },
        "on_enter": {
          "type": "object",
          "required": ["type", "text"],
          "properties": {
            "type": { "const": "message" },
            "text": { "$ref": "#/$defs/text" }
          }
        }
      }
    },

    "diceOutcome": {
      "type": "object",
      "properties": {
        "message": { "$ref": "#/$defs/text" },
        "sets_flag": { "$ref": "#/$defs/flag" },
        "unlocks_exit": {
          "type": "object",
          "required": ["direction"],
          "properties": {
            "direction": { "type": "string", "minLength": 1 },
            "room": { "$ref": "#/$defs/id" }
          }
        },
        "unlocks_dialogue": {
          "type": "object",
          "required": ["topic"],
          "properties": { "topic": { "type": "string", "minLength": 1 } }
        }
      }
    },
    "diceRoll": {
      "type": "object",
      "required": ["dc", "on_success", "on_failure"],
      "properties": {
        "dc": { "type": "integer", "minimum": 1 },
        "dice": { "type": "string", "pattern": "^[0-9]+d[0-9]+$", "default": "1d20" },
        "attempt_message": { "$ref": "#/$defs/text" },
        "completed_message": { "$ref": "#/$defs/text" },
        "consume_on": { "enum": ["failure", "success", "any"] },
        "on_success": { "$ref": "#/$defs/diceOutcome" },
        "on_failure": { "$ref": "#/$defs/diceOutcome" }
      }
    },

    "item": {
      "type": "object",
      "required": ["name", "description"],
      "properties": {
        "name": { "type": "string", "minLength": 1 },
        "description": { "$ref": "#/$defs/text" },
        "keywords": { "$ref": "#/$defs/stringList" },
        "takeable": { "type": "boolean", "default": true },
        "cant_take_msg": { "$ref": "#/$defs/text" },
        "consumable": { "type": "boolean", "default": false },
        "weapon_damage": { "type": "number", "minimum": 0 },
        "defense_bonus": { "type": "number", "minimum": 0 },
        "combat_effect": {
          "type": "object",
          "required": ["type"],
          "properties": {
            "type": { "const": "heal" },
            "amount": { "type": "number", "minimum": 1, "default": 10 }
          }
        },
        "on_use": {
          "type": "object",
          "required": ["type"],
          "properties": {
            "type": { "enum": ["unlock", "message", "heal"] },
            "requires_target": { "type": "boolean", "default": false },
            "sets_flag": { "$ref": "#/$defs/flag" },
            "success_msg": { "$ref": "#/$defs/text" },
            "text": { "$ref": "#/$defs/text" },
            "amount": { "type": "number", "minimum": 1 }
          }
        },
        "reveals_exit": {
          "type": "object",
          "required": ["direction"],
          "properties": {
            "direction": { "type": "string", "minLength": 1 },
            "message": { "$ref": "#/$defs/text" }
          }
        },
        "on_examine": {
          "type": "object",
          "properties": {
            "reveals_exit": { "type": "string", "minLength": 1 },
            "text": { "$ref": "#/$defs/text" }
          }
        },
        "dice_roll": { "$ref": "#/$defs/diceRoll" },
        "is_container": { "type": "boolean", "default": false },
        "starts_closed": { "type": "boolean", "default": true },
        "locked": { "type": "boolean", "default": false },
        "unlock_item": { "$ref": "#/$defs/id" },
        "unlock_flag": { "$ref": "#/$defs/flag" },
        "locked_message": { "$ref": "#/$defs/text" },
        "on_open_message": { "$ref": "#/$defs/text" },
        "on_close_message": { "$ref": "#/$defs/text" },
        "open_description": { "$ref": "#/$defs/text" },
        "closed_description": { "$ref": "#/$defs/text" },
        "empty_message": { "$ref": "#/$defs/text" },
        "contents": { "$ref": "#/$defs/idList" }
      }
    },

    "topic": {
      "type": "object",
      "properties": {
        "keywords": { "$ref": "#/$defs/stringList" },
        "text": { "$ref": "#/$defs/text" },
        "sets_flag": { "$ref": "#/$defs/flag" },
        "requires_flag": { "$ref": "#/$defs/flag" },
        "requires_item": { "$ref": "#/$defs/id" },
        "locked_text": { "$ref": "#/$defs/text" },
        "leads_to": { "$ref": "#/$defs/stringList" }
      }
    },
    "dialogue": {
      "type": "object",
      "properties": {
        "greeting": { "$ref": "#/$defs/text" },
        "default": { "$ref": "#/$defs/text" },
        "sets_flag": { "$ref": "#/$defs/flag" },
        "topics": { "type": "object", "additionalProperties": { "$ref": "#/$defs/topic" } }
      }
    },

    "movement": {
      "oneOf": [
        { "$ref": "#/$defs/patrolMovement" },
        { "$ref": "#/$defs/triggeredMovement" }
      ]
    },
    "patrolMovement": {
      "type": "object",
      "required": ["type", "schedule"],
      "properties": {
        "type": { "const": "patrol" },
        "schedule": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "required": ["room"],
            "properties": {
              "room": { "$ref": "#/$defs/id" },
              "duration": { "type": "integer", "minimum": 1 },
              "blocked_while_player_in": { "$ref": "#/$defs/idList" }
            }
          }
        },
        "depart_msg": { "$ref": "#/$defs/text" },
        "arrive_msg": { "$ref": "#/$defs/text" }
      }
    },
    "triggeredMovement": {
      "type": "object",
      "required": ["type", "trigger_flag", "destination"],
      "properties": {
        "type": { "const": "triggered" },
        "trigger_flag": { "$ref": "#/$defs/flag" },
        "destination": { "$ref": "#/$defs/id" },
        "depart_msg": { "$ref": "#/$defs/text" },
        "arrive_msg": { "$ref": "#/$defs/text" }
      }
    },

    "npc": {
      "type": "object",
      "required": ["name", "description"],
      "properties": {
        "name": { "type": "string", "minLength": 1 },
        "description": { "$ref": "#/$defs/text" },
        "keywords": { "$ref": "#/$defs/stringList" },
        "dialogue": { "$ref": "#/$defs/dialogue" },
        "movement": { "$ref": "#/$defs/movement" },
        "accepts_item": { "$ref": "#/$defs/id" },
        "gives_item": { "$ref": "#/$defs/id" },
        "accept_message": { "$ref": "#/$defs/text" },
        "sets_flag": { "$ref": "#/$defs/flag" }
      }
    },

    "creature": {
      "type": "object",
      "required": ["name", "description", "health"],
      "properties": {
        "name": { "type": "string", "minLength": 1 },
        "description": { "$ref": "#/$defs/text" },
        "keywords": { "$ref": "#/$defs/stringList" },
        "health": { "type": "number", "exclusiveMinimum": 0 },
        "attack": { "type": "number", "minimum": 0, "default": 5 },
        "defense": { "type": "number", "minimum": 0, "default": 0 },
        "hostile": { "type": "boolean", "default": false },
        "talk_text": { "$ref": "#/$defs/text" },
        "aggro_text": { "$ref": "#/$defs/text" },
        "attack_condition": {
          "type": "object",
          "description": "Exactly one of moves, room_entries, on_talk. The engine evaluates only one key, and an unknown key makes the creature never attack.",
          "minProperties": 1,
          "maxProperties": 1,
          "properties": {
            "moves": { "type": "integer", "minimum": 1 },
            "room_entries": { "type": "integer", "minimum": 1 },
            "on_talk": { "type": "boolean" }
          },
          "additionalProperties": false
        },
        "movement": { "$ref": "#/$defs/movement" },
        "on_defeat_msg": { "$ref": "#/$defs/text" },
        "on_flee_msg": { "$ref": "#/$defs/text" },
        "loot": { "$ref": "#/$defs/idList" },
        "sets_flag_on_defeat": { "$ref": "#/$defs/flag" }
      }
    }
  },
  "x-engine-defaults": {
    "player_max_health": 10,
    "player_base_attack": 5,
    "attack_variance": 2,
    "defend_bonus": 3,
    "flee_chance": 0.5
  }
}
