{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "$id": "https://github.com/Bobikobi/GAME/schema/json/school.schema.json",
  "type": "object",
  "required": [
    "format",
    "id",
    "title",
    "creator",
    "type",
    "school"
  ],
  "properties": {
    "$schema": {
      "type": "string"
    },
    "format": {
      "description": "Format version of this file.",
      "const": 1,
      "type": "number"
    },
    "id": {
      "pattern": "^[a-z0-9_-]+/[a-z0-9_-]+$",
      "maxLength": 80,
      "description": "Stable id \"creator/name\": lowercase letters, digits, \"_\" and \"-\". Never reused once merged.",
      "type": "string"
    },
    "title": {
      "minLength": 1,
      "maxLength": 40,
      "type": "string"
    },
    "creator": {
      "pattern": "^[a-z0-9_-]+$",
      "maxLength": 39,
      "description": "GitHub handle, lowercase.",
      "type": "string"
    },
    "description": {
      "minLength": 1,
      "maxLength": 240,
      "type": "string"
    },
    "ai_tools": {
      "maxItems": 8,
      "description": "AI tools used to make this file or its art. Required when any were used.",
      "type": "array",
      "items": {
        "minLength": 1,
        "maxLength": 60,
        "type": "string"
      }
    },
    "items": {
      "maxItems": 40,
      "type": "array",
      "items": {
        "additionalProperties": false,
        "type": "object",
        "required": [
          "id",
          "name",
          "kind",
          "rarity",
          "sprite",
          "weight"
        ],
        "properties": {
          "id": {
            "pattern": "^[a-z0-9_-]+/[a-z0-9_-]+$",
            "maxLength": 80,
            "description": "Stable id \"creator/name\": lowercase letters, digits, \"_\" and \"-\". Never reused once merged.",
            "type": "string"
          },
          "name": {
            "minLength": 1,
            "maxLength": 40,
            "type": "string"
          },
          "description": {
            "minLength": 1,
            "maxLength": 240,
            "type": "string"
          },
          "kind": {
            "anyOf": [
              {
                "const": "material",
                "type": "string"
              },
              {
                "const": "food",
                "type": "string"
              },
              {
                "const": "tool",
                "type": "string"
              },
              {
                "const": "fragment",
                "type": "string"
              },
              {
                "const": "treasure",
                "type": "string"
              }
            ]
          },
          "rarity": {
            "description": "Declared rarity. The check compares it with the rarity computed from the data.",
            "anyOf": [
              {
                "const": "common",
                "type": "string"
              },
              {
                "const": "uncommon",
                "type": "string"
              },
              {
                "const": "rare",
                "type": "string"
              },
              {
                "const": "legendary",
                "type": "string"
              }
            ]
          },
          "class": {
            "description": "A base class (plant, wood, fibre, ...) or the id of a class an extension declares under one of them.",
            "anyOf": [
              {
                "const": "plant",
                "type": "string"
              },
              {
                "const": "wood",
                "type": "string"
              },
              {
                "const": "fibre",
                "type": "string"
              },
              {
                "const": "stone",
                "type": "string"
              },
              {
                "const": "clay",
                "type": "string"
              },
              {
                "const": "mineral",
                "type": "string"
              },
              {
                "const": "metal",
                "type": "string"
              },
              {
                "const": "animal",
                "type": "string"
              },
              {
                "const": "meal",
                "type": "string"
              },
              {
                "const": "tool",
                "type": "string"
              },
              {
                "const": "garment",
                "type": "string"
              },
              {
                "const": "jewelry",
                "type": "string"
              },
              {
                "const": "relic",
                "type": "string"
              },
              {
                "pattern": "^[a-z0-9_-]+/[a-z0-9_-]+$",
                "maxLength": 80,
                "description": "Stable id \"creator/name\": lowercase letters, digits, \"_\" and \"-\". Never reused once merged.",
                "type": "string"
              }
            ]
          },
          "family": {
            "pattern": "^[a-z0-9_-]+$",
            "maxLength": 24,
            "description": "A group inside the class, for example \"berry\" for every berry. Open: any lowercase word.",
            "type": "string"
          },
          "tags": {
            "maxItems": 8,
            "uniqueItems": true,
            "description": "Open words that describe it, for example \"sweet\" or \"night\".",
            "type": "array",
            "items": {
              "pattern": "^[a-z0-9_-]+$",
              "maxLength": 24,
              "description": "A lowercase word.",
              "type": "string"
            }
          },
          "tier": {
            "minimum": 1,
            "maximum": 20,
            "description": "How advanced it is, from 1. The rarity says how often it appears; the tier says how far up the ladder it is. A tool has the tier of its \"tool\".",
            "type": "integer"
          },
          "sprite": {
            "pattern": "^[a-z0-9_-]+/[a-z0-9_-]+:[a-z0-9_/-]+$",
            "maxLength": 120,
            "description": "A frame of an art kit: \"<kit id>:<frame name>\", for example \"core/base-kit:items/berry\".",
            "type": "string"
          },
          "weight": {
            "minimum": 0,
            "maximum": 20,
            "description": "How heavy one unit is (DECISIONS.md, 2026-10-23): 0 for herbs, seeds and coins, 1 for most things, 3 for logs, stone and ore. The load against the weight limit sets the walking pace.",
            "type": "integer"
          },
          "food": {
            "additionalProperties": false,
            "type": "object",
            "required": [
              "satiety"
            ],
            "properties": {
              "satiety": {
                "minimum": 0,
                "maximum": 100,
                "description": "Health points restored when eaten (the meter holds 100 at the start). 0 for a food that only hurts.",
                "type": "integer"
              },
              "hurt": {
                "minimum": 1,
                "maximum": 100,
                "description": "Health points it takes when eaten, at any hour: a bad fruit or a raw herb (G30).",
                "type": "integer"
              }
            }
          },
          "tool": {
            "additionalProperties": false,
            "type": "object",
            "required": [
              "kind",
              "tier"
            ],
            "properties": {
              "kind": {
                "anyOf": [
                  {
                    "const": "basket",
                    "type": "string"
                  },
                  {
                    "const": "trowel",
                    "type": "string"
                  },
                  {
                    "const": "axe",
                    "type": "string"
                  },
                  {
                    "const": "staff",
                    "type": "string"
                  },
                  {
                    "const": "hammer",
                    "type": "string"
                  },
                  {
                    "const": "pick",
                    "type": "string"
                  },
                  {
                    "const": "rod",
                    "type": "string"
                  }
                ]
              },
              "tier": {
                "minimum": 1,
                "maximum": 5,
                "description": "Better tools work faster. The calculator clamps the total.",
                "type": "integer"
              },
              "uses": {
                "minimum": 1,
                "maximum": 1000,
                "description": "The tool wears out after this many uses. Left out, the tool lasts (DECISIONS.md, 2026-10-09: the crafting economy).",
                "type": "integer"
              }
            }
          },
          "fragment": {
            "additionalProperties": false,
            "description": "A found scrap that teaches a recipe.",
            "type": "object",
            "required": [
              "unlocks"
            ],
            "properties": {
              "unlocks": {
                "pattern": "^[a-z0-9_-]+/[a-z0-9_-]+$",
                "maxLength": 80,
                "description": "Stable id \"creator/name\": lowercase letters, digits, \"_\" and \"-\". Never reused once merged.",
                "type": "string"
              }
            }
          },
          "wear": {
            "additionalProperties": false,
            "description": "Something the character wears (DECISIONS.md, 2026-10-09: the upgrade). A worn item stays in the bag; it takes no place there, but its weight counts (2026-10-23).",
            "type": "object",
            "required": [
              "slot"
            ],
            "properties": {
              "slot": {
                "description": "Where it is worn: head, top, legs (trousers), outfit (takes the top and the legs), feet, jewelry (two places), hand (a tool) or back (one bag or one quiver).",
                "anyOf": [
                  {
                    "const": "head",
                    "type": "string"
                  },
                  {
                    "const": "top",
                    "type": "string"
                  },
                  {
                    "const": "legs",
                    "type": "string"
                  },
                  {
                    "const": "outfit",
                    "type": "string"
                  },
                  {
                    "const": "feet",
                    "type": "string"
                  },
                  {
                    "const": "jewelry",
                    "type": "string"
                  },
                  {
                    "const": "hand",
                    "type": "string"
                  },
                  {
                    "const": "back",
                    "type": "string"
                  }
                ]
              },
              "look": {
                "description": "A look: worn on the look layer over the work clothes. It shows and counts for nothing, so it has no effects and no set.",
                "type": "boolean"
              },
              "effects": {
                "minItems": 1,
                "maxItems": 2,
                "description": "Small bonuses while it is worn on the work layer. Two at most, each effect once.",
                "type": "array",
                "items": {
                  "additionalProperties": false,
                  "type": "object",
                  "required": [
                    "effect",
                    "value"
                  ],
                  "properties": {
                    "effect": {
                      "description": "What a worn item helps: gathering, digging or crafting speed, the craft chance, or rare finds.",
                      "anyOf": [
                        {
                          "const": "gather_speed_pct",
                          "type": "string"
                        },
                        {
                          "const": "dig_speed_pct",
                          "type": "string"
                        },
                        {
                          "const": "craft_speed_pct",
                          "type": "string"
                        },
                        {
                          "const": "craft_chance_pct",
                          "type": "string"
                        },
                        {
                          "const": "rare_find_pct",
                          "type": "string"
                        }
                      ]
                    },
                    "value": {
                      "minimum": 1,
                      "maximum": 100,
                      "description": "Percent, at most the cap of the item's tier (params.wear_effect_max_pct).",
                      "type": "integer"
                    }
                  }
                }
              },
              "set": {
                "pattern": "^[a-z0-9_-]+/[a-z0-9_-]+$",
                "maxLength": 80,
                "description": "A set of one creator: when every piece of it is worn, their effects grow by params.wear_set_bonus_pct.",
                "type": "string"
              },
              "places": {
                "minimum": 1,
                "maximum": 99,
                "description": "A bag on the back: the places it gives, instead of the hands' `bag_base`. Only a work piece on the back carries it (DECISIONS.md, 2026-10-23).",
                "type": "integer"
              },
              "load_bonus": {
                "minimum": 1,
                "maximum": 100,
                "description": "A bag on the back that moves the load to the hips: what it adds to the weight limit (`load.base` + might). Needs `places`.",
                "type": "integer"
              }
            }
          },
          "grade": {
            "additionalProperties": false,
            "description": "A finer grade of another item: the same good off a better animal. A stack splits by grade the way it splits by tier, so each grade is an item of its own; it keeps the plain item's class, kind, tier and value, and a recipe asks for it with \"min_grade\".",
            "type": "object",
            "required": [
              "of",
              "step"
            ],
            "properties": {
              "of": {
                "pattern": "^[a-z0-9_-]+/[a-z0-9_-]+$",
                "maxLength": 80,
                "description": "Stable id \"creator/name\": lowercase letters, digits, \"_\" and \"-\". Never reused once merged.",
                "type": "string"
              },
              "step": {
                "minimum": 2,
                "maximum": 3,
                "description": "2 is fine, 3 is true. A grade is read as a pose on the screen, never as a number (HUNTING section 5).",
                "type": "integer"
              }
            }
          },
          "premium_later": {
            "description": "Marks a look that may also be sold for premium currency once stage 6 brings it. Only a look item with no effect may carry it, and it stays obtainable in the game (DECISIONS.md, 2026-10-09).",
            "type": "boolean"
          }
        }
      }
    },
    "classes": {
      "maxItems": 10,
      "description": "Classes of your own, each under one base class.",
      "type": "array",
      "items": {
        "additionalProperties": false,
        "description": "A class of your own, declared under one base class. Every rule written for the base class holds for it.",
        "type": "object",
        "required": [
          "id",
          "name",
          "base"
        ],
        "properties": {
          "id": {
            "pattern": "^[a-z0-9_-]+/[a-z0-9_-]+$",
            "maxLength": 80,
            "description": "Stable id \"creator/name\": lowercase letters, digits, \"_\" and \"-\". Never reused once merged.",
            "type": "string"
          },
          "name": {
            "minLength": 1,
            "maxLength": 40,
            "type": "string"
          },
          "base": {
            "description": "plant (leaves, herbs, berries, fruit, roots, mushrooms), wood, fibre (reeds, grass, thread, cloth), stone, clay (clay, sand, pottery, brick), mineral (crystals, gems, salt, ore), metal (waits for its discovery), animal (feathers, wool, hide, bone), meal (cooked food), tool, garment (worn clothes), jewelry, relic (old things from the ruins).",
            "anyOf": [
              {
                "const": "plant",
                "type": "string"
              },
              {
                "const": "wood",
                "type": "string"
              },
              {
                "const": "fibre",
                "type": "string"
              },
              {
                "const": "stone",
                "type": "string"
              },
              {
                "const": "clay",
                "type": "string"
              },
              {
                "const": "mineral",
                "type": "string"
              },
              {
                "const": "metal",
                "type": "string"
              },
              {
                "const": "animal",
                "type": "string"
              },
              {
                "const": "meal",
                "type": "string"
              },
              {
                "const": "tool",
                "type": "string"
              },
              {
                "const": "garment",
                "type": "string"
              },
              {
                "const": "jewelry",
                "type": "string"
              },
              {
                "const": "relic",
                "type": "string"
              }
            ]
          },
          "description": {
            "minLength": 1,
            "maxLength": 240,
            "type": "string"
          }
        }
      }
    },
    "type": {
      "const": "school",
      "type": "string"
    },
    "school": {
      "additionalProperties": false,
      "type": "object",
      "required": [
        "name",
        "profession",
        "nodes"
      ],
      "properties": {
        "name": {
          "minLength": 1,
          "maxLength": 40,
          "type": "string"
        },
        "profession": {
          "pattern": "^[a-z0-9_-]+/[a-z0-9_-]+$",
          "maxLength": 80,
          "description": "The profession whose wing this is. The profession keeps its levels; the school is what the points buy.",
          "type": "string"
        },
        "nodes": {
          "minItems": 1,
          "maxItems": 400,
          "description": "The paths and the corners. At most `growth.nodes_max` nodes for each race (the paths and that race's corner).",
          "type": "array",
          "items": {
            "additionalProperties": false,
            "type": "object",
            "required": [
              "id",
              "name",
              "text",
              "size",
              "effect",
              "value"
            ],
            "properties": {
              "id": {
                "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
                "maxLength": 40,
                "description": "The node's name inside its school, for example \"steady\". A learned node is kept as \"<school id>/<node id>\".",
                "type": "string"
              },
              "name": {
                "minLength": 1,
                "maxLength": 40,
                "type": "string",
                "description": "A thing the player knows how to do, for example \"Depth sense\"."
              },
              "text": {
                "minLength": 1,
                "maxLength": 240,
                "type": "string",
                "description": "One sentence in words, never a number or a percentage (the number stays in the code)."
              },
              "size": {
                "anyOf": [
                  {
                    "const": "small",
                    "type": "string"
                  },
                  {
                    "const": "major",
                    "type": "string"
                  },
                  {
                    "const": "corner",
                    "type": "string"
                  },
                  {
                    "const": "keystone",
                    "type": "string"
                  }
                ]
              },
              "needs": {
                "maxItems": 3,
                "uniqueItems": true,
                "description": "The nodes learned before this one, all of them: its place on the path. Most are of the same school; a node of another school joins the two wings.",
                "type": "array",
                "items": {
                  "pattern": "^([a-z0-9]+(-[a-z0-9]+)*/[a-z0-9]+(-[a-z0-9]+)*/)?[a-z0-9]+(-[a-z0-9]+)*$",
                  "maxLength": 120,
                  "description": "A node of the same school by its id (\"steady\"), or of another school by \"<school id>/<node id>\" (\"core/digging-school/steady\").",
                  "type": "string"
                }
              },
              "cost": {
                "minimum": 1,
                "maximum": 20,
                "description": "Its price in skill points, inside the band of its size (params growth.cost, min to max). Left out: the size's price.",
                "type": "integer"
              },
              "key": {
                "additionalProperties": false,
                "description": "Keys: the attribute pips the node asks for, for example { \"senses\": 3 }. A key opens the node; it is never spent.",
                "type": "object",
                "properties": {
                  "might": {
                    "minimum": 1,
                    "maximum": 10,
                    "type": "integer"
                  },
                  "senses": {
                    "minimum": 1,
                    "maximum": 10,
                    "type": "integer"
                  },
                  "agility": {
                    "minimum": 1,
                    "maximum": 10,
                    "type": "integer"
                  },
                  "luck": {
                    "minimum": 1,
                    "maximum": 10,
                    "type": "integer"
                  },
                  "stamina": {
                    "minimum": 1,
                    "maximum": 10,
                    "type": "integer"
                  },
                  "spirit": {
                    "minimum": 1,
                    "maximum": 10,
                    "type": "integer"
                  }
                }
              },
              "effect": {
                "description": "What a technique does. A closed list: the kernel knows how to apply each one. A craft effect of a technique (craft_speed_pct, craft_chance_pct) helps the recipes of its own profession.",
                "anyOf": [
                  {
                    "const": "see_ripe",
                    "type": "string"
                  },
                  {
                    "const": "timing_window_pct",
                    "type": "string"
                  },
                  {
                    "const": "second_hint",
                    "type": "string"
                  },
                  {
                    "const": "rare_find_pct",
                    "type": "string"
                  },
                  {
                    "const": "craft_speed_pct",
                    "type": "string"
                  },
                  {
                    "const": "meal_fill_pct",
                    "type": "string"
                  },
                  {
                    "const": "gather_speed_pct",
                    "type": "string"
                  },
                  {
                    "const": "dig_speed_pct",
                    "type": "string"
                  },
                  {
                    "const": "craft_chance_pct",
                    "type": "string"
                  }
                ]
              },
              "value": {
                "minimum": 0,
                "maximum": 100,
                "description": "The number the effect moves. Shown only on a long press, never on the tree.",
                "type": "integer"
              },
              "slot": {
                "description": "A technique: it works only while it sits in an active slot of the build. Left out: always on once learned. Never on a small node.",
                "type": "boolean"
              },
              "race": {
                "pattern": "^[a-z0-9_-]+/[a-z0-9_-]+$",
                "maxLength": 80,
                "description": "A corner node only: the race whose node this is. Only a player of that race sees and learns it.",
                "type": "string"
              },
              "icon": {
                "pattern": "^[a-z0-9_-]+/[a-z0-9_-]+$",
                "maxLength": 80,
                "description": "An item whose sprite pictures the node on the tree.",
                "type": "string"
              },
              "cluster": {
                "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
                "maxLength": 40,
                "description": "The cluster of its school it belongs to (the school's `clusters`): nodes that stand together.",
                "type": "string"
              },
              "pos": {
                "additionalProperties": false,
                "description": "Where it stands on the tree, in steps from the trunk (x to the right, y down). A school gives every node a place or none; with none, the screen lays the school out from its paths.",
                "type": "object",
                "required": [
                  "x",
                  "y"
                ],
                "properties": {
                  "x": {
                    "minimum": -400,
                    "maximum": 400,
                    "type": "integer"
                  },
                  "y": {
                    "minimum": -400,
                    "maximum": 400,
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "tint": {
          "description": "The colour family of the core palette the wing is drawn in. Left out: wood.",
          "anyOf": [
            {
              "const": "green",
              "type": "string"
            },
            {
              "const": "wood",
              "type": "string"
            },
            {
              "const": "skin",
              "type": "string"
            },
            {
              "const": "red",
              "type": "string"
            },
            {
              "const": "water",
              "type": "string"
            },
            {
              "const": "plum",
              "type": "string"
            },
            {
              "const": "fire",
              "type": "string"
            },
            {
              "const": "grey",
              "type": "string"
            }
          ]
        },
        "angle": {
          "minimum": 0,
          "maximum": 359,
          "description": "The direction the wing grows from the trunk, in degrees clockwise from the right. Left out: the wings share the circle in the order of their ids.",
          "type": "integer"
        },
        "clusters": {
          "maxItems": 40,
          "description": "Named groups of the school's nodes, for example \"the trowel\". A node names its cluster.",
          "type": "array",
          "items": {
            "additionalProperties": false,
            "type": "object",
            "required": [
              "id",
              "name"
            ],
            "properties": {
              "id": {
                "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
                "maxLength": 40,
                "description": "The node's name inside its school, for example \"steady\". A learned node is kept as \"<school id>/<node id>\".",
                "type": "string"
              },
              "name": {
                "minLength": 1,
                "maxLength": 40,
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}
