Skip to main content
Glama

pixel_render

Destructive

Export pixel-art recipes into game-ready sprite sheets, atlases, Godot SpriteFrames, GIFs, APNGs, and video frame sequences, then verify deliverables against expected frame, color, and transparency specs.

Instructions

Export pixel art as game-ready files: sprite sheet, sprites, animations, GIFs or frames for a video. Writes a PNG sheet, atlas JSON, Godot 4 SpriteFrames, frame PNGs, APNGs, CSS and a Canvas player to a new folder in the export directory, and returns the key files, a check of what was delivered and a contact sheet. Pass expect with what the user asked for, then fix every problem the check lists before handing the files on. Pass saveAs to also write files where the user asked.

Input Schema

NameRequiredDescriptionDefault
gifNoAlso write each animation as a GIF in gifs/ ({} for defaults): exact colours, at most 256 per frame.
shareNoAlso return a playground link that reopens this recipe in a browser.
expectNoWhat the user asked for: frame size, frames per animation, at most colors, a transparent background, loop, GIF scale. The reply's check lists each miss and the call that fixes it.
saveAsNoAlso write files where the user asked, relative to the server root: {"sheet"|"atlas"|"godot"|"recipe"|"gif"|"apng" or a listed file such as "gifs/walk.gif": "assets/hero.png"}. A GIF here implies gif: {}.
projectNoA version-1 Pixelbolt recipe: {"version": 1, "name", "width", "height", "palette", "symbols"?, "frames": [{"name", "duration"?, "from"?, "ops"?, "layers"?}], "animations"?}; see pixel_help.
revisionNoRevision id from an earlier response, sent instead of project.
sequenceNoAlso write numbered PNG frames with alpha for a video editor into sequence/, with the ffmpeg command for a .mov.
animationNoPreview this animation in playback order; every animation is exported either way.
listFilesNoList every exported file, not only the key ones.
overwriteNoLet saveAs replace existing files. Default: refuse.
embedRecipeNoEmbed the recipe in the exported PNGs so they reopen in the playground.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed21 schema fields changedv0.9.2
    • changedInput schema / properties / animation / description
      Previous value: -"Preview this animation in playback order. Default: preview every frame in project order. All animations are always exported."New value: +"Preview this animation in playback order; every animation is exported either way."
    • changedInput schema / properties / expect / description
      Previous value: -"What the user asked for: pixel size of each frame, frames, colours, background, loop, GIF scale. The reply's check lists every file that misses it and the call that fixes it."New value: +"What the user asked for: frame size, frames per animation, at most colors, a transparent background, loop, GIF scale. The reply's check lists each miss and the call that fixes it."
    • removedInput schema / properties / expect / properties / colors / description
      Removed value: -"At most."
    • removedInput schema / properties / expect / properties / frames / description
      Removed value: -"Per animation."
    • removedInput schema / properties / expect / properties / transparent / description
      Removed value: -"Background."
    • changedInput schema / properties / gif / description
      Previous value: -"Also write every animation as a GIF in gifs/ for sharing ({} for defaults). Exact colours, at most 256 per frame, never quantized; notes report alpha and timing changes."New value: +"Also write each animation as a GIF in gifs/ ({} for defaults): exact colours, at most 256 per frame."
    • changedInput schema / properties / gif / properties / background / description
      Previous value: -"Palette name or opaque hex colour to blend onto. Default: 1-bit transparency."New value: +"Palette name or opaque hex to blend onto. Default: 1-bit transparency."
    • changedInput schema / properties / gif / properties / scale / description
      Previous value: -"Default: 1, the frames' own size. Enlarge (4 or 8) only for posting where a small sprite would look tiny."New value: +"Default 1, the frames' own size; enlarge only for posting."
    • changedInput schema / properties / listFiles / description
      Previous value: -"List every exported file. Default: key files plus frame/animation counts."New value: +"List every exported file, not only the key ones."
    • 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 / 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 / saveAs / description
      Previous value: -"Also write files where the user asked, relative to the server root: keys sheet, atlas, recipe, gif or apng (one animation), or a listed file such as gifs/walk.gif; values such as \"assets/hero.png\". A GIF here implies gif: {}."New value: +"Also write files where the user asked, relative to the server root: {\"sheet\"|\"atlas\"|\"godot\"|\"recipe\"|\"gif\"|\"apng\" or a listed file such as \"gifs/walk.gif\": \"assets/hero.png\"}. A GIF here implies gif: {}."
    • changedInput schema / properties / sequence / description
      Previous value: -"Also write a numbered PNG sequence for a video editor into sequence/: one PNG per video frame with alpha, plus sequence.json and a README with the ffmpeg command for a .mov. The result reports timing changes and whether a loop closes on the frame grid."New value: +"Also write numbered PNG frames with alpha for a video editor into sequence/, with the ffmpeg command for a .mov."
    • changedInput schema / properties / sequence / properties / background / description
      Previous value: -"Palette name or opaque hex colour. Default: transparent."New value: +"Palette name or opaque hex. Default: transparent."
    • removedInput schema / properties / sequence / properties / fit / description
      Removed value: -"contain (default): the whole sprite fits the canvas. cover: the smallest scale that covers the canvas; the overhang is cropped."
    • changedInput schema / properties / sequence / properties / fps / description
      Previous value: -"Frame rate: 24, 25, 30, 60, or \"23.976\", \"29.97\", \"59.94\" (exact n000/1001), or a fraction such as \"30000/1001\"."New value: +"24, 25, 30, 60, \"23.976\", \"29.97\", \"59.94\" or a fraction such as \"30000/1001\"."
    • changedInput schema / properties / sequence / properties / offset / description
      Previous value: -"[x, y] canvas pixels added to the aligned position."New value: +"[x, y] added to the aligned position."
    • removedInput schema / properties / sequence / properties / scale / description
      Removed value: -"Whole-number enlargement. Default: the largest that fits the canvas, or 1 without a size."
    • changedInput schema / properties / sequence / properties / seconds / description
      Previous value: -"Length of the clip instead of loops."New value: +"Clip length instead of loops."
    • changedInput schema / properties / sequence / properties / size / description
      Previous value: -"Canvas: 720p, 1080p, 1440p, 4k, vertical, square or WIDTHxHEIGHT. Default: the sprite at scale."New value: +"720p, 1080p, 1440p, 4k, vertical, square or WIDTHxHEIGHT. Default: the sprite at scale."
    • changedInput schema / properties / sequence / properties / step / description
      Previous value: -"Hold each sampled frame for this many video frames; 2 animates on twos."New value: +"Video frames per sampled frame; 2 animates on twos."
  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 / embedRecipe
      Added value: +{
      +  "description": "Embed the recipe in the exported PNGs so they reopen in the playground.",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / expect
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "What the user asked for: pixel size of each frame, frames, colours, background, loop, GIF scale. The reply's check lists every file that misses it and the call that fixes it.",
      +  "properties": {
      +    "colors": {
      +      "description": "At most.",
      +      "minimum": 1,
      +      "type": "integer"
      +    },
      +    "frames": {
      +      "description": "Per animation.",
      +      "minimum": 1,
      +      "type": "integer"
      +    },
      +    "gifScale": {
      +      "maximum": 16,
      +      "minimum": 1,
      +      "type": "integer"
      +    },
      +    "height": {
      +      "minimum": 1,
      +      "type": "integer"
      +    },
      +    "loop": {
      +      "type": "boolean"
      +    },
      +    "transparent": {
      +      "description": "Background.",
      +      "type": "boolean"
      +    },
      +    "width": {
      +      "minimum": 1,
      +      "type": "integer"
      +    }
      +  },
      +  "type": "object"
      +}
    • changedInput schema / properties / gif / properties / scale / description
      Previous value: -"Default: the largest that keeps the longer side within 256 pixels."New value: +"Default: 1, the frames' own size. Enlarge (4 or 8) only for posting where a small sprite would look tiny."
    • addedInput schema / properties / overwrite
      Added value: +{
      +  "description": "Let saveAs replace existing files. Default: refuse.",
      +  "type": "boolean"
      +}
    • 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 / saveAs
      Added value: +{
      +  "additionalProperties": {
      +    "type": "string"
      +  },
      +  "description": "Also write files where the user asked, relative to the server root: keys sheet, atlas, recipe, gif or apng (one animation), or a listed file such as gifs/walk.gif; values such as \"assets/hero.png\". A GIF here implies gif: {}.",
      +  "type": "object"
      +}
    • addedInput schema / properties / share
      Added value: +{
      +  "description": "Also return a playground link that reopens this recipe in a browser.",
      +  "type": "boolean"
      +}
  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?

Annotations declare destructiveHint=true and idempotentHint=false, and the description earns credit by specifying that output goes to a new folder in the export directory (limiting overwrite risk) and that it returns key files, a delivery check, and a contact sheet. The overwrite-refusal default is only in the schema, not repeated here, and no rate/permission context is given, keeping it at 4.

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

Conciseness4/5

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

Front-loaded with the deliverable list, then the expect/check/fix workflow, then the saveAs note — a sensible order with no filler sentences. Slightly dense with enumerations, but every clause conveys an output type or an action the agent must take.

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

Completeness5/5

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

With 11 parameters, nested objects, and no output schema, the description compensates by naming what the call returns (key files, delivery check, contact sheet) and how to respond to the check. Combined with 100% schema coverage and populated annotations, an agent has enough to call this correctly.

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 description coverage is 100% and the nested gif, sequence, expect, and saveAs descriptions already carry rich semantics, so the baseline is 3. The prose mainly restates expect and saveAs and adds the workflow instruction to act on the check output, but contributes no new syntax or format detail for the remaining parameters.

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?

States a specific verb ('Export') and resource ('pixel art as game-ready files') and enumerates the concrete artifacts produced: sprite sheet, atlas JSON, Godot 4 SpriteFrames, frame PNGs, APNGs, CSS, Canvas player. That enumeration also distinguishes it from siblings like pixel_compile or pixel_scene, which produce intermediate representations rather than deliverables.

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?

Gives clear workflow context: pass expect with what the user asked for, then fix every problem the check lists before handing files on, and pass saveAs to also write where the user asked. It does not name an explicit alternative tool or a when-not-to-use condition, but the sequencing guidance is concrete enough to guide invocation.

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