{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://dustwright.dev/schema/rsd-1.json",
  "title": "Dustwright redstone design, format rsd/1",
  "description": "A redstone design: layers of character grids, a legend of block specs, and optional ports, tests, annotations and build steps. Hard limits (sizes, counts) are enforced by the parser with E013 before this schema runs.",
  "type": "object",
  "required": ["format", "meta", "legend", "layers"],
  "additionalProperties": false,
  "properties": {
    "format": {
      "const": "rsd/1",
      "description": "Format id and major version."
    },
    "meta": {
      "$ref": "#/$defs/meta"
    },
    "legend": {
      "type": "object",
      "description": "Maps single printable ASCII characters (not '.' or space) to block specs. At least one entry, unless the design stamps patterns with `include`.",
      "propertyNames": {
        "pattern": "^[!-\\-/-~]$"
      },
      "additionalProperties": {
        "$ref": "#/$defs/blockSpec"
      }
    },
    "layers": {
      "type": "array",
      "description": "Horizontal slices bottom to top; each is a list of rows north to south; each character is one block west to east.",
      "minItems": 1,
      "items": {
        "type": "array",
        "minItems": 1,
        "items": {
          "type": "string",
          "minLength": 1
        }
      }
    },
    "blocks": {
      "type": "array",
      "description": "Explicit per-position overrides applied after layers.",
      "items": {
        "type": "object",
        "required": ["pos", "block"],
        "additionalProperties": false,
        "properties": {
          "pos": {
            "$ref": "#/$defs/pos"
          },
          "block": {
            "$ref": "#/$defs/blockSpec"
          }
        }
      }
    },
    "include": {
      "type": "array",
      "maxItems": 64,
      "description": "Patterns from the built-in library stamped into the design: each lands with its lowest corner at `at`, turned `rotate` degrees clockwise seen from above. Only blocks are stamped; they must fall on air inside the grid.",
      "items": {
        "type": "object",
        "required": ["pattern", "at"],
        "additionalProperties": false,
        "properties": {
          "pattern": {
            "enum": [
              "and-gate",
              "hopper-sorter",
              "not-gate",
              "observer-clock",
              "or-gate",
              "piston-door-2x2",
              "pulse-extender",
              "pulse-limiter",
              "repeater-clock",
              "repeater-line",
              "rs-latch",
              "t-flip-flop",
              "xor-gate"
            ],
            "description": "A pattern from the library (patterns/ in the repository; list_patterns over MCP)."
          },
          "at": {
            "$ref": "#/$defs/pos"
          },
          "rotate": {
            "enum": [0, 90, 180, 270],
            "default": 0,
            "description": "Degrees clockwise seen from above."
          }
        }
      }
    },
    "ports": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "inputs": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/port"
          }
        },
        "outputs": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/port"
          }
        }
      }
    },
    "tests": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/testCase"
      }
    },
    "annotations": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["pos", "text"],
        "additionalProperties": false,
        "properties": {
          "pos": {
            "$ref": "#/$defs/pos"
          },
          "text": {
            "type": "string",
            "minLength": 1
          }
        }
      }
    },
    "steps": {
      "type": "array",
      "items": {
        "oneOf": [
          {
            "type": "object",
            "required": ["title", "layers"],
            "additionalProperties": false,
            "properties": {
              "title": {
                "$ref": "#/$defs/title"
              },
              "layers": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "type": "integer",
                  "minimum": 0
                },
                "uniqueItems": true,
                "maxItems": 384
              }
            }
          },
          {
            "type": "object",
            "required": ["title", "positions"],
            "additionalProperties": false,
            "properties": {
              "title": {
                "$ref": "#/$defs/title"
              },
              "positions": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "$ref": "#/$defs/pos"
                }
              }
            }
          }
        ]
      }
    },
    "ext": {
      "type": "object",
      "description": "Reserved for tool-specific data. Carried through untouched and never interpreted."
    }
  },
  "$defs": {
    "meta": {
      "type": "object",
      "required": ["name"],
      "additionalProperties": false,
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[^\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029\\u202a-\\u202e\\u2066-\\u2069]+$",
          "description": "A label only: printable text without control, line-separator or bidi-override characters. Never used as a path or a command."
        },
        "slug": {
          "type": "string",
          "description": "Runs of [a-z0-9] joined by '-', 1 to 48 characters. Checked by the parser (E012)."
        },
        "description": {
          "type": "string"
        },
        "edition": {
          "enum": ["bedrock", "java"],
          "description": "The edition switch. Defaults to bedrock."
        },
        "gameVersion": {
          "type": "string",
          "pattern": "^[0-9]+\\.[0-9]+(\\.([0-9]+|x))*$",
          "description": "The game version the design was verified against, e.g. 1.26.50."
        }
      }
    },
    "blockSpec": {
      "type": "string",
      "minLength": 1,
      "description": "<type> [key=value ...], abstract names from the registry."
    },
    "pos": {
      "type": "array",
      "minItems": 3,
      "maxItems": 3,
      "items": {
        "type": "integer",
        "minimum": 0
      },
      "description": "[x, y, z]: +X east, +Y up, +Z south, origin at the north-west bottom corner."
    },
    "portName": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{1,32}$"
    },
    "port": {
      "type": "object",
      "required": ["name", "pos"],
      "additionalProperties": false,
      "properties": {
        "name": {
          "$ref": "#/$defs/portName"
        },
        "pos": {
          "$ref": "#/$defs/pos"
        }
      }
    },
    "title": {
      "type": "string",
      "minLength": 1
    },
    "testCase": {
      "type": "object",
      "required": ["set", "afterTicks", "expect"],
      "additionalProperties": false,
      "properties": {
        "set": {
          "type": "object",
          "propertyNames": {
            "$ref": "#/$defs/portName"
          },
          "additionalProperties": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1
          }
        },
        "afterTicks": {
          "type": "integer",
          "minimum": 0,
          "description": "Redstone ticks (0.1 s each) to run after set."
        },
        "expect": {
          "type": "object",
          "minProperties": 1,
          "propertyNames": {
            "$ref": "#/$defs/portName"
          },
          "additionalProperties": {
            "type": "integer",
            "minimum": 0,
            "maximum": 15
          }
        }
      }
    }
  }
}
