@vitreajs/vitrea 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../geometry/src/channels.ts","../../geometry/src/errors.ts","../../geometry/src/corner.ts","../../geometry/src/coefficients.ts","../../geometry/src/apple.ts","../../geometry/src/shape.ts","../../geometry/src/concentric.ts","../../geometry/src/union.ts","../../geometry/src/wgsl.ts","../../motion/src/channels.ts","../../motion/src/states.ts","../../motion/src/drivers/low-pass.ts","../../motion/src/tunables.ts"],"names":["fit"],"mappings":"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","file":"chunk-ST6GFEQT.js","sourcesContent":["/**\n * X8 — the shape channel set, and the vocabulary every other module speaks.\n *\n * `{ center, size, radii, smoothing, thickness }`. Every channel is numeric,\n * which is what makes v1's morphs parametric rather than crossfaded: capsule\n * <-> rounded rect, button <-> menu platter, indicator slides.\n *\n * Two conventions worth stating once, because mixing them up is the easiest way\n * to get corner math subtly wrong:\n *\n * - `size` is the FULL width and height. Corner math works in half-extents\n * (`halfW`, `halfH`), which is what the pseudo-SDF families are written in.\n * - `smoothing` here is the value the author wrote. The budget clamp\n * (§corner.ts) is applied at derivation and lands in `ResolvedCorner`, so\n * shrinking a shape and growing it back is lossless (X8 rider 1).\n */\n\nexport type Vec2 = readonly [x: number, y: number];\n\n/** Corner radii, clockwise from top-left. */\nexport type CornerRadii = readonly [\n topLeft: number,\n topRight: number,\n bottomRight: number,\n bottomLeft: number,\n];\n\n/** The v1 shape families (§Geometry). Not \"Apple's taxonomy\" — vitrea's supported set. */\nexport const SHAPE_FAMILIES = [\"fixed-rounded-rect\", \"capsule\", \"concentric-rounded-rect\"] as const;\n\nexport type ShapeFamily = (typeof SHAPE_FAMILIES)[number];\n\n/**\n * Public sugar over the internal numeric corner profile: `\"circular\"` is\n * `smoothing: 0`, `\"continuous\"` is the calibration-determined Apple-matching\n * corner (§apple.ts). Authoring a number instead puts the shape on the Figma\n * smoothing axis, which is the interpolable authoring family.\n */\nexport type CornerProfile = \"continuous\" | \"circular\";\n\n/**\n * X8 — the shape channel set. Every channel is numeric, which is what makes\n * v1's parametric morphs total: capsule <-> rounded rect, button <-> platter.\n */\nexport interface ShapeChannels {\n readonly center: Vec2;\n readonly size: Vec2;\n readonly radii: CornerRadii;\n /** 0 = circular arc corners, 1 = maximum continuous-curvature smoothing. */\n readonly smoothing: number;\n /** Material thickness, in CSS px, driving lensing depth and shadow. */\n readonly thickness: number;\n}\n\nexport const lerp = (a: number, b: number, t: number): number => a + (b - a) * t;\n\nexport const clamp = (x: number, lo: number, hi: number): number => Math.min(Math.max(x, lo), hi);\n\n/** Hermite ramp, 0 below `edge0` and 1 above `edge1`. C1 at both ends. */\nexport function smoothstep(edge0: number, edge1: number, x: number): number {\n if (edge1 <= edge0) return x < edge0 ? 0 : 1;\n const t = clamp((x - edge0) / (edge1 - edge0), 0, 1);\n return t * t * (3 - 2 * t);\n}\n\n/**\n * Interpolate the whole channel set. Total by construction: there is no channel\n * that needs a special case, which is the property X8 exists to guarantee.\n */\nexport function lerpShapeChannels(a: ShapeChannels, b: ShapeChannels, t: number): ShapeChannels {\n return {\n center: [lerp(a.center[0], b.center[0], t), lerp(a.center[1], b.center[1], t)],\n size: [lerp(a.size[0], b.size[0], t), lerp(a.size[1], b.size[1], t)],\n radii: [\n lerp(a.radii[0], b.radii[0], t),\n lerp(a.radii[1], b.radii[1], t),\n lerp(a.radii[2], b.radii[2], t),\n lerp(a.radii[3], b.radii[3], t),\n ],\n smoothing: lerp(a.smoothing, b.smoothing, t),\n thickness: lerp(a.thickness, b.thickness, t),\n };\n}\n\n/**\n * The channel set as a flat vector, in a fixed order. Exists so continuity\n * tests can sweep \"every channel\" without enumerating them by hand — a channel\n * added to X8 and not added here shows up as a length mismatch.\n */\nexport const SHAPE_CHANNEL_COUNT = 10;\n\nexport function flattenShapeChannels(c: ShapeChannels): number[] {\n return [\n c.center[0],\n c.center[1],\n c.size[0],\n c.size[1],\n c.radii[0],\n c.radii[1],\n c.radii[2],\n c.radii[3],\n c.smoothing,\n c.thickness,\n ];\n}\n\n/** Half-extents, which is the frame all corner and field math works in. */\nexport function halfExtents(size: Vec2): { halfW: number; halfH: number } {\n return { halfW: Math.max(size[0], 0) / 2, halfH: Math.max(size[1], 0) / 2 };\n}\n\nexport const uniformRadii = (r: number): CornerRadii => [r, r, r, r];\n","/**\n * Dev-mode errors. Geometry has no diagnostics channel of its own (it has no\n * dependency on core), so everything it refuses, it refuses by throwing — which\n * is the right severity for the two things it refuses: a shape the v1 evaluator\n * cannot express, and a morph between shapes whose corners are not on the same\n * reference curve. Both are authoring mistakes with no sensible fallback, and\n * inventing one would produce a shape the declared error bound does not cover.\n */\n\nexport type GeometryErrorCode =\n /** X8 rider 3: the v1 corner algebra is mirror-symmetric, so all four radii must match. */\n | \"non-uniform-radii\"\n /** A morph whose endpoints sit on different corner reference curves. */\n | \"corner-reference-mismatch\"\n /** A channel value the shape families cannot express (negative size, NaN). */\n | \"invalid-channel\";\n\nexport class GeometryError extends Error {\n readonly code: GeometryErrorCode;\n\n constructor(code: GeometryErrorCode, message: string) {\n super(message);\n this.name = \"GeometryError\";\n this.code = code;\n }\n}\n","/**\n * The corner: the rounding-and-smoothing budget clamp, and the cubic-offset\n * scalars of the reference contour construction.\n *\n * X8 rider 1 — `smoothing` is clamped by a budget derived from `size` and\n * `radii`. This is a real interaction between X8 channels and it is\n * load-bearing three times over:\n *\n * - it is why a capsule is EXACT. At the capsule limit the radius equals the\n * budget, so `budget / r - 1` is 0 and effective smoothing is forced to\n * exactly 0: a capsule is always a true stadium, and every pseudo-SDF family\n * is exact on a stadium (S2, §Capsule limit).\n * - it bounds the worst-case corner radius, and the field's contour deviation\n * is linear in that radius. Smoothing 1.0 is only reachable when\n * `r <= 1/4` of the short side, which is what keeps the declared bound flat\n * across three orders of magnitude of shape size.\n * - it is CONTINUOUS in size (`d s_eff / d size = 1/r` while clamped, 0 once\n * the clamp releases), so a morph that shrinks a shape through the clamp does\n * not snap. Asserted in `test/corner.test.ts`.\n *\n * The authored smoothing is preserved on the channel and the clamp is applied\n * here, at derivation — so shrinking a shape and growing it back is lossless.\n *\n * The construction itself is Figma's (\"Desperately seeking squircles\"), which is\n * the family the spec names as the authoring parameterization: one corner is\n * straight edge -> cubic -> circular arc -> cubic -> straight edge, with the\n * cubics ramping curvature from 0 at the straight edge up to 1/r at the arc.\n * `smoothing = 0` degenerates the cubics to zero length and reproduces the plain\n * circular rounded rectangle exactly.\n */\n\nconst toRad = (deg: number): number => (deg * Math.PI) / 180;\n\n/**\n * The rounding-and-smoothing budget for one corner. Figma splits each side\n * between its two corners in proportion to their radii; with a uniform radius\n * that is half the side, and the corner's budget is the min over its two sides.\n * In half-extents that is `min(halfW, halfH)`.\n */\nexport function cornerBudget(halfW: number, halfH: number): number {\n return Math.min(halfW, halfH);\n}\n\n/**\n * The continuous smoothing ceiling at a given radius and budget: the largest\n * smoothing whose corner reach `(1 + s) * r` still fits the budget.\n *\n * Exposed because it is the thing a morph must stay under, and a caller that\n * wants to check \"will this shape be clamped?\" should read the same function the\n * resolver reads rather than re-deriving it.\n */\nexport function smoothingCeiling(radius: number, budget: number): number {\n if (radius <= 0) return Number.POSITIVE_INFINITY;\n return Math.max(0, budget / radius - 1);\n}\n\nexport interface CornerConstruction {\n /** radius actually used, after the budget clamp on the radius itself */\n readonly radius: number;\n /** smoothing actually used, after the budget clamp */\n readonly smoothingEff: number;\n /** how far the corner reaches along each adjacent edge from the corner vertex */\n readonly reach: number;\n /** cubic control-offset scalars of the reference construction */\n readonly a: number;\n readonly b: number;\n readonly c: number;\n readonly d: number;\n /** axis-aligned extent of the circular arc section (equal in x and y) */\n readonly arcSectionLength: number;\n /** central angle of the circular arc, radians */\n readonly arcMeasure: number;\n /** min(halfW, halfH) — the per-corner rounding-and-smoothing budget */\n readonly budget: number;\n}\n\n/**\n * Apply the budget clamp and derive the reference construction's scalars.\n *\n * `radius` and `smoothing` are the authored values; both are clamped here. The\n * radius is clamped first, because a radius larger than the budget cannot be\n * honoured at any smoothing.\n */\nexport function resolveCornerConstruction(\n halfW: number,\n halfH: number,\n radius: number,\n smoothing: number,\n): CornerConstruction {\n const budget = cornerBudget(halfW, halfH);\n const r = Math.min(Math.max(radius, 0), budget);\n const requested = Math.min(Math.max(smoothing, 0), 1);\n const s = Math.min(requested, smoothingCeiling(r, budget));\n const reach = Math.min((1 + s) * r, budget);\n\n const arcMeasureDeg = 90 * (1 - s);\n const arcSectionLength = Math.sin(toRad(arcMeasureDeg / 2)) * r * Math.SQRT2;\n\n const angleAlpha = (90 - arcMeasureDeg) / 2;\n const p3ToP4Distance = r * Math.tan(toRad(angleAlpha / 2));\n\n const angleBeta = 45 * s;\n const c = p3ToP4Distance * Math.cos(toRad(angleBeta));\n const d = c * Math.tan(toRad(angleBeta));\n\n const b = (reach - arcSectionLength - c - d) / 3;\n const a = 2 * b;\n\n return {\n radius: r,\n smoothingEff: s,\n reach,\n a,\n b,\n c,\n d,\n arcSectionLength,\n arcMeasure: toRad(arcMeasureDeg),\n budget,\n };\n}\n","/**\n * The fitted corner-correction coefficient tables. TRANSCRIBED FROM S2, not\n * recomputed — and deliberately so.\n *\n * Source: `spikes/s2-geometry-field/src/coefficients.ts`, itself generated by\n * that spike's `npm run fit`. The fit is an exact Chebyshev (L-inf) solve, not a\n * general optimizer: pinning the corner offset to the true corner reach makes\n * the objective linear in the coefficients, and the residual equioscillates\n * across 11 alternating extrema. The reported deviation is therefore the\n * family's capability rather than an optimizer's luck.\n *\n * These stay committed for the reason S2 gives: a table that silently moves when\n * the fitter is touched is not a regression target. `test/error-bound.test.ts`\n * asserts the declared bound against this table, so regenerating it means\n * re-deriving the bound and updating the spec's Decision Log.\n *\n * Two tables, because there are two reference curves and S2 measured that the\n * choice between them matters 6.4x more than the field family does:\n *\n * - `FIGMA_RSUPN_TABLE` — indexed by EFFECTIVE smoothing (after the budget\n * clamp) across the whole [0, 1] range. This is the interpolable authoring\n * axis, and the axis the declared bound is measured on.\n * - `APPLE_RSUPN` — one coefficient set, fit directly against Apple's measured\n * `.continuous` corner. `profile: \"continuous\"` resolves to this\n * (Decision Log #20: routing through Figma at smoothing 0.66 was measured and\n * rejected at 6.4x the value error).\n *\n * Families C (`rsup`) and D (`rsupn`) share a zero level set and are fit\n * separately only because the normalization shifts the L-inf optimum slightly.\n */\n\n/**\n * Number of correction terms. The basis is `s2^2 .. s2^(N+1)` where\n * `s2 = sin(2*theta)`.\n *\n * Starting at the SQUARE is not arbitrary: the true radial support has\n * `R'(0) = 0`, because the corner leaves the straight edge both tangentially and\n * with zero curvature, so any basis with a linear term in `s2` would have to\n * cancel it. Odd powers above that are included because `R` is not an even\n * function of theta about 0 — a `sin^2`-only basis converges visibly more\n * slowly.\n *\n * Order is a tuning knob, not a design commitment. Five terms give 1.38e-3 r\n * worst case with coefficients bounded by 3.3. Seven give 6.6e-4 r but push\n * coefficients to ~56, which starts to matter in f32 — at which point the\n * polynomial wants re-expressing in a Chebyshev basis rather than monomials.\n */\nexport const RSUP_BASIS_ORDER = 5;\n\nexport type CornerCoefficients = readonly [number, number, number, number, number];\n\nexport const ZERO_COEFFICIENTS: CornerCoefficients = [0, 0, 0, 0, 0];\n\nexport interface CoefficientRow {\n /** effective smoothing, after the budget clamp */\n readonly sEff: number;\n readonly k: CornerCoefficients;\n /** max |field| on the true contour in units of the corner radius — scale-free */\n readonly contourDevPerR: number;\n}\n\n/**\n * Family D (`rsupn`) against the Figma corner family, per effective smoothing.\n *\n * The row at `sEff: 0` is exactly zero rather than the fitter's 1e-13 residue,\n * so `smoothing: 0` is a plain circular rounded rectangle to the last bit — an\n * exactness property the suite asserts rather than approximates.\n */\nexport const FIGMA_RSUPN_TABLE: readonly CoefficientRow[] = [\n { sEff: 0, k: [0, 0, 0, 0, 0], contourDevPerR: 4.44089e-15 },\n { sEff: 0.05, k: [0.17192786, -0.57453584, 0.93678851, -0.74224658, 0.22784924], contourDevPerR: 0.0000787263 },\n { sEff: 0.1, k: [0.15267992, -0.12909167, -0.22499519, 0.42555348, -0.18655278], contourDevPerR: 0.000220143 },\n { sEff: 0.15, k: [0.08481224, 0.39460055, -1.27818536, 1.3146661, -0.46204673], contourDevPerR: 0.000208252 },\n { sEff: 0.2, k: [0.06710202, 0.4426899, -1.08444805, 0.90833394, -0.26486554], contourDevPerR: 0.000317781 },\n { sEff: 0.25, k: [0.08593485, 0.21389316, -0.284278, -0.03442399, 0.10203143], contourDevPerR: 0.000393427 },\n { sEff: 0.3, k: [0.12086127, -0.09837148, 0.58740362, -0.92505881, 0.41092682], contourDevPerR: 0.000384627 },\n { sEff: 0.35, k: [0.19195982, -0.58065521, 1.68747114, -1.90488976, 0.71339719], contourDevPerR: 0.00037628 },\n { sEff: 0.4, k: [0.20580383, -0.64359429, 1.72183146, -1.7735827, 0.60749205], contourDevPerR: 0.000554941 },\n { sEff: 0.45, k: [0.20963391, -0.6235343, 1.53893663, -1.42961335, 0.43272753], contourDevPerR: 0.000696599 },\n { sEff: 0.5, k: [0.18345838, -0.39738895, 0.86930065, -0.6162596, 0.09847429], contourDevPerR: 0.000729633 },\n { sEff: 0.55, k: [0.15021017, -0.10642312, 0.03456218, 0.35031318, -0.28210743], contourDevPerR: 0.00065723 },\n { sEff: 0.6, k: [0.0574571, 0.54959359, -1.57948482, 2.01134453, -0.88391405], contourDevPerR: 0.000533969 },\n { sEff: 0.65, k: [0.0290426, 0.79150557, -2.2341988, 2.71564668, -1.13881723], contourDevPerR: 0.000705984 },\n { sEff: 0.7, k: [0.00610502, 0.95536509, -2.62411085, 3.08626749, -1.25256664], contourDevPerR: 0.000951783 },\n { sEff: 0.75, k: [-0.00912482, 1.0608592, -2.86162189, 3.29090302, -1.30280908], contourDevPerR: 0.00120108 },\n { sEff: 0.8, k: [-0.02095565, 1.12907188, -2.97794712, 3.34408477, -1.28941254], contourDevPerR: 0.00134355 },\n { sEff: 0.85, k: [0.01389408, 0.89302668, -2.39016784, 2.70448292, -1.03018516], contourDevPerR: 0.00138316 },\n { sEff: 0.9, k: [0.01628625, 0.79606496, -2.00469962, 2.1767657, -0.78757687], contourDevPerR: 0.00120458 },\n { sEff: 0.95, k: [0.06037106, 0.48294462, -1.1916487, 1.27094865, -0.42038678], contourDevPerR: 0.000843346 },\n { sEff: 1, k: [0.10910234, 0.09555105, -0.1426663, 0.0921919, 0.05309191], contourDevPerR: 0.000328245 },\n];\n\n/** Family C (`rsup`) against the same family. Same zero level set, own L-inf fit. */\nexport const FIGMA_RSUP_TABLE: readonly CoefficientRow[] = [\n { sEff: 0, k: [0, 0, 0, 0, 0], contourDevPerR: 4.55191e-15 },\n { sEff: 0.05, k: [0.17229922, -0.57843267, 0.94745447, -0.75342668, 0.23188652], contourDevPerR: 0.0000795723 },\n { sEff: 0.1, k: [0.1528229, -0.13030581, -0.2214859, 0.42143243, -0.18487674], contourDevPerR: 0.000221321 },\n { sEff: 0.15, k: [0.08799722, 0.37596759, -1.23860352, 1.27833243, -0.44984249], contourDevPerR: 0.00020311 },\n { sEff: 0.2, k: [0.06791092, 0.43598426, -1.06654674, 0.88960434, -0.25815276], contourDevPerR: 0.000314471 },\n { sEff: 0.25, k: [0.08568973, 0.21482387, -0.2852117, -0.03448269, 0.10234124], contourDevPerR: 0.000397169 },\n { sEff: 0.3, k: [0.12107115, -0.10034704, 0.59290929, -0.93110813, 0.41323529], contourDevPerR: 0.000389318 },\n { sEff: 0.35, k: [0.19473757, -0.60204839, 1.74425741, -1.96756079, 0.73790198], contourDevPerR: 0.000396366 },\n { sEff: 0.4, k: [0.20633019, -0.64612615, 1.7257626, -1.77580066, 0.60777871], contourDevPerR: 0.000562865 },\n { sEff: 0.45, k: [0.20740344, -0.60694729, 1.49567287, -1.38229267, 0.41422845], contourDevPerR: 0.000702136 },\n { sEff: 0.5, k: [0.18442027, -0.40109927, 0.87301409, -0.61604528, 0.09728682], contourDevPerR: 0.000741861 },\n { sEff: 0.55, k: [0.14433866, -0.07398101, -0.03198855, 0.41009473, -0.30191682], contourDevPerR: 0.000669581 },\n { sEff: 0.6, k: [0.05594672, 0.55731115, -1.59567242, 2.0268849, -0.88947808], contourDevPerR: 0.000540505 },\n { sEff: 0.65, k: [0.02895533, 0.78966635, -2.22398983, 2.69966652, -1.13103881], contourDevPerR: 0.000713798 },\n { sEff: 0.7, k: [0.00364655, 0.97120044, -2.65963414, 3.1198731, -1.26404382], contourDevPerR: 0.000983567 },\n { sEff: 0.75, k: [-0.00900127, 1.06159349, -2.86306846, 3.29036733, -1.30166793], contourDevPerR: 0.00123106 },\n { sEff: 0.8, k: [-0.02181546, 1.13237968, -2.9794545, 3.33958794, -1.28583799], contourDevPerR: 0.00137656 },\n { sEff: 0.85, k: [0.01079982, 0.90842707, -2.41502297, 2.7192196, -1.03234863], contourDevPerR: 0.00140703 },\n { sEff: 0.9, k: [0.02082712, 0.78785003, -2.01122302, 2.1960281, -0.79663302], contourDevPerR: 0.00122131 },\n { sEff: 0.95, k: [0.06028054, 0.48151129, -1.18355152, 1.25863912, -0.41464021], contourDevPerR: 0.000863628 },\n { sEff: 1, k: [0.10998181, 0.08963148, -0.12832942, 0.07742006, 0.05857178], contourDevPerR: 0.000337869 },\n];\n\n/**\n * Fits against Apple's actual `.continuous` corner. Apple's curve has no\n * smoothing parameter, so each is a single coefficient set. Measured at\n * 6.06e-4 r contour deviation — ~3x closer to Apple than Figma's best-matching\n * smoothing can get, and 6.4x better on band value error than routing through\n * Figma's family (0.104 px vs 0.670 px).\n *\n * The residual barely improves with a higher-order basis (6.06e-4 -> 6.02e-4 for\n * one extra term), because it is limited by Apple's own 2.4532-degree tangent\n * break rather than by the polynomial. This field is as close to Apple's curve\n * as a smooth field can usefully get.\n */\nexport const APPLE_RSUPN = {\n k: [0.058939, 0.53608908, -1.48273371, 1.77253595, -0.74059491] as CornerCoefficients,\n contourDevPerR: 0.000606381,\n} as const;\n\nexport const APPLE_RSUP = {\n k: [0.05944375, 0.53352847, -1.47723609, 1.76689797, -0.73837656] as CornerCoefficients,\n contourDevPerR: 0.00062355,\n} as const;\n\n/**\n * Linear interpolation into a coefficient table by effective smoothing.\n *\n * Real shapes rarely land on the fit grid: the budget clamp produces arbitrary\n * effective smoothing. `test/error-bound.test.ts` asserts the bound still holds\n * at off-grid values, which is what says the 0.05 grid is dense enough.\n */\nexport function coefficientsAt(\n table: readonly CoefficientRow[],\n sEff: number,\n): { k: CornerCoefficients; contourDevPerR: number } {\n const first = table[0];\n const last = table[table.length - 1];\n if (first === undefined || last === undefined) {\n throw new Error(\"empty coefficient table\");\n }\n if (sEff <= first.sEff) return { k: first.k, contourDevPerR: first.contourDevPerR };\n if (sEff >= last.sEff) return { k: last.k, contourDevPerR: last.contourDevPerR };\n\n for (let i = 1; i < table.length; i++) {\n const lo = table[i - 1];\n const hi = table[i];\n if (lo === undefined || hi === undefined) break;\n if (sEff <= hi.sEff) {\n const u = (sEff - lo.sEff) / (hi.sEff - lo.sEff);\n const mix = (j: 0 | 1 | 2 | 3 | 4): number => lo.k[j] + u * (hi.k[j] - lo.k[j]);\n return {\n k: [mix(0), mix(1), mix(2), mix(3), mix(4)],\n contourDevPerR: lo.contourDevPerR + u * (hi.contourDevPerR - lo.contourDevPerR),\n };\n }\n }\n return { k: last.k, contourDevPerR: last.contourDevPerR };\n}\n","/**\n * Apple's actual `.continuous` corner — the curve `profile: \"continuous\"` is fit\n * against.\n *\n * S2's most consequential finding was not the error bound. It was that the bound\n * is **smaller than the distance between the reference contour family and\n * Apple's actual corner**: the pseudo-SDF is not the limiting factor on\n * geometric fidelity, the choice of reference curve is. Routing\n * `profile: \"continuous\"` through the Figma family at its best-matching\n * smoothing costs 6.4x the value error of fitting the same field directly to\n * Apple's curve (0.670 px vs 0.104 px), which is why Decision Log #20 rebased\n * the seed onto a direct fit and rejected the intermediate family.\n *\n * ## Provenance\n *\n * The control points are a CGPath dump of\n * `RoundedRectangle(cornerRadius:style:.continuous)` on macOS 26, normalized by\n * the corner radius. The edge reach is not reverse-engineered folklore: Apple\n * publishes it as `+[CALayer cornerCurveExpansionFactor:]` for\n * `kCACornerCurveContinuous`. Every derived property is recomputed from these\n * numbers alone in `test/apple.test.ts`, so the dump is checked rather than\n * trusted:\n *\n * - three cubics per corner, no arcs and no quadratics;\n * - the MIDDLE cubic IS a circular arc — its handle length matches the exact\n * arc-as-cubic identity `(4/3)*tan(sweep/4)*R` to 1.5e-7, at radius\n * 0.931253 r, centre 0.950002 r, sweep 50.0000 degrees. (The handle identity\n * is the real proof; radius and centre alone could be a coincidence of\n * symmetry.);\n * - G2 — zero curvature — where the corner meets the straight edge. That is\n * the join that reads visually, and Apple gets it right;\n * - a **2.4532-degree tangent break** at both shoulder/arc joins. Apple's own\n * path is not even G1 there.\n *\n * That last number is the most useful one in the whole spike, because it\n * calibrates the gradient budget: the curve vitrea is chasing carries a\n * 2.45-degree normal discontinuity of its own, and the field's worst rim-band\n * gradient error is 1.55 degrees. Chasing further precision on the normal is\n * chasing an artifact of the target.\n */\n\nimport {\n type Contour,\n type ContourSegment,\n type Point,\n ringFromCorner,\n translateSegment,\n} from \"./contour\";\nimport type { CornerConstruction } from \"./corner\";\nimport type { Vec2 } from \"./channels\";\n\n/** Apple's published corner-curve expansion factor for `.continuous`. */\nexport const APPLE_REACH = 1.52866495;\n\n/**\n * The smoothing seed for `profile: \"continuous\"`.\n *\n * Expressed on the same scale as the `smoothing` channel — a corner of reach\n * `(1 + s) * r` — so the two references describe their corners in the same\n * units even though only one of them has a free smoothing parameter. This is\n * the value the public `profile: \"continuous\"` sugar resolves to; it is\n * internal and calibration-refinable, seeded here from S2's direct fit.\n */\nexport const APPLE_CONTINUOUS_SMOOTHING_SEED = APPLE_REACH - 1;\n\n/**\n * Radius above which Apple can no longer honour the reach and the corner warps:\n * `reach * r` must fit in half the side. Apple's budget policy — clamp the\n * radius — differs from the reference family's, which clamps smoothing. Shapes\n * above this ratio are not comparable to Apple and C7 should measure them\n * separately or exclude them from fidelity claims.\n */\nexport const APPLE_SATURATION_RADIUS_RATIO = 1 / (2 * APPLE_REACH);\n\n/**\n * The Figma smoothing that best reproduces Apple's corner — one of the spec's\n * named delegated unknowns, answered numerically ahead of C7.\n *\n * Informational now that the Apple-direct fit is what `profile: \"continuous\"`\n * renders, but recorded because the widely cited \"Figma smoothing 0.6 = iOS\" is\n * measurably wrong: 0.66 is nearly 2x closer. It is also the escape hatch for a\n * caller who needs an Apple-like corner ON the interpolable smoothing axis (see\n * `morph.ts` on why the two references do not morph into each other).\n */\nexport const APPLE_BEST_FIGMA_SMOOTHING = {\n radiusFixed: { smoothing: 0.66, hausdorffPerR: 0.00196015 },\n radiusFree: { smoothing: 0.6566210937500002, radiusScale: 0.99875, hausdorffPerR: 0.00174916 },\n} as const;\n\n/**\n * One corner, normalized by r, in a frame whose origin is the box corner and\n * whose interior lies toward +x/+y. Listed in the dump's own order: from the\n * vertical edge round to the horizontal edge.\n */\nexport const APPLE_CORNER_DUMP: readonly (readonly [number, number])[] = [\n [0, 1.52866495], // on the vertical edge\n [0, 1.08849001],\n [0, 0.86840701],\n [0.0749114, 0.63149399], // shoulder -> arc join\n [0.16906001, 0.37282401],\n [0.37282401, 0.16906001],\n [0.63149399, 0.0749114], // arc -> shoulder join\n [0.86840701, 0],\n [1.08849001, 0],\n [1.52866495, 0], // on the horizontal edge\n];\n\n/**\n * Top-right corner in the centred, y-up frame, emitted CLOCKWISE (top edge to\n * right edge) to match the ring builder's orientation. The dump runs the other\n * way round the corner, so it is reversed here.\n */\nfunction appleTopRightCorner(halfW: number, halfH: number, radius: number): ContourSegment[] {\n const pts: Point[] = APPLE_CORNER_DUMP.map(([u, w]) => ({\n x: halfW - u * radius,\n y: halfH - w * radius,\n }));\n pts.reverse();\n const out: ContourSegment[] = [];\n for (let i = 0; i + 3 < pts.length; i += 3) {\n out.push({\n kind: \"cubic\",\n p0: pts[i] as Point,\n p1: pts[i + 1] as Point,\n p2: pts[i + 2] as Point,\n p3: pts[i + 3] as Point,\n });\n }\n return out;\n}\n\nexport interface AppleContour extends Contour {\n /**\n * True when the requested radius exceeded what Apple's reach can fit. The real\n * thing warps past that point in a way this construction does not reproduce,\n * so it is reported rather than silently mismodelled.\n */\n readonly saturated: boolean;\n}\n\n/**\n * Apple's continuous-corner rounded rectangle. There is no smoothing parameter —\n * Apple's curve has none, which is exactly the point.\n */\nexport function buildAppleContour(\n halfW: number,\n halfH: number,\n radius: number,\n center: Vec2 = [0, 0],\n): AppleContour {\n const budget = Math.min(halfW, halfH);\n const r = Math.max(0, Math.min(radius, budget / APPLE_REACH));\n const saturated = radius > budget / APPLE_REACH + 1e-12;\n const reach = APPLE_REACH * r;\n\n const local = ringFromCorner(halfW, halfH, reach, appleTopRightCorner(halfW, halfH, r));\n const segments =\n center[0] === 0 && center[1] === 0\n ? local\n : local.map((s) => translateSegment(s, center[0], center[1]));\n\n // a/b/c/d are the reference family's cubic offset scalars and have no Apple\n // analogue; the arc measure is Apple's own measured 50 degrees.\n const corner: CornerConstruction = {\n radius: r,\n smoothingEff: APPLE_CONTINUOUS_SMOOTHING_SEED,\n reach,\n a: 0,\n b: 0,\n c: 0,\n d: 0,\n arcSectionLength: 0,\n arcMeasure: (50 * Math.PI) / 180,\n budget,\n };\n\n return { segments, closed: true, winding: \"clockwise-y-up\", corner, center, saturated };\n}\n","/**\n * ShapeSpec -> ResolvedShape: authoring input to the X8 channel vector plus\n * everything derived from it.\n *\n * A resolved shape carries three layers, and keeping them separate is what makes\n * morphs lossless:\n *\n * - `channels` — X8 exactly, holding the AUTHORED values. `smoothing` here is\n * what the author wrote, never the clamped value.\n * - `corner` — the derivation: budget, clamped radius, effective smoothing,\n * corner reach, and the five fitted coefficients. For C6 this\n * is the six derived floats the instance buffer widens by\n * (`reach` + `k`), recomputed per frame during a morph.\n * - `reference` — which curve the corner is fit against.\n *\n * ## Two corner references, and why there are two\n *\n * S2 measured that the reference curve, not the field family, is the fidelity\n * bottleneck. That leaves a genuine tension the spec resolves in both\n * directions at once, so the kernel carries both:\n *\n * - **`\"figma-smoothing\"`** — the interpolable authoring axis. `smoothing` is\n * a free channel over [0, 1], the budget clamp applies to it, and this is\n * the axis the declared error bound is measured on (§Geometry binds a\n * continuous numeric corner profile clamped by a budget derived from size\n * and radii).\n * - **`\"apple-continuous\"`** — the Apple-direct fit. Apple's curve has no\n * smoothing parameter, so `smoothing` is pinned at the seed and Apple's own\n * budget policy applies (clamp the RADIUS, not the smoothing). This is what\n * `profile: \"continuous\"` resolves to, per Decision Log #20.\n *\n * `profile: \"circular\"` is smoothing 0, which is the same exact circular corner\n * under either reference and is therefore the member they share.\n *\n * The two references are not points on one axis and the kernel does not pretend\n * otherwise: `morph.ts` refuses a morph that crosses them rather than inventing\n * a blend the error bound does not cover. In practice v1's morph pairs share a\n * profile, and `APPLE_BEST_FIGMA_SMOOTHING` is the documented way onto the\n * interpolable axis for a caller that needs one.\n */\n\nimport { APPLE_CONTINUOUS_SMOOTHING_SEED, APPLE_REACH } from \"./apple\";\nimport {\n APPLE_RSUP,\n APPLE_RSUPN,\n type CornerCoefficients,\n coefficientsAt,\n FIGMA_RSUP_TABLE,\n FIGMA_RSUPN_TABLE,\n ZERO_COEFFICIENTS,\n} from \"./coefficients\";\nimport {\n type CornerProfile,\n type CornerRadii,\n halfExtents,\n type ShapeChannels,\n type ShapeFamily,\n uniformRadii,\n type Vec2,\n} from \"./channels\";\nimport { type CornerConstruction, cornerBudget, resolveCornerConstruction } from \"./corner\";\nimport { buildReferenceContour, type Contour } from \"./contour\";\nimport { buildAppleContour } from \"./apple\";\nimport { GeometryError } from \"./errors\";\nimport type { FieldParams } from \"./field\";\n\nexport type CornerReference = \"figma-smoothing\" | \"apple-continuous\";\n\n/** Which field family the coefficients are for. Family C is the governor's step. */\nexport type FieldFamily = \"rsupn\" | \"rsup\";\n\nexport interface ResolvedCorner extends CornerConstruction {\n readonly reference: CornerReference;\n /** the five fitted correction coefficients for family D */\n readonly k: CornerCoefficients;\n /** max |field| on the true contour in units of the radius, for this fit */\n readonly contourDevPerR: number;\n}\n\nexport interface ResolvedShape {\n readonly family: ShapeFamily;\n readonly channels: ShapeChannels;\n readonly corner: ResolvedCorner;\n}\n\nexport interface FixedRoundedRectSpec {\n readonly family: \"fixed-rounded-rect\";\n readonly center: Vec2;\n readonly size: Vec2;\n /** A scalar is spread to all four corners; the Vec4 shape is preserved (X8). */\n readonly radii: CornerRadii | number;\n /**\n * `\"continuous\"` and `\"circular\"` are the public sugar. A number authors\n * directly on the Figma smoothing axis, which is what interpolates.\n */\n readonly profile?: CornerProfile | number;\n readonly thickness?: number;\n}\n\nexport interface CapsuleSpec {\n readonly family: \"capsule\";\n readonly center: Vec2;\n readonly size: Vec2;\n readonly thickness?: number;\n}\n\nexport type ShapeSpec = FixedRoundedRectSpec | CapsuleSpec;\n\nexport interface ResolveOptions {\n /**\n * Dev-only checks — the uniform-radii restriction. Default true: it is cheap,\n * and what it catches is a shape the v1 evaluator silently mis-renders.\n */\n readonly devMode?: boolean;\n}\n\nconst DEFAULT_THICKNESS = 0;\n\nfunction toRadii(r: CornerRadii | number): CornerRadii {\n return typeof r === \"number\" ? uniformRadii(r) : r;\n}\n\n/**\n * X8 rider 3 — v1's corner algebra is built on `|x|, |y|` and is therefore\n * mirror-symmetric by construction: it cannot express four different corners as\n * written. Per-corner radii would need the corner sector selected by quadrant\n * before the corner algebra runs, and the whole error sweep re-run. S2 asked for\n * this to be an explicit scope decision rather than an implementation detail, so\n * it is a refusal and not a silent average.\n */\nexport function assertUniformRadii(radii: CornerRadii): number {\n const [tl, tr, br, bl] = radii;\n const spread = Math.max(tl, tr, br, bl) - Math.min(tl, tr, br, bl);\n if (spread > 1e-9) {\n throw new GeometryError(\n \"non-uniform-radii\",\n `Per-corner radii are post-v1: got [${tl}, ${tr}, ${br}, ${bl}]. v1's corner algebra is ` +\n \"mirror-symmetric (it reads |x| and |y|), so it cannot express four different corners, \" +\n \"and the declared error bound was swept for uniform radii only. Use one radius for all \" +\n \"four corners.\",\n );\n }\n return tl;\n}\n\nfunction coefficientsFor(\n reference: CornerReference,\n family: FieldFamily,\n sEff: number,\n radius: number,\n): { k: CornerCoefficients; contourDevPerR: number } {\n // A degenerate corner has no corner sector to correct — and zeroing the\n // coefficients is what makes `radius: 0` a plain rectangle rather than a\n // rectangle with a polynomial applied to nothing.\n if (radius <= 0) return { k: ZERO_COEFFICIENTS, contourDevPerR: 0 };\n if (reference === \"apple-continuous\") {\n const fit = family === \"rsupn\" ? APPLE_RSUPN : APPLE_RSUP;\n return { k: fit.k, contourDevPerR: fit.contourDevPerR };\n }\n return coefficientsAt(family === \"rsupn\" ? FIGMA_RSUPN_TABLE : FIGMA_RSUP_TABLE, sEff);\n}\n\n/**\n * Resolve a corner on a chosen reference. Exported because the concentric\n * resolver and the morph solver both re-derive corners from channel values and\n * must go through exactly the same clamp the authoring path does.\n */\nexport function resolveCorner(\n size: Vec2,\n radius: number,\n smoothing: number,\n reference: CornerReference,\n family: FieldFamily = \"rsupn\",\n): ResolvedCorner {\n const { halfW, halfH } = halfExtents(size);\n\n if (reference === \"apple-continuous\") {\n // Apple clamps the RADIUS so the reach fits the side; it has no smoothing to\n // clamp. Past that the real corner warps, which this does not model —\n // `buildAppleContour` reports `saturated` for the same configuration.\n const budget = cornerBudget(halfW, halfH);\n const r = Math.max(0, Math.min(radius, budget / APPLE_REACH));\n const fit = coefficientsFor(reference, family, APPLE_CONTINUOUS_SMOOTHING_SEED, r);\n return {\n reference,\n radius: r,\n smoothingEff: r > 0 ? APPLE_CONTINUOUS_SMOOTHING_SEED : 0,\n reach: APPLE_REACH * r,\n a: 0,\n b: 0,\n c: 0,\n d: 0,\n arcSectionLength: 0,\n arcMeasure: (50 * Math.PI) / 180,\n budget,\n k: fit.k,\n contourDevPerR: fit.contourDevPerR,\n };\n }\n\n const construction = resolveCornerConstruction(halfW, halfH, radius, smoothing);\n const fit = coefficientsFor(reference, family, construction.smoothingEff, construction.radius);\n return { ...construction, reference, k: fit.k, contourDevPerR: fit.contourDevPerR };\n}\n\nfunction referenceFor(profile: CornerProfile | number | undefined): {\n reference: CornerReference;\n smoothing: number;\n} {\n if (profile === undefined || profile === \"continuous\") {\n return { reference: \"apple-continuous\", smoothing: APPLE_CONTINUOUS_SMOOTHING_SEED };\n }\n if (profile === \"circular\") return { reference: \"figma-smoothing\", smoothing: 0 };\n return { reference: \"figma-smoothing\", smoothing: profile };\n}\n\nexport function resolveShape(spec: ShapeSpec, options: ResolveOptions = {}): ResolvedShape {\n const devMode = options.devMode ?? true;\n\n if (spec.family === \"capsule\") {\n // A capsule's radius IS the budget, which forces effective smoothing to\n // exactly 0 — so a capsule is a true stadium and the field is exact on it,\n // for any smoothing the author might have wanted. That is a property of the\n // budget clamp, not a special case in the evaluator.\n const { halfW, halfH } = halfExtents(spec.size);\n const r = cornerBudget(halfW, halfH);\n return {\n family: \"capsule\",\n channels: {\n center: spec.center,\n size: spec.size,\n radii: uniformRadii(r),\n smoothing: 0,\n thickness: spec.thickness ?? DEFAULT_THICKNESS,\n },\n corner: resolveCorner(spec.size, r, 0, \"figma-smoothing\"),\n };\n }\n\n const radii = toRadii(spec.radii);\n const radius = devMode ? assertUniformRadii(radii) : (radii[0] ?? 0);\n const { reference, smoothing } = referenceFor(spec.profile);\n\n return {\n family: \"fixed-rounded-rect\",\n channels: {\n center: spec.center,\n size: spec.size,\n radii,\n smoothing,\n thickness: spec.thickness ?? DEFAULT_THICKNESS,\n },\n corner: resolveCorner(spec.size, radius, smoothing, reference),\n };\n}\n\n/**\n * Re-resolve a shape from a channel vector. This is the path a morph takes every\n * frame: interpolate channels, then derive the corner through the same clamp the\n * authoring path uses, so an interpolated shape is never a shape the resolver\n * could not have produced directly.\n */\nexport function resolveFromChannels(\n channels: ShapeChannels,\n reference: CornerReference,\n family: ShapeFamily = \"fixed-rounded-rect\",\n options: ResolveOptions = {},\n): ResolvedShape {\n const devMode = options.devMode ?? true;\n const radius = devMode ? assertUniformRadii(channels.radii) : (channels.radii[0] ?? 0);\n return {\n family,\n channels,\n corner: resolveCorner(channels.size, radius, channels.smoothing, reference),\n };\n}\n\n/**\n * The shader-side parameters: half-extents plus the six derived floats. In\n * SHAPE-LOCAL coordinates — the field is evaluated about the shape's own centre,\n * so a caller subtracts `channels.center` first (or uses `sampleField`).\n */\nexport function fieldParams(shape: ResolvedShape): FieldParams {\n const { halfW, halfH } = halfExtents(shape.channels.size);\n return { halfW, halfH, reach: shape.corner.reach, k: shape.corner.k };\n}\n\n/** Field parameters for family C, the quality governor's first within-tier step. */\nexport function governorFieldParams(shape: ResolvedShape): FieldParams {\n const { halfW, halfH } = halfExtents(shape.channels.size);\n const corner = resolveCorner(\n shape.channels.size,\n shape.corner.radius,\n shape.channels.smoothing,\n shape.corner.reference,\n \"rsup\",\n );\n return { halfW, halfH, reach: corner.reach, k: corner.k };\n}\n\n/** The Contour IR for a resolved shape, in the shape's own coordinate space. */\nexport function toContour(shape: ResolvedShape): Contour {\n const { halfW, halfH } = halfExtents(shape.channels.size);\n if (shape.corner.reference === \"apple-continuous\") {\n return buildAppleContour(halfW, halfH, shape.corner.radius, shape.channels.center);\n }\n return buildReferenceContour(halfW, halfH, shape.corner, shape.channels.center);\n}\n","/**\n * The concentric resolver: a child shape derived from its parent's field as a\n * **level-set offset**.\n *\n * ## Two paths, two different bounds — and the difference is measured\n *\n * X8 rider 2 says the inner contour's accuracy IS the field's value error at\n * `d = -inset`, so `thickness` needs no bound of its own. That is exactly true\n * for one of the two ways to draw a concentric child, and only approximately\n * true for the other. The distinction is not pedantic: at inset 8 px and\n * smoothing 1.0 the two differ by 2x.\n *\n * - **`concentricField` — the level set itself.** The child's boundary is\n * `parentField(p) + inset == 0` by definition, so its accuracy is identically\n * the parent field's value error at depth `inset`: <= 0.170 px for\n * `inset <= 8`, straight off the declared bound. This is what C6 should\n * render, and it is what makes rider 2 true as written.\n *\n * - **`resolveConcentric` — the child as its own resolved shape.** Needed for\n * bounds, hit-testing, Contour IR export, and any case where the child is a\n * separate instance rather than a level set of its parent. Here a SECOND\n * approximation enters, and it is the one §Geometry warns about: exact inward\n * offsets of continuous-corner cubics leave the cubic family, so no member of\n * the family is the true offset of another member. Measured deviation from the\n * parent's true offset, worst over profile:\n *\n * inset 1 px -> 0.033 px | inset 2 -> 0.067 | inset 4 -> 0.138 | inset 8 -> 0.291\n *\n * which composes with the field error to 0.105 / 0.132 / 0.190 / 0.326 px.\n * Past about inset 4 with high smoothing that exceeds the field's own budget,\n * and the family mismatch — not the pseudo-SDF — is the dominant term.\n *\n * Concentricity governs **radii, not the curve profile** — the research\n * explicitly refuted that conflation, so the child inherits the parent's\n * smoothing and reference rather than being re-profiled.\n *\n * An inset beyond 8 px is outside the band S2 measured and would need a wider\n * sweep before being claimed; `beyondMeasuredBand` reports that rather than\n * letting a caller assume coverage it does not have.\n */\n\nimport { halfExtents, type ShapeChannels, uniformRadii, type Vec2 } from \"./channels\";\nimport { cornerBudget } from \"./corner\";\nimport { rsupnField, rsupnFieldAndGradient } from \"./field\";\nimport { fieldParams, resolveCorner, type ResolvedShape } from \"./shape\";\n\n/**\n * The band S2 measured, in px. An inset past this is not covered by the declared\n * bound.\n */\nexport const MEASURED_BAND_PX = 8;\n\n/**\n * Advisory floor on a derived radius, in px. Calibration-delegated: it exists so\n * a deep inset does not collapse a corner to a visible sharp point, and the\n * value that reads right against the reference is C7's to fit. Named here the\n * way §Geometry names `samplingPadding`'s advisory default.\n */\nexport const DEFAULT_CONCENTRIC_MIN_RADIUS = 2;\n\nexport interface ConcentricSpec {\n /** Inward offset on every side, in px. */\n readonly inset: number;\n /** Floor on the derived radius. Defaults to `DEFAULT_CONCENTRIC_MIN_RADIUS`. */\n readonly minRadius?: number;\n /** Defaults to the parent's thickness. */\n readonly thickness?: number;\n}\n\nexport interface ConcentricResult {\n readonly shape: ResolvedShape;\n /** True when the radius floor bit, so the child is no longer a pure offset. */\n readonly radiusFloored: boolean;\n /** True when the inset exceeds the band S2 measured (see above). */\n readonly beyondMeasuredBand: boolean;\n}\n\n/**\n * Derive the concentric child of a resolved parent.\n *\n * The offset itself is the simple part and is exact for circular corners: an\n * inward offset by `t` shrinks each side by `2t` and each corner's radius of\n * curvature by `t`. What makes it an approximation is only the smoothed corner,\n * and that residual is precisely the parent field's value error at depth `t`.\n */\nexport function resolveConcentric(\n parent: ResolvedShape,\n spec: ConcentricSpec,\n): ConcentricResult {\n const inset = Math.max(spec.inset, 0);\n const minRadius = spec.minRadius ?? DEFAULT_CONCENTRIC_MIN_RADIUS;\n\n const size: Vec2 = [\n Math.max(parent.channels.size[0] - 2 * inset, 0),\n Math.max(parent.channels.size[1] - 2 * inset, 0),\n ];\n const { halfW, halfH } = halfExtents(size);\n const childBudget = cornerBudget(halfW, halfH);\n\n // The offset radius, floored — then capped at the child's own budget, because\n // a floor cannot ask for more corner than the child has side to spend.\n const offsetRadius = parent.corner.radius - inset;\n const floored = Math.max(offsetRadius, minRadius);\n const radius = Math.min(floored, childBudget);\n const radiusFloored = floored > offsetRadius + 1e-12;\n\n // Concentricity governs radii, not the profile: smoothing and reference are\n // inherited so the child's corner is the same KIND of curve as the parent's.\n const smoothing = parent.channels.smoothing;\n const channels: ShapeChannels = {\n center: parent.channels.center,\n size,\n radii: uniformRadii(radius),\n smoothing,\n thickness: spec.thickness ?? parent.channels.thickness,\n };\n\n return {\n shape: {\n family: \"concentric-rounded-rect\",\n channels,\n corner: resolveCorner(size, radius, smoothing, parent.corner.reference),\n },\n radiusFloored,\n beyondMeasuredBand: inset > MEASURED_BAND_PX,\n };\n}\n\n/**\n * The concentric child implied by a shape's own `thickness` channel — the inner\n * surface of the material.\n *\n * This is what X8 rider 2 means by \"thickness needs no separate error bound\":\n * the inner contour is a level set of the same field, so its accuracy is already\n * covered by the band bound.\n */\nexport function resolveThicknessInnerShape(shape: ResolvedShape): ConcentricResult {\n return resolveConcentric(shape, { inset: shape.channels.thickness });\n}\n\n/**\n * The concentric child as a level set of the parent's field — the exact path.\n *\n * `parentField(p) + inset` is zero precisely where the parent field reads\n * `-inset`, so this IS the offset boundary rather than an approximation of it.\n * Its accuracy is the parent field's value error at that depth and nothing more,\n * which is the whole content of X8 rider 2.\n *\n * Points are in the parent's coordinate space; the centre is applied here, since\n * a level set of the parent has no independent centre of its own.\n */\nexport function concentricField(\n parent: ResolvedShape,\n inset: number,\n x: number,\n y: number,\n): number {\n const p = fieldParams(parent);\n return rsupnField(p, x - parent.channels.center[0], y - parent.channels.center[1]) + inset;\n}\n\n/**\n * The same level set with its exact gradient. The offset does not change the\n * gradient at all — it is a constant shift — so the normal at the inner surface\n * is the parent field's normal, at the parent field's accuracy.\n */\nexport function concentricFieldAndGradient(\n parent: ResolvedShape,\n inset: number,\n x: number,\n y: number,\n): { value: number; gx: number; gy: number; kink: boolean } {\n const p = fieldParams(parent);\n const s = rsupnFieldAndGradient(\n p,\n x - parent.channels.center[0],\n y - parent.channels.center[1],\n );\n return { value: s.value + inset, gx: s.gx, gy: s.gy, kink: s.kink };\n}\n","/**\n * Group union: bounded smooth-min over member fields.\n *\n * §Geometry: group rendering goes through a per-group field pass (instances ->\n * group SDF/coverage field -> one optical pass), \"which makes bounded smooth-min\n * proximity union within a group nearly free; union aesthetics (neck width, max\n * bulge, separation threshold) are **capped** and calibration-tuned so nothing\n * reads as jelly.\"\n *\n * The three tunables the spec names, and what each one actually does:\n *\n * - **`neckWidth`** — the polynomial smooth-min's blend width `k`. Two members\n * closer than about `k/2` grow a neck between them.\n * - **`maxBulge`** — the cap. A quadratic smooth-min deviates from `min` by at\n * most `k/4`, so capping the deviation caps `k` at `4 * maxBulge`; the total\n * over an n-member fold is clamped once at the end, which makes the bound\n * hold for any member count and any fold order.\n * - **`separationThreshold`** — the gate. Without it, two members far apart\n * still depress the field on the segment between them, because a smooth min\n * of two equal values dips even when both are large. That depression is what\n * reads as jelly. The gate switches blending off wherever the nearest member\n * is farther than half the threshold, which is exactly where a neck could no\n * longer form.\n *\n * Both gates are load-bearing and they catch different cases: `|a - b| >= k`\n * saturates the blend next to one member when the other is far, and the\n * separation gate kills the midpoint depression between two distant members.\n * With both, the union is EXACTLY `min` everywhere except near a real seam.\n */\n\nimport { clamp, smoothstep, type Vec2 } from \"./channels\";\nimport { rsupnField } from \"./field\";\nimport { fieldParams, type ResolvedShape } from \"./shape\";\n\nexport interface GroupUnionParams {\n /** Blend width in px. Larger means a wider, softer neck. */\n readonly neckWidth: number;\n /** Hard cap on how far the union may deviate from `min`, in px. */\n readonly maxBulge: number;\n /** Members whose gap exceeds this never blend, in px. */\n readonly separationThreshold: number;\n}\n\n/**\n * Advisory defaults, calibration-delegated in the same sense as §Geometry's\n * other union aesthetics: named here so the math has a definition, fitted by C7\n * against the reference. Chosen so the bulge cap is not already binding at the\n * default neck width — the cap is a guard against a raised `neckWidth`, not a\n * silent re-parameterization of the default.\n */\nexport const DEFAULT_GROUP_UNION: GroupUnionParams = {\n neckWidth: 8,\n maxBulge: 2,\n separationThreshold: 16,\n};\n\n/**\n * Derive a group's union parameters from its declared `mergeDistance`.\n *\n * A scene declares `mergeDistance` (§Core model) as the proximity-union\n * threshold it wants — the same number X1 also uses for the CSS-tier proxy\n * geometry. Before this function existed, nothing forwarded that number into\n * `GroupUnionParams`, so every group rendered with `DEFAULT_GROUP_UNION`\n * regardless of what it declared (C7's flow-back finding).\n *\n * The mapping treats `mergeDistance` as `separationThreshold` directly — it is\n * the same \"how close before members interact\" quantity — and scales\n * `neckWidth`/`maxBulge` by the same ratio the defaults already hold to\n * `DEFAULT_GROUP_UNION.separationThreshold` (16). That preserves the\n * default's `neckWidth = 4 * maxBulge` relationship (the point at which the\n * bulge cap is not already binding, per `DEFAULT_GROUP_UNION`'s own note) at\n * any declared distance, rather than picking a new, untuned ratio.\n *\n * Advisory, like the constants it derives from: calibration (C9) can retune\n * the ratio in this one place.\n *\n * A group with no declared `mergeDistance` — `undefined` — gets\n * `DEFAULT_GROUP_UNION` back unchanged, so existing scenes render exactly as\n * before.\n */\nexport function groupUnionFromMergeDistance(\n mergeDistance: number | undefined,\n): GroupUnionParams {\n if (mergeDistance === undefined) return DEFAULT_GROUP_UNION;\n const ratio = mergeDistance / DEFAULT_GROUP_UNION.separationThreshold;\n return {\n neckWidth: DEFAULT_GROUP_UNION.neckWidth * ratio,\n maxBulge: DEFAULT_GROUP_UNION.maxBulge * ratio,\n separationThreshold: mergeDistance,\n };\n}\n\n/**\n * Two-member bounded smooth union.\n *\n * Commutative: swapping the arguments maps `h -> 1 - h` and leaves the\n * expression unchanged, which is asserted rather than assumed because a\n * non-commutative union would make a group's appearance depend on instance\n * buffer order.\n */\nexport function smoothUnion2(a: number, b: number, params: GroupUnionParams): number {\n const nearest = Math.min(a, b);\n\n // The bulge cap, applied to k rather than to the result: a quadratic\n // smooth-min's worst deviation is k/4, so this is the k that cannot exceed it.\n const capped = Math.min(params.neckWidth, 4 * params.maxBulge);\n\n // The separation gate. `nearest` is ~0 at a real seam and ~gap/2 at the\n // midpoint between two distant members, so gating on it switches off exactly\n // the blending that would otherwise invent material in empty space.\n const gate = 1 - smoothstep(0, params.separationThreshold * 0.5, Math.max(nearest, 0));\n const k = capped * gate;\n if (k <= 0) return nearest;\n\n const h = clamp(0.5 + (0.5 * (b - a)) / k, 0, 1);\n return b + h * (a - b) - k * h * (1 - h);\n}\n\n/**\n * n-member union.\n *\n * A left fold, which is what a shader iterating its group's instances does. The\n * final clamp is what makes the bulge cap a real bound rather than a per-step\n * one: without it an n-member fold could drift by up to `(n-1) * maxBulge`, and\n * \"nothing reads as jelly\" would stop being true at high member counts. With it,\n * `|union - min| <= maxBulge` holds for any n and any order — one `max` at the\n * end, which is also why it is free on the GPU.\n */\nexport function groupUnion(values: readonly number[], params: GroupUnionParams): number {\n if (values.length === 0) return Number.POSITIVE_INFINITY;\n let acc = values[0] as number;\n let nearest = acc;\n for (let i = 1; i < values.length; i++) {\n const v = values[i] as number;\n acc = smoothUnion2(acc, v, params);\n nearest = Math.min(nearest, v);\n }\n return Math.max(acc, nearest - params.maxBulge);\n}\n\n/**\n * The group field at a point: every member's field, unioned.\n *\n * Each member is evaluated in its own shape-local frame, which is what makes the\n * union independent of where the group happens to sit.\n */\nexport function groupUnionField(\n members: readonly ResolvedShape[],\n point: Vec2,\n params: GroupUnionParams = DEFAULT_GROUP_UNION,\n): number {\n const values: number[] = [];\n for (const m of members) {\n const p = fieldParams(m);\n values.push(rsupnField(p, point[0] - m.channels.center[0], point[1] - m.channels.center[1]));\n }\n return groupUnion(values, params);\n}\n\n/** One member's field at a point, in the group's coordinate space. */\nexport function memberField(member: ResolvedShape, point: Vec2): number {\n const p = fieldParams(member);\n return rsupnField(p, point[0] - member.channels.center[0], point[1] - member.channels.center[1]);\n}\n","/**\n * The WGSL source of truth for the field, as exported string constants. C6\n * consumes these.\n *\n * These are the shaders S2 actually benchmarked\n * (`spikes/s2-geometry-field/bench/shaders.wgsl`, results.json generatedAt\n * 2026-08-24T14:29:14.670Z on a metal-3 adapter), carried here unchanged in\n * their arithmetic so the cost figures and the f32 precision result transfer\n * with them:\n *\n * - `sd_rsupn` — 0.02640 ns/eval/px; f32 vs f64 agreement 4.08e-5 px, which is\n * 0.024% of the declared 0.17 px bound. That check does double duty: it also\n * validates the port, because `rsupnField` in `field.ts` is a line-for-line\n * mirror, so a transcription slip would show as a gross disagreement rather\n * than a rounding difference.\n * - `sd_rsup` — 0.00970 ns/eval/px, the governor's first within-tier step.\n * Unchanged since S2 measured it, character for character.\n *\n * `sd_rsupn` has moved once since: its normalization is now anchored at the\n * contour radius (see `field.ts`, \"The normalization\"), which adds one divide and\n * one `select` and leaves the selected arm identical on and outside the level set.\n * The f32 result above has been re-established on hardware for the anchored\n * shader — 4.007e-5 px worst case over 5535 points on an `apple/metal-3` adapter,\n * against S2's 4.08e-5 — by `renderer-webgpu`'s `e2e/gpu/cross-check.spec.ts`,\n * which reads this string's own output back off the device. The cost figure is\n * S2's and now carries the added divide as an unmeasured delta; the renderer's\n * `@bench` suite is what holds the envelope.\n *\n * **C6 action item, spec Decision Log #20:** family C's WGSL is\n * inspection-verified only. Its cost number is a cost number and does not depend\n * on the port being bit-accurate, but `sd_rsup` must go through the f32\n * cross-check before it ships as a governor tier. Extending\n * `spikes/s2-geometry-field/bench/make-f32-check.ts` to emit a third expected\n * column is the whole job.\n *\n * `test/wgsl-sync.test.ts` holds these and the TypeScript in step: it fingerprints\n * both, so editing either one alone fails the suite with a message saying to\n * re-run the cross-check.\n */\n\n/**\n * Instance data layout. The last six scalars — `re` and `k0..k4` — are the\n * derived floats C6's instance buffer widens by. They are computed CPU-side from\n * `{ size, radii, smoothing }` (see `shape.ts`'s `fieldParams`), so they are a\n * buffer layout change and not an API or channel change. During a morph they are\n * recomputed per frame, which is trivial at v1's surface counts but should batch\n * with the existing geometry sync rather than happen per draw.\n */\nexport const WGSL_SHAPE_STRUCT = `struct Shape {\n centre : vec2f,\n half : vec2f,\n re : f32,\n k0 : f32,\n k1 : f32,\n k2 : f32,\n k3 : f32,\n k4 : f32,\n _pad : f32,\n};`;\n\n/**\n * Family D — the v1 field. Radial-support field with a degree-5 corner\n * correction in `s2 = sin(2*theta)`, plus the first-order |grad| normalization.\n *\n * No transcendentals: `s2` and `cos(2*theta)` both come out of the clamped\n * corner vector by division. Cost over a plain rounded box is one extra divide,\n * one rsqrt, and about a dozen fused multiply-adds. Branchless in the corner\n * algebra — the only guard is the clamp on the squared corner radius, which\n * keeps the deep-interior case from producing NaN without costing a branch, and\n * is why the straight-edge region needs no special case.\n */\nexport const WGSL_RSUPN = `fn sd_rsupn(p : vec2f, half : vec2f, re : f32, k : vec4f, k4 : f32) -> f32 {\n let q = abs(p) - half + vec2f(re, re);\n let c = max(q, vec2f(0.0, 0.0));\n let r2 = max(dot(c, c), 1e-20);\n let inv = 1.0 / r2;\n let s2 = 2.0 * c.x * c.y * inv; // sin(2*theta)\n let c2 = (c.x * c.x - c.y * c.y) * inv; // cos(2*theta)\n\n // R(theta) = re * (1 + s2^2 * poly(s2)), Horner\n var acc = k4;\n acc = acc * s2 + k.w;\n acc = acc * s2 + k.z;\n acc = acc * s2 + k.y;\n acc = acc * s2 + k.x;\n let R = re * (1.0 + s2 * s2 * acc);\n\n // dR/d(s2) * d(s2)/dtheta, same Horner shape with the differentiated weights\n var dac = 6.0 * k4;\n dac = dac * s2 + 5.0 * k.w;\n dac = dac * s2 + 4.0 * k.z;\n dac = dac * s2 + 3.0 * k.y;\n dac = dac * s2 + 2.0 * k.x;\n let dRdt = re * s2 * dac * (2.0 * c2);\n\n let base = sqrt(r2) + min(max(q.x, q.y), 0.0) - R;\n // The normalization is a Newton step to the zero set, so its slope is read at\n // the FOOT of the step: at the contour radius R, never at a sample radius\n // inside it. On and outside the level set the selected arm is bit-for-bit the\n // product S2 benchmarked; inside the corner sector the anchor is what stops\n // R'/rho diverging toward the sector vertex and collapsing the field into a\n // false near-surface region. See field.ts, \"The normalization\".\n let g = select(dRdt / R, dRdt * inv * sqrt(r2), sqrt(r2) >= R);\n return base * inverseSqrt(1.0 + g * g);\n}`;\n\n/**\n * Family C — family D minus the |grad| normalization. The two share a zero level\n * set and a coefficient table, so the gap between them is exactly the price of\n * the normalization: 29% of the field's total cost, in exchange for degrading the\n * bound from 0.170 px / 2.91 degrees to 0.574 px / 4.26 degrees and the eikonal\n * defect from 2.7% to 7.9%.\n *\n * Dropping the normalization is one branch and one uniform, which is what makes\n * this a cleaner within-tier governor step than reducing refraction resolution.\n * See the module note: this one still needs C6's f32 cross-check.\n */\nexport const WGSL_RSUP = `fn sd_rsup(p : vec2f, half : vec2f, re : f32, k : vec4f, k4 : f32) -> f32 {\n let q = abs(p) - half + vec2f(re, re);\n let c = max(q, vec2f(0.0, 0.0));\n let r2 = max(dot(c, c), 1e-20);\n let s2 = 2.0 * c.x * c.y / r2;\n var acc = k4;\n acc = acc * s2 + k.w;\n acc = acc * s2 + k.z;\n acc = acc * s2 + k.y;\n acc = acc * s2 + k.x;\n let R = re * (1.0 + s2 * s2 * acc);\n return sqrt(r2) + min(max(q.x, q.y), 0.0) - R;\n}`;\n\n/** Struct plus both families, ready to concatenate into a pipeline's module. */\nexport const WGSL_FIELD_MODULE = [WGSL_SHAPE_STRUCT, WGSL_RSUPN, WGSL_RSUP].join(\"\\n\\n\");\n\n/**\n * FNV-1a over the UTF-16 code units, as a zero-padded hex string.\n *\n * A drift tripwire, not a security primitive — and deliberately hand-rolled: a\n * pure package may not import `node:crypto`, and this only has to notice that a\n * string changed.\n */\nexport function fingerprint(text: string): string {\n let h = 0x811c9dc5;\n for (let i = 0; i < text.length; i++) {\n h ^= text.charCodeAt(i);\n h = Math.imul(h, 0x01000193) >>> 0;\n }\n return h.toString(16).padStart(8, \"0\");\n}\n","/**\n * The binding driver-per-channel table (§Motion).\n *\n * Every animated channel names exactly one driver family. The table is the\n * contract; the constants that configure each driver live in `tunables.ts` and\n * are advisory until calibration (C7) replaces them.\n */\n\n/** Animated channels. Each has exactly one driver family (§Motion, binding). */\nexport const MOTION_CHANNELS = [\n \"position\",\n \"size\",\n \"radius\",\n \"pressCompression\",\n \"lensStrength\",\n \"glow\",\n \"backdropAdaptation\",\n \"foregroundTone\",\n \"materialization\",\n \"disabled\",\n \"qualityTier\",\n] as const;\n\nexport type MotionChannel = (typeof MOTION_CHANNELS)[number];\n\n/**\n * Driver families. `interruptible-spring` is the velocity-preserving one:\n * a redirect continues from current position and velocity, never restarts.\n */\nexport const MOTION_DRIVER_KINDS = [\n \"interruptible-spring\",\n \"critically-damped\",\n \"attack-decay\",\n \"low-pass-hysteresis\",\n \"threshold-crossfade\",\n \"monotonic-ease\",\n \"step\",\n \"hysteresis-cooldown\",\n] as const;\n\nexport type MotionDriverKind = (typeof MOTION_DRIVER_KINDS)[number];\n\nexport const MOTION_DRIVER_BY_CHANNEL: Readonly<Record<MotionChannel, MotionDriverKind>> = {\n position: \"interruptible-spring\",\n size: \"interruptible-spring\",\n radius: \"interruptible-spring\",\n pressCompression: \"interruptible-spring\",\n lensStrength: \"critically-damped\",\n glow: \"attack-decay\",\n backdropAdaptation: \"low-pass-hysteresis\",\n foregroundTone: \"threshold-crossfade\",\n materialization: \"monotonic-ease\",\n disabled: \"step\",\n qualityTier: \"hysteresis-cooldown\",\n};\n\n/**\n * What a channel does to the material, which is what Reduced Motion needs to\n * know: `deformation` is what it removes, `positional` is what it must keep\n * (spatial continuity survives the policy — §Accessibility).\n */\nexport type ChannelRole = \"positional\" | \"deformation\" | \"optical\" | \"discrete\";\n\nexport const CHANNEL_ROLE: Readonly<Record<MotionChannel, ChannelRole>> = {\n position: \"positional\",\n size: \"positional\",\n radius: \"positional\",\n pressCompression: \"deformation\",\n lensStrength: \"optical\",\n glow: \"optical\",\n backdropAdaptation: \"optical\",\n foregroundTone: \"optical\",\n materialization: \"optical\",\n disabled: \"discrete\",\n qualityTier: \"discrete\",\n};\n\nconst channelsWithRole = (role: ChannelRole): readonly MotionChannel[] =>\n MOTION_CHANNELS.filter((channel) => CHANNEL_ROLE[channel] === role);\n\n/** Geometry channels — Reduced Motion damps these but never stops them. */\nexport const POSITIONAL_CHANNELS: readonly MotionChannel[] = channelsWithRole(\"positional\");\n\n/** Shape-distorting channels — Reduced Motion zeroes these (§Accessibility). */\nexport const DEFORMATION_CHANNELS: readonly MotionChannel[] = channelsWithRole(\"deformation\");\n\n/** Channels whose driver must never overshoot, so Reduced Motion cannot regress them. */\nexport const NON_OVERSHOOTING_CHANNELS: readonly MotionChannel[] = MOTION_CHANNELS.filter(\n (channel) =>\n MOTION_DRIVER_BY_CHANNEL[channel] === \"monotonic-ease\" ||\n MOTION_DRIVER_BY_CHANNEL[channel] === \"step\",\n);\n\n/**\n * Channels the interaction state machine drives from its state table.\n *\n * The rest are driven from outside it: geometry follows layout and morph\n * targets, `backdropAdaptation` and `foregroundTone` follow the backdrop, and\n * `qualityTier` follows the governor. They still animate through this package's\n * drivers — they are just not a function of interaction state.\n */\nexport const STATE_DRIVEN_CHANNELS: readonly MotionChannel[] = [\n \"pressCompression\",\n \"lensStrength\",\n \"glow\",\n \"materialization\",\n \"disabled\",\n];\n","import type { MotionChannel } from \"./channels\";\n\n/** v1 interaction states (§Motion). Neighbor glow diffusion is post-v1. */\nexport const INTERACTION_STATES = [\n \"idle\",\n \"hover\",\n \"pressed\",\n \"focused\",\n \"disabled\",\n \"morphing\",\n] as const;\n\nexport type InteractionState = (typeof INTERACTION_STATES)[number];\n\n/**\n * Which states each state may move to.\n *\n * Nearly everything is reachable from everything: a touch press arrives with no\n * hover before it, a pointer can leave while held, and any of them can be\n * disabled or start morphing. Two edges are missing on purpose:\n *\n * - out of `disabled`, only `idle`. A disabled control offers no press or focus\n * affordance, so re-enabling lands at rest and the host's next event decides\n * what it becomes. `applyFlags` routes through `idle` on its own.\n * - out of `morphing`, no `pressed`. A surface mid-morph is not yet a press\n * target; press-during-morph is post-v1, in line with §v1 scope shipping one\n * morph pair.\n *\n * A state is always allowed to transition to itself, as an accepted no-op.\n */\nexport const LEGAL_TRANSITIONS: Readonly<Record<InteractionState, readonly InteractionState[]>> = {\n idle: [\"hover\", \"pressed\", \"focused\", \"disabled\", \"morphing\"],\n hover: [\"idle\", \"pressed\", \"focused\", \"disabled\", \"morphing\"],\n pressed: [\"idle\", \"hover\", \"focused\", \"disabled\", \"morphing\"],\n focused: [\"idle\", \"hover\", \"pressed\", \"disabled\", \"morphing\"],\n disabled: [\"idle\"],\n morphing: [\"idle\", \"hover\", \"focused\", \"disabled\"],\n};\n\nexport function canTransition(from: InteractionState, to: InteractionState): boolean {\n return from === to || LEGAL_TRANSITIONS[from].includes(to);\n}\n\n/**\n * What a host observes. These overlap in reality — a control can be focused and\n * hovered and held at once — while §Motion names one state at a time, so the\n * kernel owns the collapse rather than leaving each binding to invent it.\n */\nexport interface InteractionFlags {\n readonly disabled: boolean;\n readonly morphing: boolean;\n readonly pressed: boolean;\n readonly hovered: boolean;\n readonly focused: boolean;\n}\n\n/**\n * Collapse overlapping flags to the one state that describes the material.\n *\n * `disabled` outranks everything because it withdraws the affordance; `morphing`\n * next because the surface is mid-transition; `pressed` next because direct\n * manipulation reads over anything ambient. `hovered` outranks `focused`\n * deliberately: focus has its own affordance in the semantic host DOM (a ring,\n * per §rendering contract), so the *material* spends its one slot on the more\n * immediate pointer feedback.\n */\nexport function resolveInteractionState(flags: InteractionFlags): InteractionState {\n if (flags.disabled) return \"disabled\";\n if (flags.morphing) return \"morphing\";\n if (flags.pressed) return \"pressed\";\n if (flags.hovered) return \"hover\";\n if (flags.focused) return \"focused\";\n return \"idle\";\n}\n\n/**\n * Per-state channel targets.\n *\n * Targets are stored per state, not per transition. A state's target vector is\n * where the material rests in that state, so arriving at `hover` from `pressed`\n * and from `idle` necessarily lands on the same place — path independence is\n * structural rather than a property each transition row has to get right. What\n * a transition *does* is therefore derivable: `channelsMovedBy` reads it off the\n * two vectors, and the asymmetries a per-transition table would encode by hand\n * (glow attacking on press, decaying on release) come from the driver families\n * instead.\n *\n * A channel absent from a state's vector is not driven by interaction state and\n * keeps whatever target it already had.\n */\nexport type StateTargets = Readonly<Partial<Record<MotionChannel, number>>>;\nexport type StateTargetTable = Readonly<Record<InteractionState, StateTargets>>;\n\n/**\n * The channels a transition actually moves — the per-transition view of the\n * state table, for tests and for callers that want to know what a state change\n * will disturb before making it.\n */\nexport function channelsMovedBy(\n table: StateTargetTable,\n from: InteractionState,\n to: InteractionState,\n): readonly MotionChannel[] {\n const source = table[from];\n const destination = table[to];\n return (Object.keys(destination) as MotionChannel[]).filter(\n (channel) => destination[channel] !== source[channel],\n );\n}\n","import type { LowPassConfig, MotionDriver } from \"../driver\";\n\n/**\n * Exponential low-pass behind a hysteresis band (§Motion:\n * backdrop-adaptation values).\n *\n * The band filters the *input*: a new observation is committed only once it\n * departs the committed value by more than `hysteresis`, so scroll jitter never\n * reaches the filter at all. The low-pass then smooths the committed staircase,\n * which is why the output moves continuously even though commitment is\n * discrete.\n *\n * The band is measured from the committed value, not the current output. Against\n * the moving output a slowly drifting input would leak through one small step at\n * a time — precisely the pumping §Foreground adaptation rules out.\n */\nexport class LowPassHysteresisDriver implements MotionDriver {\n readonly config: LowPassConfig;\n\n #value: number;\n #committed: number;\n\n constructor(config: LowPassConfig, initialValue: number) {\n this.config = config;\n this.#value = initialValue;\n this.#committed = initialValue;\n }\n\n get value(): number {\n return this.#value;\n }\n\n get velocity(): number {\n const tau = this.config.timeConstantMs;\n if (tau <= 0) return 0;\n return ((this.#committed - this.#value) * 1000) / tau;\n }\n\n get target(): number {\n return this.#committed;\n }\n\n get settled(): boolean {\n return Math.abs(this.#value - this.#committed) <= this.config.restDistance;\n }\n\n retarget(input: number): void {\n if (Math.abs(input - this.#committed) <= this.config.hysteresis) return;\n this.#committed = input;\n }\n\n jumpTo(value: number): void {\n this.#value = value;\n this.#committed = value;\n }\n\n advance(dtMs: number): void {\n if (!(dtMs > 0)) return;\n\n const tau = this.config.timeConstantMs;\n if (tau <= 0) {\n this.#value = this.#committed;\n return;\n }\n\n this.#value = this.#committed + (this.#value - this.#committed) * Math.exp(-dtMs / tau);\n }\n}\n","import { MOTION_CHANNELS, type MotionChannel } from \"./channels\";\nimport type {\n DriverConfig,\n EaseConfig,\n ExponentialConfig,\n LowPassConfig,\n SpringConfig,\n StepConfig,\n ThresholdCrossfadeConfig,\n} from \"./driver\";\nimport type { FramePolicy } from \"./frame\";\nimport { INTERACTION_STATES, type InteractionState, type StateTargetTable } from \"./states\";\n\n/**\n * Every number the motion kernel runs on, in one place.\n *\n * **These values are advisory.** §Calibration names spring constants and\n * hysteresis rates as delegated unknowns: C7's harness measures them against\n * `apple-macos-26.5-*` fixtures and replaces what is here. Nothing downstream\n * may hard-code a constant of its own — a profile object overrides every one of\n * them (`withProfileOverrides`), so replacing the whole calibrated set later is\n * a data change, not a code change.\n *\n * Units: milliseconds for time, channel units for distance, channel units per\n * second for velocity.\n */\nexport interface ReducedMotionTunables {\n /**\n * Floor applied to every spring's ζ. At 1 the spring is critically damped:\n * fastest arrival with no overshoot, which is the elastic term §Accessibility\n * removes.\n */\n readonly minDampingRatio: number;\n /**\n * Multiplier on positional response times — \"shortens morphs to non-elastic\n * interpolation\". Below 1 shortens.\n */\n readonly morphResponseFactor: number;\n}\n\nexport interface MotionProfile {\n readonly frame: FramePolicy;\n /** One driver configuration per channel. Its `kind` must match the binding table. */\n readonly channels: Readonly<Record<MotionChannel, DriverConfig>>;\n readonly stateTargets: StateTargetTable;\n readonly reducedMotion: ReducedMotionTunables;\n /**\n * Geometric scale removed at full press compression — the \"~1–2%\" of parent\n * acceptance #3. The `pressCompression` channel runs 0…1; this is what 1\n * means, so the renderer needs no constant of its own.\n */\n readonly pressCompressionScale: number;\n /**\n * Whether `withReducedMotion` has run over this profile. Derived, not a\n * tunable: it keeps the policy idempotent, and it lets a consumer report which\n * profile is actually in force rather than inferring it from a media query it\n * may not have been the one to read.\n */\n readonly reducedMotionApplied: boolean;\n}\n\nexport const DEFAULT_MOTION_PROFILE: MotionProfile = {\n frame: { maxDeltaMs: 50 },\n\n channels: {\n // Layout and morph geometry. Slightly underdamped: a trace of overshoot is\n // what reads as material rather than as a tween.\n position: {\n kind: \"interruptible-spring\",\n responseMs: 420,\n dampingRatio: 0.82,\n restDistance: 0.02,\n restVelocity: 0.05,\n },\n size: {\n kind: \"interruptible-spring\",\n responseMs: 460,\n dampingRatio: 0.85,\n restDistance: 0.02,\n restVelocity: 0.05,\n },\n radius: {\n kind: \"interruptible-spring\",\n responseMs: 460,\n dampingRatio: 0.9,\n restDistance: 0.02,\n restVelocity: 0.05,\n },\n\n // Press compression. Faster and looser than layout — the release bounce is\n // most of what makes a press feel physical.\n pressCompression: {\n kind: \"interruptible-spring\",\n responseMs: 260,\n dampingRatio: 0.72,\n restDistance: 1e-3,\n restVelocity: 5e-3,\n },\n\n // Optical strength must not overshoot: past 1 the lens visibly over-bends.\n lensStrength: {\n kind: \"critically-damped\",\n responseMs: 300,\n dampingRatio: 1,\n restDistance: 1e-3,\n restVelocity: 5e-3,\n },\n\n // Fast attack, slow decay: light arrives with the finger and lingers after.\n glow: { kind: \"attack-decay\", attackMs: 70, decayMs: 320, restDistance: 1e-3 },\n\n // Backdrop adaptation. Long constant and a wide band so scrolling past a\n // contrast edge cannot pump the material.\n backdropAdaptation: {\n kind: \"low-pass-hysteresis\",\n timeConstantMs: 500,\n hysteresis: 0.04,\n restDistance: 1e-3,\n },\n\n // Foreground light/dark. Band wide enough that mid-luminance backdrops do\n // not oscillate the text colour.\n foregroundTone: {\n kind: \"threshold-crossfade\",\n threshold: 0.5,\n hysteresis: 0.08,\n crossfadeMs: 180,\n },\n\n materialization: { kind: \"monotonic-ease\", durationMs: 220, easing: \"easeOutCubic\" },\n\n disabled: { kind: \"step\", hysteresis: 0, cooldownMs: 0 },\n\n // Quality tier. Long dwell, half-a-tier band: §Performance's governor\n // switches tiers only with long hysteresis and cooldown.\n qualityTier: { kind: \"hysteresis-cooldown\", hysteresis: 0.5, cooldownMs: 2500 },\n },\n\n // Interaction-driven targets. Geometry, backdrop and governor channels are\n // absent because interaction state does not decide them (§channels).\n stateTargets: {\n idle: {\n pressCompression: 0,\n lensStrength: 1,\n glow: 0,\n materialization: 1,\n disabled: 0,\n },\n hover: {\n pressCompression: 0,\n lensStrength: 1.06,\n glow: 0.28,\n materialization: 1,\n disabled: 0,\n },\n pressed: {\n pressCompression: 1,\n lensStrength: 1.14,\n glow: 1,\n materialization: 1,\n disabled: 0,\n },\n focused: {\n pressCompression: 0,\n lensStrength: 1.03,\n glow: 0.16,\n materialization: 1,\n disabled: 0,\n },\n disabled: {\n pressCompression: 0,\n lensStrength: 0.5,\n glow: 0,\n materialization: 1,\n disabled: 1,\n },\n morphing: {\n pressCompression: 0,\n lensStrength: 1.1,\n glow: 0.35,\n materialization: 1,\n disabled: 0,\n },\n },\n\n reducedMotion: { minDampingRatio: 1, morphResponseFactor: 0.7 },\n\n pressCompressionScale: 0.015,\n\n reducedMotionApplied: false,\n};\n\n/** A profile patch: any subset, to any depth, of what a profile holds. */\nexport interface MotionProfilePatch {\n readonly frame?: Partial<FramePolicy>;\n readonly channels?: Readonly<Partial<Record<MotionChannel, DriverConfigPatch>>>;\n readonly stateTargets?: Readonly<Partial<Record<InteractionState, Readonly<Partial<Record<MotionChannel, number>>>>>>;\n readonly reducedMotion?: Partial<ReducedMotionTunables>;\n readonly pressCompressionScale?: number;\n}\n\n/**\n * A patch to one channel's driver configuration: either a subset of one\n * family's constants, or a whole replacement configuration.\n *\n * Written as a union of the families rather than `Partial<Omit<DriverConfig,\n * \"kind\">>`, which collapses to `{}` — the union members share only `kind`, so\n * omitting it leaves nothing to be partial about, and every typo would pass.\n *\n * Naming a different `kind` replaces the configuration outright: half a\n * spring's constants merged onto an ease would type-check and mean nothing.\n */\nexport type DriverConfigPatch =\n | DriverConfig\n | Readonly<Partial<SpringConfig>>\n | Readonly<Partial<ExponentialConfig>>\n | Readonly<Partial<LowPassConfig>>\n | Readonly<Partial<ThresholdCrossfadeConfig>>\n | Readonly<Partial<EaseConfig>>\n | Readonly<Partial<StepConfig>>;\n\n/**\n * Apply a patch. This is how a calibration profile lands: C7 emits the measured\n * numbers, the app passes them here, and every constant above is replaceable\n * without touching this file.\n */\nexport function withProfileOverrides(\n base: MotionProfile,\n patch: MotionProfilePatch,\n): MotionProfile {\n const channels = {} as Record<MotionChannel, DriverConfig>;\n for (const channel of MOTION_CHANNELS) {\n channels[channel] = mergeDriverConfig(base.channels[channel], patch.channels?.[channel]);\n }\n\n const stateTargets = {} as Record<InteractionState, Readonly<Partial<Record<MotionChannel, number>>>>;\n for (const state of INTERACTION_STATES) {\n stateTargets[state] = { ...base.stateTargets[state], ...patch.stateTargets?.[state] };\n }\n\n return {\n frame: { ...base.frame, ...patch.frame },\n channels,\n stateTargets,\n reducedMotion: { ...base.reducedMotion, ...patch.reducedMotion },\n pressCompressionScale: patch.pressCompressionScale ?? base.pressCompressionScale,\n // Derived, so a patch cannot claim it. Patching a reduced profile keeps it\n // reduced, and the patch's own numbers are taken at face value.\n reducedMotionApplied: base.reducedMotionApplied,\n };\n}\n\nfunction mergeDriverConfig(\n base: DriverConfig,\n patch: DriverConfigPatch | undefined,\n): DriverConfig {\n if (patch === undefined) return base;\n\n // Only one union member carries `kind`, and reading it is the whole\n // discrimination, so probe for it rather than narrowing seven branches.\n const patchKind = (patch as { readonly kind?: DriverConfig[\"kind\"] }).kind;\n if (patchKind !== undefined && patchKind !== base.kind) return patch as DriverConfig;\n\n return { ...base, ...patch } as DriverConfig;\n}\n"]}