Skip to main content
Glama

pixel_inspect

Idempotent

Inspect pixel art, sprites, and animation frames without exporting by generating PNG contact sheets with frame names, durations, zoomed regions, and review views for iteration.

Instructions

Look at pixel art, a sprite or an animation frame by frame to check how it looks, without exporting. Returns a PNG contact sheet of every frame (or the chosen frames, or one animation in playback order) with each cell's name and duration. grid: true adds palette-key text grids for exact pixels, region zooms in, and view picks a tile, onion or motion review. Use this while iterating; call pixel_render to export.

Input Schema

NameRequiredDescriptionDefault
gridNoAlso return palette-key text grids with x/y rulers (at most 16,384 pixels).
stepNoKeep every step-th cell from offset; step 2 shows the A frames of an A/B strobe.
viewNoonion and motion need animation; motion reports foot slide, ground error and loop joins (source or metadata adds contacts); tile repeats each frame 3x3.
scaleNoContact sheet scale. Default: the largest that fits about 1024 px.
speedNoMotion view: pixels per second.
framesNoFrame names to show, in order; * and ? match, as in "flash-*". Default: every frame.
groundNoMotion view: ground row.
guidesNoPerspective guides: {horizon: row, vanish: [[x, y], …up to 3], iso: {tile: [32, 16], at: [x, y]}}.
layersNoShow only these named layers.
nativeNoAlso return a 1x contact sheet.
offsetNo
regionNoCrop every frame to this canvas rectangle.
sourceNoA pixelforge-poses source instead of project/revision, for motion review with its contacts.
projectNoA version-1 Pixelbolt recipe: {"version": 1, "name", "width", "height", "palette", "symbols"?, "frames": [{"name", "duration"?, "from"?, "ops"?, "layers"?}], "animations"?}; see pixel_help.
maxCellsNoShow at most this many evenly spaced cells; step and offset pick positions instead.
metadataNoPose metadata from pixel_compile (metadata: true), for motion review of a compiled revision.
revisionNoRevision id from an earlier response, sent instead of project.
animationNoShow one animation in playback order instead of frames.
referenceNoRevision to compare against: adds a before/after image and the changed pixels.
backgroundNochecker (default), transparent, a palette name or a hex color.
diagnosticsNoAdvisory evidence: duplicate and empty frames, stray pixels, palette, loop joins, timing, cleanup counts.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed24 schema fields changedv0.9.2
    • changedInput schema / properties / background / description
      Previous value: -"Cell background: checker (default), transparent, a palette name or a hex color."New value: +"checker (default), transparent, a palette name or a hex color."
    • changedInput schema / properties / diagnostics / description
      Previous value: -"Advisory duplicate, empty, isolated-pixel, palette and loop-boundary evidence, original animation timing, and per-frame counts of pixels a cleanup change would alter (corners, strays)."New value: +"Advisory evidence: duplicate and empty frames, stray pixels, palette, loop joins, timing, cleanup counts."
    • changedInput schema / properties / frames / description
      Previous value: -"Frame names to show, in this order. * (any characters) and ? (one character) in a name select every matching frame in project order, such as \"flash-*\". Default: every frame."New value: +"Frame names to show, in order; * and ? match, as in \"flash-*\". Default: every frame."
    • changedInput schema / properties / grid / description
      Previous value: -"Also return each frame as palette-key text rows with x/y rulers. At most 16,384 pixels in total."New value: +"Also return palette-key text grids with x/y rulers (at most 16,384 pixels)."
    • changedInput schema / properties / ground / description
      Previous value: -"Motion view: ground pixel row; overrides metadata.rig.ground."New value: +"Motion view: ground row."
    • addedInput schema / properties / guides
      Added value: +{
      +  "description": "Perspective guides: {horizon: row, vanish: [[x, y], …up to 3], iso: {tile: [32, 16], at: [x, y]}}.",
      +  "type": "object"
      +}
    • changedInput schema / properties / layers / description
      Previous value: -"Isolate named layers, preserving inheritance/transforms/visibility. Omits frame ops, background and final canvas corrections, which do not belong to a named layer."New value: +"Show only these named layers."
    • changedInput schema / properties / maxCells / description
      Previous value: -"Bound the preview with evenly spaced samples; returns omitted count and original positions. Does not alter exported animation. Evenly spaced samples can alias with a repeating pattern; use step and offset to choose positions."New value: +"Show at most this many evenly spaced cells; step and offset pick positions instead."
    • changedInput schema / properties / metadata / description
      Previous value: -"Pose metadata returned by pixel_compile with metadata:true. Needed with a compiled project/revision for contacts and joint angles; a source supplies its own."New value: +"Pose metadata from pixel_compile (metadata: true), for motion review of a compiled revision."
    • changedInput schema / properties / native / description
      Previous value: -"Also return a native-size contact sheet beside the enlarged view."New value: +"Also return a 1x contact sheet."
    • removedInput schema / properties / offset / description
      Removed value: -"First position the step keeps, counting from 0. Default 0; offset 1 with step 2 shows the B frames."
    • changedInput schema / properties / project / description
      Previous value: -"A version-1 Pixelbolt recipe: {\"version\": 1, \"name\", \"width\", \"height\", \"palette\", \"symbols\"?, \"frames\": [{\"name\", \"duration\"?, \"from\"?, \"ops\"?, \"layers\"?}], \"animations\"?}. pixel_help explains it; its topic schema is the full JSON Schema. Mistakes come back with their exact path."New value: +"A version-1 Pixelbolt recipe: {\"version\": 1, \"name\", \"width\", \"height\", \"palette\", \"symbols\"?, \"frames\": [{\"name\", \"duration\"?, \"from\"?, \"ops\"?, \"layers\"?}], \"animations\"?}; see pixel_help."
    • changedInput schema / properties / reference / description
      Previous value: -"Saved revision to compare against at the same origin. Adds a before/after image and changed-pixel report."New value: +"Revision to compare against: adds a before/after image and the changed pixels."
    • changedInput schema / properties / region / description
      Previous value: -"Crop every frame to this canvas rectangle. Smaller regions are shown larger."New value: +"Crop every frame to this canvas rectangle."
    • changedInput schema / properties / region / properties / h / maximum
      Previous value: -256New value: +4096
    • changedInput schema / properties / region / properties / w / maximum
      Previous value: -256New value: +4096
    • changedInput schema / properties / region / properties / x / maximum
      Previous value: -255New value: +4095
    • changedInput schema / properties / region / properties / y / maximum
      Previous value: -255New value: +4095
    • changedInput schema / properties / revision / description
      Previous value: -"Revision id from an earlier Pixelbolt response. Send it instead of project to reuse that recipe, including after restarting with the same MCP --out directory."New value: +"Revision id from an earlier response, sent instead of project."
    • changedInput schema / properties / scale / description
      Previous value: -"Contact sheet scale. Default: the largest that fits about 1024px."New value: +"Contact sheet scale. Default: the largest that fits about 1024 px."
    • changedInput schema / properties / source / description
      Previous value: -"A pixelforge-poses source, instead of project/revision; compiles it with contact and joint metadata for motion review."New value: +"A pixelforge-poses source instead of project/revision, for motion review with its contacts."
    • changedInput schema / properties / speed / description
      Previous value: -"Motion view: horizontal speed in pixels per second."New value: +"Motion view: pixels per second."
    • changedInput schema / properties / step / description
      Previous value: -"Keep every step-th cell of the selection (frames or animation positions), starting at offset: step 2 shows only the A frames of an A/B strobe. Applied before maxCells."New value: +"Keep every step-th cell from offset; step 2 shows the A frames of an A/B strobe."
    • changedInput schema / properties / view / description
      Previous value: -"Onion uses pink previous/cyan next poses in playback order. Motion requires animation and reports slide, signed ground error, joint seam and pose spread, with native and enlarged trajectory/timing strips. Pass source or compile metadata to measure contacts. Tile repeats each frame 3x3."New value: +"onion and motion need animation; motion reports foot slide, ground error and loop joins (source or metadata adds contacts); tile repeats each frame 3x3."
  2. Changed12 schema fields changedv0.9.1
    • removedInput schema / $defs
      Removed value: -{
      -  "frame": {
      -    "additionalProperties": false,
      -    "properties": {
      -      "anchor": {
      -        "description": "[x, y] in unscaled canvas pixels; the top-left corner of that pixel.",
      -        "items": {
      -          "maximum": 4096,
      -          "minimum": -4096,
      -          "type": "integer"
      -        },
      -        "maxItems": 2,
      -        "minItems": 2,
      -        "type": "array"
      -      },
      -      "duration": {
      -        "description": "Frame duration in milliseconds; default 100.",
      -        "maximum": 60000,
      -        "minimum": 1,
      -        "type": "integer"
      -      },
      -      "flipX": {
      -        "type": "boolean"
      -      },
      -      "flipY": {
      -        "type": "boolean"
      -      },
      -      "from": {
      -        "description": "Portable name: do not use Windows device names (CON, PRN, AUX, NUL, COM1–9, LPT1–9). Frame/animation names must be unique ignoring case.",
      -        "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$",
      -        "type": "string"
      -      },
      -      "layers": {
      -        "items": {
      -          "$ref": "#/$defs/layer"
      -        },
      -        "maxItems": 64,
      -        "type": "array"
      -      },
      -      "name": {
      -        "description": "Portable name: do not use Windows device names (CON, PRN, AUX, NUL, COM1–9, LPT1–9). Frame/animation names must be unique ignoring case.",
      -        "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$",
      -        "type": "string"
      -      },
      -      "ops": {
      -        "items": {
      -          "$ref": "#/$defs/op"
      -        },
      -        "maxItems": 2048,
      -        "type": "array"
      -      },
      -      "palette": {
      -        "additionalProperties": {
      -          "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -          "type": "string"
      -        },
      -        "description": "Recolor project palette keys for this frame only (palette cycling). Keys must exist in the project palette.",
      -        "maxProperties": 256,
      -        "type": "object"
      -      },
      -      "pixels": {
      -        "description": "Final canvas-coordinate RGBA replacements after ops and layers. Coordinates must be inside this canvas. Later entries win; transparent erases.",
      -        "items": {
      -          "$ref": "#/$defs/pixel"
      -        },
      -        "maxItems": 65536,
      -        "type": "array"
      -      },
      -      "points": {
      -        "additionalProperties": {
      -          "description": "[x, y] in unscaled canvas pixels; the top-left corner of that pixel.",
      -          "items": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "maxItems": 2,
      -          "minItems": 2,
      -          "type": "array"
      -        },
      -        "description": "Named frame positions (hands, hit points, glyph advance), exported in the atlas in exported pixels.",
      -        "maxProperties": 64,
      -        "propertyNames": {
      -          "description": "Portable name: do not use Windows device names (CON, PRN, AUX, NUL, COM1–9, LPT1–9). Frame/animation names must be unique ignoring case.",
      -          "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$",
      -          "type": "string"
      -        },
      -        "type": "object"
      -      },
      -      "translate": {
      -        "items": {
      -          "maximum": 4096,
      -          "minimum": -4096,
      -          "type": "integer"
      -        },
      -        "maxItems": 2,
      -        "minItems": 2,
      -        "type": "array"
      -      },
      -      "wrap": {
      -        "description": "Translation wraps around the canvas instead of clipping (scrolling tiles).",
      -        "type": "boolean"
      -      }
      -    },
      -    "required": [
      -      "name"
      -    ],
      -    "type": "object"
      -  },
      -  "layer": {
      -    "additionalProperties": false,
      -    "properties": {
      -      "name": {
      -        "description": "Portable name: do not use Windows device names (CON, PRN, AUX, NUL, COM1–9, LPT1–9). Frame/animation names must be unique ignoring case.",
      -        "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$",
      -        "type": "string"
      -      },
      -      "opacity": {
      -        "maximum": 1,
      -        "minimum": 0,
      -        "type": "number"
      -      },
      -      "ops": {
      -        "items": {
      -          "$ref": "#/$defs/op"
      -        },
      -        "maxItems": 2048,
      -        "type": "array"
      -      },
      -      "palette": {
      -        "additionalProperties": {
      -          "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -          "type": "string"
      -        },
      -        "description": "Recolor project palette keys for this layer's drawing only, on top of the frame palette: a flash or cycle on one layer leaves other drawing that shares the key alone. Keys must exist in the project palette; values are colours or project palette names.",
      -        "maxProperties": 256,
      -        "type": "object"
      -      },
      -      "visible": {
      -        "type": "boolean"
      -      },
      -      "x": {
      -        "maximum": 4096,
      -        "minimum": -4096,
      -        "type": "integer"
      -      },
      -      "y": {
      -        "maximum": 4096,
      -        "minimum": -4096,
      -        "type": "integer"
      -      }
      -    },
      -    "required": [],
      -    "type": "object"
      -  },
      -  "op": {
      -    "oneOf": [
      -      {
      -        "additionalProperties": false,
      -        "properties": {
      -          "color": {
      -            "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -            "type": "string"
      -          },
      -          "op": {
      -            "const": "pixel"
      -          },
      -          "x": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op",
      -          "color"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "properties": {
      -          "color": {
      -            "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -            "type": "string"
      -          },
      -          "filled": {
      -            "type": "boolean"
      -          },
      -          "h": {
      -            "maximum": 512,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "op": {
      -            "const": "rect"
      -          },
      -          "w": {
      -            "maximum": 512,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "x": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op",
      -          "w",
      -          "h",
      -          "color"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "properties": {
      -          "color": {
      -            "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -            "type": "string"
      -          },
      -          "filled": {
      -            "type": "boolean"
      -          },
      -          "h": {
      -            "maximum": 512,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "op": {
      -            "const": "ellipse"
      -          },
      -          "w": {
      -            "maximum": 512,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "x": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op",
      -          "w",
      -          "h",
      -          "color"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "properties": {
      -          "color": {
      -            "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -            "type": "string"
      -          },
      -          "op": {
      -            "const": "line"
      -          },
      -          "x": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "x2": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y2": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op",
      -          "x2",
      -          "y2",
      -          "color"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "properties": {
      -          "h": {
      -            "maximum": 512,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "op": {
      -            "const": "clear"
      -          },
      -          "w": {
      -            "maximum": 512,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "x": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op",
      -          "w",
      -          "h"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "properties": {
      -          "color": {
      -            "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -            "type": "string"
      -          },
      -          "op": {
      -            "const": "fill"
      -          },
      -          "x": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op",
      -          "color"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "properties": {
      -          "from": {
      -            "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -            "type": "string"
      -          },
      -          "op": {
      -            "const": "replace"
      -          },
      -          "to": {
      -            "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -            "type": "string"
      -          }
      -        },
      -        "required": [
      -          "op",
      -          "from",
      -          "to"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "properties": {
      -          "flipX": {
      -            "type": "boolean"
      -          },
      -          "flipY": {
      -            "type": "boolean"
      -          },
      -          "op": {
      -            "const": "grid"
      -          },
      -          "remap": {
      -            "additionalProperties": {
      -              "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -              "type": "string"
      -            },
      -            "description": "Recolor grid characters for this operation only: character → palette name or colour.",
      -            "propertyNames": {
      -              "maxLength": 1,
      -              "minLength": 1
      -            },
      -            "type": "object"
      -          },
      -          "rotate": {
      -            "enum": [
      -              0,
      -              90,
      -              180,
      -              270
      -            ]
      -          },
      -          "rows": {
      -            "$ref": "#/$defs/rows"
      -          },
      -          "scale": {
      -            "maximum": 16,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "x": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op",
      -          "rows"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "properties": {
      -          "flipX": {
      -            "type": "boolean"
      -          },
      -          "flipY": {
      -            "type": "boolean"
      -          },
      -          "op": {
      -            "const": "stamp"
      -          },
      -          "remap": {
      -            "additionalProperties": {
      -              "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -              "type": "string"
      -            },
      -            "description": "Recolor grid characters for this operation only: character → palette name or colour.",
      -            "propertyNames": {
      -              "maxLength": 1,
      -              "minLength": 1
      -            },
      -            "type": "object"
      -          },
      -          "rotate": {
      -            "enum": [
      -              0,
      -              90,
      -              180,
      -              270
      -            ]
      -          },
      -          "scale": {
      -            "maximum": 16,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "symbol": {
      -            "description": "Portable name: do not use Windows device names (CON, PRN, AUX, NUL, COM1–9, LPT1–9). Frame/animation names must be unique ignoring case.",
      -            "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$",
      -            "type": "string"
      -          },
      -          "x": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op",
      -          "symbol"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "description": "Draw text in the built-in pixel font. y is the top of the capitals; a line is 8 rows: capitals 7 tall, lowercase 5, descenders 1 below, and the accent of a capital reaches 2 rows above y. Glyphs are 1 to 5 pixels wide (© ® — Œ œ are 7), digits always 5, a space 3. One line of n average characters is about 6n pixels wide.",
      -        "properties": {
      -          "align": {
      -            "description": "What x is: the left edge of the text (default), its centre or its right edge. Lines align the same way.",
      -            "enum": [
      -              "left",
      -              "center",
      -              "right"
      -            ]
      -          },
      -          "color": {
      -            "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -            "type": "string"
      -          },
      -          "flipX": {
      -            "type": "boolean"
      -          },
      -          "flipY": {
      -            "type": "boolean"
      -          },
      -          "lineHeight": {
      -            "description": "Pixels from the top of one line to the top of the next; default 10.",
      -            "maximum": 64,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "op": {
      -            "const": "text"
      -          },
      -          "rotate": {
      -            "enum": [
      -              0,
      -              90,
      -              180,
      -              270
      -            ]
      -          },
      -          "scale": {
      -            "maximum": 16,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "spacing": {
      -            "description": "Pixels between glyphs; default 1.",
      -            "maximum": 16,
      -            "minimum": 0,
      -            "type": "integer"
      -          },
      -          "text": {
      -            "description": "Printable ASCII, western European accented letters and common symbols; \\n starts a new line (at most 64).",
      -            "maxLength": 512,
      -            "minLength": 1,
      -            "type": "string"
      -          },
      -          "x": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op",
      -          "text",
      -          "color"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "description": "Copy a rectangle of a symbol (palette characters) or an earlier frame (exact RGBA).",
      -        "oneOf": [
      -          {
      -            "required": [
      -              "from"
      -            ]
      -          },
      -          {
      -            "required": [
      -              "symbol"
      -            ]
      -          }
      -        ],
      -        "properties": {
      -          "flipX": {
      -            "type": "boolean"
      -          },
      -          "flipY": {
      -            "type": "boolean"
      -          },
      -          "from": {
      -            "description": "Portable name: do not use Windows device names (CON, PRN, AUX, NUL, COM1–9, LPT1–9). Frame/animation names must be unique ignoring case.",
      -            "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$",
      -            "type": "string"
      -          },
      -          "h": {
      -            "maximum": 256,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "op": {
      -            "const": "copy"
      -          },
      -          "remap": {
      -            "additionalProperties": {
      -              "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -              "type": "string"
      -            },
      -            "description": "Symbol copies: recolor grid characters (one character each) → palette name or colour. Frame copies: swap exact colours, named by project palette key or hex (#RGB to #RRGGBBAA), for a palette name or colour.",
      -            "maxProperties": 256,
      -            "propertyNames": {
      -              "maxLength": 64,
      -              "minLength": 1
      -            },
      -            "type": "object"
      -          },
      -          "rotate": {
      -            "enum": [
      -              0,
      -              90,
      -              180,
      -              270
      -            ]
      -          },
      -          "scale": {
      -            "maximum": 16,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "sx": {
      -            "maximum": 255,
      -            "minimum": 0,
      -            "type": "integer"
      -          },
      -          "sy": {
      -            "maximum": 255,
      -            "minimum": 0,
      -            "type": "integer"
      -          },
      -          "symbol": {
      -            "description": "Portable name: do not use Windows device names (CON, PRN, AUX, NUL, COM1–9, LPT1–9). Frame/animation names must be unique ignoring case.",
      -            "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$",
      -            "type": "string"
      -          },
      -          "w": {
      -            "maximum": 256,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "x": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "description": "Draw one autotile tile from a template symbol two tiles wide and three tall: the four quarters the mask selects (see the autotile template format). The autotile compiler emits one per frame, so editing the template redraws every tile.",
      -        "properties": {
      -          "mask": {
      -            "description": "Neighbour mask. Blob: N=1, NE=2, E=4, SE=8, S=16, SW=32, W=64, NW=128, reduced so a diagonal counts only with both of its sides. Cardinal: N=1, E=2, S=4, W=8.",
      -            "maximum": 255,
      -            "minimum": 0,
      -            "type": "integer"
      -          },
      -          "mode": {
      -            "description": "blob (default) or cardinal.",
      -            "enum": [
      -              "blob",
      -              "cardinal"
      -            ]
      -          },
      -          "op": {
      -            "const": "autotile"
      -          },
      -          "remap": {
      -            "additionalProperties": {
      -              "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -              "type": "string"
      -            },
      -            "description": "Recolor grid characters for this operation only: character → palette name or colour.",
      -            "propertyNames": {
      -              "maxLength": 1,
      -              "minLength": 1
      -            },
      -            "type": "object"
      -          },
      -          "symbol": {
      -            "description": "Portable name: do not use Windows device names (CON, PRN, AUX, NUL, COM1–9, LPT1–9). Frame/animation names must be unique ignoring case.",
      -            "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$",
      -            "type": "string"
      -          },
      -          "x": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op",
      -          "symbol",
      -          "mask"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "description": "Outline every visible pixel in this buffer (frame or layer): outside rings, inside edge rings or both, in chosen directions.",
      -        "properties": {
      -          "color": {
      -            "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -            "type": "string"
      -          },
      -          "diagonal": {
      -            "type": "boolean"
      -          },
      -          "directions": {
      -            "description": "3×3 grid centred on a visible pixel, with a dot in the centre: x marks the sides that get the line, e.g. [\"...\", \"...\", \"..x\"] for a drop shadow or [\".x.\", \"...\", \"...\"] for a top rim. Replaces diagonal.",
      -            "items": {
      -              "pattern": "^[x.]{3}$",
      -              "type": "string"
      -            },
      -            "maxItems": 3,
      -            "minItems": 3,
      -            "type": "array"
      -          },
      -          "op": {
      -            "const": "outline"
      -          },
      -          "position": {
      -            "description": "outside (default) surrounds the visible pixels; inside recolors their edge pixels; middle splits the width, the odd pixel outside.",
      -            "enum": [
      -              "outside",
      -              "inside",
      -              "middle"
      -            ]
      -          },
      -          "width": {
      -            "description": "Rings of pixels; default 1.",
      -            "maximum": 8,
      -            "minimum": 1,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op",
      -          "color"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "anyOf": [
      -          {
      -            "required": [
      -              "color"
      -            ]
      -          },
      -          {
      -            "required": [
      -              "erase"
      -            ]
      -          }
      -        ],
      -        "description": "Ordered dither between the current pixels and color: draws (or erases) where a canvas-anchored threshold pattern is below the density (default 0.5). x/y default to 0 and w/h reach the canvas edge.",
      -        "properties": {
      -          "color": {
      -            "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -            "type": "string"
      -          },
      -          "density": {
      -            "description": "Share of pixels changed, 0–1, or [from, to] ramping along direction.",
      -            "oneOf": [
      -              {
      -                "maximum": 1,
      -                "minimum": 0,
      -                "type": "number"
      -              },
      -              {
      -                "items": {
      -                  "maximum": 1,
      -                  "minimum": 0,
      -                  "type": "number"
      -                },
      -                "maxItems": 2,
      -                "minItems": 2,
      -                "type": "array"
      -              }
      -            ]
      -          },
      -          "direction": {
      -            "description": "Ramp direction for a [from, to] density; radial runs from the centre to the edge.",
      -            "enum": [
      -              "down",
      -              "up",
      -              "right",
      -              "left",
      -              "radial"
      -            ]
      -          },
      -          "erase": {
      -            "description": "Clear the pattern pixels instead of drawing a color.",
      -            "type": "boolean"
      -          },
      -          "h": {
      -            "maximum": 512,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "offset": {
      -            "description": "Shift the pattern, for example to crawl it between frames.",
      -            "items": {
      -              "maximum": 4096,
      -              "minimum": -4096,
      -              "type": "integer"
      -            },
      -            "maxItems": 2,
      -            "minItems": 2,
      -            "type": "array"
      -          },
      -          "op": {
      -            "const": "dither"
      -          },
      -          "over": {
      -            "description": "Only change pixels whose exact current color is listed (transparent included).",
      -            "oneOf": [
      -              {
      -                "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -                "type": "string"
      -              },
      -              {
      -                "items": {
      -                  "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -                  "type": "string"
      -                },
      -                "maxItems": 64,
      -                "minItems": 1,
      -                "type": "array"
      -              }
      -            ]
      -          },
      -          "pattern": {
      -            "description": "bayer4 by default. noise is seeded white noise with no repeating grid; value is seeded smooth noise that forms clumps about scale pixels across. Patterns follow canvas coordinates, so neighbouring areas line up.",
      -            "oneOf": [
      -              {
      -                "enum": [
      -                  "bayer2",
      -                  "bayer4",
      -                  "bayer8",
      -                  "noise",
      -                  "value"
      -                ]
      -              },
      -              {
      -                "description": "Rows of ranks from 0 to cells − 1; low ranks turn on first, and a highest rank n gives n + 1 levels, so [[0, 1], [1, 0]] is a checkerboard at 0.5.",
      -                "items": {
      -                  "items": {
      -                    "maximum": 255,
      -                    "minimum": 0,
      -                    "type": "integer"
      -                  },
      -                  "maxItems": 16,
      -                  "minItems": 1,
      -                  "type": "array"
      -                },
      -                "maxItems": 16,
      -                "minItems": 1,
      -                "type": "array"
      -              }
      -            ]
      -          },
      -          "scale": {
      -            "description": "Clump size of the value pattern in pixels; default 4.",
      -            "maximum": 64,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "seed": {
      -            "description": "Orders the noise or value pattern (default 0): the same seed keeps every pixel's threshold, so a rising density grows in a fixed order; another seed reshuffles it.",
      -            "maximum": 2147483647,
      -            "minimum": 0,
      -            "type": "integer"
      -          },
      -          "w": {
      -            "maximum": 512,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "x": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "description": "Move every pixel of the region along its colour's ramp (see ramps), so shadows and light keep the palette on any surface. Colours on no ramp stay. density (default 1) and pattern give a dithered edge. x/y default to 0 and w/h reach the canvas edge.",
      -        "properties": {
      -          "density": {
      -            "description": "Share of pixels changed, 0–1, or [from, to] ramping along direction.",
      -            "oneOf": [
      -              {
      -                "maximum": 1,
      -                "minimum": 0,
      -                "type": "number"
      -              },
      -              {
      -                "items": {
      -                  "maximum": 1,
      -                  "minimum": 0,
      -                  "type": "number"
      -                },
      -                "maxItems": 2,
      -                "minItems": 2,
      -                "type": "array"
      -              }
      -            ]
      -          },
      -          "direction": {
      -            "description": "Ramp direction for a [from, to] density; radial runs from the centre to the edge.",
      -            "enum": [
      -              "down",
      -              "up",
      -              "right",
      -              "left",
      -              "radial"
      -            ]
      -          },
      -          "h": {
      -            "maximum": 512,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "offset": {
      -            "description": "Shift the pattern, for example to crawl it between frames.",
      -            "items": {
      -              "maximum": 4096,
      -              "minimum": -4096,
      -              "type": "integer"
      -            },
      -            "maxItems": 2,
      -            "minItems": 2,
      -            "type": "array"
      -          },
      -          "op": {
      -            "const": "shade"
      -          },
      -          "over": {
      -            "description": "Only change pixels whose exact current color is listed (transparent included).",
      -            "oneOf": [
      -              {
      -                "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -                "type": "string"
      -              },
      -              {
      -                "items": {
      -                  "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -                  "type": "string"
      -                },
      -                "maxItems": 64,
      -                "minItems": 1,
      -                "type": "array"
      -              }
      -            ]
      -          },
      -          "pattern": {
      -            "description": "bayer4 by default. noise is seeded white noise with no repeating grid; value is seeded smooth noise that forms clumps about scale pixels across. Patterns follow canvas coordinates, so neighbouring areas line up.",
      -            "oneOf": [
      -              {
      -                "enum": [
      -                  "bayer2",
      -                  "bayer4",
      -                  "bayer8",
      -                  "noise",
      -                  "value"
      -                ]
      -              },
      -              {
      -                "description": "Rows of ranks from 0 to cells − 1; low ranks turn on first, and a highest rank n gives n + 1 levels, so [[0, 1], [1, 0]] is a checkerboard at 0.5.",
      -                "items": {
      -                  "items": {
      -                    "maximum": 255,
      -                    "minimum": 0,
      -                    "type": "integer"
      -                  },
      -                  "maxItems": 16,
      -                  "minItems": 1,
      -                  "type": "array"
      -                },
      -                "maxItems": 16,
      -                "minItems": 1,
      -                "type": "array"
      -              }
      -            ]
      -          },
      -          "scale": {
      -            "description": "Clump size of the value pattern in pixels; default 4.",
      -            "maximum": 64,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "seed": {
      -            "description": "Orders the noise or value pattern (default 0): the same seed keeps every pixel's threshold, so a rising density grows in a fixed order; another seed reshuffles it.",
      -            "maximum": 2147483647,
      -            "minimum": 0,
      -            "type": "integer"
      -          },
      -          "shape": {
      -            "description": "rect (default) or the ellipse inside the region.",
      -            "enum": [
      -              "rect",
      -              "ellipse"
      -            ]
      -          },
      -          "steps": {
      -            "description": "Steps along each colour's ramp: negative darkens (default -1), positive lightens. Clamped at the ramp's ends; not 0.",
      -            "maximum": 8,
      -            "minimum": -8,
      -            "type": "integer"
      -          },
      -          "w": {
      -            "maximum": 512,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "x": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "description": "Draw a character map from this recipe's symbols and autotile templates, with variants, weights and neighbour rules, so later operations see the composed result (dither over its floor, rewrite rules at its corners).",
      -        "properties": {
      -          "classes": {
      -            "additionalProperties": {
      -              "maxLength": 64,
      -              "minLength": 1,
      -              "type": "string"
      -            },
      -            "description": "Characters that stand for several map characters in rule patterns, e.g. {\"#\": \"WV\"}; include the empty character to match empty cells too.",
      -            "propertyNames": {
      -              "maxLength": 1,
      -              "minLength": 1
      -            },
      -            "type": "object"
      -          },
      -          "empty": {
      -            "description": "A character, not a legend key, that stands for empty cells in rule patterns.",
      -            "maxLength": 1,
      -            "minLength": 1,
      -            "type": "string"
      -          },
      -          "legend": {
      -            "additionalProperties": {
      -              "oneOf": [
      -                {
      -                  "description": "A symbol drawn at the cell's top-left; with autotile, {mask} in its name becomes the neighbour mask.",
      -                  "type": "string"
      -                },
      -                {
      -                  "additionalProperties": false,
      -                  "properties": {
      -                    "autotile": {
      -                      "description": "Pick by neighbour mask: 47-value blob or 16-value cardinal.",
      -                      "enum": [
      -                        "blob",
      -                        "cardinal"
      -                      ]
      -                    },
      -                    "match": {
      -                      "description": "Characters counted as the same terrain for the mask; default: this character.",
      -                      "minLength": 1,
      -                      "type": "string"
      -                    },
      -                    "symbol": {
      -                      "description": "A symbol drawn at the cell's top-left; with autotile, {mask} in its name becomes the neighbour mask.",
      -                      "type": "string"
      -                    },
      -                    "symbols": {
      -                      "description": "Variants of symbol, picked by cell position (seeded when the tilemap has a seed).",
      -                      "items": {
      -                        "minLength": 1,
      -                        "type": "string"
      -                      },
      -                      "maxItems": 64,
      -                      "minItems": 1,
      -                      "type": "array"
      -                    },
      -                    "template": {
      -                      "description": "An autotile template symbol (two tiles wide, three tall) drawn with the cell's neighbour mask; needs autotile.",
      -                      "type": "string"
      -                    },
      -                    "templates": {
      -                      "description": "Variants of template, picked by cell position (seeded when the tilemap has a seed).",
      -                      "items": {
      -                        "minLength": 1,
      -                        "type": "string"
      -                      },
      -                      "maxItems": 64,
      -                      "minItems": 1,
      -                      "type": "array"
      -                    },
      -                    "weights": {
      -                      "description": "One whole-number weight per variant name; heavier names are picked more often.",
      -                      "items": {
      -                        "maximum": 1000,
      -                        "minimum": 1,
      -                        "type": "integer"
      -                      },
      -                      "maxItems": 64,
      -                      "minItems": 1,
      -                      "type": "array"
      -                    }
      -                  },
      -                  "required": [],
      -                  "type": "object"
      -                },
      -                {
      -                  "additionalProperties": false,
      -                  "properties": {
      -                    "rules": {
      -                      "description": "Every rule that matches draws, in order, until one with stop draws.",
      -                      "items": {
      -                        "additionalProperties": false,
      -                        "properties": {
      -                          "autotile": {
      -                            "description": "Pick by neighbour mask: 47-value blob or 16-value cardinal.",
      -                            "enum": [
      -                              "blob",
      -                              "cardinal"
      -                            ]
      -                          },
      -                          "chance": {
      -                            "description": "Seeded probability that a matching rule draws.",
      -                            "maximum": 1,
      -                            "minimum": 0,
      -                            "type": "number"
      -                          },
      -                          "match": {
      -                            "description": "Characters counted as the same terrain for the mask; default: this character.",
      -                            "minLength": 1,
      -                            "type": "string"
      -                          },
      -                          "offset": {
      -                            "description": "[columns, rows] to draw into another cell; it draws over that cell's own tiles.",
      -                            "items": {
      -                              "maximum": 8,
      -                              "minimum": -8,
      -                              "type": "integer"
      -                            },
      -                            "maxItems": 2,
      -                            "minItems": 2,
      -                            "type": "array"
      -                          },
      -                          "stop": {
      -                            "description": "When this rule draws, skip the rest of the cell's rules.",
      -                            "type": "boolean"
      -                          },
      -                          "symbol": {
      -                            "description": "A symbol drawn at the cell's top-left; with autotile, {mask} in its name becomes the neighbour mask.",
      -                            "type": "string"
      -                          },
      -                          "symbols": {
      -                            "description": "Variants of symbol, picked by cell position (seeded when the tilemap has a seed).",
      -                            "items": {
      -                              "minLength": 1,
      -                              "type": "string"
      -                            },
      -                            "maxItems": 64,
      -                            "minItems": 1,
      -                            "type": "array"
      -                          },
      -                          "template": {
      -                            "description": "An autotile template symbol (two tiles wide, three tall) drawn with the cell's neighbour mask; needs autotile.",
      -                            "type": "string"
      -                          },
      -                          "templates": {
      -                            "description": "Variants of template, picked by cell position (seeded when the tilemap has a seed).",
      -                            "items": {
      -                              "minLength": 1,
      -                              "type": "string"
      -                            },
      -                            "maxItems": 64,
      -                            "minItems": 1,
      -                            "type": "array"
      -                          },
      -                          "weights": {
      -                            "description": "One whole-number weight per variant name; heavier names are picked more often.",
      -                            "items": {
      -                              "maximum": 1000,
      -                              "minimum": 1,
      -                              "type": "integer"
      -                            },
      -                            "maxItems": 64,
      -                            "minItems": 1,
      -                            "type": "array"
      -                          },
      -                          "where": {
      -                            "description": "3×3 characters centred on the cell. A dot matches anything, the tilemap's empty character matches empty cells (and cells beyond the map), a class matches its members, any other character matches itself.",
      -                            "items": {
      -                              "maxLength": 3,
      -                              "minLength": 3,
      -                              "type": "string"
      -                            },
      -                            "maxItems": 3,
      -                            "minItems": 3,
      -                            "type": "array"
      -                          }
      -                        },
      -                        "required": [],
      -                        "type": "object"
      -                      },
      -                      "maxItems": 32,
      -                      "minItems": 1,
      -                      "type": "array"
      -                    }
      -                  },
      -                  "required": [
      -                    "rules"
      -                  ],
      -                  "type": "object"
      -                },
      -                {
      -                  "description": "Context cell: never drawn, but it counts for other entries' match and for rules.",
      -                  "type": "null"
      -                }
      -              ]
      -            },
      -            "description": "Map characters to what they draw. Dot and space are empty cells.",
      -            "propertyNames": {
      -              "maxLength": 1,
      -              "minLength": 1
      -            },
      -            "type": "object"
      -          },
      -          "op": {
      -            "const": "tilemap"
      -          },
      -          "outside": {
      -            "description": "Cells beyond the map: empty (default), or matching every mask and rule pattern.",
      -            "enum": [
      -              "empty",
      -              "match"
      -            ]
      -          },
      -          "rows": {
      -            "items": {
      -              "maxLength": 256,
      -              "minLength": 1,
      -              "type": "string"
      -            },
      -            "maxItems": 256,
      -            "minItems": 1,
      -            "type": "array"
      -          },
      -          "seed": {
      -            "description": "Reorders seeded variant picks and rule chances; default 0 keeps position-based picks.",
      -            "maximum": 2147483647,
      -            "minimum": 0,
      -            "type": "integer"
      -          },
      -          "tile": {
      -            "description": "Cell size [width, height] in pixels.",
      -            "items": {
      -              "maximum": 256,
      -              "minimum": 1,
      -              "type": "integer"
      -            },
      -            "maxItems": 2,
      -            "minItems": 2,
      -            "type": "array"
      -          },
      -          "x": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op",
      -          "tile",
      -          "rows",
      -          "legend"
      -        ],
      -        "type": "object"
      -      },
      -      {
      -        "additionalProperties": false,
      -        "description": "Markov-style rewrite rules on this buffer: each small match grid found on exact colors is replaced, in a seeded order, without overlapping rewrites. The region defaults to the whole canvas.",
      -        "properties": {
      -          "chance": {
      -            "description": "Seeded probability that each match is applied.",
      -            "maximum": 1,
      -            "minimum": 0,
      -            "type": "number"
      -          },
      -          "empty": {
      -            "description": "A non-palette character meaning a transparent pixel in match and erasure in replace.",
      -            "maxLength": 1,
      -            "minLength": 1,
      -            "type": "string"
      -          },
      -          "h": {
      -            "maximum": 512,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "limit": {
      -            "description": "Most replacements per rule per step.",
      -            "maximum": 65536,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "mirror": {
      -            "description": "Also match the rules mirrored horizontally.",
      -            "type": "boolean"
      -          },
      -          "op": {
      -            "const": "rewrite"
      -          },
      -          "rotate": {
      -            "description": "Also match the rules turned 90°, 180° and 270°.",
      -            "type": "boolean"
      -          },
      -          "rules": {
      -            "items": {
      -              "additionalProperties": false,
      -              "properties": {
      -                "match": {
      -                  "description": "Equal-width rows of palette keys; dot or space matches anything (match) or keeps the pixel (replace); the empty character stands for transparent.",
      -                  "items": {
      -                    "maxLength": 8,
      -                    "minLength": 1,
      -                    "type": "string"
      -                  },
      -                  "maxItems": 8,
      -                  "minItems": 1,
      -                  "type": "array"
      -                },
      -                "replace": {
      -                  "description": "Equal-width rows of palette keys; dot or space matches anything (match) or keeps the pixel (replace); the empty character stands for transparent.",
      -                  "items": {
      -                    "maxLength": 8,
      -                    "minLength": 1,
      -                    "type": "string"
      -                  },
      -                  "maxItems": 8,
      -                  "minItems": 1,
      -                  "type": "array"
      -                }
      -              },
      -              "required": [
      -                "match",
      -                "replace"
      -              ],
      -              "type": "object"
      -            },
      -            "maxItems": 16,
      -            "minItems": 1,
      -            "type": "array"
      -          },
      -          "seed": {
      -            "maximum": 2147483647,
      -            "minimum": 0,
      -            "type": "integer"
      -          },
      -          "steps": {
      -            "description": "Passes over every rule; stops early when a pass changes nothing.",
      -            "maximum": 64,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "w": {
      -            "maximum": 512,
      -            "minimum": 1,
      -            "type": "integer"
      -          },
      -          "x": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          },
      -          "y": {
      -            "maximum": 4096,
      -            "minimum": -4096,
      -            "type": "integer"
      -          }
      -        },
      -        "required": [
      -          "op",
      -          "rules"
      -        ],
      -        "type": "object"
      -      }
      -    ]
      -  },
      -  "pixel": {
      -    "additionalProperties": false,
      -    "properties": {
      -      "color": {
      -        "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -        "type": "string"
      -      },
      -      "x": {
      -        "maximum": 255,
      -        "minimum": 0,
      -        "type": "integer"
      -      },
      -      "y": {
      -        "maximum": 255,
      -        "minimum": 0,
      -        "type": "integer"
      -      }
      -    },
      -    "required": [
      -      "x",
      -      "y",
      -      "color"
      -    ],
      -    "type": "object"
      -  },
      -  "rows": {
      -    "description": "Equal-width text rows. Every character is a palette key; dot and space skip a pixel.",
      -    "items": {
      -      "maxLength": 256,
      -      "minLength": 1,
      -      "type": "string"
      -    },
      -    "maxItems": 256,
      -    "minItems": 1,
      -    "type": "array"
      -  }
      -}
    • addedInput schema / properties / ground
      Added value: +{
      +  "description": "Motion view: ground pixel row; overrides metadata.rig.ground.",
      +  "maximum": 255,
      +  "minimum": 0,
      +  "type": "integer"
      +}
    • addedInput schema / properties / metadata
      Added value: +{
      +  "description": "Pose metadata returned by pixel_compile with metadata:true. Needed with a compiled project/revision for contacts and joint angles; a source supplies its own.",
      +  "type": "object"
      +}
    • removedInput schema / properties / project / additionalProperties
      Removed value: -false
    • addedInput schema / properties / project / description
      Added value: +"A version-1 Pixelbolt recipe: {\"version\": 1, \"name\", \"width\", \"height\", \"palette\", \"symbols\"?, \"frames\": [{\"name\", \"duration\"?, \"from\"?, \"ops\"?, \"layers\"?}], \"animations\"?}. pixel_help explains it; its topic schema is the full JSON Schema. Mistakes come back with their exact path."
    • removedInput schema / properties / project / properties
      Removed value: -{
      -  "$schema": {
      -    "type": "string"
      -  },
      -  "anchor": {
      -    "description": "Default pivot for every frame, exported to the atlas as anchor (exported pixels) and pivot (anchor / source size).",
      -    "items": {
      -      "maximum": 4096,
      -      "minimum": -4096,
      -      "type": "integer"
      -    },
      -    "maxItems": 2,
      -    "minItems": 2,
      -    "type": "array"
      -  },
      -  "animations": {
      -    "additionalProperties": {
      -      "additionalProperties": false,
      -      "properties": {
      -        "direction": {
      -          "enum": [
      -            "forward",
      -            "reverse",
      -            "pingpong"
      -          ]
      -        },
      -        "frames": {
      -          "items": {
      -            "description": "Portable name: do not use Windows device names (CON, PRN, AUX, NUL, COM1–9, LPT1–9). Frame/animation names must be unique ignoring case.",
      -            "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$",
      -            "type": "string"
      -          },
      -          "maxItems": 1024,
      -          "minItems": 1,
      -          "type": "array"
      -        },
      -        "loop": {
      -          "type": "boolean"
      -        }
      -      },
      -      "required": [
      -        "frames"
      -      ],
      -      "type": "object"
      -    },
      -    "description": "ZIP32 bundle budget: animation count + frame count + 7 support files must not exceed 65,535.",
      -    "maxProperties": 65527,
      -    "minProperties": 1,
      -    "propertyNames": {
      -      "description": "Portable name: do not use Windows device names (CON, PRN, AUX, NUL, COM1–9, LPT1–9). Frame/animation names must be unique ignoring case.",
      -      "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$",
      -      "type": "string"
      -    },
      -    "type": "object"
      -  },
      -  "background": {
      -    "description": "Palette name, transparent, or #RGB/#RGBA/#RRGGBB/#RRGGBBAA.",
      -    "type": "string"
      -  },
      -  "frames": {
      -    "items": {
      -      "$ref": "#/$defs/frame"
      -    },
      -    "maxItems": 256,
      -    "minItems": 1,
      -    "type": "array"
      -  },
      -  "height": {
      -    "maximum": 256,
      -    "minimum": 1,
      -    "type": "integer"
      -  },
      -  "name": {
      -    "description": "Portable name: do not use Windows device names (CON, PRN, AUX, NUL, COM1–9, LPT1–9). Frame/animation names must be unique ignoring case.",
      -    "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$",
      -    "type": "string"
      -  },
      -  "palette": {
      -    "additionalProperties": {
      -      "pattern": "^(transparent|#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8}))$",
      -      "type": "string"
      -    },
      -    "maxProperties": 257,
      -    "properties": {
      -      "$ref": {
      -        "description": "Shared palette file, resolved by the CLI and MCP relative to this recipe. Local entries add to or override it.",
      -        "type": "string"
      -      }
      -    },
      -    "type": "object"
      -  },
      -  "ramps": {
      -    "description": "Palette keys from dark to light, one list per material. shade and scene ramp lighting move a colour along its ramp instead of inventing one. A key belongs to one ramp. A shared palette file ({\"palette\": {...}, \"ramps\": [...]}) can supply them.",
      -    "items": {
      -      "items": {
      -        "maxLength": 64,
      -        "minLength": 1,
      -        "type": "string"
      -      },
      -      "maxItems": 32,
      -      "minItems": 2,
      -      "type": "array"
      -    },
      -    "maxItems": 64,
      -    "type": "array"
      -  },
      -  "sheet": {
      -    "additionalProperties": false,
      -    "properties": {
      -      "columns": {
      -        "maximum": 256,
      -        "minimum": 1,
      -        "type": "integer"
      -      },
      -      "padding": {
      -        "maximum": 16,
      -        "minimum": 0,
      -        "type": "integer"
      -      },
      -      "scale": {
      -        "maximum": 16,
      -        "minimum": 1,
      -        "type": "integer"
      -      },
      -      "trim": {
      -        "description": "Pack each frame by its visible bounds; the atlas reports trimmed rectangles with spriteSourceSize. Ignores columns.",
      -        "type": "boolean"
      -      }
      -    },
      -    "required": [],
      -    "type": "object"
      -  },
      -  "symbols": {
      -    "additionalProperties": {
      -      "$ref": "#/$defs/rows"
      -    },
      -    "propertyNames": {
      -      "description": "Portable name: do not use Windows device names (CON, PRN, AUX, NUL, COM1–9, LPT1–9). Frame/animation names must be unique ignoring case.",
      -      "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$",
      -      "type": "string"
      -    },
      -    "type": "object"
      -  },
      -  "version": {
      -    "const": 1
      -  },
      -  "width": {
      -    "maximum": 256,
      -    "minimum": 1,
      -    "type": "integer"
      -  }
      -}
    • removedInput schema / properties / project / required
      Removed value: -[
      -  "version",
      -  "name",
      -  "width",
      -  "height",
      -  "frames"
      -]
    • removedInput schema / properties / project / title
      Removed value: -"Pixelbolt project"
    • addedInput schema / properties / source
      Added value: +{
      +  "description": "A pixelforge-poses source, instead of project/revision; compiles it with contact and joint metadata for motion review.",
      +  "type": "object"
      +}
    • addedInput schema / properties / speed
      Added value: +{
      +  "description": "Motion view: horizontal speed in pixels per second.",
      +  "maximum": 4096,
      +  "minimum": -4096,
      +  "type": "number"
      +}
    • changedInput schema / properties / view / description
      Previous value: -"Onion uses pink previous/cyan next poses in animation playback order (requires animation). Tile repeats each frame 3x3 and reports doubled edges and wrap steps."New value: +"Onion uses pink previous/cyan next poses in playback order. Motion requires animation and reports slide, signed ground error, joint seam and pose spread, with native and enlarged trajectory/timing strips. Pass source or compile metadata to measure contacts. Tile repeats each frame 3x3."
    • changedInput schema / properties / view / enum
      Previous value: -[
      -  "color",
      -  "silhouette",
      -  "grayscale",
      -  "onion",
      -  "tile"
      -]New value: +[
      +  "color",
      +  "silhouette",
      +  "grayscale",
      +  "onion",
      +  "tile",
      +  "motion"
      +]
  3. First observedv0.9.0

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses key behavior beyond annotations: it returns a PNG contact sheet, includes frame names and durations, supports grid and region outputs, and changes behavior by view. It does not reconcile the non-read-only annotation for an inspection tool, but it also does not explicitly claim read-only behavior, so there is no direct contradiction.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three tight sentences with the core action and output front-loaded. Each sentence contributes useful context without waste.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex 21-parameter tool with no output schema, the description covers the main output format, frame/animation selection, grid and region behavior, and the iteration versus export distinction. It omits details on several optional parameters, but the high schema coverage compensates for most of that.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 95%, so the schema already documents nearly every parameter in detail. The description highlights a few parameters such as grid, region, and view, but largely restates schema-provided semantics rather than adding substantial new meaning.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Look at pixel art, a sprite or an animation frame by frame to check how it looks.' It also distinguishes the inspection use from export by naming pixel_render as the export path.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a clear usage context ('Use this while iterating') and an explicit alternative for exporting ('call pixel_render to export'). It does not spell out when-not-to-use cases beyond that export distinction, so it is strong but not exhaustive.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.