@vitreajs/vitrea 0.13.0 → 0.15.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.
package/dist/index.d.ts CHANGED
@@ -1812,6 +1812,50 @@ interface MaterialOptics {
1812
1812
  * not carry this constant renders the W23 material byte for byte.
1813
1813
  */
1814
1814
  readonly rimLitExponent: number;
1815
+ /**
1816
+ * **The lit edge's along-side field (W25; claims §5.113, W25 Decision Log 3
1817
+ * (c))** — the slope of the position field the rim's amplitude is graded by
1818
+ * across the surface, riding `sizeThickness`.
1819
+ *
1820
+ * W24 gave the rim a factor of the NORMAL, which is one number on a whole
1821
+ * straight side. W25 G0 read the rim's peak excess by POSITION along each
1822
+ * straight side instead and found it graded on every thick cell of both probe
1823
+ * grids, on flat solid backdrops where a lens has no gradient to refract: the
1824
+ * 1x dark `dark-solid__rrect-md` reads slopes of −0.000192 (top), +0.000192
1825
+ * (bottom), −0.000379 (left) and +0.000379 (right) luma per CSS px, and the
1826
+ * corner-to-corner range 0.0187 / 0.0226 / 0.0345 on `light-solid` /
1827
+ * `dark-solid` / `mid-dark-solid` at span 96 falls to 0.0000 / 0.0016 at spans
1828
+ * 32 and 44 and saturates above 96 — the signature of a term on
1829
+ * `sizeThickness` and not of the lens.
1830
+ *
1831
+ * **The field is the product of the two normalised coordinates, and that is
1832
+ * what the four slopes say.** A field linear in position — `a·x + b·y` — gives
1833
+ * the top and the bottom side the SAME slope in x; the reference's are equal
1834
+ * and opposite. `(x / halfWidth) · (y / halfHeight)` gives exactly the measured
1835
+ * antisymmetry, is +1 at the top-left and bottom-right corners and −1 at the
1836
+ * other two — the same diagonal `rimLitAxis` is symmetric about, which is why
1837
+ * this is the position half of one light and not a second one — and predicts
1838
+ * the two sides' slope ratio as the box's aspect: 160/96 = 1.67 against the
1839
+ * measured 0.000379 / 0.000192 = 1.97, where a metric diagonal would predict
1840
+ * 1.0. Reading the reference's two sides through it gives 0.62 (top) and 0.73
1841
+ * (left) for this constant, which is the agreement a 56 CSS px straight side
1842
+ * on a six-code contrast supports.
1843
+ *
1844
+ * The factor is `1 + rimAlongSideSlope · sizeThickness(span) · field`, applied
1845
+ * beside W24's `lit` factor and outside W23's amplitude bracket. Two
1846
+ * consequences are exact rather than approximate. **The field's mean over every
1847
+ * straight side is zero**, because the product is odd in the coordinate that
1848
+ * runs along the side, so W23's and W24's straight-span amplitudes keep their
1849
+ * fitted meaning and the CSS tier — whose one inset shadow cannot vary around a
1850
+ * contour — is coherent with the GPU tier's side mean without carrying the term
1851
+ * at all. **It is exactly 0 at or below `sizeSpanMin`**, so `rrect-sm` and every
1852
+ * thin control are untouched by construction; the capsule's span of 44 takes
1853
+ * 0.0923 of it, which is the thin end's whole exposure.
1854
+ *
1855
+ * At 0 the factor is exactly 1 everywhere, so a profile that does not carry
1856
+ * this constant renders the W24 material byte for byte.
1857
+ */
1858
+ readonly rimAlongSideSlope: number;
1815
1859
  /** Inner-shadow depth (0..1) and how much of it is applied. */
1816
1860
  readonly shadowDepth: number;
1817
1861
  readonly shadowAlpha: number;
@@ -2171,6 +2215,17 @@ interface MaterialProfile {
2171
2215
  * σ 10 device px against a body σ of 1.25, and the gain sweep has a clear
2172
2216
  * minimum at 8 (RMS 0.0164 against 0.0180 at 6 and 0.0191 at 10 on the probe
2173
2217
  * bed).
2218
+ *
2219
+ * **INERT on the GPU tier at any material that names a heavy width** (W26;
2220
+ * claims §5.122 §6a). This constant reaches the deep sample only through
2221
+ * `scatterLod`, and where `sizeHeavyTapSigma` is non-zero the optics pass
2222
+ * overwrites that sample with the heavy texture — measured, not argued: fifty
2223
+ * rows byte-identical between `sizeScatterGainFar2x` 9.9 and 4.8 at the landed
2224
+ * width. It is KEPT and not retired because retiring it means deciding what a
2225
+ * profile naming NO heavy width draws, which is a code-removal wave with no
2226
+ * fidelity content (W26 Decision Log 6 (b)); the tracker carries it. The CSS
2227
+ * tier still reads it for the same reason — the collapsed single-`blur()` form
2228
+ * and the sampling-padding projection, where no heavy width applies.
2174
2229
  */
2175
2230
  readonly sizeScatterGainMax: number;
2176
2231
  /**
@@ -2290,6 +2345,11 @@ interface MaterialProfile {
2290
2345
  * **The span top, 256** — the 1x value, unchanged, because a floor of 1
2291
2346
  * leaves the deep value nothing to rise to and the top has no work at this
2292
2347
  * ratio. Stage 1 swept it against 128 and the bed did not ask for the change.
2348
+ *
2349
+ * **The gain is INERT on the GPU tier at any material that names a heavy
2350
+ * width** (W26; claims §5.122 §6a), for `sizeScatterGainMax`'s reason and with
2351
+ * the same measurement behind it. The floor and the span top above are NOT —
2352
+ * they grade the deep value's SHARE, which the heavy texture does not touch.
2293
2353
  */
2294
2354
  readonly sizeScatterGainMax2x: number;
2295
2355
  readonly sizeScatterFloor2x: number;
@@ -2339,6 +2399,19 @@ interface MaterialProfile {
2339
2399
  * 0.1125 → 0.0993 against native 0.1018 at 9.9, which is the sweep's best on
2340
2400
  * its own objective. The confirmation's holdout `rrect-lg` rose 0.9661 →
2341
2401
  * 0.9762 with its interior spread 0.0721 against native 0.0810.
2402
+ *
2403
+ * **INERT on the GPU tier at any material that names a heavy width**, and this
2404
+ * is the one of the three whose silence costs something (W26; claims §5.122
2405
+ * §§3, 6a). It is the constant that graded the 2x heavy width with the span,
2406
+ * and the heavy texture is one width per SOURCE, so where a heavy width is
2407
+ * named there is nothing left for it to grade: fifty rows byte-identical
2408
+ * between 9.9 and 4.8 at the landed width. What that gives up is measured on
2409
+ * the instrument of record rather than estimated — the reference wants **1.66
2410
+ * device px more heavy width at span 160 than at span 96 at dpr 2**, 20 % of
2411
+ * the smaller, and W26 lands one number inside 11 % of each instead. That is
2412
+ * W26 Decision Log 2 (f)'s recorded gap, now with a size; the constant is kept
2413
+ * against the profile that names no heavy width and against the wave that
2414
+ * makes the width per surface.
2342
2415
  */
2343
2416
  readonly sizeScatterGainFar2x: number;
2344
2417
  /**
@@ -2489,6 +2562,210 @@ interface MaterialProfile {
2489
2562
  readonly sizeScatterRampStartFar2x: number;
2490
2563
  readonly sizeScatterRampReach1xPx: number;
2491
2564
  readonly sizeScatterRampReach2xPx: number;
2565
+ /**
2566
+ * **The heavy share's thick end** (W25; claims §5.113, W25 Decision Log 3 (a))
2567
+ * — how much heavier the deep value runs on a thick surface than the span
2568
+ * curve above makes it, riding `sizeThickness` and read once per scale.
2569
+ *
2570
+ * W25 G0 measured the reference's kernel as TWO components and found that what
2571
+ * separates the thick surface from the thin one is neither width: read on the
2572
+ * same cell at 1x the reference's single-Gaussian width is 1.30 device px
2573
+ * against a 16 CSS px checkerboard, 4.75 against 32 and 6.25 against 64, where
2574
+ * one Gaussian returns one number at every pitch. The dot's own two-component
2575
+ * fit on `impulse__rrect-md` reads sharp σ 2.79 and heavy σ 19.52 device px at
2576
+ * 1x and 1.40 / 11.29 at 2x — both components HALVING in device px — with the
2577
+ * heavy SHARE moving 0.47 → 0.69 the other way, and 0.00 on the collapsed
2578
+ * capsule. vitrea's own pair on the landed material reaches 0.69 at 2x and
2579
+ * carries 0.23 at 1x. So the share is the quantity, and this is where the wave
2580
+ * puts it.
2581
+ *
2582
+ * ```
2583
+ * kDeep(span, dpr) = floor(dpr) + (1 − floor(dpr))
2584
+ * · smoothstep(sizeSpanMin, sizeScatterSpanMax(dpr), span)
2585
+ * + heavyShareThick(dpr) · sizeThickness(span) ← this
2586
+ * ```
2587
+ *
2588
+ * **What stays, and why the thin capsule cannot move.** `sizeScatterFloor`,
2589
+ * `sizeScatterSpanMax` and the ramp's start anchors are NOT re-derived: the
2590
+ * W11c curve stays as the law's thin end and this constant is the lift the
2591
+ * thick end takes above it. That is X5 discharged by the form rather than by a
2592
+ * capture — `sizeThickness` is exactly 0 at and below `sizeSpanMin`, so
2593
+ * `rrect-sm` and every smaller control are bit-identical whatever this says,
2594
+ * and the capsule at span 44 takes 0.0923 of it, which is the whole of the
2595
+ * thin end's exposure and is what the ladder's thin-invariance rung reads. The
2596
+ * alternative — re-expressing the whole curve on a thin/thick anchor pair —
2597
+ * would have made the capsule's own share a fitted quantity of this wave, and
2598
+ * the reference does not ask for that: its sharp σ is 2.62 device px on the
2599
+ * collapsed capsule against 2.79 on the thick rrect, span-flat to 6 %.
2600
+ *
2601
+ * **Inert at 0**, which is what ships until G3 declares the fit: the term is
2602
+ * one multiplication by zero added to the curve W11c fitted, so the resolved
2603
+ * material and every golden are byte-identical to the W24 bed.
2604
+ *
2605
+ * At dpr 2 the 2x anchor is additionally inert on the landed material for a
2606
+ * second reason: `sizeScatterFloor2x` is 1, so `kDeep` is already 1 at every
2607
+ * span and the clamp absorbs any lift. The 2x share is therefore not a
2608
+ * quantity this bed can carry, and it waits for G1's 2x probe fixtures.
2609
+ */
2610
+ readonly sizeScatterHeavyShareThick1x: number;
2611
+ readonly sizeScatterHeavyShareThick2x: number;
2612
+ /**
2613
+ * **The body's level above the thickness knee** (W25; claims §5.113, W25
2614
+ * Decision Log 3 (b)) — an OFFSET on the interior level a surface settles at,
2615
+ * in the tone response's own encoded units, reached at `sizeScatterSpanMax`.
2616
+ *
2617
+ * W25 G0 answered clause 4 in the landed law's favour and found one residual
2618
+ * it cannot carry. `sizeThickness(short side)` at knee 96 scores r 0.95–0.998
2619
+ * against 0.68–0.96 for the long side, the area, √area and the radius, on the
2620
+ * width and on the level, in both probe grids and both schemes — so the
2621
+ * argument and the knee stay. But the reference's body LEVEL keeps grading
2622
+ * above 96: on the W9 light grid over `checkerboard` it reads 0.61484 /
2623
+ * 0.61299 / 0.67915 / 0.69083 / 0.70458 across spans 32 / 44 / 96 / 128 / 160,
2624
+ * where `sizeThickness` has been flat since 96. The width does not grade there
2625
+ * on any pitch; only the level does.
2626
+ *
2627
+ * ```
2628
+ * R(x, span, dpr) = R₀(x, sizeK)
2629
+ * + sizeToneLevelFar · smoothstep(sizeSpanMax,
2630
+ * sizeScatterSpanMax(dpr), span)
2631
+ * ```
2632
+ *
2633
+ * **Where it enters.** On the tone response's OUTPUT — the settled interior
2634
+ * level `backdropToneResponse` returns, which is the law that owns the
2635
+ * interior mean (claims §5.33) — and not in the blur, not on a gain on the
2636
+ * mix, and not (this is the correction) on the response's own thin-to-thick
2637
+ * blend.
2638
+ *
2639
+ * **Why not on the blend, which is what the wave chartered.** G0's reading
2640
+ * that the residual's "sign follows the backdrop" is a reading of the
2641
+ * REFERENCE's absolute grading, and the quantity a term has to close is the
2642
+ * residual AGAINST vitrea, whose own deep value keeps rising to
2643
+ * `sizeScatterSpanMax` over the same spans. G2's ladder measured both shapes
2644
+ * on the same rows (`fit-level.txt`): carrying the blend past the thick row
2645
+ * explains 0.3 % of the above-knee residual and its per-row gains run
2646
+ * −1.13 … +0.24 with the two grids disagreeing in sign, because the blend's
2647
+ * direction is different at every backdrop level and the residual's is not. An
2648
+ * offset explains 11 % of it and the light grid's rows agree: `resid(160) −
2649
+ * resid(96)` is +2.2 … +4.2 codes over `checkerboard` at four pitches,
2650
+ * `dark-solid`, `mid-dark-solid`, `hc-text` at two pitches and `photo`, over
2651
+ * backdrops spanning 0.012 to 0.89 linear. Backdrop-independent is what the
2652
+ * rows say, so backdrop-independent is the shape.
2653
+ *
2654
+ * The curve is the one the ramp's far anchor declines along and the 2x heavy
2655
+ * gain rises along, so no new span statistic enters the material, and it is
2656
+ * **exactly 0 at and below `sizeSpanMax`** — nothing at or under span 96 moves
2657
+ * at any value of this constant, which is what let G2 probe it against the
2658
+ * frozen bed with 77 control rows reading a lever of 0.000000 codes per unit.
2659
+ *
2660
+ * Folded with `sizeK`, like the response it offsets: under reduced
2661
+ * transparency the material has stopped transmitting and the level it settles
2662
+ * at is the preference's, not the size law's.
2663
+ *
2664
+ * **Inert at 0**, which is what ships until G3 declares the fit. What G2's
2665
+ * ladder read is in `fit-level.txt`; the constant is weakly conditioned on the
2666
+ * bed as it stands and G1's probe set at both scales is what would condition
2667
+ * it (G0 §6: 118 level rows on the 1x grids, 202–238 with the 2x captures).
2668
+ */
2669
+ readonly sizeToneLevelFar: number;
2670
+ /**
2671
+ * **The heavy blur's width, in device px per scale** (W26; W26 Decision Log 1
2672
+ * and 2, from the measured cause in claims §5.116 §2) — the constant that makes
2673
+ * the deep sample's width a continuous parameter instead of a chain level.
2674
+ *
2675
+ * W25 could not raise the heavy share because vitrea's heavy component is
2676
+ * 13.3 device px at 1x against the reference's 19.5, and raising the share at
2677
+ * the wrong width adds narrow structure the reference does not have. The width
2678
+ * was not a lever: `sizeScatterGainMax` 8 → 10.3 left reader A at 13.29. W26 G0
2679
+ * measured why, and it is arithmetic rather than material
2680
+ * (`results/2026-09-10-w26-heavy-width/g0/tap-today.txt`). The body's deep
2681
+ * sample is
2682
+ *
2683
+ * ```
2684
+ * scatterLod = clamp(bodyChainLod + log2(gain), 0, chainMaxLod)
2685
+ * ```
2686
+ *
2687
+ * `chainMaxLod` is `planPyramid`'s `levelCount − 1`, and the chain stops when
2688
+ * the shorter side would fall below `MIN_LEVEL_EXTENT` = 8. On the bed's
2689
+ * 320 × 200 backdrop raster the chain is five levels, so `chainMaxLod` is **4**
2690
+ * at dpr 1, and `bodyChainLod + log2(8)` is 4.06 — **already past the clamp**.
2691
+ * Every gain at or above 7.5 draws the same level 4, whose own kernel is
2692
+ * 13.4 level-0 texels wide (`CHAIN_LEVEL_SIGMA`) — which is the 13.3 the ledger
2693
+ * recorded. G0 rendered the three impulse rows at gains 8, 10.3, 16 and 32 and
2694
+ * read them identical to the last digit, moving only at gain 4, which asks for
2695
+ * level 3.06 and gets it. So `sizeScatterGainMax`, `sizeScatterGainMax2x` and
2696
+ * `sizeScatterGainFar2x` grade a quantity the clamp then discards, and a
2697
+ * fractional level or a blend of two levels cannot widen anything at dpr 1 —
2698
+ * G0 measured both **inert to the bit** there and removed them again (W26
2699
+ * Decision Log 2 (a); the branch and `results/…/g0/` are their record).
2700
+ *
2701
+ * What widens is a Gaussian, and it is built where the chain is built. The
2702
+ * pyramid takes the chain level whose own blur is nearest below this σ and runs
2703
+ * the two separable passes it already runs for the body until the total is this
2704
+ * σ exactly (`heavyTapPlan`, `PyramidResources.heavy`), so the width is
2705
+ * continuous and is bounded by nothing the chain's depth decides — the residual
2706
+ * carries whatever octave the chain lacks. The optics pass then reads that
2707
+ * texture in place of the chain tap.
2708
+ *
2709
+ * **Per scale**, resolved by `rampAtScale` on the pattern `sizeScatterGainMax2x`
2710
+ * established, because the reference's heavy component is a device-px quantity
2711
+ * that does not simply halve between the scales (19.52 at 1x, 11.29 at 2x;
2712
+ * claims §5.113 §2) — so a profile naming only the 1x end drags the 2x end
2713
+ * toward the 2x constant's own default, and a document that means both names
2714
+ * both.
2715
+ *
2716
+ * **At 0 the mechanism does not exist**: no texture is allocated, no pass is
2717
+ * encoded, and the optics pass takes the single `textureSampleLevel` of the
2718
+ * chain the material has always taken. That is what makes the landed 0.14.0
2719
+ * goldens byte-identical.
2720
+ *
2721
+ * **What it costs, and what it gives up.** One texture and two passes per
2722
+ * source per frame — 0.070 ms on the mobile bench row, against +1.1 ms for the
2723
+ * in-shader grid G0 measured (W26 Decision Log 2 (b)). The width is then one
2724
+ * per SOURCE rather than one per pixel, so the span grading `sizeScatterGainFar2x`
2725
+ * carried does not reach the deep sample where this is on. The reference's heavy
2726
+ * width does not grade with the span at 1x (claims §5.113 §4), which is what
2727
+ * says the material can afford that; at 2x it does (the `-lg` row reads 16.92
2728
+ * against `-md`'s 11.29), and that is a recorded gap rather than an answered
2729
+ * one (W26 Decision Log 2 (f)).
2730
+ *
2731
+ * ---
2732
+ *
2733
+ * **WHAT WAS THEN MEASURED, and it moved the target as well as the width**
2734
+ * (W26 G1b and G1c; claims §5.121 and §5.122; W26 Decision Log 5 and 6 (a)).
2735
+ * The readings quoted above — 19.52 at 1x, 11.29 and 16.92 at 2x — are
2736
+ * two-Gaussian fits to ONE backdrop each, and the same reference reads 8.4
2737
+ * through `checkerboard-64` where it reads 19.5 through the impulse tile. They
2738
+ * stand as recorded, with these beside them.
2739
+ *
2740
+ * The instrument of record is the family reader: the composite this renderer
2741
+ * actually computes, fitted to the pixels jointly across every thick untinted
2742
+ * backdrop of one surface, with a per-backdrop gain. It is the only reader in
2743
+ * this wave that was held against a control — it returns vitrea's OWN drawn
2744
+ * width to 0.6 % — and on it **Apple's heavy width is 8.6–9.2 device px at
2745
+ * dpr 1 and 8.7–9.6 at dpr 2**, on both surfaces and in both schemes, with the
2746
+ * share already right to 0.07. So the 13.418 the clamped tap drew at dpr 1 was
2747
+ * half again too wide, and the reason two waves could not raise the share is
2748
+ * that more of a too-wide heavy component is more of the wrong thing.
2749
+ *
2750
+ * **The landed 9 and 9**, fitted through a ladder the reader sees one for one
2751
+ * (slope 0.995–1.111, rms 0.03–0.06 device px over seven rungs from 8 to
2752
+ * 13.418), inverted per cell: 9.48 / 8.63 / 9.19 at dpr 1 and 8.13 / 9.79 /
2753
+ * 8.37 at dpr 2. At dpr 1 the spread is 9.8 % and one number serves both spans;
2754
+ * at dpr 2 it is 20.4 % and one number does not, which is the span grading of
2755
+ * the paragraph above, now measured at 1.66 device px.
2756
+ *
2757
+ * **The mechanism has no small values** (W26 Decision Log 6 (c)). The paragraph
2758
+ * above says the mechanism does not exist at 0; the counterpart is that it does
2759
+ * exist at 0.001, where `heavyTapPlan` selects chain level 0 with a residual of
2760
+ * a thousandth of a texel and the deep sample becomes the UNBLURRED backdrop.
2761
+ * A near-zero σ is therefore the opposite of "almost off" — it is the widest
2762
+ * possible departure from the material — and a profile must name 0 or a real
2763
+ * width and nothing between. The domain is 0 or at least the chain's level-1
2764
+ * width; a floor in `heavyTapPlan` would make it continuous, and the tracker
2765
+ * carries it.
2766
+ */
2767
+ readonly sizeHeavyTapSigma: number;
2768
+ readonly sizeHeavyTapSigma2x: number;
2492
2769
  /**
2493
2770
  * The occlusion gain — "a larger size is more opaque. A smaller size is
2494
2771
  * clearer" (S284). The fraction of the *remaining* transparency the size law
@@ -2952,6 +3229,11 @@ interface MaterialProfilePatch {
2952
3229
  readonly sizeScatterRampStartFar2x?: number;
2953
3230
  readonly sizeScatterRampReach1xPx?: number;
2954
3231
  readonly sizeScatterRampReach2xPx?: number;
3232
+ readonly sizeScatterHeavyShareThick1x?: number;
3233
+ readonly sizeScatterHeavyShareThick2x?: number;
3234
+ readonly sizeToneLevelFar?: number;
3235
+ readonly sizeHeavyTapSigma?: number;
3236
+ readonly sizeHeavyTapSigma2x?: number;
2955
3237
  readonly sizeOcclusionGain?: number;
2956
3238
  readonly sizeShadowGainMax?: number;
2957
3239
  readonly lensRefractionGain?: number;
@@ -3736,6 +4018,7 @@ interface RebuildLedger {
3736
4018
  * import provider frame -> chain mip 0 (premultiplied linear, X5)
3737
4019
  * downsample mip n-1 -> chain mip n (13-tap, one pass per level)
3738
4020
  * blur x2 chain[bodyLvl] -> body (separable, residual sigma)
4021
+ * blur x2 chain[heavyLvl] -> heavy (separable, residual sigma; W26)
3739
4022
  * analysis chain[anaLvl] -> stats buffer (compute, one workgroup)
3740
4023
  * ```
3741
4024
  *
@@ -3750,6 +4033,23 @@ interface PyramidResources {
3750
4033
  readonly plan: PyramidPlan;
3751
4034
  readonly chain: GPUTexture;
3752
4035
  readonly body: GPUTexture;
4036
+ /**
4037
+ * The **heavy** blur (W26; `MaterialProfile.sizeHeavyTapSigma`) — one more
4038
+ * texture beside the body, built by the same two separable passes from
4039
+ * whichever chain level `heavyTapPlan` names, and `undefined` where the
4040
+ * profile asked for no width.
4041
+ *
4042
+ * It is here rather than in the optics pass because the optics pass is a
4043
+ * fragment shader over the glass's own area: W26 G0 measured a 9 × 9 in-shader
4044
+ * grid at +1.1 ms on the mobile bench row against 0.070 ms for the two
4045
+ * separable passes the chain already runs (W26 Decision Log 2 (b)). The price
4046
+ * of building it here is that the width is **one per source** rather than one
4047
+ * per pixel — every group sampling this source gets the same heavy width,
4048
+ * because the texture is built before any group is drawn. The reference's heavy
4049
+ * width does not grade with the span at 1x (claims §5.113 §4), which is what
4050
+ * says the material can afford that.
4051
+ */
4052
+ readonly heavy: GPUTexture | undefined;
3753
4053
  readonly stats: GPUBuffer;
3754
4054
  /** Source size epoch this allocation was made for. */
3755
4055
  readonly sizeEpoch: number;
@@ -3787,6 +4087,14 @@ interface PyramidResources {
3787
4087
  * chain's body and `bodyChainLod` belong to the previous scale.
3788
4088
  */
3789
4089
  readonly bodySigmaCss: number;
4090
+ /**
4091
+ * The heavy blur's σ in **CSS px** the build converted with, for `sameBody`'s
4092
+ * reason and by the same rule: the heavy width is a device-px quantity
4093
+ * (claims §5.113 §2), so `sizeHeavyTapSigma / dpr` is what reaches the source's
4094
+ * texels, and a window dragged between displays asks for a different heavy
4095
+ * texture from the same clean source.
4096
+ */
4097
+ readonly heavySigmaCss: number;
3790
4098
  }
3791
4099
  interface PyramidInstrumentation {
3792
4100
  /** Successful rebuilds since the store was created. */
@@ -3816,6 +4124,12 @@ interface PyramidBuildRequest {
3816
4124
  * The build resolves it once the frame's real extent is known.
3817
4125
  */
3818
4126
  readonly bodySigmaCss: number;
4127
+ /**
4128
+ * The heavy blur's σ in **CSS px** (W26), 0 where the profile declines the
4129
+ * width — at which point nothing is allocated and nothing is drawn, so a
4130
+ * material that names no heavy width pays for none of this.
4131
+ */
4132
+ readonly heavySigmaCss: number;
3819
4133
  readonly viewportCss: readonly [number, number];
3820
4134
  /**
3821
4135
  * Where the source sits on the plane, in CSS px relative to the viewport, if
package/dist/index.js CHANGED
@@ -1092,7 +1092,7 @@ function isHealthy(state) {
1092
1092
 
1093
1093
  // src/renderer-seam.ts
1094
1094
  async function loadWebGPURendererModule() {
1095
- return import('./dist-2V7EQHSR.js');
1095
+ return import('./dist-PT4PUS4K.js');
1096
1096
  }
1097
1097
  async function loadWebGPURenderer() {
1098
1098
  const { createWebGPURenderer } = await loadWebGPURendererModule();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitreajs/vitrea",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Framework-agnostic Liquid Glass material runtime: scene model, capability resolution, material policy, accessibility policy.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/SSFSKIM/designer",