@pixerate/editor 0.271.0 → 0.272.1

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 (96) hide show
  1. package/dist/agent/index.cjs +34 -32
  2. package/dist/agent/index.js +10 -8
  3. package/dist/animation-editor/index.cjs +60 -60
  4. package/dist/animation-editor/index.js +4 -4
  5. package/dist/brushes/index.cjs +69 -66
  6. package/dist/brushes/index.js +6 -3
  7. package/dist/brushes/patch.d.cts +1 -1
  8. package/dist/brushes/patch.d.ts +1 -1
  9. package/dist/brushes/schema.d.cts +1 -0
  10. package/dist/brushes/schema.d.ts +1 -0
  11. package/dist/brushes/types.d.cts +8 -0
  12. package/dist/brushes/types.d.ts +8 -0
  13. package/dist/{brushstrokeRender-WR2N5HHY.cjs → brushstrokeRender-5YBCLFDQ.cjs} +15 -15
  14. package/dist/{brushstrokeRender-A727TTNQ.js → brushstrokeRender-DZGFU3J4.js} +3 -3
  15. package/dist/canvas/index.cjs +13 -13
  16. package/dist/canvas/index.js +1 -1
  17. package/dist/chart/contract.d.cts +95 -0
  18. package/dist/chart/contract.d.ts +95 -0
  19. package/dist/chart/controller.d.cts +4 -0
  20. package/dist/chart/controller.d.ts +4 -0
  21. package/dist/chart/index.cjs +492 -26
  22. package/dist/chart/index.d.cts +1 -0
  23. package/dist/chart/index.d.ts +1 -0
  24. package/dist/chart/index.js +476 -10
  25. package/dist/chart/tools.d.cts +3 -0
  26. package/dist/chart/tools.d.ts +3 -0
  27. package/dist/chart-maps/index.cjs +4 -64
  28. package/dist/chart-maps/index.js +3 -63
  29. package/dist/{chunk-YNFSH72F.js → chunk-2F3YDXCN.js} +1 -1
  30. package/dist/{chunk-QYXUE37Q.js → chunk-2XDNF73E.js} +1 -1
  31. package/dist/{chunk-3KNQ2W6X.cjs → chunk-3JKCREEU.cjs} +59 -53
  32. package/dist/{chunk-NMZQMPFZ.js → chunk-4LZQO5Y7.js} +2 -0
  33. package/dist/{chunk-B5W677L7.js → chunk-5HP2VSAS.js} +1 -1
  34. package/dist/chunk-5UGRMVNE.js +328 -0
  35. package/dist/{chunk-X4FTNFIA.js → chunk-7MGPW2OV.js} +1 -1
  36. package/dist/{chunk-BPJSI4NT.cjs → chunk-7W2EQQBP.cjs} +2 -2
  37. package/dist/{chunk-DGITFN73.cjs → chunk-BWJBZAUE.cjs} +8 -8
  38. package/dist/chunk-CHZYVNXZ.js +336 -0
  39. package/dist/{chunk-LETF75CA.js → chunk-CLB2I2FU.js} +1 -1
  40. package/dist/chunk-DRMZ5R3S.cjs +67 -0
  41. package/dist/{chunk-BAZMCFVL.js → chunk-DZVRH63Y.js} +1 -1
  42. package/dist/chunk-EI5GFTKG.js +67 -0
  43. package/dist/{chunk-U2ASNGO6.cjs → chunk-FXVCB6YG.cjs} +35 -35
  44. package/dist/{chunk-EU2ZZA6L.cjs → chunk-GHGBIAWQ.cjs} +126 -39
  45. package/dist/chunk-JV7DKLMX.js +1073 -0
  46. package/dist/{chunk-2WOGXEDX.cjs → chunk-M3RZOF3G.cjs} +1 -1
  47. package/dist/chunk-MFBUQC67.cjs +328 -0
  48. package/dist/{chunk-GSTSZPSR.js → chunk-N3SKPQEH.js} +225 -18
  49. package/dist/{chunk-J5VQYK7T.cjs → chunk-N5IMDAHM.cjs} +251 -44
  50. package/dist/{chunk-ININNHLM.js → chunk-NAHJV3O6.js} +11 -5
  51. package/dist/{chunk-IBXEQOGI.js → chunk-PYHB2EHE.js} +2 -324
  52. package/dist/chunk-QEUI6OGE.cjs +336 -0
  53. package/dist/{chunk-6ZSFRRJI.js → chunk-RSF6BT34.js} +1 -1
  54. package/dist/{chunk-JJ2H25JG.cjs → chunk-SNAFFCAX.cjs} +200 -174
  55. package/dist/chunk-T2HUQBRC.cjs +1073 -0
  56. package/dist/{chunk-M2ZVPU7L.cjs → chunk-TNLKFW7D.cjs} +23 -23
  57. package/dist/{chunk-KZI3YWKW.js → chunk-UXYCY3HP.js} +93 -6
  58. package/dist/{chunk-GSLOH2IN.cjs → chunk-VF6ODSYX.cjs} +2 -0
  59. package/dist/{chunk-NQUKTRKN.cjs → chunk-WZLNNGG3.cjs} +45 -45
  60. package/dist/{chunk-NRBGAEFI.cjs → chunk-Y3RUYOOW.cjs} +8 -330
  61. package/dist/{chunk-DDL3ZBHK.cjs → chunk-YJANF2BL.cjs} +86 -86
  62. package/dist/{chunk-TNHDCP4R.js → chunk-ZEI7ZEA4.js} +31 -5
  63. package/dist/commands/index.cjs +7 -323
  64. package/dist/commands/index.js +5 -321
  65. package/dist/extensions/index.cjs +4 -4
  66. package/dist/extensions/index.js +3 -3
  67. package/dist/image-editor/index.cjs +789 -789
  68. package/dist/image-editor/index.js +5 -5
  69. package/dist/index.cjs +9 -9
  70. package/dist/index.js +3 -3
  71. package/dist/paint/index.cjs +2 -2
  72. package/dist/paint/index.js +1 -1
  73. package/dist/paint/wet/world.d.cts +37 -1
  74. package/dist/paint/wet/world.d.ts +37 -1
  75. package/dist/paint-editor/brush-controls.d.cts +2 -0
  76. package/dist/paint-editor/brush-controls.d.ts +2 -0
  77. package/dist/paint-editor/brush-model.d.cts +14 -1
  78. package/dist/paint-editor/brush-model.d.ts +14 -1
  79. package/dist/paint-editor/index.cjs +343 -306
  80. package/dist/paint-editor/index.js +48 -11
  81. package/dist/paint-editor/ui.d.cts +1 -1
  82. package/dist/paint-editor/ui.d.ts +1 -1
  83. package/dist/paint-editor/wet.d.cts +8 -1
  84. package/dist/paint-editor/wet.d.ts +8 -1
  85. package/dist/persistence/index.cjs +24 -1042
  86. package/dist/persistence/index.js +22 -1040
  87. package/dist/tools/index.cjs +5 -3
  88. package/dist/tools/index.js +8 -6
  89. package/dist/vector/index.cjs +999 -999
  90. package/dist/vector/index.js +5 -5
  91. package/package.json +1 -1
  92. package/skills/pixerate-brushes/SKILL.md +1 -0
  93. package/skills/pixerate-paint-editor/SKILL.md +4 -11
  94. package/skills/pixerate-paint-editor/references/brush-facets.md +1 -0
  95. package/skills/pixerate-paint-editor/references/ghost.md +47 -0
  96. package/skills/pixerate-paint-watercolour/SKILL.md +1 -0
@@ -6,12 +6,13 @@ import {
6
6
  BRUSH_PATCH_PROPERTIES,
7
7
  BRUSH_SLIDERS,
8
8
  BRUSH_UNSET_PROPERTY,
9
+ GHOST_SLIDERS,
9
10
  brushEngineControls,
10
11
  createBrushLibraryToolProvider,
11
12
  mergeBrushPatch,
12
13
  multiColorSliders,
13
14
  patchBrushValue
14
- } from "../chunk-ININNHLM.js";
15
+ } from "../chunk-NAHJV3O6.js";
15
16
  import {
16
17
  createStickerTools,
17
18
  describeStickerRef
@@ -19,7 +20,7 @@ import {
19
20
  import {
20
21
  BRUSH_PREVIEW_DARK_PAPER,
21
22
  brushPreviewsOnDark
22
- } from "../chunk-LETF75CA.js";
23
+ } from "../chunk-CLB2I2FU.js";
23
24
  import {
24
25
  toVideoFfmpeg
25
26
  } from "../chunk-S2XPXA47.js";
@@ -34,7 +35,7 @@ import {
34
35
  resolveExternalAssets,
35
36
  serializeLayers,
36
37
  serializeLayersExternal
37
- } from "../chunk-B5W677L7.js";
38
+ } from "../chunk-5HP2VSAS.js";
38
39
  import "../chunk-ADMRNWR7.js";
39
40
  import "../chunk-WJ4WQJCC.js";
40
41
  import {
@@ -117,7 +118,7 @@ import {
117
118
  wetPaperInput,
118
119
  wetPigmentOf,
119
120
  withStrokePath
120
- } from "../chunk-TNHDCP4R.js";
121
+ } from "../chunk-ZEI7ZEA4.js";
121
122
  import {
122
123
  BRUSH_ID,
123
124
  BRUSH_PRESETS,
@@ -129,6 +130,7 @@ import {
129
130
  DEFAULT_BRISTLE,
130
131
  DEFAULT_BRUSH_ID,
131
132
  DEFAULT_DYNAMICS,
133
+ DEFAULT_GHOST,
132
134
  DEFAULT_IMPASTO,
133
135
  DEFAULT_LIGHT,
134
136
  DEFAULT_MULTI_COLOR,
@@ -168,6 +170,7 @@ import {
168
170
  normalizeBristle,
169
171
  normalizeBrushSpec,
170
172
  normalizeDynamics,
173
+ normalizeGhost,
171
174
  normalizeImpasto,
172
175
  normalizeLight,
173
176
  normalizeMultiColor,
@@ -180,7 +183,7 @@ import {
180
183
  readBrushSource,
181
184
  resolveBrush,
182
185
  usesPaintExchange
183
- } from "../chunk-KZI3YWKW.js";
186
+ } from "../chunk-UXYCY3HP.js";
184
187
  import {
185
188
  STAMP_KINDS,
186
189
  StickersSlot,
@@ -228,7 +231,7 @@ import {
228
231
  normalizeWetParams,
229
232
  pigmentOf,
230
233
  wetSupport
231
- } from "../chunk-GSTSZPSR.js";
234
+ } from "../chunk-N3SKPQEH.js";
232
235
  import "../chunk-W6JDUXP3.js";
233
236
  import {
234
237
  CoverageMap,
@@ -6390,12 +6393,27 @@ var PaintEditorController = class _PaintEditorController {
6390
6393
  })();
6391
6394
  const brush = this.strokeBrush(init);
6392
6395
  if (this.previewing && isWetBrush(brush)) throw new PaintEditorError(`Wet paint can't be previewed; the ${brush.name} brush paints wet media. Suggest it with a dry brush.`);
6396
+ if (brush.ghost) {
6397
+ if (brush.light.amount > 0) throw new PaintEditorError(`Ghost paint (${brush.name}) shares the wet light channel and can't emit light; set light.amount to 0 or ghost to null (set_brush).`);
6398
+ if (isPaintEffectPaint(init.paint) || init.paint === void 0 && init.color === void 0 && Boolean(this.effectPaint)) {
6399
+ throw new PaintEditorError(`Ghost paint (${brush.name}) needs a single colour to subtract as it fades, so it can't paint with an effect; set a solid colour (set_color) or pass color to paint_stroke.`);
6400
+ }
6401
+ if (brush.behavior.multicolor.mode !== "off") throw new PaintEditorError(`Ghost paint (${brush.name}) needs a single colour to subtract as it fades, so it can't use multi-colour paint; set behavior.multicolor.mode to "off" (set_brush).`);
6402
+ const gCol = init.color === void 0 ? this.ownColour(init.brushId) ?? this.color : parseColor(init.color);
6403
+ if (this.strokeGradient(init, brush, gCol)) throw new PaintEditorError(`Ghost paint (${brush.name}) needs a single colour to subtract as it fades, so it can't paint a gradient; set a solid colour (set_color) or pass color to paint_stroke.`);
6404
+ if (Boolean(doc.light?.[layer.id]?.tiles.size) || Boolean(this.wet.world?.hasLane(layer.id) && this.wet.world.isLit(layer.id))) {
6405
+ throw new PaintEditorError(`Layer "${layer.name}" holds light paint, and ghost paint shares the wet light channel; paint on another layer (add_layer) or clear the light first (clear_light).`);
6406
+ }
6407
+ }
6393
6408
  const effect = this.strokeEffect(init, brush, points);
6394
6409
  if (!points && init.paint === void 0 && effect?.source.map?.by === "path" && isWetBrush(brush)) {
6395
6410
  throw new PaintEditorError(`The ${brush.name} brush paints wet media, which can't follow a path that isn't known until the stroke ends; paint with a dry brush, set_effect_paint with by: "stroke", or give the points to paint_stroke.`);
6396
6411
  }
6397
6412
  if (brush.light.amount > 0) {
6398
6413
  if (brush.engine === "sticker") throw new PaintEditorError(`The ${brush.name} brush sprays stickers, which don't take light yet; paint light with a dab or particle light brush (glow-gel, neon-marker; list_brush_presets lists them), or set light.amount to 0 (set_brush).`);
6414
+ if (Boolean(this.wet.world?.hasLane(layer.id) && this.wet.world.getGhost(layer.id)) || Boolean(this.wet.settings.get(layer.id)?.ghost)) {
6415
+ throw new PaintEditorError(`Layer "${layer.name}" has ghost paint fading on it, which uses the wet light channel; paint light on another layer (add_layer), or finish the ghost first (dry_paint).`);
6416
+ }
6399
6417
  }
6400
6418
  if (doc.liveStrokes?.[layer.id]) {
6401
6419
  const why = liveStrokeRefusal(brush);
@@ -6448,7 +6466,8 @@ var PaintEditorController = class _PaintEditorController {
6448
6466
  copies: [IDENTITY_AFFINE, ...copiesW].map(affineCopy),
6449
6467
  mask: init.maskStencils ? this.stencilSelection(init.maskStencils) : init.stencils !== false ? this.stencilSelection() : null,
6450
6468
  paint: effect ? null : wetGradient,
6451
- effect: effect?.pixels ?? null
6469
+ effect: effect?.pixels ?? null,
6470
+ speed: this.wet.speed
6452
6471
  });
6453
6472
  init = { ...init, seed: seedW, load };
6454
6473
  wet = { driver, surface: () => world.display(id).surface };
@@ -7370,7 +7389,11 @@ var PaintEditorController = class _PaintEditorController {
7370
7389
  /** Wet settings saved with the document (`null` when they are all defaults). */
7371
7390
  wetJSON() {
7372
7391
  const layers = {};
7373
- for (const [id, set] of this.wet.settings) if (findLayer(this.doc.layers, id)) layers[id] = { params: { ...set.params }, flow: { ...set.flow } };
7392
+ for (const [id, set] of this.wet.settings) {
7393
+ if (!findLayer(this.doc.layers, id)) continue;
7394
+ const ghost = this.wet.world?.hasLane(id) ? this.wet.world.getGhost(id) : set.ghost ?? null;
7395
+ layers[id] = { params: { ...set.params }, flow: { ...set.flow }, ...ghost ? { ghost } : {} };
7396
+ }
7374
7397
  const flat = this.wet.tilt.angle === 0;
7375
7398
  if (!Object.keys(layers).length && flat) return void 0;
7376
7399
  return { tilt: { ...this.wet.tilt }, layers };
@@ -7387,7 +7410,8 @@ var PaintEditorController = class _PaintEditorController {
7387
7410
  continue;
7388
7411
  }
7389
7412
  const x = v;
7390
- this.wet.settings.set(id, { params: normalizeWetParams(x.params ?? {}), flow: normalizeFlow(x.flow ?? {}) });
7413
+ const ghost = readWetGhost(x.ghost);
7414
+ this.wet.settings.set(id, { params: normalizeWetParams(x.params ?? {}), flow: normalizeFlow(x.flow ?? {}), ...ghost ? { ghost } : {} });
7391
7415
  }
7392
7416
  }
7393
7417
  }
@@ -8874,6 +8898,16 @@ function clipToCanvas(rect, canvas) {
8874
8898
  function toEffectPaintPixels(p, pixels) {
8875
8899
  return { width: pixels.width, height: pixels.height, data: new Uint8ClampedArray(pixels.data), ...p.map?.by === "stroke" ? { map: p.map } : {} };
8876
8900
  }
8901
+ function readWetGhost(raw) {
8902
+ if (!raw || typeof raw !== "object") return null;
8903
+ const g = raw;
8904
+ if (!Array.isArray(g.pigment) || g.pigment.length !== 4 || !g.pigment.every((x) => typeof x === "number" && Number.isFinite(x))) return null;
8905
+ const remaining = typeof g.remaining === "number" && Number.isFinite(g.remaining) ? Math.max(0, Math.round(g.remaining)) : 0;
8906
+ if (remaining <= 0) return null;
8907
+ const total = typeof g.total === "number" && Number.isFinite(g.total) ? Math.max(1, Math.round(g.total)) : remaining;
8908
+ const linger = typeof g.linger === "number" && Number.isFinite(g.linger) ? Math.max(0.5, Math.min(60, g.linger)) : 4;
8909
+ return { pigment: [g.pigment[0], g.pigment[1], g.pigment[2], g.pigment[3]], remaining, total, linger };
8910
+ }
8877
8911
 
8878
8912
  // src/paint-editor/presence.ts
8879
8913
  var PaintPresence = class extends PresenceStore {
@@ -9796,7 +9830,7 @@ function createPaintWetTools(c) {
9796
9830
  {
9797
9831
  name: "get_wet_paint",
9798
9832
  title: "Get the wet paint",
9799
- description: "Reads the wet media: whether any paint is still wet, the simulation clock (steps), whether time is paused, its speed (steps per second), the canvas tilt, the kernel running it (typescript, wasm, wasm-threads with the thread count, and a message when it runs slower than it could, e.g. without cross-origin isolation), and each wet layer: wet cells, where the water is (wetBounds), live tiles, its settings and flow map. Also engine: Engine B, the GPU accelerator: WebGPU in browsers, Metal in the Swift editors (mode auto/off, state, reason, adapter, minTiles, activeTiles). Call it after wet strokes to see what is still wet.",
9833
+ description: "Reads the wet media: whether any paint is still wet, the simulation clock (steps), whether time is paused, its speed (steps per second), the canvas tilt, the kernel running it (typescript, wasm, wasm-threads with the thread count, and a message when it runs slower than it could, e.g. without cross-origin isolation), and each wet layer: wet cells, where the water is (wetBounds), live tiles, its settings, flow map, and ghost: { remaining } (seconds of simulated time left when ghost paint is fading on the layer). Also engine: Engine B, the GPU accelerator: WebGPU in browsers, Metal in the Swift editors (mode auto/off, state, reason, adapter, minTiles, activeTiles). Call it after wet strokes to see what is still wet.",
9800
9834
  inputSchema: EMPTY2,
9801
9835
  annotations: { title: "Get the wet paint", ...READ3 },
9802
9836
  coordinates: { out: ["layers[].wetBounds", "layers[].flow.center"] },
@@ -12385,7 +12419,7 @@ function uiTables() {
12385
12419
  brushOptions: BRUSH_OPTION_CONTROLS,
12386
12420
  blendMenu: BLEND_MENU_GROUPS,
12387
12421
  layerEffects: LAYER_EFFECT_MENU.map(({ kind, label }) => ({ kind, label, controls: layerEffectControls(defaultLayerEffect(kind, kind)) })),
12388
- sliders: { paper: PAPER_SLIDERS, wet: WET_PARAM_SLIDERS, wetTilt: WET_TILT_SLIDERS, wetSpeed: WET_SPEED_SLIDER, light: LIGHT_SLIDERS, stroke: STROKE_SLIDERS, sticker: STICKER_SLIDERS },
12422
+ sliders: { paper: PAPER_SLIDERS, wet: WET_PARAM_SLIDERS, wetTilt: WET_TILT_SLIDERS, wetSpeed: WET_SPEED_SLIDER, light: LIGHT_SLIDERS, ghost: GHOST_SLIDERS, stroke: STROKE_SLIDERS, sticker: STICKER_SLIDERS },
12389
12423
  pathStrokeTapers: PATH_STROKE_TAPERS,
12390
12424
  padSampleSizes: PAD_SAMPLE_SIZES,
12391
12425
  wetGpu: WET_GPU_OPTIONS,
@@ -12462,6 +12496,7 @@ export {
12462
12496
  DEFAULT_DPI,
12463
12497
  DEFAULT_DYNAMICS,
12464
12498
  DEFAULT_FLOW,
12499
+ DEFAULT_GHOST,
12465
12500
  DEFAULT_IMPASTO,
12466
12501
  DEFAULT_LIGHT,
12467
12502
  DEFAULT_LIGHTING,
@@ -12484,6 +12519,7 @@ export {
12484
12519
  DryStroke,
12485
12520
  EFFECT_MAP_REPEATS,
12486
12521
  EFFECT_STRIP_HEIGHT,
12522
+ GHOST_SLIDERS,
12487
12523
  GPU_LEAD,
12488
12524
  GRAIN_PATTERNS,
12489
12525
  GRAIN_TILE,
@@ -12692,6 +12728,7 @@ export {
12692
12728
  normalizeCanvas,
12693
12729
  normalizeDynamics,
12694
12730
  normalizeFlow,
12731
+ normalizeGhost,
12695
12732
  normalizeHexColor,
12696
12733
  normalizeImpasto,
12697
12734
  normalizeLight,
@@ -153,7 +153,7 @@ export declare function guideOverlay(controller: PaintEditorController, drag?: {
153
153
  to: Point2;
154
154
  kind: GuideOverlayKind;
155
155
  }>;
156
- export { BRUSH_SLIDERS, brushEngineControls, multiColorSliders, patchBrushValue, type BrushEngineControls, type SliderControl } from './brush-controls.cjs';
156
+ export { BRUSH_SLIDERS, brushEngineControls, GHOST_SLIDERS, multiColorSliders, patchBrushValue, type BrushEngineControls, type SliderControl } from './brush-controls.cjs';
157
157
  import type { SliderControl } from './brush-controls.cjs';
158
158
  export declare const PAPER_SLIDERS: readonly SliderControl[];
159
159
  /** Reads `a.b` from an object. */
@@ -153,7 +153,7 @@ export declare function guideOverlay(controller: PaintEditorController, drag?: {
153
153
  to: Point2;
154
154
  kind: GuideOverlayKind;
155
155
  }>;
156
- export { BRUSH_SLIDERS, brushEngineControls, multiColorSliders, patchBrushValue, type BrushEngineControls, type SliderControl } from './brush-controls.js';
156
+ export { BRUSH_SLIDERS, brushEngineControls, GHOST_SLIDERS, multiColorSliders, patchBrushValue, type BrushEngineControls, type SliderControl } from './brush-controls.js';
157
157
  import type { SliderControl } from './brush-controls.js';
158
158
  export declare const PAPER_SLIDERS: readonly SliderControl[];
159
159
  /** Reads `a.b` from an object. */
@@ -27,7 +27,7 @@ import { type PaintGradientSource } from '../paint/gradient-paint.cjs';
27
27
  import { type EffectPaintPixels } from './effect-paint.cjs';
28
28
  import { type WetParams, type WetTilt } from '../paint/wet/model.cjs';
29
29
  import { type WetGpuProvider } from '../paint/wet/gpu.cjs';
30
- import { WetWorld, type WetBackendInfo, type WetPaperInput, type WetSnapshot, type WetWorldOptions } from '../paint/wet/world.cjs';
30
+ import { WetWorld, type WetBackendInfo, type WetGhostState, type WetPaperInput, type WetSnapshot, type WetWorldOptions } from '../paint/wet/world.cjs';
31
31
  import { TiledSurface, type RasterRect } from '../raster/surface.cjs';
32
32
  import type { PaintBrush, WetMediumKind } from './brushes.cjs';
33
33
  import { type PenPoint, type StrokeCopy } from './engine.cjs';
@@ -60,6 +60,8 @@ export interface WetStrokeInit {
60
60
  * the water like any pigment.
61
61
  */
62
62
  effect?: EffectPaintPixels | null;
63
+ /** Simulation steps per second (for ghost fade countdown). */
64
+ speed?: number;
63
65
  }
64
66
  /**
65
67
  * One wet-brush stroke: pen samples → dabs → wet tool edits. Points are applied only while the
@@ -110,6 +112,7 @@ export declare const WET_MEDIUM_PARAMS: Readonly<Record<WetMediumKind, Partial<W
110
112
  export interface WetLayerSettings {
111
113
  params: WetParams;
112
114
  flow: WetFlowSpec;
115
+ ghost?: WetGhostState | null;
113
116
  }
114
117
  /** One wet layer as tools describe it. */
115
118
  export interface WetLayerInfo {
@@ -124,6 +127,10 @@ export interface WetLayerInfo {
124
127
  activeTiles: number;
125
128
  params: WetParams;
126
129
  flow: WetFlowSpec;
130
+ /** Active ghost fade on the layer (`remaining` seconds of simulated time), when one is fading. */
131
+ ghost?: {
132
+ remaining: number;
133
+ };
127
134
  }
128
135
  /** An event recorded with the clock it happened at (strokes are recorded separately), for replays. */
129
136
  export interface WetEvent {
@@ -27,7 +27,7 @@ import { type PaintGradientSource } from '../paint/gradient-paint.js';
27
27
  import { type EffectPaintPixels } from './effect-paint.js';
28
28
  import { type WetParams, type WetTilt } from '../paint/wet/model.js';
29
29
  import { type WetGpuProvider } from '../paint/wet/gpu.js';
30
- import { WetWorld, type WetBackendInfo, type WetPaperInput, type WetSnapshot, type WetWorldOptions } from '../paint/wet/world.js';
30
+ import { WetWorld, type WetBackendInfo, type WetGhostState, type WetPaperInput, type WetSnapshot, type WetWorldOptions } from '../paint/wet/world.js';
31
31
  import { TiledSurface, type RasterRect } from '../raster/surface.js';
32
32
  import type { PaintBrush, WetMediumKind } from './brushes.js';
33
33
  import { type PenPoint, type StrokeCopy } from './engine.js';
@@ -60,6 +60,8 @@ export interface WetStrokeInit {
60
60
  * the water like any pigment.
61
61
  */
62
62
  effect?: EffectPaintPixels | null;
63
+ /** Simulation steps per second (for ghost fade countdown). */
64
+ speed?: number;
63
65
  }
64
66
  /**
65
67
  * One wet-brush stroke: pen samples → dabs → wet tool edits. Points are applied only while the
@@ -110,6 +112,7 @@ export declare const WET_MEDIUM_PARAMS: Readonly<Record<WetMediumKind, Partial<W
110
112
  export interface WetLayerSettings {
111
113
  params: WetParams;
112
114
  flow: WetFlowSpec;
115
+ ghost?: WetGhostState | null;
113
116
  }
114
117
  /** One wet layer as tools describe it. */
115
118
  export interface WetLayerInfo {
@@ -124,6 +127,10 @@ export interface WetLayerInfo {
124
127
  activeTiles: number;
125
128
  params: WetParams;
126
129
  flow: WetFlowSpec;
130
+ /** Active ghost fade on the layer (`remaining` seconds of simulated time), when one is fading. */
131
+ ghost?: {
132
+ remaining: number;
133
+ };
127
134
  }
128
135
  /** An event recorded with the clock it happened at (strokes are recorded separately), for replays. */
129
136
  export interface WetEvent {