@vitreajs/vitrea 0.1.0 → 0.2.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.
@@ -83,6 +83,30 @@ fn cs_analysis(@builtin(local_invocation_index) lane : u32) {
83
83
  }
84
84
  }`;
85
85
 
86
+ // ../policy/dist/refraction.js
87
+ var REFRACTION_LADDER = ["none", "approximate", "true"];
88
+ function refractionRank(quality) {
89
+ return REFRACTION_LADDER.indexOf(quality);
90
+ }
91
+ function accessibilityRefractionCap(policy) {
92
+ switch (policy.refraction) {
93
+ case "nominal":
94
+ return "true";
95
+ case "reduced":
96
+ return "approximate";
97
+ case "none":
98
+ return "none";
99
+ }
100
+ }
101
+ function effectiveRefraction(a, b) {
102
+ return refractionRank(a) <= refractionRank(b) ? a : b;
103
+ }
104
+ var DEFAULT_REFRACTION_SCALE = {
105
+ none: 0,
106
+ approximate: 0.45,
107
+ true: 1
108
+ };
109
+
86
110
  // ../renderer-webgpu/dist/color.js
87
111
  var BACKDROP_COLOR_SPACES = ["srgb", "display-p3"];
88
112
  var BACKDROP_ALPHA_MODES = ["premultiplied", "unpremultiplied", "opaque"];
@@ -161,26 +185,18 @@ function encodeOutputBytes(linear, alpha) {
161
185
  }
162
186
 
163
187
  // ../renderer-webgpu/dist/material.js
164
- var REFRACTION_LADDER = ["none", "approximate", "true"];
165
- function refractionRank(quality) {
166
- return REFRACTION_LADDER.indexOf(quality);
167
- }
168
- function accessibilityRefractionCap(policy) {
169
- switch (policy.refraction) {
170
- case "nominal":
171
- return "true";
172
- case "reduced":
173
- return "approximate";
174
- case "none":
175
- return "none";
176
- }
177
- }
178
- function effectiveRefraction(a, b) {
179
- return refractionRank(a) <= refractionRank(b) ? a : b;
180
- }
188
+ var NOMINAL_MATERIAL_POLICY = {
189
+ glass: "material",
190
+ frost: "nominal",
191
+ refraction: "nominal",
192
+ occlusion: "nominal",
193
+ border: "nominal",
194
+ ambientTint: "nominal",
195
+ foreground: "adaptive"
196
+ };
181
197
  var MATERIAL_VARIANTS = ["regular", "clear"];
182
198
  var SRGB_WHITE_TINT = [1, 1, 1];
183
- var INCREASED_OCCLUSION_LIFT = 0.4722;
199
+ var INCREASED_OCCLUSION_LIFT = 0.75;
184
200
  function occlusionAlphaUnderPolicy(nominal, occlusion, lift = INCREASED_OCCLUSION_LIFT) {
185
201
  switch (occlusion) {
186
202
  case "nominal":
@@ -197,24 +213,48 @@ var DEFAULT_MATERIAL_PROFILE = {
197
213
  // `platform-web`'s CSS-tier blur on the same number — and makes core's 24 px
198
214
  // `samplingPadding` advisory exactly the 3σ S1 measured.
199
215
  regular: {
200
- blurSigma: 8,
216
+ /*
217
+ * REFITTED (recalibration cascade, 2026-08-31) against the ACTIVE-pose bed.
218
+ * C9a's 0.62 and σ = 8 were fitted against the inactive material and are
219
+ * kept in git history, not here.
220
+ *
221
+ * `blurSigma` 3 is the first value this constant has ever had that the
222
+ * fixtures could identify. §6.1 called it unidentifiable because the
223
+ * inactive reference passed almost no backdrop structure; the active one
224
+ * passes a great deal (interior standard deviation 0.1358 over the
225
+ * checkerboard against vitrea's 0.0176 at σ = 8), so the objective's spread
226
+ * term now bites — it triples from σ = 3 to σ = 6 at a fixed alpha.
227
+ *
228
+ * `tintAlpha` 0.46 is the minimiser over the eight tone-inert untinted rest
229
+ * calibration cells, a genuine interior optimum (0.38 → 0.16945, 0.46 →
230
+ * 0.15704, 0.54 → 0.17248) with ΔE and SSIM agreeing. It remains true that
231
+ * no single value is right for every scene: a lerp toward one tint colour
232
+ * implies an alpha of 0.24 over the checkerboard and 0.47 over the photo at
233
+ * the same 44 px span, which is the lerp-versus-multiply question C9a
234
+ * recorded and nobody has acted on. Claims §5.13.
235
+ */
236
+ blurSigma: 3,
201
237
  tint: srgbToLinear(SRGB_WHITE_TINT),
202
- // MEASURED (C9a), against apple-macos-26.5-1x-light-standard. The advisory
203
- // 0.28 made vitrea's regular material roughly half as opaque as the
204
- // reference: regressing interior level against backdrop level across the
205
- // calibration scenes puts Apple's transmission at 0.26 of the backdrop
206
- // where vitrea's was 0.70. 0.62 is the minimiser of the declared tuning
207
- // objective; see docs/doperpowers/specs/c9a-fidelity-claims.md, and note
208
- // that no single value can be right for every size — Apple's opacity falls
209
- // with surface span (0.88 at 32 px to 0.56 at 96 px) and this renderer has
210
- // no size term on the tint.
211
- tintAlpha: 0.62,
238
+ tintAlpha: 0.46,
212
239
  rimWidth: 1.5,
213
240
  rimAlpha: 0.18,
214
241
  specularPower: 6,
215
242
  specularGain: 0.55,
216
243
  shadowDepth: 0.35,
217
- shadowAlpha: 0.55,
244
+ /*
245
+ * REFITTED 0.55 → 0.05 (2026-08-31), and it is the largest single
246
+ * improvement in the cascade: the objective falls 0.14615 → 0.09333 with
247
+ * ΔE and SSIM both improving.
248
+ *
249
+ * The active reference's contour is BRIGHTER than its own body — a rim peak
250
+ * of 0.025…0.129 above baseline, always at one pixel deep. vitrea's read
251
+ * 0.0000 with its peak 8-12 px in, meaning it had no edge feature at all:
252
+ * the inner shadow at 0.55 was darkening the contour faster than the rim lit
253
+ * it. The rim constants themselves did not move; the thing suppressing them
254
+ * did. On the inactive bed the reference's rim was 0.0000…0.0041 and this
255
+ * was invisible, which is what §6.2 recorded as "below quantisation".
256
+ */
257
+ shadowAlpha: 0.05,
218
258
  highlight: srgbToLinear(SRGB_WHITE_TINT)
219
259
  },
220
260
  // Persistently more transparent, so it frosts less and tints less.
@@ -260,20 +300,277 @@ var DEFAULT_MATERIAL_PROFILE = {
260
300
  adaptiveTintLight: srgbToLinear(SRGB_WHITE_TINT),
261
301
  adaptiveLuminanceLow: 0.12,
262
302
  adaptiveLuminanceHigh: 0.42,
263
- refractionScale: {
264
- none: 0,
265
- approximate: 0.45,
266
- true: 1
267
- },
268
- lensSpanMin: 28,
269
- lensSpanMax: 420,
303
+ // Authored in `@vitrea/policy` alongside the ladder it is keyed by, because the
304
+ // CSS tier needs the same table and used to hold two more copies of it
305
+ // (Decision Log #23(d)). The profile is still what the shaders read and still
306
+ // what `withMaterialOverrides` replaces — only the *default* literal moved, and
307
+ // it moved unchanged.
308
+ refractionScale: DEFAULT_REFRACTION_SCALE,
309
+ /*
310
+ * MEASURED (W2), against the settled apple-macos-26.5 bed, and set from the
311
+ * REFERENCE's own size-dependence rather than from the objective's minimum.
312
+ *
313
+ * The band was 28…420 while it served the lens alone and nothing had measured
314
+ * it, which put the whole canonical range (32…160 px) inside the first 17% of
315
+ * the curve. What the settled reference actually does, over a fixed checkerboard
316
+ * backdrop: it passes 0.244 of the backdrop's contrast at a 32 px span, 0.230 at
317
+ * 44 px and 0.144 at 96 px. So 6% of the movement happens between 32 and 44 and
318
+ * the rest between 44 and 96, and it is finished by 96 — which is what these two
319
+ * numbers are. `sizeSpanMin` is also the smallest canonical component's span, so
320
+ * that component is the law's exact zero by construction.
321
+ *
322
+ * The tuning objective would rather have 64 (0.1568 against 0.1637 on the rest
323
+ * cells, a 4% win inside a grid that spans 1.07×). Declined, and the reason is
324
+ * recorded rather than the win taken: the gain comes entirely from the interior
325
+ * *level* term, whose residual is the backdrop tone-adaptation gap that wave
326
+ * child W7 is chartered to close — so a band fitted to 64 would be using the
327
+ * size law as a proxy for a mechanism vitrea does not have yet, against the
328
+ * reference's own measurement of where its size-dependence lives, and W7 would
329
+ * have to unpick it. See the claims doc's size-law section.
330
+ */
331
+ sizeSpanMin: 32,
332
+ sizeSpanMax: 96,
270
333
  lensSizeGainMax: 2.6,
334
+ /*
335
+ * MEASURED (W2), each with its own status — see the fields' notes.
336
+ *
337
+ * `sizeScatterGainMax` = 1: implemented on both tiers and inert, because the
338
+ * canonical fixtures cannot resolve it. The objective is flat to 1.00× over
339
+ * gains 1…6, and that is not the backdrop chain's depth talking: re-run with the
340
+ * chain deepened (MIN_LEVEL_EXTENT 8 → 4, which lifts the reachable σ on the
341
+ * 320×200 canvas from 1.2× to 2.4×) the grid is still flat to 1.00× and still
342
+ * best at 1. A number the fixtures cannot see is a number that will be met by
343
+ * accident, so this ships at the identity with the mechanism in place.
344
+ *
345
+ * `sizeOcclusionGain` = 0: fitted, and the fit is a boundary optimum with real
346
+ * leverage against it — the objective rises monotonically, 0.168 → 0.224 across
347
+ * 0…0.5, a 1.33× spread. The diagnosis is not a tuning failure: vitrea's
348
+ * interior sits 0.16–0.19 above the reference's at every span because the
349
+ * reference's level is set by backdrop tone adaptation toward roughly 0.63 while
350
+ * vitrea lerps toward a white tint, so making a large surface *more* opaque can
351
+ * only take it further from the reference. The facet is Apple's ("a larger size
352
+ * is more opaque"); the axis that would let it fit is W7's.
353
+ *
354
+ * `sizeShadowGainMax` = 1.4: fitted on the calibration REST cells, where the
355
+ * grid 1.0/1.2/1.4/1.6/1.8/2.2 reads 0.1577/0.1551/0.1533/0.1530/0.1529/0.1531.
356
+ * 1.4 through 2.2 are one flat region (0.25% apart); 1.4 is the point inside it
357
+ * that costs the checks least (ΔE 0.01282 against the baseline's 0.01290, SSIM
358
+ * 0.9671 against 0.9689) and it is the per-cell minimum on both well-conditioned
359
+ * span-96 rest cells. The pressed cells prefer 2.4 monotonically and are
360
+ * excluded from the fit on §6.3's grounds — their native side carries no press
361
+ * pose, so they compare two different states and cannot arbitrate a material
362
+ * constant.
363
+ *
364
+ * ## Re-fitted against the ACTIVE bed (2026-08-31)
365
+ *
366
+ * `sizeScatterGainMax` stays 1 and is now POSITIVELY identified rather than
367
+ * flat: with `blurSigma` refitted to 3 the objective rises 6-7% at gains 2 and
368
+ * 4, so the identity is the optimum instead of a tie.
369
+ *
370
+ * `sizeOcclusionGain` stays 0, a boundary result for the fourth time and for a
371
+ * fourth distinct reason. The reasoning above is retired: the per-cell residual
372
+ * now points the gain's way in every backdrop (web − reference falls with span
373
+ * — checkerboard +0.105/+0.112/+0.046 at 32/44/96 px, photo −0.004/−0.087,
374
+ * light-solid −0.028/+0.001), so the facet finally has the right sign. What it
375
+ * does not have is magnitude: over 0/0.05/0.10/0.15 × spans ending 96/128/160
376
+ * the whole grid is flat to 1.04×, and a 0.2% preference is not evidence. It
377
+ * ships at the identity because the bed cannot identify it, not because the
378
+ * mechanism is wrong.
379
+ *
380
+ * ## Re-fitted against the FROZEN bed (2026-09-01)
381
+ *
382
+ * `sizeOcclusionGain` 0 → **0.05**, and this is the first time the bed could
383
+ * see it. Every earlier fit was a boundary result because calibration held no
384
+ * span above `sizeSpanMax`, so the curve this gain rides was saturated at every
385
+ * cell that could vote. The frozen bed adds three span-128 `rrect-ml`
386
+ * calibration cells, and with them the grid stops being flat: 0.05 is the
387
+ * optimum at **both** backing scales independently — 1× 0.08288 against the
388
+ * identity's 0.08374, 2× 0.09338 against 0.09557 — and ΔE and SSIM move the
389
+ * same way at both.
390
+ *
391
+ * It is still a small number and it is reported as one: a 1.0% preference at
392
+ * 1×, against the 0.2% this doc rightly refused a round ago. What changed is
393
+ * not the margin's size but that it now reproduces on an independent profile
394
+ * instead of resting on one flat grid.
395
+ *
396
+ * `sizeSpanMax` stays 96, and that is now a measurement rather than an
397
+ * assumption. With a span-128 cell voting, the grid rises monotonically —
398
+ * 96/112/128/144/160/192 reading 0.0837/0.0858/0.0943/0.1060/0.1171/0.1300 —
399
+ * so the band's top is where it was, and the suspicion that the bed simply
400
+ * could not see above it is answered rather than inherited.
401
+ *
402
+ * `sizeShadowGainMax` 1.4 → 1. Its evidence dissolved rather than reversing:
403
+ * this is a gain on the INNER SHADOW, and the inner shadow was refitted from
404
+ * `shadowAlpha` 0.55 to 0.05, so there is almost nothing left for it to gain
405
+ * on. The grid 1.0/1.4/2.2 is flat to 0.2%. Carrying a fitted-looking 1.4 whose
406
+ * measurement no longer exists would be the worse of the two errors.
407
+ */
408
+ sizeScatterGainMax: 1,
409
+ sizeOcclusionGain: 0.05,
410
+ sizeShadowGainMax: 1,
271
411
  lensBodyLodPerPx: 0.16,
272
412
  lensRimLodBias: 2.5,
273
413
  reducedTransparencyFrost: 1.75,
274
414
  increasedOcclusionLift: INCREASED_OCCLUSION_LIFT,
275
415
  strongBorderRim: { rimWidth: 2, rimAlpha: 0.95 },
276
416
  reducedTintAdaptation: 0.35,
417
+ // ADVISORY (W3). The span 0.02 … 0.65 covers most of the canonical backdrop
418
+ // range (0.003 … 0.891 linear), and the two ends are deliberately symmetric —
419
+ // 0.45 of the seed's brightness at the dark end, 0.45 of the way to white at
420
+ // the bright end — so the seed itself sits mid-range and the excursion reads
421
+ // as glass rather than as two different colours.
422
+ /*
423
+ * FITTED (2026-08-31, active bed) — and the fit is that THE CURVE IS THE
424
+ * IDENTITY. `floor` → 1 and `ceilMix` → 0 make `tintTone` return the seed
425
+ * unchanged at every backdrop, which leaves `low` and `high` describing
426
+ * nothing at all; they keep their provisional values so a bed that can
427
+ * identify them has somewhere to land, exactly as `adaptiveTint` keeps two
428
+ * equal ends.
429
+ *
430
+ * Monotone and uncontested over floor ∈ {0.45, 0.7, 0.85, 1} × ceilMix ∈
431
+ * {0, 0.2, 0.45}: objective 0.26523 → 0.12676, a 2.09× spread, with ΔE
432
+ * (0.01331 → 0.00831) and SSIM (0.9749 → 0.9788) agreeing. The reference
433
+ * says the same thing directly — on three of the five calibration backdrops
434
+ * its tinted interior is the declared seed EXACTLY in linear light, at a
435
+ * per-channel standard deviation of 0.000. Apple's tint is the author's
436
+ * colour, not a range of tones mapped to it; S219's wording described the
437
+ * inactive material's residue. Claims §5.13.
438
+ */
439
+ tintToneFloor: 1,
440
+ tintToneCeilMix: 0,
441
+ tintToneLow: 0.02,
442
+ tintToneHigh: 0.65,
443
+ /*
444
+ * MEASURED (W7), against the settled apple-macos-26.5 bed, on the light and
445
+ * dark standard profiles jointly and at both scales.
446
+ *
447
+ * The observable these four are set from is the one quantity in this bed that
448
+ * isolates adaptation from everything else: the *separation* between the light
449
+ * and dark references over the same backdrop, on the same component. Under this
450
+ * mechanism that separation is `(1 − tintAlpha)(1 − a)(tintLight − tintDark)`,
451
+ * so the material's transmission, the backdrop's structure and each scheme's
452
+ * own tint all cancel and what is left is `a`. Normalised at the checkerboard,
453
+ * where nothing adapts, it reads:
454
+ *
455
+ * span 44: 0.000 at backdrop 0.500, 0.030 at 0.205, 1.000 at 0.0117, 1.000 at 0.0049
456
+ * span 96: 0.000 at backdrop 0.500, 0.028 at 0.216, 0.256 at 0.0117
457
+ *
458
+ * and the 2× bed reproduces every one of those to three decimals (0.2553
459
+ * against 0.2556 on the one that is not a boundary). Two facts follow. The
460
+ * adaptation is off across the whole ordinary range and turns on only below
461
+ * roughly a fifth of the backdrop scale — so it cannot disturb a cell that
462
+ * already passes. And it is size-gated hard: same backdrop, same material, 1.000
463
+ * against 0.256.
464
+ *
465
+ * The band's dark end is not identifiable from this bed and the fit says so:
466
+ * the reference's backdrops jump from 0.0117 to 0.205 with nothing in between,
467
+ * so `backdropToneLow` is bounded only by "at or below the darkest calibration
468
+ * backdrop". What the two dark backdrops DO pin is the curve's slope near zero —
469
+ * the 96 px surface reads 0.256 at 0.0117 and 0.356 at 0.0039, which is a
470
+ * measured intermediate rather than a step, and it is what fixes
471
+ * `backdropToneSizeBias` against `backdropToneHigh`. `impulse__rrect-md__rest`
472
+ * is a VALIDATION cell and was not fitted to: the calibration set predicts
473
+ * 0.34 there and the cell reads 0.356.
474
+ */
475
+ backdropToneMax: 1,
476
+ backdropToneLow: 0.03,
477
+ backdropToneHigh: 0.14,
478
+ /*
479
+ * REFITTED 0.09 → 0.13 (2026-08-31, active bed). The law's SHAPE is untouched
480
+ * (Decision Log 13 stands; the two-axis rework is next wave) and `max`, `low`
481
+ * and `high` are unmoved — only the size gate widened.
482
+ *
483
+ * What it changes is one cell: a 96 pt surface over the darkest backdrop now
484
+ * barely adapts, where at 0.09 it adapted by a quarter. Two independent reads
485
+ * of the reference disagreed about that and the validation set broke the tie.
486
+ * The light-versus-dark separation estimator §5.8 fitted on says the 96 pt
487
+ * surface adapts by 0.30; the reference's own interior LEVEL says it does not
488
+ * (0.4844, against 0.466 for an unadapted surface at the refitted tint alpha
489
+ * and 0.3566 for an adapted one). Measured once on
490
+ * `impulse__rrect-md__rest`, which is validation and was fitted to by
491
+ * neither: 0.09 renders 0.2858 against a reference of 0.4358 at ΔE 0.02344;
492
+ * 0.13 renders 0.4594 at ΔE 0.00378, six times better. The separation
493
+ * estimator's algebra assumes the two colour schemes share one tint alpha,
494
+ * and this profile pair does not (0.46 against 0.97) — recorded in §5.13 as
495
+ * the reason it is no longer the primary evidence for this constant.
496
+ */
497
+ backdropToneSizeBias: 0.13,
498
+ /*
499
+ * FITTED (recalibration cascade, 2026-08-31). W8's geometry SURVIVES the fit
500
+ * unchanged — `sigmaPx` 15.55, `offsetPx` 7.95, `spreadPx` 3.10 — and only the
501
+ * two amplitudes moved: `occlusion` 0.33 → 0.285 and
502
+ * `reducedTransparencyOcclusion` 0.566 → 0.70. The dark scheme's amplitude
503
+ * (0.09) lands as a profile patch, not as a branch here.
504
+ *
505
+ * The fit is `scripts/sweep.ts --objective shadow`, whose term is the mean over
506
+ * calibration cells of |Δ meanDeparture| — the light each side removes from the
507
+ * whole exterior, in linear light, which is the one commensurate quantity the
508
+ * facet has. The amplitude is a genuine interior optimum over 0.18…0.44 (a
509
+ * 2.23× spread) and flat to 1.04% across 0.255…0.315, which is exactly the
510
+ * scene-to-scene amplitude spread the reference itself shows. The geometry is
511
+ * NOT identifiable against this objective — σ ∈ {13.5, 15.55, 17.5} × offset ∈
512
+ * {6.5, 7.95, 9.4} spans 1.04× with 15.55 the argmin — because an integral over
513
+ * the exterior is insensitive to how the darkening is distributed within it.
514
+ * W8's own two-dimensional fit against the occlusion field measures the
515
+ * geometry far more powerfully (RMS 0.0021 over 142,550 pixels), and the
516
+ * instrument's independent shadow axis agrees, so the geometry stands on those
517
+ * two and this objective is not asked to re-decide it.
518
+ *
519
+ * `reducedTransparencyOcclusion` 0.70 is sharp where the amplitude is flat: a
520
+ * 3.83× spread over 0.45…0.95 with a clear minimum. It also reconciles two
521
+ * routes — 0.285 × 0.70 = 0.1995 against the reference's directly measured
522
+ * reduce-transparency amplitude of 0.203 at a 44 px span. W8's 0.566 was the
523
+ * ratio of the reference's two amplitudes; this is the ratio that makes
524
+ * vitrea's shadow match the reference's under the preference, and the two
525
+ * differ because the base amplitude is a compromise across scenes whose spread
526
+ * is much wider in the standard profile than under reduced transparency.
527
+ *
528
+ * Extracted from the active bed's native fixtures directly —
529
+ * `results/2026-08-31-active-bed-stage0.json` measured the gap, these numbers
530
+ * measure the facet — and left provisional deliberately: X1 gives the fit to
531
+ * the recalibration cascade, which owns the holdout discipline. This child owns
532
+ * the mechanism.
533
+ *
534
+ * Method: for each `rest` fixture, the occlusion field `1 − L_capture/L_background`
535
+ * in linear Rec.709 luminance, over every pixel outside the declared component
536
+ * geometry with a backdrop bright enough to carry a signal; fitted in two
537
+ * dimensions against `occlusion · Φ(−sd/σ)`, where `sd` is the signed distance
538
+ * to the component's own rounded silhouette translated down by `offsetPx` and
539
+ * outset by `spreadPx`.
540
+ *
541
+ * The fit and its residual, on `1x-light-standard` and `2x-light-standard`:
542
+ *
543
+ * backdrop span occlusion σ (1×/2×) offset (1×/2×) RMS
544
+ * photo 44 0.323 15.5/31.1 7.9/15.8 0.0021
545
+ * checkerboard 44 0.338 15.7/31.0 8.0/15.9 0.0016
546
+ * hc-text 44 0.339 15.7/31.1 8.0/15.9 0.0014
547
+ * photo 32 0.321 15.5/30.9 8.1/16.2 0.0018
548
+ * mid-dark-solid 44 0.310 — — 0.0079
549
+ *
550
+ * — against a peak occlusion of 0.24, so the model carries the facet to under
551
+ * 1% of its own amplitude, over up to 142,550 pixels per cell. The three
552
+ * lengths are the consensus across every well-conditioned cell; `occlusion` is
553
+ * the light-standard amplitude at a span the size law leaves alone.
554
+ *
555
+ * What the amplitude does NOT yet have is a mechanism for its scene-to-scene
556
+ * spread. Over the flat near-white `light-solid` backdrop the same fit reads
557
+ * 0.123 rather than 0.33, reproducibly and at both scales, while the other flat
558
+ * backdrop (`mid-dark-solid`, linear 0.0595) reads 0.310 — so it is not a
559
+ * function of the backdrop's luminance, its structure, or the material's
560
+ * interior level, and no compositing model in either colour space produces both.
561
+ * The dark-scheme profile is a separate amplitude entirely (0.046…0.061 at a
562
+ * 44 px span — the dark material's shadow is nearly invisible) and lands as a
563
+ * profile patch, not as a branch here. Both are stated in the claims doc as the
564
+ * open question the cascade's fit inherits.
565
+ */
566
+ outerShadow: {
567
+ offsetPx: 7.95,
568
+ sigmaPx: 15.55,
569
+ spreadPx: 3.1,
570
+ occlusion: 0.285,
571
+ reducedTransparencyOcclusion: 0.7,
572
+ sizeGain: 0
573
+ },
277
574
  lightDirection: [-0.3714, -0.9285],
278
575
  sweepBandRadians: 0.55,
279
576
  glowRadiusCss: 44,
@@ -286,11 +583,19 @@ var ADAPTIVE_TINT_DARK = DEFAULT_MATERIAL_PROFILE.adaptiveTintDark;
286
583
  var ADAPTIVE_TINT_LIGHT = DEFAULT_MATERIAL_PROFILE.adaptiveTintLight;
287
584
  var ADAPTIVE_LUMINANCE_LOW = DEFAULT_MATERIAL_PROFILE.adaptiveLuminanceLow;
288
585
  var ADAPTIVE_LUMINANCE_HIGH = DEFAULT_MATERIAL_PROFILE.adaptiveLuminanceHigh;
289
- var LENS_SPAN_MIN = DEFAULT_MATERIAL_PROFILE.lensSpanMin;
290
- var LENS_SPAN_MAX = DEFAULT_MATERIAL_PROFILE.lensSpanMax;
586
+ var SIZE_SPAN_MIN = DEFAULT_MATERIAL_PROFILE.sizeSpanMin;
587
+ var SIZE_SPAN_MAX = DEFAULT_MATERIAL_PROFILE.sizeSpanMax;
291
588
  var LENS_SIZE_GAIN_MAX = DEFAULT_MATERIAL_PROFILE.lensSizeGainMax;
589
+ var SIZE_SCATTER_GAIN_MAX = DEFAULT_MATERIAL_PROFILE.sizeScatterGainMax;
590
+ var SIZE_OCCLUSION_GAIN = DEFAULT_MATERIAL_PROFILE.sizeOcclusionGain;
591
+ var SIZE_SHADOW_GAIN_MAX = DEFAULT_MATERIAL_PROFILE.sizeShadowGainMax;
292
592
  var LENS_BODY_LOD_PER_PX = DEFAULT_MATERIAL_PROFILE.lensBodyLodPerPx;
293
593
  var LENS_RIM_LOD_BIAS = DEFAULT_MATERIAL_PROFILE.lensRimLodBias;
594
+ var BACKDROP_TONE_MAX = DEFAULT_MATERIAL_PROFILE.backdropToneMax;
595
+ var BACKDROP_TONE_LOW = DEFAULT_MATERIAL_PROFILE.backdropToneLow;
596
+ var BACKDROP_TONE_HIGH = DEFAULT_MATERIAL_PROFILE.backdropToneHigh;
597
+ var BACKDROP_TONE_SIZE_BIAS = DEFAULT_MATERIAL_PROFILE.backdropToneSizeBias;
598
+ var OUTER_SHADOW = DEFAULT_MATERIAL_PROFILE.outerShadow;
294
599
  function withMaterialOverrides(base, patch) {
295
600
  const optics = {};
296
601
  for (const variant of MATERIAL_VARIANTS) {
@@ -307,15 +612,27 @@ function withMaterialOverrides(base, patch) {
307
612
  adaptiveLuminanceLow: patch.adaptiveLuminanceLow ?? base.adaptiveLuminanceLow,
308
613
  adaptiveLuminanceHigh: patch.adaptiveLuminanceHigh ?? base.adaptiveLuminanceHigh,
309
614
  refractionScale,
310
- lensSpanMin: patch.lensSpanMin ?? base.lensSpanMin,
311
- lensSpanMax: patch.lensSpanMax ?? base.lensSpanMax,
615
+ sizeSpanMin: patch.sizeSpanMin ?? base.sizeSpanMin,
616
+ sizeSpanMax: patch.sizeSpanMax ?? base.sizeSpanMax,
312
617
  lensSizeGainMax: patch.lensSizeGainMax ?? base.lensSizeGainMax,
618
+ sizeScatterGainMax: patch.sizeScatterGainMax ?? base.sizeScatterGainMax,
619
+ sizeOcclusionGain: patch.sizeOcclusionGain ?? base.sizeOcclusionGain,
620
+ sizeShadowGainMax: patch.sizeShadowGainMax ?? base.sizeShadowGainMax,
313
621
  lensBodyLodPerPx: patch.lensBodyLodPerPx ?? base.lensBodyLodPerPx,
314
622
  lensRimLodBias: patch.lensRimLodBias ?? base.lensRimLodBias,
315
623
  reducedTransparencyFrost: patch.reducedTransparencyFrost ?? base.reducedTransparencyFrost,
316
624
  increasedOcclusionLift: patch.increasedOcclusionLift ?? base.increasedOcclusionLift,
317
625
  strongBorderRim: { ...base.strongBorderRim, ...patch.strongBorderRim },
318
626
  reducedTintAdaptation: patch.reducedTintAdaptation ?? base.reducedTintAdaptation,
627
+ tintToneFloor: patch.tintToneFloor ?? base.tintToneFloor,
628
+ tintToneCeilMix: patch.tintToneCeilMix ?? base.tintToneCeilMix,
629
+ tintToneLow: patch.tintToneLow ?? base.tintToneLow,
630
+ tintToneHigh: patch.tintToneHigh ?? base.tintToneHigh,
631
+ backdropToneMax: patch.backdropToneMax ?? base.backdropToneMax,
632
+ backdropToneLow: patch.backdropToneLow ?? base.backdropToneLow,
633
+ backdropToneHigh: patch.backdropToneHigh ?? base.backdropToneHigh,
634
+ backdropToneSizeBias: patch.backdropToneSizeBias ?? base.backdropToneSizeBias,
635
+ outerShadow: { ...base.outerShadow, ...patch.outerShadow },
319
636
  lightDirection: patch.lightDirection ?? base.lightDirection,
320
637
  sweepBandRadians: patch.sweepBandRadians ?? base.sweepBandRadians,
321
638
  glowRadiusCss: patch.glowRadiusCss ?? base.glowRadiusCss,
@@ -350,14 +667,147 @@ function adaptationStrength(policy, analysisExact, profile = DEFAULT_MATERIAL_PR
350
667
  return 0;
351
668
  }
352
669
  }
670
+ function tintToneAdaptation(policy, profile = DEFAULT_MATERIAL_PROFILE) {
671
+ switch (policy.ambientTint) {
672
+ case "nominal":
673
+ return 1;
674
+ case "reduced":
675
+ return profile.reducedTintAdaptation;
676
+ case "none":
677
+ return 0;
678
+ }
679
+ }
680
+ function tintTone(seed, backdropLuminance, toneAdaptation, profile = DEFAULT_MATERIAL_PROFILE) {
681
+ const t = smoothstep(profile.tintToneLow, profile.tintToneHigh, backdropLuminance);
682
+ const k = Math.min(1, Math.max(0, toneAdaptation));
683
+ const channel = (index) => {
684
+ const s = seed[index];
685
+ const low = s * profile.tintToneFloor;
686
+ const high = s + (1 - s) * profile.tintToneCeilMix;
687
+ return s + (low + (high - low) * t - s) * k;
688
+ };
689
+ return [channel(0), channel(1), channel(2)];
690
+ }
691
+ function tintedTintColour(neutral, tint, backdropLuminance, toneAdaptation, profile = DEFAULT_MATERIAL_PROFILE) {
692
+ if (tint === void 0)
693
+ return neutral;
694
+ const tone = tintTone(tint.color, backdropLuminance, toneAdaptation, profile);
695
+ const k = Math.min(1, Math.max(0, tint.strength));
696
+ return [
697
+ neutral[0] + (tone[0] - neutral[0]) * k,
698
+ neutral[1] + (tone[1] - neutral[1]) * k,
699
+ neutral[2] + (tone[2] - neutral[2]) * k
700
+ ];
701
+ }
702
+ function backdropToneAdaptation(backdropLuminance, thickness, profile = DEFAULT_MATERIAL_PROFILE) {
703
+ const x = backdropLuminance + profile.backdropToneSizeBias * Math.min(1, Math.max(0, thickness));
704
+ const span = Math.max(profile.backdropToneHigh - profile.backdropToneLow, 1e-6);
705
+ const t = Math.min(1, Math.max(0, (x - profile.backdropToneLow) / span));
706
+ return Math.min(1, Math.max(0, profile.backdropToneMax)) * (1 - t * t * (3 - 2 * t));
707
+ }
708
+ function backdropToneUnderPolicy(policy, profile = DEFAULT_MATERIAL_PROFILE) {
709
+ return tintToneAdaptation(policy, profile) * profile.refractionScale[accessibilityRefractionCap(policy)];
710
+ }
711
+ function backdropToneSizeBiasUnderPolicy(policy, profile = DEFAULT_MATERIAL_PROFILE) {
712
+ const cap = profile.refractionScale[accessibilityRefractionCap(policy)];
713
+ return cap <= 0 ? 0 : profile.backdropToneSizeBias / cap;
714
+ }
715
+ function adaptedTintColour(neutral, backdrop, adaptation, tintAlpha) {
716
+ const k = Math.min(1, Math.max(0, adaptation));
717
+ if (k === 0)
718
+ return neutral;
719
+ const alpha = adaptedTintAlpha(tintAlpha, k);
720
+ if (alpha <= 0)
721
+ return neutral;
722
+ const wNeutral = (1 - k) * tintAlpha;
723
+ return [
724
+ (neutral[0] * wNeutral + backdrop[0] * k) / alpha,
725
+ (neutral[1] * wNeutral + backdrop[1] * k) / alpha,
726
+ (neutral[2] * wNeutral + backdrop[2] * k) / alpha
727
+ ];
728
+ }
729
+ function adaptedTintAlpha(tintAlpha, adaptation) {
730
+ const k = Math.min(1, Math.max(0, adaptation));
731
+ return tintAlpha + k * (1 - tintAlpha);
732
+ }
353
733
  var smoothstep = (edge0, edge1, x) => {
354
734
  if (edge1 <= edge0)
355
735
  return x < edge0 ? 0 : 1;
356
736
  const t = Math.min(1, Math.max(0, (x - edge0) / (edge1 - edge0)));
357
737
  return t * t * (3 - 2 * t);
358
738
  };
739
+ function sizeThickness(spanPx, profile = DEFAULT_MATERIAL_PROFILE) {
740
+ return smoothstep(profile.sizeSpanMin, profile.sizeSpanMax, spanPx);
741
+ }
742
+ function sizeThicknessUnderPolicy(spanPx, policy, profile = DEFAULT_MATERIAL_PROFILE) {
743
+ return sizeThickness(spanPx, profile) * profile.refractionScale[accessibilityRefractionCap(policy)];
744
+ }
359
745
  function lensSizeGain(spanPx, profile = DEFAULT_MATERIAL_PROFILE) {
360
- return 1 + (profile.lensSizeGainMax - 1) * smoothstep(profile.lensSpanMin, profile.lensSpanMax, spanPx);
746
+ return 1 + (profile.lensSizeGainMax - 1) * sizeThickness(spanPx, profile);
747
+ }
748
+ function lensSizeGainFromThickness(thickness, profile = DEFAULT_MATERIAL_PROFILE) {
749
+ return 1 + (profile.lensSizeGainMax - 1) * thickness;
750
+ }
751
+ function sizeScatterSigma(sigmaPx, spanPx, profile = DEFAULT_MATERIAL_PROFILE) {
752
+ return sizeScatterSigmaAt(sigmaPx, sizeThickness(spanPx, profile), profile);
753
+ }
754
+ function sizeScatterSigmaAt(sigmaPx, thickness, profile = DEFAULT_MATERIAL_PROFILE) {
755
+ return sigmaPx * (1 + (profile.sizeScatterGainMax - 1) * thickness);
756
+ }
757
+ function sizeOcclusionAlpha(alpha, spanPx, profile = DEFAULT_MATERIAL_PROFILE) {
758
+ return sizeOcclusionAlphaAt(alpha, sizeThickness(spanPx, profile), profile);
759
+ }
760
+ function sizeOcclusionAlphaAt(alpha, thickness, profile = DEFAULT_MATERIAL_PROFILE) {
761
+ return Math.min(1, alpha + profile.sizeOcclusionGain * thickness * (1 - alpha));
762
+ }
763
+ function sizeShadowDepth(shadowDepth, spanPx, profile = DEFAULT_MATERIAL_PROFILE) {
764
+ return sizeShadowDepthAt(shadowDepth, sizeThickness(spanPx, profile), profile);
765
+ }
766
+ function sizeShadowDepthAt(shadowDepth, thickness, profile = DEFAULT_MATERIAL_PROFILE) {
767
+ return shadowDepth * (1 + (profile.sizeShadowGainMax - 1) * thickness);
768
+ }
769
+ function sizeOuterShadowOcclusion(occlusion, spanPx, profile = DEFAULT_MATERIAL_PROFILE) {
770
+ return sizeOuterShadowOcclusionAt(occlusion, sizeThickness(spanPx, profile), profile);
771
+ }
772
+ function sizeOuterShadowOcclusionAt(occlusion, thickness, profile = DEFAULT_MATERIAL_PROFILE) {
773
+ return Math.min(1, occlusion + profile.outerShadow.sizeGain * thickness * (1 - occlusion));
774
+ }
775
+ function outerShadowUnderPolicy(policy, profile = DEFAULT_MATERIAL_PROFILE) {
776
+ const shadow = profile.outerShadow;
777
+ if (policy.glass === "none" || policy.frost === "none")
778
+ return { ...shadow, occlusion: 0 };
779
+ if (policy.frost === "increased") {
780
+ return { ...shadow, occlusion: shadow.occlusion * shadow.reducedTransparencyOcclusion };
781
+ }
782
+ return shadow;
783
+ }
784
+ var SRGB_ENCODING_EXPONENT = 2.4;
785
+ function outerShadowFalloff(signedDistancePx, sigmaPx) {
786
+ const x = -signedDistancePx / Math.max(sigmaPx, 1e-4);
787
+ return 0.5 * (1 + Math.tanh(0.7978845608028654 * (x + 0.044715 * x * x * x)));
788
+ }
789
+ function outerShadowAlpha(occlusion) {
790
+ const occ = Math.min(1, Math.max(0, occlusion));
791
+ return 1 - Math.pow(1 - occ, 1 / SRGB_ENCODING_EXPONENT);
792
+ }
793
+ function outerShadowReachPx(shadow) {
794
+ const alpha = outerShadowAlpha(shadow.occlusion);
795
+ if (!(alpha > 0))
796
+ return 0;
797
+ const cutoff = 1 / 255 / alpha;
798
+ if (cutoff >= 1)
799
+ return 0;
800
+ const sigma = Math.max(shadow.sigmaPx, 1e-4);
801
+ let lo = -(8 * sigma + Math.abs(shadow.offsetPx) + Math.abs(shadow.spreadPx));
802
+ let hi = 8 * sigma;
803
+ for (let i = 0; i < 48; i += 1) {
804
+ const mid = (lo + hi) / 2;
805
+ if (outerShadowFalloff(mid, sigma) > cutoff)
806
+ lo = mid;
807
+ else
808
+ hi = mid;
809
+ }
810
+ return Math.max(0, hi + Math.abs(shadow.offsetPx) + shadow.spreadPx);
361
811
  }
362
812
  function lensDepthPx(thicknessPx, spanPx, profile = DEFAULT_MATERIAL_PROFILE) {
363
813
  const gain = lensSizeGain(spanPx, profile);
@@ -1252,7 +1702,7 @@ var IDLE_CHANNELS = {
1252
1702
  };
1253
1703
 
1254
1704
  // ../renderer-webgpu/dist/instances.js
1255
- var INSTANCE_FLOATS = 16;
1705
+ var INSTANCE_FLOATS = 18;
1256
1706
  var INSTANCE_BYTES = INSTANCE_FLOATS * 4;
1257
1707
  var channelsOf = (input) => ({
1258
1708
  ...IDLE_CHANNELS,
@@ -1264,7 +1714,7 @@ function compressedChannels(shape, press) {
1264
1714
  const scale = 1 - Math.min(1, Math.max(0, press)) * DEFAULT_MOTION_PROFILE.pressCompressionScale;
1265
1715
  return { ...shape, size: [shape.size[0] * scale, shape.size[1] * scale] };
1266
1716
  }
1267
- function resolveSurfaces(group, family, profile = DEFAULT_MATERIAL_PROFILE) {
1717
+ function resolveSurfaces(group, family, profile = DEFAULT_MATERIAL_PROFILE, policy = NOMINAL_MATERIAL_POLICY) {
1268
1718
  const byId = /* @__PURE__ */ new Map();
1269
1719
  for (const surface of group.surfaces) {
1270
1720
  if (byId.has(surface.nodeId)) {
@@ -1308,6 +1758,7 @@ function resolveSurfaces(group, family, profile = DEFAULT_MATERIAL_PROFILE) {
1308
1758
  const fieldSource = surface.concentricOf === void 0 ? shape : resolveShapeOf(byId.get(surface.concentricOf.nodeId));
1309
1759
  const inset = surface.concentricOf?.inset ?? 0;
1310
1760
  const spanPx = Math.min(shape.channels.size[0], shape.channels.size[1]);
1761
+ const thickness = sizeThicknessUnderPolicy(spanPx, policy, profile);
1311
1762
  return {
1312
1763
  nodeId: surface.nodeId,
1313
1764
  shape,
@@ -1316,11 +1767,13 @@ function resolveSurfaces(group, family, profile = DEFAULT_MATERIAL_PROFILE) {
1316
1767
  centre: [fieldSource.channels.center[0], fieldSource.channels.center[1]],
1317
1768
  channels,
1318
1769
  spanPx,
1319
- lensDepthPx: lensDepthPx(shape.channels.thickness, spanPx, profile)
1770
+ lensDepthPx: Math.min(Math.max(shape.channels.thickness, 0) * lensSizeGainFromThickness(thickness, profile), spanPx * 0.5),
1771
+ sizeThickness: thickness,
1772
+ tintStrength: Math.min(1, Math.max(0, surface.tint?.strength ?? 0))
1320
1773
  };
1321
1774
  });
1322
1775
  }
1323
- function groupFieldRect(surfaces, union = DEFAULT_GROUP_UNION, rimWidthPx = 2) {
1776
+ function groupFieldRect(surfaces, union = DEFAULT_GROUP_UNION, rimWidthPx = 2, outerShadowReachPx2 = 0) {
1324
1777
  if (surfaces.length === 0)
1325
1778
  return { x: 0, y: 0, width: 0, height: 0 };
1326
1779
  let minX = Number.POSITIVE_INFINITY;
@@ -1335,7 +1788,7 @@ function groupFieldRect(surfaces, union = DEFAULT_GROUP_UNION, rimWidthPx = 2) {
1335
1788
  maxX = Math.max(maxX, cx + w / 2);
1336
1789
  maxY = Math.max(maxY, cy + h / 2);
1337
1790
  }
1338
- const pad = rimWidthPx + union.maxBulge + 1;
1791
+ const pad = Math.max(rimWidthPx + union.maxBulge + 1, Math.max(outerShadowReachPx2, 0));
1339
1792
  return {
1340
1793
  x: minX - pad,
1341
1794
  y: minY - pad,
@@ -1380,7 +1833,9 @@ function packInstances(surfaces, origin, into) {
1380
1833
  data[o + 12] = s.channels.press;
1381
1834
  data[o + 13] = s.channels.glow;
1382
1835
  data[o + 14] = s.lensDepthPx * Math.min(1, Math.max(0, s.channels.lensStrength));
1383
- data[o + 15] = 0;
1836
+ data[o + 15] = s.tintStrength;
1837
+ data[o + 16] = s.sizeThickness;
1838
+ data[o + 17] = 0;
1384
1839
  }
1385
1840
  return { data, count: surfaces.length };
1386
1841
  }
@@ -1656,7 +2111,9 @@ var WGSL_INSTANCE_STRUCT = `struct Instance {
1656
2111
  press : f32, // 48 interaction channel value, 0..1
1657
2112
  glow : f32, // 52 interaction channel value, 0..1
1658
2113
  lensDepth : f32, // 56 CPU-resolved lens depth in CSS px (material.ts), already scaled by the lensStrength channel
1659
- _pad : f32, // 60
2114
+ tintK : f32, // 60 author tint strength, 0..1 \u2014 the seed itself is a group uniform
2115
+ sizeK : f32, // 64 the size law's thickness factor, 0..1 \u2014 material.ts's sizeThickness(span)
2116
+ _pad : f32, // 68 stride padding: vec2f aligns the struct to 8, so 72 is the next legal size
1660
2117
  };`;
1661
2118
  var WGSL_FIELD_SAMPLE = `struct FieldSample {
1662
2119
  d : f32,
@@ -1821,7 +2278,29 @@ var WGSL_FIELD_PASS = `struct FieldUniforms {
1821
2278
  // field but its members are not one size, and carrying them per pixel is what
1822
2279
  // lets a 40 px button and a 320 px platter share a field pass and still lens by
1823
2280
  // their own depth \u2014 parent acceptance #2 inside a GlassEffectContainer.
1824
- // aux = (lensDepthPx, glow, thicknessPx, press)
2281
+ // aux = (lensDepthPx, glow, sizeK, tintStrength)
2282
+ //
2283
+ // The third and fourth slots both changed hands, and neither of the values they
2284
+ // used to carry was ever read by a fragment stage. 'thick' left because
2285
+ // 'lensDepth' is the thickness times the size gain, so the raw number told the
2286
+ // shader nothing the depth had not already said; 'press' left because press
2287
+ // compression is resolved on the CPU, as a transform on the surface's own size
2288
+ // (instances.ts). Both are still packed into the instance buffer, unchanged, for
2289
+ // a future consumer that wants them per pixel; they simply no longer ride here.
2290
+ //
2291
+ // What replaced them are the two quantities that genuinely have to be per pixel,
2292
+ // because a group is one field pass and one optics pass while its members are
2293
+ // not one thing:
2294
+ //
2295
+ // 'sizeK' \u2014 the size law's thickness factor (W2). A 40 px button and a
2296
+ // 320 px platter in one container lens, scatter, occlude and
2297
+ // shadow by their own size.
2298
+ // 'tintStrength' \u2014 the author tint's strength (W3). One emphasised control
2299
+ // among plain ones is the composition Apple's guidance
2300
+ // describes; the seed itself is a group uniform.
2301
+ //
2302
+ // They compose rather than compete: the size law moves what the material does to
2303
+ // the backdrop, the tint moves what colour it does it in.
1825
2304
 
1826
2305
  @group(0) @binding(0) var<uniform> fu : FieldUniforms;
1827
2306
  @group(0) @binding(1) var<storage, read> instances : array<Instance>;
@@ -1842,7 +2321,7 @@ fn eval_instance(i : u32, p : vec2f) -> Member {
1842
2321
  var m : Member;
1843
2322
  m.d = f.d + s.inset;
1844
2323
  m.g = f.g;
1845
- m.aux = vec4f(s.lensDepth, s.glow, s.thick, s.press);
2324
+ m.aux = vec4f(s.lensDepth, s.glow, s.sizeK, s.tintK);
1846
2325
  return m;
1847
2326
  }
1848
2327
 
@@ -1925,6 +2404,21 @@ var WGSL_HIGHLIGHT_PASS = `struct HighlightUniforms {
1925
2404
  colour : vec4f,
1926
2405
  /// fieldSize.xy, fieldUpsampled (z), unused (w)
1927
2406
  flags : vec4f,
2407
+ /// backdrop tone adaptation (W7), exactly as the optics pass takes it:
2408
+ /// backdropToneLow, backdropToneHigh, the size bias already divided by the
2409
+ /// accessibility refraction cap, and the resolved strength \u2014 zero where there
2410
+ /// is no measured backdrop tone, which stands the fade down
2411
+ toneAdapt : vec4f,
2412
+ /// the backdrop source's own average luminance (x); yzw unused
2413
+ toneLevel : vec4f,
2414
+ /// this pass's uv into the FIELD texture's uv: scale (xy), offset (zw).
2415
+ ///
2416
+ /// The identity for a pass whose scissor is the field's own rect, and not the
2417
+ /// identity since W8: the outer shadow made the field rect much larger than
2418
+ /// anything this pass draws in, so the highlight is scoped back to the rect it
2419
+ /// had before and reads the field through this remap instead. Derived from the
2420
+ /// two rects by highlightPass in passes.ts.
2421
+ fieldFit : vec4f,
1928
2422
  };
1929
2423
 
1930
2424
  @group(0) @binding(0) var<uniform> hu : HighlightUniforms;
@@ -1946,13 +2440,14 @@ fn fs_highlight(in : FullscreenOut) -> @location(0) vec4f {
1946
2440
  // Exact load nominally; filtered when the governor's resolution knob had the
1947
2441
  // field rasterised below the group's rect. The sweep rides the rim, which is
1948
2442
  // the one place a nearest read of a coarse field would show its grid.
2443
+ let fieldUv = in.uv * hu.fieldFit.xy + hu.fieldFit.zw;
1949
2444
  var field : vec4f;
1950
2445
  var aux : vec4f;
1951
2446
  if (hu.flags.z > 0.5) {
1952
- field = textureSampleLevel(fieldTexture, fieldSampler, in.uv, 0.0);
1953
- aux = textureSampleLevel(auxTexture, fieldSampler, in.uv, 0.0);
2447
+ field = textureSampleLevel(fieldTexture, fieldSampler, fieldUv, 0.0);
2448
+ aux = textureSampleLevel(auxTexture, fieldSampler, fieldUv, 0.0);
1954
2449
  } else {
1955
- let texel = vec2i(in.uv * hu.flags.xy);
2450
+ let texel = vec2i(fieldUv * hu.flags.xy);
1956
2451
  field = textureLoad(fieldTexture, texel, 0);
1957
2452
  aux = textureLoad(auxTexture, texel, 0);
1958
2453
  }
@@ -1981,7 +2476,32 @@ fn fs_highlight(in : FullscreenOut) -> @location(0) vec4f {
1981
2476
  // whose members glow independently does not need a pass each.
1982
2477
  let press = radial * radial * hu.glow.w * aux.y;
1983
2478
 
1984
- let intensity = clamp(sweep + press, 0.0, 1.0) * coverage;
2479
+ /*
2480
+ * Backdrop tone adaptation (W7) fades this pass with the rest of the surface's
2481
+ * own appearance. A highlight is the material catching light, and a material
2482
+ * that has taken its backdrop's tone is not there to catch any \u2014 the settled
2483
+ * reference's capsule over a near-black backdrop is byte-identical to that
2484
+ * background, sweep included.
2485
+ *
2486
+ * The curve is the optics pass's, evaluated the same way off the same uniforms
2487
+ * and the same per-pixel 'sizeK', because two statements of one curve is how
2488
+ * they drift. This pass would otherwise have gone on drawing a bright crescent
2489
+ * around a surface the optics pass had made invisible \u2014 measured at 60 pixels
2490
+ * past 2/255, peaking at 97/255, on exactly that cell.
2491
+ */
2492
+ let sizeK = clamp(aux.z, 0.0, 1.0);
2493
+ var toneAdapt = 0.0;
2494
+ if (hu.toneAdapt.w > 0.0) {
2495
+ let toneX = hu.toneLevel.x + hu.toneAdapt.z * sizeK;
2496
+ let toneT = clamp(
2497
+ (toneX - hu.toneAdapt.x) / max(hu.toneAdapt.y - hu.toneAdapt.x, 1e-6),
2498
+ 0.0,
2499
+ 1.0,
2500
+ );
2501
+ toneAdapt = clamp(hu.toneAdapt.w, 0.0, 1.0) * (1.0 - toneT * toneT * (3.0 - 2.0 * toneT));
2502
+ }
2503
+
2504
+ let intensity = clamp(sweep + press, 0.0, 1.0) * coverage * (1.0 - toneAdapt);
1985
2505
  if (intensity <= 0.0) {
1986
2506
  return vec4f(0.0);
1987
2507
  }
@@ -2000,12 +2520,40 @@ var WGSL_OPTICS_PASS = `struct OpticsUniforms {
2000
2520
  tint : vec4f,
2001
2521
  /// adapted tint colour, linear light (xyz), adaptation strength (w)
2002
2522
  adapt : vec4f,
2523
+ /// author tint seed, linear light (xyz), tone adaptation under the contrast regime (w)
2524
+ seed : vec4f,
2525
+ /// tintToneFloor, tintToneCeilMix, tintToneLow, tintToneHigh
2526
+ tone : vec4f,
2003
2527
  /// rimWidthPx, rimAlpha, specularPower, specularGain
2004
2528
  rim : vec4f,
2005
2529
  /// light direction, unit (xy), shadowDepth (z), shadowAlpha (w)
2006
2530
  light : vec4f,
2007
2531
  /// hasBackdrop, fieldSize.xy, fieldUpsampled
2008
2532
  flags : vec4f,
2533
+ /// the size law's gains (W2): scatterGainMax, occlusionGain, shadowGainMax,
2534
+ /// bodyChainLod \u2014 the last being the chain level whose blur already matches the
2535
+ /// body texture, which is what the scattering term measures its octaves from
2536
+ size : vec4f,
2537
+ /// backdrop tone adaptation (W7): backdropToneLow, backdropToneHigh, the size
2538
+ /// bias ALREADY divided by the accessibility refraction cap (so that it may be
2539
+ /// multiplied by the policy-folded 'sizeK' below and still mean the geometric
2540
+ /// thickness), and the strength the accessibility policy resolved \u2014 zero where
2541
+ /// the host could not measure a backdrop tone, which stands the axis down
2542
+ toneAdapt : vec4f,
2543
+ /// the backdrop source's own average colour, linear (xyz), and its luminance (w)
2544
+ toneColour : vec4f,
2545
+ /// the outer shadow (W8): its compositing-space alpha before the size law (x),
2546
+ /// the blur's sigma in group-local CSS px (y), the silhouette's outward spread
2547
+ /// in the same units (z), and the downward offset expressed in field-texture UV
2548
+ /// (w) \u2014 a UV so the offset silhouette is one texture read rather than a
2549
+ /// gradient extrapolation, which is exact only on a straight edge and a capsule
2550
+ /// is mostly not one
2551
+ shadow : vec4f,
2552
+ /// the outer shadow's size-law gain (x), read against the casting surface's own
2553
+ /// thickness, and the field rect's height in CSS px (y) \u2014 the conversion the
2554
+ /// shadow's shift needs when it lands outside the texture; z and w are the
2555
+ /// padding a uniform's vec4 alignment requires
2556
+ shadowSize : vec4f,
2009
2557
  };
2010
2558
 
2011
2559
  @group(0) @binding(0) var<uniform> ou : OpticsUniforms;
@@ -2024,6 +2572,80 @@ fn rim_weight(d : f32, width : f32) -> f32 {
2024
2572
  return t * t;
2025
2573
  }
2026
2574
 
2575
+ /// The Gaussian CDF the outer shadow's edge falls off by: 1 deep inside the
2576
+ /// shadow's silhouette, 0.5 exactly on it, 0 far outside. The tanh form of the
2577
+ /// normal CDF \u2014 WGSL has no erf, and this is within 1.8e-4 of it everywhere,
2578
+ /// which is 0.015 of one 8-bit code at the shipped occlusion. Mirrors
2579
+ /// material.ts's 'outerShadowFalloff' term for term.
2580
+ fn outer_shadow_falloff(signedDistance : f32, sigma : f32) -> f32 {
2581
+ let x = -signedDistance / max(sigma, 1e-4);
2582
+ return 0.5 * (1.0 + tanh(0.7978845608028654 * (x + 0.044715 * x * x * x)));
2583
+ }
2584
+
2585
+ /// The outer shadow's alpha at this pixel (W8).
2586
+ ///
2587
+ /// The shadow is the group's OWN field, translated down by 'shadow.w' in field
2588
+ /// UV and outset by 'shadow.z', then blurred. Reading the offset silhouette from
2589
+ /// the field texture rather than extrapolating the local distance along the
2590
+ /// normal is what keeps a corner a corner: the first-order estimate is exact only
2591
+ /// on a straight edge, and a capsule is mostly not one.
2592
+ ///
2593
+ /// The value is an ALPHA on pure black, so what it composites to is the backdrop
2594
+ /// times '1 - alpha' \u2014 multiplicative occlusion, and exactly zero over black,
2595
+ /// with no branch for it.
2596
+ fn outer_shadow_alpha(uv : vec2f, upsampled : f32, fieldSize : vec2f) -> f32 {
2597
+ if (ou.shadow.x <= 0.0) {
2598
+ return 0.0;
2599
+ }
2600
+ /*
2601
+ * The shift can leave the field texture, and clamping alone reads a lie there.
2602
+ *
2603
+ * Two ways it happens, and the second is not an edge case: the rect is clipped
2604
+ * to the canvas, so a surface within the shadow's reach of the viewport's TOP
2605
+ * has no rows above it to shift into; and even unclipped, the rect's pad is the
2606
+ * shadow's reach, so the topmost band of every group needs rows a further
2607
+ * 'offset' above that. Clamped, both repeat the edge texel \u2014 a distance that is
2608
+ * too SMALL, which reads as a flat, too-dark falloff exactly where the shadow
2609
+ * should be fading out, and diverges from the CSS tier, which has no texture to
2610
+ * run out of.
2611
+ *
2612
+ * Reconstructed instead of clamped. A signed distance field is 1-Lipschitz, and
2613
+ * in the region this happens in \u2014 directly above the surface \u2014 moving away from
2614
+ * it increases the distance by exactly the displacement. So adding back the
2615
+ * distance that was clamped off is exact there, and past the surface's corners
2616
+ * it over-estimates, which errs toward LESS shadow rather than more.
2617
+ */
2618
+ let shiftedY = uv.y - ou.shadow.w;
2619
+ let clampedOffCss =
2620
+ (max(0.0, -shiftedY) + max(0.0, shiftedY - 1.0)) * ou.shadowSize.y;
2621
+ let shadowUv = clamp(vec2f(uv.x, shiftedY), vec2f(0.0), vec2f(1.0));
2622
+ var shadowField : vec4f;
2623
+ var shadowAux : vec4f;
2624
+ if (upsampled > 0.5) {
2625
+ shadowField = textureSampleLevel(fieldTexture, fieldSampler, shadowUv, 0.0);
2626
+ shadowAux = textureSampleLevel(auxTexture, fieldSampler, shadowUv, 0.0);
2627
+ } else {
2628
+ let texel = clamp(vec2i(shadowUv * fieldSize), vec2i(0), vec2i(fieldSize) - vec2i(1));
2629
+ shadowField = textureLoad(fieldTexture, texel, 0);
2630
+ shadowAux = textureLoad(auxTexture, texel, 0);
2631
+ }
2632
+
2633
+ // The size law reaches the amplitude and nothing else \u2014 the reference's three
2634
+ // lengths are span-invariant across 32\u2026160 px. The gain rides the thickness of
2635
+ // the surface that CAST the shadow, which is the one read at the offset
2636
+ // position rather than the one under the pixel being shaded.
2637
+ //
2638
+ // Applied on '1 - alpha' rather than on the alpha: the gain is a fraction of
2639
+ // the remaining transparency in LINEAR light ('sizeOuterShadowOcclusionAt'),
2640
+ // and (1 - occ) is exactly what raising to 1/2.4 turns into (1 - alpha), so the
2641
+ // two forms are the same law and neither needs the other's space.
2642
+ let sizeK = clamp(shadowAux.z, 0.0, 1.0);
2643
+ let sizeFold = pow(max(1.0 - ou.shadowSize.x * sizeK, 0.0), 1.0 / 2.4);
2644
+ let alpha = 1.0 - (1.0 - ou.shadow.x) * sizeFold;
2645
+
2646
+ return alpha * outer_shadow_falloff(shadowField.x + clampedOffCss - ou.shadow.z, ou.shadow.y);
2647
+ }
2648
+
2027
2649
  @fragment
2028
2650
  fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
2029
2651
  // Nominally the field is one texel per device pixel, so the read is an exact
@@ -2045,8 +2667,19 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
2045
2667
  let d = field.x;
2046
2668
  let normal = field.yz;
2047
2669
  let coverage = field.w;
2670
+
2671
+ /*
2672
+ * The outer shadow (W8) sits UNDER the material, so it is resolved before the
2673
+ * body and carried into the output's alpha rather than into its colour: the
2674
+ * pass writes premultiplied black there, and premultiplied black over the page
2675
+ * IS the multiplication. Outside the contour that is the whole of what this
2676
+ * pixel is; inside it the surface covers the shadow, exactly as a 'box-shadow'
2677
+ * is clipped out of its own border box, and the two meet across the coverage
2678
+ * ramp with no seam because it is one expression.
2679
+ */
2680
+ let shadowAlpha = outer_shadow_alpha(in.uv, ou.flags.w, ou.flags.yz);
2048
2681
  if (coverage <= 0.0) {
2049
- return vec4f(0.0);
2682
+ return vec4f(0.0, 0.0, 0.0, shadowAlpha);
2050
2683
  }
2051
2684
 
2052
2685
  let viewport01 = in.position.xy / ou.screen.xy;
@@ -2054,6 +2687,10 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
2054
2687
 
2055
2688
  // Per-pixel, unioned through the field pass. See the module note.
2056
2689
  let lensDepth = max(aux.x, 1e-4);
2690
+ // The size law's thickness factor for whichever surface owns this pixel, 0..1.
2691
+ // Zero on anything at or below the profile's 'sizeSpanMin', and every term
2692
+ // below multiplies by it \u2014 so a small control takes the pre-law path exactly.
2693
+ let sizeK = clamp(aux.z, 0.0, 1.0);
2057
2694
  // '-d' is depth inside the surface, so the profile runs 1 at the contour to 0
2058
2695
  // at 'lensDepth' inward, and the square makes the falloff read as curvature
2059
2696
  // rather than as a linear ramp.
@@ -2069,36 +2706,176 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
2069
2706
  let bodyLod = clamp(lensDepth * ou.lens.y, 0.0, ou.lens.w);
2070
2707
  let lod = clamp(bodyLod - ou.lens.z * profile, 0.0, ou.lens.w);
2071
2708
 
2709
+ /*
2710
+ * The scattering facet of the size law: "a softer scattering of light".
2711
+ *
2712
+ * The body texture is one blur for the whole backdrop source \u2014 it is built once
2713
+ * per source per frame, so it cannot be per-surface. The chain beside it can:
2714
+ * 'size.w' is the chain level whose blur already matches that body, so
2715
+ * 'size.w + log2(scatterGain)' is the level whose blur is 'scatterGain' times
2716
+ * wider, and lerping the body toward it by 'sizeK' widens the kernel with the
2717
+ * surface. At sizeK = 0 the weight is zero and the body sample stands alone,
2718
+ * which is what makes the whole facet inert on a small control.
2719
+ */
2720
+ let scatterGain = 1.0 + (ou.size.x - 1.0) * sizeK;
2721
+ let scatterLod = clamp(ou.size.w + log2(max(scatterGain, 1e-4)), 0.0, ou.lens.w);
2722
+
2072
2723
  var backdrop = vec3f(0.0);
2073
2724
  if (ou.flags.x > 0.5) {
2074
2725
  let lensSample = textureSampleLevel(backdropChain, backdropSampler, refractedUv, lod);
2075
2726
  let bodySample = textureSampleLevel(backdropBody, backdropSampler, straightUv, 0.0);
2727
+ let scatterSample = textureSampleLevel(backdropChain, backdropSampler, straightUv, scatterLod);
2076
2728
  // Premultiplied linear in, straight colour out: the material composites over
2077
2729
  // whatever is behind it, so a partially transparent backdrop must not darken
2078
2730
  // the glass.
2079
2731
  let lensColour = lensSample.rgb / max(lensSample.a, 1e-6);
2080
- let bodyColour = bodySample.rgb / max(bodySample.a, 1e-6);
2732
+ let scatterColour = scatterSample.rgb / max(scatterSample.a, 1e-6);
2733
+ let bodyColour = mix(bodySample.rgb / max(bodySample.a, 1e-6), scatterColour, sizeK);
2081
2734
  backdrop = mix(bodyColour, lensColour, profile);
2082
2735
  }
2083
2736
 
2084
2737
  // Adaptive tint. 'adapt.w' is the strength the accessibility policy and the
2085
2738
  // group's analysis quality already agreed on; at 0 the fixed tint stands, which
2086
2739
  // is what a 'hint' or 'none' group gets.
2087
- let tintColour = mix(ou.tint.rgb, ou.adapt.rgb, ou.adapt.w);
2088
- var colour = mix(backdrop, tintColour, ou.tint.w);
2740
+ let neutral = mix(ou.tint.rgb, ou.adapt.rgb, ou.adapt.w);
2741
+
2742
+ let backdropLuma = dot(backdrop, vec3f(0.2126, 0.7152, 0.0722));
2743
+
2744
+ /*
2745
+ * Backdrop tone adaptation (W7) \u2014 step two of the composition contract, between
2746
+ * the colour scheme's neutral and the author's tint.
2747
+ *
2748
+ * Read against 'toneColour': the backdrop SOURCE's own average, measured by the
2749
+ * host from the pixels it supplied and handed to both tiers as one number. Not a
2750
+ * per-pixel sample, and that is the load-bearing choice \u2014 see
2751
+ * 'GroupRenderInput.backdropTone' for the cross-tier measurement that settled
2752
+ * it, and note that the reference agrees: its capsule over a sparse bright grid
2753
+ * is a flat body rather than a window onto the grid.
2754
+ *
2755
+ * 'sizeK' is still per pixel, so a container holding a small control and a large
2756
+ * platter adapts each of them by its own thickness out of one pass.
2757
+ *
2758
+ * Strength is zero where the host measured no tone, and the whole axis stands
2759
+ * down where there is no backdrop at all rather than reading the zero vector as
2760
+ * a black backdrop and dissolving the surface into nothing.
2761
+ *
2762
+ * Written out instead of calling smoothstep() so that a profile patched with
2763
+ * low >= high degrades to a step rather than to NaN.
2764
+ */
2765
+ var toneAdapt = 0.0;
2766
+ if (ou.flags.x > 0.5 && ou.toneAdapt.w > 0.0) {
2767
+ let toneX = ou.toneColour.w + ou.toneAdapt.z * sizeK;
2768
+ let toneT = clamp(
2769
+ (toneX - ou.toneAdapt.x) / max(ou.toneAdapt.y - ou.toneAdapt.x, 1e-6),
2770
+ 0.0,
2771
+ 1.0,
2772
+ );
2773
+ toneAdapt = clamp(ou.toneAdapt.w, 0.0, 1.0) * (1.0 - toneT * toneT * (3.0 - 2.0 * toneT));
2774
+ }
2775
+ /*
2776
+ * Both the colour and the alpha move, together and not separately. What the
2777
+ * adaptation means is that the INTERIOR converges on the backdrop's tone \u2014
2778
+ * mix(interior, tone, k) \u2014 and this is the (colour, alpha) pair that composites
2779
+ * to exactly that. Lerping the two independently makes a partially adapted
2780
+ * surface lighter than it started (more opaque toward a tint still mostly
2781
+ * neutral), which the 96 px cells caught at once: interior 0.4545 \u2192 0.5179
2782
+ * against a reference of 0.4542. The alpha half is not a colour axis reaching
2783
+ * the occlusion axis \u2014 an adapting material stops transmitting, which is what
2784
+ * the reference's flat body over the impulse grid is. See 'adaptedTintColour'
2785
+ * and 'adaptedTintAlpha' in material.ts.
2786
+ */
2787
+ let sizedAlpha = ou.tint.w + ou.size.y * sizeK * (1.0 - ou.tint.w);
2788
+ let adaptedAlpha = sizedAlpha + toneAdapt * (1.0 - sizedAlpha);
2789
+ var adapted = neutral;
2790
+ if (toneAdapt > 0.0 && adaptedAlpha > 0.0) {
2791
+ adapted =
2792
+ (neutral * ((1.0 - toneAdapt) * sizedAlpha) + ou.toneColour.rgb * toneAdapt) / adaptedAlpha;
2793
+ }
2794
+
2795
+ // The author tint. 'aux.w' is the per-pixel strength, unioned in the field pass,
2796
+ // so a toolbar can carry one tinted control among plain ones; the seed is a
2797
+ // group uniform. At strength 0 this is the identity and the material is the one
2798
+ // the calibration bed measures, byte for byte.
2799
+ //
2800
+ // Apple's mechanism, not a fill: the seed is the middle of a range of tones
2801
+ // 'mapped to content brightness underneath'. 'backdrop' is what this pixel is
2802
+ // actually looking through \u2014 lens-displaced, LOD-sampled, already the thing the
2803
+ // material transmits \u2014 so the tone is taken against the same light the tint is
2804
+ // about to be mixed into, and a tinted surface over a dark backdrop settles to a
2805
+ // shade of the author's colour rather than sitting on it as paint.
2806
+ //
2807
+ // That 'backdrop' is also where the size law's scattering has already been
2808
+ // applied (above), and that is the right order rather than a coincidence: a
2809
+ // large surface diffuses more of what is behind it, so the tone the tint maps
2810
+ // to should be the diffused light, not the sharp light nobody sees through it.
2811
+ //
2812
+ // It displaces the ADAPTED neutral, not the raw one: a full-strength tint is its
2813
+ // own adaptation and replaces this axis outright, while a partial one moves with
2814
+ // it. That order is the wave's composition contract, in one line.
2815
+ var tintColour = adapted;
2816
+ if (aux.w > 0.0) {
2817
+ let t = smoothstep(ou.tone.z, ou.tone.w, backdropLuma);
2818
+ let low = ou.seed.rgb * ou.tone.x;
2819
+ let high = ou.seed.rgb + (vec3f(1.0) - ou.seed.rgb) * ou.tone.y;
2820
+ // 'seed.w' is the contrast regime's grip on the excursion, never on the hue:
2821
+ // at 0 the material shows the author's colour flat and stops responding.
2822
+ let tone = mix(ou.seed.rgb, mix(low, high, t), clamp(ou.seed.w, 0.0, 1.0));
2823
+ tintColour = mix(adapted, tone, clamp(aux.w, 0.0, 1.0));
2824
+ }
2825
+
2826
+ // The tint layer's ALPHA is untouched by the author's colour. It is the
2827
+ // material's occlusion \u2014 what reduced transparency lifts, and the axis the
2828
+ // system's own Clear-to-Tinted preference runs on \u2014 and an author choosing a
2829
+ // colour does not get to move it.
2830
+ //
2831
+ // The size law does move it, and the two do not collide: "a larger size is more
2832
+ // opaque, a smaller size is clearer" is a statement about how much material
2833
+ // there is, not about what colour it is. A fraction of whatever transparency
2834
+ // the resolved alpha still has, for the reason 'increasedOcclusionLift' is a
2835
+ // fraction \u2014 see material.ts.
2836
+ //
2837
+ // The backdrop adaptation moves it, above, and the author's colour does not.
2838
+ var colour = mix(backdrop, tintColour, adaptedAlpha);
2839
+
2840
+ /*
2841
+ * Everything below is the surface's own APPEARANCE \u2014 the marks that say a
2842
+ * surface is here rather than what is behind it \u2014 and all of it fades with the
2843
+ * adaptation, on the one factor.
2844
+ *
2845
+ * That is not symmetry for its own sake; it is what the reference does, and it
2846
+ * is a calibration cell rather than an inference: the reference's capsule over
2847
+ * the dark-solid backdrop is byte-identical to that background, rim included. A
2848
+ * material that has taken its backdrop's tone has no lit edge to show, because
2849
+ * there is no light in front of it to show one with.
2850
+ *
2851
+ * The gap was invisible until this axis existed. A rim of up to 130/255 sat
2852
+ * unnoticed inside a bright capsule body; with the body gone it is a white
2853
+ * outline around a surface that should not be there at all \u2014 595 pixels past
2854
+ * 2/255 on that one cell, and OKLab \u0394E max 0.47 where p95 already read 0.0000.
2855
+ * The same lesson W3 recorded from the other direction: a term that happens to
2856
+ * be hidden is one feature away from being visible.
2857
+ */
2858
+ let present = 1.0 - toneAdapt;
2089
2859
 
2090
2860
  // Inner shadow: the material's own occlusion, deepest where the lens is
2091
- // strongest, which is what makes a thicker surface read as heavier.
2092
- colour = colour * (1.0 - profile * ou.light.z * ou.light.w);
2861
+ // strongest, which is what makes a thicker surface read as heavier \u2014 and the
2862
+ // shadow facet of the size law deepens it further with the span.
2863
+ let shadowDepth = ou.light.z * (1.0 + (ou.size.z - 1.0) * sizeK);
2864
+ colour = colour * (1.0 - profile * shadowDepth * ou.light.w * present);
2093
2865
 
2094
2866
  // Rim and specular from the gradient. The rim is unlit ambient edge brightness;
2095
2867
  // the specular term is the same edge lit from 'light.xy'.
2096
2868
  let rw = rim_weight(d, ou.rim.x);
2097
2869
  let facing = dot(normal, ou.light.xy);
2098
2870
  let spec = pow(clamp(facing, 0.0, 1.0), max(ou.rim.z, 1e-3)) * ou.rim.w;
2099
- colour = colour + vec3f(rw * (ou.rim.y + spec));
2871
+ colour = colour + vec3f(rw * (ou.rim.y + spec) * present);
2100
2872
 
2101
- return encode_output(max(colour, vec3f(0.0)), coverage);
2873
+ // The material over its own shadow over the page, in one premultiplied vector:
2874
+ // the colour is the surface's, weighted by its coverage, and the alpha is what
2875
+ // the two of them together leave the page \u2014 'coverage' from the surface, and
2876
+ // the shadow filling whatever transparency is left.
2877
+ let body = encode_output(max(colour, vec3f(0.0)), coverage);
2878
+ return vec4f(body.rgb, body.a + shadowAlpha * (1.0 - body.a));
2102
2879
  }`;
2103
2880
 
2104
2881
  // ../renderer-webgpu/dist/wgsl/prelude.js
@@ -2345,7 +3122,7 @@ function createPassRunner(context) {
2345
3122
  };
2346
3123
  },
2347
3124
  opticsPass(encoder, args) {
2348
- const slot = uniformSlot(`optics:${args.groupId}`, 32);
3125
+ const slot = uniformSlot(`optics:${args.groupId}`, 60);
2349
3126
  const d = slot.data;
2350
3127
  d[0] = args.viewportDevice[0];
2351
3128
  d[1] = args.viewportDevice[1];
@@ -2367,18 +3144,46 @@ function createPassRunner(context) {
2367
3144
  d[17] = args.adaptTint[1];
2368
3145
  d[18] = args.adaptTint[2];
2369
3146
  d[19] = args.adaptStrength;
2370
- d[20] = args.rimWidth;
2371
- d[21] = args.rimAlpha;
2372
- d[22] = args.specularPower;
2373
- d[23] = args.specularGain;
2374
- d[24] = args.lightDirection[0];
2375
- d[25] = args.lightDirection[1];
2376
- d[26] = args.shadowDepth;
2377
- d[27] = args.shadowAlpha;
2378
- d[28] = args.backdrop === void 0 ? 0 : 1;
2379
- d[29] = args.fields.width;
2380
- d[30] = args.fields.height;
2381
- d[31] = args.fields.upsampled ? 1 : 0;
3147
+ d[20] = args.tintSeed[0];
3148
+ d[21] = args.tintSeed[1];
3149
+ d[22] = args.tintSeed[2];
3150
+ d[23] = args.tintToneAdaptation;
3151
+ d[24] = args.tintTone[0];
3152
+ d[25] = args.tintTone[1];
3153
+ d[26] = args.tintTone[2];
3154
+ d[27] = Math.max(args.tintTone[3], args.tintTone[2] + 1e-4);
3155
+ d[28] = args.rimWidth;
3156
+ d[29] = args.rimAlpha;
3157
+ d[30] = args.specularPower;
3158
+ d[31] = args.specularGain;
3159
+ d[32] = args.lightDirection[0];
3160
+ d[33] = args.lightDirection[1];
3161
+ d[34] = args.shadowDepth;
3162
+ d[35] = args.shadowAlpha;
3163
+ d[36] = args.backdrop === void 0 ? 0 : 1;
3164
+ d[37] = args.fields.width;
3165
+ d[38] = args.fields.height;
3166
+ d[39] = args.fields.upsampled ? 1 : 0;
3167
+ d[40] = args.sizeScatterGainMax;
3168
+ d[41] = args.sizeOcclusionGain;
3169
+ d[42] = args.sizeShadowGainMax;
3170
+ d[43] = args.bodyChainLod;
3171
+ d[44] = args.backdropTone[0];
3172
+ d[45] = Math.max(args.backdropTone[1], args.backdropTone[0] + 1e-4);
3173
+ d[46] = args.backdropTone[2];
3174
+ d[47] = args.backdropTone[3];
3175
+ d[48] = args.backdropToneColour[0];
3176
+ d[49] = args.backdropToneColour[1];
3177
+ d[50] = args.backdropToneColour[2];
3178
+ d[51] = args.backdropToneColour[3];
3179
+ d[52] = args.outerShadow[0];
3180
+ d[53] = args.outerShadow[1];
3181
+ d[54] = args.outerShadow[2];
3182
+ d[55] = args.outerShadow[3];
3183
+ d[56] = args.outerShadowSizeGain;
3184
+ d[57] = args.outerShadowRectCssHeight;
3185
+ d[58] = 0;
3186
+ d[59] = 0;
2382
3187
  slot.write();
2383
3188
  const chain = args.backdrop?.chain ?? placeholderView;
2384
3189
  const body = args.backdrop?.body ?? placeholderView;
@@ -2408,7 +3213,7 @@ function createPassRunner(context) {
2408
3213
  pass.end();
2409
3214
  },
2410
3215
  highlightPass(encoder, args) {
2411
- const slot = uniformSlot(`highlight:${args.groupId}`, 20);
3216
+ const slot = uniformSlot(`highlight:${args.groupId}`, 32);
2412
3217
  const d = slot.data;
2413
3218
  d[0] = args.viewportDevice[0];
2414
3219
  d[1] = args.viewportDevice[1];
@@ -2430,6 +3235,19 @@ function createPassRunner(context) {
2430
3235
  d[17] = args.fields.height;
2431
3236
  d[18] = args.fields.upsampled ? 1 : 0;
2432
3237
  d[19] = 0;
3238
+ d[20] = args.backdropTone[0];
3239
+ d[21] = Math.max(args.backdropTone[1], args.backdropTone[0] + 1e-4);
3240
+ d[22] = args.backdropTone[2];
3241
+ d[23] = args.backdropTone[3];
3242
+ d[24] = args.backdropToneLevel;
3243
+ d[25] = 0;
3244
+ d[26] = 0;
3245
+ d[27] = 0;
3246
+ const fieldRect = args.fieldRectDevice;
3247
+ d[28] = args.rectDevice.width / Math.max(fieldRect.width, 1e-6);
3248
+ d[29] = args.rectDevice.height / Math.max(fieldRect.height, 1e-6);
3249
+ d[30] = (args.rectDevice.x - fieldRect.x) / Math.max(fieldRect.width, 1e-6);
3250
+ d[31] = (args.rectDevice.y - fieldRect.y) / Math.max(fieldRect.height, 1e-6);
2433
3251
  slot.write();
2434
3252
  const pipeline = highlightPipeline(args.targetFormat);
2435
3253
  const pass = encoder.beginRenderPass({
@@ -2540,6 +3358,13 @@ function planPyramid(sourceWidth, sourceHeight, policy) {
2540
3358
  };
2541
3359
  }
2542
3360
  var CHAIN_SIGMA_AT_LEVEL_1 = 1.2;
3361
+ function chainLodForSigma(sigmaTexels) {
3362
+ if (!(sigmaTexels > 0))
3363
+ return 0;
3364
+ if (sigmaTexels <= CHAIN_SIGMA_AT_LEVEL_1)
3365
+ return sigmaTexels / CHAIN_SIGMA_AT_LEVEL_1;
3366
+ return 1 + Math.log2(sigmaTexels / CHAIN_SIGMA_AT_LEVEL_1);
3367
+ }
2543
3368
  function bodyBlurPlan(sigmaPx, plan) {
2544
3369
  if (sigmaPx <= 0)
2545
3370
  return { level: 0, residualSigmaTexels: 0 };
@@ -2683,7 +3508,7 @@ function createPyramidStore(context) {
2683
3508
  return target;
2684
3509
  };
2685
3510
  const mipView = (texture, level) => texture.createView({ baseMipLevel: level, mipLevelCount: 1, dimension: "2d" });
2686
- function allocate(sourceId, plan, bodyLevel, sizeEpoch, builtEpoch) {
3511
+ function allocate(sourceId, plan, bodyLevel, sizeEpoch, builtEpoch, bodySigmaTexels) {
2687
3512
  const existing = resources.get(sourceId);
2688
3513
  const bodyWidth = (plan.levels[bodyLevel] ?? plan.levels[0]).width;
2689
3514
  const bodyHeight = (plan.levels[bodyLevel] ?? plan.levels[0]).height;
@@ -2720,7 +3545,8 @@ function createPyramidStore(context) {
2720
3545
  body,
2721
3546
  stats,
2722
3547
  sizeEpoch,
2723
- builtEpoch
3548
+ builtEpoch,
3549
+ bodySigmaTexels
2724
3550
  };
2725
3551
  resources.set(sourceId, next);
2726
3552
  return next;
@@ -2929,8 +3755,9 @@ function createPyramidStore(context) {
2929
3755
  const [viewportW, viewportH] = request.viewportCss;
2930
3756
  const cover = viewportW > 0 && viewportH > 0 ? Math.max(frame.width / viewportW, frame.height / viewportH) : 1;
2931
3757
  const planScale = frame.width > 0 ? plan.width / frame.width : 1;
2932
- const bodyPlan = bodyBlurPlan(request.bodySigmaCss * cover * planScale, plan);
2933
- const target = allocate(request.sourceId, plan, bodyPlan.level, frame.sizeEpoch, request.epoch);
3758
+ const bodySigmaTexels = request.bodySigmaCss * cover * planScale;
3759
+ const bodyPlan = bodyBlurPlan(bodySigmaTexels, plan);
3760
+ const target = allocate(request.sourceId, plan, bodyPlan.level, frame.sizeEpoch, request.epoch, bodySigmaTexels);
2934
3761
  runImport(encoder, request.sourceId, frame, target.chain);
2935
3762
  runChain(encoder, request.sourceId, plan, target.chain);
2936
3763
  runBodyBlur(encoder, request.sourceId, plan, target.chain, target.body, bodyPlan.level, bodyPlan.residualSigmaTexels);
@@ -3046,15 +3873,6 @@ var RENDERER_PASS_IDS = [
3046
3873
  OPTICS_PASS_ID,
3047
3874
  HIGHLIGHT_PASS_ID
3048
3875
  ];
3049
- var NOMINAL_MATERIAL_POLICY = {
3050
- glass: "material",
3051
- frost: "nominal",
3052
- refraction: "nominal",
3053
- occlusion: "nominal",
3054
- border: "nominal",
3055
- ambientTint: "nominal",
3056
- foreground: "adaptive"
3057
- };
3058
3876
  function createWebGPURenderer(options = {}) {
3059
3877
  const providers = /* @__PURE__ */ new Map();
3060
3878
  const groups = /* @__PURE__ */ new Map();
@@ -3157,6 +3975,7 @@ function createWebGPURenderer(options = {}) {
3157
3975
  };
3158
3976
  const unionOf = (input) => input.union ?? DEFAULT_GROUP_UNION;
3159
3977
  const variantOf = (input) => input.variant ?? "regular";
3978
+ const groupTintSeed = (input) => input.surfaces.find((surface) => surface.fieldReferenceOnly !== true && (surface.tint?.strength ?? 0) > 0)?.tint?.color;
3160
3979
  const stateOf = (input, resolution) => {
3161
3980
  const resolved = resolution?.groups.find((group) => group.groupId === input.groupId);
3162
3981
  if (resolved === void 0) {
@@ -3246,9 +4065,10 @@ function createWebGPURenderer(options = {}) {
3246
4065
  const input = entry.input;
3247
4066
  if (input.surfaces.length === 0)
3248
4067
  continue;
4068
+ const policy = resolution?.accessibility.material ?? accessibility;
3249
4069
  let surfaces;
3250
4070
  try {
3251
- surfaces = resolveSurfaces(input, governor.knobs.fieldFamily, material);
4071
+ surfaces = resolveSurfaces(input, governor.knobs.fieldFamily, material, policy);
3252
4072
  } catch (error) {
3253
4073
  skipped.push({
3254
4074
  groupId: input.groupId,
@@ -3257,10 +4077,17 @@ function createWebGPURenderer(options = {}) {
3257
4077
  continue;
3258
4078
  }
3259
4079
  const union = unionOf(input);
3260
- const snapped = snapRectToDevicePixels(groupFieldRect(surfaces, union), dpr);
4080
+ const shadow = outerShadowUnderPolicy(policy, material);
4081
+ let reachOcclusion = shadow.occlusion;
4082
+ for (const surface of surfaces) {
4083
+ reachOcclusion = Math.max(reachOcclusion, sizeOuterShadowOcclusionAt(shadow.occlusion, surface.sizeThickness, material));
4084
+ }
4085
+ const shadowReachPx = outerShadowReachPx({ ...shadow, occlusion: reachOcclusion });
4086
+ const snapped = snapRectToDevicePixels(groupFieldRect(surfaces, union, void 0, shadowReachPx), dpr);
3261
4087
  const rectDevice = clipFieldRectToCanvas(snapped, dpr, viewportDevice);
3262
4088
  if (rectDevice === void 0)
3263
4089
  continue;
4090
+ const surfaceRectDevice = shadowReachPx === 0 ? rectDevice : clipFieldRectToCanvas(snapRectToDevicePixels(groupFieldRect(surfaces, union), dpr), dpr, viewportDevice) ?? rectDevice;
3264
4091
  const packed = packInstances(surfaces, [
3265
4092
  rectDevice.x * cssPerDevice,
3266
4093
  rectDevice.y * cssPerDevice
@@ -3279,9 +4106,15 @@ function createWebGPURenderer(options = {}) {
3279
4106
  union
3280
4107
  });
3281
4108
  const state = stateOf(input, resolution);
3282
- const policy = resolution?.accessibility.material ?? accessibility;
3283
4109
  const variant = variantOf(input);
3284
4110
  const optics = opticsUnderPolicy(material.optics[variant], policy, material);
4111
+ const backdropTone = [
4112
+ material.backdropToneLow,
4113
+ material.backdropToneHigh,
4114
+ backdropToneSizeBiasUnderPolicy(policy, material),
4115
+ input.backdropTone === void 0 ? 0 : backdropToneUnderPolicy(policy, material) * material.backdropToneMax
4116
+ ];
4117
+ const backdropToneLevel = input.backdropTone === void 0 ? 0 : relativeLuminance(input.backdropTone);
3285
4118
  const refraction = effectiveRefraction(accessibilityRefractionCap(policy), state.refraction);
3286
4119
  const refractionScale = material.refractionScale[refraction];
3287
4120
  const sourceId = input.backdropSourceId;
@@ -3305,6 +4138,26 @@ function createWebGPURenderer(options = {}) {
3305
4138
  tintAlpha: optics.tintAlpha,
3306
4139
  adaptTint: adapt?.tint ?? optics.tint,
3307
4140
  adaptStrength: adapt?.observed === true ? adaptationStrength(policy, state.analysisExact, material) : 0,
4141
+ /*
4142
+ * `glass: "none"` is forced colours, and it takes the author's tint with
4143
+ * the rest of the material.
4144
+ *
4145
+ * The pass keeps running — it is what paints the surface at all on this
4146
+ * tier — but with no backdrop and an opaque occlusion, so whatever colour
4147
+ * sits here IS the surface. An author tint arriving into that lands as a
4148
+ * flat fill of the author's colour where the platform's palette is
4149
+ * required, which is the one composition forced colours exists to
4150
+ * prevent. Falling back to the material's own tint reproduces exactly
4151
+ * what an untinted surface draws.
4152
+ */
4153
+ tintSeed: (policy.glass === "none" ? void 0 : groupTintSeed(input)) ?? optics.tint,
4154
+ tintToneAdaptation: tintToneAdaptation(policy, material),
4155
+ tintTone: [
4156
+ material.tintToneFloor,
4157
+ material.tintToneCeilMix,
4158
+ material.tintToneLow,
4159
+ material.tintToneHigh
4160
+ ],
3308
4161
  rimWidth: optics.rimWidth,
3309
4162
  rimAlpha: optics.rimAlpha,
3310
4163
  specularPower: optics.specularPower,
@@ -3312,6 +4165,33 @@ function createWebGPURenderer(options = {}) {
3312
4165
  lightDirection: material.lightDirection,
3313
4166
  shadowDepth: optics.shadowDepth,
3314
4167
  shadowAlpha: optics.shadowAlpha,
4168
+ // The size law's gains, per group (W2); the per-pixel factor they
4169
+ // multiply rides the field pass's aux channel, so one uniform serves a
4170
+ // group whose members are not one size. `bodyChainLod` is where the body
4171
+ // blur already sits on the chain — only the pyramid that built it knows
4172
+ // the CSS-px-to-texel conversion, so it publishes the σ and this converts.
4173
+ sizeScatterGainMax: material.sizeScatterGainMax,
4174
+ sizeOcclusionGain: material.sizeOcclusionGain,
4175
+ sizeShadowGainMax: material.sizeShadowGainMax,
4176
+ bodyChainLod: chainLodForSigma(pyramid?.bodySigmaTexels ?? 0),
4177
+ backdropTone,
4178
+ backdropToneColour: [
4179
+ input.backdropTone?.[0] ?? 0,
4180
+ input.backdropTone?.[1] ?? 0,
4181
+ input.backdropTone?.[2] ?? 0,
4182
+ backdropToneLevel
4183
+ ],
4184
+ outerShadow: [
4185
+ outerShadowAlpha(shadow.occlusion),
4186
+ shadow.sigmaPx,
4187
+ shadow.spreadPx,
4188
+ // The offset in field-texture UV. The field spans exactly this rect, so
4189
+ // one CSS px is one over the rect's CSS height however the governor
4190
+ // scaled the rasterisation.
4191
+ shadow.offsetPx / Math.max(rectDevice.height * cssPerDevice, 1e-6)
4192
+ ],
4193
+ outerShadowSizeGain: shadow.sizeGain,
4194
+ outerShadowRectCssHeight: rectDevice.height * cssPerDevice,
3315
4195
  backdrop: pyramid === void 0 || policy.glass === "none" ? void 0 : {
3316
4196
  chain: pyramid.chain.createView(),
3317
4197
  body: pyramid.body.createView()
@@ -3323,7 +4203,9 @@ function createWebGPURenderer(options = {}) {
3323
4203
  groupId: input.groupId,
3324
4204
  target: active.highlight,
3325
4205
  targetFormat: active.format,
3326
- rectDevice,
4206
+ // The surface's own rect, not the shadow's — see `surfaceRectDevice`.
4207
+ rectDevice: surfaceRectDevice,
4208
+ fieldRectDevice: rectDevice,
3327
4209
  fields,
3328
4210
  viewportDevice,
3329
4211
  cssPerDevice,
@@ -3335,7 +4217,11 @@ function createWebGPURenderer(options = {}) {
3335
4217
  pressPointCss: lead.channels.pressPoint ?? lead.centre,
3336
4218
  glowRadiusCss: material.glowRadiusCss,
3337
4219
  glowGain: material.glowGain,
3338
- colour: optics.highlight
4220
+ colour: optics.highlight,
4221
+ // The same four numbers the optics pass took, so the two passes fade
4222
+ // the surface on one curve rather than on two copies of it.
4223
+ backdropTone,
4224
+ backdropToneLevel
3339
4225
  });
3340
4226
  }
3341
4227
  drawn += 1;
@@ -3568,6 +4454,6 @@ function createWebGPURenderer(options = {}) {
3568
4454
  };
3569
4455
  }
3570
4456
 
3571
- export { ADAPTIVE_LUMINANCE_HIGH, ADAPTIVE_LUMINANCE_LOW, ADAPTIVE_TINT_DARK, ADAPTIVE_TINT_LIGHT, ANALYSIS_DISPATCH, ANALYSIS_GRID, ANALYSIS_PASS_ID, ANALYSIS_STATS_FLOATS, ANALYSIS_TARGET_EXTENT, ANALYSIS_WORKGROUP, BACKDROP_ALPHA_MODES, BACKDROP_COLOR_SPACES, BACKDROP_KINDS, BACKDROP_PASS_ID, CANVAS_FORMAT, CHAIN_SIGMA_AT_LEVEL_1, CROSS_CHECK_SHAPE_FLOATS, CROSS_CHECK_WORKGROUP, DEFAULT_MATERIAL_PROFILE, FAMILY_C_CROSS_CHECK, FIELD_PASS_ID, GOVERNOR_LADDER, HIGHLIGHT_PASS_ID, IDLE_CHANNELS, INCREASED_OCCLUSION_LIFT, INSTANCE_BYTES, INSTANCE_FLOATS, LENS_BODY_LOD_PER_PX, LENS_RIM_LOD_BIAS, LENS_SIZE_GAIN_MAX, LENS_SPAN_MAX, LENS_SPAN_MIN, LUMINANCE_WEIGHTS, MATERIAL_OPTICS, MATERIAL_VARIANTS, MAX_CHAIN_LEVELS, MIN_LEVEL_EXTENT, NOMINAL_GOVERNOR, NOMINAL_MATERIAL_POLICY, OPTICS_PASS_ID, OUTPUT_TEXTURE_FORMAT, PASS_LABEL, REFRACTION_LADDER, REFRACTION_SCALE, RENDERER_ERROR_CODES, RENDERER_PASS_IDS, RendererError, SUPPORTED_APP_TEXTURE_FORMATS, WGSL_ANALYSIS_PASS, WGSL_CROSS_CHECK_PASS, WGSL_DOWNSAMPLE_PASS, WGSL_FIELD_KERNELS, WGSL_FIELD_PASS, WGSL_FIELD_SAMPLE, WGSL_HIGHLIGHT_PASS, WGSL_IMPORT_PASS, WGSL_INSTANCE_STRUCT, WGSL_OPTICS_PASS, WGSL_PRELUDE, WGSL_RSUPN_GRAD, WGSL_RSUP_GRAD, WGSL_SMOOTH_UNION, WORKING_TEXTURE_FORMAT, ZERO_STATS, accessibilityRefractionCap, adaptationStrength, adaptiveTint, allShaderSource, alphaNormalisationMode, analysisModule, bodyBlurPlan, bodyLod, chainModule, clipFieldRectToCanvas, createAdaptationState, createAppTextureProvider, createCopyProvider, createDeviceHost, createGovernor, createGpuContext, createGradientProvider, createPassRunner, createPipelineCache, createPyramidStore, createRebuildLedger, createStorageSlot, createTexturePool, createTimingCollector, createUniformSlot, createVideoProvider, createWebGPURenderer, crossCheckKernelModule, displayP3ToSrgbLinear, effectiveRefraction, encodeOutput, encodeOutputBytes, fieldModule, fieldPassSource, groupFieldRect, highlightModule, importColorMatrix, importModule, importPassSource, lensDepthPx, lensSizeGain, linearGradientStops, linearToSrgb, linearToSrgbChannel, occlusionAlphaUnderPolicy, opticsModule, opticsUnderPolicy, packInstances, pipelineKey, planPyramid, poolKey, readbackDue, refractionRank, relativeLuminance, rendererError, resolveSurfaces, snapRectToDevicePixels, srgbToLinear, srgbToLinearChannel, statsFromBuffer, supportsTimestamps, validateAppTexture, withMaterialOverrides };
3572
- //# sourceMappingURL=dist-FGJI5LQM.js.map
3573
- //# sourceMappingURL=dist-FGJI5LQM.js.map
4457
+ export { ADAPTIVE_LUMINANCE_HIGH, ADAPTIVE_LUMINANCE_LOW, ADAPTIVE_TINT_DARK, ADAPTIVE_TINT_LIGHT, ANALYSIS_DISPATCH, ANALYSIS_GRID, ANALYSIS_PASS_ID, ANALYSIS_STATS_FLOATS, ANALYSIS_TARGET_EXTENT, ANALYSIS_WORKGROUP, BACKDROP_ALPHA_MODES, BACKDROP_COLOR_SPACES, BACKDROP_KINDS, BACKDROP_PASS_ID, BACKDROP_TONE_HIGH, BACKDROP_TONE_LOW, BACKDROP_TONE_MAX, BACKDROP_TONE_SIZE_BIAS, CANVAS_FORMAT, CHAIN_SIGMA_AT_LEVEL_1, CROSS_CHECK_SHAPE_FLOATS, CROSS_CHECK_WORKGROUP, DEFAULT_MATERIAL_PROFILE, FAMILY_C_CROSS_CHECK, FIELD_PASS_ID, GOVERNOR_LADDER, HIGHLIGHT_PASS_ID, IDLE_CHANNELS, INCREASED_OCCLUSION_LIFT, INSTANCE_BYTES, INSTANCE_FLOATS, LENS_BODY_LOD_PER_PX, LENS_RIM_LOD_BIAS, LENS_SIZE_GAIN_MAX, LUMINANCE_WEIGHTS, MATERIAL_OPTICS, MATERIAL_VARIANTS, MAX_CHAIN_LEVELS, MIN_LEVEL_EXTENT, NOMINAL_GOVERNOR, NOMINAL_MATERIAL_POLICY, OPTICS_PASS_ID, OUTER_SHADOW, OUTPUT_TEXTURE_FORMAT, PASS_LABEL, REFRACTION_LADDER, REFRACTION_SCALE, RENDERER_ERROR_CODES, RENDERER_PASS_IDS, RendererError, SIZE_OCCLUSION_GAIN, SIZE_SCATTER_GAIN_MAX, SIZE_SHADOW_GAIN_MAX, SIZE_SPAN_MAX, SIZE_SPAN_MIN, SRGB_ENCODING_EXPONENT, SUPPORTED_APP_TEXTURE_FORMATS, WGSL_ANALYSIS_PASS, WGSL_CROSS_CHECK_PASS, WGSL_DOWNSAMPLE_PASS, WGSL_FIELD_KERNELS, WGSL_FIELD_PASS, WGSL_FIELD_SAMPLE, WGSL_HIGHLIGHT_PASS, WGSL_IMPORT_PASS, WGSL_INSTANCE_STRUCT, WGSL_OPTICS_PASS, WGSL_PRELUDE, WGSL_RSUPN_GRAD, WGSL_RSUP_GRAD, WGSL_SMOOTH_UNION, WORKING_TEXTURE_FORMAT, ZERO_STATS, accessibilityRefractionCap, adaptationStrength, adaptedTintAlpha, adaptedTintColour, adaptiveTint, allShaderSource, alphaNormalisationMode, analysisModule, backdropToneAdaptation, backdropToneSizeBiasUnderPolicy, backdropToneUnderPolicy, bodyBlurPlan, bodyLod, chainModule, clipFieldRectToCanvas, createAdaptationState, createAppTextureProvider, createCopyProvider, createDeviceHost, createGovernor, createGpuContext, createGradientProvider, createPassRunner, createPipelineCache, createPyramidStore, createRebuildLedger, createStorageSlot, createTexturePool, createTimingCollector, createUniformSlot, createVideoProvider, createWebGPURenderer, crossCheckKernelModule, displayP3ToSrgbLinear, effectiveRefraction, encodeOutput, encodeOutputBytes, fieldModule, fieldPassSource, groupFieldRect, highlightModule, importColorMatrix, importModule, importPassSource, lensDepthPx, lensSizeGain, lensSizeGainFromThickness, linearGradientStops, linearToSrgb, linearToSrgbChannel, occlusionAlphaUnderPolicy, opticsModule, opticsUnderPolicy, outerShadowAlpha, outerShadowFalloff, outerShadowReachPx, outerShadowUnderPolicy, packInstances, pipelineKey, planPyramid, poolKey, readbackDue, refractionRank, relativeLuminance, rendererError, resolveSurfaces, sizeOcclusionAlpha, sizeOcclusionAlphaAt, sizeOuterShadowOcclusion, sizeOuterShadowOcclusionAt, sizeScatterSigma, sizeScatterSigmaAt, sizeShadowDepth, sizeShadowDepthAt, sizeThickness, sizeThicknessUnderPolicy, snapRectToDevicePixels, srgbToLinear, srgbToLinearChannel, statsFromBuffer, supportsTimestamps, tintTone, tintToneAdaptation, tintedTintColour, validateAppTexture, withMaterialOverrides };
4458
+ //# sourceMappingURL=dist-RQ4ZMA3D.js.map
4459
+ //# sourceMappingURL=dist-RQ4ZMA3D.js.map