@pixerate/editor 0.243.0 → 0.245.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 (113) hide show
  1. package/dist/agent/index.cjs +34 -32
  2. package/dist/agent/index.js +6 -4
  3. package/dist/animation-editor/index.cjs +61 -61
  4. package/dist/animation-editor/index.js +4 -4
  5. package/dist/{brushstrokeRender-274HGNZR.js → brushstrokeRender-4542T5HP.js} +2 -2
  6. package/dist/{brushstrokeRender-C7RJZPMC.cjs → brushstrokeRender-QA5WVIDT.cjs} +5 -5
  7. package/dist/canvas/changeFeed.d.cts +71 -0
  8. package/dist/canvas/changeFeed.d.ts +71 -0
  9. package/dist/canvas/index.cjs +490 -34
  10. package/dist/canvas/index.d.cts +3 -0
  11. package/dist/canvas/index.d.ts +3 -0
  12. package/dist/canvas/index.js +483 -27
  13. package/dist/canvas/proposal-tools.d.cts +21 -0
  14. package/dist/canvas/proposal-tools.d.ts +21 -0
  15. package/dist/canvas/proposals.d.cts +112 -0
  16. package/dist/canvas/proposals.d.ts +112 -0
  17. package/dist/chart/index.cjs +21 -21
  18. package/dist/chart/index.js +2 -2
  19. package/dist/{chunk-6L3WWIDW.js → chunk-4EQY4TZJ.js} +1 -1
  20. package/dist/{chunk-5FZS7J7C.cjs → chunk-4T47Z4J3.cjs} +18 -2
  21. package/dist/{chunk-4I76OCVD.cjs → chunk-5DLCOQOQ.cjs} +16 -1
  22. package/dist/{chunk-4ZHXLFXS.js → chunk-5RFENYO5.js} +1 -1
  23. package/dist/{chunk-SRXWDOUI.js → chunk-7YQNKSQ7.js} +1 -1
  24. package/dist/{chunk-WGN7AUKB.js → chunk-7YW7T5TJ.js} +1 -1
  25. package/dist/{chunk-X3ZINLIP.js → chunk-AU6MWQKZ.js} +1 -1
  26. package/dist/{chunk-OONFIFEU.cjs → chunk-BDIH4DYR.cjs} +23 -23
  27. package/dist/{chunk-AIS56JOO.js → chunk-BVRRIW7F.js} +1 -1
  28. package/dist/{chunk-KK2FWMA2.cjs → chunk-HZ547SGQ.cjs} +1 -1
  29. package/dist/{chunk-3WOZEBXZ.cjs → chunk-IZL5V5RK.cjs} +2 -2
  30. package/dist/{chunk-IXI5KATU.js → chunk-L5PB7EG3.js} +1 -1
  31. package/dist/{chunk-R2FVAKFH.cjs → chunk-M6JORA5Z.cjs} +86 -86
  32. package/dist/{chunk-RNSANXPT.js → chunk-NS62CQ2B.js} +112 -14
  33. package/dist/{chunk-UZU6DIZA.cjs → chunk-NUTMINJZ.cjs} +45 -45
  34. package/dist/{chunk-H4Z72Z4Z.js → chunk-OSTKS3YN.js} +1 -1
  35. package/dist/{chunk-HLBTRSQJ.js → chunk-P3NIPIKS.js} +263 -63
  36. package/dist/{chunk-7V7QMFTI.cjs → chunk-QOBCBGGI.cjs} +2 -2
  37. package/dist/{chunk-UV7OOZJQ.cjs → chunk-SD23XMVJ.cjs} +35 -35
  38. package/dist/{chunk-FQVVO5YA.js → chunk-SGPIVHJB.js} +1 -1
  39. package/dist/{chunk-HQZHOPNE.js → chunk-TBJEMELS.js} +18 -2
  40. package/dist/{chunk-QTMQ5TGA.js → chunk-UCYW4M66.js} +16 -1
  41. package/dist/{chunk-XFYLA3ZO.cjs → chunk-VMROIWLL.cjs} +290 -90
  42. package/dist/{chunk-W7GRGGBH.cjs → chunk-W7L4HUT2.cjs} +8 -8
  43. package/dist/{chunk-FJ2MSI43.cjs → chunk-WKPXOGZZ.cjs} +124 -26
  44. package/dist/{chunk-D2SGV525.cjs → chunk-XZNU4DUX.cjs} +2 -2
  45. package/dist/diagram/index.cjs +2 -2
  46. package/dist/diagram/index.js +1 -1
  47. package/dist/effects/index.cjs +4 -4
  48. package/dist/effects/index.js +2 -2
  49. package/dist/extensions/index.cjs +5 -5
  50. package/dist/extensions/index.js +4 -4
  51. package/dist/gradient/index.cjs +2 -2
  52. package/dist/gradient/index.js +1 -1
  53. package/dist/history/HistoryManager.d.cts +20 -1
  54. package/dist/history/HistoryManager.d.ts +20 -1
  55. package/dist/history/index.cjs +8 -2
  56. package/dist/history/index.js +8 -2
  57. package/dist/history/types.d.cts +31 -0
  58. package/dist/history/types.d.ts +31 -0
  59. package/dist/image-editor/index.cjs +785 -777
  60. package/dist/image-editor/index.js +13 -5
  61. package/dist/index.cjs +16 -10
  62. package/dist/index.js +11 -5
  63. package/dist/kernel-wasm-4KVONDAU.js +9 -0
  64. package/dist/kernel-wasm-TTYRBGAK.cjs +9 -0
  65. package/dist/paint/index.cjs +10 -2
  66. package/dist/paint/index.js +9 -1
  67. package/dist/paint/wet/film.d.cts +19 -1
  68. package/dist/paint/wet/film.d.ts +19 -1
  69. package/dist/paint/wet/gpu-kernels.d.cts +2 -2
  70. package/dist/paint/wet/gpu-kernels.d.ts +2 -2
  71. package/dist/paint/wet/kernel-wasm.d.cts +5 -5
  72. package/dist/paint/wet/kernel-wasm.d.ts +5 -5
  73. package/dist/paint/wet/kernel.d.cts +1 -1
  74. package/dist/paint/wet/kernel.d.ts +1 -1
  75. package/dist/paint/wet/medium.d.cts +3 -1
  76. package/dist/paint/wet/medium.d.ts +3 -1
  77. package/dist/paint/wet/model.d.cts +19 -4
  78. package/dist/paint/wet/model.d.ts +19 -4
  79. package/dist/paint/wet/world.d.cts +34 -8
  80. package/dist/paint/wet/world.d.ts +34 -8
  81. package/dist/paint-editor/backlight.d.cts +77 -0
  82. package/dist/paint-editor/backlight.d.ts +77 -0
  83. package/dist/paint-editor/controller.d.cts +11 -0
  84. package/dist/paint-editor/controller.d.ts +11 -0
  85. package/dist/paint-editor/index.cjs +685 -397
  86. package/dist/paint-editor/index.d.cts +1 -0
  87. package/dist/paint-editor/index.d.ts +1 -0
  88. package/dist/paint-editor/index.js +308 -20
  89. package/dist/paint-editor/paper.d.cts +7 -0
  90. package/dist/paint-editor/paper.d.ts +7 -0
  91. package/dist/paint-editor/wet.d.cts +9 -0
  92. package/dist/paint-editor/wet.d.ts +9 -0
  93. package/dist/parameters/index.cjs +2 -2
  94. package/dist/parameters/index.js +1 -1
  95. package/dist/tools/index.cjs +4 -2
  96. package/dist/tools/index.d.cts +1 -0
  97. package/dist/tools/index.d.ts +1 -0
  98. package/dist/tools/index.js +3 -1
  99. package/dist/tools/origin.d.cts +22 -0
  100. package/dist/tools/origin.d.ts +22 -0
  101. package/dist/vector/index.cjs +964 -964
  102. package/dist/vector/index.js +5 -5
  103. package/dist/video-editor/index.cjs +4 -4
  104. package/dist/video-editor/index.js +2 -2
  105. package/package.json +1 -1
  106. package/skills/pixerate-canvas/SKILL.md +25 -0
  107. package/skills/pixerate-paint-editor/SKILL.md +3 -3
  108. package/skills/pixerate-paint-editor/references/light-paint.md +30 -7
  109. package/skills/pixerate-paint-watercolour/SKILL.md +10 -1
  110. package/skills/pixerate-paint-watercolour/evals/glowing-wash.json +22 -0
  111. package/skills/pixerate-paint-watercolour/evals/stained-glass.json +16 -0
  112. package/dist/kernel-wasm-6FPOLVUZ.cjs +0 -9
  113. 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 = import("./kernel-wasm-WIMMJUF2.js").then((m) => WebAssembly.compile(decode(shared ? m.WET_KERNEL_SHARED : m.WET_KERNEL_SINGLE)));
744
+ p = import("./kernel-wasm-4KVONDAU.js").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 TiledSurface(1, /* @__PURE__ */ new Map(), 0);
927
+ function newLane(id, index, params, flow, flowStrength, base, light) {
928
+ const l = 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 {
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(options.params ?? {}, DEFAULT_WET_PARAMS),
1037
- flow: options.flow ?? null,
1038
- flowStrength: options.flow ? options.flowStrength ?? 1 : 0,
1039
- base: options.base ?? new 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(options.params ?? {}, DEFAULT_WET_PARAMS), options.flow ?? null, options.flow ? options.flowStrength ?? 1 : 0, options.base ?? new 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 {
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, 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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
1704
1775
  const a = Math.min(1, Math.max(0, amount * w));
1705
1776
  if (!(a > 0)) return;
1706
1777
  const key = tileKey(x >> 8, y >> 8);
1778
+ const li = (y & 255) * TILE + (x & 255);
1779
+ let lt = lightTiles?.get(key);
1780
+ if (lt && (lt.uniform ?? 1) !== 0) {
1781
+ if (!lightWritten.has(key)) {
1782
+ const data = lt.data.length ? new Uint8ClampedArray(lt.data) : new Uint8ClampedArray(TILE * TILE).fill(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 {
1731
1818
  }
1732
1819
  this.setBase(id, new TiledSurface(4, tiles, 0));
1733
1820
  }
1821
+ if (lightWritten.size) {
1822
+ for (const key of lightWritten) {
1823
+ const t = finishTile(lightTiles.get(key).data, 1, 0);
1824
+ if (t) lightTiles.set(key, t);
1825
+ else lightTiles.delete(key);
1826
+ }
1827
+ this.setLight(id, new 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 {
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 {
1790
1885
  const lane = this.lanes[this.laneIndexOf(key)];
1791
1886
  const [tx, ty] = this.tileXY(key);
1792
1887
  const tk = tileKey(tx, ty);
1888
+ let lightChanged = false;
1889
+ if (lane.lit) {
1890
+ const lout = this.shadeLightSlot(key, lane.light.tiles.get(tk) ?? null, all ? "bake-all" : "bake");
1891
+ if (lout) {
1892
+ const tiles2 = new Map(lane.light.tiles);
1893
+ const t2 = finishTile(lout, 1, 0);
1894
+ if (t2) tiles2.set(tk, t2);
1895
+ else tiles2.delete(tk);
1896
+ this.setLight(lane.id, new TiledSurface(1, tiles2, 0));
1897
+ lightChanged = true;
1898
+ }
1899
+ }
1793
1900
  const src = lane.base.tiles.get(tk)?.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 = finishTile(out, 4, 0);
1798
1905
  if (t) tiles.set(tk, t);
@@ -1800,6 +1907,19 @@ var WetWorld = class {
1800
1907
  this.setBase(lane.id, new 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(TILE * TILE).fill(src.uniform ?? 0) : null;
1916
+ const out = full ? new Uint8ClampedArray(full) : new Uint8ClampedArray(TILE * 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 {
1812
1932
  const out = src ? new Uint8ClampedArray(src) : new Uint8ClampedArray(TILE * 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 {
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
  }
@@ -1844,6 +1968,23 @@ var WetWorld = class {
1844
1968
  }
1845
1969
  return new 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, lane.light.tiles.get(tk) ?? null, "bake-all");
1981
+ if (!out) continue;
1982
+ const t = finishTile(out, 1, 0);
1983
+ if (t) tiles.set(tk, t);
1984
+ else tiles.delete(tk);
1985
+ }
1986
+ return new 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
@@ -1972,6 +2113,38 @@ var WetWorld = 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 (lane.lightShaded.get(tk)?.stamp === lane.stamp) continue;
2131
+ const src = lane.light.tiles.get(tk) ?? null;
2132
+ const out = this.shadeLightSlot(key2, src, "display");
2133
+ lane.lightShaded.set(tk, { stamp: lane.stamp, tile: out ? finishTile(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 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 {
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();
@@ -2189,11 +2362,11 @@ var WetWorld = 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 {
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: moved.base ?? l.base, flow: moved.flow === void 0 ? l.flow : moved.flow };
2438
+ return { ...l, base: moved.base ?? l.base, light: 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
  }
@@ -2396,6 +2571,7 @@ var WetMedium = class {
2396
2571
  }
2397
2572
  deposit(rect, cov, dep, paint, _pigment, field = null) {
2398
2573
  const { water, mass, body } = this.options;
2574
+ const light = (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 = 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
  };
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;
@@ -3140,14 +3336,17 @@ export {
3140
3336
  P_UY,
3141
3337
  P_SW,
3142
3338
  P_P0,
3339
+ P_L,
3143
3340
  P_EDGE,
3144
3341
  P_D0,
3342
+ P_DL,
3145
3343
  P_H,
3146
3344
  P_FX,
3147
3345
  P_FY,
3148
3346
  SLOT_PLANES,
3149
3347
  SLOT_BYTES,
3150
3348
  PIGMENT_CHANNELS,
3349
+ MASS_CHANNELS,
3151
3350
  EX,
3152
3351
  EY,
3153
3352
  OPP,
@@ -3192,6 +3391,7 @@ export {
3192
3391
  filmPixel,
3193
3392
  pixelPigment,
3194
3393
  shadeTile,
3394
+ shadeLightTile,
3195
3395
  stepSlotTs,
3196
3396
  loadWetKernel,
3197
3397
  canShareMemory,