@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
@@ -849,9 +849,13 @@ var SLOT_FLOATS = SLOT_PLANES * PLANE;
849
849
  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
- function newLane(id, index, params, flow, flowStrength, base, light) {
852
+ function cloneGhost(g) {
853
+ return g ? { pigment: [...g.pigment], remaining: g.remaining, total: g.total, linger: g.linger } : null;
854
+ }
855
+ function newLane(id, index, params, flow, flowStrength, base, light, ghost) {
853
856
  const l = _nullishCoalesce(light, () => ( noLight()));
854
- return { id, index, params, flow, flowStrength, base, shaded: /* @__PURE__ */ new Map(), urgent: /* @__PURE__ */ new Set(), stamp: 1, display: null, shadedVersion: 0, displayKey: "", light: l, lit: l.tiles.size > 0, lightShaded: /* @__PURE__ */ new Map(), lightDisplay: null, lightKey: "" };
857
+ const g = cloneGhost(ghost);
858
+ return { id, index, params, flow, flowStrength, base, shaded: /* @__PURE__ */ new Map(), urgent: /* @__PURE__ */ new Set(), stamp: 1, display: null, shadedVersion: 0, displayKey: "", light: l, lit: !g && l.tiles.size > 0, ghost: g, lightShaded: /* @__PURE__ */ new Map(), lightDisplay: null, lightKey: "" };
855
859
  }
856
860
  var WetWorldError = class extends Error {
857
861
  constructor(message) {
@@ -1012,11 +1016,12 @@ var WetWorld = (_class = class {
1012
1016
  if (options.params) this.setParams(id, options.params);
1013
1017
  if (options.base) this.setBase(id, options.base);
1014
1018
  if (options.light) this.setLight(id, options.light);
1019
+ if (options.ghost !== void 0) this.setGhost(id, options.ghost);
1015
1020
  return;
1016
1021
  }
1017
1022
  const index = this.lanes.indexOf(null);
1018
1023
  if (index < 0) throw new WetWorldError(`At most ${MAX_WET_LANES} layers can hold wet paint at once; dry one (dry_paint) first.`);
1019
- this.lanes[index] = newLane(id, index, normalizeWetParams(_nullishCoalesce(options.params, () => ( {})), DEFAULT_WET_PARAMS), _nullishCoalesce(options.flow, () => ( null)), options.flow ? _nullishCoalesce(options.flowStrength, () => ( 1)) : 0, _nullishCoalesce(options.base, () => ( new (0, _chunk474IQG74cjs.TiledSurface)(4, /* @__PURE__ */ new Map(), 0))), options.light);
1024
+ this.lanes[index] = newLane(id, index, normalizeWetParams(_nullishCoalesce(options.params, () => ( {})), DEFAULT_WET_PARAMS), _nullishCoalesce(options.flow, () => ( null)), options.flow ? _nullishCoalesce(options.flowStrength, () => ( 1)) : 0, _nullishCoalesce(options.base, () => ( new (0, _chunk474IQG74cjs.TiledSurface)(4, /* @__PURE__ */ new Map(), 0))), options.light, options.ghost);
1020
1025
  this.pack(this.lanes[index]);
1021
1026
  }
1022
1027
  /** Drops a wet layer and its wet paint (nothing is baked). */
@@ -1089,17 +1094,46 @@ var WetWorld = (_class = class {
1089
1094
  }
1090
1095
  /** Whether a layer has light paint, under its wet paint or in it. */
1091
1096
  isLit(id) {
1092
- return this.laneOf(id).lit;
1097
+ const l = this.laneOf(id);
1098
+ return l.lit && !l.ghost;
1093
1099
  }
1094
1100
  /** Replaces the light paint under a layer's wet paint. */
1095
1101
  setLight(id, light) {
1096
1102
  const lane = this.laneOf(id);
1097
1103
  if (light === lane.light) return;
1098
1104
  lane.light = light;
1099
- if (light.tiles.size) lane.lit = true;
1105
+ if (light.tiles.size && !lane.ghost) lane.lit = true;
1100
1106
  lane.lightShaded.clear();
1101
1107
  lane.stamp++;
1102
1108
  }
1109
+ /** Active ghost paint state on the layer, or `null`. */
1110
+ getGhost(id) {
1111
+ return cloneGhost(this.laneOf(id).ghost);
1112
+ }
1113
+ /** Sets or clears a layer's active ghost paint state. */
1114
+ setGhost(id, ghost) {
1115
+ this.requireIdle();
1116
+ const lane = this.laneOf(id);
1117
+ lane.ghost = cloneGhost(ghost);
1118
+ if (lane.ghost) lane.lit = false;
1119
+ }
1120
+ /**
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.
1123
+ */
1124
+ startGhost(id, pigment, linger, stepsPerSecond = 120) {
1125
+ this.requireIdle();
1126
+ const lane = this.laneOf(id);
1127
+ const p = [Math.fround(pigment[0]), Math.fround(pigment[1]), Math.fround(pigment[2]), Math.fround(pigment[3])];
1128
+ if (lane.ghost) {
1129
+ const g = lane.ghost.pigment;
1130
+ 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
+ }
1133
+ const total = Math.max(1, Math.round(linger * stepsPerSecond / FREE_INTERVAL));
1134
+ lane.ghost = { pigment: p, remaining: total, total, linger };
1135
+ lane.lit = false;
1136
+ }
1103
1137
  pack(lane) {
1104
1138
  packWetParams(lane.params, _nullishCoalesce(this.paper, () => ( DEFAULT_WET_PAPER)), this.tilt, lane.flowStrength, this.prm);
1105
1139
  this.f32.set(this.prm, this.layout.params + lane.index * PARAM_FLOATS * 4 >> 2);
@@ -1332,6 +1366,13 @@ var WetWorld = (_class = class {
1332
1366
  let woke = false;
1333
1367
  for (const key of wakeups) woke = this.activate(key) || woke;
1334
1368
  if (this.clock % FREE_INTERVAL === 0) {
1369
+ for (const lane of this.lanes) {
1370
+ if (!_optionalChain([lane, 'optionalAccess', _19 => _19.ghost])) continue;
1371
+ const g = lane.ghost;
1372
+ const amount = g.remaining <= 1 ? 1 : 1 / g.remaining;
1373
+ g.remaining = Math.max(0, g.remaining - 1);
1374
+ this.fadeGhost(lane.id, amount);
1375
+ }
1335
1376
  const dead = this.active.filter((key) => {
1336
1377
  if (this.u32[lastBase + key] & FLAG_ALIVE) return false;
1337
1378
  const laneBase = this.laneIndexOf(key) * perLane;
@@ -1531,7 +1572,10 @@ var WetWorld = (_class = class {
1531
1572
  paint(id, rect, cov, water, pigment, field = null, light = 0) {
1532
1573
  if (!(water >= 0)) throw new WetWorldError(`water ${water} must be 0 or more.`);
1533
1574
  if (!(light >= 0)) throw new WetWorldError(`light ${light} must be 0 or more.`);
1534
- if (light > 0) this.laneOf(id).lit = true;
1575
+ if (light > 0) {
1576
+ const l = this.laneOf(id);
1577
+ if (!l.ghost) l.lit = true;
1578
+ }
1535
1579
  this.footprint(id, rect, cov, (c, w, x, y) => {
1536
1580
  const dw = Math.fround(water * w);
1537
1581
  if (dw > 0) this.addWaterAt(c, dw);
@@ -1683,12 +1727,12 @@ var WetWorld = (_class = class {
1683
1727
  * the paper as settled pigment, so water can move it again, and the same share of its light
1684
1728
  * paint (`light`) as settled light. Only where there is surface water.
1685
1729
  */
1686
- rewet(id, rect, cov, amount, paper) {
1730
+ rewet(id, rect, cov, amount, paper, suspend = false) {
1687
1731
  const lane = this.laneOf(id);
1688
1732
  const pig = [0, 0, 0, 0];
1689
1733
  const tiles = new Map(lane.base.tiles);
1690
1734
  const written = /* @__PURE__ */ new Set();
1691
- const lightTiles = lane.lit ? new Map(lane.light.tiles) : null;
1735
+ const lightTiles = lane.lit && !lane.ghost ? new Map(lane.light.tiles) : null;
1692
1736
  const lightWritten = /* @__PURE__ */ new Set();
1693
1737
  const scratch2 = new Uint8ClampedArray(4);
1694
1738
  this.footprint(
@@ -1701,7 +1745,7 @@ var WetWorld = (_class = class {
1701
1745
  if (!(a > 0)) return;
1702
1746
  const key = _chunk474IQG74cjs.tileKey.call(void 0, x >> 8, y >> 8);
1703
1747
  const li = (y & 255) * _chunk474IQG74cjs.TILE + (x & 255);
1704
- let lt = _optionalChain([lightTiles, 'optionalAccess', _19 => _19.get, 'call', _20 => _20(key)]);
1748
+ let lt = _optionalChain([lightTiles, 'optionalAccess', _20 => _20.get, 'call', _21 => _21(key)]);
1705
1749
  if (lt && (_nullishCoalesce(lt.uniform, () => ( 1))) !== 0) {
1706
1750
  if (!lightWritten.has(key)) {
1707
1751
  const data = lt.data.length ? new Uint8ClampedArray(lt.data) : new Uint8ClampedArray(_chunk474IQG74cjs.TILE * _chunk474IQG74cjs.TILE).fill(_nullishCoalesce(lt.uniform, () => ( 0)));
@@ -1730,8 +1774,8 @@ var WetWorld = (_class = class {
1730
1774
  const clear = new Uint8ClampedArray([0, 0, 0, 0]);
1731
1775
  filmPixel(pig[0] * (1 - a), pig[1] * (1 - a), pig[2] * (1 - a), 0, clear, 0, paper, 0, scratch2, 0);
1732
1776
  tile.data.set(scratch2, i);
1733
- const d = this.single(c) + P_D0 * PLANE;
1734
- for (let q = 0; q < 3; q++) this.f32[d + q * PLANE] += Math.fround(pig[q] * a);
1777
+ const into = suspend ? c + P_P0 * PLANE : this.single(c) + P_D0 * PLANE;
1778
+ for (let q = 0; q < 3; q++) this.f32[into + q * PLANE] += Math.fround(pig[q] * a);
1735
1779
  },
1736
1780
  false
1737
1781
  );
@@ -1752,6 +1796,114 @@ var WetWorld = (_class = class {
1752
1796
  this.setLight(id, new (0, _chunk474IQG74cjs.TiledSurface)(1, lightTiles, 0));
1753
1797
  }
1754
1798
  }
1799
+ /**
1800
+ * Subtracts `amount` (0..1) of a ghost lane's remaining tracer (`P_L`, `P_DL`) and proportional
1801
+ * ghost pigment (`lane.ghost.pigment`) across all live tiles of `id`. Speeds up in cells that
1802
+ * are nearly dry so drying paint never pops. Clears the lane's `ghost` state once no tracer remains.
1803
+ */
1804
+ fadeGhost(id, amount) {
1805
+ this.requireIdle();
1806
+ const lane = this.laneOf(id);
1807
+ if (!lane.ghost) return;
1808
+ const keys = this.active.filter((k) => this.laneIndexOf(k) === lane.index);
1809
+ let anyRemaining = false;
1810
+ for (const key of keys) {
1811
+ if (this.fadeGhostKey(key, lane, amount)) anyRemaining = true;
1812
+ }
1813
+ if (!anyRemaining || amount >= 1 - 1e-6) lane.ghost = null;
1814
+ this.refreshHalos();
1815
+ }
1816
+ /** Whether any live tile of `lane` still holds ghost tracer (`P_L > 0` or `P_DL > 0`). */
1817
+ hasGhostTracer(lane) {
1818
+ const f = this.f32;
1819
+ for (const key of this.active) {
1820
+ if (this.laneIndexOf(key) !== lane.index) continue;
1821
+ const b = this.slotBase(this.slotOf(key));
1822
+ const cur = b + this.parity * SET_PLANES * PLANE;
1823
+ for (let y = 1; y <= WET_TILE; y++) {
1824
+ for (let x = 1; x <= WET_TILE; x++) {
1825
+ const c = y * S2 + x;
1826
+ if (f[cur + P_L * PLANE + c] > 0 || f[b + P_DL * PLANE + c] > 0) return true;
1827
+ }
1828
+ }
1829
+ }
1830
+ return false;
1831
+ }
1832
+ /**
1833
+ * Fades ghost tracer and pigment in one slot by `amount` (0..1). Returns whether any tracer
1834
+ * remains in the slot.
1835
+ */
1836
+ fadeGhostKey(key, lane, amount) {
1837
+ if (!lane.ghost) return false;
1838
+ const pig = lane.ghost.pigment;
1839
+ const slot = this.slotOf(key);
1840
+ if (slot < 0) return false;
1841
+ const b = this.slotBase(slot);
1842
+ const cur = b + this.parity * SET_PLANES * PLANE;
1843
+ const f = this.f32;
1844
+ const baseAmt = Math.min(1, Math.max(0, amount));
1845
+ let remainingTracer = false;
1846
+ let yMin = WET_TILE + 1;
1847
+ let yMax = 0;
1848
+ for (let y = 1; y <= WET_TILE; y++) {
1849
+ for (let x = 1; x <= WET_TILE; x++) {
1850
+ const c = y * S2 + x;
1851
+ const tl = f[cur + P_L * PLANE + c];
1852
+ const dl = f[b + P_DL * PLANE + c];
1853
+ if (!(tl > 0 || dl > 0)) continue;
1854
+ const water = f[cur + P_RHO * PLANE + c] + f[cur + P_SW * PLANE + c];
1855
+ const dryFade = water <= DRY_EPS ? 1 : water < 0.02 ? (1 - water / 0.02) * 0.5 : 0;
1856
+ const a = baseAmt >= 1 - 1e-6 ? 1 : Math.min(1, Math.max(baseAmt, dryFade));
1857
+ if (!(a > 0)) {
1858
+ remainingTracer = true;
1859
+ continue;
1860
+ }
1861
+ if (y < yMin) yMin = y;
1862
+ if (y > yMax) yMax = y;
1863
+ if (tl > 0) {
1864
+ const done = a >= 1 || tl * (1 - a) <= 1e-7;
1865
+ const dt = done ? tl : Math.fround(tl * a);
1866
+ const tNew = done ? 0 : Math.fround(tl - dt);
1867
+ f[cur + P_L * PLANE + c] = tNew;
1868
+ for (let q = 0; q < 4; q++) {
1869
+ const idx = cur + (P_P0 + q) * PLANE + c;
1870
+ const eps = Math.max(1e-3, pig[q] * 2e-4);
1871
+ const v = Math.max(0, Math.fround(f[idx] - Math.fround(dt * pig[q])));
1872
+ f[idx] = tNew === 0 && v <= eps ? 0 : v;
1873
+ }
1874
+ if (tNew > 0) remainingTracer = true;
1875
+ }
1876
+ if (dl > 0) {
1877
+ const done = a >= 1 || dl * (1 - a) <= 1e-7;
1878
+ const dd = done ? dl : Math.fround(dl * a);
1879
+ const dNew = done ? 0 : Math.fround(dl - dd);
1880
+ f[b + P_DL * PLANE + c] = dNew;
1881
+ for (let q = 0; q < 4; q++) {
1882
+ const idx = b + (P_D0 + q) * PLANE + c;
1883
+ const eps = Math.max(1e-3, pig[q] * 2e-4);
1884
+ const v = Math.max(0, Math.fround(f[idx] - Math.fround(dd * pig[q])));
1885
+ f[idx] = dNew === 0 && v <= eps ? 0 : v;
1886
+ }
1887
+ if (dNew > 0) remainingTracer = true;
1888
+ }
1889
+ if (f[cur + P_L * PLANE + c] === 0 && f[b + P_DL * PLANE + c] === 0) {
1890
+ for (let q = 0; q < 4; q++) {
1891
+ const eps = Math.max(1e-3, pig[q] * 2e-4);
1892
+ const pi = cur + (P_P0 + q) * PLANE + c;
1893
+ if (f[pi] <= eps) f[pi] = 0;
1894
+ const di = b + (P_D0 + q) * PLANE + c;
1895
+ if (f[di] <= eps) f[di] = 0;
1896
+ }
1897
+ }
1898
+ }
1899
+ }
1900
+ if (yMax >= yMin) {
1901
+ lane.urgent.add(this.tileKeyOf(key));
1902
+ this.gpuDirtySlot(slot, yMin, yMax);
1903
+ lane.stamp++;
1904
+ }
1905
+ return remainingTracer;
1906
+ }
1755
1907
  /**
1756
1908
  * Dries wet paint at once (everything settles where it is, the water goes): in `rect`, or the
1757
1909
  * whole layer. Whole tiles that end up dry are baked into the base and freed. Returns the
@@ -1779,10 +1931,28 @@ var WetWorld = (_class = class {
1779
1931
  continue;
1780
1932
  }
1781
1933
  const f = this.f32;
1934
+ const pig = _nullishCoalesce(_optionalChain([lane, 'access', _22 => _22.ghost, 'optionalAccess', _23 => _23.pigment]), () => ( null));
1782
1935
  for (let y = y0; y < y1; y++) {
1783
1936
  for (let x = x0; x < x1; x++) {
1784
1937
  const c = this.cell(lane, x, y);
1785
1938
  const d = this.single(c) + P_D0 * PLANE;
1939
+ if (pig) {
1940
+ const tl = f[c + P_L * PLANE];
1941
+ const dl = f[d + 4 * PLANE];
1942
+ if (tl > 0 || dl > 0) {
1943
+ f[c + P_L * PLANE] = 0;
1944
+ f[d + 4 * PLANE] = 0;
1945
+ for (let q = 0; q < 4; q++) {
1946
+ const eps = Math.max(1e-3, pig[q] * 2e-4);
1947
+ const pi = c + (P_P0 + q) * PLANE;
1948
+ const pv = Math.max(0, Math.fround(f[pi] - Math.fround(tl * pig[q])));
1949
+ f[pi] = pv <= eps ? 0 : pv;
1950
+ const di = d + q * PLANE;
1951
+ const dv = Math.max(0, Math.fround(f[di] - Math.fround(dl * pig[q])));
1952
+ f[di] = dv <= eps ? 0 : dv;
1953
+ }
1954
+ }
1955
+ }
1786
1956
  for (let q = 0; q < MASS_CHANNELS; q++) {
1787
1957
  f[d + q * PLANE] += f[c + (P_P0 + q) * PLANE];
1788
1958
  f[c + (P_P0 + q) * PLANE] = 0;
@@ -1797,6 +1967,7 @@ var WetWorld = (_class = class {
1797
1967
  bounds = unionRect(bounds, { x: x0, y: y0, width: x1 - x0, height: y1 - y0 });
1798
1968
  this.markEdited(lane, x0, y0, x1, y1);
1799
1969
  }
1970
+ if (lane.ghost && !this.hasGhostTracer(lane)) lane.ghost = null;
1800
1971
  this.syncList();
1801
1972
  this.refreshHalos();
1802
1973
  lane.stamp++;
@@ -1808,10 +1979,14 @@ var WetWorld = (_class = class {
1808
1979
  */
1809
1980
  bakeKey(key, all) {
1810
1981
  const lane = this.lanes[this.laneIndexOf(key)];
1982
+ if (lane.ghost) {
1983
+ this.fadeGhostKey(key, lane, 1);
1984
+ if (!this.hasGhostTracer(lane)) lane.ghost = null;
1985
+ }
1811
1986
  const [tx, ty] = this.tileXY(key);
1812
1987
  const tk = _chunk474IQG74cjs.tileKey.call(void 0, tx, ty);
1813
1988
  let lightChanged = false;
1814
- if (lane.lit) {
1989
+ if (lane.lit && !lane.ghost) {
1815
1990
  const lout = this.shadeLightSlot(key, _nullishCoalesce(lane.light.tiles.get(tk), () => ( null)), all ? "bake-all" : "bake");
1816
1991
  if (lout) {
1817
1992
  const tiles2 = new Map(lane.light.tiles);
@@ -1822,7 +1997,7 @@ var WetWorld = (_class = class {
1822
1997
  lightChanged = true;
1823
1998
  }
1824
1999
  }
1825
- const src = _nullishCoalesce(_optionalChain([lane, 'access', _21 => _21.base, 'access', _22 => _22.tiles, 'access', _23 => _23.get, 'call', _24 => _24(tk), 'optionalAccess', _25 => _25.data]), () => ( null));
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));
1826
2001
  const out = this.shadeSlot(key, src, all ? "bake-all" : "bake");
1827
2002
  if (!out) return lightChanged;
1828
2003
  const tiles = new Map(lane.base.tiles);
@@ -1857,7 +2032,8 @@ var WetWorld = (_class = class {
1857
2032
  const out = src ? new Uint8ClampedArray(src) : new Uint8ClampedArray(_chunk474IQG74cjs.TILE * _chunk474IQG74cjs.TILE * 4);
1858
2033
  const w = Math.min(WET_TILE, this.width - tx * WET_TILE);
1859
2034
  const h = Math.min(WET_TILE, this.height - ty * WET_TILE);
1860
- const lit = this.lanes[this.laneIndexOf(key)].lit;
2035
+ const lane = this.lanes[this.laneIndexOf(key)];
2036
+ const lit = lane.lit && !lane.ghost;
1861
2037
  const changed = shadeTile(
1862
2038
  this.f32,
1863
2039
  PLANE,
@@ -1885,7 +2061,38 @@ var WetWorld = (_class = class {
1885
2061
  for (const key of this.active) {
1886
2062
  if (this.laneIndexOf(key) !== lane.index) continue;
1887
2063
  const tk = this.tileKeyOf(key);
1888
- const out = this.shadeSlot(key, _nullishCoalesce(_optionalChain([lane, 'access', _26 => _26.base, 'access', _27 => _27.tiles, 'access', _28 => _28.get, 'call', _29 => _29(tk), 'optionalAccess', _30 => _30.data]), () => ( null)), "bake-all");
2064
+ let out;
2065
+ if (lane.ghost) {
2066
+ const b = this.slotBase(this.slotOf(key));
2067
+ const cur = b + this.parity * SET_PLANES * PLANE;
2068
+ const savedP = this.f32.slice(cur + P_P0 * PLANE, cur + (P_P0 + MASS_CHANNELS) * PLANE);
2069
+ const savedD = this.f32.slice(b + P_D0 * PLANE, b + (P_D0 + MASS_CHANNELS) * PLANE);
2070
+ const pig = lane.ghost.pigment;
2071
+ for (let y = 1; y <= WET_TILE; y++) {
2072
+ for (let x = 1; x <= WET_TILE; x++) {
2073
+ const c = y * S2 + x;
2074
+ const tl = this.f32[cur + P_L * PLANE + c];
2075
+ const dl = this.f32[b + P_DL * PLANE + c];
2076
+ if (!(tl > 0 || dl > 0)) continue;
2077
+ this.f32[cur + P_L * PLANE + c] = 0;
2078
+ this.f32[b + P_DL * PLANE + c] = 0;
2079
+ for (let q = 0; q < 4; q++) {
2080
+ const eps = Math.max(1e-3, pig[q] * 2e-4);
2081
+ const pi = cur + (P_P0 + q) * PLANE + c;
2082
+ const pv = Math.max(0, Math.fround(this.f32[pi] - Math.fround(tl * pig[q])));
2083
+ this.f32[pi] = pv <= eps ? 0 : pv;
2084
+ const di = b + (P_D0 + q) * PLANE + c;
2085
+ const dv = Math.max(0, Math.fround(this.f32[di] - Math.fround(dl * pig[q])));
2086
+ this.f32[di] = dv <= eps ? 0 : dv;
2087
+ }
2088
+ }
2089
+ }
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");
2091
+ this.f32.set(savedP, cur + P_P0 * PLANE);
2092
+ this.f32.set(savedD, b + P_D0 * PLANE);
2093
+ } 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");
2095
+ }
1889
2096
  if (!out) continue;
1890
2097
  const t = _chunk474IQG74cjs.finishTile.call(void 0, out, 4, 0);
1891
2098
  if (t) tiles.set(tk, t);
@@ -1897,7 +2104,7 @@ var WetWorld = (_class = class {
1897
2104
  bakedLight(id) {
1898
2105
  this.requireIdle();
1899
2106
  const lane = this.laneOf(id);
1900
- if (!lane.lit) return lane.light;
2107
+ if (!lane.lit || lane.ghost) return lane.light;
1901
2108
  const tiles = new Map(lane.light.tiles);
1902
2109
  for (const key of this.active) {
1903
2110
  if (this.laneIndexOf(key) !== lane.index) continue;
@@ -2008,14 +2215,14 @@ var WetWorld = (_class = class {
2008
2215
  display(id, maxTiles = Infinity) {
2009
2216
  const lane = this.laneOf(id);
2010
2217
  const keys = this.active.filter((k) => this.laneIndexOf(k) === lane.index);
2011
- const stale = keys.filter((k) => _optionalChain([lane, 'access', _31 => _31.shaded, 'access', _32 => _32.get, 'call', _33 => _33(this.tileKeyOf(k)), 'optionalAccess', _34 => _34.stamp]) !== lane.stamp);
2012
- stale.sort((a, b) => Number(lane.urgent.has(this.tileKeyOf(b))) - Number(lane.urgent.has(this.tileKeyOf(a))) || (_nullishCoalesce(_optionalChain([lane, 'access', _35 => _35.shaded, 'access', _36 => _36.get, 'call', _37 => _37(this.tileKeyOf(a)), 'optionalAccess', _38 => _38.stamp]), () => ( 0))) - (_nullishCoalesce(_optionalChain([lane, 'access', _39 => _39.shaded, 'access', _40 => _40.get, 'call', _41 => _41(this.tileKeyOf(b)), 'optionalAccess', _42 => _42.stamp]), () => ( 0))));
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))));
2013
2220
  const changed = [];
2014
2221
  let done = 0;
2015
2222
  for (const key of stale) {
2016
2223
  if (done >= maxTiles) break;
2017
2224
  const tk = this.tileKeyOf(key);
2018
- const src = _nullishCoalesce(_optionalChain([lane, 'access', _43 => _43.base, 'access', _44 => _44.tiles, 'access', _45 => _45.get, 'call', _46 => _46(tk), 'optionalAccess', _47 => _47.data]), () => ( null));
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));
2019
2226
  const out = this.shadeSlot(key, src, "display");
2020
2227
  const tile = out ? _chunk474IQG74cjs.finishTile.call(void 0, out, 4, 0) : _nullishCoalesce(lane.base.tiles.get(tk), () => ( null));
2021
2228
  lane.shaded.set(tk, { stamp: lane.stamp, tile: _nullishCoalesce(tile, () => ( { data: new Uint8ClampedArray(0), uniform: 0 })) });
@@ -2045,14 +2252,14 @@ var WetWorld = (_class = class {
2045
2252
  */
2046
2253
  lightDisplay(id) {
2047
2254
  const lane = this.laneOf(id);
2048
- if (!lane.lit) return null;
2255
+ if (!lane.lit || lane.ghost) return null;
2049
2256
  const keys = this.active.filter((k) => this.laneIndexOf(k) === lane.index);
2050
2257
  let changed = false;
2051
2258
  const live = /* @__PURE__ */ new Set();
2052
2259
  for (const key2 of keys) {
2053
2260
  const tk = this.tileKeyOf(key2);
2054
2261
  live.add(tk);
2055
- if (_optionalChain([lane, 'access', _48 => _48.lightShaded, 'access', _49 => _49.get, 'call', _50 => _50(tk), 'optionalAccess', _51 => _51.stamp]) === lane.stamp) continue;
2262
+ if (_optionalChain([lane, 'access', _56 => _56.lightShaded, 'access', _57 => _57.get, 'call', _58 => _58(tk), 'optionalAccess', _59 => _59.stamp]) === lane.stamp) continue;
2056
2263
  const src = _nullishCoalesce(lane.light.tiles.get(tk), () => ( null));
2057
2264
  const out = this.shadeLightSlot(key2, src, "display");
2058
2265
  lane.lightShaded.set(tk, { stamp: lane.stamp, tile: out ? _chunk474IQG74cjs.finishTile.call(void 0, out, 1, 0) : src });
@@ -2250,7 +2457,7 @@ var WetWorld = (_class = class {
2250
2457
  if (!this.gpu || this.busyFlag && !plan || !plan && !this.gpuDecide()) return { ...this.display(id, maxCpuTiles), gpu: false };
2251
2458
  const lane = this.laneOf(id);
2252
2459
  const keys = this.active.filter((k) => this.laneIndexOf(k) === lane.index);
2253
- const stale = plan ? keys : keys.filter((k) => _optionalChain([lane, 'access', _52 => _52.shaded, 'access', _53 => _53.get, 'call', _54 => _54(this.tileKeyOf(k)), 'optionalAccess', _55 => _55.stamp]) !== lane.stamp);
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);
2254
2461
  if (!stale.length) return { ...this.display(id, 0), gpu: true };
2255
2462
  let tiles;
2256
2463
  const stamp = lane.stamp;
@@ -2258,7 +2465,7 @@ var WetWorld = (_class = class {
2258
2465
  try {
2259
2466
  if (!plan) this.gpuSync();
2260
2467
  const parity = plan ? plan.parity + plan.done & 1 : this.parity;
2261
- tiles = await this.gpu.shade(this.gpuJobs(stale), parity, stale.map((k) => _nullishCoalesce(_optionalChain([base, 'access', _56 => _56.tiles, 'access', _57 => _57.get, 'call', _58 => _58(this.tileKeyOf(k)), 'optionalAccess', _59 => _59.data]), () => ( null))), [this.paperBytes[0], this.paperBytes[1], this.paperBytes[2]]);
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]]);
2262
2469
  } catch (e) {
2263
2470
  this.gpuStop(`Wet paint stopped using the GPU (${e.message}).`);
2264
2471
  return { ...this.display(id, maxCpuTiles), gpu: false };
@@ -2291,7 +2498,7 @@ var WetWorld = (_class = class {
2291
2498
  slots.set(key, { planes, last: this.u32[(this.layout.last >> 2) + key] });
2292
2499
  bytes += planes.byteLength;
2293
2500
  }
2294
- const lanes = this.lanes.filter((l) => Boolean(l)).map((l) => ({ id: l.id, index: l.index, params: { ...l.params }, flow: l.flow, flowStrength: l.flowStrength, base: l.base, light: l.light, lit: l.lit }));
2501
+ const lanes = this.lanes.filter((l) => Boolean(l)).map((l) => ({ id: l.id, index: l.index, params: { ...l.params }, flow: l.flow, flowStrength: l.flowStrength, base: l.base, light: l.light, lit: l.lit, ghost: cloneGhost(l.ghost) }));
2295
2502
  return { clock: this.clock, tilt: { ...this.tilt }, lanes, slots, bytes };
2296
2503
  }
2297
2504
  /** Puts a snapshot back exactly (the simulation then continues as it would have). */
@@ -2301,8 +2508,8 @@ var WetWorld = (_class = class {
2301
2508
  this.lanes.fill(null);
2302
2509
  this.tilt = { ...snap.tilt };
2303
2510
  for (const l of snap.lanes) {
2304
- const lane = newLane(l.id, l.index, { ...l.params }, l.flow, l.flowStrength, l.base, l.light);
2305
- lane.lit = lane.lit || Boolean(l.lit);
2511
+ const lane = newLane(l.id, l.index, { ...l.params }, l.flow, l.flowStrength, l.base, l.light, l.ghost);
2512
+ lane.lit = !lane.ghost && (lane.lit || Boolean(l.lit));
2306
2513
  this.lanes[l.index] = lane;
2307
2514
  this.pack(this.lanes[l.index]);
2308
2515
  }
@@ -2437,8 +2644,8 @@ function normalizeFlow(raw = {}, base = DEFAULT_FLOW) {
2437
2644
  }
2438
2645
  function flowField(spec, width, height) {
2439
2646
  const k = FLOW_ACCEL;
2440
- const cx = _nullishCoalesce(_optionalChain([spec, 'access', _60 => _60.center, 'optionalAccess', _61 => _61.x]), () => ( width / 2));
2441
- const cy = _nullishCoalesce(_optionalChain([spec, 'access', _62 => _62.center, 'optionalAccess', _63 => _63.y]), () => ( height / 2));
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));
2442
2649
  const r0 = Math.max(width, height) / 2;
2443
2650
  switch (spec.kind) {
2444
2651
  case "none":
@@ -2901,7 +3108,7 @@ var UNIFORM = 64;
2901
3108
  var STORAGE = 128;
2902
3109
  function findWebGpu() {
2903
3110
  const nav = globalThis.navigator;
2904
- return _nullishCoalesce(_optionalChain([nav, 'optionalAccess', _64 => _64.gpu]), () => ( null));
3111
+ return _nullishCoalesce(_optionalChain([nav, 'optionalAccess', _72 => _72.gpu]), () => ( null));
2905
3112
  }
2906
3113
  async function createWetGpuEngine(gpu = findWebGpu()) {
2907
3114
  const no = (reason, adapter2 = null) => ({ engine: null, support: { supported: false, reason, adapter: adapter2, maxSlots: 0 } });
@@ -2913,7 +3120,7 @@ async function createWetGpuEngine(gpu = findWebGpu()) {
2913
3120
  return no(`WebGPU gave no adapter (${e.message}); wet paint runs on the CPU.`);
2914
3121
  }
2915
3122
  if (!adapter) return no("WebGPU gave no adapter (the GPU may be blocklisted); wet paint runs on the CPU.");
2916
- const name = [_optionalChain([adapter, 'access', _65 => _65.info, 'optionalAccess', _66 => _66.vendor]), _optionalChain([adapter, 'access', _67 => _67.info, 'optionalAccess', _68 => _68.architecture])].filter(Boolean).join(" ") || null;
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;
2917
3124
  const binding = Math.min(_nullishCoalesce(adapter.limits.maxStorageBufferBindingSize, () => ( 0)), _nullishCoalesce(adapter.limits.maxBufferSize, () => ( 0)));
2918
3125
  const maxSlots = Math.min(MAX_WET_SLOTS, Math.floor(binding / SLOT_BYTES));
2919
3126
  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);
@@ -2933,27 +3140,27 @@ async function createWetGpuEngine(gpu = findWebGpu()) {
2933
3140
  const engine = await WetGpuEngine.start(device, maxSlots, name);
2934
3141
  return { engine, support: { supported: true, reason: null, adapter: name, maxSlots } };
2935
3142
  } catch (e) {
2936
- _optionalChain([device, 'access', _69 => _69.destroy, 'optionalCall', _70 => _70()]);
3143
+ _optionalChain([device, 'access', _77 => _77.destroy, 'optionalCall', _78 => _78()]);
2937
3144
  return no(`The wet-media shaders did not compile on this GPU (${e.message}); wet paint runs on the CPU.`, name);
2938
3145
  }
2939
3146
  }
2940
3147
  async function wetGpuSupport(gpu = findWebGpu()) {
2941
3148
  const { engine, support } = await createWetGpuEngine(gpu);
2942
- _optionalChain([engine, 'optionalAccess', _71 => _71.destroy, 'call', _72 => _72()]);
3149
+ _optionalChain([engine, 'optionalAccess', _79 => _79.destroy, 'call', _80 => _80()]);
2943
3150
  return support;
2944
3151
  }
2945
3152
  async function pipeline(device, code, label) {
2946
- _optionalChain([device, 'access', _73 => _73.pushErrorScope, 'optionalCall', _74 => _74("validation")]);
3153
+ _optionalChain([device, 'access', _81 => _81.pushErrorScope, 'optionalCall', _82 => _82("validation")]);
2947
3154
  const module = device.createShaderModule({ code, label });
2948
- const info = await _optionalChain([module, 'access', _75 => _75.getCompilationInfo, 'optionalCall', _76 => _76()]);
2949
- const errors = _nullishCoalesce(_optionalChain([info, 'optionalAccess', _77 => _77.messages, 'access', _78 => _78.filter, 'call', _79 => _79((m) => m.type === "error")]), () => ( []));
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")]), () => ( []));
2950
3157
  if (errors.length) {
2951
- await _optionalChain([device, 'access', _80 => _80.popErrorScope, 'optionalCall', _81 => _81()]);
3158
+ await _optionalChain([device, 'access', _88 => _88.popErrorScope, 'optionalCall', _89 => _89()]);
2952
3159
  throw new Error(`${label}: ${errors.map((m) => `line ${m.lineNum}: ${m.message}`).join("; ")}`);
2953
3160
  }
2954
3161
  const desc = { layout: "auto", compute: { module, entryPoint: "main" }, label };
2955
3162
  const p = device.createComputePipelineAsync ? await device.createComputePipelineAsync(desc) : device.createComputePipeline(desc);
2956
- const err = await _optionalChain([device, 'access', _82 => _82.popErrorScope, 'optionalCall', _83 => _83()]);
3163
+ const err = await _optionalChain([device, 'access', _90 => _90.popErrorScope, 'optionalCall', _91 => _91()]);
2957
3164
  if (err) throw new Error(`${label}: ${err.message}`);
2958
3165
  return p;
2959
3166
  }
@@ -2979,8 +3186,8 @@ var WetGpuEngine = (_class3 = class _WetGpuEngine {
2979
3186
  return b;
2980
3187
  });
2981
3188
  this.shadeUni = d.createBuffer({ size: 16, usage: UNIFORM | COPY_DST, label: "wet shade" });
2982
- void _optionalChain([device, 'access', _84 => _84.lost, 'optionalAccess', _85 => _85.then, 'call', _86 => _86((info) => {
2983
- this.lostReason = _optionalChain([info, 'optionalAccess', _87 => _87.message]) || _optionalChain([info, 'optionalAccess', _88 => _88.reason]) || "the GPU device was lost";
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";
2984
3191
  })]);
2985
3192
  }
2986
3193
 
@@ -3014,7 +3221,7 @@ var WetGpuEngine = (_class3 = class _WetGpuEngine {
3014
3221
  }
3015
3222
  /** Bytes of GPU memory in use. */
3016
3223
  memoryBytes() {
3017
- return this.state.size + this.jobs.size + (_nullishCoalesce(_optionalChain([this, 'access', _89 => _89.srcTiles, 'optionalAccess', _90 => _90.size]), () => ( 0))) + (_nullishCoalesce(_optionalChain([this, 'access', _91 => _91.outTiles, 'optionalAccess', _92 => _92.size]), () => ( 0))) * 2;
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;
3018
3225
  }
3019
3226
  /** Slots the state buffer holds now. */
3020
3227
  get capacity() {
@@ -3163,9 +3370,9 @@ var WetGpuEngine = (_class3 = class _WetGpuEngine {
3163
3370
  const d = this.device;
3164
3371
  const tileBytes = TILE_WORDS * 4;
3165
3372
  if (n > this.tileCap || !this.outTiles || !this.outRead || !this.srcTiles) {
3166
- _optionalChain([this, 'access', _93 => _93.srcTiles, 'optionalAccess', _94 => _94.destroy, 'call', _95 => _95()]);
3167
- _optionalChain([this, 'access', _96 => _96.outTiles, 'optionalAccess', _97 => _97.destroy, 'call', _98 => _98()]);
3168
- _optionalChain([this, 'access', _99 => _99.outRead, 'optionalAccess', _100 => _100.destroy, 'call', _101 => _101()]);
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()]);
3169
3376
  this.tileCap = Math.max(n, this.tileCap * 2, 4);
3170
3377
  this.srcTiles = d.createBuffer({ size: this.tileCap * tileBytes, usage: STORAGE | COPY_DST, label: "wet base tiles" });
3171
3378
  this.outTiles = d.createBuffer({ size: this.tileCap * tileBytes, usage: STORAGE | COPY_SRC, label: "wet shaded tiles" });
@@ -3219,7 +3426,7 @@ var WetGpuEngine = (_class3 = class _WetGpuEngine {
3219
3426
  return this.device.queue.onSubmittedWorkDone();
3220
3427
  }
3221
3428
  destroy() {
3222
- 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', _102 => _102.destroy, 'call', _103 => _103()]);
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()]);
3223
3430
  this.device.destroy();
3224
3431
  }
3225
3432
  }, _class3);
@@ -27,7 +27,7 @@ import {
27
27
  resolveEngine,
28
28
  summarizeBrushSource,
29
29
  withKeptIssues
30
- } from "./chunk-KZI3YWKW.js";
30
+ } from "./chunk-UXYCY3HP.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", "motion", "colour"];
47
+ var UNSETTABLE_BRUSH_FACETS = ["description", "tags", "medium", "variety", "marks", "dynamics", "behaviour", "light", "ghost", "motion", "colour"];
48
48
  var BRUSH_UNSET_PROPERTY = {
49
49
  type: "array",
50
50
  minItems: 1,
@@ -1117,9 +1117,13 @@ var PICKUP_SLIDERS = [
1117
1117
  { path: "behavior.pickup", label: "Pick-up", min: 0, max: 1, step: 0.01 },
1118
1118
  { path: "behavior.mix", label: "Mix", min: 0, max: 1, step: 0.01 }
1119
1119
  ];
1120
+ var GHOST_SLIDERS = [
1121
+ { path: "ghost.linger", label: "Linger", min: 0.5, max: 60, step: 0.5 },
1122
+ { path: "ghost.smear", label: "Smear", min: 0, max: 1, step: 0.01 }
1123
+ ];
1120
1124
  function brushEngineControls(brush) {
1121
1125
  const c = engineControls(brush);
1122
- const wetPaint = brush.engine === "wet" && brush.wet?.tool === "paint";
1126
+ const wetPaint = brush.engine === "wet" && brush.wet?.tool === "paint" && !brush.ghost;
1123
1127
  if (!c || !(wetPaint || ["dab", "mixer", "particle", "bristle", "impasto"].includes(brush.engine))) return c;
1124
1128
  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 }] : [];
1125
1129
  return { ...c, sliders: [...c.sliders, LIGHT_SLIDER, ...extra], light: true };
@@ -1203,6 +1207,7 @@ function engineControls(brush) {
1203
1207
  };
1204
1208
  case "wet": {
1205
1209
  const tool = brush.wet?.tool ?? "paint";
1210
+ const ghost = tool === "paint" && Boolean(brush.ghost);
1206
1211
  const sliders = tool === "paint" ? [
1207
1212
  { path: "wet.water", label: "Water", min: 0, max: 1, step: 0.01 },
1208
1213
  { path: "wet.pigment", label: "Pigment", min: 0, max: 1, step: 0.01 },
@@ -1210,12 +1215,12 @@ function engineControls(brush) {
1210
1215
  { path: "wet.rewet", label: "Re-wet", min: 0, max: 1, step: 0.01 },
1211
1216
  { path: "wet.load", label: "Load", min: 0, max: 1, step: 0.01 },
1212
1217
  { path: "wet.thinner", label: "Thinner", min: 0, max: 1, step: 0.01 },
1213
- ...PICKUP_SLIDERS
1218
+ ...ghost ? GHOST_SLIDERS : PICKUP_SLIDERS
1214
1219
  ] : tool === "water" ? [
1215
1220
  { path: "wet.water", label: "Water", min: 0, max: 1, step: 0.01 },
1216
1221
  { path: "wet.rewet", label: "Re-wet", min: 0, max: 1, step: 0.01 }
1217
1222
  ] : [{ path: "wet.strength", label: "Strength", min: 0, max: 1, step: 0.01 }];
1218
- return { title: "Wet brush", sliders, mixingPath: tool === "paint" ? "behavior.mixing" : null, dirty: tool === "paint", multicolor: false, load: tool === "paint", wetTool: true };
1223
+ return { title: "Wet brush", sliders, mixingPath: tool === "paint" ? "behavior.mixing" : null, dirty: tool === "paint" && !ghost, multicolor: false, load: tool === "paint" && !ghost, wetTool: true };
1219
1224
  }
1220
1225
  case "blender":
1221
1226
  return {
@@ -1284,6 +1289,7 @@ export {
1284
1289
  createBrushLibraryTools,
1285
1290
  createBrushLibraryToolProvider,
1286
1291
  BRUSH_SLIDERS,
1292
+ GHOST_SLIDERS,
1287
1293
  brushEngineControls,
1288
1294
  multiColorSliders,
1289
1295
  patchBrushValue