Skip to main content
Glama

pixel_patch

Idempotent

Fix or tweak pixel art—a single pixel, colour, frame, or its timing—without redrawing. Apply atomic, validated patches to a project or revision and get changed frames plus a before/after PNG.

Instructions

Fix or tweak pixel art (one pixel, a colour, a frame or its timing) without redrawing it. Pass revision or project plus changes applied in order, such as {"set": "frames[blink].duration", "value": 120} or {"paint": "frames[blink]", "value": [{"x": 9, "y": 7, "color": "k"}]}; other paths look like palette.k or frames[blink].layers[body].ops[2].x2. The patch is atomic and must validate. Returns the new revision, every frame whose pixels changed and a before/after PNG.

Input Schema

NameRequiredDescriptionDefault
changesNoEdits applied in order. Paths match error paths, such as frames[3].duration; lists also accept [name] and [-] to append.
overlayNoA saved pixelforge-overlay to apply instead of changes.
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.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed10 schema fields changedv0.9.2
    • changedInput schema / properties / changes / items / properties / cleanup / description
      Previous value: -"Frame path. Value: {corners?, strays?, x?, y?, w?, h?, mask?, colors?}. Proposes fixes as canvas corrections: corners removes doubled L-shaped steps in one-pixel lines; strays gives a pixel the colour all eight neighbours share (noise, pinholes, isolated specks). Check the report and keep deliberate corners, eyes and sparks. Inspection diagnostics count both per frame."New value: +"Frame path; value: {corners?, strays?, x?, y?, w?, h?, mask?, colors?}. Proposes fixes for doubled corners and stray pixels; check the report."
    • changedInput schema / properties / changes / items / properties / grid / description
      Previous value: -"Frame path. Value: {x,y,rows,erase?,mask?}. Dots/spaces preserve; explicit erase character clears; mask x selects and dot preserves."New value: +"Frame path; value: {x, y, rows, erase?, mask?}, dots keep pixels."
    • changedInput schema / properties / changes / items / properties / move / description
      Previous value: -"Frame path. Value: {x,y,w,h,dx,dy,mask?}. Moves exact pixels including corrections; erases selected source. Destination must fit."New value: +"Frame path; value: {x, y, w, h, dx, dy, mask?}."
    • changedInput schema / properties / changes / items / properties / paint / description
      Previous value: -"Frame path, such as frames[blink]. Replaces pixels in final canvas coordinates, after all layers, preserving source operations. Use transparent to erase."New value: +"Frame path such as frames[blink]; value: [{x, y, color}] canvas pixels after all layers (transparent erases)."
    • changedInput schema / properties / changes / items / properties / recolor / description
      Previous value: -"Frame path. Value: {x,y,w,h,from,to,mask?}. Replaces only matching selected colors."New value: +"Frame path; value: {x, y, w, h, from, to, mask?}."
    • changedInput schema / properties / changes / items / properties / scope / description
      Previous value: -"Canvas edits: inherited is default. frame preserves other poses by recording compensating canvas corrections, reported in edits.protected."New value: +"Canvas edits: inherited (default), or frame to leave frames that inherit it unchanged."
    • removedInput schema / properties / changes / items / properties / value / description
      Removed value: -"JSON value for set or insert; for paint, a list of {x, y, color} pixels inside the canvas (palette names or hex colors, exact RGBA replacement)."
    • changedInput schema / properties / overlay / description
      Previous value: -"Apply a saved pixelforge-overlay instead of changes. Canvas-only overlays reapply when the frames they touch are unchanged (reported as rebased)."New value: +"A saved pixelforge-overlay to apply instead of changes."
    • 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."
  2. Changed6 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"
      -  }
      -}
    • 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"
  3. First observedv0.9.0

TDQS

A4.5/5.0
Behavior5/5

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

Adds substantial traits beyond the annotations: the patch is atomic, must validate, and returns the new revision plus every frame whose pixels changed and a before/after PNG. That is real behavioral context (failure mode, idempotency backing, return payload) rather than restatement of the hints.

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 purpose sentence, then concrete examples, then the contract sentence about atomicity and returns. Dense but every clause carries information; the inline JSON examples cost some readability but buy real clarity.

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 4-parameter, nested-object mutation tool with no output schema, the description covers the edit grammar, atomicity, and return values, which is most of what an agent needs. The gap is 'overlay' being completely unaddressed and the revision-vs-project exclusivity being implied rather than stated.

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

Parameters4/5

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

With 100% schema coverage the baseline is 3, but the description earns an extra point by showing the change-object DSL in use ({"set": "frames[blink].duration", "value": 120}) and the path grammar (palette.k, frames[blink].layers[body].ops[2].x2), which clarifies ordering and addressing intent beyond the per-key schema blurbs. It omits any mention of the overlay parameter's role.

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 (fix/tweak) on a specific resource (pixel art: one pixel, a colour, a frame or its timing) and draws the boundary against the sibling that redraws ('without redrawing it'). An agent can distinguish this from pixel_compile or pixel_scene without opening a schema.

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 the prerequisite shape ('Pass revision or project plus changes applied in order') and the operative condition of when it applies, i.e. small edits that must not trigger a redraw. It never names an alternative sibling (pixel_compile) or an explicit when-not, so it stops short of a full routing rule.

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