{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://iamar.world/schemas/scene-0.schema.json",
  "title": "iamar world scene (format 0)",
  "description": "scene.json: the data-only description of a world. Metres, +Y up, -Z forward, right-handed (the glTF axes). Angles in degrees. See docs/WORLD_FORMAT.md.",
  "type": "object",
  "required": [
    "format",
    "format_version"
  ],
  "additionalProperties": false,
  "patternProperties": {
    "^x-": {}
  },
  "properties": {
    "format": {
      "const": "iamar-scene"
    },
    "format_version": {
      "const": 0
    },
    "environment": {
      "$ref": "#/$defs/environment"
    },
    "terrain": {
      "$ref": "#/$defs/terrain"
    },
    "water": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/water"
      }
    },
    "objects": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/object"
      }
    },
    "primitives": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/primitive"
      }
    },
    "lights": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/light"
      }
    },
    "spawn_points": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/spawn_point"
      }
    },
    "sounds": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/sound"
      }
    },
    "portals": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/portal"
      }
    },
    "triggers": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/trigger"
      }
    },
    "paths": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/path_line"
      }
    },
    "npcs": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/npc"
      }
    },
    "creatures": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/creature"
      }
    },
    "quests": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/quest"
      }
    }
  },
  "$defs": {
    "id": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
    },
    "file": {
      "type": "string",
      "pattern": "^(?!/)(?!.*\\.\\.)(?!.*//)[A-Za-z0-9_][A-Za-z0-9_./-]{0,199}$"
    },
    "glb": {
      "type": "string",
      "pattern": "^(?!/)(?!.*\\.\\.)(?!.*//)[A-Za-z0-9_][A-Za-z0-9_./-]{0,195}\\.glb$"
    },
    "image": {
      "type": "string",
      "pattern": "^(?!/)(?!.*\\.\\.)(?!.*//)[A-Za-z0-9_][A-Za-z0-9_./-]{0,195}\\.(png|jpg|jpeg)$"
    },
    "audio": {
      "type": "string",
      "pattern": "^(?!/)(?!.*\\.\\.)(?!.*//)[A-Za-z0-9_][A-Za-z0-9_./-]{0,195}\\.ogg$"
    },
    "vec2": {
      "type": "array",
      "items": {
        "type": "number",
        "minimum": -100000,
        "maximum": 100000,
        "description": "metres or degrees; within 100 km, as the world API requires"
      },
      "minItems": 2,
      "maxItems": 2
    },
    "vec3": {
      "type": "array",
      "items": {
        "type": "number",
        "minimum": -100000,
        "maximum": 100000,
        "description": "metres or degrees; within 100 km, as the world API requires"
      },
      "minItems": 3,
      "maxItems": 3
    },
    "xz_or_xyz": {
      "type": "array",
      "items": {
        "type": "number",
        "minimum": -100000,
        "maximum": 100000,
        "description": "metres or degrees; within 100 km, as the world API requires"
      },
      "minItems": 2,
      "maxItems": 3
    },
    "scale": {
      "oneOf": [
        {
          "type": "number",
          "exclusiveMinimum": 0,
          "maximum": 10000
        },
        {
          "$ref": "#/$defs/vec3"
        }
      ]
    },
    "color": {
      "type": "string",
      "pattern": "^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
    },
    "address": {
      "type": "string",
      "description": "iamar://<owner>/<world>, iamar://<owner> (their main world) or a local world id; optional #<spawn point id>.",
      "pattern": "^(iamar://[a-z0-9][a-z0-9_-]{0,63}(/[a-z0-9][a-z0-9_-]{0,63})?|[a-z0-9][a-z0-9_-]{0,63})(#[a-z0-9][a-z0-9_-]{0,63})?$"
    },
    "transform": {
      "type": "object",
      "properties": {
        "position": {
          "$ref": "#/$defs/vec3"
        },
        "rotation": {
          "$ref": "#/$defs/vec3",
          "description": "Euler degrees [x, y, z], applied Y (yaw) then X (pitch) then Z (roll), all intrinsic."
        },
        "scale": {
          "$ref": "#/$defs/scale"
        }
      }
    },
    "collision": {
      "enum": [
        "none",
        "box",
        "mesh"
      ]
    },
    "environment": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "sky": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "type": {
              "enum": [
                "procedural",
                "cubemap",
                "color"
              ]
            },
            "color": {
              "$ref": "#/$defs/color"
            },
            "top_color": {
              "$ref": "#/$defs/color"
            },
            "horizon_color": {
              "$ref": "#/$defs/color"
            },
            "ground_color": {
              "$ref": "#/$defs/color"
            },
            "faces": {
              "type": "object",
              "description": "Cube map faces as seen from inside the cube (OpenGL order and orientation).",
              "required": [
                "px",
                "nx",
                "py",
                "ny",
                "pz",
                "nz"
              ],
              "additionalProperties": false,
              "properties": {
                "px": {
                  "$ref": "#/$defs/image"
                },
                "nx": {
                  "$ref": "#/$defs/image"
                },
                "py": {
                  "$ref": "#/$defs/image"
                },
                "ny": {
                  "$ref": "#/$defs/image"
                },
                "pz": {
                  "$ref": "#/$defs/image"
                },
                "nz": {
                  "$ref": "#/$defs/image"
                }
              },
              "patternProperties": {
                "^x-": {}
              }
            },
            "rotation": {
              "type": "number",
              "description": "Yaw of the cube map, degrees."
            },
            "energy": {
              "type": "number",
              "minimum": 0
            }
          },
          "patternProperties": {
            "^x-": {}
          }
        },
        "time_of_day": {
          "type": "number",
          "minimum": 0,
          "maximum": 24,
          "description": "Hours; positions the sun when sun.direction is absent. Default 12."
        },
        "day_length_minutes": {
          "type": "number",
          "minimum": 0,
          "description": "0 = time stands still (default)."
        },
        "sun": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "enabled": {
              "type": "boolean"
            },
            "color": {
              "$ref": "#/$defs/color"
            },
            "energy": {
              "type": "number",
              "minimum": 0
            },
            "direction": {
              "$ref": "#/$defs/vec3",
              "description": "Direction the light travels (pointing down from the sun)."
            },
            "shadows": {
              "type": "boolean"
            }
          },
          "patternProperties": {
            "^x-": {}
          }
        },
        "ambient": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "color": {
              "$ref": "#/$defs/color"
            },
            "energy": {
              "type": "number",
              "minimum": 0
            },
            "source": {
              "enum": [
                "color",
                "sky"
              ]
            }
          },
          "patternProperties": {
            "^x-": {}
          }
        },
        "fog": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "enabled": {
              "type": "boolean"
            },
            "color": {
              "$ref": "#/$defs/color"
            },
            "density": {
              "type": "number",
              "minimum": 0,
              "maximum": 1
            },
            "height": {
              "type": "number"
            },
            "height_density": {
              "type": "number",
              "minimum": 0
            }
          },
          "patternProperties": {
            "^x-": {}
          }
        },
        "weather": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "kind": {
              "enum": [
                "clear",
                "cloudy",
                "rain",
                "storm",
                "snow"
              ]
            },
            "intensity": {
              "type": "number",
              "minimum": 0,
              "maximum": 1
            }
          },
          "patternProperties": {
            "^x-": {}
          }
        },
        "exposure": {
          "type": "number",
          "minimum": 0
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    },
    "terrain": {
      "type": "object",
      "required": [
        "type"
      ],
      "additionalProperties": false,
      "properties": {
        "type": {
          "enum": [
            "none",
            "flat",
            "heightmap",
            "procedural"
          ]
        },
        "size": {
          "$ref": "#/$defs/vec2",
          "description": "[width x, depth z] metres, centred on position."
        },
        "position": {
          "$ref": "#/$defs/vec3"
        },
        "resolution": {
          "type": "integer",
          "minimum": 2,
          "maximum": 1025,
          "description": "Vertices per side for heightmap/procedural (default 129)."
        },
        "heightmap": {
          "$ref": "#/$defs/image",
          "description": "Greyscale image; black = min_height, white = max_height. Rows run from -Z (top of image) to +Z."
        },
        "min_height": {
          "type": "number"
        },
        "max_height": {
          "type": "number"
        },
        "procedural": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "seed": {
              "type": "integer"
            },
            "amplitude": {
              "type": "number",
              "minimum": 0
            },
            "frequency": {
              "type": "number",
              "exclusiveMinimum": 0,
              "description": "Cycles per metre of the first octave."
            },
            "octaves": {
              "type": "integer",
              "minimum": 1,
              "maximum": 8
            },
            "flat_radius": {
              "type": "number",
              "minimum": 0,
              "description": "Keep a flat meadow of this radius round the terrain centre."
            }
          },
          "patternProperties": {
            "^x-": {}
          }
        },
        "materials": {
          "type": "array",
          "maxItems": 4,
          "description": "Ground layers, lowest first. Each covers heights from its min_height up; steep slopes use the first layer with slope true.",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "texture": {
                "$ref": "#/$defs/image"
              },
              "color": {
                "$ref": "#/$defs/color"
              },
              "uv_scale": {
                "type": "number",
                "exclusiveMinimum": 0,
                "description": "Metres per texture repeat (default 4)."
              },
              "min_height": {
                "type": "number"
              },
              "slope": {
                "type": "boolean"
              }
            },
            "patternProperties": {
              "^x-": {}
            }
          }
        },
        "collision": {
          "type": "boolean"
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    },
    "water": {
      "type": "object",
      "required": [
        "position",
        "size"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "position": {
          "$ref": "#/$defs/vec3",
          "description": "Centre of the water surface."
        },
        "size": {
          "$ref": "#/$defs/vec2"
        },
        "depth": {
          "type": "number",
          "minimum": 0
        },
        "color": {
          "$ref": "#/$defs/color"
        },
        "swimmable": {
          "type": "boolean"
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    },
    "attachment": {
      "type": "object",
      "required": [
        "asset",
        "bone"
      ],
      "additionalProperties": false,
      "properties": {
        "asset": {
          "$ref": "#/$defs/glb"
        },
        "bone": {
          "type": "string",
          "maxLength": 64
        },
        "position": {
          "$ref": "#/$defs/vec3"
        },
        "rotation": {
          "$ref": "#/$defs/vec3"
        },
        "scale": {
          "$ref": "#/$defs/scale"
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    },
    "object": {
      "type": "object",
      "required": [
        "asset"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "asset": {
          "$ref": "#/$defs/glb"
        },
        "position": {
          "$ref": "#/$defs/vec3"
        },
        "rotation": {
          "$ref": "#/$defs/vec3"
        },
        "scale": {
          "$ref": "#/$defs/scale"
        },
        "collision": {
          "$ref": "#/$defs/collision"
        },
        "animation": {
          "type": "string",
          "maxLength": 64,
          "description": "Animation to loop, by glTF animation name."
        },
        "attach": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/attachment"
          }
        },
        "cast_shadow": {
          "type": "boolean"
        },
        "visible_range": {
          "type": "number",
          "minimum": 0,
          "description": "Hide beyond this distance (0 = always)."
        },
        "min_tier": {
          "enum": [
            "low",
            "medium",
            "high",
            "ultra"
          ],
          "description": "drawn only at this hardware tier and better (docs/WORLD_TIERS.md)"
        },
        "impostor": {
          "type": "number",
          "minimum": 5,
          "maximum": 100000,
          "description": "beyond this many metres the object is drawn as its impostor card (WorldImpostors; pictures from iamar-world impostors)"
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    },
    "primitive": {
      "type": "object",
      "required": [
        "shape"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "shape": {
          "enum": [
            "box",
            "sphere",
            "cylinder",
            "capsule",
            "plane"
          ]
        },
        "size": {
          "$ref": "#/$defs/vec3",
          "description": "box: [x, y, z]; plane: [x, 0, z]."
        },
        "radius": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "height": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "position": {
          "$ref": "#/$defs/vec3"
        },
        "rotation": {
          "$ref": "#/$defs/vec3"
        },
        "scale": {
          "$ref": "#/$defs/scale"
        },
        "color": {
          "$ref": "#/$defs/color"
        },
        "texture": {
          "$ref": "#/$defs/image"
        },
        "uv_scale": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "emission": {
          "$ref": "#/$defs/color"
        },
        "unlit": {
          "type": "boolean"
        },
        "collision": {
          "$ref": "#/$defs/collision"
        },
        "min_tier": {
          "enum": [
            "low",
            "medium",
            "high",
            "ultra"
          ],
          "description": "drawn only at this hardware tier and better (docs/WORLD_TIERS.md)"
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    },
    "light": {
      "type": "object",
      "required": [
        "type"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "type": {
          "enum": [
            "point",
            "spot",
            "directional"
          ]
        },
        "position": {
          "$ref": "#/$defs/vec3"
        },
        "rotation": {
          "$ref": "#/$defs/vec3",
          "description": "spot/directional shine along -Z of this rotation."
        },
        "color": {
          "$ref": "#/$defs/color"
        },
        "energy": {
          "type": "number",
          "minimum": 0
        },
        "range": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "point/spot reach in metres (default 10)."
        },
        "attenuation": {
          "type": "number",
          "minimum": 0,
          "description": "Falloff exponent: 0 = constant (2009 Ogre style), 1 = default, 2 = inverse square."
        },
        "spot_angle": {
          "type": "number",
          "exclusiveMinimum": 0,
          "maximum": 90
        },
        "shadows": {
          "type": "boolean"
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    },
    "spawn_point": {
      "type": "object",
      "required": [
        "id",
        "position"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "position": {
          "$ref": "#/$defs/vec3",
          "description": "Feet position. The browser drops the visitor onto the ground below."
        },
        "yaw": {
          "type": "number",
          "description": "Facing, degrees about +Y; 0 looks toward -Z, 90 toward -X."
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    },
    "sound": {
      "type": "object",
      "required": [
        "file"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "file": {
          "$ref": "#/$defs/audio"
        },
        "position": {
          "$ref": "#/$defs/vec3",
          "description": "Absent = ambient (not positional)."
        },
        "radius": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Positional sounds fade out by this distance (default 20)."
        },
        "volume_db": {
          "type": "number",
          "maximum": 12
        },
        "loop": {
          "type": "boolean"
        },
        "autoplay": {
          "type": "boolean"
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    },
    "portal": {
      "type": "object",
      "required": [
        "to",
        "position"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "to": {
          "$ref": "#/$defs/address"
        },
        "label": {
          "type": "string",
          "maxLength": 80
        },
        "position": {
          "$ref": "#/$defs/vec3",
          "description": "Centre of the bottom of the portal (on the ground)."
        },
        "yaw": {
          "type": "number"
        },
        "size": {
          "$ref": "#/$defs/vec3",
          "description": "[width, height, depth] of the entry volume; default [2.4, 3.2, 0.8]."
        },
        "visual": {
          "enum": [
            "ring",
            "arch",
            "none"
          ]
        },
        "color": {
          "$ref": "#/$defs/color"
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    },
    "trigger": {
      "type": "object",
      "required": [
        "id",
        "position"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "shape": {
          "enum": [
            "box",
            "sphere"
          ]
        },
        "position": {
          "$ref": "#/$defs/vec3"
        },
        "yaw": {
          "type": "number"
        },
        "size": {
          "$ref": "#/$defs/vec3"
        },
        "radius": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "debug_visible": {
          "type": "boolean"
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    },
    "path_line": {
      "type": "object",
      "required": [
        "id",
        "points"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "points": {
          "type": "array",
          "minItems": 2,
          "items": {
            "$ref": "#/$defs/xz_or_xyz"
          }
        },
        "closed": {
          "type": "boolean"
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    },
    "npc": {
      "type": "object",
      "required": [
        "id",
        "position"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "name": {
          "type": "string",
          "maxLength": 64,
          "description": "Shown on the nameplate; unique within the world."
        },
        "title": {
          "type": "string",
          "maxLength": 64,
          "description": "Second nameplate line, e.g. <Quartermaster>."
        },
        "kind": {
          "enum": [
            "villager",
            "guard",
            "character"
          ],
          "description": "villager/guard: a built-in townsperson (look optional, random if absent). character: a glTF model (model required)."
        },
        "look": {
          "type": "object",
          "description": "Built-in body. hero: a preset (human_male, human_female, elf_female, villager_man, villager_woman, villager_lady, villager_dress, guard, ...) or body: male|female; the rest overrides the preset.",
          "additionalProperties": false,
          "properties": {
            "hero": {
              "type": "string",
              "maxLength": 32
            },
            "body": {
              "enum": [
                "male",
                "female"
              ]
            },
            "height": {
              "type": "number",
              "minimum": 1.2,
              "maximum": 2.4
            },
            "build": {
              "type": "number",
              "minimum": 0.7,
              "maximum": 1.4
            },
            "skin": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{6}$"
            },
            "hair": {
              "type": "string",
              "maxLength": 32
            },
            "hair_color": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{6}$"
            },
            "beard": {
              "type": "string",
              "maxLength": 32
            },
            "seed": {
              "type": "integer",
              "description": "Seeds a random townsperson look when hero/body is absent."
            }
          },
          "patternProperties": {
            "^x-": {}
          }
        },
        "outfit": {
          "type": "object",
          "description": "Gear by slot, as Oakhaven's Gear: head, chest, back, main_hand, off_hand (item ids) and dye, pants_dye, boots_dye, trim_dye (dye ids).",
          "additionalProperties": false,
          "properties": {
            "head": {
              "type": "string"
            },
            "chest": {
              "type": "string"
            },
            "back": {
              "type": "string"
            },
            "main_hand": {
              "type": "string"
            },
            "off_hand": {
              "type": "string"
            },
            "dye": {
              "type": "string"
            },
            "pants_dye": {
              "type": "string"
            },
            "boots_dye": {
              "type": "string"
            },
            "trim_dye": {
              "type": "string"
            }
          },
          "patternProperties": {
            "^x-": {}
          }
        },
        "model": {
          "$ref": "#/$defs/glb",
          "description": "kind character: a skinned glTF."
        },
        "model_forward": {
          "enum": [
            "+Z",
            "-Z"
          ],
          "description": "Which way the model faces in its own file. glTF's convention is +Z (default); the 2009 iamar characters face -Z."
        },
        "scale": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "attach": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/attachment"
          }
        },
        "position": {
          "$ref": "#/$defs/vec3",
          "description": "Feet position; dropped to the ground."
        },
        "yaw": {
          "type": "number",
          "description": "Facing, degrees; 0 = toward -Z."
        },
        "idle": {
          "type": "string",
          "maxLength": 32,
          "description": "Idle animation (built-in: idle, sit, kneel, talk, ...; model: a glTF animation name)."
        },
        "walk": {
          "type": "string",
          "maxLength": 32
        },
        "emotes": {
          "type": "array",
          "items": {
            "type": "string",
            "maxLength": 32
          }
        },
        "wander_radius": {
          "type": "number",
          "minimum": 0,
          "description": "Stroll at random within this radius of position (0 = stay put)."
        },
        "path": {
          "$ref": "#/$defs/id",
          "description": "Walk this path (paths[].id) instead of wandering."
        },
        "speed": {
          "type": "number",
          "exclusiveMinimum": 0,
          "maximum": 8
        },
        "greeting": {
          "type": "array",
          "items": {
            "type": "string",
            "maxLength": 400
          },
          "description": "Lines said when a visitor talks to them (one is picked)."
        },
        "shop": {
          "type": "array",
          "description": "Wares as [slot, item id, price], like Oakhaven QuestDefs.SHOPS.",
          "items": {
            "type": "array",
            "prefixItems": [
              {
                "type": "string"
              },
              {
                "type": "string"
              },
              {
                "type": "integer",
                "minimum": 0
              }
            ],
            "minItems": 3,
            "maxItems": 3
          }
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    },
    "creature": {
      "type": "object",
      "required": [
        "kind",
        "position"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "kind": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9_]{0,31}$",
          "description": "Built-in kind. v0 renders chicken, bunny, cow, horse; others (duck, frog, fish, turtle, slime, dragon, ...) are reserved and skipped by browsers that lack them."
        },
        "position": {
          "$ref": "#/$defs/vec3"
        },
        "count": {
          "type": "integer",
          "minimum": 1,
          "maximum": 64
        },
        "wander_radius": {
          "type": "number",
          "minimum": 0
        },
        "color": {
          "$ref": "#/$defs/color",
          "description": "Coat tint; random per kind if absent."
        },
        "seed": {
          "type": "integer"
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    },
    "objective": {
      "type": "object",
      "required": [
        "kind",
        "text"
      ],
      "additionalProperties": false,
      "description": "One quest objective. world absent = this world.",
      "properties": {
        "kind": {
          "enum": [
            "talk",
            "goto",
            "ride",
            "kill",
            "loot",
            "pickup"
          ]
        },
        "text": {
          "type": "string",
          "maxLength": 200,
          "description": "What the quest tracker shows."
        },
        "world": {
          "$ref": "#/$defs/address",
          "description": "The world the objective is in, e.g. iamar://local/oakhaven. Absent = this world."
        },
        "npc": {
          "type": "string",
          "maxLength": 64,
          "description": "talk: an npc id of this world, or, in another world, what that world calls them (Oakhaven: the NPC's name, e.g. 'Goodwife Alys')."
        },
        "choice": {
          "type": "string",
          "maxLength": 200,
          "description": "talk: what the visitor says."
        },
        "line": {
          "type": "string",
          "maxLength": 1000,
          "description": "talk: what the NPC answers."
        },
        "at": {
          "$ref": "#/$defs/xz_or_xyz",
          "description": "talk: where the NPC stands (map marker; stand-in spot when the NPC is missing)."
        },
        "target": {
          "$ref": "#/$defs/xz_or_xyz",
          "description": "goto/ride: the place to reach ([x, z] or [x, y, z])."
        },
        "trigger": {
          "$ref": "#/$defs/id",
          "description": "goto/ride: reach this trigger volume instead of target."
        },
        "radius": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "kinds": {
          "type": "array",
          "items": {
            "type": "string",
            "maxLength": 32
          },
          "description": "kill/loot: creature or monster kinds that count."
        },
        "count": {
          "type": "integer",
          "minimum": 1
        },
        "item": {
          "type": "string",
          "maxLength": 64,
          "description": "pickup/loot: item name."
        },
        "look": {
          "type": "string",
          "maxLength": 32,
          "description": "pickup/loot: how the item looks (Oakhaven: horseshoe, jelly, lily, book, iron, herb)."
        },
        "spots": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/xz_or_xyz"
          },
          "description": "pickup: where the items lie."
        },
        "areas": {
          "type": "array",
          "description": "kill/loot: [[x, z], radius] where to look (map markers).",
          "items": {
            "type": "array",
            "prefixItems": [
              {
                "$ref": "#/$defs/xz_or_xyz"
              },
              {
                "type": "number"
              }
            ],
            "minItems": 2,
            "maxItems": 2
          }
        },
        "camps": {
          "type": "array",
          "items": {
            "type": "integer",
            "minimum": 0
          },
          "description": "Oakhaven only: indices into Monsters.CAMPS."
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    },
    "quest": {
      "type": "object",
      "required": [
        "id",
        "title",
        "giver",
        "offer",
        "thanks"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "$ref": "#/$defs/id"
        },
        "title": {
          "type": "string",
          "maxLength": 80
        },
        "chain": {
          "type": "string",
          "maxLength": 32
        },
        "giver": {
          "$ref": "#/$defs/id",
          "description": "npc id of this world."
        },
        "turn_in": {
          "type": "string",
          "maxLength": 64,
          "description": "npc id of this world (default: the giver)."
        },
        "turn_in_world": {
          "$ref": "#/$defs/address",
          "description": "When the quest is handed in somewhere else; turn_in is then that world's NPC name."
        },
        "requires": {
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^(iamar://[a-z0-9_/-]+#|[a-z0-9_-]+#)?[a-z0-9][a-z0-9_-]{0,63}$"
          },
          "description": "Quest ids to finish first: 'id' (this world) or '<address>#<id>' (another world)."
        },
        "offer": {
          "type": "string",
          "maxLength": 2000
        },
        "accept": {
          "type": "string",
          "maxLength": 1000,
          "description": "Optional line when the quest is accepted."
        },
        "waiting": {
          "type": "string",
          "maxLength": 1000,
          "description": "Line while the quest is in progress."
        },
        "thanks": {
          "type": "string",
          "maxLength": 2000,
          "description": "Line when it is handed in (done)."
        },
        "steps": {
          "type": "array",
          "description": "Done in order. Each step is one objective, or an array of objectives worked on together.",
          "items": {
            "oneOf": [
              {
                "$ref": "#/$defs/objective"
              },
              {
                "type": "array",
                "minItems": 1,
                "items": {
                  "$ref": "#/$defs/objective"
                }
              }
            ]
          }
        },
        "gold": {
          "type": "integer",
          "minimum": 0
        },
        "xp": {
          "type": "integer",
          "minimum": 0
        },
        "reward": {
          "type": "array",
          "description": "[slot, item id] from Gear, or [] for none.",
          "items": {
            "type": "string"
          },
          "maxItems": 2
        }
      },
      "patternProperties": {
        "^x-": {}
      }
    }
  }
}
