@pixerate/editor 0.273.0 → 0.275.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) 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/{chunk-2XDNF73E.js → chunk-43XJC3PP.js} +1 -1
  20. package/dist/{chunk-GHGBIAWQ.cjs → chunk-6QNDGMK7.cjs} +263 -45
  21. package/dist/{chunk-2F3YDXCN.js → chunk-75SYKUOT.js} +1 -1
  22. package/dist/{chunk-M3RZOF3G.cjs → chunk-C6VB7CPY.cjs} +1 -1
  23. package/dist/{chunk-N5IMDAHM.cjs → chunk-DHC4E2DU.cjs} +89 -37
  24. package/dist/{chunk-NAHJV3O6.js → chunk-G5TCMKX5.js} +28 -11
  25. package/dist/{chunk-DZVRH63Y.js → chunk-IEEY5RJV.js} +1 -1
  26. package/dist/{chunk-7W2EQQBP.cjs → chunk-JDXWLCXF.cjs} +6 -6
  27. package/dist/{chunk-N3SKPQEH.js → chunk-LMG57L5A.js} +62 -10
  28. package/dist/{chunk-5HP2VSAS.js → chunk-MSWLJI4G.js} +1 -1
  29. package/dist/{chunk-SNAFFCAX.cjs → chunk-OLZXXAER.cjs} +221 -196
  30. package/dist/{chunk-YJANF2BL.cjs → chunk-RAPNRK4R.cjs} +86 -86
  31. package/dist/{chunk-CLB2I2FU.js → chunk-RJGW3RQS.js} +2 -2
  32. package/dist/{chunk-FXVCB6YG.cjs → chunk-T6KNJVBP.cjs} +35 -35
  33. package/dist/{chunk-ZEI7ZEA4.js → chunk-Y2ISSVPH.js} +34 -9
  34. package/dist/{chunk-3JKCREEU.cjs → chunk-Y354ETAE.cjs} +76 -59
  35. package/dist/{chunk-TNLKFW7D.cjs → chunk-YYZC2EQO.cjs} +23 -23
  36. package/dist/{chunk-UXYCY3HP.js → chunk-ZM7IKQ4X.js} +239 -21
  37. package/dist/image-editor/index.cjs +789 -789
  38. package/dist/image-editor/index.js +5 -5
  39. package/dist/paint/index.cjs +2 -2
  40. package/dist/paint/index.js +1 -1
  41. package/dist/paint/wet/world.d.cts +15 -2
  42. package/dist/paint/wet/world.d.ts +15 -2
  43. package/dist/paint-editor/brush-controls.d.cts +7 -0
  44. package/dist/paint-editor/brush-controls.d.ts +7 -0
  45. package/dist/paint-editor/brush-model.d.cts +59 -1
  46. package/dist/paint-editor/brush-model.d.ts +59 -1
  47. package/dist/paint-editor/collaboration.d.cts +6 -1
  48. package/dist/paint-editor/collaboration.d.ts +6 -1
  49. package/dist/paint-editor/controller.d.cts +199 -1
  50. package/dist/paint-editor/controller.d.ts +199 -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/evolution-tools.d.cts +48 -0
  58. package/dist/paint-editor/evolution-tools.d.ts +48 -0
  59. package/dist/paint-editor/evolution.d.cts +175 -0
  60. package/dist/paint-editor/evolution.d.ts +175 -0
  61. package/dist/paint-editor/heat.d.cts +156 -0
  62. package/dist/paint-editor/heat.d.ts +156 -0
  63. package/dist/paint-editor/index.cjs +4905 -1525
  64. package/dist/paint-editor/index.d.cts +5 -0
  65. package/dist/paint-editor/index.d.ts +5 -0
  66. package/dist/paint-editor/index.js +10952 -7572
  67. package/dist/paint-editor/mutation-tools.d.cts +13 -0
  68. package/dist/paint-editor/mutation-tools.d.ts +13 -0
  69. package/dist/paint-editor/mutation.d.cts +67 -0
  70. package/dist/paint-editor/mutation.d.ts +67 -0
  71. package/dist/paint-editor/presence.d.cts +7 -1
  72. package/dist/paint-editor/presence.d.ts +7 -1
  73. package/dist/paint-editor/procedural.d.cts +3 -0
  74. package/dist/paint-editor/procedural.d.ts +3 -0
  75. package/dist/paint-editor/stroke.d.cts +32 -2
  76. package/dist/paint-editor/stroke.d.ts +32 -2
  77. package/dist/paint-editor/thick.d.cts +48 -7
  78. package/dist/paint-editor/thick.d.ts +48 -7
  79. package/dist/paint-editor/ui.d.cts +36 -1
  80. package/dist/paint-editor/ui.d.ts +36 -1
  81. package/dist/paint-editor/wet.d.cts +8 -1
  82. package/dist/paint-editor/wet.d.ts +8 -1
  83. package/dist/vector/index.cjs +999 -999
  84. package/dist/vector/index.js +5 -5
  85. package/package.json +1 -1
  86. package/skills/README.md +1 -0
  87. package/skills/pixerate-brushes/SKILL.md +3 -0
  88. package/skills/pixerate-paint-editor/SKILL.md +36 -53
  89. package/skills/pixerate-paint-editor/references/brush-facets.md +3 -0
  90. package/skills/pixerate-paint-editor/references/mutation.md +25 -0
  91. package/skills/pixerate-paint-editor/references/oils.md +8 -1
  92. package/skills/pixerate-paint-editor/references/smart-inks.md +51 -0
  93. package/skills/pixerate-paint-mutation/SKILL.md +76 -0
  94. package/skills/pixerate-paint-mutation/evals/decline-unanswered.json +58 -0
  95. package/skills/pixerate-paint-mutation/evals/dry-ink-answer.json +74 -0
  96. package/skills/pixerate-paint-mutation/evals/wet-wash-answer.json +76 -0
  97. package/skills/pixerate-paint-oil-impasto/SKILL.md +2 -1
@@ -850,7 +850,7 @@ var S2 = SLOT_SIDE;
850
850
  var SNAP_PLANES = SET_PLANES + MASS_CHANNELS;
851
851
  var noLight = () => new (0, _chunk474IQG74cjs.TiledSurface)(1, /* @__PURE__ */ new Map(), 0);
852
852
  function cloneGhost(g) {
853
- return g ? { pigment: [...g.pigment], remaining: g.remaining, total: g.total, linger: g.linger } : null;
853
+ return g ? { pigment: [...g.pigment], remaining: g.remaining, total: g.total, linger: g.linger, ...g.trigger === "agent" ? { trigger: "agent" } : {} } : null;
854
854
  }
855
855
  function newLane(id, index, params, flow, flowStrength, base, light, ghost) {
856
856
  const l = _nullishCoalesce(light, () => ( noLight()));
@@ -1119,21 +1119,59 @@ var WetWorld = (_class = class {
1119
1119
  }
1120
1120
  /**
1121
1121
  * Starts a ghost paint fade on `id` with `pigment` per unit tracer and `linger` seconds. If an
1122
- * earlier ghost of a different colour is still fading on the layer, it finishes at once first.
1122
+ * earlier ghost of a different colour is still fading on the layer, it finishes at once first
1123
+ * (or keeps its paint when it was an unclaimed agent-triggered ghost).
1123
1124
  */
1124
- startGhost(id, pigment, linger, stepsPerSecond = 120) {
1125
+ startGhost(id, pigment, linger, stepsPerSecond = 120, trigger = "immediate") {
1125
1126
  this.requireIdle();
1126
1127
  const lane = this.laneOf(id);
1127
1128
  const p = [Math.fround(pigment[0]), Math.fround(pigment[1]), Math.fround(pigment[2]), Math.fround(pigment[3])];
1128
1129
  if (lane.ghost) {
1129
1130
  const g = lane.ghost.pigment;
1130
1131
  const same = Math.abs(g[0] - p[0]) <= 1e-6 && Math.abs(g[1] - p[1]) <= 1e-6 && Math.abs(g[2] - p[2]) <= 1e-6 && Math.abs(g[3] - p[3]) <= 1e-6;
1131
- if (!same) this.fadeGhost(id, 1);
1132
+ if (!same) {
1133
+ if (lane.ghost.trigger === "agent") this.keepGhost(id);
1134
+ else this.fadeGhost(id, 1);
1135
+ }
1132
1136
  }
1133
1137
  const total = Math.max(1, Math.round(linger * stepsPerSecond / FREE_INTERVAL));
1134
- lane.ghost = { pigment: p, remaining: total, total, linger };
1138
+ lane.ghost = { pigment: p, remaining: total, total, linger, ...trigger === "agent" ? { trigger: "agent" } : {} };
1135
1139
  lane.lit = false;
1136
1140
  }
1141
+ /**
1142
+ * Releases a held (`trigger: 'agent'`) ghost on `id` so its fade starts counting down on the
1143
+ * simulation clock over `linger` seconds (default: the lane's configured `linger`).
1144
+ */
1145
+ releaseGhost(id, linger, stepsPerSecond = 120) {
1146
+ this.requireIdle();
1147
+ const lane = this.laneOf(id);
1148
+ if (!lane.ghost) return;
1149
+ const l = _nullishCoalesce(linger, () => ( lane.ghost.linger));
1150
+ const total = Math.max(1, Math.round(l * stepsPerSecond / FREE_INTERVAL));
1151
+ lane.ghost = { pigment: lane.ghost.pigment, remaining: total, total, linger: l };
1152
+ }
1153
+ /**
1154
+ * Drops a ghost lane's tracer (`P_L`, `P_DL`) without subtracting its pigment, so an unclaimed
1155
+ * or declined wet mutation stroke stays as ordinary wet paint.
1156
+ */
1157
+ keepGhost(id) {
1158
+ this.requireIdle();
1159
+ const lane = this.laneOf(id);
1160
+ if (!lane.ghost) return;
1161
+ const f = this.f32;
1162
+ for (const key of this.active) {
1163
+ if (this.laneIndexOf(key) !== lane.index) continue;
1164
+ const slot = this.slotOf(key);
1165
+ if (slot < 0) continue;
1166
+ const b = this.slotBase(slot);
1167
+ const cur = b + this.parity * SET_PLANES * PLANE;
1168
+ f.fill(0, cur + P_L * PLANE, cur + (P_L + 1) * PLANE);
1169
+ f.fill(0, b + P_DL * PLANE, b + (P_DL + 1) * PLANE);
1170
+ this.gpuDirtySlot(slot, 1, WET_TILE);
1171
+ }
1172
+ lane.ghost = null;
1173
+ this.refreshHalos();
1174
+ }
1137
1175
  pack(lane) {
1138
1176
  packWetParams(lane.params, _nullishCoalesce(this.paper, () => ( DEFAULT_WET_PAPER)), this.tilt, lane.flowStrength, this.prm);
1139
1177
  this.f32.set(this.prm, this.layout.params + lane.index * PARAM_FLOATS * 4 >> 2);
@@ -1367,7 +1405,7 @@ var WetWorld = (_class = class {
1367
1405
  for (const key of wakeups) woke = this.activate(key) || woke;
1368
1406
  if (this.clock % FREE_INTERVAL === 0) {
1369
1407
  for (const lane of this.lanes) {
1370
- if (!_optionalChain([lane, 'optionalAccess', _19 => _19.ghost])) continue;
1408
+ if (!_optionalChain([lane, 'optionalAccess', _19 => _19.ghost]) || lane.ghost.trigger === "agent") continue;
1371
1409
  const g = lane.ghost;
1372
1410
  const amount = g.remaining <= 1 ? 1 : 1 / g.remaining;
1373
1411
  g.remaining = Math.max(0, g.remaining - 1);
@@ -1931,12 +1969,16 @@ var WetWorld = (_class = class {
1931
1969
  continue;
1932
1970
  }
1933
1971
  const f = this.f32;
1934
- const pig = _nullishCoalesce(_optionalChain([lane, 'access', _22 => _22.ghost, 'optionalAccess', _23 => _23.pigment]), () => ( null));
1972
+ const pig = lane.ghost && lane.ghost.trigger !== "agent" ? lane.ghost.pigment : null;
1973
+ const clearTracer = Boolean(lane.ghost && lane.ghost.trigger === "agent");
1935
1974
  for (let y = y0; y < y1; y++) {
1936
1975
  for (let x = x0; x < x1; x++) {
1937
1976
  const c = this.cell(lane, x, y);
1938
1977
  const d = this.single(c) + P_D0 * PLANE;
1939
- if (pig) {
1978
+ if (clearTracer) {
1979
+ f[c + P_L * PLANE] = 0;
1980
+ f[d + 4 * PLANE] = 0;
1981
+ } else if (pig) {
1940
1982
  const tl = f[c + P_L * PLANE];
1941
1983
  const dl = f[d + 4 * PLANE];
1942
1984
  if (tl > 0 || dl > 0) {
@@ -1980,7 +2022,17 @@ var WetWorld = (_class = class {
1980
2022
  bakeKey(key, all) {
1981
2023
  const lane = this.lanes[this.laneIndexOf(key)];
1982
2024
  if (lane.ghost) {
1983
- this.fadeGhostKey(key, lane, 1);
2025
+ if (lane.ghost.trigger === "agent") {
2026
+ const slot = this.slotOf(key);
2027
+ if (slot >= 0) {
2028
+ const b = this.slotBase(slot);
2029
+ const cur = b + this.parity * SET_PLANES * PLANE;
2030
+ this.f32.fill(0, cur + P_L * PLANE, cur + (P_L + 1) * PLANE);
2031
+ this.f32.fill(0, b + P_DL * PLANE, b + (P_DL + 1) * PLANE);
2032
+ }
2033
+ } else {
2034
+ this.fadeGhostKey(key, lane, 1);
2035
+ }
1984
2036
  if (!this.hasGhostTracer(lane)) lane.ghost = null;
1985
2037
  }
1986
2038
  const [tx, ty] = this.tileXY(key);
@@ -1997,7 +2049,7 @@ var WetWorld = (_class = class {
1997
2049
  lightChanged = true;
1998
2050
  }
1999
2051
  }
2000
- const src = _nullishCoalesce(_optionalChain([lane, 'access', _24 => _24.base, 'access', _25 => _25.tiles, 'access', _26 => _26.get, 'call', _27 => _27(tk), 'optionalAccess', _28 => _28.data]), () => ( null));
2052
+ const src = _nullishCoalesce(_optionalChain([lane, 'access', _22 => _22.base, 'access', _23 => _23.tiles, 'access', _24 => _24.get, 'call', _25 => _25(tk), 'optionalAccess', _26 => _26.data]), () => ( null));
2001
2053
  const out = this.shadeSlot(key, src, all ? "bake-all" : "bake");
2002
2054
  if (!out) return lightChanged;
2003
2055
  const tiles = new Map(lane.base.tiles);
@@ -2062,7 +2114,7 @@ var WetWorld = (_class = class {
2062
2114
  if (this.laneIndexOf(key) !== lane.index) continue;
2063
2115
  const tk = this.tileKeyOf(key);
2064
2116
  let out;
2065
- if (lane.ghost) {
2117
+ if (lane.ghost && lane.ghost.trigger !== "agent") {
2066
2118
  const b = this.slotBase(this.slotOf(key));
2067
2119
  const cur = b + this.parity * SET_PLANES * PLANE;
2068
2120
  const savedP = this.f32.slice(cur + P_P0 * PLANE, cur + (P_P0 + MASS_CHANNELS) * PLANE);
@@ -2087,11 +2139,11 @@ var WetWorld = (_class = class {
2087
2139
  }
2088
2140
  }
2089
2141
  }
2090
- out = this.shadeSlot(key, _nullishCoalesce(_optionalChain([lane, 'access', _29 => _29.base, 'access', _30 => _30.tiles, 'access', _31 => _31.get, 'call', _32 => _32(tk), 'optionalAccess', _33 => _33.data]), () => ( null)), "bake-all");
2142
+ out = this.shadeSlot(key, _nullishCoalesce(_optionalChain([lane, 'access', _27 => _27.base, 'access', _28 => _28.tiles, 'access', _29 => _29.get, 'call', _30 => _30(tk), 'optionalAccess', _31 => _31.data]), () => ( null)), "bake-all");
2091
2143
  this.f32.set(savedP, cur + P_P0 * PLANE);
2092
2144
  this.f32.set(savedD, b + P_D0 * PLANE);
2093
2145
  } else {
2094
- out = this.shadeSlot(key, _nullishCoalesce(_optionalChain([lane, 'access', _34 => _34.base, 'access', _35 => _35.tiles, 'access', _36 => _36.get, 'call', _37 => _37(tk), 'optionalAccess', _38 => _38.data]), () => ( null)), "bake-all");
2146
+ out = this.shadeSlot(key, _nullishCoalesce(_optionalChain([lane, 'access', _32 => _32.base, 'access', _33 => _33.tiles, 'access', _34 => _34.get, 'call', _35 => _35(tk), 'optionalAccess', _36 => _36.data]), () => ( null)), "bake-all");
2095
2147
  }
2096
2148
  if (!out) continue;
2097
2149
  const t = _chunk474IQG74cjs.finishTile.call(void 0, out, 4, 0);
@@ -2215,14 +2267,14 @@ var WetWorld = (_class = class {
2215
2267
  display(id, maxTiles = Infinity) {
2216
2268
  const lane = this.laneOf(id);
2217
2269
  const keys = this.active.filter((k) => this.laneIndexOf(k) === lane.index);
2218
- const stale = keys.filter((k) => _optionalChain([lane, 'access', _39 => _39.shaded, 'access', _40 => _40.get, 'call', _41 => _41(this.tileKeyOf(k)), 'optionalAccess', _42 => _42.stamp]) !== lane.stamp);
2219
- stale.sort((a, b) => Number(lane.urgent.has(this.tileKeyOf(b))) - Number(lane.urgent.has(this.tileKeyOf(a))) || (_nullishCoalesce(_optionalChain([lane, 'access', _43 => _43.shaded, 'access', _44 => _44.get, 'call', _45 => _45(this.tileKeyOf(a)), 'optionalAccess', _46 => _46.stamp]), () => ( 0))) - (_nullishCoalesce(_optionalChain([lane, 'access', _47 => _47.shaded, 'access', _48 => _48.get, 'call', _49 => _49(this.tileKeyOf(b)), 'optionalAccess', _50 => _50.stamp]), () => ( 0))));
2270
+ const stale = keys.filter((k) => _optionalChain([lane, 'access', _37 => _37.shaded, 'access', _38 => _38.get, 'call', _39 => _39(this.tileKeyOf(k)), 'optionalAccess', _40 => _40.stamp]) !== lane.stamp);
2271
+ stale.sort((a, b) => Number(lane.urgent.has(this.tileKeyOf(b))) - Number(lane.urgent.has(this.tileKeyOf(a))) || (_nullishCoalesce(_optionalChain([lane, 'access', _41 => _41.shaded, 'access', _42 => _42.get, 'call', _43 => _43(this.tileKeyOf(a)), 'optionalAccess', _44 => _44.stamp]), () => ( 0))) - (_nullishCoalesce(_optionalChain([lane, 'access', _45 => _45.shaded, 'access', _46 => _46.get, 'call', _47 => _47(this.tileKeyOf(b)), 'optionalAccess', _48 => _48.stamp]), () => ( 0))));
2220
2272
  const changed = [];
2221
2273
  let done = 0;
2222
2274
  for (const key of stale) {
2223
2275
  if (done >= maxTiles) break;
2224
2276
  const tk = this.tileKeyOf(key);
2225
- const src = _nullishCoalesce(_optionalChain([lane, 'access', _51 => _51.base, 'access', _52 => _52.tiles, 'access', _53 => _53.get, 'call', _54 => _54(tk), 'optionalAccess', _55 => _55.data]), () => ( null));
2277
+ const src = _nullishCoalesce(_optionalChain([lane, 'access', _49 => _49.base, 'access', _50 => _50.tiles, 'access', _51 => _51.get, 'call', _52 => _52(tk), 'optionalAccess', _53 => _53.data]), () => ( null));
2226
2278
  const out = this.shadeSlot(key, src, "display");
2227
2279
  const tile = out ? _chunk474IQG74cjs.finishTile.call(void 0, out, 4, 0) : _nullishCoalesce(lane.base.tiles.get(tk), () => ( null));
2228
2280
  lane.shaded.set(tk, { stamp: lane.stamp, tile: _nullishCoalesce(tile, () => ( { data: new Uint8ClampedArray(0), uniform: 0 })) });
@@ -2259,7 +2311,7 @@ var WetWorld = (_class = class {
2259
2311
  for (const key2 of keys) {
2260
2312
  const tk = this.tileKeyOf(key2);
2261
2313
  live.add(tk);
2262
- if (_optionalChain([lane, 'access', _56 => _56.lightShaded, 'access', _57 => _57.get, 'call', _58 => _58(tk), 'optionalAccess', _59 => _59.stamp]) === lane.stamp) continue;
2314
+ if (_optionalChain([lane, 'access', _54 => _54.lightShaded, 'access', _55 => _55.get, 'call', _56 => _56(tk), 'optionalAccess', _57 => _57.stamp]) === lane.stamp) continue;
2263
2315
  const src = _nullishCoalesce(lane.light.tiles.get(tk), () => ( null));
2264
2316
  const out = this.shadeLightSlot(key2, src, "display");
2265
2317
  lane.lightShaded.set(tk, { stamp: lane.stamp, tile: out ? _chunk474IQG74cjs.finishTile.call(void 0, out, 1, 0) : src });
@@ -2457,7 +2509,7 @@ var WetWorld = (_class = class {
2457
2509
  if (!this.gpu || this.busyFlag && !plan || !plan && !this.gpuDecide()) return { ...this.display(id, maxCpuTiles), gpu: false };
2458
2510
  const lane = this.laneOf(id);
2459
2511
  const keys = this.active.filter((k) => this.laneIndexOf(k) === lane.index);
2460
- const stale = plan ? keys : keys.filter((k) => _optionalChain([lane, 'access', _60 => _60.shaded, 'access', _61 => _61.get, 'call', _62 => _62(this.tileKeyOf(k)), 'optionalAccess', _63 => _63.stamp]) !== lane.stamp);
2512
+ const stale = plan ? keys : keys.filter((k) => _optionalChain([lane, 'access', _58 => _58.shaded, 'access', _59 => _59.get, 'call', _60 => _60(this.tileKeyOf(k)), 'optionalAccess', _61 => _61.stamp]) !== lane.stamp);
2461
2513
  if (!stale.length) return { ...this.display(id, 0), gpu: true };
2462
2514
  let tiles;
2463
2515
  const stamp = lane.stamp;
@@ -2465,7 +2517,7 @@ var WetWorld = (_class = class {
2465
2517
  try {
2466
2518
  if (!plan) this.gpuSync();
2467
2519
  const parity = plan ? plan.parity + plan.done & 1 : this.parity;
2468
- tiles = await this.gpu.shade(this.gpuJobs(stale), parity, stale.map((k) => _nullishCoalesce(_optionalChain([base, 'access', _64 => _64.tiles, 'access', _65 => _65.get, 'call', _66 => _66(this.tileKeyOf(k)), 'optionalAccess', _67 => _67.data]), () => ( null))), [this.paperBytes[0], this.paperBytes[1], this.paperBytes[2]]);
2520
+ tiles = await this.gpu.shade(this.gpuJobs(stale), parity, stale.map((k) => _nullishCoalesce(_optionalChain([base, 'access', _62 => _62.tiles, 'access', _63 => _63.get, 'call', _64 => _64(this.tileKeyOf(k)), 'optionalAccess', _65 => _65.data]), () => ( null))), [this.paperBytes[0], this.paperBytes[1], this.paperBytes[2]]);
2469
2521
  } catch (e) {
2470
2522
  this.gpuStop(`Wet paint stopped using the GPU (${e.message}).`);
2471
2523
  return { ...this.display(id, maxCpuTiles), gpu: false };
@@ -2644,8 +2696,8 @@ function normalizeFlow(raw = {}, base = DEFAULT_FLOW) {
2644
2696
  }
2645
2697
  function flowField(spec, width, height) {
2646
2698
  const k = FLOW_ACCEL;
2647
- const cx = _nullishCoalesce(_optionalChain([spec, 'access', _68 => _68.center, 'optionalAccess', _69 => _69.x]), () => ( width / 2));
2648
- const cy = _nullishCoalesce(_optionalChain([spec, 'access', _70 => _70.center, 'optionalAccess', _71 => _71.y]), () => ( height / 2));
2699
+ const cx = _nullishCoalesce(_optionalChain([spec, 'access', _66 => _66.center, 'optionalAccess', _67 => _67.x]), () => ( width / 2));
2700
+ const cy = _nullishCoalesce(_optionalChain([spec, 'access', _68 => _68.center, 'optionalAccess', _69 => _69.y]), () => ( height / 2));
2649
2701
  const r0 = Math.max(width, height) / 2;
2650
2702
  switch (spec.kind) {
2651
2703
  case "none":
@@ -3108,7 +3160,7 @@ var UNIFORM = 64;
3108
3160
  var STORAGE = 128;
3109
3161
  function findWebGpu() {
3110
3162
  const nav = globalThis.navigator;
3111
- return _nullishCoalesce(_optionalChain([nav, 'optionalAccess', _72 => _72.gpu]), () => ( null));
3163
+ return _nullishCoalesce(_optionalChain([nav, 'optionalAccess', _70 => _70.gpu]), () => ( null));
3112
3164
  }
3113
3165
  async function createWetGpuEngine(gpu = findWebGpu()) {
3114
3166
  const no = (reason, adapter2 = null) => ({ engine: null, support: { supported: false, reason, adapter: adapter2, maxSlots: 0 } });
@@ -3120,7 +3172,7 @@ async function createWetGpuEngine(gpu = findWebGpu()) {
3120
3172
  return no(`WebGPU gave no adapter (${e.message}); wet paint runs on the CPU.`);
3121
3173
  }
3122
3174
  if (!adapter) return no("WebGPU gave no adapter (the GPU may be blocklisted); wet paint runs on the CPU.");
3123
- const name = [_optionalChain([adapter, 'access', _73 => _73.info, 'optionalAccess', _74 => _74.vendor]), _optionalChain([adapter, 'access', _75 => _75.info, 'optionalAccess', _76 => _76.architecture])].filter(Boolean).join(" ") || null;
3175
+ const name = [_optionalChain([adapter, 'access', _71 => _71.info, 'optionalAccess', _72 => _72.vendor]), _optionalChain([adapter, 'access', _73 => _73.info, 'optionalAccess', _74 => _74.architecture])].filter(Boolean).join(" ") || null;
3124
3176
  const binding = Math.min(_nullishCoalesce(adapter.limits.maxStorageBufferBindingSize, () => ( 0)), _nullishCoalesce(adapter.limits.maxBufferSize, () => ( 0)));
3125
3177
  const maxSlots = Math.min(MAX_WET_SLOTS, Math.floor(binding / SLOT_BYTES));
3126
3178
  if (maxSlots < 4) return no(`The GPU's storage buffers hold ${Math.max(0, maxSlots)} wet tiles; at least 4 are needed. Wet paint runs on the CPU.`, name);
@@ -3140,27 +3192,27 @@ async function createWetGpuEngine(gpu = findWebGpu()) {
3140
3192
  const engine = await WetGpuEngine.start(device, maxSlots, name);
3141
3193
  return { engine, support: { supported: true, reason: null, adapter: name, maxSlots } };
3142
3194
  } catch (e) {
3143
- _optionalChain([device, 'access', _77 => _77.destroy, 'optionalCall', _78 => _78()]);
3195
+ _optionalChain([device, 'access', _75 => _75.destroy, 'optionalCall', _76 => _76()]);
3144
3196
  return no(`The wet-media shaders did not compile on this GPU (${e.message}); wet paint runs on the CPU.`, name);
3145
3197
  }
3146
3198
  }
3147
3199
  async function wetGpuSupport(gpu = findWebGpu()) {
3148
3200
  const { engine, support } = await createWetGpuEngine(gpu);
3149
- _optionalChain([engine, 'optionalAccess', _79 => _79.destroy, 'call', _80 => _80()]);
3201
+ _optionalChain([engine, 'optionalAccess', _77 => _77.destroy, 'call', _78 => _78()]);
3150
3202
  return support;
3151
3203
  }
3152
3204
  async function pipeline(device, code, label) {
3153
- _optionalChain([device, 'access', _81 => _81.pushErrorScope, 'optionalCall', _82 => _82("validation")]);
3205
+ _optionalChain([device, 'access', _79 => _79.pushErrorScope, 'optionalCall', _80 => _80("validation")]);
3154
3206
  const module = device.createShaderModule({ code, label });
3155
- const info = await _optionalChain([module, 'access', _83 => _83.getCompilationInfo, 'optionalCall', _84 => _84()]);
3156
- const errors = _nullishCoalesce(_optionalChain([info, 'optionalAccess', _85 => _85.messages, 'access', _86 => _86.filter, 'call', _87 => _87((m) => m.type === "error")]), () => ( []));
3207
+ const info = await _optionalChain([module, 'access', _81 => _81.getCompilationInfo, 'optionalCall', _82 => _82()]);
3208
+ const errors = _nullishCoalesce(_optionalChain([info, 'optionalAccess', _83 => _83.messages, 'access', _84 => _84.filter, 'call', _85 => _85((m) => m.type === "error")]), () => ( []));
3157
3209
  if (errors.length) {
3158
- await _optionalChain([device, 'access', _88 => _88.popErrorScope, 'optionalCall', _89 => _89()]);
3210
+ await _optionalChain([device, 'access', _86 => _86.popErrorScope, 'optionalCall', _87 => _87()]);
3159
3211
  throw new Error(`${label}: ${errors.map((m) => `line ${m.lineNum}: ${m.message}`).join("; ")}`);
3160
3212
  }
3161
3213
  const desc = { layout: "auto", compute: { module, entryPoint: "main" }, label };
3162
3214
  const p = device.createComputePipelineAsync ? await device.createComputePipelineAsync(desc) : device.createComputePipeline(desc);
3163
- const err = await _optionalChain([device, 'access', _90 => _90.popErrorScope, 'optionalCall', _91 => _91()]);
3215
+ const err = await _optionalChain([device, 'access', _88 => _88.popErrorScope, 'optionalCall', _89 => _89()]);
3164
3216
  if (err) throw new Error(`${label}: ${err.message}`);
3165
3217
  return p;
3166
3218
  }
@@ -3186,8 +3238,8 @@ var WetGpuEngine = (_class3 = class _WetGpuEngine {
3186
3238
  return b;
3187
3239
  });
3188
3240
  this.shadeUni = d.createBuffer({ size: 16, usage: UNIFORM | COPY_DST, label: "wet shade" });
3189
- void _optionalChain([device, 'access', _92 => _92.lost, 'optionalAccess', _93 => _93.then, 'call', _94 => _94((info) => {
3190
- this.lostReason = _optionalChain([info, 'optionalAccess', _95 => _95.message]) || _optionalChain([info, 'optionalAccess', _96 => _96.reason]) || "the GPU device was lost";
3241
+ void _optionalChain([device, 'access', _90 => _90.lost, 'optionalAccess', _91 => _91.then, 'call', _92 => _92((info) => {
3242
+ this.lostReason = _optionalChain([info, 'optionalAccess', _93 => _93.message]) || _optionalChain([info, 'optionalAccess', _94 => _94.reason]) || "the GPU device was lost";
3191
3243
  })]);
3192
3244
  }
3193
3245
 
@@ -3221,7 +3273,7 @@ var WetGpuEngine = (_class3 = class _WetGpuEngine {
3221
3273
  }
3222
3274
  /** Bytes of GPU memory in use. */
3223
3275
  memoryBytes() {
3224
- return this.state.size + this.jobs.size + (_nullishCoalesce(_optionalChain([this, 'access', _97 => _97.srcTiles, 'optionalAccess', _98 => _98.size]), () => ( 0))) + (_nullishCoalesce(_optionalChain([this, 'access', _99 => _99.outTiles, 'optionalAccess', _100 => _100.size]), () => ( 0))) * 2;
3276
+ return this.state.size + this.jobs.size + (_nullishCoalesce(_optionalChain([this, 'access', _95 => _95.srcTiles, 'optionalAccess', _96 => _96.size]), () => ( 0))) + (_nullishCoalesce(_optionalChain([this, 'access', _97 => _97.outTiles, 'optionalAccess', _98 => _98.size]), () => ( 0))) * 2;
3225
3277
  }
3226
3278
  /** Slots the state buffer holds now. */
3227
3279
  get capacity() {
@@ -3370,9 +3422,9 @@ var WetGpuEngine = (_class3 = class _WetGpuEngine {
3370
3422
  const d = this.device;
3371
3423
  const tileBytes = TILE_WORDS * 4;
3372
3424
  if (n > this.tileCap || !this.outTiles || !this.outRead || !this.srcTiles) {
3373
- _optionalChain([this, 'access', _101 => _101.srcTiles, 'optionalAccess', _102 => _102.destroy, 'call', _103 => _103()]);
3374
- _optionalChain([this, 'access', _104 => _104.outTiles, 'optionalAccess', _105 => _105.destroy, 'call', _106 => _106()]);
3375
- _optionalChain([this, 'access', _107 => _107.outRead, 'optionalAccess', _108 => _108.destroy, 'call', _109 => _109()]);
3425
+ _optionalChain([this, 'access', _99 => _99.srcTiles, 'optionalAccess', _100 => _100.destroy, 'call', _101 => _101()]);
3426
+ _optionalChain([this, 'access', _102 => _102.outTiles, 'optionalAccess', _103 => _103.destroy, 'call', _104 => _104()]);
3427
+ _optionalChain([this, 'access', _105 => _105.outRead, 'optionalAccess', _106 => _106.destroy, 'call', _107 => _107()]);
3376
3428
  this.tileCap = Math.max(n, this.tileCap * 2, 4);
3377
3429
  this.srcTiles = d.createBuffer({ size: this.tileCap * tileBytes, usage: STORAGE | COPY_DST, label: "wet base tiles" });
3378
3430
  this.outTiles = d.createBuffer({ size: this.tileCap * tileBytes, usage: STORAGE | COPY_SRC, label: "wet shaded tiles" });
@@ -3426,7 +3478,7 @@ var WetGpuEngine = (_class3 = class _WetGpuEngine {
3426
3478
  return this.device.queue.onSubmittedWorkDone();
3427
3479
  }
3428
3480
  destroy() {
3429
- for (const b of [this.state, this.params, this.jobs, this.flags, this.flagsRead, ...this.uni, this.shadeUni, this.srcTiles, this.outTiles, this.outRead]) _optionalChain([b, 'optionalAccess', _110 => _110.destroy, 'call', _111 => _111()]);
3481
+ for (const b of [this.state, this.params, this.jobs, this.flags, this.flagsRead, ...this.uni, this.shadeUni, this.srcTiles, this.outTiles, this.outRead]) _optionalChain([b, 'optionalAccess', _108 => _108.destroy, 'call', _109 => _109()]);
3430
3482
  this.device.destroy();
3431
3483
  }
3432
3484
  }, _class3);
@@ -27,7 +27,7 @@ import {
27
27
  resolveEngine,
28
28
  summarizeBrushSource,
29
29
  withKeptIssues
30
- } from "./chunk-UXYCY3HP.js";
30
+ } from "./chunk-ZM7IKQ4X.js";
31
31
  import {
32
32
  base64ToBytes
33
33
  } from "./chunk-OTTT5N3K.js";
@@ -44,7 +44,7 @@ var BRUSH_PATCH_PROPERTIES = (() => {
44
44
  const { required: _r, ...tip } = rest.tip;
45
45
  return { ...rest, tip: { ...tip, description: `${tip.description} In a patch, shape may be left out to keep it.` } };
46
46
  })();
47
- var UNSETTABLE_BRUSH_FACETS = ["description", "tags", "medium", "variety", "marks", "dynamics", "behaviour", "light", "ghost", "motion", "colour"];
47
+ var UNSETTABLE_BRUSH_FACETS = ["description", "tags", "medium", "variety", "marks", "dynamics", "behaviour", "light", "fluorescence", "thermochromic", "iridescence", "ghost", "mutation", "motion", "colour"];
48
48
  var BRUSH_UNSET_PROPERTY = {
49
49
  type: "array",
50
50
  minItems: 1,
@@ -1113,6 +1113,11 @@ var BRUSH_SLIDERS = [
1113
1113
  { path: "dynamics.velocitySize", label: "Speed size", min: -1, max: 1, step: 0.01 }
1114
1114
  ];
1115
1115
  var LIGHT_SLIDER = { path: "light.amount", label: "Light", min: 0, max: 1, step: 0.01 };
1116
+ var IRIDESCENCE_SLIDERS = [
1117
+ { path: "iridescence.thickness", label: "Film thickness (nm)", min: 100, max: 1e3, step: 5 },
1118
+ { path: "iridescence.pressureToThickness", label: "Pressure \u2192 thickness", min: 0, max: 1, step: 0.01 },
1119
+ { path: "iridescence.ior", label: "Refractive index", min: 1.2, max: 2, step: 0.01 }
1120
+ ];
1116
1121
  var PICKUP_SLIDERS = [
1117
1122
  { path: "behavior.pickup", label: "Pick-up", min: 0, max: 1, step: 0.01 },
1118
1123
  { path: "behavior.mix", label: "Mix", min: 0, max: 1, step: 0.01 }
@@ -1121,12 +1126,22 @@ var GHOST_SLIDERS = [
1121
1126
  { path: "ghost.linger", label: "Linger", min: 0.5, max: 60, step: 0.5 },
1122
1127
  { path: "ghost.smear", label: "Smear", min: 0, max: 1, step: 0.01 }
1123
1128
  ];
1129
+ var MUTATION_SLIDERS = [
1130
+ { path: "mutation.settle", label: "Settle", min: 0.5, max: 10, step: 0.5 },
1131
+ { path: "mutation.patience", label: "Patience", min: 5, max: 300, step: 5 },
1132
+ { path: "mutation.fade", label: "Fade", min: 0.5, max: 10, step: 0.5 }
1133
+ ];
1124
1134
  function brushEngineControls(brush) {
1125
1135
  const c = engineControls(brush);
1126
- const wetPaint = brush.engine === "wet" && brush.wet?.tool === "paint" && !brush.ghost;
1127
- if (!c || !(wetPaint || ["dab", "mixer", "particle", "bristle", "impasto"].includes(brush.engine))) return c;
1136
+ if (!c) return null;
1137
+ const mut = brush.mutation ? MUTATION_SLIDERS : [];
1138
+ const wetPaint = brush.engine === "wet" && brush.wet?.tool === "paint" && !brush.ghost && !brush.mutation;
1139
+ if (!(wetPaint || ["dab", "mixer", "particle", "bristle", "impasto"].includes(brush.engine))) {
1140
+ return mut.length ? { ...c, sliders: [...c.sliders, ...mut] } : c;
1141
+ }
1128
1142
  const extra = brush.engine === "impasto" ? [{ path: "impasto.rim", label: "Rim", min: 0, max: 1, step: 0.01 }, { path: "impasto.clarity", label: "Clarity", min: 0, max: 1, step: 0.01 }] : [];
1129
- return { ...c, sliders: [...c.sliders, LIGHT_SLIDER, ...extra], light: true };
1143
+ const film = brush.iridescence ? IRIDESCENCE_SLIDERS : [];
1144
+ return { ...c, sliders: [...c.sliders, ...mut, LIGHT_SLIDER, ...extra, ...film], light: true, iridescence: true };
1130
1145
  }
1131
1146
  function engineControls(brush) {
1132
1147
  switch (brush.engine) {
@@ -1197,17 +1212,17 @@ function engineControls(brush) {
1197
1212
  { path: "impasto.load", label: "Load", min: 0, max: 1, step: 0.01 },
1198
1213
  { path: "impasto.thinner", label: "Thinner", min: 0, max: 1, step: 0.01 },
1199
1214
  { path: "impasto.drag", label: "Drag", min: 0, max: 1, step: 0.01 },
1200
- ...PICKUP_SLIDERS
1215
+ ...brush.mutation ? [] : PICKUP_SLIDERS
1201
1216
  ],
1202
1217
  mixingPath: "behavior.mixing",
1203
- dirty: true,
1218
+ dirty: !brush.mutation,
1204
1219
  multicolor: false,
1205
- load: true,
1220
+ load: !brush.mutation,
1206
1221
  impastoTool: true
1207
1222
  };
1208
1223
  case "wet": {
1209
1224
  const tool = brush.wet?.tool ?? "paint";
1210
- const ghost = tool === "paint" && Boolean(brush.ghost);
1225
+ const ghost = tool === "paint" && (Boolean(brush.ghost) || Boolean(brush.mutation));
1211
1226
  const sliders = tool === "paint" ? [
1212
1227
  { path: "wet.water", label: "Water", min: 0, max: 1, step: 0.01 },
1213
1228
  { path: "wet.pigment", label: "Pigment", min: 0, max: 1, step: 0.01 },
@@ -1215,7 +1230,7 @@ function engineControls(brush) {
1215
1230
  { path: "wet.rewet", label: "Re-wet", min: 0, max: 1, step: 0.01 },
1216
1231
  { path: "wet.load", label: "Load", min: 0, max: 1, step: 0.01 },
1217
1232
  { path: "wet.thinner", label: "Thinner", min: 0, max: 1, step: 0.01 },
1218
- ...ghost ? GHOST_SLIDERS : PICKUP_SLIDERS
1233
+ ...brush.mutation ? [{ path: "ghost.smear", label: "Smear", min: 0, max: 1, step: 0.01 }] : ghost ? GHOST_SLIDERS : PICKUP_SLIDERS
1219
1234
  ] : tool === "water" ? [
1220
1235
  { path: "wet.water", label: "Water", min: 0, max: 1, step: 0.01 },
1221
1236
  { path: "wet.rewet", label: "Re-wet", min: 0, max: 1, step: 0.01 }
@@ -1249,7 +1264,7 @@ function engineControls(brush) {
1249
1264
  case "mixer":
1250
1265
  return { title: "Mixer Brush", sliders: [...PICKUP_SLIDERS, { path: "mixer.load", label: "Load", min: 0, max: 1, step: 0.01 }], mixingPath: "behavior.mixing", dirty: true, multicolor: true, load: true };
1251
1266
  case "dab":
1252
- return { title: "Paint behaviour", sliders: PICKUP_SLIDERS, mixingPath: "behavior.mixing", dirty: true, multicolor: true, load: true };
1267
+ return { title: "Paint behaviour", sliders: brush.mutation ? [] : PICKUP_SLIDERS, mixingPath: "behavior.mixing", dirty: !brush.mutation, multicolor: !brush.mutation, load: !brush.mutation };
1253
1268
  default:
1254
1269
  return null;
1255
1270
  }
@@ -1289,7 +1304,9 @@ export {
1289
1304
  createBrushLibraryTools,
1290
1305
  createBrushLibraryToolProvider,
1291
1306
  BRUSH_SLIDERS,
1307
+ IRIDESCENCE_SLIDERS,
1292
1308
  GHOST_SLIDERS,
1309
+ MUTATION_SLIDERS,
1293
1310
  brushEngineControls,
1294
1311
  multiColorSliders,
1295
1312
  patchBrushValue
@@ -74,7 +74,7 @@ function profileAlongPolyline(points, values, count = 32) {
74
74
  var round3 = (v) => Math.round(v * 1e3) / 1e3;
75
75
  var loading = null;
76
76
  function loadCanvasBrushstrokeRenderer() {
77
- loading ??= import("./brushstrokeRender-DZGFU3J4.js").catch((error) => {
77
+ loading ??= import("./brushstrokeRender-WGY4DZHF.js").catch((error) => {
78
78
  loading = null;
79
79
  throw error;
80
80
  });
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkGHGBIAWQcjs = require('./chunk-GHGBIAWQ.cjs');
3
+ var _chunk6QNDGMK7cjs = require('./chunk-6QNDGMK7.cjs');
4
4
 
5
5
 
6
6
  var _chunkJBZRVLXVcjs = require('./chunk-JBZRVLXV.cjs');
@@ -33,25 +33,25 @@ var BRUSH_PREVIEW_FALLBACKS = {
33
33
  tools: { light: "#475569", dark: "#cbd5e1" }
34
34
  };
35
35
  function brushPreviewsOnDark(brush) {
36
- return (_nullishCoalesce(_optionalChain([brush, 'access', _ => _.light, 'optionalAccess', _2 => _2.amount]), () => ( 0))) > 0 && _optionalChain([brush, 'access', _3 => _3.tip, 'optionalAccess', _4 => _4.blend]) !== "multiply";
36
+ return ((_nullishCoalesce(_optionalChain([brush, 'access', _ => _.light, 'optionalAccess', _2 => _2.amount]), () => ( 0))) > 0 || (_nullishCoalesce(_optionalChain([brush, 'access', _3 => _3.fluorescence, 'optionalAccess', _4 => _4.amount]), () => ( 0))) > 0) && _optionalChain([brush, 'access', _5 => _5.tip, 'optionalAccess', _6 => _6.blend]) !== "multiply";
37
37
  }
38
38
  var hexOf = (c) => _nullishCoalesce(_chunkJBZRVLXVcjs.brushColourHex.call(void 0, c, "light"), () => ( "#ffffff"));
39
39
  function brushPreviewStrength(brush) {
40
40
  const opacity = _nullishCoalesce(brush.tip.opacity, () => ( 1));
41
41
  const flow = _nullishCoalesce(brush.tip.flow, () => ( 1));
42
- const grain = _nullishCoalesce(_optionalChain([brush, 'access', _5 => _5.behaviour, 'optionalAccess', _6 => _6.grain]), () => ( 0));
42
+ const grain = _nullishCoalesce(_optionalChain([brush, 'access', _7 => _7.behaviour, 'optionalAccess', _8 => _8.grain]), () => ( 0));
43
43
  return Math.max(0.25, Math.min(1, 0.85 * opacity * (0.6 + 0.4 * flow) * (1 - 0.3 * grain)));
44
44
  }
45
45
  var over = (fg, bg, alpha) => ({ r: fg.r * alpha + bg.r * (1 - alpha), g: fg.g * alpha + bg.g * (1 - alpha), b: fg.b * alpha + bg.b * (1 - alpha), a: 1 });
46
46
  function brushPreviewColours(brush, options = {}) {
47
- const profile = _nullishCoalesce(options.profile, () => ( _chunkGHGBIAWQcjs.PAINT_PROFILE));
47
+ const profile = _nullishCoalesce(options.profile, () => ( _chunk6QNDGMK7cjs.PAINT_PROFILE));
48
48
  let background = profile.light === "full" && brushPreviewsOnDark(brush) ? BRUSH_PREVIEW_DARK_PAPER : hexOf(_nullishCoalesce(options.paper, () => ( "#ffffff")));
49
49
  if (!options.keepPaper && brush.tip.blend === "multiply" && _chunk5UEFBM2Mcjs.relativeLuminance.call(void 0, _chunk5UEFBM2Mcjs.parseColor.call(void 0, background)) < 0.18) background = "#ffffff";
50
50
  const bg = _chunk5UEFBM2Mcjs.parseColor.call(void 0, background);
51
51
  const scheme = _chunk5UEFBM2Mcjs.relativeLuminance.call(void 0, bg) < 0.18 ? "dark" : "light";
52
- const own = brush.kind === "mark" ? _optionalChain([brush, 'access', _7 => _7.colour, 'optionalAccess', _8 => _8.default]) : void 0;
52
+ const own = brush.kind === "mark" ? _optionalChain([brush, 'access', _9 => _9.colour, 'optionalAccess', _10 => _10.default]) : void 0;
53
53
  const ownHex = typeof own === "string" || _chunkMLLTWAXHcjs.isDualColor.call(void 0, own) ? _chunkJBZRVLXVcjs.brushColourHex.call(void 0, own, scheme) : null;
54
- if (ownHex && brush.tip.blend === "multiply" && (_nullishCoalesce(_optionalChain([brush, 'access', _9 => _9.light, 'optionalAccess', _10 => _10.amount]), () => ( 0))) > 0 && scheme === "light") return { background, color: ownHex, scheme };
54
+ if (ownHex && brush.tip.blend === "multiply" && (_nullishCoalesce(_optionalChain([brush, 'access', _11 => _11.light, 'optionalAccess', _12 => _12.amount]), () => ( 0))) > 0 && scheme === "light") return { background, color: ownHex, scheme };
55
55
  const group = brush.kind === "tool" ? "tools" : _nullishCoalesce(brush.medium, () => ( "other"));
56
56
  const color = _nullishCoalesce(ownHex, () => ( _chunkJBZRVLXVcjs.brushColourHex.call(void 0, _nullishCoalesce(BRUSH_PREVIEW_FALLBACKS[group], () => ( BRUSH_PREVIEW_FALLBACKS.other)), scheme)));
57
57
  const alpha = brushPreviewStrength(brush);
@@ -850,7 +850,7 @@ var S2 = SLOT_SIDE;
850
850
  var SNAP_PLANES = SET_PLANES + MASS_CHANNELS;
851
851
  var noLight = () => new TiledSurface(1, /* @__PURE__ */ new Map(), 0);
852
852
  function cloneGhost(g) {
853
- return g ? { pigment: [...g.pigment], remaining: g.remaining, total: g.total, linger: g.linger } : null;
853
+ return g ? { pigment: [...g.pigment], remaining: g.remaining, total: g.total, linger: g.linger, ...g.trigger === "agent" ? { trigger: "agent" } : {} } : null;
854
854
  }
855
855
  function newLane(id, index, params, flow, flowStrength, base, light, ghost) {
856
856
  const l = light ?? noLight();
@@ -1119,21 +1119,59 @@ var WetWorld = class {
1119
1119
  }
1120
1120
  /**
1121
1121
  * Starts a ghost paint fade on `id` with `pigment` per unit tracer and `linger` seconds. If an
1122
- * earlier ghost of a different colour is still fading on the layer, it finishes at once first.
1122
+ * earlier ghost of a different colour is still fading on the layer, it finishes at once first
1123
+ * (or keeps its paint when it was an unclaimed agent-triggered ghost).
1123
1124
  */
1124
- startGhost(id, pigment, linger, stepsPerSecond = 120) {
1125
+ startGhost(id, pigment, linger, stepsPerSecond = 120, trigger = "immediate") {
1125
1126
  this.requireIdle();
1126
1127
  const lane = this.laneOf(id);
1127
1128
  const p = [Math.fround(pigment[0]), Math.fround(pigment[1]), Math.fround(pigment[2]), Math.fround(pigment[3])];
1128
1129
  if (lane.ghost) {
1129
1130
  const g = lane.ghost.pigment;
1130
1131
  const same = Math.abs(g[0] - p[0]) <= 1e-6 && Math.abs(g[1] - p[1]) <= 1e-6 && Math.abs(g[2] - p[2]) <= 1e-6 && Math.abs(g[3] - p[3]) <= 1e-6;
1131
- if (!same) this.fadeGhost(id, 1);
1132
+ if (!same) {
1133
+ if (lane.ghost.trigger === "agent") this.keepGhost(id);
1134
+ else this.fadeGhost(id, 1);
1135
+ }
1132
1136
  }
1133
1137
  const total = Math.max(1, Math.round(linger * stepsPerSecond / FREE_INTERVAL));
1134
- lane.ghost = { pigment: p, remaining: total, total, linger };
1138
+ lane.ghost = { pigment: p, remaining: total, total, linger, ...trigger === "agent" ? { trigger: "agent" } : {} };
1135
1139
  lane.lit = false;
1136
1140
  }
1141
+ /**
1142
+ * Releases a held (`trigger: 'agent'`) ghost on `id` so its fade starts counting down on the
1143
+ * simulation clock over `linger` seconds (default: the lane's configured `linger`).
1144
+ */
1145
+ releaseGhost(id, linger, stepsPerSecond = 120) {
1146
+ this.requireIdle();
1147
+ const lane = this.laneOf(id);
1148
+ if (!lane.ghost) return;
1149
+ const l = linger ?? lane.ghost.linger;
1150
+ const total = Math.max(1, Math.round(l * stepsPerSecond / FREE_INTERVAL));
1151
+ lane.ghost = { pigment: lane.ghost.pigment, remaining: total, total, linger: l };
1152
+ }
1153
+ /**
1154
+ * Drops a ghost lane's tracer (`P_L`, `P_DL`) without subtracting its pigment, so an unclaimed
1155
+ * or declined wet mutation stroke stays as ordinary wet paint.
1156
+ */
1157
+ keepGhost(id) {
1158
+ this.requireIdle();
1159
+ const lane = this.laneOf(id);
1160
+ if (!lane.ghost) return;
1161
+ const f = this.f32;
1162
+ for (const key of this.active) {
1163
+ if (this.laneIndexOf(key) !== lane.index) continue;
1164
+ const slot = this.slotOf(key);
1165
+ if (slot < 0) continue;
1166
+ const b = this.slotBase(slot);
1167
+ const cur = b + this.parity * SET_PLANES * PLANE;
1168
+ f.fill(0, cur + P_L * PLANE, cur + (P_L + 1) * PLANE);
1169
+ f.fill(0, b + P_DL * PLANE, b + (P_DL + 1) * PLANE);
1170
+ this.gpuDirtySlot(slot, 1, WET_TILE);
1171
+ }
1172
+ lane.ghost = null;
1173
+ this.refreshHalos();
1174
+ }
1137
1175
  pack(lane) {
1138
1176
  packWetParams(lane.params, this.paper ?? DEFAULT_WET_PAPER, this.tilt, lane.flowStrength, this.prm);
1139
1177
  this.f32.set(this.prm, this.layout.params + lane.index * PARAM_FLOATS * 4 >> 2);
@@ -1367,7 +1405,7 @@ var WetWorld = class {
1367
1405
  for (const key of wakeups) woke = this.activate(key) || woke;
1368
1406
  if (this.clock % FREE_INTERVAL === 0) {
1369
1407
  for (const lane of this.lanes) {
1370
- if (!lane?.ghost) continue;
1408
+ if (!lane?.ghost || lane.ghost.trigger === "agent") continue;
1371
1409
  const g = lane.ghost;
1372
1410
  const amount = g.remaining <= 1 ? 1 : 1 / g.remaining;
1373
1411
  g.remaining = Math.max(0, g.remaining - 1);
@@ -1931,12 +1969,16 @@ var WetWorld = class {
1931
1969
  continue;
1932
1970
  }
1933
1971
  const f = this.f32;
1934
- const pig = lane.ghost?.pigment ?? null;
1972
+ const pig = lane.ghost && lane.ghost.trigger !== "agent" ? lane.ghost.pigment : null;
1973
+ const clearTracer = Boolean(lane.ghost && lane.ghost.trigger === "agent");
1935
1974
  for (let y = y0; y < y1; y++) {
1936
1975
  for (let x = x0; x < x1; x++) {
1937
1976
  const c = this.cell(lane, x, y);
1938
1977
  const d = this.single(c) + P_D0 * PLANE;
1939
- if (pig) {
1978
+ if (clearTracer) {
1979
+ f[c + P_L * PLANE] = 0;
1980
+ f[d + 4 * PLANE] = 0;
1981
+ } else if (pig) {
1940
1982
  const tl = f[c + P_L * PLANE];
1941
1983
  const dl = f[d + 4 * PLANE];
1942
1984
  if (tl > 0 || dl > 0) {
@@ -1980,7 +2022,17 @@ var WetWorld = class {
1980
2022
  bakeKey(key, all) {
1981
2023
  const lane = this.lanes[this.laneIndexOf(key)];
1982
2024
  if (lane.ghost) {
1983
- this.fadeGhostKey(key, lane, 1);
2025
+ if (lane.ghost.trigger === "agent") {
2026
+ const slot = this.slotOf(key);
2027
+ if (slot >= 0) {
2028
+ const b = this.slotBase(slot);
2029
+ const cur = b + this.parity * SET_PLANES * PLANE;
2030
+ this.f32.fill(0, cur + P_L * PLANE, cur + (P_L + 1) * PLANE);
2031
+ this.f32.fill(0, b + P_DL * PLANE, b + (P_DL + 1) * PLANE);
2032
+ }
2033
+ } else {
2034
+ this.fadeGhostKey(key, lane, 1);
2035
+ }
1984
2036
  if (!this.hasGhostTracer(lane)) lane.ghost = null;
1985
2037
  }
1986
2038
  const [tx, ty] = this.tileXY(key);
@@ -2062,7 +2114,7 @@ var WetWorld = class {
2062
2114
  if (this.laneIndexOf(key) !== lane.index) continue;
2063
2115
  const tk = this.tileKeyOf(key);
2064
2116
  let out;
2065
- if (lane.ghost) {
2117
+ if (lane.ghost && lane.ghost.trigger !== "agent") {
2066
2118
  const b = this.slotBase(this.slotOf(key));
2067
2119
  const cur = b + this.parity * SET_PLANES * PLANE;
2068
2120
  const savedP = this.f32.slice(cur + P_P0 * PLANE, cur + (P_P0 + MASS_CHANNELS) * PLANE);
@@ -115,7 +115,7 @@ import {
115
115
  transformTextRect,
116
116
  translateMatrix,
117
117
  warpPresetMesh
118
- } from "./chunk-ZEI7ZEA4.js";
118
+ } from "./chunk-Y2ISSVPH.js";
119
119
  import {
120
120
  bezierPathBounds,
121
121
  outlineStroke,