{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://crawler-vault-marketing.web.app/schema/common.schema.json",
  "title": "Shared definitions",
  "description": "Types used by both the system definition and the character document.",
  "$defs": {
    "expression": {
      "type": "string",
      "description": "A DSL expression, held as source. Parsed, not evaluated by the JSON reader."
    },
    "attrPath": {
      "type": "string",
      "pattern": "^[A-Za-z_][A-Za-z0-9_.\\-]*$",
      "description": "A flat, dotted, namespaced attribute path: abilities.str.score."
    },
    "valueKind": {
      "type": "string",
      "enum": [
        "NUM",
        "RANK",
        "BOOL",
        "TEXT",
        "TAGS",
        "DICE"
      ],
      "description": "NUM, RANK, BOOL, TEXT, TAGS or DICE. COLLECTION is evaluation-only and may not be declared."
    },
    "contentRef": {
      "type": "object",
      "required": [
        "repo",
        "id"
      ],
      "additionalProperties": false,
      "properties": {
        "repo": {
          "type": "string"
        },
        "id": {
          "type": "string"
        },
        "version": {
          "type": "string",
          "description": "Omit to mean 'whatever the repository has'."
        }
      },
      "description": "A pointer into a content repository."
    },
    "term": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "count": {
          "type": "integer",
          "minimum": 0
        },
        "faces": {
          "type": "integer",
          "minimum": 1
        },
        "flat": {
          "type": "number"
        },
        "tags": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "factor": {
          "type": "number",
          "description": "With 'of': a group whose rolled result is multiplied by this. Multiplying a result is not the same as rolling more dice, so the group is kept rather than distributed."
        },
        "of": {
          "type": "array",
          "description": "The terms the factor applies to. Groups may nest.",
          "items": {
            "$ref": "#/$defs/term"
          }
        }
      },
      "description": "One addend of a dice line: a die, or a flat number. Tags are the damage type."
    },
    "value": {
      "type": "object",
      "required": [
        "kind"
      ],
      "properties": {
        "kind": {
          "type": "string"
        }
      },
      "oneOf": [
        {
          "properties": {
            "kind": {
              "const": "num"
            },
            "v": {
              "type": "number"
            }
          },
          "required": [
            "v"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "bool"
            },
            "v": {
              "type": "boolean"
            }
          },
          "required": [
            "v"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "text"
            },
            "v": {
              "type": "string"
            }
          },
          "required": [
            "v"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "rank"
            },
            "scaleId": {
              "type": "string"
            },
            "index": {
              "type": "integer"
            }
          },
          "required": [
            "scaleId",
            "index"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "tags"
            },
            "v": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "required": [
            "v"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "dice"
            },
            "terms": {
              "type": "array",
              "items": {
                "$ref": "common.schema.json#/$defs/term"
              }
            },
            "tags": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "flags": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "additionalProperties": false
        }
      ],
      "description": "A stored value, tagged by kind."
    },
    "predicate": {
      "type": "object",
      "required": [
        "kind"
      ],
      "properties": {
        "kind": {
          "type": "string"
        }
      },
      "oneOf": [
        {
          "properties": {
            "kind": {
              "const": "always"
            }
          },
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "tag"
            },
            "tag": {
              "type": "string"
            }
          },
          "required": [
            "tag"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "and"
            },
            "of": {
              "type": "array",
              "items": {
                "$ref": "common.schema.json#/$defs/predicate"
              }
            }
          },
          "required": [
            "of"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "or"
            },
            "of": {
              "type": "array",
              "items": {
                "$ref": "common.schema.json#/$defs/predicate"
              }
            }
          },
          "required": [
            "of"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "not"
            },
            "of": {
              "$ref": "common.schema.json#/$defs/predicate"
            }
          },
          "required": [
            "of"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "expr"
            },
            "expr": {
              "type": "string",
              "description": "A DSL expression, held as source. Parsed, not evaluated by the JSON reader."
            }
          },
          "required": [
            "expr"
          ],
          "additionalProperties": false
        }
      ],
      "description": "A condition on namespaced string tags. Three-valued: a target: or situation: tag the sheet cannot decide makes the modifier conditional."
    },
    "targetSelector": {
      "type": "object",
      "required": [
        "kind"
      ],
      "properties": {
        "kind": {
          "type": "string"
        }
      },
      "oneOf": [
        {
          "properties": {
            "kind": {
              "const": "exact"
            },
            "path": {
              "$ref": "common.schema.json#/$defs/attrPath"
            }
          },
          "required": [
            "path"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "pattern"
            },
            "glob": {
              "type": "string",
              "description": "`*` matches exactly one path segment. There is no `**`."
            },
            "where": {
              "$ref": "common.schema.json#/$defs/predicate"
            }
          },
          "required": [
            "glob"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "own"
            },
            "localPath": {
              "type": "string",
              "description": "A descriptor id on the granting entity, e.g. 'damage'."
            }
          },
          "required": [
            "localPath"
          ],
          "additionalProperties": false
        }
      ],
      "description": "Which attribute or attributes a modifier aims at."
    },
    "op": {
      "type": "string",
      "enum": [
        "Add",
        "Multiply",
        "Override",
        "Upgrade",
        "Downgrade",
        "UpgradeRank",
        "DowngradeRank",
        "MultiplyBaseDice",
        "MultiplyTotalDice",
        "AddDice",
        "SetDieCount",
        "UpgradeDieSize",
        "GrantFlag",
        "ClearFlag"
      ],
      "description": "What a modifier does. The phase each runs in is fixed by the engine."
    },
    "modifier": {
      "type": "object",
      "required": [
        "target",
        "op",
        "value",
        "label"
      ],
      "additionalProperties": false,
      "properties": {
        "target": {
          "$ref": "common.schema.json#/$defs/targetSelector"
        },
        "op": {
          "$ref": "common.schema.json#/$defs/op"
        },
        "value": {
          "type": "string",
          "description": "A DSL expression, held as source. Parsed, not evaluated by the JSON reader."
        },
        "label": {
          "type": "string",
          "description": "Shown in the breakdown."
        },
        "type": {
          "type": "string",
          "description": "Stacking type. Omit for untyped, which always stacks."
        },
        "predicate": {
          "$ref": "common.schema.json#/$defs/predicate"
        },
        "priority": {
          "type": "integer",
          "description": "Explicit precedence. Ties fall to source order, later winning."
        }
      },
      "description": "A typed, predicated change to a value."
    },
    "choiceSource": {
      "type": "object",
      "required": [
        "kind"
      ],
      "properties": {
        "kind": {
          "type": "string"
        }
      },
      "oneOf": [
        {
          "properties": {
            "kind": {
              "const": "inline"
            },
            "options": {
              "type": "array",
              "items": {
                "$ref": "common.schema.json#/$defs/contentRef"
              }
            }
          },
          "required": [
            "options"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "query"
            },
            "where": {
              "type": "string"
            }
          },
          "required": [
            "where"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "freeText"
            }
          },
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "table"
            },
            "ref": {
              "$ref": "common.schema.json#/$defs/contentRef"
            }
          },
          "required": [
            "ref"
          ],
          "additionalProperties": false
        }
      ],
      "description": "Where the options for a choice come from."
    },
    "ruleElement": {
      "type": "object",
      "required": [
        "kind"
      ],
      "properties": {
        "kind": {
          "type": "string"
        }
      },
      "oneOf": [
        {
          "properties": {
            "kind": {
              "const": "Modifier"
            },
            "modifier": {
              "$ref": "common.schema.json#/$defs/modifier"
            }
          },
          "required": [
            "modifier"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "GrantTag"
            },
            "tag": {
              "type": "string"
            },
            "predicate": {
              "$ref": "common.schema.json#/$defs/predicate"
            }
          },
          "required": [
            "tag"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "Note"
            },
            "text": {
              "type": "string"
            },
            "attachTo": {
              "$ref": "common.schema.json#/$defs/attrPath"
            },
            "predicate": {
              "$ref": "common.schema.json#/$defs/predicate"
            }
          },
          "required": [
            "text"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "ChoiceSet"
            },
            "id": {
              "type": "string"
            },
            "prompt": {
              "type": "string"
            },
            "from": {
              "$ref": "common.schema.json#/$defs/choiceSource"
            },
            "mode": {
              "type": "string",
              "enum": [
                "PICK",
                "ASSIGN",
                "ROLL"
              ]
            },
            "minCount": {
              "type": "integer",
              "minimum": 0
            },
            "maxCount": {
              "type": "integer",
              "minimum": 0
            },
            "slots": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "grants": {
              "type": "boolean",
              "description": "Whether the selection is applied, not merely recorded. Only a granting choice cascades."
            },
            "bind": {
              "type": "object",
              "additionalProperties": {
                "$ref": "common.schema.json#/$defs/expression"
              },
              "description": "The starting state of what is granted, read from its rules as @source.<key>, as a GrantEntry's is. Only for a granting choice."
            },
            "retain": {
              "type": "boolean",
              "default": false,
              "description": "Whether what was granted stays, as the character's own, when the entry that granted it goes."
            }
          },
          "required": [
            "id",
            "prompt",
            "from"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "GrantTemp"
            },
            "resource": {
              "$ref": "common.schema.json#/$defs/attrPath"
            },
            "amount": {
              "type": "string",
              "description": "A DSL expression, held as source. Parsed, not evaluated by the JSON reader."
            }
          },
          "required": [
            "resource",
            "amount"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "GrantEntry"
            },
            "ref": {
              "$ref": "common.schema.json#/$defs/contentRef"
            },
            "bind": {
              "type": "object",
              "additionalProperties": {
                "$ref": "common.schema.json#/$defs/expression"
              },
              "description": "The entry's starting state, read from its rules as @source.<key>. Evaluated in the granter's scope when the content is taken: \"rank\": \"7\"."
            },
            "appliesWhile": {
              "type": "string",
              "enum": [
                "EQUIPPED",
                "CARRIED"
              ],
              "default": "EQUIPPED",
              "description": "Whether what an item grants needs the item worn. CARRIED applies while it is merely in the pack."
            },
            "predicate": {
              "$ref": "common.schema.json#/$defs/predicate",
              "description": "When the grant holds. The cascade grants while it is true and withdraws after: \"@character.floor >= 6\"."
            },
            "retain": {
              "type": "boolean",
              "default": false,
              "description": "Whether what was granted stays, as the character's own, when the grant stops holding."
            }
          },
          "required": [
            "ref"
          ],
          "additionalProperties": false
        },
        {
          "properties": {
            "kind": {
              "const": "GrantEntity"
            },
            "ref": {
              "$ref": "common.schema.json#/$defs/contentRef"
            },
            "bind": {
              "type": "object",
              "additionalProperties": {
                "type": "string",
                "description": "A DSL expression, held as source. Parsed, not evaluated by the JSON reader."
              },
              "description": "Evaluated in the granter's scope. Scopes do not nest."
            }
          },
          "required": [
            "ref"
          ],
          "additionalProperties": false
        }
      ],
      "description": "What a feat, class feature or item carries."
    },
    "tableRow": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "range": {
          "type": "array",
          "items": {
            "type": "integer"
          },
          "minItems": 1,
          "maxItems": 2
        },
        "weight": {
          "type": "integer",
          "minimum": 1
        },
        "text": {
          "type": "string"
        },
        "ref": {
          "$ref": "common.schema.json#/$defs/contentRef"
        }
      },
      "description": "One outcome. Addressed by range, or by weight as sugar; a table uses one idiom or the other, never both. Carries text, a ref, or both."
    },
    "table": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "dice": {
          "$ref": "common.schema.json#/$defs/term"
        },
        "rows": {
          "type": "array",
          "items": {
            "$ref": "common.schema.json#/$defs/tableRow"
          }
        },
        "allowDuplicates": {
          "type": "boolean"
        }
      },
      "description": "A weighted table. Rolled once, at resolution time."
    },
    "contentDoc": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "name": {
          "type": "string"
        },
        "tags": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "rules": {
          "type": "array",
          "items": {
            "$ref": "common.schema.json#/$defs/ruleElement"
          }
        },
        "table": {
          "$ref": "common.schema.json#/$defs/table"
        },
        "rollDescriptors": {
          "type": "array",
          "description": "Layer-2 lines this content declares, such as a skill's base damage.",
          "items": {
            "$ref": "#/$defs/rollDescriptor"
          }
        },
        "charges": {
          "type": "array",
          "description": "Limited uses this content grants: a wand's charges, a ring's castings per day. The rule lives here; how many are left lives in the ledger entry's `bound`.",
          "items": {
            "$ref": "#/$defs/charge"
          }
        },
        "timers": {
          "type": "array",
          "description": "How long it lasts and when it can be used again. The default lives here; what is left lives in the ledger entry's `bound`.",
          "items": {
            "$ref": "#/$defs/timer"
          }
        },
        "requirements": {
          "type": "array",
          "description": "What a character must be to take this content. Each is reported in its own words when unmet.",
          "items": {
            "$ref": "#/$defs/requirement"
          }
        },
        "meta": {
          "type": "object",
          "description": "What an authoring tool remembers about how it built this content, so it can reopen it as it left it. Never evaluated.",
          "additionalProperties": {
            "type": "string"
          }
        }
      },
      "description": "One applied source. Authoritative for evaluation once snapshotted."
    },
    "requirement": {
      "type": "object",
      "required": [
        "text",
        "when"
      ],
      "additionalProperties": false,
      "properties": {
        "text": {
          "type": "string",
          "description": "The requirement in words: \"Needs a Strength or Dexterity skill at rank 5\"."
        },
        "when": {
          "$ref": "common.schema.json#/$defs/expression",
          "description": "The test, true when the requirement is met: \"maxOf(entries(tag('stat:str')), 'rank') >= 5\"."
        }
      }
    },
    "rollDescriptor": {
      "type": "object",
      "required": [
        "id"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "type": "string"
        },
        "dice": {
          "type": "array",
          "items": {
            "$ref": "common.schema.json#/$defs/term"
          }
        },
        "bonus": {
          "$ref": "common.schema.json#/$defs/expression",
          "description": "Evaluated in the wielder's scope: @abilities.str.mod."
        },
        "tags": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "The line's default damage type. Terms declaring none inherit it."
        },
        "flags": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "description": "A layer-2 dice line. The id is local to the declaring entity; the engine instantiates it via a path pattern."
    },
    "charge": {
      "type": "object",
      "required": [
        "max"
      ],
      "additionalProperties": false,
      "properties": {
        "key": {
          "type": "string",
          "default": "charges",
          "description": "The `bound` key holding what is left; read as @source.<key>."
        },
        "max": {
          "$ref": "common.schema.json#/$defs/expression",
          "description": "Refilled to this on a matching rest. An expression, so it may read the owner."
        },
        "resetOn": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Rest period identifiers, declared in the system's restTypes."
        }
      }
    },
    "timer": {
      "type": "object",
      "required": [
        "key"
      ],
      "additionalProperties": false,
      "properties": {
        "key": {
          "type": "string",
          "enum": [
            "duration",
            "cooldown"
          ]
        },
        "amount": {
          "type": "integer",
          "minimum": 0,
          "description": "With unit: something a player can count down."
        },
        "unit": {
          "type": "string",
          "enum": [
            "seconds",
            "rounds",
            "minutes",
            "hours",
            "days"
          ]
        },
        "text": {
          "type": "string",
          "description": "For what no amount says: \"until the end of combat\". Not with amount or unit."
        }
      },
      "description": "A duration or a cooldown: an amount of a unit, or text."
    }
  }
}
