{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://tinyworld.local/world.schema.json",
  "title": "TinyWorld",
  "description": "Scene description for the Tiny World Builder. The renderer supports home grids from 8x8 to 20x20, plus sparse user-edited ghost-board cells outside the home grid. Each non-default cell has a terrain, optional object kind, separate object floors and terrainFloors counts, optional house buildingType, optional fenceSide, optional decorative extras, optional within-tile transform, and optional appearance overrides.",
  "type": "object",
  "additionalProperties": false,
  "required": ["v", "cells"],
  "properties": {
    "v": {
      "type": "integer",
      "enum": [4],
      "description": "Schema version. Must be 4."
    },
    "gridSize": {
      "type": "integer",
      "enum": [8, 10, 12, 16, 20],
      "description": "Optional home board edge length. Defaults to the current app grid when omitted."
    },
    "cells": {
      "type": "array",
      "description": "Sparse list of non-default cells. The app exports compact tuple cells but also accepts object cells for AI/tooling convenience. Coordinates may include user-edited ghost-board cells outside the home grid.",
      "items": { "$ref": "#/$defs/cell" }
    },
    "islands": {
      "type": "array",
      "description": "Optional editable duplicate island boards. Cells on these islands use world coordinates derived from boardX/boardZ and are saved in cells only when non-default.",
      "items": { "$ref": "#/$defs/island" }
    },
    "moorings": {
      "type": "array",
      "description": "Optional point-to-point cable ties between the home board and duplicate islands. Anchors are stored in home or island local coordinates so cables follow moved islands.",
      "maxItems": 96,
      "items": { "$ref": "#/$defs/mooring" }
    },
    "cameraMode": {
      "type": "string",
      "enum": ["ortho", "topdown", "perspective", "fp"],
      "description": "Optional preferred camera. Saved worlds usually use ortho, perspective, or fp; topdown is accepted for imports."
    },
    "toolId": {
      "type": "string",
      "description": "Optional tool to leave selected after load. Ignored if unknown."
    },
    "useLandscapeEngine": {
      "type": "boolean",
      "description": "Optional legacy/generated flag for discrete LandscapeEngine-derived worlds."
    },
    "landscapeMeshMode": {
      "type": "boolean",
      "description": "Optional flag that restores continuous LandscapeEngine terrain in place of home-board tile meshes."
    },
    "landscapeMeshBiome": {
      "type": "string",
      "enum": ["grassland", "desert", "snow"],
      "description": "Optional biome used by continuous LandscapeEngine terrain."
    },
    "landscapeMeshStyle": {
      "type": "string",
      "enum": ["lowpoly", "realistic"],
      "description": "Optional material style used by continuous LandscapeEngine terrain."
    },
    "landscapeEngineSeed": {
      "type": ["number", "string", "null"],
      "description": "Optional LandscapeEngine seed for restored continuous or sampled terrain."
    },
    "landscapeEngineBiome": {
      "type": ["string", "null"],
      "enum": ["grassland", "desert", "snow", null],
      "description": "Optional LandscapeEngine biome for restored continuous or sampled terrain."
    },
    "planetLandscape": {
      "type": ["object", "null"],
      "additionalProperties": false,
      "description": "Optional LandscapeEngine planet surface rendered below the floating home board.",
      "properties": {
        "enabled": { "type": "boolean" },
        "seed": { "type": ["number", "string"] },
        "biome": { "type": "string", "enum": ["grassland", "desert", "snow"] },
        "styleMode": { "type": "string", "enum": ["lowpoly", "realistic"] },
        "drop": { "type": "number", "minimum": 20, "maximum": 300 }
      }
    }
  },
  "$defs": {
    "coord": { "type": "integer", "minimum": -1024, "maximum": 1024 },
    "terrain": {
      "type": "string",
      "enum": ["grass", "path", "dirt", "water", "stone", "lava", "sand", "snow"],
      "description": "Tile material. Water and lava normally clear hosted kinds except bridge/rock handling in the renderer. Dirt is auto-applied under crops. Stone/snow read as mountain caps; sand as a water-edge beach."
    },
    "kind": {
      "type": ["string", "null"],
      "enum": [null, "house", "tree", "fence", "rock", "bridge", "crop", "corn", "wheat", "pumpkin", "carrot", "sunflower", "tuft", "flower", "bush", "cow", "sheep", "lamp-post", "spotlight", "voxel-build", "model-stamp"]
    },
    "island": {
      "type": "object",
      "additionalProperties": false,
      "required": ["boardX", "boardZ"],
      "properties": {
        "id": { "type": "string" },
        "boardX": { "type": "integer", "minimum": -1024, "maximum": 1024 },
        "boardZ": { "type": "integer", "minimum": -1024, "maximum": 1024 },
        "positionX": { "type": "number" },
        "positionY": { "type": "number" },
        "positionZ": { "type": "number" },
        "rotationY": { "type": "number" },
        "engines": {
          "type": "array",
          "maxItems": 4,
          "items": { "$ref": "#/$defs/islandEngine" }
        }
      }
    },
    "islandEngine": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "id": { "type": "string" },
        "slot": { "type": "integer", "minimum": 0, "maximum": 3 },
        "type": { "type": "string", "enum": ["lift", "turbo", "heavy"] },
        "level": { "type": "integer", "minimum": 1, "maximum": 3 },
        "installed": { "type": "boolean" }
      }
    },
    "mooring": {
      "type": "object",
      "additionalProperties": false,
      "required": ["a", "b"],
      "properties": {
        "id": { "type": "string" },
        "a": { "$ref": "#/$defs/mooringAnchor" },
        "b": { "$ref": "#/$defs/mooringAnchor" }
      }
    },
    "mooringAnchor": {
      "type": "object",
      "additionalProperties": false,
      "required": ["scope", "local"],
      "properties": {
        "scope": { "type": "string", "enum": ["home", "island"] },
        "islandId": { "type": ["string", "null"] },
        "local": { "$ref": "#/$defs/vector3" }
      }
    },
    "vector3": {
      "type": "object",
      "additionalProperties": false,
      "required": ["x", "y", "z"],
      "properties": {
        "x": { "type": "number", "minimum": -2048, "maximum": 2048 },
        "y": { "type": "number", "minimum": -2048, "maximum": 2048 },
        "z": { "type": "number", "minimum": -2048, "maximum": 2048 }
      }
    },
    "buildingType": {
      "type": ["string", "null"],
      "enum": [null, "cottage", "manor", "tower", "turret", "skyscraper"]
    },
    "fenceSide": {
      "type": ["string", "null"],
      "enum": [null, "n", "s", "e", "w", "center-x", "center-z"]
    },
    "extra": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "kind": { "type": "string", "enum": ["fence", "tuft"] },
        "k": { "type": "string", "enum": ["fence", "tuft"] },
        "fenceSide": { "$ref": "#/$defs/fenceSide" },
        "s": { "$ref": "#/$defs/fenceSide" },
        "floors": { "type": "integer", "minimum": 1, "maximum": 8 },
        "f": { "type": "integer", "minimum": 1, "maximum": 8 },
        "appearance": { "$ref": "#/$defs/appearance" },
        "a": { "$ref": "#/$defs/appearance" }
      },
      "anyOf": [
        { "required": ["kind"] },
        { "required": ["k"] }
      ]
    },
    "extras": {
      "type": ["array", "null"],
      "items": { "$ref": "#/$defs/extra" }
    },
    "appearance": {
      "type": ["object", "null"],
      "additionalProperties": false,
      "properties": {
        "bodyColor": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" },
        "topColor": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" },
        "voxelBuildId": { "type": "string", "pattern": "^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$" },
        "modelStampId": { "type": "string", "pattern": "^[a-zA-Z0-9][a-zA-Z0-9_-]{0,95}$" },
        "objectScale": { "type": "number", "minimum": 0.2, "maximum": 24 },
        "scaleX": { "type": "number", "minimum": 0.15, "maximum": 24 },
        "scaleY": { "type": "number", "minimum": 0.15, "maximum": 24 },
        "scaleZ": { "type": "number", "minimum": 0.15, "maximum": 24 },
        "materialTexture": { "type": "string" },
        "materialTextureScale": { "type": "number", "minimum": 0.5, "maximum": 4 },
        "bodyTexture": { "type": "string" },
        "bodyTextureScale": { "type": "number", "minimum": 0.5, "maximum": 4 },
        "topTexture": { "type": "string" },
        "topTextureScale": { "type": "number", "minimum": 0.5, "maximum": 4 },
        "objectStyle": { "type": "string", "enum": ["normal", "voxel"] },
        "fenceStyle": { "type": "string", "enum": ["garden"] }
      }
    },
    "transform": {
      "oneOf": [
        {
          "type": "array",
          "minItems": 3,
          "maxItems": 4,
          "prefixItems": [
            { "type": "number", "description": "rotationY in radians" },
            { "type": "number", "description": "offsetX in tile units" },
            { "type": "number", "description": "offsetZ in tile units" },
            { "type": "number", "description": "offsetY in tile units" }
          ],
          "items": false
        },
        {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "rotationY": { "type": "number" },
            "offsetX": { "type": "number" },
            "offsetZ": { "type": "number" },
            "offsetY": { "type": "number" }
          }
        }
      ]
    },
    "cellObject": {
      "type": "object",
      "additionalProperties": false,
      "required": ["x", "z", "terrain", "kind", "floors", "terrainFloors", "buildingType", "fenceSide"],
      "properties": {
        "x": { "$ref": "#/$defs/coord" },
        "z": { "$ref": "#/$defs/coord" },
        "terrain": { "$ref": "#/$defs/terrain" },
        "kind": { "$ref": "#/$defs/kind" },
        "floors": {
          "type": "integer",
          "minimum": 1,
          "maximum": 8,
          "description": "Object stack/intensity count. For houses it means floors; for non-house props/crops it is enhancement density from repeat taps. It must not be used to raise the ground."
        },
        "terrainFloors": {
          "type": "integer",
          "minimum": 1,
          "maximum": 8,
          "description": "Ground height stack for the terrain layer only. Terrain repeat taps raise this value. Object repeat taps raise floors instead."
        },
        "buildingType": { "$ref": "#/$defs/buildingType" },
        "fenceSide": { "$ref": "#/$defs/fenceSide" },
        "extras": { "$ref": "#/$defs/extras" },
        "transform": { "$ref": "#/$defs/transform" },
        "appearance": { "$ref": "#/$defs/appearance" },
        "customName": {
          "type": "string",
          "description": "Optional short name for a bespoke custom object authored via customParts."
        },
        "customFootprint": {
          "type": "number",
          "minimum": 0.6,
          "maximum": 3.2,
          "description": "Optional initial render footprint in tile units for customParts. Use about 1.1 for compact bridges/props, 1.2 for normal single-cell objects, and 1.5-1.8 only for deliberate hero objects."
        },
        "customParts": { "$ref": "#/$defs/customParts" }
      }
    },
    "customParts": {
      "type": "array",
      "description": "Optional bespoke custom 3D object authored inline as low-poly primitive parts. When present this cell becomes a unique voxel object (it overrides kind). Use for hero/landmark things with no native kind: windmill, statue, fountain, vehicle, robot, lighthouse, ship, glass greenhouse, dome, airship, sign, etc. Keep parts connected and roughly within a compact 1-cell footprint unless the object is explicitly a larger hero. Use native houses/fences/rocks/trees/terrain only when those components are actually needed.",
      "maxItems": 180,
      "items": { "$ref": "#/$defs/customPart" }
    },
    "customPart": {
      "type": "object",
      "additionalProperties": false,
      "required": ["kind", "material", "size", "pos"],
      "description": "One low-poly primitive of a custom object. Coordinates are voxel units centered on the tile (x left-right, y up, z front-back). Use sphere/ellipsoid for rounded envelopes, domes, canopies, and tanks. Sphere/ellipsoid parts may use phiStart/phiLength/thetaStart/thetaLength for curved slices, such as balloon fabric panels. Use cable for ropes, tethers, rigging, and mooring-style connections; cable parts must include from/to endpoints and should still include size/pos for compatibility.",
      "properties": {
        "kind": { "type": "string", "enum": ["box", "cylinder", "cone", "sphere", "ellipsoid", "cable"] },
        "material": {
          "type": "string",
          "description": "Color/material name. Prefer exact TinyWorld names such as wood, woodDark, woodLight, leather, rope, ropeLight, cable, stone, stoneDark, metal, steel, silver, brass, brassDark, copper, bronze, glass, glassBlue, glassGreen, fabric, canvas, fabricRed, fabricOrange, fabricYellow, fabricBlue, fabricPurple, fabricGreen, roof, roofEdge, white, cream, red, orange, yellow, blue, teal, purple, green, black, charcoal. Do not default to stone unless the part is stone."
        },
        "size": { "type": "array", "minItems": 3, "maxItems": 3, "items": { "type": "number" } },
        "pos": { "type": "array", "minItems": 3, "maxItems": 3, "items": { "type": "number" } },
        "scale": { "type": "array", "minItems": 3, "maxItems": 3, "items": { "type": "number" } },
        "from": { "type": "array", "minItems": 3, "maxItems": 3, "items": { "type": "number" } },
        "to": { "type": "array", "minItems": 3, "maxItems": 3, "items": { "type": "number" } },
        "radius": { "type": "number", "minimum": 0.006, "maximum": 0.3 },
        "sag": { "type": "number", "minimum": -8, "maximum": 8 },
        "segments": { "type": "integer", "minimum": 4, "maximum": 64 },
        "verticalSegments": { "type": "integer", "minimum": 3, "maximum": 24 },
        "phiStart": { "type": "number", "minimum": 0, "maximum": 6.28319 },
        "phiLength": { "type": "number", "minimum": 0.05, "maximum": 6.28319 },
        "thetaStart": { "type": "number", "minimum": 0, "maximum": 3.14159 },
        "thetaLength": { "type": "number", "minimum": 0.05, "maximum": 3.14159 }
      }
    },
    "cellTuple": {
      "type": "array",
      "minItems": 4,
      "maxItems": 11,
      "prefixItems": [
        { "$ref": "#/$defs/coord", "description": "x" },
        { "$ref": "#/$defs/coord", "description": "z" },
        { "$ref": "#/$defs/terrain" },
        { "$ref": "#/$defs/kind" },
        { "type": "integer", "minimum": 1, "maximum": 8, "description": "floors" },
        { "$ref": "#/$defs/buildingType" },
        { "type": "integer", "minimum": 1, "maximum": 8, "description": "terrainFloors" },
        { "$ref": "#/$defs/fenceSide" },
        { "$ref": "#/$defs/extras" },
        { "$ref": "#/$defs/transform" },
        { "$ref": "#/$defs/appearance" }
      ],
      "items": false
    },
    "cell": {
      "oneOf": [
        { "$ref": "#/$defs/cellObject" },
        { "$ref": "#/$defs/cellTuple" }
      ]
    }
  }
}
