@pixerate/editor 0.272.1 → 0.274.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/chunk-2A5IKZMU.js +557 -0
  2. package/dist/chunk-YH6SKC7P.cjs +557 -0
  3. package/dist/paint-editor/collaboration.d.cts +6 -1
  4. package/dist/paint-editor/collaboration.d.ts +6 -1
  5. package/dist/paint-editor/controller.d.cts +117 -0
  6. package/dist/paint-editor/controller.d.ts +117 -0
  7. package/dist/paint-editor/document.d.cts +1 -1
  8. package/dist/paint-editor/document.d.ts +1 -1
  9. package/dist/paint-editor/evolution-tools.d.cts +48 -0
  10. package/dist/paint-editor/evolution-tools.d.ts +48 -0
  11. package/dist/paint-editor/evolution.d.cts +175 -0
  12. package/dist/paint-editor/evolution.d.ts +175 -0
  13. package/dist/paint-editor/index.cjs +8500 -7258
  14. package/dist/paint-editor/index.d.cts +3 -0
  15. package/dist/paint-editor/index.d.ts +3 -0
  16. package/dist/paint-editor/index.js +8481 -7239
  17. package/dist/paint-editor/presence.d.cts +5 -1
  18. package/dist/paint-editor/presence.d.ts +5 -1
  19. package/dist/paint-editor/ui.d.cts +7 -2
  20. package/dist/paint-editor/ui.d.ts +7 -2
  21. package/dist/raster/fill.d.cts +85 -0
  22. package/dist/raster/fill.d.ts +85 -0
  23. package/dist/raster/index.cjs +9 -1
  24. package/dist/raster/index.d.cts +1 -0
  25. package/dist/raster/index.d.ts +1 -0
  26. package/dist/raster/index.js +8 -0
  27. package/package.json +1 -1
  28. package/skills/README.md +1 -0
  29. package/skills/pixerate-brushes/SKILL.md +3 -0
  30. package/skills/pixerate-paint-editor/SKILL.md +55 -96
  31. package/skills/pixerate-paint-editor/references/brush-facets.md +3 -0
  32. package/skills/pixerate-paint-editor/references/evolution.md +27 -0
  33. package/skills/pixerate-paint-editor/references/mutation.md +25 -0
  34. package/skills/pixerate-paint-editor/references/oils.md +8 -1
  35. package/skills/pixerate-paint-editor/references/smart-inks.md +51 -0
  36. package/skills/pixerate-paint-mutation/SKILL.md +76 -0
  37. package/skills/pixerate-paint-mutation/evals/decline-unanswered.json +58 -0
  38. package/skills/pixerate-paint-mutation/evals/dry-ink-answer.json +74 -0
  39. package/skills/pixerate-paint-mutation/evals/wet-wash-answer.json +76 -0
  40. package/skills/pixerate-paint-oil-impasto/SKILL.md +2 -1
  41. package/skills/pixerate-paint-sketch-to-finish/SKILL.md +16 -5
  42. package/skills/pixerate-paint-sketch-to-finish/evals/flats-under-ink.json +2 -3
@@ -68,6 +68,7 @@ the pitfalls, and uses the tools below.
68
68
  - `pixerate-paint-watercolour`: paper, light to dark, wet-in-wet and wet-on-dry, drying and glazing.
69
69
  - `pixerate-paint-oil-impasto`: toned ground, thin to thick, pigment mixing, impasto and its light.
70
70
  - `pixerate-paint-sketch-to-finish`: sketch, ink, flats, clipped shading and light, one layer per pass.
71
+ - `pixerate-paint-mutation`: claim a user's mutation stroke (`read_mutation`), let it fade, and paint an answer (`answer_mutation`).
71
72
 
72
73
  ## Working method
73
74
 
@@ -77,36 +78,14 @@ the pitfalls, and uses the tools below.
77
78
  `new_canvas` with `paper`): cold pressed or rough for broken, sparkling texture, sanded pastel
78
79
  paper for pastel, charcoal paper for charcoal, cotton canvas or gesso for painterly work,
79
80
  smooth bristol for ink. Set `depth` for how much texture you want.
80
- 3. **Block in big shapes first.** Put each pass on its own layer (`add_layer` with a name).
81
- `fill_layer` gives a ground colour (flat) or a tonal layer that catches the grain
82
- (`grain` 0.6..1, `pressure` 0.2..0.6). Paint large masses with a big soft pastel or airbrush
83
- in a few long `paint_stroke`s rather than many small ones.
84
- 4. **Plan and mix colours before painting.** `color_harmony` gives a palette around a key colour
85
- (on the painter's red-yellow-blue wheel by default). `mix_colors` predicts what paints mixed
86
- in given parts look like (Kubelka-Munk: blue and yellow make green, white tints rather than
87
- greys); the mixing pad (`mixing_pad_stroke`, `pick_mixing_pad_color`) mixes by hand with a
88
- palette knife. Keep chosen colours as swatches (`add_swatch`). Use the result as the stroke
89
- `color`; `set_color` makes it the default.
90
- 5. **Refine** with smaller brushes on new layers: pencil and ink for line, charcoal for value.
91
- **Blend** on the layer that holds the paint: `blender` to melt neighbouring colours together
92
- (pigment mixing, so blue next to yellow goes green), `smudge` / `wet-smudge` to drag paint
93
- into soft edges and streaks, `mixer-brush` to paint wet into wet, `dirty-round` to paint with
94
- a brush that picks up and carries colour, `multicolor-flat` for strokes loaded with several
95
- colours. Glaze with a `watercolor` layer, warm or cool with `tint`, light with `highlight`,
96
- shade with `shadow`.
97
- 6. **Let the guides multiply your marks.** `set_symmetry` (mirror for faces and vases,
98
- kaleidoscope for mandalas and ornaments) paints every copy from one stroke;
99
- `set_perspective_guide` plus `paint_stroke` with `snapToPerspective` lays converging lines for
100
- buildings and roads. To copy an image's composition and colours, `set_tracing_image` with
101
- `sampleColors` and paint over it.
102
- 7. **Repeat and vary instead of drawing every mark.** Paint one good stroke, then
103
- `replay_strokes` it with `dx` / `dy`, `rotate`, `scale`, `flipX`, `color`, `brushId` and
104
- `repeat` for hatching, grass, foliage, rows, fans or spirals, in one undo step.
105
- 8. **Check** with `render_preview` after each block of work, not after each stroke.
106
- `pick_color` reads exact values. Undo what made it worse.
81
+ 3. **Block in big shapes first.** Put each pass on its own layer (`add_layer` with a name). `fill_layer` gives a ground colour (flat) or a tonal layer that catches the grain (`grain` 0.6..1, `pressure` 0.2..0.6). For line art, call `find_unfilled_regions` against the ink layer (`reference: inkLayerId`, `closeGap: 4..8`) to list enclosed shapes and their interior seed points, then `fill_region` on a flats layer below the ink (`reference: inkLayerId`, `closeGap: 4..8`, `expand: 1..2` so no halo shows at anti-aliased line edges, or `wet: true` to pour a watercolour wash into the region). For unoutlined masses, paint with a big soft pastel or airbrush in a few long `paint_stroke`s rather than many small ones.
82
+ 4. **Plan and mix colours before painting.** `color_harmony` gives a palette around a key colour (on the painter's red-yellow-blue wheel by default). `mix_colors` predicts what paints mixed in given parts look like (Kubelka-Munk: blue and yellow make green, white tints rather than greys); the mixing pad (`mixing_pad_stroke`, `pick_mixing_pad_color`) mixes by hand with a palette knife. Keep chosen colours as swatches (`add_swatch`). Use the result as the stroke `color`; `set_color` makes it the default.
83
+ 5. **Refine** with smaller brushes on new layers: pencil and ink for line, charcoal for value. **Blend** on the layer that holds the paint: `blender` to melt neighbouring colours together (pigment mixing, so blue next to yellow goes green), `smudge` / `wet-smudge` to drag paint into soft edges and streaks, `mixer-brush` to paint wet into wet, `dirty-round` to paint with a brush that picks up and carries colour, `multicolor-flat` for strokes loaded with several colours. Glaze with a `watercolor` layer, warm or cool with `tint`, light with `highlight`, shade with `shadow`.
84
+ 6. **Let the guides multiply your marks.** `set_symmetry` (mirror for faces and vases, kaleidoscope for mandalas and ornaments) paints every copy from one stroke; `set_perspective_guide` plus `paint_stroke` with `snapToPerspective` lays converging lines for buildings and roads. To copy an image's composition and colours, `set_tracing_image` with `sampleColors` and paint over it.
85
+ 7. **Repeat and vary instead of drawing every mark.** Paint one good stroke, then `replay_strokes` it with `dx` / `dy`, `rotate`, `scale`, `flipX`, `color`, `brushId` and `repeat` for hatching, grass, foliage, rows, fans or spirals, in one undo step.
86
+ 8. **Check** with `render_preview` after each block of work, not after each stroke. `pick_color` reads exact values. Undo what made it worse.
107
87
  9. **Watercolour** is its own way of working: see "Wet media" below.
108
- 10. **Deliver**: `export_image` (PNG, or JPEG / WebP in a browser), `export_psd` for Photoshop,
109
- `save_paint_document` to keep the painting with its strokes (when the host has storage).
88
+ 10. **Deliver**: `export_image` (PNG, or JPEG / WebP in a browser), `export_psd` for Photoshop, `save_paint_document` to keep the painting with its strokes (when the host has storage).
110
89
 
111
90
  ## Best practices
112
91
 
@@ -123,20 +102,16 @@ the pitfalls, and uses the tools below.
123
102
  - Mixer Brush: blend with flow 0.3..0.6; at full flow it paints only its own colour.
124
103
  - **Pick-up and mix ratios**: `pickup` 0.3..0.7 with `mix` 0.4..0.7 gives a brush that drags the colours it crosses into its own; `dirty: true` keeps them for the next stroke (wipe with
125
104
  `clean_mixer_brush`). Keep `pickup` 0 for clean, predictable colour.
126
- - **Multi-colour**: give 2..8 `colors` (or none for the paint and secondary colours). `tip` mode with a flat tip and `rotation: "stroke"` paints ribbons; `softness` 0.3..0.7 keeps the stripes readable. `dab` mode with `order: "sequence"` and `length` 5..20 walks through a palette;
127
- `random` for foliage and confetti.
105
+ - **Multi-colour**: give 2..8 `colors` (or none for the paint and secondary colours). `tip` mode with a flat tip and `rotation: "stroke"` paints ribbons (`softness` 0.3..0.7); `dab` mode with `order: "sequence"` and `length` 5..20 walks through a palette (`random` for foliage and confetti).
128
106
  - **Pigment for paint, multiply for markers**: tip `blend: "pigment"` mixes subtractively on the canvas; `multiply` darkens where marker strokes overlap. Pigment layers are written as Normal to PSD (the export warns).
129
107
  - **Painterly layer modes do the glazing for you** (`update_layer` `blend`, or a brush tip):
130
- - `watercolor`: the layer is a transparent wash. Washes multiply wavelength by wavelength, so each glaze darkens and blue over yellow makes green. White is clear: paint glazes on a layer over the underpainting, at opacity 0.4..0.8.
131
- - `tint`: recolours what is below (its hue and chroma) and keeps its lightness, gently near white and black. Use it to warm or cool a whole passage.
132
- - `highlight`: adds coloured light that rolls off towards white instead of clipping. Use it for glints and glow; black does nothing.
133
- - `shadow`: shades as if light were blocked, coloured by the layer (paint shadows in a cool blue-violet), never crushing to black. White does nothing.
134
-
135
- PSD has none of them: `export_psd` writes the nearest standard mode (watercolor and shadow as Multiply, tint as Color, highlight as Screen) and warns; the merged image stays exact.
136
- - **Grow the canvas instead of starting big** with `extend_canvas` (pixels per side; nothing painted moves relative to anything else). Adding `left` or `top` shifts every coordinate you hold by (+left, +top): re-read `list_layers` or use the returned `rect`. Dry wet paint first.
137
- `set_canvas` only crops or grows at the bottom-right.
138
- - **Save brushes you build** in the brush library: `create_brush` with `basedOn` and the facets you changed (or `get_brush` then `create_brush` with its `brush`), and share them with
139
- `export_brushes` / `import_brushes`. A saved brush shows in every editor that shares the library; `set_brush` its id to paint with it.
108
+ - `watercolor`: transparent wash; washes multiply wavelength by wavelength (blue over yellow makes green, white is clear; opacity 0.4..0.8).
109
+ - `tint`: recolours what is below (hue and chroma) and keeps its lightness.
110
+ - `highlight`: adds coloured light that rolls off towards white without clipping (black does nothing).
111
+ - `shadow`: shades as if light were blocked, coloured by the layer, never crushing to black (white does nothing).
112
+ PSD has none of them: `export_psd` writes the nearest standard mode and warns; the merged image stays exact.
113
+ - **Grow the canvas instead of starting big** with `extend_canvas` (pixels per side; nothing painted moves relative to anything else). Adding `left` or `top` shifts every coordinate you hold by (+left, +top): re-read `list_layers` or use the returned `rect`. Dry wet paint first. `set_canvas` only crops or grows at the bottom-right.
114
+ - **Save brushes you build** in the brush library: `create_brush` with `basedOn` and the facets you changed, and share them with `export_brushes` / `import_brushes`. A saved brush shows in every editor that shares the library; `set_brush` its id to paint with it.
140
115
 
141
116
  Which facet sets which engine setting: [references/brush-facets.md](references/brush-facets.md).
142
117
 
@@ -144,11 +119,10 @@ Which facet sets which engine setting: [references/brush-facets.md](references/b
144
119
 
145
120
  Watercolour, gouache and wet oil run on a fluid simulation: lay washes light to dark, let them flow (`advance_wet_paint`), dry them (`dry_paint`) before glazing. Nothing moves for an agent until you advance or dry it, and saving dries the paint. Load `pixerate-paint-watercolour`, and read [references/wet-media.md](references/wet-media.md) for the engine and the wet tools. **Ghost paint and invisible ink** (`ghost` facet: `ghost-wash`, `ghost-gouache`, `ghost-oil`, `invisible-ink`) mix and flow while wet, then fade without leaving their own colour, leaving only a smear in the paint they touched: read [references/ghost.md](references/ghost.md).
146
121
 
147
- ## Oils and thick paint
122
+ ## Oils, thick paint and smart inks
148
123
 
149
- Wet oil blends where the brush meets it; impasto adds relief lit by `set_lighting`: load
150
- `pixerate-paint-oil-impasto` and read [references/oils.md](references/oils.md). **Light paint**
151
- (neon, glowing glass and watercolour: `light.amount`, the Light category; backlit paper): [references/light-paint.md](references/light-paint.md).
124
+ Wet oil blends where the brush meets it; impasto adds relief lit by `set_lighting`, and `iridescence` (`scarab-lacquer`, `soap-film`, `pearl-glaze`) adds thin-film structural colour that shifts hue with light and `set_view_tilt`: load `pixerate-paint-oil-impasto` and read [references/oils.md](references/oils.md). **Light paint** (neon, glowing glass and watercolour: `light.amount`, the Light category; backlit paper): [references/light-paint.md](references/light-paint.md).
125
+ **Smart inks** (`fluorescence` for UV/invisible ink under `set_uv_light`, `thermochromic` for heat-reactive ink with `paint_heat` / `advance_heat`; presets `uv-marker`, `thermo-ink`): [references/smart-inks.md](references/smart-inks.md).
152
126
 
153
127
  ## Gradient paint
154
128
 
@@ -192,39 +166,15 @@ it work with the painting. Search first: `search_effects` with `use` `generator`
192
166
  fills), `overlay` / `title` (layers, fills) or `filter` (light on paint), then `describe_effect`
193
167
  for the controls and presets.
194
168
 
195
- - **Effect paper**: `set_paper_effect` draws a generator over the sheet, under every layer.
196
- `multiply` (the default) keeps the paper's colour and grain showing through, which is what
197
- makes it look like printed or dyed paper; `normal` covers it. It saves and exports with the
198
- painting, and brush grain still follows the real paper.
199
- - **Effect layers** (`add_effect_layer`): a pixel layer drawn by a generator, overlay or title
200
- effect. Use them for backgrounds you want to change later, and for overlays (grain, light
201
- leaks, captions) above the paint with a blend mode (`screen` / `linear-dodge` for light,
202
- `multiply` for textures). `update_effect` draws the layer again and replaces its pixels, paint
203
- on it included, so paint on a layer above it.
204
- - **Effect fills** (`fill_layer_with_effect`): the effect composited onto a layer's pixels with
205
- no link kept; `lockTransparency` colours only what is painted (an effect inside a shape).
206
- - **Light on dried paint** (`apply_effect`): a filter (light, glow, grade) run on a layer's paint
207
- and baked in, at `opacity` strength. Transparent areas stay transparent, and thick paint keeps
208
- its relief and stays lit by the editor's light. Dry wet paint first (`dry_paint`).
169
+ - **Effect paper**: `set_paper_effect` draws a generator over the sheet, under every layer (`multiply` keeps the paper's colour and grain showing through; `normal` covers it).
170
+ - **Effect layers** (`add_effect_layer`): a pixel layer drawn by a generator, overlay or title effect (`update_effect` redraws and replaces its pixels, so paint on a layer above it).
171
+ - **Effect fills** (`fill_layer_with_effect`): the effect composited onto a layer's pixels (`lockTransparency` colours only what is painted).
172
+ - **Light on dried paint** (`apply_effect`): a filter run on a layer's dried paint and baked in at `opacity` strength, keeping thick relief.
209
173
  - **Effect paint** (`set_effect_paint`): brushes paint with the effect itself; see below.
210
174
 
211
175
  ## Effect paint
212
176
 
213
- `set_effect_paint` makes a generator, overlay or title effect the paint: rendered once, every
214
- stroke takes each pixel's colour from it, fixed to the canvas (painting reveals a hidden image).
215
- Its transparency paints nothing.
216
-
217
- 1. `search_effects` (`use` generator, overlay or title) and `describe_effect` for the controls.
218
- 2. `set_effect_paint` with `effect`, `values`, `time` (ms, default 0). Wait for it to return.
219
- 3. Paint: dab, particle and bristle brushes take the colour per pixel; the Mixer Brush and pick-up
220
- dab brushes take it per dab at its centre. Wet and impasto brushes take it per pixel, shifted by
221
- paint they pick up (in watercolour and gouache it flows with the water; impasto keeps its relief).
222
- Sprayed stickers each take the colour at their centre (they stay recolourable).
223
- 4. `set_color`, `set_paint` or `remove_effect` (target and effect `paint`) go back to colour;
224
- `update_effect` (target and effect `paint`, `values`) renders new values.
225
- 5. Along each stroke (`map`) or playing on stroke layers (`live`): [references/effect-paint.md](references/effect-paint.md).
226
-
227
- When to use which:
177
+ `set_effect_paint` makes a generator, overlay or title effect the paint: rendered once, every stroke takes each pixel's colour from it, fixed to the canvas (painting reveals a hidden image); transparency paints nothing. Call `search_effects` (`use` generator, overlay or title) and `describe_effect`, then `set_effect_paint` with `effect`, `values`, `time` (ms, default 0) and wait for it to return before painting. `set_color`, `set_paint` or `remove_effect` (target and effect `paint`) go back to colour; `update_effect` (target and effect `paint`, `values`) renders new values. Along each stroke (`map`) or playing on stroke layers (`live`): [references/effect-paint.md](references/effect-paint.md).
228
178
 
229
179
  | Want | Use |
230
180
  | --- | --- |
@@ -236,8 +186,7 @@ When to use which:
236
186
 
237
187
  ## Canvas-hosted layer
238
188
 
239
- When `get_canvas_info` has `bounds`, the painting is a layer of a larger canvas and every position
240
- is a canvas coordinate: read [references/canvas-hosted.md](references/canvas-hosted.md).
189
+ When `get_canvas_info` has `bounds`, the painting is a layer of a larger canvas and every position is a canvas coordinate: read [references/canvas-hosted.md](references/canvas-hosted.md).
241
190
 
242
191
  ## Tool reference
243
192
 
@@ -253,9 +202,9 @@ is a canvas coordinate: read [references/canvas-hosted.md](references/canvas-hos
253
202
  | `get_paper` | Read every setting of the current paper (pattern, seed, scale, tiling, depth, shading, dry and wet tuning, and `metallic`, `backlight`, `backlightColor` when set). |
254
203
  | `set_paper` | Change the paper: `preset`, then any of `color`, `depth` (0..1), `scale` (0.25..8), `seed` (another sheet), `pattern`, `tiling`, `shading`, `dry` / `wet` tuning (`contrast` 0..3, `invert`), `metallic`, `backlight` (0..1, light from behind: glazes glow, opaque paint blocks; preset `stained-glass`) and `backlightColor`. Affects new strokes' grain and how the paper shows; strokes already painted keep their pixels. Returns the paper. |
255
204
 
256
- ### Suggesting paint
205
+ ### Suggesting and answering paint
257
206
 
258
- Contributing while the user paints: suggest, don't paint. Read [references/painting-alongside.md](references/painting-alongside.md).
207
+ Contributing while the user paints: suggest with proposals ([references/painting-alongside.md](references/painting-alongside.md)), or answer `mutation` strokes ([references/mutation.md](references/mutation.md), `pixerate-paint-mutation`).
259
208
 
260
209
  | Tool | Use |
261
210
  | --- | --- |
@@ -263,6 +212,10 @@ Contributing while the user paints: suggest, don't paint. Read [references/paint
263
212
  | `list_paint_proposals` | Read the pending paint proposals: id, title, rationale, who proposed it, op kinds, placement, bounds, the strokes it echoes and any since removed (`missing`), and whether accepting is allowed. Call before accepting or rejecting. |
264
213
  | `accept_paint_proposal` | Paint a pending proposal on a new layer as one undo step recorded as the proposer's (`proposalId`). Only when the user said yes. Returns the layer id, stroke ids and undo step name. Refuses when a stroke it echoes is gone. |
265
214
  | `reject_paint_proposal` | Dismiss a pending proposal (`proposalId`); nothing changes. Use it to withdraw a suggestion that no longer fits. Returns the id and title. |
215
+ | `list_mutations` | List pending mutations (`id`, `layerId`, `medium`, `bounds`, `age`, `state`, `strokes`) raised after the user paints with a `mutation` brush (`mutation-wash`, `mutation-ink`) and pauses (`includeAll: true` includes claimed, answered and declined). Call before `read_mutation`. |
216
+ | `read_mutation` | Claim a pending mutation (`id`) and start its fade so the user's paint sinks away (`padding` px around bounds, `maxSide` for the canvas preview). Returns `strokes` (points, colour, brush), `cropPng` and `canvasPng` (as images and structured data). Refused if already claimed. |
217
+ | `answer_mutation` | Paint your answer to a claimed mutation (`id`, `strokes` with `brush`, `color`, `points`, optional `layerId`, `note`). Runs through the paint director when present and commits as one undo step (`agent:<id>`). Returns `strokeIds` and `bounds`. Refused if not claimed or already answered. |
218
+ | `decline_mutation` | Give up on a mutation (`id`, optional `reason`) without answering so the user's paint stays on the canvas as ordinary paint. |
266
219
 
267
220
  ### Layers
268
221
 
@@ -283,6 +236,8 @@ Contributing while the user paints: suggest, don't paint. Read [references/paint
283
236
  | `move_layer` | Reorder: `index` among siblings (0 = bottom), optionally into `parentId` (or `null` for the top level). |
284
237
  | `merge_layer_down` | Merge a pixel layer into the pixel layer directly below (which keeps its settings). |
285
238
  | `fill_layer` | Fill a pixel layer with `color` at `opacity`: flat, or with `grain` 0..1 following the paper grain (`pressure` 0..1 decides how far into the valleys, default 0.5). The fastest way to lay a ground or a textured tone. Returns the bounds. |
239
+ | `fill_region` | Flood-fill one or more enclosed regions on a pixel layer (`x`, `y` or `points: [{x, y}]`). Set `reference` to an ink layer id (or `"all"` / `"composite"`, or `"layer"`), `closeGap` (0..32 px) to bridge small gaps in line art without leaking, `expand` (-16..16 px, e.g. 1..2 px) to bleed under anti-aliased lines so no halo shows, `grain` (0..1) for paper texture, or `wet: true` to pour a watercolour wash into the region on the wet layer. One undo step. Returns `layerId`, `bounds`, `pixelCount`, `regions`, and `leaked` (`true` when the fill touched the canvas border). |
240
+ | `find_unfilled_regions` | Find enclosed unfilled regions from a reference line-art layer (`reference: inkLayerId`) or `"all"` / `"composite"`, closing gaps up to `closeGap` px and excluding areas already painted on `layerId`. Changes nothing. Returns `regions` sorted largest first with an interior seed `point` (`{x, y}`) ready for `fill_region`, `centroid`, `bounds`, `pixelCount`, and `touchesEdge`. |
286
241
  | `clear_layer` | Remove every pixel of a layer. Returns the bounds that were cleared. |
287
242
 
288
243
  ### Brushes
@@ -292,7 +247,7 @@ Listing, saving, sharing and previewing brushes are the brush library's tools (`
292
247
  | Tool | Use |
293
248
  | --- | --- |
294
249
  | `get_brush` | Read a brush as facets (`brush`), or the active brush as edited (no `id`), with the `engine` and `category` it paints with, `builtin` (an unchanged preset) and `strokePaint` (what its next stroke paints). Read before changing a brush you did not build. |
295
- | `set_brush` | Choose the active brush (`id`, from `list_brush_presets`) and / or lay facets over it: `tip`, `marks`, `dynamics`, `behaviour`, `light`, `ghost`, `motion`, `colour` (objects merge, lists replace; marks of another kind replace the marks), `unset` facets to drop. Editor state, not saved: `create_brush` stores a brush. Returns `brush` (facets), `engine` and `problems` (values clamped). The old flat fields (`particle`, `impasto`, `behavior`…) are refused. |
250
+ | `set_brush` | Choose the active brush (`id`, from `list_brush_presets`) and / or lay facets over it: `tip`, `marks`, `dynamics`, `behaviour`, `light`, `iridescence`, `ghost`, `mutation`, `motion`, `colour` (objects merge, lists replace; marks of another kind replace the marks), `unset` facets to drop. Editor state, not saved: `create_brush` stores a brush. Returns `brush` (facets), `engine` and `problems` (values clamped). The old flat fields (`particle`, `impasto`, `behavior`…) are refused. |
296
251
 
297
252
  ### Colour
298
253
 
@@ -335,6 +290,9 @@ Listing, saving, sharing and previewing brushes are the brush library's tools (`
335
290
  | `convert_to_stroke_layer` | Make an empty pixel layer a stroke layer. Returns the layer. |
336
291
  | `rasterize_stroke_layer` | Turn a stroke layer into a plain pixel layer with the same pixels (strokes no longer editable). Do it before erasing, smudging or filling on it. |
337
292
  | `replay_strokes` | Paint recorded `strokeIds` again: exactly, or varied by `dx` / `dy` (px), `rotate` (degrees clockwise), `scale` (brush size follows), `flipX`, `origin`, `pressureScale`, `color`, `brushId`, `layerId`, `repeat` (each copy applies the variation again). One undo step. Returns the new stroke ids, bounds and layers. |
293
+ | `breed_strokes` | Breed `count` (2..12, default 4) mutated variants from recorded `strokeIds` (1..16) as ghost paint proposals: `mutation` (`geometry`, `color`, `brush`, `seed`, each 0..1), `seed`. Returns `proposalIds` and each variant's `diff` and `bounds`. Read [references/evolution.md](references/evolution.md). |
294
+ | `breed_proposal` | Breed `count` (2..12, default 4) next-generation variants from `proposalIds` (1 id mutates it; 2 ids cross their points and brush facets over, then mutate), with `mutation` and `seed`. Returns `proposalIds` and `proposals`. |
295
+ | `get_proposal_genome` | Read a pending proposal's stroke genome (`proposalId`): `strokes` with full brush facets, points, colour and seed. |
338
296
  | `undo_paint` | Undo the last step (a wet stroke's undo puts the wet paint back as it was before it). Returns what was undone and what is next. |
339
297
  | `redo_paint` | Redo the step just undone. |
340
298
 
@@ -354,16 +312,24 @@ Listing, saving, sharing and previewing brushes are the brush library's tools (`
354
312
  | `set_wet_engine` | Let Engine B (WebGPU in browsers, Metal in the Swift editors) shade large washes (`gpu` "auto", the default) or force the CPU (`gpu` "off"); `minTiles` sets the wet area at which the GPU takes over. Never changes the result, only how it is drawn; editor setting, not saved, not an undo step. Returns `engine` (`state`: off, not-started, starting, unavailable, standby, active, failed; `reason`, `adapter`). |
355
313
  | `set_instant_dry` | `on`: every wet stroke dries as it ends (same undo step); off (default) wet paint keeps moving. Returns `instantDry`. |
356
314
 
357
- ### Thick paint and light
315
+ ### Thick paint, light and smart inks
358
316
 
359
317
  | Tool | Use |
360
318
  | --- | --- |
361
- | `set_lighting` | Move the light for thick paint and metallic paper: `azimuth` (degrees clockwise from the top), `elevation` (5..90), `relief` (0..1). Nothing is repainted; not an undo step. Returns the lighting. Use a low elevation to show brushwork. |
319
+ | `set_lighting` | Move the light for thick paint, thin-film iridescence and metallic paper: `azimuth` (degrees clockwise from the top), `elevation` (5..90), `relief` (0..1). Nothing is repainted; not an undo step. Returns the lighting. Use a low elevation to show brushwork. |
320
+ | `set_view_tilt` | Tilt the view angle (`x` -1..1 left to right, `y` -1..1 top to bottom) to shift thin-film iridescent hue and specular glints without moving the light. View state only, not saved, not an undo step. Returns `viewTilt`. |
321
+ | `get_view_tilt` | Read the current `viewTilt` (`{ x, y }`). Changes nothing. |
362
322
  | `get_relief` | Read a layer's relief (`bounds`, `maxHeight` 0..255, null when flat) and the lighting. Changes nothing. |
363
- | `flatten_relief` | Remove a layer's relief and material (rim and clarity too), keeping its colour and light. One undo step. Returns whether anything was flattened. |
323
+ | `flatten_relief` | Remove a layer's relief, material and thin-film iridescence (rim and clarity too), keeping its colour and light. One undo step. Returns whether anything was flattened. |
364
324
  | `get_light` | Read a layer's light paint: `hasLight`, `bounds`, `peak` 0..255 and its `spill`. Changes nothing. |
365
325
  | `set_light_spill` | How far a layer's light spills (`radius` px, 0 off) and how strongly (`strength` 0..2). Not an undo step; nothing is repainted. |
366
326
  | `clear_light` | Take a layer's light away, keeping its colour. One undo step. |
327
+ | `set_uv_light` | Switch the virtual UV blacklight (`on`) and set its glow `strength` (0..2, default 1) and `ambient` daylight level (0..1, default 0.15) to reveal `fluorescence` strokes (`uv-marker`). Saved with the document, not an undo step. Returns the UV settings and `fluorescentLayerIds`. |
328
+ | `get_uv_light` | Read the UV blacklight settings (`on`, `strength`, `ambient`) and `fluorescentLayerIds`. Changes nothing. |
329
+ | `paint_heat` | Warm or cool the canvas heat field along `points` (`radius` px default 24, `temperature` 0..1 default 1): shifts `thermochromic` strokes (`thermo-ink`) toward their `hotColor` above `threshold` and dries wet paint under hot cells. One undo step. Returns heat stats and `dried`. |
330
+ | `advance_heat` | Step thermal diffusion and Newtonian cooling by `steps` (1..200, default 10) so warm patches spread and fade. Not an undo step. Returns `steps`, heat stats and `dried`. |
331
+ | `clear_heat` | Cool the entire canvas heat field back to 0 at once so thermochromic paint returns to its cold colour. One undo step when warm. Returns `{ cleared }`. |
332
+ | `get_heat` | Read the canvas heat stats (`max`, `mean`, `hotCells`, `bounds`), optional temperature `samples` at `points`, and `thermochromicLayerIds`. Changes nothing. |
367
333
 
368
334
  ### Stickers
369
335
 
@@ -442,20 +408,13 @@ The host may add the shared document tools (see `pixerate-agent-tools`). A paint
442
408
  ## Pitfalls
443
409
 
444
410
  - With agent playback on, strokes, fills and replays are painted live by the director, and other writes open their panel (Colour, Wet, Paper, Layers, Effects) and point at the control. Results are unchanged.
445
- - **Stroke-by-stroke drawing is slow and expensive.** Do not draw a detailed picture point by
446
- point. Block in with fills and big strokes, then replay and vary a few good strokes.
447
- - **A hidden or locked layer refuses paint**: show it (`update_layer visible true`) or unlock it.
448
- `transparency` lock is not an error: paint only lands where the layer already has paint.
449
- - **Depth 0 means no grain** whatever the brush; grainy media then look smooth. Smooth bristol
450
- has depth 0.
451
- - **Grain is fixed to the document**, not to the layer: moving content does not move the grain,
452
- and two strokes on the same spot catch the same peaks.
453
- - **Blending tools need paint to blend.** Blender and smudge only move what is on the target
454
- layer: blend on the layer you painted, not on an empty one above it.
455
- - **A dirty brush stays dirty** across strokes and colour changes until `clean_mixer_brush`.
456
- Pick-up lifts only paint on the target layer: the paper and other layers lift nothing.
457
- - **Replays use the recorded load**: a replayed pick-up stroke starts with what the brush carried
458
- when it was painted, not with the current load.
411
+ - **Stroke-by-stroke drawing is slow and expensive.** Do not draw a detailed picture point by point. Block in with fills and big strokes, then replay and vary a few good strokes.
412
+ - **A hidden or locked layer refuses paint**: show it (`update_layer visible true`) or unlock it. `transparency` lock is not an error: paint only lands where the layer already has paint.
413
+ - **Depth 0 means no grain** whatever the brush; grainy media then look smooth. Smooth bristol has depth 0.
414
+ - **Grain is fixed to the document**, not to the layer: moving content does not move the grain, and two strokes on the same spot catch the same peaks.
415
+ - **Blending tools need paint to blend.** Blender and smudge only move what is on the target layer: blend on the layer you painted, not on an empty one above it.
416
+ - **A dirty brush stays dirty** across strokes and colour changes until `clean_mixer_brush`. Pick-up lifts only paint on the target layer: the paper and other layers lift nothing.
417
+ - **Replays use the recorded load**: a replayed pick-up stroke starts with what the brush carried when it was painted, not with the current load.
459
418
  - **Big soft brushes at full flow look flat.** Lower `flow` (airbrush 0.05..0.15) and build up.
460
419
  - **JPEG and WebP need a browser**; in Node use PNG. Exports never upscale (`scale` ≤ 1).
461
420
  - **Symmetry stays on until you turn it off.** Set `mode: "off"` after the symmetric part, or
@@ -20,6 +20,9 @@ more; this is where each one went.
20
20
  | Bristles | `tip.shape` `bristle` and `tip.bristle` |
21
21
  | A brush's own gradient (paint) | `colour.default` (a gradient); `unset: ['colour']` drops it |
22
22
  | Light | `light` (`amount`, `pressure`) |
23
+ | UV-fluorescent ink (glows under `set_uv_light`, optional invisible ink) | `fluorescence` (`amount` 0..1, `invisible`); `unset: ['fluorescence']` drops it |
24
+ | Thermochromic ink (shifts toward `hotColor` where `paint_heat` warms the canvas) | `thermochromic` (`hotColor` `#rrggbb`, `threshold` 0.05..0.95); `unset: ['thermochromic']` drops it |
25
+ | Thin-film iridescence | `iridescence` (`thickness` 100..1000 nm, `pressureToThickness` 0..1, `ior` 1.2..2.0); `unset: ['iridescence']` drops it |
23
26
  | Ghost paint (linger, smear) | `ghost` (`linger`, `smear`); `unset: ['ghost']` drops it |
24
27
 
25
28
  ## Examples
@@ -0,0 +1,27 @@
1
+ # Evolutionary strokes: breeding and mutating variants
2
+
3
+ Every dry stroke painted in the editor is recorded as a `RecordedStroke` genome: its raw points (with pressure, timing and tilt), its brush facets (`tip`, `dynamics`, `behaviour`, `marks`, `motion`, `colour`), its paint colour and its deterministic `seed`.
4
+
5
+ Rather than guessing facet knobs by hand, you can **breed** a batch of variants from one or more painted strokes, compare their ghost previews and diff summaries, cross two favourites over, or accept one onto a new layer in a single undo step.
6
+
7
+ ## Workflow
8
+
9
+ 1. **Pick parent strokes.** Call `list_strokes` (or use the `strokeId` returned by `paint_stroke` / `stroke_path`). Only dry strokes that `liveStrokeRefusal` permits can be bred — wet, impasto, sticker, eraser, blender, smudge, light and pick-up brushes are refused with the reason.
10
+ 2. **Breed a generation.** Call `breed_strokes` with `strokeIds` (`1..16`), `count` (`2..12`, default `4`), optional `mutation` rates (`geometry`, `color`, `brush`, `seed`, each `0..1`) and an optional deterministic `seed`.
11
+ - Each variant is added as a pending paint proposal (`kind: "strokes"`) rendered through `previewPaint` one after another.
12
+ - The result lists `proposalIds` and `proposals`, each with `proposalId`, `title`, `seed`, `bounds`, a human-readable `diff` summary (`"geometry ±4.2px, scale 0.96×; colour ΔL -0.03, Δh +8°; brush tip.size=18.4, tip.hardness=0.62; seed → 1849201"`) and `changes` tags.
13
+ 3. **Evolve further or cross over.** Call `breed_proposal` with `proposalIds`:
14
+ - **1 proposal id**: mutates that proposal's genome into `count` offspring.
15
+ - **2 proposal ids**: crosses the two parent genomes over — resampling corresponding stroke point paths and mixing their OKLCH colours and numeric brush facets — then mutates each child.
16
+ 4. **Inspect or accept.**
17
+ - Call `get_proposal_genome` with `proposalId` to read the full stroke records (`brush`, `color`, `points`, `seed`) if you want to save the mutated brush with `create_brush`.
18
+ - Call `accept_paint_proposal` to paint the chosen variant onto a new layer as a single undo step, or `reject_paint_proposal` to dismiss variants you don't want.
19
+
20
+ ## Mutation channels (`mutation`)
21
+
22
+ | Channel | Default | What it mutates |
23
+ | --- | --- | --- |
24
+ | `geometry` | `0.35` | Seeded `varyStroke` transform (small offset, rotation, scale and pressure scaling) plus smoothed 3-tap point jitter and pressure drift. Set to `0` to keep the exact path. |
25
+ | `color` | `0.25` | Perceptual drift in OKLCH (`L` ±0.18, `C` ±0.08, `h` ±45° at rate `1.0`), clamped to gamut. Set to `0` to keep the exact colour. |
26
+ | `brush` | `0.3` | Drifts numeric brush facets present on the stroke within their schema bounds (`tip.size`, `tip.hardness`, `tip.spacing`, `tip.roundness`, `tip.angle`, `tip.opacity`, `tip.flow`, `dynamics.sizeJitter`, `dynamics.opacityJitter`, `dynamics.scatter`, `dynamics.angleJitter`, `behaviour.grain`, plus active `marks` particle/bristle/chalk fields). Set to `0` to keep the brush unchanged. |
27
+ | `seed` | `0.5` | Probability of rerolling each stroke's deterministic RNG `seed` so grain, scatter and bristle/particle marks vary even when `geometry`, `color` and `brush` are `0`. |
@@ -0,0 +1,25 @@
1
+ # Mutation paint (`mutation` facet)
2
+
3
+ Mutation paint is an exchange between the person at the canvas and an agent. The user paints one or more strokes with a `mutation` brush (`mutation-wash` for wet watercolour, `mutation-ink` for dry ink, or any brush with a `mutation` facet) and pauses. After `settle` seconds (default 1.5 s), those strokes form one pending **mutation** (`m1`, `m2`, …). When an agent claims it with `read_mutation`, the user's paint sinks away into the paper over `fade` seconds (default 2 s) and the agent paints its own answer back with `answer_mutation`.
4
+
5
+ If no agent claims the mutation before `patience` seconds (default 45 s), or an agent calls `decline_mutation`, nothing is lost: the user's strokes stay on the canvas as ordinary paint.
6
+
7
+ ## Facet settings
8
+
9
+ | Facet path | Engine path | Range | Default | Meaning |
10
+ | --- | --- | --- | --- | --- |
11
+ | `mutation.settle` | `mutation.settle` | `0.5..10` s | `1.5` | Seconds after the user's last mutation stroke before the mutation is ready to claim |
12
+ | `mutation.patience` | `mutation.patience` | `5..300` s | `45` | Seconds to wait for an agent to claim the mutation before keeping the paint as ordinary paint |
13
+ | `mutation.fade` | `mutation.fade` | `0.5..10` s | `2` | Seconds the user's paint takes to sink away once claimed |
14
+
15
+ ## How wet and dry mutation strokes work
16
+
17
+ - **Wet mutation strokes (`mutation-wash`):** Under the hood, a wet mutation brush is a ghost brush (`ghost.trigger: "agent"`, `ghost.linger: mutation.fade`). While the mutation is waiting to be claimed, the layer's evaporation is held at `0` so the wash stays wet even if the model takes several seconds to reply. When `read_mutation` claims it, evaporation is restored and the ghost fade is released: the user's pigment subtracts away over `fade` seconds while any smear it made in existing wet or dry paint remains. If unclaimed or declined, `keepGhost` drops the ghost tracer without subtracting pigment, so the wash dries and bakes as ordinary watercolour.
18
+ - **Dry mutation strokes (`mutation-ink`):** Dry paint cannot be subtracted from a layer after other paint lands on it, so dry mutation strokes are routed to a hidden temporary pixel layer named `"Mutation"` directly above the active layer (`activeLayerId` stays on the target layer). When `read_mutation` claims it, the temporary layer's opacity fades to `0` over `fade` seconds and the layer (along with its recorded strokes) is silently removed without adding an undo step. If unclaimed, declined, or saved before claim, the temporary layer merges down into the target layer.
19
+
20
+ ## The agent exchange (`pixerate-paint-mutation`)
21
+
22
+ 1. **Discover (`list_mutations`):** Lists pending mutations (`id`, `layerId`, `medium`, `bounds`, `age`, `state`, `strokes`).
23
+ 2. **Claim and inspect (`read_mutation`):** Claims `id` (starting its fade immediately) and returns the user's strokes (points, colour, brush), a PNG crop around them (`cropPng`), and a whole-canvas preview (`canvasPng`). Refuses if another agent already claimed it.
24
+ 3. **Answer (`answer_mutation`):** Paints the agent's response (`id`, `strokes`, optional `layerId` and `note`). Strokes use the same shape as `propose_paint` / `PaintAgentCommand` (`brush`, `color`, `points`) and run through `PaintDirector` so the user watches the agent's cursor paint live. Commits as one undo step with origin `agent:<id>`. Refuses a second answer to the same `id`.
25
+ 4. **Decline (`decline_mutation`):** Releases the mutation without answering so the user's paint stays as ordinary paint.
@@ -15,5 +15,12 @@
15
15
  long stroke (dry-brush ends); `thinner` makes paint flatter and more transparent.
16
16
  - **Light** is a view of the relief, not paint: move it (`set_lighting`) to read the texture
17
17
  (a low elevation, 15..30°, shows brushwork best) and pick the final light before exporting.
18
- `get_relief` reads a layer's relief; `flatten_relief` removes it but keeps the colour.
18
+ `get_relief` reads a layer's relief; `flatten_relief` removes it (and any thin film) but keeps the colour.
19
+ - **Structural colour (thin-film iridescence)**: `iridescence: { thickness: 100..1000, pressureToThickness: 0..1, ior: 1.2..2.0 }`
20
+ lays a dielectric thin film (`ThickChannels.film`) over thick or flat paint. Its interference
21
+ colour comes from the optical path difference $2 \cdot n \cdot d \cdot \cos(\theta_t)$ between the surface normal,
22
+ light direction (`set_lighting`) and view vector (`set_view_tilt` / `get_view_tilt`, or device
23
+ gyroscope tilt on iPhone and iPad). Presets: `scarab-lacquer` (thick metallic beetle shell),
24
+ `soap-film` (sheer rainbow wash whose hue follows pen pressure via `pressureToThickness`) and
25
+ `pearl-glaze` (soft nacreous lustre on flat or raised paint).
19
26
  - **Metallic paper**: `set_paper` `metallic` 0..1 makes the grain sparkle under the light.
@@ -0,0 +1,51 @@
1
+ # UV-fluorescent and thermochromic smart inks
2
+
3
+ Two smart inks extend any dab, bristle, particle or wet brush with per-layer channels that react to
4
+ the virtual UV blacklight (`uvLight`) or the document's thermal heat field (`heat`).
5
+
6
+ ## UV-fluorescent ink (`fluorescence`)
7
+
8
+ - **Facet**: `fluorescence: { amount: 0..1, invisible?: boolean }` (or a bare number `0..1` as a
9
+ shorthand for `{ amount }` in brush specs). Preset: `uv-marker` (`amount: 0.9, invisible: true`,
10
+ cyan `#00ffd5`).
11
+ - **How it paints**: each stroke writes its fluorescent emission (`[r, g, b, alpha * amount]`) into
12
+ the target layer's `doc.fluor[layerId]` surface in layer space.
13
+ - With `invisible: false` (default), the stroke lays normal daylight pigment onto the layer's
14
+ surface **and** stores UV emission, like a fluorescent highlighter that is visible in daylight
15
+ and glows under blacklight.
16
+ - With `invisible: true`, no daylight pigment is written to the layer's surface — the stroke is
17
+ completely invisible under normal light (`uvLight.on: false`) and only appears when the UV
18
+ blacklight is switched on.
19
+ - **Virtual UV blacklight** (`set_uv_light`, `get_uv_light`):
20
+ - `on`: switches the blacklight on or off (default `false`).
21
+ - `strength` (`0..2`, default `1`): scales both the direct UV emission on the layer and the
22
+ Linear Dodge (`id::uv`) halo spill around fluorescent strokes.
23
+ - `ambient` (`0..1`, default `0.15`): dims the paper and all normal daylight paint to `ambient`
24
+ when `on` is `true` so fluorescent strokes pop against the darkened room.
25
+
26
+ ## Thermochromic ink (`thermochromic`)
27
+
28
+ - **Facet**: `thermochromic: { hotColor: '#rrggbb' | null, threshold?: 0.05..0.95 }` (default
29
+ `threshold: 0.5`). Preset: `thermo-ink` (`hotColor: '#ff7a18', threshold: 0.45`, cold `#1e2a78`).
30
+ - **How it paints**: each stroke lays its cold colour onto the layer as usual and writes its target
31
+ `hotColor` RGB and `threshold` into `doc.thermo[layerId]` where the stroke's alpha lands.
32
+ - **Document heat field** (`paint_heat`, `advance_heat`, `clear_heat`, `get_heat`):
33
+ - Stored on a 1/4-resolution grid across the canvas (`0..1` temperature).
34
+ - `paint_heat` warms (or with `temperature: 0` cools) along `points` with a smooth falloff of
35
+ `radius` document pixels (default `24`). One undo step.
36
+ - `advance_heat` runs `steps` (default `10`) of 5-point Laplacian diffusion + Newtonian cooling
37
+ so warm spots spread outward and gradually cool back to `0`. Not an undo step.
38
+ - `clear_heat` cools the entire canvas to `0` at once (one undo step when warm).
39
+ - At render time, thermochromic pixels smoothly interpolate from their cold colour to `hotColor`
40
+ in a `±0.15` band around `threshold`.
41
+ - **Wet-paint interaction**: any wet paint under hot cells (`temperature >= 0.5`) dries in place
42
+ automatically when `paint_heat` or `advance_heat` warms it.
43
+
44
+ ## Lifecycle
45
+
46
+ - Both per-layer channels (`fluor` and `thermo`) follow the layer through `duplicate_layer`,
47
+ `merge_layer_down`, `clear_layer`, `remove_layer`, `extend_canvas` (which also shifts the heat
48
+ field) and symmetry guides, and are saved in paint documents (`fluor`, `thermo`, `heat`,
49
+ `uvLight`).
50
+ - Stroke layers (`type: "strokes"`) refuse fluorescent and thermochromic brushes; paint them on a
51
+ regular pixel layer.
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: pixerate-paint-mutation
3
+ description: The mutation paint workflow in the Pixerate paint editor - discovering pending mutation strokes after the user pauses, claiming them with read_mutation so the user's wet or dry paint sinks into the canvas, reading the local crop and whole-canvas preview to choose a placement and medium, and painting an answering gesture back with answer_mutation or leaving the mark as ordinary paint with decline_mutation. Use when asked to answer a mutation stroke, respond to mutation paint, or when list_mutations, read_mutation, answer_mutation or decline_mutation are available. Load with pixerate-paint-editor, which lists every tool.
4
+ metadata:
5
+ kind: workflow
6
+ reference: pixerate-paint-editor
7
+ ---
8
+
9
+ # Answering mutation paint
10
+
11
+ With a `mutation` brush (`mutation-wash` for wet watercolour, `mutation-ink` for dry ink, or any brush with a `mutation` facet), the user paints a mark and pauses. Once `settle` seconds pass, the strokes form a pending **mutation**. Claiming it with `read_mutation` starts the fade so the user's paint sinks away into the paper while you read it; `answer_mutation` then sends your cursor onto the canvas to paint an answer back as a single undo step. If you decline with `decline_mutation` (or never claim it), the user's paint stays as ordinary paint.
12
+
13
+ This skill is the method. `pixerate-paint-editor` has the full tool reference and `references/mutation.md` the facet details.
14
+
15
+ ## Goal and quality bar
16
+
17
+ - Every pending mutation you handle is either claimed (`read_mutation`) and answered (`answer_mutation`) or explicitly left as ordinary paint (`decline_mutation`), leaving `list_mutations` empty (`mutations` length 0).
18
+ - You always call `read_mutation` before `answer_mutation` so the fade begins and you see both the stroke crop and the whole canvas.
19
+ - The answer reinterprets or converses with the user's mark rather than tracing its exact points, and commits as one undo step.
20
+ - Because the user is watching the canvas while their stroke sinks away, keep your tool sequence short and direct.
21
+
22
+ ## Inputs to confirm
23
+
24
+ Ask only when the host task is ambiguous; otherwise act immediately because the person is watching the canvas.
25
+
26
+ - **Whether to answer or keep the mark:** answer with `read_mutation` followed by `answer_mutation` by default; call `decline_mutation` when the mark should stay untouched as ordinary paint.
27
+ - **Where and how to answer:** read `canvasPng` and `cropPng` from `read_mutation` to choose the layer, brush, colour and placement yourself.
28
+
29
+ ## Working method
30
+
31
+ 1. **Find pending mutations.** Call `list_mutations` to see the pending mutations (`id`, `layerId`, `medium`, `bounds`, `age`, `state`, `strokes`).
32
+ **Check:** `list_mutations` lists at least one mutation with `state` `"pending"` or `"settling"`.
33
+ 2. **Claim or decline.**
34
+ - If the mark should stay as ordinary paint without an answer, call `decline_mutation` with `id` and stop.
35
+ - Otherwise, call `read_mutation` with `id` immediately. This claims the mutation, starts its fade (`fade` seconds), and returns the user's `strokes` (points, colour, brush), `cropPng` (around the mark) and `canvasPng` (the whole painting).
36
+ **Check:** `read_mutation` returns `state: "claimed"`, `strokes`, `cropPng` and `canvasPng`.
37
+ 3. **Plan the answer from the preview.** Inspect `strokes`, `cropPng` and `canvasPng`:
38
+ - Notice the user's gesture (direction, rhythm, scale, colour, `medium`) and what is already on the canvas.
39
+ - Choose where your answer belongs (near the mark, completing a form, answering across the sheet, or on a specific `layerId`), which brush and colour suit it, and a handful of expressive strokes. Reinterpret rather than copying the user's points verbatim.
40
+ 4. **Paint the answer.** Call `answer_mutation` with `id`, `strokes` (each with `points`, optional `brushId`, `brush` facets and `color`), and optional `layerId` and `note`. When the stroke is wet (`medium: "wet"`), call `advance_wet_paint` afterwards if you need the fade and wet answer to settle in headless runs.
41
+ **Check:** `answer_mutation` returns `state: "answered"` and non-empty `strokeIds`; `list_mutations` now shows no pending mutations.
42
+
43
+ ## Decision table
44
+
45
+ | Situation | Approach |
46
+ | --- | --- |
47
+ | Wet mutation (`medium: "wet"`, e.g. `mutation-wash`) | `read_mutation` releases the evaporation hold and ghost fade; answer with a wet or dry brush via `answer_mutation`, then `advance_wet_paint` if waiting for the fade to finish |
48
+ | Dry mutation (`medium: "dry"`, e.g. `mutation-ink`) | `read_mutation` fades the temporary mutation layer; answer on the target layer with `answer_mutation` |
49
+ | Several pending mutations in `list_mutations` | Handle each `id` in turn (`read_mutation` then `answer_mutation`), or `decline_mutation` any you should leave as ordinary paint |
50
+ | The mark is already complete or shouldn't be taken away | Call `decline_mutation` with `id` (before `read_mutation`) so the user's paint stays on the canvas |
51
+ | Another agent already claimed the mutation | `read_mutation` refuses; skip that `id` |
52
+
53
+ ## Recipes
54
+
55
+ **Answer a wet wash mutation.** `list_mutations` → `read_mutation` `{ id: "m1" }` → `answer_mutation` `{ id: "m1", strokes: [{ brushId: "watercolor-round", brush: { tip: { size: 22 } }, color: "#1f3a5f", points: [...] }] }` → `advance_wet_paint` `{ seconds: 3 }`.
56
+
57
+ **Answer a dry ink mutation.** `list_mutations` → `read_mutation` `{ id: "m1" }` → `answer_mutation` `{ id: "m1", strokes: [{ brushId: "ink-pen", brush: { tip: { size: 8 } }, color: "#1e293b", points: [...] }] }`.
58
+
59
+ **Keep a mutation as ordinary paint.** `list_mutations` → `decline_mutation` `{ id: "m1", reason: "Keep as drawn" }`.
60
+
61
+ ## Pitfalls
62
+
63
+ - **Calling `answer_mutation` before `read_mutation`.** `answer_mutation` refuses unless the mutation has been claimed with `read_mutation` first.
64
+ - **Claiming when you intend to decline.** `read_mutation` starts the fade immediately; once claimed, `decline_mutation` is refused. Decide before claiming, or call `decline_mutation` directly.
65
+ - **Answering with a mutation brush.** Do not pass `mutation-wash` or `mutation-ink` as `brushId` in `answer_mutation`; use an ordinary wet or dry brush (`watercolor-round`, `ink-pen`, `pencil-hb`, `gouache`…) so the answer stays on the canvas.
66
+ - **Copying the user's stroke point-for-point.** The user's mark is a prompt or gesture that disappears; answer in your own way rather than replaying the same coordinates.
67
+ - **Taking many slow tool calls between claim and answer.** The fade starts at `read_mutation`; go straight from `read_mutation` to `answer_mutation`.
68
+
69
+ ## Tools used
70
+
71
+ - `list_mutations`: list pending mutations waiting for an agent
72
+ - `read_mutation`: claim a mutation, start its fade, and inspect its strokes, local crop and canvas preview
73
+ - `answer_mutation`: paint the answering strokes as one undo step
74
+ - `decline_mutation`: leave a pending mutation on the canvas as ordinary paint
75
+ - `advance_wet_paint`, `get_wet_paint`: let wet fades and wet answers flow in headless sessions
76
+ - `get_canvas_info`, `list_layers`, `list_strokes`: check the canvas, layers and recorded strokes
@@ -0,0 +1,58 @@
1
+ {
2
+ "prompt": "Decline the pending mutation stroke so the user's dry ink mark stays on the active layer as ordinary paint without an agent answer.",
3
+ "fixture": {
4
+ "editor": "paint",
5
+ "width": 200,
6
+ "height": 200,
7
+ "setup": [
8
+ { "tool": "set_brush", "args": { "id": "mutation-ink" } },
9
+ {
10
+ "tool": "paint_stroke",
11
+ "args": {
12
+ "color": "#0f172a",
13
+ "points": [
14
+ { "x": 40, "y": 50, "pressure": 0.8 },
15
+ { "x": 150, "y": 150, "pressure": 0.8 }
16
+ ]
17
+ }
18
+ },
19
+ { "tool": "advance_wet_paint", "args": { "seconds": 2 } }
20
+ ]
21
+ },
22
+ "checks": [
23
+ {
24
+ "tool": "list_mutations",
25
+ "expect": { "mutations": { "$length": 0 } },
26
+ "why": "No pending mutations remain"
27
+ },
28
+ {
29
+ "tool": "list_mutations",
30
+ "args": { "includeAll": true },
31
+ "expect": {
32
+ "mutations": [
33
+ {
34
+ "id": "m1",
35
+ "state": "declined"
36
+ }
37
+ ]
38
+ },
39
+ "why": "Mutation m1 was declined"
40
+ },
41
+ {
42
+ "tool": "list_layers",
43
+ "expect": {
44
+ "layers": [
45
+ {
46
+ "name": "Layer 1",
47
+ "bounds": { "width": { "$min": 50 } }
48
+ }
49
+ ]
50
+ },
51
+ "why": "The temporary mutation layer merged down into Layer 1 so the user's paint stayed"
52
+ }
53
+ ],
54
+ "solution": [
55
+ { "tool": "list_mutations" },
56
+ { "tool": "decline_mutation", "args": { "id": "m1", "reason": "Keep the user's mark as ordinary paint" } }
57
+ ]
58
+ }