{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://teztopia.xyz/sdk/world.schema.json",
  "title": "Teztopia scene",
  "description": "A Teztopia world as the builder saves it (spec tezworlds-dev/1). The SDK and the builder edit one cell, parcel \"1,1\" unless editableParcel names another. Units are metres, y is up. Node positions are the centre of the object's bounding box, relative to the cell's corner: x and z run 0 to 16, y = 0 is the ground. Rules this schema cannot express are listed in $comment and are enforced by the site and by third-life-pack.mjs --check.",
  "$comment": "Also enforced: every node must fit inside its 16 x 16 x 16 m cell after rotation (bottom down to y = -0.3); solid nodes (collider) must stay upright, turned around y only; node ids are unique within a cell; a build group id has one name; a cell has at most 8 distinct local files, 16 model instances and 24 image instances; models total at most 500,000 triangles per cell and files at most 48 MB; at most 8192 collision surfaces on solid models per cell; the spawn lies inside bounds and every parcel inside bounds.",
  "type": "object",
  "required": ["spec", "name", "description", "parcelSize", "bounds", "spawn", "parcels"],
  "properties": {
    "spec": { "const": "tezworlds-dev/1" },
    "name": { "type": "string", "maxLength": 160 },
    "description": { "type": "string", "maxLength": 2000 },
    "parcelSize": { "const": 16 },
    "bounds": {
      "description": "[minX, minZ, maxX, maxZ] of the world in metres. The free builder uses [0, 0, 48, 48], with parcel 1,1 spanning 16 to 32.",
      "type": "array",
      "items": { "type": "number" },
      "minItems": 4,
      "maxItems": 4
    },
    "spawn": {
      "description": "Where a visitor arrives, in world coordinates [x, y, z]. The free builder uses [24, 0, 34]: two metres south of the cell.",
      "$ref": "#/$defs/vec3"
    },
    "editableParcel": {
      "description": "The cell being edited. Defaults to \"1,1\".",
      "type": "string",
      "pattern": "^-?\\d+,-?\\d+$"
    },
    "parcels": {
      "type": "array",
      "maxItems": 1000,
      "items": { "$ref": "#/$defs/parcel" }
    }
  },
  "$defs": {
    "vec3": {
      "type": "array",
      "items": { "type": "number" },
      "minItems": 3,
      "maxItems": 3
    },
    "parcel": {
      "type": "object",
      "required": ["id", "title", "common", "nodes"],
      "properties": {
        "id": {
          "description": "\"x,z\" cell index. Its corner sits at (16 x, 16 z) in world coordinates.",
          "type": "string",
          "pattern": "^-?\\d+,-?\\d+$"
        },
        "title": { "type": "string", "maxLength": 160 },
        "common": {
          "description": "Public land. The cell a creator edits is false.",
          "type": "boolean"
        },
        "nodes": {
          "type": "array",
          "maxItems": 500,
          "items": { "$ref": "#/$defs/node" }
        }
      }
    },
    "node": {
      "type": "object",
      "required": ["id", "shape", "position", "size", "color"],
      "properties": {
        "id": { "type": "string", "minLength": 1, "maxLength": 120 },
        "shape": {
          "description": "box; cylinder (size = diameter x, height, diameter z); sphere; torus (an upright ring in the x-y plane); vault (a barrel-vault shell, arch over x, open below); foliage; model (a GLB declared in asset).",
          "enum": ["box", "cylinder", "sphere", "torus", "model", "vault", "foliage"]
        },
        "position": {
          "description": "Centre of the bounding box [x, y, z] in metres, relative to the cell's corner.",
          "$ref": "#/$defs/vec3"
        },
        "size": {
          "description": "Full extent [x, y, z] in metres.",
          "type": "array",
          "items": { "type": "number", "exclusiveMinimum": 0, "maximum": 16 },
          "minItems": 3,
          "maxItems": 3
        },
        "rotation": {
          "description": "XYZ Euler angles in radians. Solid nodes may only turn around y: [0, yaw, 0].",
          "$ref": "#/$defs/vec3"
        },
        "color": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" },
        "material": {
          "enum": [
            "stone",
            "metal",
            "glass",
            "plaster",
            "copper",
            "plywood",
            "painted",
            "concrete",
            "brick",
            "fabric"
          ]
        },
        "collider": {
          "description": "Solid: visitors cannot walk through it. Collides as its bounding box, a model as its collision surfaces.",
          "type": "boolean"
        },
        "collisionOnly": {
          "description": "An invisible solid box. Requires shape box and collider true.",
          "type": "boolean"
        },
        "galleryFrame": {
          "description": "An empty picture frame for a Tezos NFT, filled from the builder's Art tab after import: a thin non-solid box whose picture faces +z at rotation [0, 0, 0] (+x at [0, 1.5708, 0], -x at [0, -1.5708, 0], -z at [0, 3.1416, 0]).",
          "type": "boolean"
        },
        "label": { "type": "string", "maxLength": 200 },
        "buildGroup": {
          "description": "Objects the builder selects and moves together.",
          "type": "object",
          "required": ["id", "name"],
          "properties": {
            "id": { "type": "string", "pattern": "^[a-zA-Z0-9_-]{1,120}$" },
            "name": { "type": "string", "minLength": 1, "maxLength": 80 }
          }
        },
        "asset": { "$ref": "#/$defs/localAsset" },
        "nft": {
          "description": "A live Tezos NFT display: a non-solid box. Usually added in the builder's Art tab.",
          "type": "object",
          "required": ["network", "contract", "tokenId"],
          "properties": {
            "network": { "const": "mainnet" },
            "contract": { "type": "string", "pattern": "^KT1[1-9A-HJ-NP-Za-km-z]{33}$" },
            "tokenId": { "type": "string", "pattern": "^(0|[1-9]\\d{0,77})$" }
          }
        },
        "artwork": {
          "description": "A Tezos artwork copied into the scene by the builder. Written by the site, not by hand.",
          "type": "object"
        },
        "portal": {
          "description": "A link to another published space. Written by the site, not by hand.",
          "type": "object"
        }
      }
    },
    "localAsset": {
      "description": "A file carried in the ZIP as assets/<key>.<ext>. For a GLB the node's shape is model; for an image it is box.",
      "type": "object",
      "required": ["source", "key", "name", "mimeType", "bytes", "width", "height"],
      "properties": {
        "source": { "const": "file" },
        "key": {
          "description": "Lower-case hex SHA-256 of the file's bytes.",
          "type": "string",
          "pattern": "^[a-f0-9]{64}$"
        },
        "name": { "type": "string", "minLength": 1, "maxLength": 200 },
        "mimeType": { "enum": ["model/gltf-binary", "image/png", "image/jpeg", "image/webp"] },
        "bytes": {
          "description": "File size. At most 20,000,000 for a GLB, 8,000,000 for an image.",
          "type": "integer",
          "minimum": 1,
          "maximum": 20000000
        },
        "width": { "description": "1 for a model; pixels for an image.", "type": "integer", "minimum": 1 },
        "height": { "description": "1 for a model; pixels for an image.", "type": "integer", "minimum": 1 },
        "model": {
          "description": "Required for a GLB, and must match the file.",
          "type": "object",
          "required": ["triangles", "points", "texturePixels"],
          "properties": {
            "triangles": { "type": "integer", "minimum": 0, "maximum": 200000 },
            "points": { "type": "integer", "minimum": 0, "maximum": 1000000 },
            "texturePixels": { "type": "integer", "minimum": 0, "maximum": 16777216 }
          }
        },
        "collision": {
          "description": "Collision surfaces of a GLB: boxes [minX, minY, minZ, maxX, maxY, maxZ] in the model's unit cube (-0.5 to 0.5), scaled by the node's size.",
          "type": "array",
          "maxItems": 4096,
          "items": {
            "type": "array",
            "items": { "type": "number", "minimum": -0.50001, "maximum": 0.50001 },
            "minItems": 6,
            "maxItems": 6
          }
        },
        "lod": {
          "description": "A lighter copy for distance, written by the builder's import.",
          "type": "object",
          "required": ["key", "bytes", "model"],
          "properties": {
            "key": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
            "bytes": { "type": "integer", "minimum": 1 },
            "model": { "type": "object" }
          },
          "additionalProperties": false
        }
      }
    }
  }
}
