@pixerate/editor 0.274.0 → 0.275.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 (86) hide show
  1. package/dist/animation-editor/index.cjs +60 -60
  2. package/dist/animation-editor/index.js +4 -4
  3. package/dist/brushes/index.cjs +102 -74
  4. package/dist/brushes/index.js +34 -6
  5. package/dist/brushes/patch.d.cts +1 -1
  6. package/dist/brushes/patch.d.ts +1 -1
  7. package/dist/brushes/preview.d.cts +3 -0
  8. package/dist/brushes/preview.d.ts +3 -0
  9. package/dist/brushes/profiles.d.cts +2 -0
  10. package/dist/brushes/profiles.d.ts +2 -0
  11. package/dist/brushes/schema.d.cts +4 -0
  12. package/dist/brushes/schema.d.ts +4 -0
  13. package/dist/brushes/types.d.cts +47 -0
  14. package/dist/brushes/types.d.ts +47 -0
  15. package/dist/{brushstrokeRender-5YBCLFDQ.cjs → brushstrokeRender-2RQL46WB.cjs} +15 -15
  16. package/dist/{brushstrokeRender-DZGFU3J4.js → brushstrokeRender-WGY4DZHF.js} +3 -3
  17. package/dist/canvas/index.cjs +13 -13
  18. package/dist/canvas/index.js +1 -1
  19. package/dist/chart/contract.d.cts +3 -1
  20. package/dist/chart/contract.d.ts +3 -1
  21. package/dist/{chunk-2XDNF73E.js → chunk-43XJC3PP.js} +1 -1
  22. package/dist/{chunk-GHGBIAWQ.cjs → chunk-6QNDGMK7.cjs} +263 -45
  23. package/dist/{chunk-2F3YDXCN.js → chunk-75SYKUOT.js} +1 -1
  24. package/dist/{chunk-M3RZOF3G.cjs → chunk-C6VB7CPY.cjs} +1 -1
  25. package/dist/{chunk-N5IMDAHM.cjs → chunk-DHC4E2DU.cjs} +89 -37
  26. package/dist/{chunk-NAHJV3O6.js → chunk-G5TCMKX5.js} +28 -11
  27. package/dist/{chunk-DZVRH63Y.js → chunk-IEEY5RJV.js} +1 -1
  28. package/dist/{chunk-7W2EQQBP.cjs → chunk-JDXWLCXF.cjs} +6 -6
  29. package/dist/{chunk-N3SKPQEH.js → chunk-LMG57L5A.js} +62 -10
  30. package/dist/{chunk-5HP2VSAS.js → chunk-MSWLJI4G.js} +1 -1
  31. package/dist/{chunk-SNAFFCAX.cjs → chunk-OLZXXAER.cjs} +221 -196
  32. package/dist/{chunk-YJANF2BL.cjs → chunk-RAPNRK4R.cjs} +86 -86
  33. package/dist/{chunk-CLB2I2FU.js → chunk-RJGW3RQS.js} +2 -2
  34. package/dist/{chunk-FXVCB6YG.cjs → chunk-T6KNJVBP.cjs} +35 -35
  35. package/dist/{chunk-ZEI7ZEA4.js → chunk-Y2ISSVPH.js} +34 -9
  36. package/dist/{chunk-3JKCREEU.cjs → chunk-Y354ETAE.cjs} +76 -59
  37. package/dist/{chunk-TNLKFW7D.cjs → chunk-YYZC2EQO.cjs} +23 -23
  38. package/dist/{chunk-UXYCY3HP.js → chunk-ZM7IKQ4X.js} +239 -21
  39. package/dist/image-editor/index.cjs +789 -789
  40. package/dist/image-editor/index.js +5 -5
  41. package/dist/paint/index.cjs +2 -2
  42. package/dist/paint/index.js +1 -1
  43. package/dist/paint/wet/world.d.cts +15 -2
  44. package/dist/paint/wet/world.d.ts +15 -2
  45. package/dist/paint-editor/brush-controls.d.cts +7 -0
  46. package/dist/paint-editor/brush-controls.d.ts +7 -0
  47. package/dist/paint-editor/brush-model.d.cts +59 -1
  48. package/dist/paint-editor/brush-model.d.ts +59 -1
  49. package/dist/paint-editor/controller.d.cts +168 -1
  50. package/dist/paint-editor/controller.d.ts +168 -1
  51. package/dist/paint-editor/dab-stroke.d.cts +3 -0
  52. package/dist/paint-editor/dab-stroke.d.ts +3 -0
  53. package/dist/paint-editor/document.d.cts +13 -3
  54. package/dist/paint-editor/document.d.ts +13 -3
  55. package/dist/paint-editor/engine.d.cts +3 -0
  56. package/dist/paint-editor/engine.d.ts +3 -0
  57. package/dist/paint-editor/heat.d.cts +156 -0
  58. package/dist/paint-editor/heat.d.ts +156 -0
  59. package/dist/paint-editor/index.cjs +3386 -855
  60. package/dist/paint-editor/index.d.cts +3 -0
  61. package/dist/paint-editor/index.d.ts +3 -0
  62. package/dist/paint-editor/index.js +2731 -200
  63. package/dist/paint-editor/mutation-tools.d.cts +13 -0
  64. package/dist/paint-editor/mutation-tools.d.ts +13 -0
  65. package/dist/paint-editor/mutation.d.cts +67 -0
  66. package/dist/paint-editor/mutation.d.ts +67 -0
  67. package/dist/paint-editor/presence.d.cts +7 -1
  68. package/dist/paint-editor/presence.d.ts +7 -1
  69. package/dist/paint-editor/procedural.d.cts +3 -0
  70. package/dist/paint-editor/procedural.d.ts +3 -0
  71. package/dist/paint-editor/stroke.d.cts +32 -2
  72. package/dist/paint-editor/stroke.d.ts +32 -2
  73. package/dist/paint-editor/thick.d.cts +48 -7
  74. package/dist/paint-editor/thick.d.ts +48 -7
  75. package/dist/paint-editor/ui.d.cts +34 -1
  76. package/dist/paint-editor/ui.d.ts +34 -1
  77. package/dist/paint-editor/wet.d.cts +8 -1
  78. package/dist/paint-editor/wet.d.ts +8 -1
  79. package/dist/vector/index.cjs +999 -999
  80. package/dist/vector/index.js +5 -5
  81. package/package.json +1 -1
  82. package/skills/pixerate-brushes/SKILL.md +1 -0
  83. package/skills/pixerate-paint-editor/SKILL.md +7 -1
  84. package/skills/pixerate-paint-editor/references/brush-facets.md +1 -0
  85. package/skills/pixerate-paint-editor/references/live-patterns.md +41 -0
  86. package/skills/pixerate-paint-watercolour/SKILL.md +8 -0
@@ -55,7 +55,11 @@ import { EffectsSlot } from '../effects/helpers.cjs';
55
55
  import type { EffectDescription, EffectProvider, EffectValue } from '../effects/types.cjs';
56
56
  import { type PaintEffectPaint } from './effect-paint.cjs';
57
57
  import { type PaintEffectPlace, type PaintEffectSource, type PaintPaperEffect, type PaperEffectBlend } from './effects.cjs';
58
- import { type PaintLighting } from './thick.cjs';
58
+ import { type PaintLighting, type PaintViewTilt } from './thick.cjs';
59
+ import { type HeatSample, type HeatStats, type UvLightSettings } from './heat.cjs';
60
+ import { type PaintMutationAnswerInput, type PaintMutationAnswerResult, type PaintMutationInfo, type PaintMutationReadResult, type PaintMutationRequestListener } from './mutation.cjs';
61
+ import type { PaintDirector } from './presence.cjs';
62
+ import type { PresenceActorInfo } from '../animation/presence.cjs';
59
63
  import { type PaintProposals } from './collaboration.cjs';
60
64
  import { type BreedStrokesResult, type ProposalGenomeResult, type StrokeMutationRates } from './evolution.cjs';
61
65
  export declare class PaintEditorError extends Error {
@@ -146,6 +150,8 @@ export interface PaintEditorState {
146
150
  /** What the Mixer Brush carries between strokes. */
147
151
  mixerLoad: MixerLoad;
148
152
  view: PaintView;
153
+ /** View tilt for thin-film iridescence and specular highlights (-1..1, default {x: 0, y: 0}; editor state, not saved with the document). */
154
+ viewTilt: PaintViewTilt;
149
155
  hand: Hand;
150
156
  /** Live effect paint: whether strokes on stroke layers play their effect, the frame rate cap and the time (ms) the view shows. Editor state. */
151
157
  effectPlayback: {
@@ -173,6 +179,14 @@ export interface PaintEditorState {
173
179
  stroking: boolean;
174
180
  /** Wet media. */
175
181
  wet: PaintWetStateInfo;
182
+ /** The UV / blacklight view setting (saved with the document, not undo steps). */
183
+ uvLight: UvLightSettings;
184
+ /** The coarse heat field's temperature summary. */
185
+ heat: HeatStats;
186
+ /** Pending and active mutation exchanges (editor state, not saved with the document). */
187
+ mutations: readonly PaintMutationInfo[];
188
+ /** Whether an agent is listening for mutation requests (`onMutationRequest` or `setMutationAgentConnected`). */
189
+ hasMutationAgent: boolean;
176
190
  /** Increments on every change. */
177
191
  version: number;
178
192
  }
@@ -208,6 +222,8 @@ export interface PaintLayerInfo {
208
222
  liveStrokes?: number;
209
223
  /** How many live layer effects (glows, shadows, strokes…) it has, when any (`getLayerEffects`). */
210
224
  effects?: number;
225
+ /** A temporary layer created for a dry mutation stroke (fades away once claimed or merges down if unanswered). */
226
+ temporary?: boolean;
211
227
  }
212
228
  /** A live stroke as it is painted (`onStrokeSample`), for agents that paint along with the user. */
213
229
  export interface PaintStrokeSample {
@@ -519,9 +535,17 @@ export declare class PaintEditorController {
519
535
  private readonly wetOptions;
520
536
  /** Where the light comes from for thick paint (saved with the document, not undo steps). */
521
537
  private lighting;
538
+ /** Where the viewer looks from for thin-film iridescence and specular highlights (editor state, not saved with the document). */
539
+ private viewTilt;
522
540
  private readonly litCaches;
523
541
  /** The live impasto stroke's channels. */
524
542
  private liveThick;
543
+ /** The UV / blacklight setting (saved with the document, not undo steps). */
544
+ private uvLight;
545
+ private readonly uvCaches;
546
+ private readonly thermoCaches;
547
+ private liveFluor;
548
+ private liveThermo;
525
549
  /** Set while an operation that changes wet paint runs: the simulation before it (for undo). */
526
550
  private wetOp;
527
551
  /** The effects provider slot (see `setEffectsProvider`). */
@@ -536,6 +560,15 @@ export declare class PaintEditorController {
536
560
  private readonly rasterizeSticker;
537
561
  private readonly effectsRenderer;
538
562
  private paperEffectNodeCache;
563
+ private mutations;
564
+ private nextMutationId;
565
+ private readonly mutationListeners;
566
+ private mutationAgentConnected;
567
+ private mutationDirector;
568
+ private readonly heldEvaporation;
569
+ private readonly tempLayerTargets;
570
+ private answeringMutation;
571
+ private lastMutationTick;
539
572
  constructor(options?: PaintEditorOptions);
540
573
  subscribe(listener: () => void): () => void;
541
574
  /**
@@ -1159,12 +1192,81 @@ export declare class PaintEditorController {
1159
1192
  layerId: string;
1160
1193
  cleared: boolean;
1161
1194
  };
1195
+ private fluorWithout;
1196
+ private thermoWithout;
1197
+ /** The UV / blacklight view setting. */
1198
+ getUvLight(): UvLightSettings;
1199
+ /** Layers that carry UV-fluorescent ink. */
1200
+ fluorescentLayerIds(): string[];
1201
+ /** Layers that carry thermochromic ink. */
1202
+ thermochromicLayerIds(): string[];
1203
+ /**
1204
+ * Switches or tunes the virtual UV / blacklight (`on`, `strength` 0..2, `ambient` 0..1). When
1205
+ * `on` is true, the composite dims to `ambient` and fluorescent ink (`brush.fluorescence`) glows
1206
+ * in its stroke colour with a Linear Dodge halo. Saved with the document, not an undo step.
1207
+ */
1208
+ setUvLight(update: Partial<UvLightSettings>): UvLightSettings;
1209
+ /**
1210
+ * Reads the document's heat field: its temperature summary (`peak`, `mean`, `hotCells`) and,
1211
+ * when `points` are given (document pixels), the bilinearly sampled temperature (0..1) at each.
1212
+ */
1213
+ getHeat(points?: readonly {
1214
+ x: number;
1215
+ y: number;
1216
+ }[]): HeatStats & {
1217
+ samples: HeatSample[];
1218
+ thermochromicLayerIds: string[];
1219
+ };
1220
+ /**
1221
+ * Warms (or with `temperature` 0 cools) the canvas heat field along `points` (document pixels)
1222
+ * with a soft brush of `radius` px (default 28) towards `temperature` 0..1 (default 1). Where
1223
+ * temperature reaches `HEAT_DRY_THRESHOLD` (0.5), any wet paint on those tiles dries at once.
1224
+ * One undo step.
1225
+ */
1226
+ paintHeat(options: {
1227
+ points: readonly {
1228
+ x: number;
1229
+ y: number;
1230
+ }[];
1231
+ radius?: number;
1232
+ temperature?: number;
1233
+ }): HeatStats & {
1234
+ bounds: RasterRect | null;
1235
+ dried: Array<{
1236
+ layerId: string;
1237
+ bounds: RasterRect | null;
1238
+ }>;
1239
+ };
1240
+ /**
1241
+ * Advances the heat field by `steps` simulation steps (1..200, default 10): heat diffuses into
1242
+ * neighbouring cells and cools towards 0 by Newtonian cooling. Newly heated tiles above the dry
1243
+ * threshold dry any wet paint on them. Not an undo step (time passing never is).
1244
+ */
1245
+ advanceHeat(stepsOrOptions?: number | {
1246
+ steps?: number;
1247
+ }): HeatStats & {
1248
+ steps: number;
1249
+ dried: Array<{
1250
+ layerId: string;
1251
+ bounds: RasterRect | null;
1252
+ }>;
1253
+ };
1254
+ /** Cools the whole canvas heat field to 0 at once (one undo step when anything was warm). */
1255
+ clearHeat(): {
1256
+ cleared: boolean;
1257
+ };
1258
+ /** Dries any wet paint on tiles where `field` is at or above `HEAT_DRY_THRESHOLD`. */
1259
+ private dryWetFromHeat;
1162
1260
  /** The document's `thick` without these layers (as a spread: `{}` when unchanged). */
1163
1261
  private thickWithout;
1164
1262
  /** Where the light comes from for thick paint and metallic paper. */
1165
1263
  getLighting(): PaintLighting;
1166
1264
  /** Moves the light (thick paint and metallic paper are relit; nothing is repainted). Saved with the document, not an undo step. */
1167
1265
  setLighting(update: Partial<PaintLighting>): PaintLighting;
1266
+ /** Where the viewer looks from for thin-film iridescence and specular highlights (-1..1; editor state, not saved with the document). */
1267
+ getViewTilt(): PaintViewTilt;
1268
+ /** Tilts the view vector for thin-film iridescence and specular highlights (-1..1; editor state, not saved with the document, not an undo step). */
1269
+ setViewTilt(update: Partial<PaintViewTilt>): PaintViewTilt;
1168
1270
  /** A layer's relief: where it is, its highest point (0..255) and its tiles; `null` when it has none. */
1169
1271
  reliefOf(layerId?: string): {
1170
1272
  layerId: string;
@@ -1315,6 +1417,14 @@ export declare class PaintEditorController {
1315
1417
  private tracingNode;
1316
1418
  /** The layers with every wet layer dried where it is (what saving and exporting see). */
1317
1419
  private driedLayers;
1420
+ /** The layers with thermochromic paint shifted toward its hot colour where the canvas is warm. */
1421
+ private thermoLayers;
1422
+ /**
1423
+ * Under the UV blacklight: every pixel layer's daylight paint dims to `uvLight.ambient`, any
1424
+ * fluorescent ink (`doc.fluor[id]`) glows in its own colour, and each fluorescent layer casts a
1425
+ * Linear Dodge (`::uv`) spill layer directly above it.
1426
+ */
1427
+ private uvLayers;
1318
1428
  /** The layers with thick paint lit (each thick layer's pixels replaced by its lit tiles). */
1319
1429
  private litLayers;
1320
1430
  /**
@@ -1692,6 +1802,63 @@ export declare class PaintEditorController {
1692
1802
  removeMixingPad(id: string): SavedMixingPadInfo;
1693
1803
  private mixingPadJSON;
1694
1804
  private readMixingPad;
1805
+ /**
1806
+ * Hears each mutation once it has settled (`settle` seconds after the user's last mutation
1807
+ * stroke). Registering a listener marks an agent as connected (`state.hasMutationAgent`).
1808
+ */
1809
+ onMutationRequest(listener: PaintMutationRequestListener): () => void;
1810
+ /** Marks whether a mutation-answering agent is connected (for hosts that wake agents via tools). */
1811
+ setMutationAgentConnected(connected: boolean): void;
1812
+ /** Attaches a `PaintDirector` so `answerMutation` and `readMutation` animate the agent's cursor. */
1813
+ setMutationDirector(director: PaintDirector | null): void;
1814
+ /** Lists pending mutations (or all mutations when `includeAll` is true). */
1815
+ listMutations(options?: {
1816
+ includeAll?: boolean;
1817
+ }): PaintMutationInfo[];
1818
+ /** One mutation by id (`undefined` when unknown). */
1819
+ getMutation(id: string): PaintMutationInfo | undefined;
1820
+ /**
1821
+ * Claims a mutation (`id`) and starts its fade. Returns the strokes (points, colour, brush), a
1822
+ * PNG of the area around them (`cropPng`), and a preview of the whole painting (`canvasPng`) so
1823
+ * the agent can choose where to answer. Refused if already claimed, answered or declined.
1824
+ */
1825
+ readMutation(id: string, options?: {
1826
+ actor?: string;
1827
+ padding?: number;
1828
+ maxSide?: number;
1829
+ }): PaintMutationReadResult;
1830
+ /**
1831
+ * Paints the agent's answer to a claimed mutation (`id`). When a `PaintDirector` is attached (or
1832
+ * passed in `options`), the agent's cursor is seen painting live; all strokes commit as one undo
1833
+ * step with origin `agent:<id>`. A second answer to the same id is refused.
1834
+ */
1835
+ answerMutation(id: string, input: PaintMutationAnswerInput, options?: {
1836
+ director?: PaintDirector;
1837
+ actor?: PresenceActorInfo;
1838
+ }): Promise<PaintMutationAnswerResult>;
1839
+ /**
1840
+ * Gives up on a mutation (`id`) without answering. The user's paint stays as ordinary paint (wet
1841
+ * strokes stay in the wet layer, and dry temporary layers merge down into their target layer).
1842
+ */
1843
+ declineMutation(id: string, reason?: string): PaintMutationInfo;
1844
+ /**
1845
+ * Advances mutation timers (`settle`, `patience`, and dry temporary-layer `fade`) by `seconds`
1846
+ * of simulated time. Called automatically from `tickMutations` and `advanceWet`.
1847
+ */
1848
+ advanceMutations(seconds: number): void;
1849
+ /** Steps mutation timers with the wall clock (`performance.now()`, in ms). */
1850
+ tickMutations(now: number): void;
1851
+ private toMutationInfo;
1852
+ private recordMutationStroke;
1853
+ private holdMutationEvaporation;
1854
+ private releaseMutationEvaporation;
1855
+ private finishWetMutationsOnDry;
1856
+ private expireMutation;
1857
+ private keepMutationPaint;
1858
+ private mergeTempLayerIntoTarget;
1859
+ private removeTempMutationLayer;
1860
+ private reconcileMutationsAfterUndo;
1861
+ private resetMutations;
1695
1862
  /** A layer's tile surface (live while stroking), for thumbnails. */
1696
1863
  layerSurface(id: string): TiledSurface | null;
1697
1864
  private proposalsStore;
@@ -55,7 +55,11 @@ import { EffectsSlot } from '../effects/helpers.js';
55
55
  import type { EffectDescription, EffectProvider, EffectValue } from '../effects/types.js';
56
56
  import { type PaintEffectPaint } from './effect-paint.js';
57
57
  import { type PaintEffectPlace, type PaintEffectSource, type PaintPaperEffect, type PaperEffectBlend } from './effects.js';
58
- import { type PaintLighting } from './thick.js';
58
+ import { type PaintLighting, type PaintViewTilt } from './thick.js';
59
+ import { type HeatSample, type HeatStats, type UvLightSettings } from './heat.js';
60
+ import { type PaintMutationAnswerInput, type PaintMutationAnswerResult, type PaintMutationInfo, type PaintMutationReadResult, type PaintMutationRequestListener } from './mutation.js';
61
+ import type { PaintDirector } from './presence.js';
62
+ import type { PresenceActorInfo } from '../animation/presence.js';
59
63
  import { type PaintProposals } from './collaboration.js';
60
64
  import { type BreedStrokesResult, type ProposalGenomeResult, type StrokeMutationRates } from './evolution.js';
61
65
  export declare class PaintEditorError extends Error {
@@ -146,6 +150,8 @@ export interface PaintEditorState {
146
150
  /** What the Mixer Brush carries between strokes. */
147
151
  mixerLoad: MixerLoad;
148
152
  view: PaintView;
153
+ /** View tilt for thin-film iridescence and specular highlights (-1..1, default {x: 0, y: 0}; editor state, not saved with the document). */
154
+ viewTilt: PaintViewTilt;
149
155
  hand: Hand;
150
156
  /** Live effect paint: whether strokes on stroke layers play their effect, the frame rate cap and the time (ms) the view shows. Editor state. */
151
157
  effectPlayback: {
@@ -173,6 +179,14 @@ export interface PaintEditorState {
173
179
  stroking: boolean;
174
180
  /** Wet media. */
175
181
  wet: PaintWetStateInfo;
182
+ /** The UV / blacklight view setting (saved with the document, not undo steps). */
183
+ uvLight: UvLightSettings;
184
+ /** The coarse heat field's temperature summary. */
185
+ heat: HeatStats;
186
+ /** Pending and active mutation exchanges (editor state, not saved with the document). */
187
+ mutations: readonly PaintMutationInfo[];
188
+ /** Whether an agent is listening for mutation requests (`onMutationRequest` or `setMutationAgentConnected`). */
189
+ hasMutationAgent: boolean;
176
190
  /** Increments on every change. */
177
191
  version: number;
178
192
  }
@@ -208,6 +222,8 @@ export interface PaintLayerInfo {
208
222
  liveStrokes?: number;
209
223
  /** How many live layer effects (glows, shadows, strokes…) it has, when any (`getLayerEffects`). */
210
224
  effects?: number;
225
+ /** A temporary layer created for a dry mutation stroke (fades away once claimed or merges down if unanswered). */
226
+ temporary?: boolean;
211
227
  }
212
228
  /** A live stroke as it is painted (`onStrokeSample`), for agents that paint along with the user. */
213
229
  export interface PaintStrokeSample {
@@ -519,9 +535,17 @@ export declare class PaintEditorController {
519
535
  private readonly wetOptions;
520
536
  /** Where the light comes from for thick paint (saved with the document, not undo steps). */
521
537
  private lighting;
538
+ /** Where the viewer looks from for thin-film iridescence and specular highlights (editor state, not saved with the document). */
539
+ private viewTilt;
522
540
  private readonly litCaches;
523
541
  /** The live impasto stroke's channels. */
524
542
  private liveThick;
543
+ /** The UV / blacklight setting (saved with the document, not undo steps). */
544
+ private uvLight;
545
+ private readonly uvCaches;
546
+ private readonly thermoCaches;
547
+ private liveFluor;
548
+ private liveThermo;
525
549
  /** Set while an operation that changes wet paint runs: the simulation before it (for undo). */
526
550
  private wetOp;
527
551
  /** The effects provider slot (see `setEffectsProvider`). */
@@ -536,6 +560,15 @@ export declare class PaintEditorController {
536
560
  private readonly rasterizeSticker;
537
561
  private readonly effectsRenderer;
538
562
  private paperEffectNodeCache;
563
+ private mutations;
564
+ private nextMutationId;
565
+ private readonly mutationListeners;
566
+ private mutationAgentConnected;
567
+ private mutationDirector;
568
+ private readonly heldEvaporation;
569
+ private readonly tempLayerTargets;
570
+ private answeringMutation;
571
+ private lastMutationTick;
539
572
  constructor(options?: PaintEditorOptions);
540
573
  subscribe(listener: () => void): () => void;
541
574
  /**
@@ -1159,12 +1192,81 @@ export declare class PaintEditorController {
1159
1192
  layerId: string;
1160
1193
  cleared: boolean;
1161
1194
  };
1195
+ private fluorWithout;
1196
+ private thermoWithout;
1197
+ /** The UV / blacklight view setting. */
1198
+ getUvLight(): UvLightSettings;
1199
+ /** Layers that carry UV-fluorescent ink. */
1200
+ fluorescentLayerIds(): string[];
1201
+ /** Layers that carry thermochromic ink. */
1202
+ thermochromicLayerIds(): string[];
1203
+ /**
1204
+ * Switches or tunes the virtual UV / blacklight (`on`, `strength` 0..2, `ambient` 0..1). When
1205
+ * `on` is true, the composite dims to `ambient` and fluorescent ink (`brush.fluorescence`) glows
1206
+ * in its stroke colour with a Linear Dodge halo. Saved with the document, not an undo step.
1207
+ */
1208
+ setUvLight(update: Partial<UvLightSettings>): UvLightSettings;
1209
+ /**
1210
+ * Reads the document's heat field: its temperature summary (`peak`, `mean`, `hotCells`) and,
1211
+ * when `points` are given (document pixels), the bilinearly sampled temperature (0..1) at each.
1212
+ */
1213
+ getHeat(points?: readonly {
1214
+ x: number;
1215
+ y: number;
1216
+ }[]): HeatStats & {
1217
+ samples: HeatSample[];
1218
+ thermochromicLayerIds: string[];
1219
+ };
1220
+ /**
1221
+ * Warms (or with `temperature` 0 cools) the canvas heat field along `points` (document pixels)
1222
+ * with a soft brush of `radius` px (default 28) towards `temperature` 0..1 (default 1). Where
1223
+ * temperature reaches `HEAT_DRY_THRESHOLD` (0.5), any wet paint on those tiles dries at once.
1224
+ * One undo step.
1225
+ */
1226
+ paintHeat(options: {
1227
+ points: readonly {
1228
+ x: number;
1229
+ y: number;
1230
+ }[];
1231
+ radius?: number;
1232
+ temperature?: number;
1233
+ }): HeatStats & {
1234
+ bounds: RasterRect | null;
1235
+ dried: Array<{
1236
+ layerId: string;
1237
+ bounds: RasterRect | null;
1238
+ }>;
1239
+ };
1240
+ /**
1241
+ * Advances the heat field by `steps` simulation steps (1..200, default 10): heat diffuses into
1242
+ * neighbouring cells and cools towards 0 by Newtonian cooling. Newly heated tiles above the dry
1243
+ * threshold dry any wet paint on them. Not an undo step (time passing never is).
1244
+ */
1245
+ advanceHeat(stepsOrOptions?: number | {
1246
+ steps?: number;
1247
+ }): HeatStats & {
1248
+ steps: number;
1249
+ dried: Array<{
1250
+ layerId: string;
1251
+ bounds: RasterRect | null;
1252
+ }>;
1253
+ };
1254
+ /** Cools the whole canvas heat field to 0 at once (one undo step when anything was warm). */
1255
+ clearHeat(): {
1256
+ cleared: boolean;
1257
+ };
1258
+ /** Dries any wet paint on tiles where `field` is at or above `HEAT_DRY_THRESHOLD`. */
1259
+ private dryWetFromHeat;
1162
1260
  /** The document's `thick` without these layers (as a spread: `{}` when unchanged). */
1163
1261
  private thickWithout;
1164
1262
  /** Where the light comes from for thick paint and metallic paper. */
1165
1263
  getLighting(): PaintLighting;
1166
1264
  /** Moves the light (thick paint and metallic paper are relit; nothing is repainted). Saved with the document, not an undo step. */
1167
1265
  setLighting(update: Partial<PaintLighting>): PaintLighting;
1266
+ /** Where the viewer looks from for thin-film iridescence and specular highlights (-1..1; editor state, not saved with the document). */
1267
+ getViewTilt(): PaintViewTilt;
1268
+ /** Tilts the view vector for thin-film iridescence and specular highlights (-1..1; editor state, not saved with the document, not an undo step). */
1269
+ setViewTilt(update: Partial<PaintViewTilt>): PaintViewTilt;
1168
1270
  /** A layer's relief: where it is, its highest point (0..255) and its tiles; `null` when it has none. */
1169
1271
  reliefOf(layerId?: string): {
1170
1272
  layerId: string;
@@ -1315,6 +1417,14 @@ export declare class PaintEditorController {
1315
1417
  private tracingNode;
1316
1418
  /** The layers with every wet layer dried where it is (what saving and exporting see). */
1317
1419
  private driedLayers;
1420
+ /** The layers with thermochromic paint shifted toward its hot colour where the canvas is warm. */
1421
+ private thermoLayers;
1422
+ /**
1423
+ * Under the UV blacklight: every pixel layer's daylight paint dims to `uvLight.ambient`, any
1424
+ * fluorescent ink (`doc.fluor[id]`) glows in its own colour, and each fluorescent layer casts a
1425
+ * Linear Dodge (`::uv`) spill layer directly above it.
1426
+ */
1427
+ private uvLayers;
1318
1428
  /** The layers with thick paint lit (each thick layer's pixels replaced by its lit tiles). */
1319
1429
  private litLayers;
1320
1430
  /**
@@ -1692,6 +1802,63 @@ export declare class PaintEditorController {
1692
1802
  removeMixingPad(id: string): SavedMixingPadInfo;
1693
1803
  private mixingPadJSON;
1694
1804
  private readMixingPad;
1805
+ /**
1806
+ * Hears each mutation once it has settled (`settle` seconds after the user's last mutation
1807
+ * stroke). Registering a listener marks an agent as connected (`state.hasMutationAgent`).
1808
+ */
1809
+ onMutationRequest(listener: PaintMutationRequestListener): () => void;
1810
+ /** Marks whether a mutation-answering agent is connected (for hosts that wake agents via tools). */
1811
+ setMutationAgentConnected(connected: boolean): void;
1812
+ /** Attaches a `PaintDirector` so `answerMutation` and `readMutation` animate the agent's cursor. */
1813
+ setMutationDirector(director: PaintDirector | null): void;
1814
+ /** Lists pending mutations (or all mutations when `includeAll` is true). */
1815
+ listMutations(options?: {
1816
+ includeAll?: boolean;
1817
+ }): PaintMutationInfo[];
1818
+ /** One mutation by id (`undefined` when unknown). */
1819
+ getMutation(id: string): PaintMutationInfo | undefined;
1820
+ /**
1821
+ * Claims a mutation (`id`) and starts its fade. Returns the strokes (points, colour, brush), a
1822
+ * PNG of the area around them (`cropPng`), and a preview of the whole painting (`canvasPng`) so
1823
+ * the agent can choose where to answer. Refused if already claimed, answered or declined.
1824
+ */
1825
+ readMutation(id: string, options?: {
1826
+ actor?: string;
1827
+ padding?: number;
1828
+ maxSide?: number;
1829
+ }): PaintMutationReadResult;
1830
+ /**
1831
+ * Paints the agent's answer to a claimed mutation (`id`). When a `PaintDirector` is attached (or
1832
+ * passed in `options`), the agent's cursor is seen painting live; all strokes commit as one undo
1833
+ * step with origin `agent:<id>`. A second answer to the same id is refused.
1834
+ */
1835
+ answerMutation(id: string, input: PaintMutationAnswerInput, options?: {
1836
+ director?: PaintDirector;
1837
+ actor?: PresenceActorInfo;
1838
+ }): Promise<PaintMutationAnswerResult>;
1839
+ /**
1840
+ * Gives up on a mutation (`id`) without answering. The user's paint stays as ordinary paint (wet
1841
+ * strokes stay in the wet layer, and dry temporary layers merge down into their target layer).
1842
+ */
1843
+ declineMutation(id: string, reason?: string): PaintMutationInfo;
1844
+ /**
1845
+ * Advances mutation timers (`settle`, `patience`, and dry temporary-layer `fade`) by `seconds`
1846
+ * of simulated time. Called automatically from `tickMutations` and `advanceWet`.
1847
+ */
1848
+ advanceMutations(seconds: number): void;
1849
+ /** Steps mutation timers with the wall clock (`performance.now()`, in ms). */
1850
+ tickMutations(now: number): void;
1851
+ private toMutationInfo;
1852
+ private recordMutationStroke;
1853
+ private holdMutationEvaporation;
1854
+ private releaseMutationEvaporation;
1855
+ private finishWetMutationsOnDry;
1856
+ private expireMutation;
1857
+ private keepMutationPaint;
1858
+ private mergeTempLayerIntoTarget;
1859
+ private removeTempMutationLayer;
1860
+ private reconcileMutationsAfterUndo;
1861
+ private resetMutations;
1695
1862
  /** A layer's tile surface (live while stroking), for thumbnails. */
1696
1863
  layerSurface(id: string): TiledSurface | null;
1697
1864
  private proposalsStore;
@@ -22,6 +22,7 @@ import type { MultiColorSettings, PaintBrush } from './brushes.cjs';
22
22
  import { type EffectPaintPixels } from './effect-paint.cjs';
23
23
  import { type PenPoint, type StrokeCopy } from './engine.cjs';
24
24
  import { type PaperGrain } from './paper.cjs';
25
+ import type { FilmBuffer } from './thick.cjs';
25
26
  import type { StickerArtLookup } from './sticker-art.cjs';
26
27
  export interface PaintDabStrokeOptions {
27
28
  /** Where the stroke reads and writes pixels. */
@@ -42,6 +43,8 @@ export interface PaintDabStrokeOptions {
42
43
  effect?: EffectPaintPixels | null;
43
44
  /** Loaded sticker art, for a stamp tip of a sticker from the sticker library (`brush.stamp`). */
44
45
  stampArt?: StickerArtLookup;
46
+ /** Thin-film iridescence channel for dab / mixer brushes with `iridescence`. */
47
+ film?: FilmBuffer | null;
45
48
  }
46
49
  /** Colours of a multi-colour brush, resolved against the paint and secondary colours. */
47
50
  export declare function multiColorPalette(mc: MultiColorSettings, color: RgbTriple, secondary: RgbTriple): RgbTriple[];
@@ -22,6 +22,7 @@ import type { MultiColorSettings, PaintBrush } from './brushes.js';
22
22
  import { type EffectPaintPixels } from './effect-paint.js';
23
23
  import { type PenPoint, type StrokeCopy } from './engine.js';
24
24
  import { type PaperGrain } from './paper.js';
25
+ import type { FilmBuffer } from './thick.js';
25
26
  import type { StickerArtLookup } from './sticker-art.js';
26
27
  export interface PaintDabStrokeOptions {
27
28
  /** Where the stroke reads and writes pixels. */
@@ -42,6 +43,8 @@ export interface PaintDabStrokeOptions {
42
43
  effect?: EffectPaintPixels | null;
43
44
  /** Loaded sticker art, for a stamp tip of a sticker from the sticker library (`brush.stamp`). */
44
45
  stampArt?: StickerArtLookup;
46
+ /** Thin-film iridescence channel for dab / mixer brushes with `iridescence`. */
47
+ film?: FilmBuffer | null;
45
48
  }
46
49
  /** Colours of a multi-colour brush, resolved against the paint and secondary colours. */
47
50
  export declare function multiColorPalette(mc: MultiColorSettings, color: RgbTriple, secondary: RgbTriple): RgbTriple[];
@@ -7,7 +7,7 @@
7
7
  * Saved documents are JSON plus PNG assets, through the image editor's layer serializer (so the
8
8
  * same layer tree loads in both editors), with the paper as plain settings.
9
9
  */
10
- import type { RasterGroupLayer, RasterLayer, RasterPixelLayer } from '../raster/layers.cjs';
10
+ import type { RasterGroupLayer, RasterPixelLayer } from '../raster/layers.cjs';
11
11
  import { type TiledSurface } from '../raster/surface.cjs';
12
12
  import { type PaperSettings } from './paper.cjs';
13
13
  import type { RecordedStroke } from './strokes.cjs';
@@ -15,10 +15,14 @@ import type { WetEvent } from './wet.cjs';
15
15
  import type { ThickChannels } from './thick.cjs';
16
16
  import type { Sticker } from './stickers.cjs';
17
17
  import type { PaintEffectSource, PaintPaperEffect } from './effects.cjs';
18
- export type PaintPixelLayer = RasterPixelLayer;
18
+ import type { HeatField } from './heat.cjs';
19
+ export type PaintPixelLayer = RasterPixelLayer & {
20
+ /** A temporary layer for a dry mutation stroke (fades on claim, merges down on timeout or save). */
21
+ temporary?: boolean;
22
+ };
19
23
  export type PaintGroupLayer = RasterGroupLayer<PaintLayer>;
20
24
  /** A layer of the paint editor: the shared stack's pixel layers and groups. */
21
- export type PaintLayer = RasterLayer;
25
+ export type PaintLayer = PaintPixelLayer | PaintGroupLayer;
22
26
  /** Canvas size limits (pixels per side). */
23
27
  export declare const MIN_CANVAS_SIDE = 1;
24
28
  export declare const MAX_CANVAS_SIDE = 16384;
@@ -51,6 +55,12 @@ export interface PaintDocumentState {
51
55
  liveStrokes?: Readonly<Record<string, RecordedStroke[]>>;
52
56
  /** Light paint: each layer's light channel, 0..255 per pixel in layer space (layers without light are missing). See `light.ts`. */
53
57
  light?: Readonly<Record<string, TiledSurface>>;
58
+ /** UV-fluorescent ink: each layer's 4-channel RGBA fluorescence surface in layer space (layers without fluorescence are missing). See `heat.ts`. */
59
+ fluor?: Readonly<Record<string, TiledSurface>>;
60
+ /** Thermochromic ink: each layer's 4-channel RGBA thermochromic surface (`[hotR, hotG, hotB, thresholdByte]`) in layer space. See `heat.ts`. */
61
+ thermo?: Readonly<Record<string, TiledSurface>>;
62
+ /** Coarse document-level heat field (1/4 resolution) for thermochromic ink and wet-paint drying. See `heat.ts`. */
63
+ heat?: HeatField;
54
64
  /** Thick paint: each layer's height and material channels (layers without relief are missing). */
55
65
  thick?: Readonly<Record<string, ThickChannels>>;
56
66
  /** Effect layers: the provider effect each one is drawn from (layers that are gone are ignored). */
@@ -7,7 +7,7 @@
7
7
  * Saved documents are JSON plus PNG assets, through the image editor's layer serializer (so the
8
8
  * same layer tree loads in both editors), with the paper as plain settings.
9
9
  */
10
- import type { RasterGroupLayer, RasterLayer, RasterPixelLayer } from '../raster/layers.js';
10
+ import type { RasterGroupLayer, RasterPixelLayer } from '../raster/layers.js';
11
11
  import { type TiledSurface } from '../raster/surface.js';
12
12
  import { type PaperSettings } from './paper.js';
13
13
  import type { RecordedStroke } from './strokes.js';
@@ -15,10 +15,14 @@ import type { WetEvent } from './wet.js';
15
15
  import type { ThickChannels } from './thick.js';
16
16
  import type { Sticker } from './stickers.js';
17
17
  import type { PaintEffectSource, PaintPaperEffect } from './effects.js';
18
- export type PaintPixelLayer = RasterPixelLayer;
18
+ import type { HeatField } from './heat.js';
19
+ export type PaintPixelLayer = RasterPixelLayer & {
20
+ /** A temporary layer for a dry mutation stroke (fades on claim, merges down on timeout or save). */
21
+ temporary?: boolean;
22
+ };
19
23
  export type PaintGroupLayer = RasterGroupLayer<PaintLayer>;
20
24
  /** A layer of the paint editor: the shared stack's pixel layers and groups. */
21
- export type PaintLayer = RasterLayer;
25
+ export type PaintLayer = PaintPixelLayer | PaintGroupLayer;
22
26
  /** Canvas size limits (pixels per side). */
23
27
  export declare const MIN_CANVAS_SIDE = 1;
24
28
  export declare const MAX_CANVAS_SIDE = 16384;
@@ -51,6 +55,12 @@ export interface PaintDocumentState {
51
55
  liveStrokes?: Readonly<Record<string, RecordedStroke[]>>;
52
56
  /** Light paint: each layer's light channel, 0..255 per pixel in layer space (layers without light are missing). See `light.ts`. */
53
57
  light?: Readonly<Record<string, TiledSurface>>;
58
+ /** UV-fluorescent ink: each layer's 4-channel RGBA fluorescence surface in layer space (layers without fluorescence are missing). See `heat.ts`. */
59
+ fluor?: Readonly<Record<string, TiledSurface>>;
60
+ /** Thermochromic ink: each layer's 4-channel RGBA thermochromic surface (`[hotR, hotG, hotB, thresholdByte]`) in layer space. See `heat.ts`. */
61
+ thermo?: Readonly<Record<string, TiledSurface>>;
62
+ /** Coarse document-level heat field (1/4 resolution) for thermochromic ink and wet-paint drying. See `heat.ts`. */
63
+ heat?: HeatField;
54
64
  /** Thick paint: each layer's height and material channels (layers without relief are missing). */
55
65
  thick?: Readonly<Record<string, ThickChannels>>;
56
66
  /** Effect layers: the provider effect each one is drawn from (layers that are gone are ignored). */
@@ -20,6 +20,7 @@ import type { PaintBrush } from './brushes.cjs';
20
20
  import { type EffectPaintPixels } from './effect-paint.cjs';
21
21
  import type { StickerArtLookup } from './sticker-art.cjs';
22
22
  import { type PaperGrain } from './paper.cjs';
23
+ import type { FilmBuffer } from './thick.cjs';
23
24
  /** One pen sample in document pixels. Every channel but x and y is optional. */
24
25
  export interface PenPoint {
25
26
  x: number;
@@ -136,6 +137,8 @@ export interface DryStrokeOptions {
136
137
  * tip grain (`brush.tipGrain.source`); without it such a tip paints round, and the grain is left out.
137
138
  */
138
139
  stampArt?: StickerArtLookup;
140
+ /** Thin-film iridescence channel for dry brushes with `iridescence` (or eraser). */
141
+ film?: FilmBuffer | null;
139
142
  }
140
143
  /**
141
144
  * One dab-engine stroke (dab and eraser brushes): smoothing, the dab walk of every copy, grain and
@@ -20,6 +20,7 @@ import type { PaintBrush } from './brushes.js';
20
20
  import { type EffectPaintPixels } from './effect-paint.js';
21
21
  import type { StickerArtLookup } from './sticker-art.js';
22
22
  import { type PaperGrain } from './paper.js';
23
+ import type { FilmBuffer } from './thick.js';
23
24
  /** One pen sample in document pixels. Every channel but x and y is optional. */
24
25
  export interface PenPoint {
25
26
  x: number;
@@ -136,6 +137,8 @@ export interface DryStrokeOptions {
136
137
  * tip grain (`brush.tipGrain.source`); without it such a tip paints round, and the grain is left out.
137
138
  */
138
139
  stampArt?: StickerArtLookup;
140
+ /** Thin-film iridescence channel for dry brushes with `iridescence` (or eraser). */
141
+ film?: FilmBuffer | null;
139
142
  }
140
143
  /**
141
144
  * One dab-engine stroke (dab and eraser brushes): smoothing, the dab walk of every copy, grain and