{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.troupes.ai/schema/0.12/bundle.schema.json",
  "title": "Troupe bundle",
  "description": "A Troupe bundle: characters, worlds and scenarios, with publishing metadata stated once.",
  "type": "object",
  "required": [
    "troupe",
    "kind"
  ],
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "type": "string",
      "minLength": 1
    },
    "troupe": {
      "$ref": "#/$defs/version"
    },
    "kind": {
      "const": "bundle"
    },
    "id": {
      "$ref": "#/$defs/opaqueId"
    },
    "name": {
      "$ref": "#/$defs/nonEmpty"
    },
    "version": {
      "$ref": "#/$defs/nonEmpty"
    },
    "authors": {
      "$ref": "#/$defs/list"
    },
    "credits": {
      "$ref": "#/$defs/nonEmpty"
    },
    "language": {
      "$ref": "#/$defs/nonEmpty"
    },
    "tags": {
      "$ref": "#/$defs/list"
    },
    "rating": {
      "enum": [
        "general",
        "mature",
        "explicit"
      ]
    },
    "warnings": {
      "$ref": "#/$defs/list"
    },
    "cover": {
      "$ref": "#/$defs/image"
    },
    "characters": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/character"
      },
      "minItems": 1
    },
    "worlds": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/world"
      },
      "minItems": 1
    },
    "scenarios": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/scenario"
      },
      "minItems": 1
    },
    "ext": {
      "$ref": "#/$defs/ext"
    },
    "tagline": {
      "$ref": "#/$defs/nonEmpty"
    }
  },
  "$defs": {
    "version": {
      "description": "Spec version as major.minor, compared as two integers.",
      "type": "string",
      "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
    },
    "id": {
      "description": "An author ID. CEL reserved words are not allowed.",
      "type": "string",
      "pattern": "^[a-z][a-z0-9_]{0,63}$",
      "not": {
        "enum": [
          "as",
          "break",
          "const",
          "continue",
          "else",
          "false",
          "for",
          "function",
          "if",
          "import",
          "in",
          "let",
          "loop",
          "namespace",
          "null",
          "package",
          "return",
          "true",
          "var",
          "void",
          "while"
        ]
      }
    },
    "ownerId": {
      "description": "A character, world or scenario ID.",
      "allOf": [
        {
          "$ref": "#/$defs/id"
        },
        {
          "not": {
            "enum": [
              "char",
              "user",
              "narrator",
              "session",
              "state",
              "scene",
              "players",
              "own"
            ]
          }
        }
      ]
    },
    "characterId": {
      "$ref": "#/$defs/ownerId"
    },
    "variableId": {
      "allOf": [
        {
          "$ref": "#/$defs/id"
        },
        {
          "not": {
            "enum": [
              "scene",
              "own"
            ]
          }
        }
      ]
    },
    "opaqueId": {
      "description": "A message or bundle ID.",
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{1,64}$"
    },
    "entryRef": {
      "description": "<owner>/<entryId>, where the owner is a character, world or scenario ID, or session.",
      "type": "string",
      "pattern": "^[a-z][a-z0-9_]{0,63}/[a-z][a-z0-9_]{0,63}$"
    },
    "stateKey": {
      "description": "A variable ID, optionally preceded by an owner and followed by a key; or scene.location / scene.present.",
      "type": "string",
      "pattern": "^[a-z][a-z0-9_]{0,63}(\\.[a-z][a-z0-9_]{0,63}){0,2}$"
    },
    "stateValues": {
      "description": "State keys with absolute values, validated against their declarations.",
      "type": "object",
      "propertyNames": {
        "$ref": "#/$defs/stateKey"
      },
      "additionalProperties": {
        "not": {
          "type": "null"
        }
      }
    },
    "nonEmpty": {
      "type": "string",
      "minLength": 1
    },
    "timestamp": {
      "type": "string",
      "format": "date-time",
      "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-5][0-9](\\.[0-9]+)?Z$"
    },
    "httpsUrl": {
      "type": "string",
      "pattern": "^https://."
    },
    "cel": {
      "type": "string",
      "minLength": 1
    },
    "packagePath": {
      "description": "A relative path inside a package (Format 13.3).",
      "type": "string",
      "pattern": "^[A-Za-z0-9_-][A-Za-z0-9._-]*(/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$"
    },
    "image": {
      "description": "A package path, an https URL, or a base64 data URI of a raster image.",
      "anyOf": [
        {
          "$ref": "#/$defs/packagePath"
        },
        {
          "$ref": "#/$defs/httpsUrl"
        },
        {
          "type": "string",
          "pattern": "^data:image/(png|jpeg|webp|avif|gif);base64,[A-Za-z0-9+/=]+$"
        }
      ]
    },
    "extName": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9.-]{0,63}$"
    },
    "ext": {
      "description": "Extension data, keyed by extension name.",
      "type": "object",
      "propertyNames": {
        "$ref": "#/$defs/extName"
      }
    },
    "unit": {
      "type": "number",
      "minimum": 0,
      "maximum": 1
    },
    "character": {
      "type": "object",
      "required": [
        "id",
        "name",
        "description"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/characterId"
        },
        "name": {
          "$ref": "#/$defs/nonEmpty"
        },
        "aliases": {
          "$ref": "#/$defs/list"
        },
        "description": {
          "type": "string"
        },
        "summary": {
          "$ref": "#/$defs/nonEmpty"
        },
        "examples": {
          "type": "array",
          "items": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/$defs/line"
            }
          },
          "minItems": 1
        },
        "entries": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/entry"
          },
          "minItems": 1
        },
        "variables": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/variable"
          },
          "minItems": 1
        },
        "events": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/event"
          },
          "minItems": 1
        },
        "avatar": {
          "$ref": "#/$defs/image"
        },
        "ext": {
          "$ref": "#/$defs/ext"
        }
      }
    },
    "line": {
      "type": "object",
      "required": [
        "text"
      ],
      "additionalProperties": false,
      "properties": {
        "speaker": {
          "anyOf": [
            {
              "$ref": "#/$defs/characterId"
            },
            {
              "enum": [
                "char",
                "user"
              ]
            }
          ]
        },
        "text": {
          "type": "string"
        },
        "ext": {
          "$ref": "#/$defs/ext"
        }
      }
    },
    "world": {
      "type": "object",
      "required": [
        "id",
        "name"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/ownerId"
        },
        "name": {
          "$ref": "#/$defs/nonEmpty"
        },
        "description": {
          "$ref": "#/$defs/nonEmpty"
        },
        "locations": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/location"
          },
          "minItems": 1
        },
        "entries": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/entry"
          },
          "minItems": 1
        },
        "variables": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/variable"
          },
          "minItems": 1
        },
        "events": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/event"
          },
          "minItems": 1
        },
        "ext": {
          "$ref": "#/$defs/ext"
        }
      }
    },
    "location": {
      "type": "object",
      "required": [
        "id",
        "name"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "name": {
          "$ref": "#/$defs/nonEmpty"
        },
        "description": {
          "$ref": "#/$defs/nonEmpty"
        },
        "parent": {
          "$ref": "#/$defs/id"
        },
        "image": {
          "$ref": "#/$defs/image"
        },
        "ext": {
          "$ref": "#/$defs/ext"
        }
      }
    },
    "entry": {
      "type": "object",
      "required": [
        "id",
        "text"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "text": {
          "type": "string"
        },
        "title": {
          "$ref": "#/$defs/nonEmpty"
        },
        "keys": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          },
          "minItems": 1
        },
        "when": {
          "$ref": "#/$defs/cel"
        },
        "scanDepth": {
          "type": "integer",
          "minimum": 0,
          "default": 4,
          "maximum": 100
        },
        "once": {
          "type": "boolean"
        },
        "sticky": {
          "type": "integer",
          "minimum": 1
        },
        "cooldown": {
          "type": "integer",
          "minimum": 1
        },
        "chance": {
          "$ref": "#/$defs/unit",
          "default": 1
        },
        "group": {
          "$ref": "#/$defs/id"
        },
        "weight": {
          "type": "integer",
          "minimum": 1,
          "maximum": 1000000,
          "default": 1
        },
        "priority": {
          "type": "integer",
          "minimum": 0,
          "maximum": 100,
          "default": 50
        },
        "position": {
          "anyOf": [
            {
              "enum": [
                "system",
                "context"
              ]
            },
            {
              "type": "integer",
              "minimum": 0
            }
          ],
          "default": "context"
        },
        "knownBy": {
          "$ref": "#/$defs/knownBy"
        },
        "disabled": {
          "type": "boolean"
        },
        "ext": {
          "$ref": "#/$defs/ext"
        },
        "pull": {
          "type": "boolean"
        }
      },
      "if": {
        "required": [
          "pull"
        ],
        "properties": {
          "pull": {
            "const": true
          }
        }
      },
      "then": {
        "required": [
          "title"
        ]
      }
    },
    "scenario": {
      "type": "object",
      "required": [
        "id"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/ownerId"
        },
        "name": {
          "$ref": "#/$defs/nonEmpty"
        },
        "premise": {
          "$ref": "#/$defs/nonEmpty"
        },
        "briefing": {
          "$ref": "#/$defs/nonEmpty"
        },
        "mode": {
          "enum": [
            "characters",
            "narrator"
          ],
          "default": "characters"
        },
        "narrator": {
          "$ref": "#/$defs/nonEmpty",
          "default": "the narrator"
        },
        "cast": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/castMember"
          },
          "minItems": 1
        },
        "worlds": {
          "description": "World IDs; [] for none.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/ownerId"
          }
        },
        "openings": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/opening"
          },
          "minItems": 1
        },
        "entries": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/entry"
          },
          "minItems": 1
        },
        "variables": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/variable"
          },
          "minItems": 1
        },
        "events": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/event"
          },
          "minItems": 1
        },
        "ext": {
          "$ref": "#/$defs/ext"
        },
        "tagline": {
          "$ref": "#/$defs/nonEmpty"
        }
      }
    },
    "castMember": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "character": {
          "$ref": "#/$defs/characterId"
        },
        "notes": {
          "$ref": "#/$defs/nonEmpty"
        },
        "ext": {
          "$ref": "#/$defs/ext"
        },
        "player": {
          "type": "boolean"
        },
        "spoiler": {
          "type": "boolean"
        }
      },
      "allOf": [
        {
          "if": {
            "required": [
              "player"
            ],
            "properties": {
              "player": {
                "const": true
              }
            }
          },
          "then": {
            "required": [
              "character"
            ]
          }
        }
      ]
    },
    "opening": {
      "type": "object",
      "required": [
        "id"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "title": {
          "$ref": "#/$defs/nonEmpty"
        },
        "set": {
          "$ref": "#/$defs/nonEmptyStateValues"
        },
        "content": {
          "type": "array",
          "minItems": 1,
          "items": {
            "$ref": "#/$defs/part"
          }
        },
        "ext": {
          "$ref": "#/$defs/ext"
        }
      }
    },
    "variable": {
      "type": "object",
      "required": [
        "id",
        "type",
        "description"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/variableId"
        },
        "type": {
          "enum": [
            "bool",
            "int",
            "number",
            "string",
            "enum",
            "set"
          ]
        },
        "values": {
          "type": "array",
          "minItems": 1,
          "uniqueItems": true,
          "items": {
            "type": "string"
          }
        },
        "labels": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/$defs/nonEmpty"
          },
          "minProperties": 1
        },
        "min": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "$ref": "#/$defs/variableId"
            }
          ]
        },
        "max": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "$ref": "#/$defs/variableId"
            }
          ]
        },
        "initial": {
          "description": "Validity is checked against the declaration (INVALID_VALUE).",
          "not": {
            "type": "null"
          },
          "if": {
            "type": "object"
          },
          "then": {
            "additionalProperties": {
              "not": {
                "type": "null"
              }
            }
          }
        },
        "of": {
          "anyOf": [
            {
              "enum": [
                "characters",
                "cast",
                "others",
                "*"
              ]
            },
            {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "$ref": "#/$defs/id"
              }
            }
          ]
        },
        "description": {
          "$ref": "#/$defs/nonEmpty"
        },
        "by": {
          "type": "array",
          "uniqueItems": true,
          "items": {
            "enum": [
              "player",
              "model"
            ]
          },
          "minItems": 1
        },
        "maxStep": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "oneWay": {
          "type": "boolean"
        },
        "ask": {
          "$ref": "#/$defs/nonEmpty"
        },
        "ext": {
          "$ref": "#/$defs/ext"
        },
        "spoiler": {
          "type": "boolean"
        },
        "derive": {
          "$ref": "#/$defs/cel"
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "type": {
                "const": "enum"
              }
            }
          },
          "then": {
            "required": [
              "values"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "enum": [
                  "int",
                  "number",
                  "string",
                  "bool"
                ]
              }
            }
          },
          "then": {
            "properties": {
              "values": false
            }
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "enum": [
                  "int",
                  "number",
                  "string"
                ]
              }
            }
          },
          "then": {
            "properties": {
              "labels": false
            }
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "enum": [
                  "int",
                  "number"
                ]
              }
            }
          },
          "else": {
            "properties": {
              "min": false,
              "max": false,
              "maxStep": false
            }
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "int"
              }
            }
          },
          "then": {
            "properties": {
              "min": {
                "anyOf": [
                  {
                    "type": "integer"
                  },
                  {
                    "$ref": "#/$defs/variableId"
                  }
                ]
              },
              "max": {
                "anyOf": [
                  {
                    "type": "integer"
                  },
                  {
                    "$ref": "#/$defs/variableId"
                  }
                ]
              },
              "maxStep": {
                "type": "integer"
              }
            }
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "string"
              }
            }
          },
          "then": {
            "properties": {
              "oneWay": {
                "const": false
              }
            }
          }
        },
        {
          "if": {
            "required": [
              "of"
            ]
          },
          "then": {
            "properties": {
              "ask": false
            }
          }
        },
        {
          "if": {
            "required": [
              "derive"
            ]
          },
          "then": {
            "properties": {
              "initial": false,
              "by": false,
              "of": false,
              "maxStep": false,
              "ask": false,
              "oneWay": {
                "const": false
              }
            }
          }
        },
        {
          "if": {
            "required": [
              "of"
            ],
            "properties": {
              "of": {
                "const": "*"
              }
            }
          },
          "then": {
            "properties": {
              "initial": false
            }
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "bool"
              }
            }
          },
          "then": {
            "properties": {
              "labels": {
                "propertyNames": {
                  "enum": [
                    "true",
                    "false"
                  ]
                }
              }
            }
          }
        }
      ]
    },
    "event": {
      "type": "object",
      "required": [
        "id",
        "when"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "when": {
          "$ref": "#/$defs/cel"
        },
        "on": {
          "enum": [
            "player",
            "model"
          ]
        },
        "once": {
          "type": "boolean"
        },
        "cooldown": {
          "type": "integer",
          "minimum": 1
        },
        "chance": {
          "$ref": "#/$defs/unit",
          "default": 1
        },
        "set": {
          "$ref": "#/$defs/nonEmptyStateValues"
        },
        "compute": {
          "type": "object",
          "propertyNames": {
            "$ref": "#/$defs/stateKey"
          },
          "additionalProperties": {
            "$ref": "#/$defs/cel"
          },
          "minProperties": 1
        },
        "message": {
          "type": "array",
          "minItems": 1,
          "items": {
            "$ref": "#/$defs/part"
          }
        },
        "direct": {
          "$ref": "#/$defs/nonEmpty"
        },
        "end": {
          "type": "boolean"
        },
        "ext": {
          "$ref": "#/$defs/ext"
        },
        "outcomes": {
          "type": "array",
          "minItems": 1,
          "items": {
            "$ref": "#/$defs/outcome"
          }
        }
      }
    },
    "outcome": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "weight": {
          "type": "integer",
          "minimum": 1,
          "maximum": 1000000,
          "default": 1
        },
        "set": {
          "$ref": "#/$defs/nonEmptyStateValues"
        },
        "compute": {
          "type": "object",
          "propertyNames": {
            "$ref": "#/$defs/stateKey"
          },
          "additionalProperties": {
            "$ref": "#/$defs/cel"
          },
          "minProperties": 1
        },
        "message": {
          "type": "array",
          "minItems": 1,
          "items": {
            "$ref": "#/$defs/part"
          }
        },
        "direct": {
          "$ref": "#/$defs/nonEmpty"
        },
        "end": {
          "type": "boolean"
        },
        "ext": {
          "$ref": "#/$defs/ext"
        }
      }
    },
    "part": {
      "description": "A text part, or an image part with type image (Format 12). A part of another type is unknown to this version (Format 14): only its type is checked.",
      "if": {
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "const": "image"
          }
        }
      },
      "then": {
        "type": "object",
        "required": [
          "type",
          "src"
        ],
        "additionalProperties": false,
        "properties": {
          "type": {
            "const": "image"
          },
          "src": {
            "$ref": "#/$defs/image"
          },
          "alt": {
            "$ref": "#/$defs/nonEmpty"
          },
          "speaker": {
            "$ref": "#/$defs/characterId"
          },
          "ext": {
            "$ref": "#/$defs/ext"
          },
          "knownBy": {
            "$ref": "#/$defs/knownBy"
          }
        }
      },
      "else": {
        "if": {
          "required": [
            "type"
          ],
          "properties": {
            "type": {
              "not": {
                "const": "text"
              }
            }
          }
        },
        "then": {
          "type": "object",
          "required": [
            "type"
          ]
        },
        "else": {
          "type": "object",
          "required": [
            "text"
          ],
          "additionalProperties": false,
          "properties": {
            "type": {
              "const": "text"
            },
            "text": {
              "type": "string"
            },
            "speaker": {
              "$ref": "#/$defs/characterId"
            },
            "knownBy": {
              "$ref": "#/$defs/knownBy"
            },
            "ext": {
              "$ref": "#/$defs/ext"
            }
          }
        }
      }
    },
    "eventName": {
      "description": "A scenario event ID, or <owner>/<id> for a character's or world's event.",
      "type": "string",
      "pattern": "^([a-z][a-z0-9_]{0,63}/)?[a-z][a-z0-9_]{0,63}$"
    },
    "limits": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "lines": {
          "$ref": "#/$defs/list"
        },
        "veils": {
          "$ref": "#/$defs/list"
        },
        "ext": {
          "$ref": "#/$defs/ext"
        }
      },
      "minProperties": 1
    },
    "list": {
      "description": "A non-empty list of strings.",
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/nonEmpty"
      }
    },
    "knownBy": {
      "description": "Character IDs, or narrator; non-empty.",
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/id"
      }
    },
    "nonEmptyStateValues": {
      "allOf": [
        {
          "$ref": "#/$defs/stateValues"
        }
      ],
      "minProperties": 1
    }
  }
}
