@pixerate/editor 0.243.0 → 0.244.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 (62) hide show
  1. package/dist/animation-editor/index.cjs +59 -59
  2. package/dist/animation-editor/index.js +3 -3
  3. package/dist/{brushstrokeRender-274HGNZR.js → brushstrokeRender-4542T5HP.js} +2 -2
  4. package/dist/{brushstrokeRender-C7RJZPMC.cjs → brushstrokeRender-QA5WVIDT.cjs} +5 -5
  5. package/dist/canvas/index.cjs +8 -8
  6. package/dist/canvas/index.js +1 -1
  7. package/dist/{chunk-6L3WWIDW.js → chunk-4EQY4TZJ.js} +1 -1
  8. package/dist/{chunk-5FZS7J7C.cjs → chunk-4T47Z4J3.cjs} +18 -2
  9. package/dist/{chunk-4ZHXLFXS.js → chunk-5RFENYO5.js} +1 -1
  10. package/dist/{chunk-SRXWDOUI.js → chunk-7YQNKSQ7.js} +1 -1
  11. package/dist/{chunk-OONFIFEU.cjs → chunk-BDIH4DYR.cjs} +23 -23
  12. package/dist/{chunk-KK2FWMA2.cjs → chunk-HZ547SGQ.cjs} +1 -1
  13. package/dist/{chunk-3WOZEBXZ.cjs → chunk-IZL5V5RK.cjs} +2 -2
  14. package/dist/{chunk-IXI5KATU.js → chunk-L5PB7EG3.js} +1 -1
  15. package/dist/{chunk-R2FVAKFH.cjs → chunk-M6JORA5Z.cjs} +86 -86
  16. package/dist/{chunk-H4Z72Z4Z.js → chunk-OSTKS3YN.js} +1 -1
  17. package/dist/{chunk-HLBTRSQJ.js → chunk-P3NIPIKS.js} +263 -63
  18. package/dist/{chunk-UV7OOZJQ.cjs → chunk-SD23XMVJ.cjs} +35 -35
  19. package/dist/{chunk-HQZHOPNE.js → chunk-TBJEMELS.js} +18 -2
  20. package/dist/{chunk-XFYLA3ZO.cjs → chunk-VMROIWLL.cjs} +290 -90
  21. package/dist/image-editor/index.cjs +785 -777
  22. package/dist/image-editor/index.js +13 -5
  23. package/dist/kernel-wasm-4KVONDAU.js +9 -0
  24. package/dist/kernel-wasm-TTYRBGAK.cjs +9 -0
  25. package/dist/paint/index.cjs +10 -2
  26. package/dist/paint/index.js +9 -1
  27. package/dist/paint/wet/film.d.cts +19 -1
  28. package/dist/paint/wet/film.d.ts +19 -1
  29. package/dist/paint/wet/gpu-kernels.d.cts +2 -2
  30. package/dist/paint/wet/gpu-kernels.d.ts +2 -2
  31. package/dist/paint/wet/kernel-wasm.d.cts +5 -5
  32. package/dist/paint/wet/kernel-wasm.d.ts +5 -5
  33. package/dist/paint/wet/kernel.d.cts +1 -1
  34. package/dist/paint/wet/kernel.d.ts +1 -1
  35. package/dist/paint/wet/medium.d.cts +3 -1
  36. package/dist/paint/wet/medium.d.ts +3 -1
  37. package/dist/paint/wet/model.d.cts +19 -4
  38. package/dist/paint/wet/model.d.ts +19 -4
  39. package/dist/paint/wet/world.d.cts +34 -8
  40. package/dist/paint/wet/world.d.ts +34 -8
  41. package/dist/paint-editor/backlight.d.cts +77 -0
  42. package/dist/paint-editor/backlight.d.ts +77 -0
  43. package/dist/paint-editor/controller.d.cts +11 -0
  44. package/dist/paint-editor/controller.d.ts +11 -0
  45. package/dist/paint-editor/index.cjs +685 -397
  46. package/dist/paint-editor/index.d.cts +1 -0
  47. package/dist/paint-editor/index.d.ts +1 -0
  48. package/dist/paint-editor/index.js +308 -20
  49. package/dist/paint-editor/paper.d.cts +7 -0
  50. package/dist/paint-editor/paper.d.ts +7 -0
  51. package/dist/paint-editor/wet.d.cts +9 -0
  52. package/dist/paint-editor/wet.d.ts +9 -0
  53. package/dist/vector/index.cjs +962 -962
  54. package/dist/vector/index.js +4 -4
  55. package/package.json +1 -1
  56. package/skills/pixerate-paint-editor/SKILL.md +3 -3
  57. package/skills/pixerate-paint-editor/references/light-paint.md +24 -7
  58. package/skills/pixerate-paint-watercolour/SKILL.md +10 -1
  59. package/skills/pixerate-paint-watercolour/evals/glowing-wash.json +22 -0
  60. package/skills/pixerate-paint-watercolour/evals/stained-glass.json +16 -0
  61. package/dist/kernel-wasm-6FPOLVUZ.cjs +0 -9
  62. package/dist/kernel-wasm-WIMMJUF2.js +0 -9
@@ -85,20 +85,23 @@ var WET_TILE = 256;
85
85
  var SLOT_SIDE = 258;
86
86
  var PLANE = SLOT_SIDE * SLOT_SIDE;
87
87
  var PLANE_BYTES = PLANE * 4;
88
- var SET_PLANES = 18;
88
+ var SET_PLANES = 19;
89
89
  var P_RHO = 9;
90
90
  var P_UX = 10;
91
91
  var P_UY = 11;
92
92
  var P_SW = 12;
93
93
  var P_P0 = 13;
94
- var P_EDGE = 17;
94
+ var P_L = P_P0 + 4;
95
+ var P_EDGE = 18;
95
96
  var P_D0 = 2 * SET_PLANES;
96
- var P_H = P_D0 + 4;
97
+ var P_DL = P_D0 + 4;
98
+ var P_H = P_D0 + 5;
97
99
  var P_FX = P_H + 1;
98
100
  var P_FY = P_H + 2;
99
101
  var SLOT_PLANES = P_FY + 1;
100
102
  var SLOT_BYTES = SLOT_PLANES * PLANE_BYTES;
101
103
  var PIGMENT_CHANNELS = 4;
104
+ var MASS_CHANNELS = 5;
102
105
  var EX = [0, 1, 0, -1, 0, 1, -1, -1, 1];
103
106
  var EY = [0, 0, 1, 0, -1, 1, 1, -1, -1];
104
107
  var OPP = [0, 3, 4, 1, 2, 7, 8, 5, 6];
@@ -358,10 +361,9 @@ function filmPixel(k0, k1, k2, s, px, i, paper, wet, out, o) {
358
361
  const e0 = linearToSrgb8(scratch[0] * dark);
359
362
  const e1 = linearToSrgb8(scratch[1] * dark);
360
363
  const e2 = linearToSrgb8(scratch[2] * dark);
361
- let alpha = a;
362
- alpha = needAlpha(e0, p0, alpha);
363
- alpha = needAlpha(e1, p1, alpha);
364
- alpha = needAlpha(e2, p2, alpha);
364
+ let alpha = needAlpha(e2, p2, needAlpha(e1, p1, needAlpha(e0, p0, 0)));
365
+ if (s > 0 && alpha < 1) alpha += (1 - expNeg(HIDE * s)) * (1 - alpha);
366
+ if (a > alpha) alpha = a;
365
367
  if (alpha <= 0) {
366
368
  out[o] = px[i];
367
369
  out[o + 1] = px[i + 1];
@@ -392,7 +394,7 @@ function pixelPigment(px, i, paper, out) {
392
394
  }
393
395
  out[3] = 0;
394
396
  }
395
- function shadeTile(f, plane, side, d, p, rho, sw, src, out, w, h, paper) {
397
+ function shadeTile(f, plane, side, d, p, rho, sw, src, out, w, h, paper, dl = -1, pl = -1) {
396
398
  const p0 = paper[0];
397
399
  const p1 = paper[1];
398
400
  const p2 = paper[2];
@@ -445,14 +447,31 @@ function shadeTile(f, plane, side, d, p, rho, sw, src, out, w, h, paper) {
445
447
  r1 += hide * rinf(k1 * inv);
446
448
  r2 += hide * rinf(k2 * inv);
447
449
  }
450
+ if (dl >= 0) {
451
+ let l = f[dl + c];
452
+ if (pl >= 0) l += f[pl + c];
453
+ if (l > 1e-7) {
454
+ if (l > 1) l = 1;
455
+ let w0 = keep * expT(tab, 2 * k0);
456
+ let w1 = keep * expT(tab, 2 * k1);
457
+ let w2 = keep * expT(tab, 2 * k2);
458
+ if (hide > 0) {
459
+ w0 += hide * rinf(k0 * inv);
460
+ w1 += hide * rinf(k1 * inv);
461
+ w2 += hide * rinf(k2 * inv);
462
+ }
463
+ r0 += l * (w0 - r0);
464
+ r1 += l * (w1 - r1);
465
+ r2 += l * (w2 - r2);
466
+ }
467
+ }
448
468
  const dark = 1 - 0.07 * wet;
449
469
  const e0 = encode(enc, r0 * dark);
450
470
  const e1 = encode(enc, r1 * dark);
451
471
  const e2 = encode(enc, r2 * dark);
452
- let alpha = a;
453
- alpha = needAlpha(e0, p0, alpha);
454
- alpha = needAlpha(e1, p1, alpha);
455
- alpha = needAlpha(e2, p2, alpha);
472
+ let alpha = needAlpha(e2, p2, needAlpha(e1, p1, needAlpha(e0, p0, 0)));
473
+ if (hide > 0 && alpha < 1) alpha += hide * (1 - alpha);
474
+ if (a > alpha) alpha = a;
456
475
  if (alpha <= 0) {
457
476
  out[i + 3] = 0;
458
477
  continue;
@@ -483,6 +502,42 @@ function encode(enc, v) {
483
502
  if (v >= 1) return 255;
484
503
  return enc[Math.round(v * ENC_STEPS)];
485
504
  }
505
+ function shadeLightTile(f, plane, side, d, p, dl, pl, src, out, w, h) {
506
+ const tab = EXP_TABLE;
507
+ let any = false;
508
+ for (let y = 0; y < h; y++) {
509
+ for (let x = 0; x < w; x++) {
510
+ const c = (y + 1) * side + x + 1;
511
+ let l = f[dl + c];
512
+ if (pl >= 0) l += f[pl + c];
513
+ const i = y * 256 + x;
514
+ const old = src ? src[i] : 0;
515
+ if (!(l > 1e-7) && old === 0) continue;
516
+ let keep = 1;
517
+ if (old > 0) {
518
+ let k0 = f[d + c];
519
+ let k1 = f[d + plane + c];
520
+ let k2 = f[d + 2 * plane + c];
521
+ let s = f[d + 3 * plane + c];
522
+ if (p >= 0) {
523
+ k0 += f[p + c];
524
+ k1 += f[p + plane + c];
525
+ k2 += f[p + 2 * plane + c];
526
+ s += f[p + 3 * plane + c];
527
+ }
528
+ if (k0 > 1e-7 || k1 > 1e-7 || k2 > 1e-7 || s > 1e-7) {
529
+ const hide = s > 0 ? 1 - expT(tab, HIDE * s) : 0;
530
+ keep = (1 - hide) * (expT(tab, 2 * k0) + expT(tab, 2 * k1) + expT(tab, 2 * k2)) / 3;
531
+ }
532
+ }
533
+ const v = Math.round(Math.min(255, 255 * Math.min(1, l > 0 ? l : 0) + old * keep));
534
+ if (v === old) continue;
535
+ out[i] = v;
536
+ any = true;
537
+ }
538
+ }
539
+ return any;
540
+ }
486
541
 
487
542
  // src/paint/wet/kernel.ts
488
543
  var F = Math.fround;
@@ -498,7 +553,7 @@ var DECAY = F(0.99);
498
553
  var NB = [0, -1, -S, 1, S, -S - 1, -S + 1, S + 1, S - 1];
499
554
  var OPP2 = [0, 3, 4, 1, 2, 7, 8, 5, 6];
500
555
  var fin = new Float32Array(9);
501
- var pNew = new Float32Array(4);
556
+ var pNew = new Float32Array(MASS_CHANNELS);
502
557
  function stepSlotTs(f, base, parity, prm) {
503
558
  const src = base + parity * SET_PLANES * PLANE;
504
559
  const dst = base + (1 - parity) * SET_PLANES * PLANE;
@@ -632,13 +687,13 @@ function stepSlotTs(f, base, parity, prm) {
632
687
  const rxWet = !rxDry;
633
688
  const uxc = f[uxS + c];
634
689
  const uyc = f[uyS + c];
635
- for (let q = 0; q < 4; q++) pNew[q] = f[pS + q * PLANE + c];
690
+ for (let q = 0; q < MASS_CHANNELS; q++) pNew[q] = f[pS + q * PLANE + c];
636
691
  for (let face = 0; face < 4; face++) {
637
692
  const nb = c + (face === 0 ? 1 : face === 1 ? -1 : face === 2 ? S : -S);
638
693
  const open = rxWet && f[rhoS + nb] >= EPS;
639
694
  const uf = face === 0 ? F(mobility * F(uxc + f[uxS + nb])) : face === 1 ? F(mobility * F(f[uxS + nb] + uxc)) : face === 2 ? F(mobility * F(uyc + f[uyS + nb])) : F(mobility * F(f[uyS + nb] + uyc));
640
695
  const out = face === 0 || face === 2;
641
- for (let q = 0; q < 4; q++) {
696
+ for (let q = 0; q < MASS_CHANNELS; q++) {
642
697
  const pc = f[pS + q * PLANE + c];
643
698
  const pn = f[pS + q * PLANE + nb];
644
699
  const flux = out ? uf > 0 ? F(uf * pc) : F(uf * pn) : uf > 0 ? F(uf * pn) : F(uf * pc);
@@ -650,7 +705,7 @@ function stepSlotTs(f, base, parity, prm) {
650
705
  const dryNow = rhoNew < EPS;
651
706
  const rate = F(F(depRate * F(1 + F(gran * F(1 - hx)))) * F(1 + F(edgeDep * ex)));
652
707
  const lift = dryNow ? 0 : F(F(pickup * Math.min(rhoNew, 1)) * F(1 + F(bloom * Math.max(0, F(rhoNew - rx)))));
653
- for (let q = 0; q < 4; q++) {
708
+ for (let q = 0; q < MASS_CHANNELS; q++) {
654
709
  const pq = Math.max(0, pNew[q]);
655
710
  const dq = f[dP + q * PLANE + c];
656
711
  const dep = dryNow ? pq : Math.min(pq, F(pq * rate));
@@ -686,7 +741,7 @@ function loadWetKernel(shared) {
686
741
  const key = shared ? "shared" : "single";
687
742
  let p = modules[key];
688
743
  if (!p) {
689
- p = Promise.resolve().then(() => _interopRequireWildcard(require("./kernel-wasm-6FPOLVUZ.cjs"))).then((m) => WebAssembly.compile(decode(shared ? m.WET_KERNEL_SHARED : m.WET_KERNEL_SINGLE)));
744
+ p = Promise.resolve().then(() => _interopRequireWildcard(require("./kernel-wasm-TTYRBGAK.cjs"))).then((m) => WebAssembly.compile(decode(shared ? m.WET_KERNEL_SHARED : m.WET_KERNEL_SINGLE)));
690
745
  p.catch(() => {
691
746
  modules = { ...modules, [key]: void 0 };
692
747
  });
@@ -864,10 +919,15 @@ var WetWorkerPool = class _WetWorkerPool {
864
919
 
865
920
  // src/paint/wet/world.ts
866
921
  var MAX_WET_LANES = 8;
867
- var MAX_WET_SLOTS = 352;
922
+ var MAX_WET_SLOTS = 328;
868
923
  var SLOT_FLOATS = SLOT_PLANES * PLANE;
869
924
  var S2 = SLOT_SIDE;
870
- var SNAP_PLANES = SET_PLANES + 4;
925
+ var SNAP_PLANES = SET_PLANES + MASS_CHANNELS;
926
+ var noLight = () => new (0, _chunk474IQG74cjs.TiledSurface)(1, /* @__PURE__ */ new Map(), 0);
927
+ function newLane(id, index, params, flow, flowStrength, base, light) {
928
+ const l = _nullishCoalesce(light, () => ( noLight()));
929
+ 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: "" };
930
+ }
871
931
  var WetWorldError = class extends Error {
872
932
  constructor(message) {
873
933
  super(message);
@@ -1026,24 +1086,12 @@ var WetWorld = (_class = class {
1026
1086
  if (existing) {
1027
1087
  if (options.params) this.setParams(id, options.params);
1028
1088
  if (options.base) this.setBase(id, options.base);
1089
+ if (options.light) this.setLight(id, options.light);
1029
1090
  return;
1030
1091
  }
1031
1092
  const index = this.lanes.indexOf(null);
1032
1093
  if (index < 0) throw new WetWorldError(`At most ${MAX_WET_LANES} layers can hold wet paint at once; dry one (dry_paint) first.`);
1033
- this.lanes[index] = {
1034
- id,
1035
- index,
1036
- params: normalizeWetParams(_nullishCoalesce(options.params, () => ( {})), DEFAULT_WET_PARAMS),
1037
- flow: _nullishCoalesce(options.flow, () => ( null)),
1038
- flowStrength: options.flow ? _nullishCoalesce(options.flowStrength, () => ( 1)) : 0,
1039
- base: _nullishCoalesce(options.base, () => ( new (0, _chunk474IQG74cjs.TiledSurface)(4, /* @__PURE__ */ new Map(), 0))),
1040
- shaded: /* @__PURE__ */ new Map(),
1041
- urgent: /* @__PURE__ */ new Set(),
1042
- stamp: 1,
1043
- display: null,
1044
- shadedVersion: 0,
1045
- displayKey: ""
1046
- };
1094
+ 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);
1047
1095
  this.pack(this.lanes[index]);
1048
1096
  }
1049
1097
  /** Drops a wet layer and its wet paint (nothing is baked). */
@@ -1110,6 +1158,23 @@ var WetWorld = (_class = class {
1110
1158
  lane.shadedVersion++;
1111
1159
  lane.stamp++;
1112
1160
  }
1161
+ /** The light paint under a layer's wet paint (one channel). */
1162
+ getLight(id) {
1163
+ return this.laneOf(id).light;
1164
+ }
1165
+ /** Whether a layer has light paint, under its wet paint or in it. */
1166
+ isLit(id) {
1167
+ return this.laneOf(id).lit;
1168
+ }
1169
+ /** Replaces the light paint under a layer's wet paint. */
1170
+ setLight(id, light) {
1171
+ const lane = this.laneOf(id);
1172
+ if (light === lane.light) return;
1173
+ lane.light = light;
1174
+ if (light.tiles.size) lane.lit = true;
1175
+ lane.lightShaded.clear();
1176
+ lane.stamp++;
1177
+ }
1113
1178
  pack(lane) {
1114
1179
  packWetParams(lane.params, _nullishCoalesce(this.paper, () => ( DEFAULT_WET_PAPER)), this.tilt, lane.flowStrength, this.prm);
1115
1180
  this.f32.set(this.prm, this.layout.params + lane.index * PARAM_FLOATS * 4 >> 2);
@@ -1535,11 +1600,13 @@ var WetWorld = (_class = class {
1535
1600
  }
1536
1601
  /**
1537
1602
  * Lays paint: `water` depth and pigment mass `pigment` (K r, g, b, S) per unit of coverage, or
1538
- * per pixel from `field` (4 values per pixel of `rect`). Pigment without water lands straight in
1539
- * the paper (settled).
1603
+ * per pixel from `field` (4 values per pixel of `rect`), and `light` (light paint mass) per unit
1604
+ * of coverage. Pigment and light without water land straight in the paper (settled).
1540
1605
  */
1541
- paint(id, rect, cov, water, pigment, field = null) {
1606
+ paint(id, rect, cov, water, pigment, field = null, light = 0) {
1542
1607
  if (!(water >= 0)) throw new WetWorldError(`water ${water} must be 0 or more.`);
1608
+ if (!(light >= 0)) throw new WetWorldError(`light ${light} must be 0 or more.`);
1609
+ if (light > 0) this.laneOf(id).lit = true;
1543
1610
  this.footprint(id, rect, cov, (c, w, x, y) => {
1544
1611
  const dw = Math.fround(water * w);
1545
1612
  if (dw > 0) this.addWaterAt(c, dw);
@@ -1548,6 +1615,7 @@ var WetWorld = (_class = class {
1548
1615
  const o = ((y - rect.y) * rect.width + (x - rect.x)) * 4;
1549
1616
  for (let q = 0; q < 4; q++) this.f32[into + q * PLANE] += field[o + q] * w;
1550
1617
  } else if (pigment) for (let q = 0; q < 4; q++) this.f32[into + q * PLANE] += pigment[q] * w;
1618
+ if (light > 0) this.f32[into + 4 * PLANE] += light * w;
1551
1619
  });
1552
1620
  }
1553
1621
  /** Damp paper: fills the paper's water towards its capacity by `amount` (0..1) × coverage, plus a thin film of surface water. */
@@ -1581,10 +1649,10 @@ var WetWorld = (_class = class {
1581
1649
  f[c + P_RHO * PLANE] *= kw;
1582
1650
  }
1583
1651
  if (ks < 1) f[c + P_SW * PLANE] *= ks;
1584
- if (kp < 1) for (let q = 0; q < 4; q++) f[c + (P_P0 + q) * PLANE] *= kp;
1652
+ if (kp < 1) for (let q = 0; q < MASS_CHANNELS; q++) f[c + (P_P0 + q) * PLANE] *= kp;
1585
1653
  if (kd < 1) {
1586
1654
  const d = this.single(c) + P_D0 * PLANE;
1587
- for (let q = 0; q < 4; q++) f[d + q * PLANE] *= kd;
1655
+ for (let q = 0; q < MASS_CHANNELS; q++) f[d + q * PLANE] *= kd;
1588
1656
  }
1589
1657
  },
1590
1658
  false
@@ -1614,10 +1682,10 @@ var WetWorld = (_class = class {
1614
1682
  false
1615
1683
  );
1616
1684
  }
1617
- /** Blends wet paint: each wet cell's suspended pigment moves `strength` × coverage towards the footprint's mean. */
1685
+ /** Blends wet paint: each wet cell's suspended pigment (and light) moves `strength` × coverage towards the footprint's mean. */
1618
1686
  blend(id, rect, cov, strength) {
1619
1687
  const lane = this.laneOf(id);
1620
- const sums = [0, 0, 0, 0];
1688
+ const sums = [0, 0, 0, 0, 0];
1621
1689
  let wsum = 0;
1622
1690
  const cells = [];
1623
1691
  this.footprint(
@@ -1628,13 +1696,13 @@ var WetWorld = (_class = class {
1628
1696
  if (!(this.f32[c + P_RHO * PLANE] >= DRY_EPS)) return;
1629
1697
  cells.push([c, w]);
1630
1698
  wsum += w;
1631
- for (let q = 0; q < 4; q++) sums[q] += this.f32[c + (P_P0 + q) * PLANE] * w;
1699
+ for (let q = 0; q < MASS_CHANNELS; q++) sums[q] += this.f32[c + (P_P0 + q) * PLANE] * w;
1632
1700
  },
1633
1701
  false
1634
1702
  );
1635
1703
  if (!(wsum > 0)) return;
1636
1704
  const k = Math.min(1, Math.max(0, strength));
1637
- for (const [c, w] of cells) for (let q = 0; q < 4; q++) {
1705
+ for (const [c, w] of cells) for (let q = 0; q < MASS_CHANNELS; q++) {
1638
1706
  const i = c + (P_P0 + q) * PLANE;
1639
1707
  this.f32[i] += k * w * (sums[q] / wsum - this.f32[i]);
1640
1708
  }
@@ -1643,7 +1711,7 @@ var WetWorld = (_class = class {
1643
1711
  }
1644
1712
  /**
1645
1713
  * Drags wet paint by (`dx`, `dy`) cells: each footprint cell takes `strength` × coverage of the
1646
- * water and suspended pigment of the cell it came from (a smudge through wet paint).
1714
+ * water and suspended pigment (and light) of the cell it came from (a smudge through wet paint).
1647
1715
  */
1648
1716
  drag(id, rect, cov, dx, dy, strength) {
1649
1717
  const lane = this.laneOf(id);
@@ -1664,14 +1732,14 @@ var WetWorld = (_class = class {
1664
1732
  );
1665
1733
  const f = this.f32;
1666
1734
  const taken = moves.map(([from, , a]) => {
1667
- const t = new Float32Array(14);
1735
+ const t = new Float32Array(10 + MASS_CHANNELS);
1668
1736
  for (let k = 0; k < 9; k++) {
1669
1737
  t[k] = f[from + k * PLANE] * a;
1670
1738
  f[from + k * PLANE] -= t[k];
1671
1739
  }
1672
1740
  t[9] = f[from + P_RHO * PLANE] * a;
1673
1741
  f[from + P_RHO * PLANE] -= t[9];
1674
- for (let q = 0; q < 4; q++) {
1742
+ for (let q = 0; q < MASS_CHANNELS; q++) {
1675
1743
  t[10 + q] = f[from + (P_P0 + q) * PLANE] * a;
1676
1744
  f[from + (P_P0 + q) * PLANE] -= t[10 + q];
1677
1745
  }
@@ -1681,19 +1749,22 @@ var WetWorld = (_class = class {
1681
1749
  const t = taken[i];
1682
1750
  for (let k = 0; k < 9; k++) f[to + k * PLANE] += t[k];
1683
1751
  f[to + P_RHO * PLANE] += t[9];
1684
- for (let q = 0; q < 4; q++) f[to + (P_P0 + q) * PLANE] += t[10 + q];
1752
+ for (let q = 0; q < MASS_CHANNELS; q++) f[to + (P_P0 + q) * PLANE] += t[10 + q];
1685
1753
  });
1686
1754
  this.refreshHalos();
1687
1755
  }
1688
1756
  /**
1689
1757
  * Re-wetting: lifts `amount` × coverage of the dried paint in the layer's pixels (`base`) back into
1690
- * the paper as settled pigment, so water can move it again. Only where there is surface water.
1758
+ * the paper as settled pigment, so water can move it again, and the same share of its light
1759
+ * paint (`light`) as settled light. Only where there is surface water.
1691
1760
  */
1692
1761
  rewet(id, rect, cov, amount, paper) {
1693
1762
  const lane = this.laneOf(id);
1694
1763
  const pig = [0, 0, 0, 0];
1695
1764
  const tiles = new Map(lane.base.tiles);
1696
1765
  const written = /* @__PURE__ */ new Set();
1766
+ const lightTiles = lane.lit ? new Map(lane.light.tiles) : null;
1767
+ const lightWritten = /* @__PURE__ */ new Set();
1697
1768
  const scratch2 = new Uint8ClampedArray(4);
1698
1769
  this.footprint(
1699
1770
  id,
@@ -1704,6 +1775,22 @@ var WetWorld = (_class = class {
1704
1775
  const a = Math.min(1, Math.max(0, amount * w));
1705
1776
  if (!(a > 0)) return;
1706
1777
  const key = _chunk474IQG74cjs.tileKey.call(void 0, x >> 8, y >> 8);
1778
+ const li = (y & 255) * _chunk474IQG74cjs.TILE + (x & 255);
1779
+ let lt = _optionalChain([lightTiles, 'optionalAccess', _19 => _19.get, 'call', _20 => _20(key)]);
1780
+ if (lt && (_nullishCoalesce(lt.uniform, () => ( 1))) !== 0) {
1781
+ if (!lightWritten.has(key)) {
1782
+ const data = lt.data.length ? new Uint8ClampedArray(lt.data) : new Uint8ClampedArray(_chunk474IQG74cjs.TILE * _chunk474IQG74cjs.TILE).fill(_nullishCoalesce(lt.uniform, () => ( 0)));
1783
+ lt = { data, uniform: null };
1784
+ lightTiles.set(key, lt);
1785
+ lightWritten.add(key);
1786
+ }
1787
+ const lv = lt.data[li];
1788
+ if (lv > 0) {
1789
+ const lifted = Math.round(lv * a);
1790
+ lt.data[li] = lv - lifted;
1791
+ this.f32[this.single(c) + P_DL * PLANE] += Math.fround(lifted / 255);
1792
+ }
1793
+ }
1707
1794
  let tile = tiles.get(key);
1708
1795
  if (!tile) return;
1709
1796
  if (!written.has(key)) {
@@ -1731,6 +1818,14 @@ var WetWorld = (_class = class {
1731
1818
  }
1732
1819
  this.setBase(id, new (0, _chunk474IQG74cjs.TiledSurface)(4, tiles, 0));
1733
1820
  }
1821
+ if (lightWritten.size) {
1822
+ for (const key of lightWritten) {
1823
+ const t = _chunk474IQG74cjs.finishTile.call(void 0, lightTiles.get(key).data, 1, 0);
1824
+ if (t) lightTiles.set(key, t);
1825
+ else lightTiles.delete(key);
1826
+ }
1827
+ this.setLight(id, new (0, _chunk474IQG74cjs.TiledSurface)(1, lightTiles, 0));
1828
+ }
1734
1829
  }
1735
1830
  /**
1736
1831
  * Dries wet paint at once (everything settles where it is, the water goes): in `rect`, or the
@@ -1763,7 +1858,7 @@ var WetWorld = (_class = class {
1763
1858
  for (let x = x0; x < x1; x++) {
1764
1859
  const c = this.cell(lane, x, y);
1765
1860
  const d = this.single(c) + P_D0 * PLANE;
1766
- for (let q = 0; q < 4; q++) {
1861
+ for (let q = 0; q < MASS_CHANNELS; q++) {
1767
1862
  f[d + q * PLANE] += f[c + (P_P0 + q) * PLANE];
1768
1863
  f[c + (P_P0 + q) * PLANE] = 0;
1769
1864
  }
@@ -1790,9 +1885,21 @@ var WetWorld = (_class = class {
1790
1885
  const lane = this.lanes[this.laneIndexOf(key)];
1791
1886
  const [tx, ty] = this.tileXY(key);
1792
1887
  const tk = _chunk474IQG74cjs.tileKey.call(void 0, tx, ty);
1793
- const src = _nullishCoalesce(_optionalChain([lane, 'access', _19 => _19.base, 'access', _20 => _20.tiles, 'access', _21 => _21.get, 'call', _22 => _22(tk), 'optionalAccess', _23 => _23.data]), () => ( null));
1888
+ let lightChanged = false;
1889
+ if (lane.lit) {
1890
+ const lout = this.shadeLightSlot(key, _nullishCoalesce(lane.light.tiles.get(tk), () => ( null)), all ? "bake-all" : "bake");
1891
+ if (lout) {
1892
+ const tiles2 = new Map(lane.light.tiles);
1893
+ const t2 = _chunk474IQG74cjs.finishTile.call(void 0, lout, 1, 0);
1894
+ if (t2) tiles2.set(tk, t2);
1895
+ else tiles2.delete(tk);
1896
+ this.setLight(lane.id, new (0, _chunk474IQG74cjs.TiledSurface)(1, tiles2, 0));
1897
+ lightChanged = true;
1898
+ }
1899
+ }
1900
+ 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));
1794
1901
  const out = this.shadeSlot(key, src, all ? "bake-all" : "bake");
1795
- if (!out) return false;
1902
+ if (!out) return lightChanged;
1796
1903
  const tiles = new Map(lane.base.tiles);
1797
1904
  const t = _chunk474IQG74cjs.finishTile.call(void 0, out, 4, 0);
1798
1905
  if (t) tiles.set(tk, t);
@@ -1800,6 +1907,19 @@ var WetWorld = (_class = class {
1800
1907
  this.setBase(lane.id, new (0, _chunk474IQG74cjs.TiledSurface)(4, tiles, 0));
1801
1908
  return true;
1802
1909
  }
1910
+ /** `shadeSlot` for light paint: one channel over a light tile (see `shadeLightTile`); `null` when nothing would change. */
1911
+ shadeLightSlot(key, src, mode) {
1912
+ const [tx, ty] = this.tileXY(key);
1913
+ const b = this.slotBase(this.slotOf(key));
1914
+ const cur = b + this.parity * SET_PLANES * PLANE;
1915
+ const full = src ? src.data.length ? src.data : new Uint8ClampedArray(_chunk474IQG74cjs.TILE * _chunk474IQG74cjs.TILE).fill(_nullishCoalesce(src.uniform, () => ( 0))) : null;
1916
+ const out = full ? new Uint8ClampedArray(full) : new Uint8ClampedArray(_chunk474IQG74cjs.TILE * _chunk474IQG74cjs.TILE);
1917
+ const w = Math.min(WET_TILE, this.width - tx * WET_TILE);
1918
+ const h = Math.min(WET_TILE, this.height - ty * WET_TILE);
1919
+ const live = mode !== "bake";
1920
+ const changed = shadeLightTile(this.f32, PLANE, S2, b + P_D0 * PLANE, live ? cur + P_P0 * PLANE : -1, b + P_DL * PLANE, live ? cur + P_L * PLANE : -1, full, out, w, h);
1921
+ return changed ? out : null;
1922
+ }
1803
1923
  /**
1804
1924
  * Shades one slot over a base tile. `display`: the wet look (suspended + settled, wet paper a
1805
1925
  * little darker); `bake`: settled pigment only; `bake-all`: suspended and settled (dry now).
@@ -1812,6 +1932,7 @@ var WetWorld = (_class = class {
1812
1932
  const out = src ? new Uint8ClampedArray(src) : new Uint8ClampedArray(_chunk474IQG74cjs.TILE * _chunk474IQG74cjs.TILE * 4);
1813
1933
  const w = Math.min(WET_TILE, this.width - tx * WET_TILE);
1814
1934
  const h = Math.min(WET_TILE, this.height - ty * WET_TILE);
1935
+ const lit = this.lanes[this.laneIndexOf(key)].lit;
1815
1936
  const changed = shadeTile(
1816
1937
  this.f32,
1817
1938
  PLANE,
@@ -1824,7 +1945,10 @@ var WetWorld = (_class = class {
1824
1945
  out,
1825
1946
  w,
1826
1947
  h,
1827
- this.paperBytes
1948
+ this.paperBytes,
1949
+ // Glowing paint shows its own hue (see shadeTile).
1950
+ lit ? b + P_DL * PLANE : -1,
1951
+ lit && mode !== "bake" ? cur + P_L * PLANE : -1
1828
1952
  );
1829
1953
  return changed ? out : null;
1830
1954
  }
@@ -1836,7 +1960,7 @@ var WetWorld = (_class = class {
1836
1960
  for (const key of this.active) {
1837
1961
  if (this.laneIndexOf(key) !== lane.index) continue;
1838
1962
  const tk = this.tileKeyOf(key);
1839
- const out = this.shadeSlot(key, _nullishCoalesce(_optionalChain([lane, 'access', _24 => _24.base, 'access', _25 => _25.tiles, 'access', _26 => _26.get, 'call', _27 => _27(tk), 'optionalAccess', _28 => _28.data]), () => ( null)), "bake-all");
1963
+ 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");
1840
1964
  if (!out) continue;
1841
1965
  const t = _chunk474IQG74cjs.finishTile.call(void 0, out, 4, 0);
1842
1966
  if (t) tiles.set(tk, t);
@@ -1844,6 +1968,23 @@ var WetWorld = (_class = class {
1844
1968
  }
1845
1969
  return new (0, _chunk474IQG74cjs.TiledSurface)(4, tiles, 0);
1846
1970
  }
1971
+ /** The layer's light paint with all its wet paint dried where it is (`bakedSurface` for light); changes nothing. */
1972
+ bakedLight(id) {
1973
+ this.requireIdle();
1974
+ const lane = this.laneOf(id);
1975
+ if (!lane.lit) return lane.light;
1976
+ const tiles = new Map(lane.light.tiles);
1977
+ for (const key of this.active) {
1978
+ if (this.laneIndexOf(key) !== lane.index) continue;
1979
+ const tk = this.tileKeyOf(key);
1980
+ const out = this.shadeLightSlot(key, _nullishCoalesce(lane.light.tiles.get(tk), () => ( null)), "bake-all");
1981
+ if (!out) continue;
1982
+ const t = _chunk474IQG74cjs.finishTile.call(void 0, out, 1, 0);
1983
+ if (t) tiles.set(tk, t);
1984
+ else tiles.delete(tk);
1985
+ }
1986
+ return new (0, _chunk474IQG74cjs.TiledSurface)(1, tiles, 0);
1987
+ }
1847
1988
  // --- Reading -------------------------------------------------------------------
1848
1989
  /**
1849
1990
  * Sums over a footprint of a lane, weighted by coverage: `weight` (coverage), `water` (surface
@@ -1942,14 +2083,14 @@ var WetWorld = (_class = class {
1942
2083
  display(id, maxTiles = Infinity) {
1943
2084
  const lane = this.laneOf(id);
1944
2085
  const keys = this.active.filter((k) => this.laneIndexOf(k) === lane.index);
1945
- const stale = keys.filter((k) => _optionalChain([lane, 'access', _29 => _29.shaded, 'access', _30 => _30.get, 'call', _31 => _31(this.tileKeyOf(k)), 'optionalAccess', _32 => _32.stamp]) !== lane.stamp);
1946
- stale.sort((a, b) => Number(lane.urgent.has(this.tileKeyOf(b))) - Number(lane.urgent.has(this.tileKeyOf(a))) || (_nullishCoalesce(_optionalChain([lane, 'access', _33 => _33.shaded, 'access', _34 => _34.get, 'call', _35 => _35(this.tileKeyOf(a)), 'optionalAccess', _36 => _36.stamp]), () => ( 0))) - (_nullishCoalesce(_optionalChain([lane, 'access', _37 => _37.shaded, 'access', _38 => _38.get, 'call', _39 => _39(this.tileKeyOf(b)), 'optionalAccess', _40 => _40.stamp]), () => ( 0))));
2086
+ 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);
2087
+ 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))));
1947
2088
  const changed = [];
1948
2089
  let done = 0;
1949
2090
  for (const key of stale) {
1950
2091
  if (done >= maxTiles) break;
1951
2092
  const tk = this.tileKeyOf(key);
1952
- const src = _nullishCoalesce(_optionalChain([lane, 'access', _41 => _41.base, 'access', _42 => _42.tiles, 'access', _43 => _43.get, 'call', _44 => _44(tk), 'optionalAccess', _45 => _45.data]), () => ( null));
2093
+ 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));
1953
2094
  const out = this.shadeSlot(key, src, "display");
1954
2095
  const tile = out ? _chunk474IQG74cjs.finishTile.call(void 0, out, 4, 0) : _nullishCoalesce(lane.base.tiles.get(tk), () => ( null));
1955
2096
  lane.shaded.set(tk, { stamp: lane.stamp, tile: _nullishCoalesce(tile, () => ( { data: new Uint8ClampedArray(0), uniform: 0 })) });
@@ -1972,6 +2113,38 @@ var WetWorld = (_class = class {
1972
2113
  lane.displayKey = displayKey;
1973
2114
  return { surface: lane.display, changed, pending: stale.length - done };
1974
2115
  }
2116
+ /**
2117
+ * The layer's light paint with its live light shaded in (`null` when the layer has none): the
2118
+ * light under the wet paint with each live tile's light over it. Like `display`, every stale
2119
+ * tile is reshaded (light tiles are cheap: one channel, no colour maths).
2120
+ */
2121
+ lightDisplay(id) {
2122
+ const lane = this.laneOf(id);
2123
+ if (!lane.lit) return null;
2124
+ const keys = this.active.filter((k) => this.laneIndexOf(k) === lane.index);
2125
+ let changed = false;
2126
+ const live = /* @__PURE__ */ new Set();
2127
+ for (const key2 of keys) {
2128
+ const tk = this.tileKeyOf(key2);
2129
+ live.add(tk);
2130
+ if (_optionalChain([lane, 'access', _48 => _48.lightShaded, 'access', _49 => _49.get, 'call', _50 => _50(tk), 'optionalAccess', _51 => _51.stamp]) === lane.stamp) continue;
2131
+ const src = _nullishCoalesce(lane.light.tiles.get(tk), () => ( null));
2132
+ const out = this.shadeLightSlot(key2, src, "display");
2133
+ lane.lightShaded.set(tk, { stamp: lane.stamp, tile: out ? _chunk474IQG74cjs.finishTile.call(void 0, out, 1, 0) : src });
2134
+ changed = true;
2135
+ }
2136
+ for (const tk of [...lane.lightShaded.keys()]) if (!live.has(tk) && lane.lightShaded.delete(tk)) changed = true;
2137
+ const key = `${lane.light.id}`;
2138
+ if (!changed && lane.lightDisplay && lane.lightKey === key) return lane.lightDisplay;
2139
+ const tiles = new Map(lane.light.tiles);
2140
+ for (const [tk, { tile }] of lane.lightShaded) {
2141
+ if (tile) tiles.set(tk, tile);
2142
+ else tiles.delete(tk);
2143
+ }
2144
+ lane.lightDisplay = new (0, _chunk474IQG74cjs.TiledSurface)(1, tiles, 0);
2145
+ lane.lightKey = key;
2146
+ return lane.lightDisplay;
2147
+ }
1975
2148
  /**
1976
2149
  * The water as an overlay tile (RGBA, blue, alpha from surface depth and damp paper) for a
1977
2150
  * document tile, or `null` where nothing is wet.
@@ -2104,7 +2277,7 @@ var WetWorld = (_class = class {
2104
2277
  }
2105
2278
  const from = y0 * SLOT_SIDE;
2106
2279
  const to = (y1 + 1) * SLOT_SIDE;
2107
- const planes = [...Array(SET_PLANES).keys()].map((k) => this.parity * SET_PLANES + k).concat([P_D0, P_D0 + 1, P_D0 + 2, P_D0 + 3]);
2280
+ const planes = [...Array(SET_PLANES).keys()].map((k) => this.parity * SET_PLANES + k).concat([...Array(MASS_CHANNELS).keys()].map((q) => P_D0 + q));
2108
2281
  for (const plane of planes) gpu.writeSlot(slot, plane * PLANE + from, this.f32.subarray(b + plane * PLANE + from, b + plane * PLANE + to));
2109
2282
  }
2110
2283
  this.gpuDirty.clear();
@@ -2152,7 +2325,7 @@ var WetWorld = (_class = class {
2152
2325
  if (!this.gpu || this.busyFlag && !plan || !plan && !this.gpuDecide()) return { ...this.display(id, maxCpuTiles), gpu: false };
2153
2326
  const lane = this.laneOf(id);
2154
2327
  const keys = this.active.filter((k) => this.laneIndexOf(k) === lane.index);
2155
- const stale = plan ? keys : keys.filter((k) => _optionalChain([lane, 'access', _46 => _46.shaded, 'access', _47 => _47.get, 'call', _48 => _48(this.tileKeyOf(k)), 'optionalAccess', _49 => _49.stamp]) !== lane.stamp);
2328
+ 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);
2156
2329
  if (!stale.length) return { ...this.display(id, 0), gpu: true };
2157
2330
  let tiles;
2158
2331
  const stamp = lane.stamp;
@@ -2160,7 +2333,7 @@ var WetWorld = (_class = class {
2160
2333
  try {
2161
2334
  if (!plan) this.gpuSync();
2162
2335
  const parity = plan ? plan.parity + plan.done & 1 : this.parity;
2163
- tiles = await this.gpu.shade(this.gpuJobs(stale), parity, stale.map((k) => _nullishCoalesce(_optionalChain([base, 'access', _50 => _50.tiles, 'access', _51 => _51.get, 'call', _52 => _52(this.tileKeyOf(k)), 'optionalAccess', _53 => _53.data]), () => ( null))), [this.paperBytes[0], this.paperBytes[1], this.paperBytes[2]]);
2336
+ 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]]);
2164
2337
  } catch (e) {
2165
2338
  this.gpuStop(`Wet paint stopped using the GPU (${e.message}).`);
2166
2339
  return { ...this.display(id, maxCpuTiles), gpu: false };
@@ -2189,11 +2362,11 @@ var WetWorld = (_class = class {
2189
2362
  const b = this.slotBase(this.slotOf(key));
2190
2363
  const planes = new Float32Array(SNAP_PLANES * PLANE);
2191
2364
  planes.set(this.f32.subarray(b + this.parity * SET_PLANES * PLANE, b + (this.parity + 1) * SET_PLANES * PLANE), 0);
2192
- planes.set(this.f32.subarray(b + P_D0 * PLANE, b + (P_D0 + 4) * PLANE), SET_PLANES * PLANE);
2365
+ planes.set(this.f32.subarray(b + P_D0 * PLANE, b + (P_D0 + MASS_CHANNELS) * PLANE), SET_PLANES * PLANE);
2193
2366
  slots.set(key, { planes, last: this.u32[(this.layout.last >> 2) + key] });
2194
2367
  bytes += planes.byteLength;
2195
2368
  }
2196
- 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 }));
2369
+ 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 }));
2197
2370
  return { clock: this.clock, tilt: { ...this.tilt }, lanes, slots, bytes };
2198
2371
  }
2199
2372
  /** Puts a snapshot back exactly (the simulation then continues as it would have). */
@@ -2203,7 +2376,9 @@ var WetWorld = (_class = class {
2203
2376
  this.lanes.fill(null);
2204
2377
  this.tilt = { ...snap.tilt };
2205
2378
  for (const l of snap.lanes) {
2206
- this.lanes[l.index] = { id: l.id, index: l.index, params: { ...l.params }, flow: l.flow, flowStrength: l.flowStrength, base: l.base, shaded: /* @__PURE__ */ new Map(), urgent: /* @__PURE__ */ new Set(), stamp: 1, display: null, shadedVersion: 0, displayKey: "" };
2379
+ const lane = newLane(l.id, l.index, { ...l.params }, l.flow, l.flowStrength, l.base, l.light);
2380
+ lane.lit = lane.lit || Boolean(l.lit);
2381
+ this.lanes[l.index] = lane;
2207
2382
  this.pack(this.lanes[l.index]);
2208
2383
  }
2209
2384
  for (const [key, { planes, last }] of snap.slots) {
@@ -2260,7 +2435,7 @@ function moveWetSnapshot(snap, from, to, dx, dy, lane = () => ({})) {
2260
2435
  }
2261
2436
  const lanes = snap.lanes.map((l) => {
2262
2437
  const moved = lane(l);
2263
- return { ...l, base: _nullishCoalesce(moved.base, () => ( l.base)), flow: moved.flow === void 0 ? l.flow : moved.flow };
2438
+ return { ...l, base: _nullishCoalesce(moved.base, () => ( l.base)), light: _nullishCoalesce(moved.light, () => ( l.light)), flow: moved.flow === void 0 ? l.flow : moved.flow };
2264
2439
  });
2265
2440
  return { clock: snap.clock, tilt: { ...snap.tilt }, lanes, slots, bytes };
2266
2441
  }
@@ -2337,8 +2512,8 @@ function normalizeFlow(raw = {}, base = DEFAULT_FLOW) {
2337
2512
  }
2338
2513
  function flowField(spec, width, height) {
2339
2514
  const k = FLOW_ACCEL;
2340
- const cx = _nullishCoalesce(_optionalChain([spec, 'access', _54 => _54.center, 'optionalAccess', _55 => _55.x]), () => ( width / 2));
2341
- const cy = _nullishCoalesce(_optionalChain([spec, 'access', _56 => _56.center, 'optionalAccess', _57 => _57.y]), () => ( height / 2));
2515
+ const cx = _nullishCoalesce(_optionalChain([spec, 'access', _60 => _60.center, 'optionalAccess', _61 => _61.x]), () => ( width / 2));
2516
+ const cy = _nullishCoalesce(_optionalChain([spec, 'access', _62 => _62.center, 'optionalAccess', _63 => _63.y]), () => ( height / 2));
2342
2517
  const r0 = Math.max(width, height) / 2;
2343
2518
  switch (spec.kind) {
2344
2519
  case "none":
@@ -2396,6 +2571,7 @@ var WetMedium = (_class2 = class {
2396
2571
  }
2397
2572
  deposit(rect, cov, dep, paint, _pigment, field = null) {
2398
2573
  const { water, mass, body } = this.options;
2574
+ const light = (_nullishCoalesce(this.options.light, () => ( 0))) * dep;
2399
2575
  if (field) {
2400
2576
  const n = rect.width * rect.height;
2401
2577
  const out = new Float32Array(n * 4);
@@ -2404,12 +2580,12 @@ var WetMedium = (_class2 = class {
2404
2580
  pigmentOf([field[p * 3], field[p * 3 + 1], field[p * 3 + 2]], body, one);
2405
2581
  for (let q = 0; q < 4; q++) out[p * 4 + q] = one[q] * mass * dep;
2406
2582
  }
2407
- this.world.paint(this.laneId, rect, cov, water * dep, null, out);
2583
+ this.world.paint(this.laneId, rect, cov, water * dep, null, out, light);
2408
2584
  return;
2409
2585
  }
2410
2586
  pigmentOf(paint, body, this.pig);
2411
2587
  const pig = [0, 1, 2, 3].map((q) => this.pig[q] * mass * dep);
2412
- this.world.paint(this.laneId, rect, cov, water * dep, mass > 0 ? pig : null);
2588
+ this.world.paint(this.laneId, rect, cov, water * dep, mass > 0 ? pig : null, null, light);
2413
2589
  }
2414
2590
  }, _class2);
2415
2591
 
@@ -2482,7 +2658,10 @@ function faces() {
2482
2658
  let pn = vec4<f32>(st[pS + nb], st[pS + PL + nb], st[pS + 2u * PL + nb], st[pS + 3u * PL + nb]);
2483
2659
  let flux = ${outF ? "select(uf * pn, uf * pc, uf > 0.0)" : "select(uf * pc, uf * pn, uf > 0.0)"};
2484
2660
  let v = pNew ${outF ? "-" : "+"} flux + bleed * (pn - pc);
2485
- if (rxWet && st[rhoS + nb] >= EPS) { pNew = v; }
2661
+ let ln = st[pS + 4u * PL + nb];
2662
+ let fluxL = ${outF ? "select(uf * ln, uf * lc, uf > 0.0)" : "select(uf * lc, uf * ln, uf > 0.0)"};
2663
+ let vL = lNew ${outF ? "-" : "+"} fluxL + bleed * (ln - lc);
2664
+ if (rxWet && st[rhoS + nb] >= EPS) { pNew = v; lNew = vL; }
2486
2665
  }`;
2487
2666
  });
2488
2667
  return out;
@@ -2605,12 +2784,14 @@ ${stream()}
2605
2784
  st[dst + c] = (f0 + omega * (wr0 * (1.0 - usq) - f0)) * scale;
2606
2785
  ${collide()}
2607
2786
 
2608
- // 5. pigment: upwind advection with the last step's velocities, plus bleed
2787
+ // 5. pigment and light: upwind advection with the last step's velocities, plus bleed
2609
2788
  let rxWet = !rxDry;
2610
2789
  let uxc = st[uxS + c];
2611
2790
  let uyc = st[uyS + c];
2612
2791
  var pNew = vec4<f32>(st[pS + c], st[pS + PL + c], st[pS + 2u * PL + c], st[pS + 3u * PL + c]);
2613
2792
  let pc = pNew;
2793
+ var lNew = st[pS + 4u * PL + c];
2794
+ let lc = lNew;
2614
2795
  ${faces()}
2615
2796
 
2616
2797
  // 6. deposition and pickup
@@ -2627,6 +2808,15 @@ ${faces()}
2627
2808
  st[dst + (${P_P0}u + q) * PL + c] = pq - dep + lq;
2628
2809
  st[dP + q * PL + c] = dq + dep - lq;
2629
2810
  }
2811
+ {
2812
+ let pq = max(0.0, lNew);
2813
+ let dq = st[dP + 4u * PL + c];
2814
+ var dep = min(pq, pq * rate);
2815
+ if (dryNow) { dep = pq; }
2816
+ let lq = dq * lift;
2817
+ st[dst + ${P_P0 + 4}u * PL + c] = pq - dep + lq;
2818
+ st[dP + 4u * PL + c] = dq + dep - lq;
2819
+ }
2630
2820
  let mean4 = (st[eS + c + 1u] + st[eS + c - 1u] + st[eS + c + S] + st[eS + c - S]) * 0.25;
2631
2821
  let eNew = min(1.0, max(min(1.0, dryNb * 0.25), DECAY * mean4));
2632
2822
  st[dst + ${P_EDGE}u * PL + c] = select(eNew, 0.0, dryNow);
@@ -2754,12 +2944,18 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
2754
2944
  if (s > 1e-9) { inv = 1.0 / s; }
2755
2945
  var film = keep * exp(-2.0 * k.xyz) * below;
2756
2946
  if (hide > 0.0) { film = film + hide * vec3<f32>(rinf(k.x * inv), rinf(k.y * inv), rinf(k.z * inv)); }
2947
+ // Light paint: glowing paint shows its own hue (the film over white), as shadeTile does.
2948
+ let l = min(1.0, st[d + 4u * PL + c] + st[p + 4u * PL + c]);
2949
+ if (l > 1e-7) {
2950
+ var w = keep * exp(-2.0 * k.xyz);
2951
+ if (hide > 0.0) { w = w + hide * vec3<f32>(rinf(k.x * inv), rinf(k.y * inv), rinf(k.z * inv)); }
2952
+ film = film + l * (w - film);
2953
+ }
2757
2954
  let dark = 1.0 - 0.07 * wet;
2758
2955
  let e = vec3<f32>(toByte(film.x * dark), toByte(film.y * dark), toByte(film.z * dark));
2759
- var alpha = a;
2760
- alpha = needAlpha(e.x, paper.x, alpha);
2761
- alpha = needAlpha(e.y, paper.y, alpha);
2762
- alpha = needAlpha(e.z, paper.z, alpha);
2956
+ var alpha = needAlpha(e.z, paper.z, needAlpha(e.y, paper.y, needAlpha(e.x, paper.x, 0.0)));
2957
+ if (hide > 0.0 && alpha < 1.0) { alpha = alpha + hide * (1.0 - alpha); }
2958
+ alpha = max(alpha, a);
2763
2959
  if (alpha <= 0.0) { outTiles[o] = px & 0x00ffffffu; return; }
2764
2960
  let a8 = max(round(min(1.0, alpha) * 255.0), pr.w);
2765
2961
  let al = a8 / 255.0;
@@ -2780,7 +2976,7 @@ var UNIFORM = 64;
2780
2976
  var STORAGE = 128;
2781
2977
  function findWebGpu() {
2782
2978
  const nav = globalThis.navigator;
2783
- return _nullishCoalesce(_optionalChain([nav, 'optionalAccess', _58 => _58.gpu]), () => ( null));
2979
+ return _nullishCoalesce(_optionalChain([nav, 'optionalAccess', _64 => _64.gpu]), () => ( null));
2784
2980
  }
2785
2981
  async function createWetGpuEngine(gpu = findWebGpu()) {
2786
2982
  const no = (reason, adapter2 = null) => ({ engine: null, support: { supported: false, reason, adapter: adapter2, maxSlots: 0 } });
@@ -2792,7 +2988,7 @@ async function createWetGpuEngine(gpu = findWebGpu()) {
2792
2988
  return no(`WebGPU gave no adapter (${e.message}); wet paint runs on the CPU.`);
2793
2989
  }
2794
2990
  if (!adapter) return no("WebGPU gave no adapter (the GPU may be blocklisted); wet paint runs on the CPU.");
2795
- const name = [_optionalChain([adapter, 'access', _59 => _59.info, 'optionalAccess', _60 => _60.vendor]), _optionalChain([adapter, 'access', _61 => _61.info, 'optionalAccess', _62 => _62.architecture])].filter(Boolean).join(" ") || null;
2991
+ 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;
2796
2992
  const binding = Math.min(_nullishCoalesce(adapter.limits.maxStorageBufferBindingSize, () => ( 0)), _nullishCoalesce(adapter.limits.maxBufferSize, () => ( 0)));
2797
2993
  const maxSlots = Math.min(MAX_WET_SLOTS, Math.floor(binding / SLOT_BYTES));
2798
2994
  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);
@@ -2812,27 +3008,27 @@ async function createWetGpuEngine(gpu = findWebGpu()) {
2812
3008
  const engine = await WetGpuEngine.start(device, maxSlots, name);
2813
3009
  return { engine, support: { supported: true, reason: null, adapter: name, maxSlots } };
2814
3010
  } catch (e) {
2815
- _optionalChain([device, 'access', _63 => _63.destroy, 'optionalCall', _64 => _64()]);
3011
+ _optionalChain([device, 'access', _69 => _69.destroy, 'optionalCall', _70 => _70()]);
2816
3012
  return no(`The wet-media shaders did not compile on this GPU (${e.message}); wet paint runs on the CPU.`, name);
2817
3013
  }
2818
3014
  }
2819
3015
  async function wetGpuSupport(gpu = findWebGpu()) {
2820
3016
  const { engine, support } = await createWetGpuEngine(gpu);
2821
- _optionalChain([engine, 'optionalAccess', _65 => _65.destroy, 'call', _66 => _66()]);
3017
+ _optionalChain([engine, 'optionalAccess', _71 => _71.destroy, 'call', _72 => _72()]);
2822
3018
  return support;
2823
3019
  }
2824
3020
  async function pipeline(device, code, label) {
2825
- _optionalChain([device, 'access', _67 => _67.pushErrorScope, 'optionalCall', _68 => _68("validation")]);
3021
+ _optionalChain([device, 'access', _73 => _73.pushErrorScope, 'optionalCall', _74 => _74("validation")]);
2826
3022
  const module = device.createShaderModule({ code, label });
2827
- const info = await _optionalChain([module, 'access', _69 => _69.getCompilationInfo, 'optionalCall', _70 => _70()]);
2828
- const errors = _nullishCoalesce(_optionalChain([info, 'optionalAccess', _71 => _71.messages, 'access', _72 => _72.filter, 'call', _73 => _73((m) => m.type === "error")]), () => ( []));
3023
+ const info = await _optionalChain([module, 'access', _75 => _75.getCompilationInfo, 'optionalCall', _76 => _76()]);
3024
+ const errors = _nullishCoalesce(_optionalChain([info, 'optionalAccess', _77 => _77.messages, 'access', _78 => _78.filter, 'call', _79 => _79((m) => m.type === "error")]), () => ( []));
2829
3025
  if (errors.length) {
2830
- await _optionalChain([device, 'access', _74 => _74.popErrorScope, 'optionalCall', _75 => _75()]);
3026
+ await _optionalChain([device, 'access', _80 => _80.popErrorScope, 'optionalCall', _81 => _81()]);
2831
3027
  throw new Error(`${label}: ${errors.map((m) => `line ${m.lineNum}: ${m.message}`).join("; ")}`);
2832
3028
  }
2833
3029
  const desc = { layout: "auto", compute: { module, entryPoint: "main" }, label };
2834
3030
  const p = device.createComputePipelineAsync ? await device.createComputePipelineAsync(desc) : device.createComputePipeline(desc);
2835
- const err = await _optionalChain([device, 'access', _76 => _76.popErrorScope, 'optionalCall', _77 => _77()]);
3031
+ const err = await _optionalChain([device, 'access', _82 => _82.popErrorScope, 'optionalCall', _83 => _83()]);
2836
3032
  if (err) throw new Error(`${label}: ${err.message}`);
2837
3033
  return p;
2838
3034
  }
@@ -2858,8 +3054,8 @@ var WetGpuEngine = (_class3 = class _WetGpuEngine {
2858
3054
  return b;
2859
3055
  });
2860
3056
  this.shadeUni = d.createBuffer({ size: 16, usage: UNIFORM | COPY_DST, label: "wet shade" });
2861
- void _optionalChain([device, 'access', _78 => _78.lost, 'optionalAccess', _79 => _79.then, 'call', _80 => _80((info) => {
2862
- this.lostReason = _optionalChain([info, 'optionalAccess', _81 => _81.message]) || _optionalChain([info, 'optionalAccess', _82 => _82.reason]) || "the GPU device was lost";
3057
+ void _optionalChain([device, 'access', _84 => _84.lost, 'optionalAccess', _85 => _85.then, 'call', _86 => _86((info) => {
3058
+ this.lostReason = _optionalChain([info, 'optionalAccess', _87 => _87.message]) || _optionalChain([info, 'optionalAccess', _88 => _88.reason]) || "the GPU device was lost";
2863
3059
  })]);
2864
3060
  }
2865
3061
 
@@ -2893,7 +3089,7 @@ var WetGpuEngine = (_class3 = class _WetGpuEngine {
2893
3089
  }
2894
3090
  /** Bytes of GPU memory in use. */
2895
3091
  memoryBytes() {
2896
- return this.state.size + this.jobs.size + (_nullishCoalesce(_optionalChain([this, 'access', _83 => _83.srcTiles, 'optionalAccess', _84 => _84.size]), () => ( 0))) + (_nullishCoalesce(_optionalChain([this, 'access', _85 => _85.outTiles, 'optionalAccess', _86 => _86.size]), () => ( 0))) * 2;
3092
+ 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;
2897
3093
  }
2898
3094
  /** Slots the state buffer holds now. */
2899
3095
  get capacity() {
@@ -3042,9 +3238,9 @@ var WetGpuEngine = (_class3 = class _WetGpuEngine {
3042
3238
  const d = this.device;
3043
3239
  const tileBytes = TILE_WORDS * 4;
3044
3240
  if (n > this.tileCap || !this.outTiles || !this.outRead || !this.srcTiles) {
3045
- _optionalChain([this, 'access', _87 => _87.srcTiles, 'optionalAccess', _88 => _88.destroy, 'call', _89 => _89()]);
3046
- _optionalChain([this, 'access', _90 => _90.outTiles, 'optionalAccess', _91 => _91.destroy, 'call', _92 => _92()]);
3047
- _optionalChain([this, 'access', _93 => _93.outRead, 'optionalAccess', _94 => _94.destroy, 'call', _95 => _95()]);
3241
+ _optionalChain([this, 'access', _93 => _93.srcTiles, 'optionalAccess', _94 => _94.destroy, 'call', _95 => _95()]);
3242
+ _optionalChain([this, 'access', _96 => _96.outTiles, 'optionalAccess', _97 => _97.destroy, 'call', _98 => _98()]);
3243
+ _optionalChain([this, 'access', _99 => _99.outRead, 'optionalAccess', _100 => _100.destroy, 'call', _101 => _101()]);
3048
3244
  this.tileCap = Math.max(n, this.tileCap * 2, 4);
3049
3245
  this.srcTiles = d.createBuffer({ size: this.tileCap * tileBytes, usage: STORAGE | COPY_DST, label: "wet base tiles" });
3050
3246
  this.outTiles = d.createBuffer({ size: this.tileCap * tileBytes, usage: STORAGE | COPY_SRC, label: "wet shaded tiles" });
@@ -3098,7 +3294,7 @@ var WetGpuEngine = (_class3 = class _WetGpuEngine {
3098
3294
  return this.device.queue.onSubmittedWorkDone();
3099
3295
  }
3100
3296
  destroy() {
3101
- 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', _96 => _96.destroy, 'call', _97 => _97()]);
3297
+ 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()]);
3102
3298
  this.device.destroy();
3103
3299
  }
3104
3300
  }, _class3);
@@ -3124,7 +3320,7 @@ function pointerStrokeSample(e, toPoint) {
3124
3320
  return s;
3125
3321
  }
3126
3322
  function pointerStrokeSamples(e, toPoint) {
3127
- const events = _nullishCoalesce(_optionalChain([e, 'access', _98 => _98.getCoalescedEvents, 'optionalCall', _99 => _99()]), () => ( []));
3323
+ const events = _nullishCoalesce(_optionalChain([e, 'access', _104 => _104.getCoalescedEvents, 'optionalCall', _105 => _105()]), () => ( []));
3128
3324
  return (events.length ? events : [e]).map((ev) => pointerStrokeSample(ev, toPoint));
3129
3325
  }
3130
3326
 
@@ -3219,4 +3415,8 @@ function pointerStrokeSamples(e, toPoint) {
3219
3415
 
3220
3416
 
3221
3417
 
3222
- exports.createPigmentTools = createPigmentTools; exports.WET_TILE = WET_TILE; exports.SLOT_SIDE = SLOT_SIDE; exports.PLANE = PLANE; exports.PLANE_BYTES = PLANE_BYTES; exports.SET_PLANES = SET_PLANES; exports.P_RHO = P_RHO; exports.P_UX = P_UX; exports.P_UY = P_UY; exports.P_SW = P_SW; exports.P_P0 = P_P0; exports.P_EDGE = P_EDGE; exports.P_D0 = P_D0; exports.P_H = P_H; exports.P_FX = P_FX; exports.P_FY = P_FY; exports.SLOT_PLANES = SLOT_PLANES; exports.SLOT_BYTES = SLOT_BYTES; exports.PIGMENT_CHANNELS = PIGMENT_CHANNELS; exports.EX = EX; exports.EY = EY; exports.OPP = OPP; exports.W9 = W9; exports.DRY_EPS = DRY_EPS; exports.U_MAX = U_MAX; exports.STRIP = STRIP; exports.MAX_BATCH = MAX_BATCH; exports.FREE_INTERVAL = FREE_INTERVAL; exports.FLAG_ALIVE = FLAG_ALIVE; exports.FLAG_LEFT = FLAG_LEFT; exports.FLAG_RIGHT = FLAG_RIGHT; exports.FLAG_TOP = FLAG_TOP; exports.FLAG_BOTTOM = FLAG_BOTTOM; exports.FLAG_WET = FLAG_WET; exports.PARAM_FLOATS = PARAM_FLOATS; exports.PARAM = PARAM; exports.DEFAULT_WET_PARAMS = DEFAULT_WET_PARAMS; exports.WET_PARAM_KEYS = WET_PARAM_KEYS; exports.DEFAULT_WET_PAPER = DEFAULT_WET_PAPER; exports.FLAT = FLAT; exports.normalizeWetParams = normalizeWetParams; exports.normalizeTilt = normalizeTilt; exports.latticeViscosity = latticeViscosity; exports.PAPER_CAPACITY = PAPER_CAPACITY; exports.MAX_TILT_ACCEL = MAX_TILT_ACCEL; exports.packWetParams = packWetParams; exports.edgeBits = edgeBits; exports.PIGMENT_UNIT = PIGMENT_UNIT; exports.HIDE = HIDE; exports.BODY_SCATTER = BODY_SCATTER; exports.dexp = dexp; exports.dlog = dlog; exports.dpow = dpow; exports.SRGB_TO_LINEAR = SRGB_TO_LINEAR; exports.srgbByteToLinear = srgbByteToLinear; exports.linearToSrgb8 = linearToSrgb8; exports.pigmentOf = pigmentOf; exports.expNeg = expNeg; exports.filmReflectance = filmReflectance; exports.pigmentColor = pigmentColor; exports.filmPixel = filmPixel; exports.pixelPigment = pixelPigment; exports.shadeTile = shadeTile; exports.stepSlotTs = stepSlotTs; exports.loadWetKernel = loadWetKernel; exports.canShareMemory = canShareMemory; exports.wetSimdSupported = wetSimdSupported; exports.wetSupport = wetSupport; exports.WetWorkerPool = WetWorkerPool; exports.MAX_WET_LANES = MAX_WET_LANES; exports.MAX_WET_SLOTS = MAX_WET_SLOTS; exports.WetWorldError = WetWorldError; exports.WetWorld = WetWorld; exports.moveWetSnapshot = moveWetSnapshot; exports.WET_FLOW_KINDS = WET_FLOW_KINDS; exports.DEFAULT_FLOW = DEFAULT_FLOW; exports.dsincos = dsincos; exports.normalizeFlow = normalizeFlow; exports.flowField = flowField; exports.WetMedium = WetMedium; exports.WGSL_STEP = WGSL_STEP; exports.WGSL_HALO = WGSL_HALO; exports.WGSL_SHADE = WGSL_SHADE; exports.findWebGpu = findWebGpu; exports.createWetGpuEngine = createWetGpuEngine; exports.wetGpuSupport = wetGpuSupport; exports.WetGpuEngine = WetGpuEngine; exports.pointerPressure = pointerPressure; exports.pointerStrokeSample = pointerStrokeSample; exports.pointerStrokeSamples = pointerStrokeSamples;
3418
+
3419
+
3420
+
3421
+
3422
+ exports.createPigmentTools = createPigmentTools; exports.WET_TILE = WET_TILE; exports.SLOT_SIDE = SLOT_SIDE; exports.PLANE = PLANE; exports.PLANE_BYTES = PLANE_BYTES; exports.SET_PLANES = SET_PLANES; exports.P_RHO = P_RHO; exports.P_UX = P_UX; exports.P_UY = P_UY; exports.P_SW = P_SW; exports.P_P0 = P_P0; exports.P_L = P_L; exports.P_EDGE = P_EDGE; exports.P_D0 = P_D0; exports.P_DL = P_DL; exports.P_H = P_H; exports.P_FX = P_FX; exports.P_FY = P_FY; exports.SLOT_PLANES = SLOT_PLANES; exports.SLOT_BYTES = SLOT_BYTES; exports.PIGMENT_CHANNELS = PIGMENT_CHANNELS; exports.MASS_CHANNELS = MASS_CHANNELS; exports.EX = EX; exports.EY = EY; exports.OPP = OPP; exports.W9 = W9; exports.DRY_EPS = DRY_EPS; exports.U_MAX = U_MAX; exports.STRIP = STRIP; exports.MAX_BATCH = MAX_BATCH; exports.FREE_INTERVAL = FREE_INTERVAL; exports.FLAG_ALIVE = FLAG_ALIVE; exports.FLAG_LEFT = FLAG_LEFT; exports.FLAG_RIGHT = FLAG_RIGHT; exports.FLAG_TOP = FLAG_TOP; exports.FLAG_BOTTOM = FLAG_BOTTOM; exports.FLAG_WET = FLAG_WET; exports.PARAM_FLOATS = PARAM_FLOATS; exports.PARAM = PARAM; exports.DEFAULT_WET_PARAMS = DEFAULT_WET_PARAMS; exports.WET_PARAM_KEYS = WET_PARAM_KEYS; exports.DEFAULT_WET_PAPER = DEFAULT_WET_PAPER; exports.FLAT = FLAT; exports.normalizeWetParams = normalizeWetParams; exports.normalizeTilt = normalizeTilt; exports.latticeViscosity = latticeViscosity; exports.PAPER_CAPACITY = PAPER_CAPACITY; exports.MAX_TILT_ACCEL = MAX_TILT_ACCEL; exports.packWetParams = packWetParams; exports.edgeBits = edgeBits; exports.PIGMENT_UNIT = PIGMENT_UNIT; exports.HIDE = HIDE; exports.BODY_SCATTER = BODY_SCATTER; exports.dexp = dexp; exports.dlog = dlog; exports.dpow = dpow; exports.SRGB_TO_LINEAR = SRGB_TO_LINEAR; exports.srgbByteToLinear = srgbByteToLinear; exports.linearToSrgb8 = linearToSrgb8; exports.pigmentOf = pigmentOf; exports.expNeg = expNeg; exports.filmReflectance = filmReflectance; exports.pigmentColor = pigmentColor; exports.filmPixel = filmPixel; exports.pixelPigment = pixelPigment; exports.shadeTile = shadeTile; exports.shadeLightTile = shadeLightTile; exports.stepSlotTs = stepSlotTs; exports.loadWetKernel = loadWetKernel; exports.canShareMemory = canShareMemory; exports.wetSimdSupported = wetSimdSupported; exports.wetSupport = wetSupport; exports.WetWorkerPool = WetWorkerPool; exports.MAX_WET_LANES = MAX_WET_LANES; exports.MAX_WET_SLOTS = MAX_WET_SLOTS; exports.WetWorldError = WetWorldError; exports.WetWorld = WetWorld; exports.moveWetSnapshot = moveWetSnapshot; exports.WET_FLOW_KINDS = WET_FLOW_KINDS; exports.DEFAULT_FLOW = DEFAULT_FLOW; exports.dsincos = dsincos; exports.normalizeFlow = normalizeFlow; exports.flowField = flowField; exports.WetMedium = WetMedium; exports.WGSL_STEP = WGSL_STEP; exports.WGSL_HALO = WGSL_HALO; exports.WGSL_SHADE = WGSL_SHADE; exports.findWebGpu = findWebGpu; exports.createWetGpuEngine = createWetGpuEngine; exports.wetGpuSupport = wetGpuSupport; exports.WetGpuEngine = WetGpuEngine; exports.pointerPressure = pointerPressure; exports.pointerStrokeSample = pointerStrokeSample; exports.pointerStrokeSamples = pointerStrokeSamples;