@vitreajs/vitrea 0.19.0 → 0.21.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
@@ -1993,8 +1993,88 @@ interface MaterialRim {
1993
1993
  interface MaterialOuterShadow {
1994
1994
  /** Downward translation of the shadow's silhouette, CSS px. */
1995
1995
  readonly offsetPx: number;
1996
- /** Gaussian σ the silhouette is blurred by, CSS px. A `box-shadow` blur is 2σ. */
1996
+ /**
1997
+ * Gaussian σ the silhouette is blurred by, CSS px. A `box-shadow` blur is 2σ.
1998
+ *
1999
+ * Since W30 G2 this is the σ at the reference span rather than the σ full
2000
+ * stop: the three leaves below grade it with the casting span, and at their
2001
+ * inert defaults the law returns exactly this number for every span. The
2002
+ * header above records the macOS 26.5 material's own span-invariance as a
2003
+ * positive measurement, which is why that material can go on expressing
2004
+ * itself with three zeros.
2005
+ */
1997
2006
  readonly sigmaPx: number;
2007
+ /**
2008
+ * The σ law's slope: CSS px of σ per CSS px of casting span, dimensionless.
2009
+ *
2010
+ * macOS 27 blurs the outer shadow wider under a wider surface. Over spans 96
2011
+ * to 160 the measured σ is linear in the span on every bed of the macOS 27
2012
+ * capture — slope 0.128 to 0.134, holding to 2 % on the median and scale-
2013
+ * invariant in CSS px to 11 % at its worst cell (claims §5.156 §2) — while
2014
+ * the shipped single σ draws 4.2 to 7.2 times too wide below span 96 and
2015
+ * about a third too narrow at 128 and above. The whole law is
2016
+ *
2017
+ * σ_css(span) = sigmaPx + max(sigmaThinOffsetPx,
2018
+ * sigmaSlopePerSpan · (span − sigmaSpanRefPx))
2019
+ *
2020
+ * evaluated PER CASTER: the GPU tier reads the casting surface's own span per
2021
+ * pixel from the field pass's `shadowAux.z`, and the CSS tier writes one blur
2022
+ * radius per surface from `surface.spanPx`. It takes no device ratio, because
2023
+ * the cut rejected the device-px reading of the thin regime in both directions
2024
+ * (§5.156 §2's two signatures) — one function of CSS span is one mirror fewer
2025
+ * for the CSS tier to keep.
2026
+ *
2027
+ * **Ships at 0, which is a multiplied zero**: the whole span term is
2028
+ * `0 · (span − sigmaSpanRefPx)`, so the `max` sees two zeros and σ is
2029
+ * `sigmaPx` identically, at every span and every scale. Fitted in the macOS 27
2030
+ * documents by claims §5.159; the frozen macOS 26.5 material keeps the zero,
2031
+ * where it is the measurement.
2032
+ */
2033
+ readonly sigmaSlopePerSpan: number;
2034
+ /**
2035
+ * The casting span, CSS px, at which σ equals `sigmaPx` — the span the line
2036
+ * pivots about.
2037
+ *
2038
+ * The law has one flat direction: shifting `sigmaPx`, `sigmaThinOffsetPx` and
2039
+ * this constant together leaves σ unchanged at every span, so a fit has to
2040
+ * hold one of the three. **The fit holds this one, at 96** (W30 Decision Log
2041
+ * 3 (c)) — the span every bed carries sixteen cells at, and the span the
2042
+ * amplitude's own anchor `thickOcclusionAt96` is keyed to — and fits the slope
2043
+ * and the offset around it, refitting `sigmaPx` as the σ at span 96.
2044
+ *
2045
+ * **Ships at 0**, which is unreachable while the slope is 0 and which the
2046
+ * identity does not depend on: a pivot multiplied by a zero slope contributes
2047
+ * nothing whatever its value. 96 is the fit's value in claims §5.159, not the
2048
+ * default's.
2049
+ */
2050
+ readonly sigmaSpanRefPx: number;
2051
+ /**
2052
+ * The width the thin regime holds, CSS px, SIGNED, stated as an offset from
2053
+ * `sigmaPx` — the floor the line is clamped below at.
2054
+ *
2055
+ * A floor is structurally necessary rather than a fit of the thin cells: the
2056
+ * measured line crosses zero at a span of 23.6 to 30.4 on every bed and the
2057
+ * smallest declared span in the bed is 32, so without one the law emits
2058
+ * 0.27 CSS px on a 32 px surface and a negative σ on a 24 px one
2059
+ * (claims §5.156 §2).
2060
+ *
2061
+ * **Its unit is CSS px and its value is a declared reading rather than a fit**
2062
+ * (W30 Decision Log 2 (b)). The thin cells are a position on the instrument's
2063
+ * valley, not a measurement of Apple's blur: the reader's (amplitude, σ) pair
2064
+ * trades at a nearly constant product there, and the thin σ bifurcates on the
2065
+ * author's TINT — untinted cells read a 1x/2x ratio of 1.86–2.57 and their
2066
+ * tinted siblings 0.51–0.56 on the same geometry, which the material's blur
2067
+ * cannot depend on. So no order statistic over those cells is a measurement,
2068
+ * and §5.159 sets this by declaration with the statistic it is checked
2069
+ * against named.
2070
+ *
2071
+ * **Ships at 0**, an added zero under a `max` whose other arm is also zero.
2072
+ * The knee — where the floor gives way to the line — is DERIVED from the three
2073
+ * (`sigmaSpanRefPx + sigmaThinOffsetPx / sigmaSlopePerSpan`) rather than being
2074
+ * a fourth leaf, because a knee stated beside a slope and a floor is a third
2075
+ * name for a quantity two of them already fix.
2076
+ */
2077
+ readonly sigmaThinOffsetPx: number;
1998
2078
  /** Outward spread of the silhouette before the blur, CSS px. */
1999
2079
  readonly spreadPx: number;
2000
2080
  /**
@@ -2827,6 +2907,88 @@ interface MaterialProfile {
2827
2907
  */
2828
2908
  readonly sizeHeavyTapSigma: number;
2829
2909
  readonly sizeHeavyTapSigma2x: number;
2910
+ /**
2911
+ * The second heavy tap's Gaussian width in CSS px, per scale — candidate (i)'s
2912
+ * width, on `sizeHeavyTapSigma`'s own pattern and resolved by the same
2913
+ * `rampAtScale`.
2914
+ *
2915
+ * It is a second `PyramidResources` texture and a second separable pair, not a
2916
+ * uniform the optics pass evaluates, for `sizeHeavyTapSigma`'s measured
2917
+ * reason: a grid of taps at the fragment costs +1.1 ms on the mobile bench row
2918
+ * against 0.070 ms for two separable passes the chain already runs (W26
2919
+ * Decision Log 2 (b)). So the width, like the first one, is one per SOURCE.
2920
+ *
2921
+ * **Ships at 0, and the width is not what makes it inert** —
2922
+ * `sizeHeavySecondShare` is. At share 0 no texture is allocated, no pass is
2923
+ * encoded and the optics pass never reads one, so this width is unread
2924
+ * whatever it holds; 0 is chosen because it is the value at which
2925
+ * `heavyTapPlan` would also decline. A profile that names a width and leaves
2926
+ * the share at 0 gets nothing, deliberately: the share is the single gate, so
2927
+ * that the off path has exactly one condition.
2928
+ */
2929
+ readonly sizeHeavySecondSigma: number;
2930
+ readonly sizeHeavySecondSigma2x: number;
2931
+ /**
2932
+ * The second heavy sample's weight in the deep mix — a SIGNED fraction, and
2933
+ * the scheme-conditioned leaf of candidate (i).
2934
+ *
2935
+ * The deep sample becomes `heavy + share · (heavy2 − heavy)`, so a positive
2936
+ * share widens the deep component toward the second width and a NEGATIVE one
2937
+ * subtracts it — an unsharp mask on the backdrop, which is a kernel that
2938
+ * passes the middle pitch less than both ends and is the only shape on offer
2939
+ * that can do what the residual asks. The sign is what the colour scheme
2940
+ * flips: on the gated 16 px cell vitrea passes 1.57× the native structure in
2941
+ * 1x light and 0.75× in 1x dark, so the light document wants structure removed
2942
+ * at that pitch and the dark document wants it added (claims §5.156 §3, §6).
2943
+ *
2944
+ * **It is also the GATE.** The second heavy texture is built, bound and read
2945
+ * only where this is non-zero, so at 0 the mechanism costs no allocation, no
2946
+ * pass and no sample, and the deep mix is the expression W26 left — which is
2947
+ * what the 34 renderer goldens prove, since they render an explicit patch over
2948
+ * the default and every one of them is byte-identical across this commit.
2949
+ *
2950
+ * **Ships at 0, a multiplied zero**: the lerp's second term is
2951
+ * `0 · (heavy2 − heavy)`, and the branch that would read `heavy2` at all is
2952
+ * not taken.
2953
+ */
2954
+ readonly sizeHeavySecondShare: number;
2955
+ /**
2956
+ * The gain on `kScatter` per unit of the source's measured scale statistic
2957
+ * about `sizeScatterScaleRef` — candidate (ii)'s scheme-conditioned leaf, a
2958
+ * fraction per unit of statistic, signed.
2959
+ *
2960
+ * `kScatter` is the share of the deep component in the body's mix, and this
2961
+ * adds `sizeScatterScaleGain · (stat − sizeScatterScaleRef)` to it before the
2962
+ * clamp — so a backdrop whose structure sits at a finer scale than the
2963
+ * reference takes a different share of the heavy component from one whose
2964
+ * structure is coarser, which is a transmission that depends on the backdrop's
2965
+ * scale rather than only on the surface's span. The statistic is the analysis
2966
+ * pass's per-source EDGE DENSITY (mean luminance-gradient magnitude per texel,
2967
+ * a reciprocal length), resolved on the CPU where the readback is and handed
2968
+ * to the optics pass as one number per group.
2969
+ *
2970
+ * The sign is the scheme's, for `sizeHeavySecondShare`'s reason and read off
2971
+ * the same cell.
2972
+ *
2973
+ * **Ships at 0, a multiplied zero**: the added term is
2974
+ * `0 · (stat − sizeScatterScaleRef)` and `kScatter` is already clamped to
2975
+ * [0, 1], so the clamp that follows is the identity on it.
2976
+ */
2977
+ readonly sizeScatterScaleGain: number;
2978
+ /**
2979
+ * The reference value of the per-source scale statistic the gain above is
2980
+ * measured about — the backdrop scale at which the operator does nothing.
2981
+ *
2982
+ * Not scheme-conditioned: a spatial scale is a property of the source raster
2983
+ * and the raster is the same in both schemes. Fitted in §5.159 as the edge
2984
+ * density of the pitch the two sides of the residual straddle, once the ladder
2985
+ * has a macOS 27 reading.
2986
+ *
2987
+ * **Ships at 0**, and the argument for its inertness is not a multiplied zero
2988
+ * but a different one and sufficient on its own: the gain that multiplies the
2989
+ * difference from it is 0, so no value of this constant can reach the mix.
2990
+ */
2991
+ readonly sizeScatterScaleRef: number;
2830
2992
  /**
2831
2993
  * The occlusion gain — "a larger size is more opaque. A smaller size is
2832
2994
  * clearer" (S284). The fraction of the *remaining* transparency the size law
@@ -3104,6 +3266,60 @@ interface MaterialProfile {
3104
3266
  * it at every ratio and this anchor is the identity on the landed material.
3105
3267
  */
3106
3268
  readonly collapseTransmission2x: number;
3269
+ /**
3270
+ * **How much of the blurred backdrop's CHROMATICITY the body restores** (W31;
3271
+ * claims §5.161 §5, fitted in §5.164) — a fraction in [0, 1], inert at 0.
3272
+ *
3273
+ * The body is a neutral plate composited over the blurred backdrop, so what a
3274
+ * photograph's hues survive the composite at is `1 − sizedAlpha` — 0.513 on the
3275
+ * macOS 27 light document and 0.095 on the dark one, against a reference that
3276
+ * reads 0.90–0.97 of its own backdrop's chroma on the same cells. Apple's body
3277
+ * at the same LEVEL keeps the hue, which is what a darkening acting on the
3278
+ * backdrop's luma while leaving its chromaticity alone does and what a plate
3279
+ * does not. No constant the material had could close that (W29 Decision Log
3280
+ * 6 (c)): it is a mechanism, and this is the one leaf that adds it.
3281
+ *
3282
+ * Applied immediately after `colour = mix(backdrop, adapted, presentAlpha)`
3283
+ * and before the tint composition, so an author's tint still displaces the
3284
+ * result. The colour is mixed toward `backdrop · (Y / Y_backdrop)` — the
3285
+ * backdrop's chromaticity carried to the colour's OWN linear luma — by this
3286
+ * fraction, so **luma is preserved by construction rather than by
3287
+ * correction**: both endpoints of the mix have linear luma exactly `Y`,
3288
+ * `dot(rgb, (0.2126, 0.7152, 0.0722))` is a linear functional, and the mix has
3289
+ * luma `Y` in exact arithmetic. (OKLab `L` is NOT linear luma, which is why the
3290
+ * formulation is in linear RGB: holding `L` while moving toward a saturated
3291
+ * chromaticity moves `Y` by −22 % at sRGB blue and +10 % at green.) Gamut is
3292
+ * taken by scaling chroma toward the neutral at fixed luma, never by clipping
3293
+ * per channel.
3294
+ *
3295
+ * **No `toneAdapt` gate**, and the reason is measured rather than assumed
3296
+ * (claims §5.161 §11, finding N1). A retention toward the backdrop's
3297
+ * chromaticity is the identity wherever the backdrop is achromatic, and the
3298
+ * only region `backdropToneAdaptation` can fire in on the macOS 27 documents
3299
+ * is `x < backdropToneHigh` = 1e-4 of linear light — achromatic to within the
3300
+ * capture's own quantisation. A future document that re-opens
3301
+ * `backdropToneHigh` must re-examine that, and must read it at a span at or
3302
+ * below `sizeSpanMin` where `backdropToneSizeBias` is not there to help.
3303
+ *
3304
+ * Conditioned by SCHEME (two values across the light and dark documents) and
3305
+ * by POSE: each receded document carries its own, read on the inactive cells,
3306
+ * because the recede's untinted body has no chroma law at all otherwise —
3307
+ * W27c's collapse acts on the tint SEED inside `if (tintK > 0.0)`. The
3308
+ * accessibility documents inherit the light value.
3309
+ *
3310
+ * **Ships at 0, a multiplied zero**: the mix's second term is `0 · (target −
3311
+ * colour)` and `colour` leaves the composite exactly as
3312
+ * `mix(backdrop, adapted, presentAlpha)` produced it, which is why the 34
3313
+ * renderer goldens are byte-identical across the commit that added it and why
3314
+ * `MATERIAL_IDENTITY_TABLE` can drop it from the fingerprint.
3315
+ *
3316
+ * One place it cannot act, declared rather than discovered: on the unsampled
3317
+ * layer path (`flags.x <= 0.5` and not `domMaterial`) the shader overwrites
3318
+ * `colour` with `adapted` and writes a layer for the browser to composite, so
3319
+ * there is no backdrop in hand and no chromaticity to restore toward. The
3320
+ * retention is silently the identity there.
3321
+ */
3322
+ readonly bodyChromaRetention: number;
3107
3323
  /**
3108
3324
  * **The rim that survives the collapse (W23)** — the one mark the collapsed
3109
3325
  * appearance keeps.
@@ -3320,6 +3536,11 @@ interface MaterialProfilePatch {
3320
3536
  readonly sizeToneLevelFar?: number;
3321
3537
  readonly sizeHeavyTapSigma?: number;
3322
3538
  readonly sizeHeavyTapSigma2x?: number;
3539
+ readonly sizeHeavySecondSigma?: number;
3540
+ readonly sizeHeavySecondSigma2x?: number;
3541
+ readonly sizeHeavySecondShare?: number;
3542
+ readonly sizeScatterScaleGain?: number;
3543
+ readonly sizeScatterScaleRef?: number;
3323
3544
  readonly sizeOcclusionGain?: number;
3324
3545
  readonly sizeShadowGainMax?: number;
3325
3546
  readonly lensRefractionGain?: number;
@@ -3349,6 +3570,7 @@ interface MaterialProfilePatch {
3349
3570
  readonly backdropToneSizeBias?: number;
3350
3571
  readonly collapseTransmission?: number;
3351
3572
  readonly collapseTransmission2x?: number;
3573
+ readonly bodyChromaRetention?: number;
3352
3574
  readonly rimCollapsed?: number;
3353
3575
  readonly rimCollapsedTinted?: number;
3354
3576
  readonly rimTintChroma?: number;
@@ -4176,6 +4398,19 @@ interface PyramidResources {
4176
4398
  * says the material can afford that.
4177
4399
  */
4178
4400
  readonly heavy: GPUTexture | undefined;
4401
+ /**
4402
+ * The **second** heavy blur (W30 G2; `MaterialProfile.sizeHeavySecondSigma`
4403
+ * gated on `sizeHeavySecondShare`) — the same construction one width along,
4404
+ * and `undefined` wherever the material leaves the share at 0, which is
4405
+ * everywhere on the landed material.
4406
+ *
4407
+ * It is a second texture and a second separable pair rather than a second tap
4408
+ * in the optics pass for the reason the first one is: the price of a width at
4409
+ * the fragment is +1.1 ms on the mobile bench row against 0.070 ms here (W26
4410
+ * Decision Log 2 (b)). The price of having it at all is therefore paid by the
4411
+ * material that names it and by nothing else.
4412
+ */
4413
+ readonly heavy2: GPUTexture | undefined;
4179
4414
  readonly stats: GPUBuffer;
4180
4415
  /** Source size epoch this allocation was made for. */
4181
4416
  readonly sizeEpoch: number;
@@ -4221,6 +4456,11 @@ interface PyramidResources {
4221
4456
  * texture from the same clean source.
4222
4457
  */
4223
4458
  readonly heavySigmaCss: number;
4459
+ /**
4460
+ * The SECOND heavy blur's σ in **CSS px** the build converted with (W30 G2),
4461
+ * on `heavySigmaCss`'s own rule and for the same staleness reason.
4462
+ */
4463
+ readonly heavy2SigmaCss: number;
4224
4464
  }
4225
4465
  interface PyramidInstrumentation {
4226
4466
  /** Successful rebuilds since the store was created. */
@@ -4256,6 +4496,14 @@ interface PyramidBuildRequest {
4256
4496
  * material that names no heavy width pays for none of this.
4257
4497
  */
4258
4498
  readonly heavySigmaCss: number;
4499
+ /**
4500
+ * The SECOND heavy blur's σ in **CSS px** (W30 G2), 0 where the material
4501
+ * leaves `sizeHeavySecondShare` at 0 — at which point nothing is allocated
4502
+ * and nothing is drawn, exactly as for the first one. The share is the gate
4503
+ * and `heavySecondTapSigmaAtScale` applies it, so this arrives already 0
4504
+ * rather than being re-decided here.
4505
+ */
4506
+ readonly heavy2SigmaCss: number;
4259
4507
  readonly viewportCss: readonly [number, number];
4260
4508
  /**
4261
4509
  * 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-VQPRX7BX.js');
1095
+ return import('./dist-N437MSRG.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.19.0",
3
+ "version": "0.21.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",