@pixerate/editor 0.273.0 → 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.
- package/dist/paint-editor/collaboration.d.cts +6 -1
- package/dist/paint-editor/collaboration.d.ts +6 -1
- package/dist/paint-editor/controller.d.cts +31 -0
- package/dist/paint-editor/controller.d.ts +31 -0
- package/dist/paint-editor/evolution-tools.d.cts +48 -0
- package/dist/paint-editor/evolution-tools.d.ts +48 -0
- package/dist/paint-editor/evolution.d.cts +175 -0
- package/dist/paint-editor/evolution.d.ts +175 -0
- package/dist/paint-editor/index.cjs +5330 -4481
- package/dist/paint-editor/index.d.cts +2 -0
- package/dist/paint-editor/index.d.ts +2 -0
- package/dist/paint-editor/index.js +8502 -7653
- package/dist/paint-editor/ui.d.cts +2 -0
- package/dist/paint-editor/ui.d.ts +2 -0
- package/package.json +1 -1
- package/skills/README.md +1 -0
- package/skills/pixerate-brushes/SKILL.md +3 -0
- package/skills/pixerate-paint-editor/SKILL.md +36 -53
- package/skills/pixerate-paint-editor/references/brush-facets.md +3 -0
- package/skills/pixerate-paint-editor/references/mutation.md +25 -0
- package/skills/pixerate-paint-editor/references/oils.md +8 -1
- package/skills/pixerate-paint-editor/references/smart-inks.md +51 -0
- package/skills/pixerate-paint-mutation/SKILL.md +76 -0
- package/skills/pixerate-paint-mutation/evals/decline-unanswered.json +58 -0
- package/skills/pixerate-paint-mutation/evals/dry-ink-answer.json +74 -0
- package/skills/pixerate-paint-mutation/evals/wet-wash-answer.json +76 -0
- package/skills/pixerate-paint-oil-impasto/SKILL.md +2 -1
|
@@ -263,6 +263,8 @@ export declare const PATH_STROKE_TAPERS: ReadonlyArray<{
|
|
|
263
263
|
export declare const STROKE_SLIDERS: readonly SliderControl[];
|
|
264
264
|
/** The sticker panel's sliders for the selected sticker. */
|
|
265
265
|
export declare const STICKER_SLIDERS: readonly SliderControl[];
|
|
266
|
+
/** The evolution panel's mutation sliders (`0..1`). */
|
|
267
|
+
export declare const EVOLUTION_SLIDERS: readonly SliderControl[];
|
|
266
268
|
/** Stencils a toolbar can lay with one click, centred on the canvas. */
|
|
267
269
|
export declare function quickStencil(kind: 'ruler' | 'circle' | 'ellipse-mask' | 'edge-mask', canvas: {
|
|
268
270
|
width: number;
|
|
@@ -263,6 +263,8 @@ export declare const PATH_STROKE_TAPERS: ReadonlyArray<{
|
|
|
263
263
|
export declare const STROKE_SLIDERS: readonly SliderControl[];
|
|
264
264
|
/** The sticker panel's sliders for the selected sticker. */
|
|
265
265
|
export declare const STICKER_SLIDERS: readonly SliderControl[];
|
|
266
|
+
/** The evolution panel's mutation sliders (`0..1`). */
|
|
267
|
+
export declare const EVOLUTION_SLIDERS: readonly SliderControl[];
|
|
266
268
|
/** Stencils a toolbar can lay with one click, centred on the canvas. */
|
|
267
269
|
export declare function quickStencil(kind: 'ruler' | 'circle' | 'ellipse-mask' | 'edge-mask', canvas: {
|
|
268
270
|
width: number;
|
package/package.json
CHANGED
package/skills/README.md
CHANGED
|
@@ -52,6 +52,7 @@ request like "clean up this export" finds it.
|
|
|
52
52
|
| [`pixerate-paint-watercolour`](pixerate-paint-watercolour/SKILL.md) | Paint (`pixerate-paint-editor`) | Watercolour paper, light to dark with the paper's white kept, wet-in-wet and wet-on-dry washes, drying before glazing, steering the water. |
|
|
53
53
|
| [`pixerate-paint-oil-impasto`](pixerate-paint-oil-impasto/SKILL.md) | Paint (`pixerate-paint-editor`) | A toned ground, thin block-in to thick paint, pigment mixing, impasto relief and its light. |
|
|
54
54
|
| [`pixerate-paint-sketch-to-finish`](pixerate-paint-sketch-to-finish/SKILL.md) | Paint (`pixerate-paint-editor`) | Sketch, ink, flats under the lines, clipped shading and light, one named layer per pass. |
|
|
55
|
+
| [`pixerate-paint-mutation`](pixerate-paint-mutation/SKILL.md) | Paint (`pixerate-paint-editor`) | Claiming a user's mutation stroke (`read_mutation`) so it fades away, reading the local crop and canvas preview, and painting an answer back (`answer_mutation`) or keeping it (`decline_mutation`). |
|
|
55
56
|
| [`pixerate-brushes-design`](pixerate-brushes-design/SKILL.md) | Brushes (`pixerate-brushes`) | From a description to a saved brush: the nearest preset, only the facets that change, checked for the host that will use it, previewed. |
|
|
56
57
|
| [`pixerate-video-social-cut`](pixerate-video-social-cut/SKILL.md) | Video (`pixerate-video-editor`) | Vertical or square reframes that fill the frame, the hook first, platform lengths, titles in the safe area. |
|
|
57
58
|
| [`pixerate-video-colour-grade`](pixerate-video-colour-grade/SKILL.md) | Video (`pixerate-video-editor`) | Correct and match shots, then one look on an adjustment clip; LUTs at moderate strength with skin protected. |
|
|
@@ -20,6 +20,9 @@ built-in presets (read-only) and the user's brushes, shared by every editor that
|
|
|
20
20
|
| Dynamics | Pen feel: pressure to size and opacity, smoothing, tilt, velocity, jitter (`sizeJitter`, `angleJitter` for rotation, `opacityJitter`, `scatter`), rotation, `count` (dabs per spacing step, each jittered) and `flip` (mirror dabs at random: `x`, `y`, `xy`). |
|
|
21
21
|
| Behaviour | `surface` `dry`, `wet` (`wet` settings) or `thick` (`thick` impasto settings); paper `grain`; `exchange` (pick-up and mix; a `reservoir` makes a Mixer Brush); tool `strength`. |
|
|
22
22
|
| Light | Light the brush gives off (`amount`), plus suggested layer `spill` and `glow`. |
|
|
23
|
+
| Fluorescence | UV-fluorescent ink (`amount` 0..1, `invisible` true for invisible ink that only appears under the paint editor's UV blacklight; preset `uv-marker`). |
|
|
24
|
+
| Thermochromic | Heat-reactive ink (`hotColor` `#rrggbb`, `threshold` 0.05..0.95) that shifts colour where the paint editor's heat brush warms the canvas (preset `thermo-ink`). |
|
|
25
|
+
| Iridescence | Structural thin-film interference (`thickness` 100..1000 nm, `pressureToThickness` 0..1, `ior` 1.2..2.0) whose hue shifts with light and view tilt (`scarab-lacquer`, `soap-film`, `pearl-glaze`). |
|
|
23
26
|
| Ghost | On wet paint (`behaviour.surface: "wet"`): `linger` (0.5..60 s before the brush's own colour fades away) and `smear` (0..1, how much it lifts and drags the paint it crosses). Presets `ghost-wash`, `ghost-gouache`, `ghost-oil` and `invisible-ink`. |
|
|
24
27
|
| Motion | Animation after painting (live effect paint, particle `waveSpeed`). |
|
|
25
28
|
| Colour | A `default` colour, a `palette` of up to 16, `own` (always its own colour). Each is a hex or `rgba()` colour, a light / dark pair `{ light, dark }`, a gradient or effect paint. A gradient default is always painted; a solid or light / dark default becomes the editor's colour when the brush is chosen (unless the user locked theirs), resolved for the colour scheme. Canvas strokes and vector brushstrokes take it too, when they are given no colour. Canvas and vector have the same lock (`canvas.setColorLocked`, `vector.setColorLocked`), and a canvas colour taken from a light / dark default follows the scheme like the paint editor's; vector strokes already made keep theirs. |
|
|
@@ -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
|
|
|
@@ -101,20 +102,16 @@ the pitfalls, and uses the tools below.
|
|
|
101
102
|
- Mixer Brush: blend with flow 0.3..0.6; at full flow it paints only its own colour.
|
|
102
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
|
|
103
104
|
`clean_mixer_brush`). Keep `pickup` 0 for clean, predictable colour.
|
|
104
|
-
- **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
|
|
105
|
-
`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).
|
|
106
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).
|
|
107
107
|
- **Painterly layer modes do the glazing for you** (`update_layer` `blend`, or a brush tip):
|
|
108
|
-
- `watercolor`:
|
|
109
|
-
- `tint`: recolours what is below (
|
|
110
|
-
- `highlight`: adds coloured light that rolls off towards white
|
|
111
|
-
- `shadow`: shades as if light were blocked, coloured by the layer
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
- **
|
|
115
|
-
`set_canvas` only crops or grows at the bottom-right.
|
|
116
|
-
- **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
|
|
117
|
-
`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.
|
|
118
115
|
|
|
119
116
|
Which facet sets which engine setting: [references/brush-facets.md](references/brush-facets.md).
|
|
120
117
|
|
|
@@ -122,11 +119,10 @@ Which facet sets which engine setting: [references/brush-facets.md](references/b
|
|
|
122
119
|
|
|
123
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).
|
|
124
121
|
|
|
125
|
-
## Oils and
|
|
122
|
+
## Oils, thick paint and smart inks
|
|
126
123
|
|
|
127
|
-
Wet oil blends where the brush meets it; impasto adds relief lit by `set_lighting`: load
|
|
128
|
-
`
|
|
129
|
-
(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).
|
|
130
126
|
|
|
131
127
|
## Gradient paint
|
|
132
128
|
|
|
@@ -170,39 +166,15 @@ it work with the painting. Search first: `search_effects` with `use` `generator`
|
|
|
170
166
|
fills), `overlay` / `title` (layers, fills) or `filter` (light on paint), then `describe_effect`
|
|
171
167
|
for the controls and presets.
|
|
172
168
|
|
|
173
|
-
- **Effect paper**: `set_paper_effect` draws a generator over the sheet, under every layer.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
- **Effect layers** (`add_effect_layer`): a pixel layer drawn by a generator, overlay or title
|
|
178
|
-
effect. Use them for backgrounds you want to change later, and for overlays (grain, light
|
|
179
|
-
leaks, captions) above the paint with a blend mode (`screen` / `linear-dodge` for light,
|
|
180
|
-
`multiply` for textures). `update_effect` draws the layer again and replaces its pixels, paint
|
|
181
|
-
on it included, so paint on a layer above it.
|
|
182
|
-
- **Effect fills** (`fill_layer_with_effect`): the effect composited onto a layer's pixels with
|
|
183
|
-
no link kept; `lockTransparency` colours only what is painted (an effect inside a shape).
|
|
184
|
-
- **Light on dried paint** (`apply_effect`): a filter (light, glow, grade) run on a layer's paint
|
|
185
|
-
and baked in, at `opacity` strength. Transparent areas stay transparent, and thick paint keeps
|
|
186
|
-
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.
|
|
187
173
|
- **Effect paint** (`set_effect_paint`): brushes paint with the effect itself; see below.
|
|
188
174
|
|
|
189
175
|
## Effect paint
|
|
190
176
|
|
|
191
|
-
`set_effect_paint` makes a generator, overlay or title effect the paint: rendered once, every
|
|
192
|
-
stroke takes each pixel's colour from it, fixed to the canvas (painting reveals a hidden image).
|
|
193
|
-
Its transparency paints nothing.
|
|
194
|
-
|
|
195
|
-
1. `search_effects` (`use` generator, overlay or title) and `describe_effect` for the controls.
|
|
196
|
-
2. `set_effect_paint` with `effect`, `values`, `time` (ms, default 0). Wait for it to return.
|
|
197
|
-
3. Paint: dab, particle and bristle brushes take the colour per pixel; the Mixer Brush and pick-up
|
|
198
|
-
dab brushes take it per dab at its centre. Wet and impasto brushes take it per pixel, shifted by
|
|
199
|
-
paint they pick up (in watercolour and gouache it flows with the water; impasto keeps its relief).
|
|
200
|
-
Sprayed stickers each take the colour at their centre (they stay recolourable).
|
|
201
|
-
4. `set_color`, `set_paint` or `remove_effect` (target and effect `paint`) go back to colour;
|
|
202
|
-
`update_effect` (target and effect `paint`, `values`) renders new values.
|
|
203
|
-
5. Along each stroke (`map`) or playing on stroke layers (`live`): [references/effect-paint.md](references/effect-paint.md).
|
|
204
|
-
|
|
205
|
-
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).
|
|
206
178
|
|
|
207
179
|
| Want | Use |
|
|
208
180
|
| --- | --- |
|
|
@@ -214,8 +186,7 @@ When to use which:
|
|
|
214
186
|
|
|
215
187
|
## Canvas-hosted layer
|
|
216
188
|
|
|
217
|
-
When `get_canvas_info` has `bounds`, the painting is a layer of a larger canvas and every position
|
|
218
|
-
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).
|
|
219
190
|
|
|
220
191
|
## Tool reference
|
|
221
192
|
|
|
@@ -231,9 +202,9 @@ is a canvas coordinate: read [references/canvas-hosted.md](references/canvas-hos
|
|
|
231
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). |
|
|
232
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. |
|
|
233
204
|
|
|
234
|
-
### Suggesting paint
|
|
205
|
+
### Suggesting and answering paint
|
|
235
206
|
|
|
236
|
-
Contributing while the user paints: suggest
|
|
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`).
|
|
237
208
|
|
|
238
209
|
| Tool | Use |
|
|
239
210
|
| --- | --- |
|
|
@@ -241,6 +212,10 @@ Contributing while the user paints: suggest, don't paint. Read [references/paint
|
|
|
241
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. |
|
|
242
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. |
|
|
243
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. |
|
|
244
219
|
|
|
245
220
|
### Layers
|
|
246
221
|
|
|
@@ -272,7 +247,7 @@ Listing, saving, sharing and previewing brushes are the brush library's tools (`
|
|
|
272
247
|
| Tool | Use |
|
|
273
248
|
| --- | --- |
|
|
274
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. |
|
|
275
|
-
| `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. |
|
|
276
251
|
|
|
277
252
|
### Colour
|
|
278
253
|
|
|
@@ -337,16 +312,24 @@ Listing, saving, sharing and previewing brushes are the brush library's tools (`
|
|
|
337
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`). |
|
|
338
313
|
| `set_instant_dry` | `on`: every wet stroke dries as it ends (same undo step); off (default) wet paint keeps moving. Returns `instantDry`. |
|
|
339
314
|
|
|
340
|
-
### Thick paint and
|
|
315
|
+
### Thick paint, light and smart inks
|
|
341
316
|
|
|
342
317
|
| Tool | Use |
|
|
343
318
|
| --- | --- |
|
|
344
|
-
| `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. |
|
|
345
322
|
| `get_relief` | Read a layer's relief (`bounds`, `maxHeight` 0..255, null when flat) and the lighting. Changes nothing. |
|
|
346
|
-
| `flatten_relief` | Remove a layer's relief and
|
|
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. |
|
|
347
324
|
| `get_light` | Read a layer's light paint: `hasLight`, `bounds`, `peak` 0..255 and its `spill`. Changes nothing. |
|
|
348
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. |
|
|
349
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. |
|
|
350
333
|
|
|
351
334
|
### Stickers
|
|
352
335
|
|
|
@@ -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,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
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Claim the pending dry ink mutation on the canvas, let its temporary layer fade out, and answer with a crimson (#dc2626) ink stroke.",
|
|
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": "#1e293b",
|
|
13
|
+
"points": [
|
|
14
|
+
{ "x": 30, "y": 80, "pressure": 0.8 },
|
|
15
|
+
{ "x": 110, "y": 80, "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": "answered"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"why": "Mutation m1 was claimed and answered"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"tool": "list_strokes",
|
|
43
|
+
"expect": {
|
|
44
|
+
"strokes": {
|
|
45
|
+
"$length": 1,
|
|
46
|
+
"$every": { "color": "#dc2626" }
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"why": "The temporary mutation stroke faded away and only the crimson answer stroke remains"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"solution": [
|
|
53
|
+
{ "tool": "list_mutations" },
|
|
54
|
+
{ "tool": "read_mutation", "args": { "id": "m1" } },
|
|
55
|
+
{
|
|
56
|
+
"tool": "answer_mutation",
|
|
57
|
+
"args": {
|
|
58
|
+
"id": "m1",
|
|
59
|
+
"strokes": [
|
|
60
|
+
{
|
|
61
|
+
"brushId": "ink-pen",
|
|
62
|
+
"brush": { "tip": { "size": 8 } },
|
|
63
|
+
"color": "#dc2626",
|
|
64
|
+
"points": [
|
|
65
|
+
{ "x": 60, "y": 40, "pressure": 0.7 },
|
|
66
|
+
{ "x": 140, "y": 140, "pressure": 0.8 }
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{ "tool": "advance_wet_paint", "args": { "seconds": 3 } }
|
|
73
|
+
]
|
|
74
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Claim the pending wet mutation stroke on the canvas, let it fade away, and paint a warm amber (#d97706) watercolour-round answer stroke across the canvas.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "paint",
|
|
5
|
+
"width": 200,
|
|
6
|
+
"height": 200,
|
|
7
|
+
"setup": [
|
|
8
|
+
{ "tool": "set_brush", "args": { "id": "mutation-wash" } },
|
|
9
|
+
{
|
|
10
|
+
"tool": "paint_stroke",
|
|
11
|
+
"args": {
|
|
12
|
+
"color": "#2563eb",
|
|
13
|
+
"points": [
|
|
14
|
+
{ "x": 40, "y": 100, "pressure": 0.8 },
|
|
15
|
+
{ "x": 120, "y": 100, "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": "answered",
|
|
36
|
+
"answerStrokeIds": { "$length": { "$min": 1 } }
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"why": "Mutation m1 was claimed and answered"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"tool": "list_strokes",
|
|
44
|
+
"expect": {
|
|
45
|
+
"strokes": {
|
|
46
|
+
"$some": { "color": "#d97706" }
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"why": "The warm amber answer stroke is recorded on the canvas"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"solution": [
|
|
53
|
+
{ "tool": "list_mutations" },
|
|
54
|
+
{ "tool": "read_mutation", "args": { "id": "m1" } },
|
|
55
|
+
{
|
|
56
|
+
"tool": "answer_mutation",
|
|
57
|
+
"args": {
|
|
58
|
+
"id": "m1",
|
|
59
|
+
"note": "Warm amber arc answering the blue wash",
|
|
60
|
+
"strokes": [
|
|
61
|
+
{
|
|
62
|
+
"brushId": "watercolor-round",
|
|
63
|
+
"brush": { "tip": { "size": 18 } },
|
|
64
|
+
"color": "#d97706",
|
|
65
|
+
"points": [
|
|
66
|
+
{ "x": 70, "y": 50, "pressure": 0.6 },
|
|
67
|
+
{ "x": 110, "y": 100, "pressure": 0.85 },
|
|
68
|
+
{ "x": 150, "y": 150, "pressure": 0.6 }
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
{ "tool": "advance_wet_paint", "args": { "seconds": 3 } }
|
|
75
|
+
]
|
|
76
|
+
}
|