@ikijs/engine 0.1.1 → 0.3.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/README.md +25 -31
- package/dist/index.d.mts +60 -2
- package/dist/index.d.ts +60 -2
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +81 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -3
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/math.ts","../src/parameter-store.ts","../src/affine.ts","../src/deform.ts","../src/warp.ts","../src/warp-grid.ts","../src/player.ts","../src/idle-motion.ts","../src/frame-clock.ts","../src/physics-motion.ts","../src/hair-chain-motion.ts"],"sourcesContent":["/**\n * Clamp `value` into `[min, max]`.\n *\n * NaN in, NaN out — `Math.max(min, Math.min(max, NaN))` is `NaN`, so this\n * cannot be used to sanitize external input. Anything taking values from a\n * host must reject non-finite input itself before clamping (see\n * {@link ParameterStore.set}).\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n}\n","import type { IkiParameter } from \"@ikijs/format\";\nimport { clamp } from \"./math\";\n\n/**\n * Holds the live value of every model parameter, clamped to its declared\n * range. This is the single surface a host drives (lip-sync, gaze, blink) and\n * the engine reads each frame to evaluate bindings.\n */\nexport class ParameterStore {\n private readonly params = new Map<string, IkiParameter>();\n private readonly values = new Map<string, number>();\n /**\n * Resting value per id: the declared default clamped into range, resolved\n * ONCE here so `reset()` is a straight copy and a malformed descriptor is\n * reported once rather than on every reset.\n */\n private readonly defaults = new Map<string, number>();\n\n constructor(parameters: IkiParameter[]) {\n for (const param of parameters) {\n this.params.set(param.id, param);\n // A non-finite default would survive `clamp` and poison every read, so it\n // falls back to the neutral in-range value — but say so rather than\n // repairing a broken descriptor silently. `parseIkiModel` requires a\n // finite default, so this only reaches a host that skipped the validator.\n if (!Number.isFinite(param.default)) {\n console.error(\n `Iki: parameter \"${param.id}\" has a non-finite default; resting at the neutral in-range value instead`,\n );\n }\n const base = Number.isFinite(param.default) ? param.default : 0;\n this.defaults.set(param.id, clamp(base, param.min, param.max));\n }\n for (const [id, value] of this.defaults) this.values.set(id, value);\n }\n\n /**\n * Set a parameter's value, clamped to its range. Unknown ids are ignored, as\n * are non-finite values: this is the boundary a host drives with live signals,\n * and `clamp` cannot filter NaN (`Math.max(min, Math.min(max, NaN))` is NaN),\n * so one bad lip-sync/gaze frame would otherwise poison every binding that\n * reads the parameter. A dropped write holds the last good pose.\n */\n set(id: string, value: number): void {\n const param = this.params.get(id);\n if (!param) return;\n if (!Number.isFinite(value)) return;\n this.values.set(id, clamp(value, param.min, param.max));\n }\n\n /** Current value, or 0 if the id is unknown. */\n get(id: string): number {\n return this.values.get(id) ?? 0;\n }\n\n /** Position of a parameter within its range, 0..1. */\n normalized(id: string): number {\n const param = this.params.get(id);\n if (!param || param.max === param.min) return 0;\n return (this.get(id) - param.min) / (param.max - param.min);\n }\n\n /** Reset every parameter to its resting value (see `defaults`). */\n reset(): void {\n for (const [id, value] of this.defaults) this.values.set(id, value);\n }\n\n list(): IkiParameter[] {\n return [...this.params.values()];\n }\n}\n","// --- 2D affine helpers ------------------------------------------------------\n// Affine stored as [a, b, c, d, e, f] => | a c e |\n// | b d f |\n// | 0 0 1 |\nexport type Affine = [number, number, number, number, number, number];\n\nexport function translate(tx: number, ty: number): Affine {\n return [1, 0, 0, 1, tx, ty];\n}\n\nexport function scale(sx: number, sy: number): Affine {\n return [sx, 0, 0, sy, 0, 0];\n}\n\nexport function rotate(degrees: number): Affine {\n const r = (degrees * Math.PI) / 180;\n const c = Math.cos(r);\n const s = Math.sin(r);\n return [c, s, -s, c, 0, 0];\n}\n\nexport function multiply(a: Affine, b: Affine): Affine {\n return [\n a[0] * b[0] + a[2] * b[1],\n a[1] * b[0] + a[3] * b[1],\n a[0] * b[2] + a[2] * b[3],\n a[1] * b[2] + a[3] * b[3],\n a[0] * b[4] + a[2] * b[5] + a[4],\n a[1] * b[4] + a[3] * b[5] + a[5],\n ];\n}\n\n/** Expand a 2D affine into a column-major mat3 for `uniformMatrix3fv`. */\nexport function toMat3(a: Affine): Float32Array {\n return new Float32Array([a[0], a[1], 0, a[2], a[3], 0, a[4], a[5], 1]);\n}\n","import type {\n IkiBinding,\n IkiDeformer,\n IkiDeformerBinding,\n IkiDeformerTransform,\n IkiMatrixDeformer,\n IkiTransform,\n} from \"@ikijs/format\";\nimport { type Affine, multiply, rotate, scale, translate } from \"./affine\";\nimport type { ParameterStore } from \"./parameter-store\";\n\n/** Resolved TRS + opacity from a transform + bindings at current parameter values. */\nexport interface ResolvedTransform {\n x: number;\n y: number;\n rotation: number;\n scaleX: number;\n scaleY: number;\n opacity: number;\n}\n\nconst IDENTITY_TRANSFORM: Required<IkiTransform> = {\n x: 0,\n y: 0,\n rotation: 0,\n scaleX: 1,\n scaleY: 1,\n opacity: 1,\n};\n\n/**\n * Shared transform evaluator: resolves the effective TRS + opacity from a\n * (possibly absent) base transform plus bindings at current parameter values.\n *\n * - Part callers pass `IkiTransform` (which may include opacity) and use all 6\n * fields including `opacity`.\n * - Deformer callers pass `IkiDeformerTransform` (no opacity field) and\n * `IkiDeformerBinding[]` (no opacity channel); the returned `opacity` is\n * always 1 on the deformer path and should be ignored by the caller.\n */\nexport function evaluateTransform(\n transform: IkiTransform | IkiDeformerTransform | undefined,\n bindings: IkiBinding[] | IkiDeformerBinding[] | undefined,\n params: ParameterStore,\n): ResolvedTransform {\n const base = transform ?? IDENTITY_TRANSFORM;\n const result: ResolvedTransform = {\n x: base.x,\n y: base.y,\n rotation: base.rotation ?? 0,\n scaleX: base.scaleX ?? 1,\n scaleY: base.scaleY ?? 1,\n opacity: (base as IkiTransform).opacity ?? 1,\n };\n\n for (const binding of bindings ?? []) {\n const t = params.normalized(binding.parameter);\n const value = binding.from + (binding.to - binding.from) * t;\n switch (binding.channel) {\n case \"translateX\":\n result.x += value;\n break;\n case \"translateY\":\n result.y += value;\n break;\n case \"rotate\":\n result.rotation += value;\n break;\n case \"scaleX\":\n result.scaleX += value;\n break;\n case \"scaleY\":\n result.scaleY += value;\n break;\n case \"opacity\":\n result.opacity *= value;\n break;\n }\n }\n\n return result;\n}\n\n/**\n * Build the local deformer matrix about its pivot:\n * translate(pivot) · TRS · translate(-pivot)\n */\nfunction deformerLocalMatrix(\n d: IkiMatrixDeformer,\n params: ParameterStore,\n): Affine {\n const t = evaluateTransform(d.transform, d.bindings, params);\n const trs: Affine = multiply(\n multiply(translate(t.x, t.y), rotate(t.rotation)),\n scale(t.scaleX, t.scaleY),\n );\n return multiply(\n multiply(translate(d.pivot.x, d.pivot.y), trs),\n translate(-d.pivot.x, -d.pivot.y),\n );\n}\n\n/**\n * Resolve every deformer's world matrix in topological order, regardless of\n * array ordering. Returns a Map from deformer id to world-space Affine.\n *\n * The validator guarantees the hierarchy is acyclic and that every `parent`\n * id exists; the engine resolves on-demand with memoization so any valid\n * array order is handled correctly.\n *\n * Throws a clear internal Error if a parent is unexpectedly absent (defense-\n * in-depth — indicates an unvalidated model was passed to the engine).\n */\nexport function resolveDeformerWorlds(\n deformers: IkiDeformer[],\n params: ParameterStore,\n): Map<string, Affine> {\n // Warp deformers are non-affine; filter them out so matrix-only fields\n // (pivot/transform/bindings) are accessible and warp deformers are never\n // resolved as matrix deformers (which would produce NaN pivots).\n // resolveWarpGrids (warp-grid.ts) handles warp deformers separately.\n const matrixDeformers = deformers.filter(\n (d): d is IkiMatrixDeformer => d.kind === \"matrix\" || d.kind === undefined,\n );\n const byId = new Map<string, IkiMatrixDeformer>(\n matrixDeformers.map((d) => [d.id, d]),\n );\n const worldById = new Map<string, Affine>();\n\n function resolve(d: IkiMatrixDeformer): Affine {\n const cached = worldById.get(d.id);\n if (cached) return cached;\n\n const local = deformerLocalMatrix(d, params);\n let world: Affine;\n if (d.parent === undefined) {\n world = local;\n } else {\n const parentDef = byId.get(d.parent);\n if (!parentDef) {\n throw new Error(\n `unresolved deformer parent \"${d.parent}\" — model not validated?`,\n );\n }\n world = multiply(resolve(parentDef), local);\n }\n\n worldById.set(d.id, world);\n return world;\n }\n\n for (const d of matrixDeformers) {\n resolve(d);\n }\n\n return worldById;\n}\n","import type { IkiGrid2DKeyform, IkiWarp } from \"@ikijs/format\";\nimport type { ParameterStore } from \"./parameter-store\";\n\n/**\n * Accumulate one keyform set's interpolated offsets into `out` (out += ...).\n * Clamps `value` to [keyforms[0].value, keyforms[last].value] (no extrapolation),\n * linearly interpolates the bracketing pair, and adds. `out.length` must be >=\n * each keyform's `offsets.length`. Shared by part-local mesh warps and grid warps.\n */\nexport function accumulateKeyformOffsets(\n keyforms: { value: number; offsets: ArrayLike<number> }[],\n value: number,\n out: Float32Array,\n): void {\n const ks = keyforms;\n\n if (value <= ks[0].value) {\n // Clamp to first keyform.\n const { offsets } = ks[0];\n for (let i = 0; i < offsets.length; i++) {\n out[i] += offsets[i];\n }\n } else if (value >= ks[ks.length - 1].value) {\n // Clamp to last keyform.\n const { offsets } = ks[ks.length - 1];\n for (let i = 0; i < offsets.length; i++) {\n out[i] += offsets[i];\n }\n } else {\n // Find bracketing pair with a linear scan (keyforms are small in practice).\n let lo = ks[0];\n let hi = ks[1];\n for (let k = 1; k < ks.length - 1; k++) {\n if (ks[k].value <= value) {\n lo = ks[k];\n hi = ks[k + 1];\n }\n }\n const t = (value - lo.value) / (hi.value - lo.value);\n const loOff = lo.offsets;\n const hiOff = hi.offsets;\n for (let i = 0; i < loOff.length; i++) {\n out[i] += loOff[i] + (hiOff[i] - loOff[i]) * t;\n }\n }\n}\n\n/**\n * Accumulate a 2D keyform grid's bilinear-interpolated offsets into `out` (out += ...).\n *\n * This is PARAMETER bilinear interpolation driven by two live parameter values\n * (`vx` along `valuesX`, `vy` along `valuesY`). It is DISTINCT from\n * {@link sampleWarpGrid}'s spatial bilinear (which interpolates model-space\n * control-point positions). Name params `tx`/`ty` to avoid confusion with\n * the spatial `s`/`t` in sampleWarpGrid.\n *\n * Contract mirrors {@link accumulateKeyformOffsets}: `out += blended offsets`,\n * pure, deterministic, no allocation inside the function, O(points).\n *\n * Bracket rule (applied identically to X and Y; length ≥ 2 is validator-guaranteed):\n * - v <= values[0] → i = 0, t = 0\n * - v >= values[last] → i = length - 2, t = 1\n * - interior → i such that values[i] <= v < values[i+1], t = (v - values[i]) / (values[i+1] - values[i])\n * This keeps i ∈ [0, length-2] so i+1 is always in range.\n *\n * Corner index (row-major): k(i, j) = j * valuesX.length + i\n */\nexport function accumulate2DKeyformOffsets(\n valuesX: number[],\n valuesY: number[],\n keyforms2d: IkiGrid2DKeyform[],\n vx: number,\n vy: number,\n out: Float32Array,\n): void {\n const lastX = valuesX.length - 1;\n const lastY = valuesY.length - 1;\n\n // Per-axis bracket: clamp at both ends, interior normal scan.\n let ix: number;\n let tx: number;\n if (vx <= valuesX[0]) {\n ix = 0;\n tx = 0;\n } else if (vx >= valuesX[lastX]) {\n ix = lastX - 1;\n tx = 1;\n } else {\n ix = 0;\n for (let k = 0; k < lastX - 1; k++) {\n if (valuesX[k + 1] <= vx) ix = k + 1;\n }\n tx = (vx - valuesX[ix]) / (valuesX[ix + 1] - valuesX[ix]);\n }\n\n let iy: number;\n let ty: number;\n if (vy <= valuesY[0]) {\n iy = 0;\n ty = 0;\n } else if (vy >= valuesY[lastY]) {\n iy = lastY - 1;\n ty = 1;\n } else {\n iy = 0;\n for (let k = 0; k < lastY - 1; k++) {\n if (valuesY[k + 1] <= vy) iy = k + 1;\n }\n ty = (vy - valuesY[iy]) / (valuesY[iy + 1] - valuesY[iy]);\n }\n\n // Row-major corners: k(i, j) = j * valuesX.length + i.\n const W = valuesX.length;\n const c00 = keyforms2d[iy * W + ix];\n const c10 = keyforms2d[iy * W + ix + 1];\n const c01 = keyforms2d[(iy + 1) * W + ix];\n const c11 = keyforms2d[(iy + 1) * W + ix + 1];\n\n // Per-component bilinear: top/bot over tx, then ty into out.\n const o00 = c00.offsets;\n const o10 = c10.offsets;\n const o01 = c01.offsets;\n const o11 = c11.offsets;\n for (let n = 0; n < o00.length; n++) {\n const top = o00[n] + (o10[n] - o00[n]) * tx;\n const bot = o01[n] + (o11[n] - o01[n]) * tx;\n out[n] += top + (bot - top) * ty;\n }\n}\n\n/**\n * Apply all warps to `rest`, accumulating per-vertex offsets into `out`.\n *\n * - Copies `rest` into `out` first (out = rest).\n * - For each warp: looks up the live parameter value (REAL range, not\n * normalized), clamps to the keyform range (no extrapolation), linearly\n * interpolates the bracketing pair, and ADDS the result to `out`.\n * - No allocation: writes into the caller-supplied `out`. `rest` is never\n * mutated.\n * - `undefined` warps → identity copy (out equals rest).\n */\nexport function applyWarps(\n rest: Float32Array,\n warps: IkiWarp[] | undefined,\n params: ParameterStore,\n out: Float32Array,\n): void {\n out.set(rest);\n\n if (!warps || warps.length === 0) return;\n\n for (const warp of warps) {\n accumulateKeyformOffsets(warp.keyforms, params.get(warp.parameter), out);\n }\n}\n","import type { IkiDeformer, IkiWarpGrid } from \"@ikijs/format\";\nimport type { Affine } from \"./affine\";\nimport type { ParameterStore } from \"./parameter-store\";\nimport { clamp } from \"./math\";\nimport { accumulate2DKeyformOffsets, accumulateKeyformOffsets } from \"./warp\";\n\n/** A warp deformer's deformed control grid for one frame. */\nexport interface ResolvedWarpGrid {\n cols: number;\n rows: number;\n /** Deformed control points, MODEL space, length (cols+1)*(rows+1)*2. */\n points: Float32Array;\n}\n\n/**\n * For each warp deformer: take its rest `grid.points`, ADD the interpolated\n * grid-keyform offsets (accumulateKeyformOffsets) in the deformer's own rest\n * frame, THEN apply the parent matrix deformer's resolved world affine (if any)\n * — i.e. `parentAffine · (rest + offsets)`. Returns a Map from warp-deformer id\n * to its deformed grid (model space).\n *\n * `matrixWorlds` is the output of resolveDeformerWorlds (matrix deformers only);\n * warp deformers are skipped by that resolver (they are non-affine).\n *\n * ORDER IS CRITICAL: keyform offsets FIRST (curvature added in the rest frame),\n * parent affine SECOND — so the curvature rotates WITH the parent head rather\n * than staying pinned to world axes. The reversed order (affine then offsets)\n * pushes the bend along world-x even when the head is turned (coordinate bug).\n */\nexport function resolveWarpGrids(\n deformers: IkiDeformer[],\n params: ParameterStore,\n matrixWorlds: Map<string, Affine>,\n): Map<string, ResolvedWarpGrid> {\n const resolved = new Map<string, ResolvedWarpGrid>();\n\n for (const d of deformers) {\n if (d.kind !== \"warp\") continue;\n\n const { cols, rows, points: restPoints } = d.grid;\n const points = Float32Array.from(restPoints);\n\n // 1. Curvature in the rest frame: offsets += per-control-point deltas.\n for (const warp of d.warps ?? []) {\n accumulateKeyformOffsets(\n warp.keyforms,\n params.get(warp.parameter),\n points,\n );\n }\n // 1D xor 2D is validator-enforced; at most one branch contributes per deformer.\n if (d.warp2d !== undefined) {\n accumulate2DKeyformOffsets(\n d.warp2d.valuesX,\n d.warp2d.valuesY,\n d.warp2d.keyforms2d,\n params.get(d.warp2d.parameter),\n params.get(d.warp2d.parameterY),\n points,\n );\n }\n\n // 2. Parent matrix deformer's world affine (if any): parentAffine · (rest + offsets).\n if (d.parent !== undefined) {\n const parentAffine = matrixWorlds.get(d.parent);\n if (parentAffine) {\n for (let i = 0; i < points.length; i += 2) {\n const x = points[i];\n const y = points[i + 1];\n points[i] =\n parentAffine[0] * x + parentAffine[2] * y + parentAffine[4];\n points[i + 1] =\n parentAffine[1] * x + parentAffine[3] * y + parentAffine[5];\n }\n }\n }\n\n resolved.set(d.id, { cols, rows, points });\n }\n\n return resolved;\n}\n\n/** A model-space point bound to a rest-grid cell with within-cell (s,t). */\nexport interface GridBinding {\n /** row*cols + col index of the containing cell. */\n cell: number;\n /** [0,1] within-cell horizontal, 0 at the left (smaller-x) edge. */\n s: number;\n /** [0,1] within-cell vertical, 0 at the TOP (larger-y) edge. */\n t: number;\n}\n\n/**\n * Bind a model-space point to the REST grid: computes the containing cell +\n * local (s,t) by linear mapping over the grid's actual column/row boundaries.\n * Out-of-bounds points clamp to an edge cell with s/t pinned to 0/1. Never\n * returns NaN. Do NOT call against a deformed grid.\n *\n * The rest grid is validated to be a regular axis-aligned lattice with EXACT\n * ordering, so boundaries are read DIRECTLY from `restGrid.points` (no sorting):\n * - row-major, +y up; row 0 = TOP (LARGEST y, y DECREASES with row index);\n * - column 0 = LEFT (smallest x, x INCREASES with column index).\n * Column x-boundaries are row 0's x values `points[col*2]`; row y-boundaries are\n * column 0's y values `points[(row*(cols+1))*2 + 1]`. Maps x left→right and y\n * top→bottom: `s = (x - xLeft)/(xRight - xLeft)`, `t = (yTop - y)/(yTop - yBottom)`\n * (numerator `yTop - y`, NOT `y - minY`, so rows are not vertically flipped).\n */\nexport function bindPointToRestGrid(\n x: number,\n y: number,\n restGrid: IkiWarpGrid,\n): GridBinding {\n const { cols, rows, points } = restGrid;\n const stride = cols + 1;\n\n // Column boundaries: row 0's x values, increasing with column index.\n let col = cols - 1;\n for (let c = 0; c < cols; c++) {\n const xRight = points[(c + 1) * 2];\n if (x < xRight) {\n col = c;\n break;\n }\n }\n const xLeft = points[col * 2];\n const xRight = points[(col + 1) * 2];\n const s = clamp((x - xLeft) / (xRight - xLeft), 0, 1);\n\n // Row boundaries: column 0's y values, decreasing with row index (top→bottom).\n let row = rows - 1;\n for (let r = 0; r < rows; r++) {\n const yBottom = points[(r + 1) * stride * 2 + 1];\n if (y > yBottom) {\n row = r;\n break;\n }\n }\n const yTop = points[row * stride * 2 + 1];\n const yBottom = points[(row + 1) * stride * 2 + 1];\n const t = clamp((yTop - y) / (yTop - yBottom), 0, 1);\n\n return { cell: row * cols + col, s, t };\n}\n\n/**\n * Compute model-space positions for a warp-deformer child's mesh vertices:\n * transform each LOCAL-space vertex by `partAffine`, rebind to the RAW rest\n * grid, and bilinear-sample the deformed grid. Writes `out` (length ===\n * localVerts.length). Affine layout [a,b,c,d,e,f]: x'=a*x+c*y+e, y'=b*x+d*y+f.\n */\nexport function applyWarpToChild(\n localVerts: Float32Array | number[],\n partAffine: Affine,\n restGrid: IkiWarpGrid,\n deformedGrid: ResolvedWarpGrid,\n out: Float32Array,\n): void {\n const n = localVerts.length / 2;\n for (let v = 0; v < n; v++) {\n const lx = localVerts[v * 2];\n const ly = localVerts[v * 2 + 1];\n const mx = partAffine[0] * lx + partAffine[2] * ly + partAffine[4];\n const my = partAffine[1] * lx + partAffine[3] * ly + partAffine[5];\n const binding = bindPointToRestGrid(mx, my, restGrid);\n const [sx, sy] = sampleWarpGrid(deformedGrid, binding);\n out[v * 2] = sx;\n out[v * 2 + 1] = sy;\n }\n}\n\n/**\n * Bilinear-sample a deformed grid at a binding, returning model-space [x, y].\n * Reads the 4 corner control points of `binding.cell` from `grid.points`, using\n * the SAME row/col convention as `bindPointToRestGrid` (s left→right between\n * col and col+1, t top→bottom between row and row+1).\n */\nexport function sampleWarpGrid(\n grid: ResolvedWarpGrid,\n binding: GridBinding,\n): [number, number] {\n const { cols, points } = grid;\n const stride = cols + 1;\n const row = Math.floor(binding.cell / cols);\n const col = binding.cell % cols;\n const { s, t } = binding;\n\n const i00 = (row * stride + col) * 2;\n const i10 = (row * stride + col + 1) * 2;\n const i01 = ((row + 1) * stride + col) * 2;\n const i11 = ((row + 1) * stride + col + 1) * 2;\n\n // Top edge: lerp p00→p10 by s; bottom edge: lerp p01→p11 by s.\n const topX = points[i00] + (points[i10] - points[i00]) * s;\n const topY = points[i00 + 1] + (points[i10 + 1] - points[i00 + 1]) * s;\n const botX = points[i01] + (points[i11] - points[i01]) * s;\n const botY = points[i01 + 1] + (points[i11 + 1] - points[i01 + 1]) * s;\n\n // Vertical: lerp top→bottom by t.\n return [topX + (botX - topX) * t, topY + (botY - topY) * t];\n}\n","import type {\n IkiModel,\n IkiParameter,\n IkiPart,\n IkiWarp,\n IkiWarpDeformer,\n} from \"@ikijs/format\";\nimport { ParameterStore } from \"./parameter-store\";\nimport { multiply, rotate, scale, toMat3, translate } from \"./affine\";\nimport { evaluateTransform, resolveDeformerWorlds } from \"./deform\";\nimport { applyWarps } from \"./warp\";\nimport { applyWarpToChild, resolveWarpGrids } from \"./warp-grid\";\n\n/**\n * Alpha threshold used only during the stencil mask-write pass: a mask fragment\n * marks the stencil only where its coverage alpha is at least this. Keeps the\n * clip region to the mask's opaque body, not its anti-aliased fringe.\n */\nconst MASK_ALPHA_CUTOFF = 0.5;\n\n/**\n * Outcome of {@link IkiPlayer.load}: the indices into `model.textures` that\n * failed to decode or upload (empty = every declared texture loaded). The model\n * is still swapped in and rendered; parts using a failed texture are skipped.\n * A host can inspect this to detect and report a partial load.\n */\nexport interface IkiLoadResult {\n failedTextures: number[];\n /**\n * True when a newer `load()` (or `destroy()`) superseded this call before it\n * adopted anything — the model was NOT loaded and `failedTextures` is empty\n * because nothing was attempted, not because everything succeeded. Without\n * this flag a caller awaiting the losing promise cannot tell the two apart.\n */\n superseded: boolean;\n}\n\n/**\n * Engine-internal runtime representation of an uploaded mesh.\n *\n * `rest` is a copy of the authored vertices (Float32Array for direct GL upload);\n * `scratch` is a same-length preallocated buffer that the per-frame warp\n * pipeline writes morphed positions into before uploading.\n *\n * Index winding convention for an implicit-quad fixture: [0,1,2, 2,1,3]\n * (counter-clockwise from bottom-left). CULL_FACE is disabled, so winding\n * direction is not enforced, but mesh generators must match this.\n */\ninterface PartMesh {\n position: WebGLBuffer;\n uv: WebGLBuffer;\n index: WebGLBuffer;\n indexCount: number;\n rest: Float32Array;\n /** Preallocated warp output buffer; only present when `warps` is non-empty. */\n scratch?: Float32Array;\n warps?: IkiWarp[];\n /** The warp deformer this mesh hangs off, if any (its part.deformer is kind:\"warp\"). */\n warpDeformer?: IkiWarpDeformer;\n /** Local-space scratch (same length as `rest`) for the per-frame warp pipeline. */\n local?: Float32Array;\n}\n\n/**\n * Drives a single `.iki` model on a WebGL2 canvas.\n *\n * v1 scope: parts are solid-color or atlas-sampled textured quads or meshes,\n * transformed each frame by their base transform plus the sum of their parameter\n * bindings. `load()` is async — it decodes and uploads textures before swapping\n * the model in. Mesh parts additionally carry per-vertex UV and optional warp\n * keyforms, interpolated each frame on the CPU into a dynamic vertex buffer.\n */\nexport class IkiPlayer {\n private readonly gl: WebGL2RenderingContext;\n private readonly program: WebGLProgram;\n private readonly quad: WebGLBuffer;\n private readonly uMatrix: WebGLUniformLocation;\n private readonly uColor: WebGLUniformLocation;\n private readonly uUseTexture: WebGLUniformLocation;\n private readonly uTex: WebGLUniformLocation;\n private readonly uUvOffset: WebGLUniformLocation;\n private readonly uUvScale: WebGLUniformLocation;\n private readonly uUseMeshUv: WebGLUniformLocation;\n private readonly uAlphaCutoff: WebGLUniformLocation;\n private readonly aPos: number;\n private readonly aUv: number;\n /** True when the context granted a stencil buffer; clipping needs it. */\n private readonly stencilAvailable: boolean;\n\n private model?: IkiModel;\n private parts: IkiPart[] = [];\n private params = new ParameterStore([]);\n private rafId?: number;\n /** Uploaded textures, index-aligned with `model.textures`; `null` = unusable. */\n private textures: (WebGLTexture | null)[] = [];\n /** Bumped by every `load` and by `destroy`; lets a stale async load bail. */\n private loadGeneration = 0;\n /** True from the moment `load()` is entered until it resolves or throws. */\n private loadPending = false;\n /** Latches the un-awaited-load report, so a repeat caller says it once. */\n private warnedLoadUnfinished = false;\n private destroyed = false;\n /**\n * Engine-internal mesh buffers, keyed by the part's INDEX in `this.parts`\n * (NOT by part id — duplicate ids must not swap buffers).\n */\n private partMeshes = new Map<number, PartMesh>();\n /**\n * Clip groups resolved once per `load()`: consumer part index (into `this.parts`)\n * → its mask part indices. A part absent from this map is unclipped.\n */\n private partClipGroups = new Map<number, number[]>();\n\n constructor(private readonly canvas: HTMLCanvasElement) {\n const gl = canvas.getContext(\"webgl2\", {\n alpha: true,\n // The whole pipeline is premultiplied-alpha: the fragment shader\n // multiplies rgb by alpha, the blend function is ONE /\n // ONE_MINUS_SRC_ALPHA, and the page compositor reads the framebuffer\n // as premultiplied. Straight-alpha (`premultipliedAlpha: false`) cannot\n // be made consistent with SRC_ALPHA-style blending: semi-transparent\n // pixels over a transparent background come out premultiplied anyway\n // and composite too dark.\n premultipliedAlpha: true,\n // Stencil buffer backs clip masks (a part rendered only inside its masks'\n // coverage). Granted by every modern browser; the load() guard reports if not.\n stencil: true,\n });\n if (!gl) throw new Error(\"WebGL2 is not available in this browser\");\n this.gl = gl;\n this.stencilAvailable = gl.getContextAttributes()?.stencil ?? false;\n\n this.program = createProgram(gl, VERTEX_SHADER, FRAGMENT_SHADER);\n this.uMatrix = getUniform(gl, this.program, \"u_matrix\");\n this.uColor = getUniform(gl, this.program, \"u_color\");\n this.uUseTexture = getUniform(gl, this.program, \"u_useTexture\");\n this.uTex = getUniform(gl, this.program, \"u_tex\");\n this.uUvOffset = getUniform(gl, this.program, \"u_uvOffset\");\n this.uUvScale = getUniform(gl, this.program, \"u_uvScale\");\n this.uUseMeshUv = getUniform(gl, this.program, \"u_useMeshUv\");\n this.uAlphaCutoff = getUniform(gl, this.program, \"u_alphaCutoff\");\n // Fetch attribute locations here so renderFrame can set them explicitly\n // per draw path (mesh vs quad), rather than hiding the wiring in createUnitQuad.\n this.aPos = gl.getAttribLocation(this.program, \"a_pos\");\n this.aUv = gl.getAttribLocation(this.program, \"a_uv\");\n\n this.quad = createUnitQuad(gl);\n\n gl.enable(gl.BLEND);\n // Premultiplied-alpha \"over\": the shader already multiplied rgb by alpha.\n // (See the premultipliedAlpha context note above — SRC_ALPHA blending into\n // a straight-alpha canvas darkens semi-transparent parts.)\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n }\n\n /**\n * Load a model and reset parameters to their defaults. All textures are\n * decoded and uploaded before the model is swapped in — the swap is atomic,\n * so you never see a partially-textured frame. `start()` may be called any\n * time, but nothing renders until the first `load()` resolves. For an\n * embedded `data:` atlas this is near-instant.\n *\n * Individual texture decode/upload failures are non-fatal: they are logged\n * via `console.error`, the affected parts are skipped, and `load()` still\n * resolves — the returned {@link IkiLoadResult} lists the indices of any\n * textures that failed, so a host can detect and report a partial load. The\n * model is assumed already validated by `@ikijs/format`.\n *\n * Mesh buffer allocation failure IS fatal (unlike per-texture skip) because\n * textures have an `IkiLoadResult.failedTextures` reporting surface and mesh\n * buffers have none — there is no partial-mesh concept in the format.\n *\n * AWAIT THIS before reading {@link getParameters}. The swap happens after\n * texture decoding, so an un-awaited `load()` leaves the parameter store\n * empty for the rest of the tick; `getParameters` reports that case rather\n * than letting a host conclude the model drives nothing.\n */\n async load(model: IkiModel): Promise<IkiLoadResult> {\n // `finally` rather than a reset before each `return`: a fatal mesh-buffer\n // throw must not latch this on and make every later getParameters() a\n // false alarm.\n this.loadPending = true;\n try {\n return await this.adoptModel(model);\n } finally {\n this.loadPending = false;\n }\n }\n\n private async adoptModel(model: IkiModel): Promise<IkiLoadResult> {\n const { gl } = this;\n const generation = ++this.loadGeneration;\n\n // v1 decodes `data:` URIs only; external sources are skipped (resolver TBD).\n const sources = model.textures ?? [];\n const decoded = await Promise.allSettled(\n sources.map((tex) => decodeTexture(tex.source)),\n );\n\n // A newer load() or destroy() superseded us while decoding — bail without\n // creating GL textures or swapping any state.\n if (generation !== this.loadGeneration || this.destroyed) {\n for (const result of decoded) {\n if (result.status === \"fulfilled\" && result.value) result.value.close();\n }\n return { failedTextures: [], superseded: true };\n }\n\n // Queried once per load, not per texture: a GL parameter read is a driver\n // round-trip and this bound is constant for the context's lifetime.\n // `getParameter` yields null on a LOST context — reachable here, because\n // load() awaits decoding — and `width > null` coerces to `width > 0`, which\n // would reject every texture and blame a \"nullpx limit\". Skip the bound\n // instead when it is unavailable; the upload path below reports the real\n // failure.\n const maxTextureSizeRaw: unknown = gl.getParameter(gl.MAX_TEXTURE_SIZE);\n const maxTextureSize =\n typeof maxTextureSizeRaw === \"number\" ? maxTextureSizeRaw : undefined;\n if (maxTextureSize === undefined) {\n // A non-number here has one cause: the context is gone. Report it, because\n // the drain below consumes the single CONTEXT_LOST_WEBGL the spec\n // guarantees, and a model of implicit quads would otherwise adopt cleanly\n // and blame \"textures failed to load\".\n console.error(\"Iki: WebGL context lost during load()\");\n }\n\n // [7] getError returns the OLDEST latched error and says nothing about which\n // call produced it, so anything the render loop left pending during the\n // await would be pinned on the first texture. Start from a clean slate.\n drainGlErrors(gl);\n\n const uploaded: (WebGLTexture | null)[] = decoded.map((result, i) => {\n if (result.status === \"rejected\") {\n console.error(`Iki: failed to decode textures[${i}]`, result.reason);\n return null;\n }\n const bitmap = result.value;\n // External source was skipped during decode.\n if (!bitmap) return null;\n\n // An atlas wider than the driver's limit uploads as a GL error and leaves\n // a non-null but unsamplable texture, which would then render as black\n // rather than being reported. Reject it up front instead.\n if (\n maxTextureSize !== undefined &&\n (bitmap.width > maxTextureSize || bitmap.height > maxTextureSize)\n ) {\n console.error(\n `Iki: textures[${i}] is ${bitmap.width}x${bitmap.height}, over this device's ${maxTextureSize}px limit`,\n );\n bitmap.close();\n return null;\n }\n\n const texture = gl.createTexture();\n if (!texture) {\n bitmap.close();\n console.error(`Iki: failed to allocate GL texture for textures[${i}]`);\n return null;\n }\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n // Clear again so the check after texImage2D can only reflect texImage2D.\n drainGlErrors(gl);\n gl.texImage2D(\n gl.TEXTURE_2D,\n 0,\n gl.RGBA,\n gl.RGBA,\n gl.UNSIGNED_BYTE,\n bitmap,\n );\n bitmap.close();\n // Out-of-memory and other upload failures surface only here; without the\n // check the slot stays non-null and is reported as a successful load.\n const uploadError = gl.getError();\n if (uploadError !== gl.NO_ERROR) {\n gl.deleteTexture(texture);\n console.error(\n `Iki: failed to upload textures[${i}] (GL error 0x${uploadError.toString(16)})`,\n );\n return null;\n }\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n return texture;\n });\n\n // Build the new model's render state into LOCAL variables BEFORE adopting\n // anything. Do NOT read this.parts here — it still points at the PREVIOUS\n // model until the adoption swap below; reading it would build buffers for\n // old parts keyed by the new loop's indices.\n const nextParts = [...model.parts].sort((a, b) => a.order - b.order);\n const nextPartMeshes = new Map<number, PartMesh>();\n\n for (let i = 0; i < nextParts.length; i++) {\n const part = nextParts[i];\n if (!part.mesh) continue;\n\n const { mesh } = part;\n // Collect buffers as they are created so we can clean up on partial failure.\n const currentPartBuffers: WebGLBuffer[] = [];\n\n const positionBuf = gl.createBuffer();\n if (!positionBuf) {\n // Nothing created yet for this part — clean up prior parts + textures.\n deletePartMeshBuffers(gl, nextPartMeshes);\n deleteUploadedTextures(gl, uploaded);\n throw new Error(\"Iki: failed to allocate mesh buffer\");\n }\n currentPartBuffers.push(positionBuf);\n\n const uvBuf = gl.createBuffer();\n if (!uvBuf) {\n // position VBO created but uv VBO failed — delete position to avoid leak.\n for (const b of currentPartBuffers) gl.deleteBuffer(b);\n deletePartMeshBuffers(gl, nextPartMeshes);\n deleteUploadedTextures(gl, uploaded);\n throw new Error(\"Iki: failed to allocate mesh buffer\");\n }\n currentPartBuffers.push(uvBuf);\n\n const indexBuf = gl.createBuffer();\n if (!indexBuf) {\n // position + uv created but index failed — delete both.\n for (const b of currentPartBuffers) gl.deleteBuffer(b);\n deletePartMeshBuffers(gl, nextPartMeshes);\n deleteUploadedTextures(gl, uploaded);\n throw new Error(\"Iki: failed to allocate mesh buffer\");\n }\n\n // All three buffers allocated — upload data.\n const rest = new Float32Array(mesh.vertices);\n // A mesh part whose `deformer` references a kind:\"warp\" deformer is a warp\n // child: it morphs every frame (bind→sample against the grid) even with no\n // part-local warps, so it also needs DYNAMIC_DRAW + scratch + a local buffer.\n const warpDeformer =\n part.deformer !== undefined\n ? model.deformers?.find(\n (d): d is IkiWarpDeformer =>\n d.kind === \"warp\" && d.id === part.deformer,\n )\n : undefined;\n const isWarpChild = warpDeformer !== undefined;\n // Only allocate scratch and use DYNAMIC_DRAW when this part has warps or is\n // a warp child; warp-less static meshes never morph and skip per-frame upload.\n const hasWarps = (part.warps?.length ?? 0) > 0;\n const dynamic = hasWarps || isWarpChild;\n const scratch = dynamic\n ? new Float32Array(mesh.vertices.length)\n : undefined;\n // Warp children also need a second scratch for the part-local pipeline\n // (applyWarps + TRS) before grid binding.\n const local = isWarpChild\n ? new Float32Array(mesh.vertices.length)\n : undefined;\n\n // Clear first so the check after the three uploads reflects only them.\n drainGlErrors(gl);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, positionBuf);\n gl.bufferData(\n gl.ARRAY_BUFFER,\n rest,\n dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW,\n );\n\n gl.bindBuffer(gl.ARRAY_BUFFER, uvBuf);\n gl.bufferData(\n gl.ARRAY_BUFFER,\n new Float32Array(mesh.uvs),\n gl.STATIC_DRAW,\n );\n\n // Vertex count is validator-capped at 65536, so Uint16 cannot wrap.\n gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuf);\n gl.bufferData(\n gl.ELEMENT_ARRAY_BUFFER,\n new Uint16Array(mesh.indices),\n gl.STATIC_DRAW,\n );\n\n // Allocation was checked above, but `bufferData` can still raise\n // OUT_OF_MEMORY and leave a valid-but-EMPTY buffer, which draws garbage or\n // nothing at all. The contract on this class calls a mesh-buffer failure\n // fatal precisely because it has no `failedTextures`-style reporting\n // surface — which only holds if the failure is detected here.\n const meshUploadError = gl.getError();\n if (meshUploadError !== gl.NO_ERROR) {\n for (const b of currentPartBuffers) gl.deleteBuffer(b);\n gl.deleteBuffer(indexBuf);\n deletePartMeshBuffers(gl, nextPartMeshes);\n deleteUploadedTextures(gl, uploaded);\n throw new Error(\n `Iki: failed to upload mesh buffers for part \"${part.id}\" (GL error 0x${meshUploadError.toString(16)})`,\n );\n }\n\n nextPartMeshes.set(i, {\n position: positionBuf,\n uv: uvBuf,\n index: indexBuf,\n indexCount: mesh.indices.length,\n rest,\n scratch,\n warps: part.warps,\n warpDeformer,\n local,\n });\n }\n\n // Resolve clip groups once: consumer part index → mask part indices. The\n // model is already validated (every mask id maps to a unique mesh part), so\n // this is a plain id→index lookup, not a per-frame reconstruction.\n const nextClipGroups = new Map<number, number[]>();\n const indexById = new Map<string, number>();\n for (let i = 0; i < nextParts.length; i++) {\n indexById.set(nextParts[i].id, i);\n }\n for (let i = 0; i < nextParts.length; i++) {\n const clip = nextParts[i].clip;\n if (!clip) continue;\n const maskIndices = clip.masks\n .map((maskId) => indexById.get(maskId))\n .filter((mi): mi is number => mi !== undefined);\n if (maskIndices.length > 0) nextClipGroups.set(i, maskIndices);\n }\n if (nextClipGroups.size > 0 && !this.stencilAvailable) {\n console.error(\n \"Iki: model uses clip masks but this WebGL2 context has no stencil buffer; rendering unclipped\",\n );\n }\n\n // Atomic adoption: release the previous model's part-mesh buffers and textures,\n // then adopt the new model's state in a single block so the render loop\n // never sees a half-adopted model.\n deletePartMeshBuffers(gl, this.partMeshes);\n for (const texture of this.textures) {\n if (texture) gl.deleteTexture(texture);\n }\n\n this.model = model;\n this.params = new ParameterStore(model.parameters);\n this.parts = nextParts;\n this.partMeshes = nextPartMeshes;\n this.partClipGroups = nextClipGroups;\n this.textures = uploaded;\n\n return {\n failedTextures: uploaded.flatMap((t, i) => (t === null ? [i] : [])),\n superseded: false,\n };\n }\n\n /**\n * Start the render loop. Safe to call more than once, and a no-op after\n * {@link destroy} — the program and buffers the loop draws with are gone, so\n * restarting would only spray GL errors.\n */\n start(): void {\n if (this.rafId !== undefined || this.destroyed) return;\n const loop = (): void => {\n this.renderFrame();\n this.rafId = requestAnimationFrame(loop);\n };\n this.rafId = requestAnimationFrame(loop);\n }\n\n stop(): void {\n if (this.rafId === undefined) return;\n cancelAnimationFrame(this.rafId);\n this.rafId = undefined;\n }\n\n /**\n * Set a parameter value (clamped to its range). Unknown ids and non-finite\n * values are ignored — see {@link ParameterStore.set}.\n */\n setParameter(id: string, value: number): void {\n this.params.set(id, value);\n }\n\n /**\n * Current value of a parameter, or 0 for an unknown id.\n *\n * Hosts need this to avoid shadowing the engine's state: the motion drivers\n * read the live pose to compute the next one, and without a read accessor\n * every host has to keep its own mirror of what it last wrote — and keep that\n * mirror's clamping in step with {@link ParameterStore} by hand.\n */\n getParameter(id: string): number {\n return this.params.get(id);\n }\n\n /**\n * The model's parameter descriptors, for building UI or host wiring.\n *\n * Empty until the first {@link load} resolves. Reaching it through an\n * un-awaited `load()` is the one mistake in this class that produces no\n * error and no motion: the caller gets `[]`, concludes the model has no\n * parameters, and drives nothing — so that case is reported instead of\n * being indistinguishable from a model that really declares none. A\n * reload is deliberately NOT reported: those parameters are stale rather\n * than absent, and warning there would fire on legitimate concurrent reads.\n */\n getParameters(): IkiParameter[] {\n if (\n this.loadPending &&\n this.model === undefined &&\n !this.warnedLoadUnfinished\n ) {\n this.warnedLoadUnfinished = true;\n console.error(\n \"Iki: getParameters() ran before load() finished, so it returned an empty list — await load() before reading parameters.\",\n );\n }\n return this.params.list();\n }\n\n destroy(): void {\n this.stop();\n // Invalidate any in-flight load so it bails before touching GL state.\n this.destroyed = true;\n ++this.loadGeneration;\n const { gl } = this;\n for (const texture of this.textures) {\n if (texture) gl.deleteTexture(texture);\n }\n this.textures = [];\n deletePartMeshBuffers(gl, this.partMeshes);\n this.partMeshes = new Map();\n gl.deleteBuffer(this.quad);\n gl.deleteProgram(this.program);\n }\n\n private renderFrame(): void {\n const { gl, canvas } = this;\n\n const dpr = window.devicePixelRatio || 1;\n const width = Math.max(1, Math.floor(canvas.clientWidth * dpr));\n const height = Math.max(1, Math.floor(canvas.clientHeight * dpr));\n if (canvas.width !== width || canvas.height !== height) {\n canvas.width = width;\n canvas.height = height;\n }\n\n gl.viewport(0, 0, width, height);\n gl.clearColor(0, 0, 0, 0);\n gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);\n\n if (!this.model) return;\n\n // Fit the logical model canvas into the drawing buffer, preserving aspect,\n // and convert model units to clip space.\n const { width: modelW, height: modelH } = this.model.canvas;\n const fit = Math.min(width / modelW, height / modelH);\n const clipX = (fit * 2) / width;\n const clipY = (fit * 2) / height;\n\n gl.useProgram(this.program);\n\n const deformerWorlds =\n this.model.deformers && this.model.deformers.length > 0\n ? resolveDeformerWorlds(this.model.deformers, this.params)\n : undefined;\n\n // Resolve each warp deformer's deformed control grid for this frame (parent\n // matrix affine + grid keyforms). Warp-child mesh parts sample these grids\n // instead of riding the affine dWorld·TRS chain.\n const warpGrids = this.model.deformers?.some((d) => d.kind === \"warp\")\n ? resolveWarpGrids(\n this.model.deformers,\n this.params,\n deformerWorlds ?? new Map(),\n )\n : undefined;\n\n // u_alphaCutoff defaults to 0 (no fragment is discarded) for normal parts;\n // the mask-write pass raises it temporarily (see drawClipped).\n gl.uniform1f(this.uAlphaCutoff, 0);\n for (let index = 0; index < this.parts.length; index++) {\n const maskIndices = this.partClipGroups.get(index);\n if (maskIndices && this.stencilAvailable) {\n this.drawClipped(\n index,\n maskIndices,\n clipX,\n clipY,\n deformerWorlds,\n warpGrids,\n );\n } else {\n // Unclipped (or stencil unavailable — load() already reported it).\n this.drawPart(index, clipX, clipY, deformerWorlds, warpGrids);\n }\n }\n }\n\n /**\n * Draw a clipped part: stencil the union of its masks' alpha coverage, then\n * draw the part only where the stencil was written. The mask parts also draw\n * normally in their own `order` slot — this is an EXTRA, color-free pass over\n * the same per-frame deformed geometry. All stencil/colorMask state the pass\n * touches is restored before returning so later parts are unaffected.\n */\n private drawClipped(\n index: number,\n maskIndices: number[],\n clipX: number,\n clipY: number,\n deformerWorlds: ReturnType<typeof resolveDeformerWorlds> | undefined,\n warpGrids: ReturnType<typeof resolveWarpGrids> | undefined,\n ): void {\n const { gl } = this;\n\n // 1. Write coverage into the stencil (REPLACE 1), no color. Each mask is\n // drawn with its per-frame deformed geometry; u_alphaCutoff discards the\n // transparent fringe so only opaque coverage marks the stencil. Multiple\n // masks union naturally (all write 1).\n gl.clear(gl.STENCIL_BUFFER_BIT);\n gl.enable(gl.STENCIL_TEST);\n gl.colorMask(false, false, false, false);\n gl.stencilMask(0xff);\n gl.stencilFunc(gl.ALWAYS, 1, 0xff);\n gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);\n gl.uniform1f(this.uAlphaCutoff, MASK_ALPHA_CUTOFF);\n for (const maskIndex of maskIndices) {\n this.drawPart(maskIndex, clipX, clipY, deformerWorlds, warpGrids);\n }\n\n // 2. Draw the consumer only where stencil == 1; restore color writes first.\n gl.uniform1f(this.uAlphaCutoff, 0);\n gl.colorMask(true, true, true, true);\n gl.stencilMask(0x00);\n gl.stencilFunc(gl.EQUAL, 1, 0xff);\n gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);\n this.drawPart(index, clipX, clipY, deformerWorlds, warpGrids);\n\n // 3. Restore every stencil state this pass changed (colorMask + u_alphaCutoff\n // already restored above) so the next unmasked part renders normally.\n gl.disable(gl.STENCIL_TEST);\n gl.stencilMask(0xff);\n gl.stencilFunc(gl.ALWAYS, 0, 0xff);\n gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);\n }\n\n /**\n * Draw a single part with its full per-part material + geometry state. Shared\n * by the normal pass, the stencil mask-write pass, and the masked consumer\n * draw — so every path prepares the SAME complete uniform/texture/VBO state\n * (the caller only sets stencil/colorMask/u_alphaCutoff around it).\n */\n private drawPart(\n index: number,\n clipX: number,\n clipY: number,\n deformerWorlds: ReturnType<typeof resolveDeformerWorlds> | undefined,\n warpGrids: ReturnType<typeof resolveWarpGrids> | undefined,\n ): void {\n const { gl } = this;\n const part = this.parts[index];\n const texture = part.texture\n ? this.textures[part.texture.index]\n : undefined;\n // A textured part whose slot is null (skipped/failed) draws nothing.\n if (part.texture && !texture) return;\n\n const t = this.evaluate(part);\n // Warp-child mesh parts bypass the affine dWorld·TRS chain entirely: their\n // vertices are computed by the per-frame grid pipeline below (which bakes\n // part TRS into model-space positions), so u_matrix carries ONLY clip-scale.\n const warpChild = this.partMeshes.get(index)?.warpDeformer;\n // clip <- project <- [deformer?] <- translate <- rotate <- scale(size)\n let m: ReturnType<typeof multiply>;\n if (warpChild) {\n m = scale(clipX, clipY);\n } else if (part.deformer !== undefined) {\n const dWorld = deformerWorlds!.get(part.deformer);\n if (!dWorld) {\n throw new Error(\n `part \"${part.id}\" references unknown deformer \"${part.deformer}\"`,\n );\n }\n m = multiply(multiply(scale(clipX, clipY), dWorld), translate(t.x, t.y));\n m = multiply(m, rotate(t.rotation));\n m = multiply(m, scale(part.width * t.scaleX, part.height * t.scaleY));\n } else {\n m = multiply(scale(clipX, clipY), translate(t.x, t.y));\n m = multiply(m, rotate(t.rotation));\n m = multiply(m, scale(part.width * t.scaleX, part.height * t.scaleY));\n }\n\n const [r, g, b, a] = part.color;\n gl.uniformMatrix3fv(this.uMatrix, false, toMat3(m));\n gl.uniform4f(this.uColor, r, g, b, a * t.opacity);\n\n if (part.texture && texture) {\n const { uv } = part.texture;\n gl.uniform1i(this.uUseTexture, 1);\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.uniform1i(this.uTex, 0);\n gl.uniform2f(this.uUvOffset, uv.x, uv.y);\n gl.uniform2f(this.uUvScale, uv.width, uv.height);\n } else {\n gl.uniform1i(this.uUseTexture, 0);\n }\n\n if (part.mesh) {\n // --- Mesh draw path ---\n const pm = this.partMeshes.get(index);\n if (!pm) {\n // Impossible after the fatal-allocation rule in load(); throwing rather\n // than skipping matches the existing unknown-deformer-parent throw in\n // deform.ts and ensures engine bugs are never silently hidden.\n throw new Error(`Iki: mesh buffers missing for part \"${part.id}\"`);\n }\n\n gl.uniform1i(this.uUseMeshUv, 1);\n\n // For warped meshes, compute morphed positions and upload to the\n // DYNAMIC_DRAW VBO. Warp-less meshes skip this — their VBO already\n // holds `rest` from load().\n if (pm.warpDeformer) {\n // --- Warp-deformer (group warp) child pipeline ---\n // Coordinate invariant (top bug risk): BIND against the RAW rest grid\n // (warpDeformer.grid — no keyform offsets, no parent affine), SAMPLE\n // against the RESOLVED grid (resolveWarpGrids output: offsets added,\n // THEN parent affine). Never bind against a resolved/deformed grid.\n const warpDef = pm.warpDeformer;\n const grid = warpGrids!.get(warpDef.id)!;\n // 1. part-local mesh warps into the local scratch (no-op if none).\n applyWarps(pm.rest, pm.warps, this.params, pm.local!);\n // 2. live part TRS (eye/mouth open etc.), baked into the vertices.\n const trs = evaluateTransform(\n part.transform,\n part.bindings,\n this.params,\n );\n const partAffine = multiply(\n multiply(translate(trs.x, trs.y), rotate(trs.rotation)),\n scale(part.width * trs.scaleX, part.height * trs.scaleY),\n );\n // 2b+3+4. transform each local vertex by partAffine, rebind to the RAW\n // rest grid, and sample the RESOLVED (deformed) grid.\n applyWarpToChild(\n pm.local!,\n partAffine,\n warpDef.grid,\n grid,\n pm.scratch!,\n );\n gl.bindBuffer(gl.ARRAY_BUFFER, pm.position);\n gl.bufferSubData(gl.ARRAY_BUFFER, 0, pm.scratch!);\n } else if (pm.warps && pm.warps.length > 0) {\n // scratch is always allocated when warps is non-empty (see load())\n applyWarps(pm.rest, pm.warps, this.params, pm.scratch!);\n gl.bindBuffer(gl.ARRAY_BUFFER, pm.position);\n gl.bufferSubData(gl.ARRAY_BUFFER, 0, pm.scratch!);\n }\n\n // Position VBO (DYNAMIC_DRAW — morphed for warped parts, rest otherwise).\n gl.bindBuffer(gl.ARRAY_BUFFER, pm.position);\n gl.enableVertexAttribArray(this.aPos);\n gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0);\n\n // UV VBO (STATIC_DRAW — mesh UVs are passed straight through, no flip).\n gl.bindBuffer(gl.ARRAY_BUFFER, pm.uv);\n gl.enableVertexAttribArray(this.aUv);\n gl.vertexAttribPointer(this.aUv, 2, gl.FLOAT, false, 0, 0);\n\n gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, pm.index);\n gl.drawElements(gl.TRIANGLES, pm.indexCount, gl.UNSIGNED_SHORT, 0);\n } else {\n // --- Implicit-quad draw path ---\n // Disable a_uv so no stale mesh UV buffer from a preceding mesh part is\n // sourced. The quad shader branch derives UV from a_pos, not a_uv.\n gl.uniform1i(this.uUseMeshUv, 0);\n gl.disableVertexAttribArray(this.aUv);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, this.quad);\n gl.enableVertexAttribArray(this.aPos);\n gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0);\n\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n }\n }\n\n /** Resolve a part's effective transform from its base plus active bindings. */\n private evaluate(part: IkiPart): ReturnType<typeof evaluateTransform> {\n return evaluateTransform(part.transform, part.bindings, this.params);\n }\n}\n\n// --- WebGL plumbing ---------------------------------------------------------\nconst VERTEX_SHADER = `#version 300 es\nin vec2 a_pos;\nin vec2 a_uv;\nuniform mat3 u_matrix;\nuniform vec2 u_uvOffset;\nuniform vec2 u_uvScale;\nuniform bool u_useMeshUv;\nout vec2 v_uv;\nvoid main() {\n if (u_useMeshUv) {\n // Mesh path: UVs are already top-left atlas-space; pass straight through,\n // no flip (the only flip in the pipeline lives in the quad branch below).\n v_uv = a_uv;\n } else {\n // Quad path: a_pos corners are +/-0.5; lift to 0..1 (y-up), then map into\n // the atlas sub-rect with a single V flip so the result is top-left UVs.\n vec2 uvLocal = a_pos + 0.5;\n v_uv = vec2(\n u_uvOffset.x + uvLocal.x * u_uvScale.x,\n u_uvOffset.y + (1.0 - uvLocal.y) * u_uvScale.y\n );\n }\n vec3 p = u_matrix * vec3(a_pos, 1.0);\n gl_Position = vec4(p.xy, 0.0, 1.0);\n}`;\n\nconst FRAGMENT_SHADER = `#version 300 es\nprecision mediump float;\nuniform vec4 u_color;\nuniform bool u_useTexture;\nuniform sampler2D u_tex;\nuniform float u_alphaCutoff;\nin vec2 v_uv;\nout vec4 outColor;\nvoid main() {\n vec4 base = u_useTexture ? texture(u_tex, v_uv) : vec4(1.0);\n vec4 tinted = base * u_color;\n // 0 for normal draws (no-op); raised during the stencil mask-write pass so\n // only opaque mask coverage marks the stencil (the transparent fringe is cut).\n if (tinted.a < u_alphaCutoff) discard;\n // Premultiply: the blend function and the canvas compositing contract\n // (premultipliedAlpha: true) both expect rgb already scaled by alpha.\n outColor = vec4(tinted.rgb * tinted.a, tinted.a);\n}`;\n\n/**\n * Decode a texture source into an ImageBitmap, or `null` for an unsupported\n * (non-`data:`) source. v1 fetches `data:` URIs only — never arbitrary URLs.\n */\nasync function decodeTexture(source: string): Promise<ImageBitmap | null> {\n if (!source.startsWith(\"data:\")) {\n console.warn(\n \"Iki: external texture sources are unsupported in v1; skipping\",\n source.slice(0, 32),\n );\n return null;\n }\n const blob = await (await fetch(source)).blob();\n return createImageBitmap(blob, {\n imageOrientation: \"none\",\n premultiplyAlpha: \"none\",\n });\n}\n\n/** Create the shared unit-quad position VBO (centered, triangle-strip). */\nfunction createUnitQuad(gl: WebGL2RenderingContext): WebGLBuffer {\n const buffer = gl.createBuffer();\n if (!buffer) throw new Error(\"failed to allocate quad buffer\");\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n // Unit square centered on the origin, as a triangle strip.\n gl.bufferData(\n gl.ARRAY_BUFFER,\n new Float32Array([-0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5]),\n gl.STATIC_DRAW,\n );\n // Attribute pointers are set explicitly in renderFrame per draw path;\n // createUnitQuad only owns buffer allocation and data upload.\n return buffer;\n}\n\n/**\n * Clear every latched GL error so the next `getError()` reflects only the call\n * that follows this one. A lost context reports CONTEXT_LOST_WEBGL once and\n * NO_ERROR after, so this terminates.\n */\nfunction drainGlErrors(gl: WebGL2RenderingContext): void {\n // Bounded: the spec guarantees this terminates (a lost context reports\n // CONTEXT_LOST_WEBGL once, then NO_ERROR), but this is the one file with no\n // unit coverage, and an unbounded driver loop would hang the tab with no\n // stack against a wedged or mocked context.\n for (let i = 0; i < 32 && gl.getError() !== gl.NO_ERROR; i++) {\n /* discard */\n }\n}\n\n/** Delete all position/uv/index buffers stored in a PartMesh map. */\nfunction deletePartMeshBuffers(\n gl: WebGL2RenderingContext,\n meshes: Map<number, PartMesh>,\n): void {\n for (const pm of meshes.values()) {\n gl.deleteBuffer(pm.position);\n gl.deleteBuffer(pm.uv);\n gl.deleteBuffer(pm.index);\n }\n}\n\n/** Delete all non-null textures from an uploaded texture array. */\nfunction deleteUploadedTextures(\n gl: WebGL2RenderingContext,\n textures: (WebGLTexture | null)[],\n): void {\n for (const texture of textures) {\n if (texture) gl.deleteTexture(texture);\n }\n}\n\nfunction createProgram(\n gl: WebGL2RenderingContext,\n vertexSrc: string,\n fragmentSrc: string,\n): WebGLProgram {\n const program = gl.createProgram();\n if (!program) throw new Error(\"failed to allocate WebGL program\");\n gl.attachShader(program, compileShader(gl, gl.VERTEX_SHADER, vertexSrc));\n gl.attachShader(program, compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc));\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(program);\n gl.deleteProgram(program);\n throw new Error(`program link failed: ${log}`);\n }\n return program;\n}\n\nfunction compileShader(\n gl: WebGL2RenderingContext,\n type: number,\n source: string,\n): WebGLShader {\n const shader = gl.createShader(type);\n if (!shader) throw new Error(\"failed to allocate shader\");\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader);\n gl.deleteShader(shader);\n throw new Error(`shader compile failed: ${log}`);\n }\n return shader;\n}\n\nfunction getUniform(\n gl: WebGL2RenderingContext,\n program: WebGLProgram,\n name: string,\n): WebGLUniformLocation {\n const loc = gl.getUniformLocation(program, name);\n if (!loc) throw new Error(`uniform not found: ${name}`);\n return loc;\n}\n","import { StandardParameter } from \"@ikijs/format\";\nimport { MAX_DT_MS } from \"./frame-clock\";\nimport { clamp } from \"./math\";\n\n// --- Module-internal timing/easing constants -----------------------------------\n// These are intentionally private; tests assert observable behavior, not config.\n\nconst BLINK_INTERVAL_MIN_MS = 1500;\nconst BLINK_INTERVAL_MAX_MS = 6000;\nconst BLINK_DURATION_MS = 120; // full close+open cycle\n\nconst BREATH_PERIOD_MS = 3500;\n\nconst GAZE_RADIUS = 0.3;\nconst GAZE_RETARGET_MIN_MS = 1200;\nconst GAZE_RETARGET_MAX_MS = 3000;\n// Fraction of the gap to close each millisecond of clamped dt.\n// Chosen so a typical 16ms frame moves ~1.5 % of remaining distance.\nconst GAZE_EASE_RATE = 0.001;\n\n// Head sway: sums of slow sines with near-coprime periods, so the drift never\n// visibly repeats. Degrees — small against the ±30° standard AngleX/Y range,\n// but enough continuous head motion to keep hair physics from sleeping.\nconst SWAY_X_AMP_A_DEG = 2.2;\nconst SWAY_X_PERIOD_A_MS = 6100;\nconst SWAY_X_AMP_B_DEG = 1.3;\nconst SWAY_X_PERIOD_B_MS = 9700;\nconst SWAY_Y_AMP_DEG = 1.6;\nconst SWAY_Y_PERIOD_MS = 7300;\n\n// --- Small pure helpers -------------------------------------------------------\n\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\n/** Blink envelope: 1 at phase 0 and 1, 0 at the midpoint (triangle dip). */\nfunction blinkEnvelope(phase: number): number {\n // phase in [0,1]; map to a symmetric triangle: 0→1, 0.5→0, 1→1\n return 2 * Math.abs(phase - 0.5);\n}\n\n/** Pick a random point within a disk of `radius` using the injected rng. */\nfunction randomInDisk(rng: () => number, radius: number): [number, number] {\n // Polar sampling: bounded, no rejection loop, safe with any [0,1) rng.\n // r = sqrt(u) gives uniform area distribution; θ spans the full circle.\n const r = Math.sqrt(rng()) * radius;\n const theta = rng() * 2 * Math.PI;\n return [r * Math.cos(theta), r * Math.sin(theta)];\n}\n\n// --- Public API ---------------------------------------------------------------\n\nexport interface IdleMotionOptions {\n /** Inject a deterministic rng for testing. Defaults to Math.random. */\n rng?: () => number;\n}\n\n/**\n * Pure-logic idle-animation driver. Animates the seven \"life\" parameters\n * (eyes, breath, gaze, head sway) on an internal clock so tab-backgrounding\n * or irregular frame delivery can't produce teleports or snap-close blinks.\n *\n * Usage:\n * const idle = new IdleMotion(player.setParameter.bind(player));\n * // inside your rAF loop:\n * idle.update(performance.now());\n *\n * The host is responsible for scheduling; this class has no timers or rAF.\n */\nexport class IdleMotion {\n private readonly sink: (id: string, value: number) => void;\n private readonly rng: () => number;\n\n // Internal clock: advances by clamped dt, NOT by raw wall-clock jumps.\n // This is the only clock blink/breath/gaze scheduling reads.\n private clockMs = 0;\n private prevNowMs: number | undefined = undefined;\n\n // Blink state\n private nextBlinkAtMs: number;\n private blinkStartMs = -1; // -1 means not currently blinking\n\n // Gaze state\n private gazeCurrentX = 0;\n private gazeCurrentY = 0;\n private gazeTargetX = 0;\n private gazeTargetY = 0;\n private nextGazeRetargetMs: number;\n\n constructor(\n sink: (id: string, value: number) => void,\n options?: IdleMotionOptions,\n ) {\n this.sink = sink;\n // Math.random is the single allowed default reference; all other uses go\n // through this.rng so tests can inject a deterministic substitute.\n this.rng = options?.rng ?? Math.random;\n\n // Schedule the first blink and gaze retarget relative to clock zero.\n this.nextBlinkAtMs = lerp(\n BLINK_INTERVAL_MIN_MS,\n BLINK_INTERVAL_MAX_MS,\n this.rng(),\n );\n this.nextGazeRetargetMs = lerp(\n GAZE_RETARGET_MIN_MS,\n GAZE_RETARGET_MAX_MS,\n this.rng(),\n );\n }\n\n /**\n * Advance the idle animation to the given wall-clock timestamp (milliseconds).\n *\n * On the first call: record prevNowMs, emit the resting pose, and return —\n * no animation advance happens so there is no jump from time 0.\n *\n * On subsequent calls: compute dt = clamp(nowMs - prevNowMs, 0, MAX_DT_MS)\n * and advance the internal clock by dt. A non-monotonic nowMs produces a\n * negative raw delta that the clamp floors to 0 — no rewind.\n */\n update(nowMs: number): void {\n // Host-supplied timestamp, same boundary rule as FixedStepClock.advance and\n // ParameterStore.set: drop the frame, touch nothing. `clockMs += NaN` would\n // stick, and every blink/gaze schedule reads clockMs — the face would go\n // still permanently. A host driving from media time hands you NaN through an\n // ordinary path (`duration * pct` before metadata loads).\n if (!Number.isFinite(nowMs)) return;\n if (this.prevNowMs === undefined) {\n this.prevNowMs = nowMs;\n // Emit resting pose so the host mirror stays in sync from frame 1.\n this.emitRestingPose();\n return;\n }\n\n // Clamp dt so a backgrounded tab or long GC pause advances the internal\n // clock by at most MAX_DT_MS — blink can't snap shut, gaze can't teleport.\n const rawDt = nowMs - this.prevNowMs;\n const dt = clamp(rawDt, 0, MAX_DT_MS);\n this.prevNowMs = nowMs;\n this.clockMs += dt;\n\n // Both eyes always carry the identical value (unified blink).\n const eyeVal = this.advanceBlink();\n this.sink(StandardParameter.EyeOpenLeft, eyeVal);\n this.sink(StandardParameter.EyeOpenRight, eyeVal);\n\n const breath = this.advanceBreath();\n this.advanceGaze(dt);\n\n this.sink(StandardParameter.Breath, breath);\n this.sink(StandardParameter.EyeballX, this.gazeCurrentX);\n this.sink(StandardParameter.EyeballY, this.gazeCurrentY);\n\n // Head sway keeps the character (and any hair physics reading AngleX/Y)\n // from freezing solid between blinks. Models without these parameters\n // ignore the writes (ParameterStore drops unknown ids).\n this.sink(StandardParameter.AngleX, this.swayX());\n this.sink(StandardParameter.AngleY, this.swayY());\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private emitRestingPose(): void {\n this.sink(StandardParameter.EyeOpenLeft, 1);\n this.sink(StandardParameter.EyeOpenRight, 1);\n // Breath at phase 0: sin(0) = 0 → 0.5 + 0.5*0 = 0.5\n this.sink(StandardParameter.Breath, 0.5);\n this.sink(StandardParameter.EyeballX, 0);\n this.sink(StandardParameter.EyeballY, 0);\n // Sway sines are all 0 at clock 0 — the head starts centered.\n this.sink(StandardParameter.AngleX, 0);\n this.sink(StandardParameter.AngleY, 0);\n }\n\n /** Returns the current eye-open value (0..1) and advances blink state. */\n private advanceBlink(): number {\n const { clockMs } = this;\n\n // If a blink is in progress, evaluate the envelope.\n if (this.blinkStartMs >= 0) {\n const phase = (clockMs - this.blinkStartMs) / BLINK_DURATION_MS;\n if (phase >= 1) {\n // Blink finished — schedule the next one on the internal clock.\n this.blinkStartMs = -1;\n this.nextBlinkAtMs =\n clockMs +\n lerp(BLINK_INTERVAL_MIN_MS, BLINK_INTERVAL_MAX_MS, this.rng());\n return 1;\n }\n return blinkEnvelope(phase);\n }\n\n // Check if it is time to start a new blink.\n if (clockMs >= this.nextBlinkAtMs) {\n this.blinkStartMs = clockMs;\n // Return the first envelope sample (exactly 1 at phase 0).\n return blinkEnvelope(0);\n }\n\n // Between blinks: eyes fully open.\n return 1;\n }\n\n private advanceBreath(): number {\n return (\n 0.5 + 0.5 * Math.sin((2 * Math.PI * this.clockMs) / BREATH_PERIOD_MS)\n );\n }\n\n /** Horizontal head sway in degrees, pure function of the internal clock. */\n private swayX(): number {\n const t = 2 * Math.PI * this.clockMs;\n return (\n SWAY_X_AMP_A_DEG * Math.sin(t / SWAY_X_PERIOD_A_MS) +\n SWAY_X_AMP_B_DEG * Math.sin(t / SWAY_X_PERIOD_B_MS)\n );\n }\n\n /** Vertical head sway in degrees, pure function of the internal clock. */\n private swayY(): number {\n return (\n SWAY_Y_AMP_DEG * Math.sin((2 * Math.PI * this.clockMs) / SWAY_Y_PERIOD_MS)\n );\n }\n\n /** Ease gaze current toward target; pick a new target on the internal clock. */\n private advanceGaze(dt: number): void {\n // Retarget check reads clockMs so a clamped pause can't skip retargets.\n if (this.clockMs >= this.nextGazeRetargetMs) {\n const [tx, ty] = randomInDisk(this.rng, GAZE_RADIUS);\n this.gazeTargetX = tx;\n this.gazeTargetY = ty;\n this.nextGazeRetargetMs =\n this.clockMs +\n lerp(GAZE_RETARGET_MIN_MS, GAZE_RETARGET_MAX_MS, this.rng());\n }\n\n // Exponential ease toward target. The ease factor is derived from the\n // CLAMPED dt so a paused tab moves at most one normal step, never a teleport.\n const factor = 1 - Math.pow(1 - GAZE_EASE_RATE, dt);\n this.gazeCurrentX = lerp(this.gazeCurrentX, this.gazeTargetX, factor);\n this.gazeCurrentY = lerp(this.gazeCurrentY, this.gazeTargetY, factor);\n }\n}\n","import { clamp } from \"./math\";\n\n/** Per-frame dt ceiling: a backgrounded tab or a long GC pause must not snap state. */\nexport const MAX_DT_MS = 100;\n/** Fixed integration sub-step, in SECONDS. */\nexport const FIXED_DT_S = 1 / 60;\n/** Catch-up cap per frame (spiral-of-death guard). */\nexport const MAX_SUBSTEPS = 6;\n\n/**\n * Fixed-timestep accumulator shared by the physics drivers, so a stability fix\n * lands in one place instead of one of two near-identical copies.\n *\n * A driver calls {@link advance} once per frame and runs its integration body\n * that many times. Leftover time carries to the next frame, which is what keeps\n * the simulation frame-rate independent; the dt clamp and the sub-step cap keep\n * a hitch from either snapping the rig or spiralling into catch-up work.\n */\nexport class FixedStepClock {\n private prevNowMs: number | undefined = undefined;\n private accumulatorS = 0;\n\n /**\n * True until the first {@link advance}. Drivers seed their rest pose on that\n * frame and integrate nothing, so a model loaded mid-motion does not kick.\n */\n get isSeedFrame(): boolean {\n return this.prevNowMs === undefined;\n }\n\n /**\n * Fold `nowMs` into the accumulator and return how many {@link FIXED_DT_S}\n * sub-steps to run this frame. On the seed frame it only records the\n * timestamp and returns 0. A non-monotonic `nowMs` floors to 0 — no rewind.\n */\n advance(nowMs: number): number {\n // Host-supplied timestamp, so the same rule as ParameterStore.set: reject\n // non-finite input at the boundary. `clamp(NaN, ...)` is NaN, which would\n // make the accumulator NaN and every `NaN >= FIXED_DT_S` false FOREVER —\n // the rig would freeze silently with no way back. Pre-existing; hardened\n // here so both host entry points behave the same way.\n if (!Number.isFinite(nowMs)) return 0;\n if (this.prevNowMs === undefined) {\n this.prevNowMs = nowMs;\n return 0;\n }\n const dtMs = clamp(nowMs - this.prevNowMs, 0, MAX_DT_MS);\n this.prevNowMs = nowMs;\n this.accumulatorS += dtMs / 1000; // boundary ms->s conversion\n\n let steps = 0;\n while (this.accumulatorS >= FIXED_DT_S && steps < MAX_SUBSTEPS) {\n this.accumulatorS -= FIXED_DT_S;\n steps++;\n }\n return steps;\n }\n}\n","import type { IkiParameter, IkiPhysics } from \"@ikijs/format\";\nimport { FIXED_DT_S, FixedStepClock } from \"./frame-clock\";\nimport { clamp } from \"./math\";\n\n// --- Small pure helpers ------------------------------------------------------\n\n/**\n * Map a parameter value to [-1, 1] around its engine-effective default. Portable\n * across param ranges: the wider side (rest→max vs min→rest) sets the unit, so\n * ±1 lands at the farther extreme. Returns 0 for a zero-width range.\n *\n * The rest center is `clamp(default, min, max)` to match ParameterStore, which\n * clamps an out-of-range default — so the spring rests where the model actually\n * renders, not at a raw out-of-range default.\n */\nfunction signedNormalized(\n value: number,\n param: { min: number; max: number; default: number },\n): number {\n const rest = clamp(param.default, param.min, param.max);\n const den = Math.max(Math.abs(param.max - rest), Math.abs(rest - param.min));\n if (den === 0) return 0;\n return clamp((value - rest) / den, -1, 1);\n}\n\n// --- Public API --------------------------------------------------------------\n\ninterface RigState {\n x: number; // spring position (lagging, normalized-ish units)\n v: number; // spring velocity\n}\n\n/**\n * Host-agnostic 1D spring-mass-damper secondary-motion driver — the physics\n * peer of {@link IdleMotion}. For each rig it reads the input parameter,\n * signed-normalizes it around the input's default × `weight` to form a spring\n * target, integrates a lagging spring position with semi-implicit (symplectic)\n * Euler on a fixed 1/60s sub-step accumulator, and writes\n * `outputDefault + x * scale` onto the output parameter — so the output lags\n * and overshoots the input (hair/accessory sway).\n *\n * Usage:\n * const physics = new PhysicsMotion(\n * model.physics ?? [],\n * model.parameters,\n * (id) => currentValue(id),\n * player.setParameter.bind(player),\n * );\n * // inside your rAF loop, right AFTER idle.update(now):\n * physics.update(performance.now());\n *\n * The host schedules updates; this class has no timers, rAF, DOM, or Date.now.\n * Writes go through the sink, exactly like IdleMotion; the player renders the\n * updated params on its own render loop (drivers and rendering are decoupled).\n */\nexport class PhysicsMotion {\n private readonly rigs: readonly IkiPhysics[];\n private readonly read: (id: string) => number;\n private readonly sink: (id: string, value: number) => void;\n private readonly params: Map<string, IkiParameter>;\n\n // Integrator state, owned here — never stored in a ParameterStore.\n private readonly state: RigState[];\n private readonly clock = new FixedStepClock();\n\n constructor(\n rigs: IkiPhysics[],\n params: IkiParameter[],\n read: (id: string) => number,\n sink: (id: string, value: number) => void,\n ) {\n this.rigs = rigs;\n this.read = read;\n this.sink = sink;\n this.params = new Map(params.map((p) => [p.id, p]));\n this.state = rigs.map(() => ({ x: 0, v: 0 }));\n }\n\n /**\n * Advance every rig to the given wall-clock timestamp (milliseconds).\n *\n * First call: seed each spring to rest AT its current target (so a model\n * loaded with a nonzero input does not kick), emit the resting output, and\n * return without integrating — mirroring IdleMotion's first-frame behavior.\n *\n * Subsequent calls: {@link FixedStepClock} folds the clamped frame delta into\n * its accumulator and returns how many {@link FIXED_DT_S} sub-steps are due;\n * the spring advances that many semi-implicit Euler steps, then each rig emits\n * its output once. The clock's dt clamp and sub-step cap plus the symplectic\n * integrator are what keep it stable across hitches.\n */\n update(nowMs: number): void {\n if (this.clock.isSeedFrame) {\n this.clock.advance(nowMs);\n for (let i = 0; i < this.rigs.length; i++) {\n const st = this.state[i];\n st.x = this.targetFor(this.rigs[i]);\n st.v = 0;\n this.emit(this.rigs[i], st);\n }\n return;\n }\n\n const steps = this.clock.advance(nowMs);\n\n // update() is synchronous, so the input cannot change mid-loop — read each\n // rig's target ONCE per frame and reuse it across the sub-steps.\n const targets = this.rigs.map((rig) => this.targetFor(rig));\n\n for (let s = 0; s < steps; s++) {\n for (let i = 0; i < this.rigs.length; i++) {\n this.step(this.rigs[i], this.state[i], targets[i]);\n }\n }\n\n // Always emit (even when zero sub-steps ran) so the sink stays in sync.\n for (let i = 0; i < this.rigs.length; i++) {\n const st = this.state[i];\n // The fixed 1/60s sub-step can diverge for an extreme-but-parse-valid rig\n // (tiny mass / huge stiffness push ω·dt past the explicit-integrator\n // stability limit). If state goes non-finite, snap back to rest at the\n // current target: the store DROPS a non-finite write, so an un-reset rig\n // would emit NaN every frame and silently freeze the part at its last\n // good pose instead of visibly diverging.\n if (!Number.isFinite(st.x) || !Number.isFinite(st.v)) {\n st.x = Number.isFinite(targets[i]) ? targets[i] : 0;\n st.v = 0;\n }\n this.emit(this.rigs[i], st);\n }\n }\n\n /** Spring target = signed-normalized input value × weight. */\n private targetFor(rig: IkiPhysics): number {\n const param = this.params.get(rig.input.parameter);\n const value = this.read(rig.input.parameter);\n const norm = param ? signedNormalized(value, param) : 0;\n return norm * rig.input.weight;\n }\n\n /** One semi-implicit (symplectic) Euler sub-step of FIXED_DT_S seconds. */\n private step(rig: IkiPhysics, st: RigState, target: number): void {\n const accel =\n (rig.stiffness * (target - st.x) - rig.damping * st.v) / rig.mass;\n st.v += accel * FIXED_DT_S;\n st.x += st.v * FIXED_DT_S;\n }\n\n /** Write outputDefault + x * scale onto the output param via the sink. */\n private emit(rig: IkiPhysics, st: RigState): void {\n const outParam = this.params.get(rig.output.parameter);\n // clamp to match ParameterStore's engine-effective default (see signedNormalized).\n const outDefault = outParam\n ? clamp(outParam.default, outParam.min, outParam.max)\n : 0;\n const value = outDefault + st.x * rig.output.scale;\n // Final guard: even a finite-but-enormous `x` could overflow the product to\n // ±Infinity. Emit the rest pose rather than hand a non-finite value to the\n // sink, which would drop the write and leave the output stuck where it was.\n this.sink(\n rig.output.parameter,\n Number.isFinite(value) ? value : outDefault,\n );\n }\n}\n","import type {\n IkiDeformer,\n IkiParameter,\n IkiPhysicsChain,\n IkiPhysicsChainSegment,\n} from \"@ikijs/format\";\nimport type { Affine } from \"./affine\";\nimport { resolveDeformerWorlds } from \"./deform\";\n// The two physics drivers stay independent of EACH OTHER; the timing primitives\n// they both need live in frame-clock.ts so a stability fix lands once.\nimport { FIXED_DT_S, FixedStepClock } from \"./frame-clock\";\nimport { clamp } from \"./math\";\nimport { ParameterStore } from \"./parameter-store\";\n\nconst DEG2RAD = Math.PI / 180;\nconst RAD2DEG = 180 / Math.PI;\n\n// --- Per-chain / per-segment state -------------------------------------------\n\n/** Integrator state for one segment. angle = θ displacement in RADIANS. */\ninterface SegmentState {\n angle: number; // θ_i in radians (displacement from rest)\n angularVelocity: number; // ω_i in radians/s\n}\n\n/** All precomputed per-chain data (rest angles already in radians). */\ninterface ChainData {\n chain: IkiPhysicsChain;\n restAnglesRad: number[]; // restAngle_j in radians; 0 when omitted\n state: SegmentState[]; // preallocated, length = segments.length\n}\n\n// --- Public API --------------------------------------------------------------\n\n/**\n * Host-agnostic multi-segment angular-pendulum-chain secondary-motion driver.\n * Peer of {@link PhysicsMotion} and {@link IdleMotion}.\n *\n * Each chain anchors to a matrix deformer in the model hierarchy. The driver\n * self-computes the anchor's world rotation via `resolveDeformerWorlds` (a\n * private `ParameterStore` is filled from `read` ONCE per frame) and integrates\n * a per-segment angular pendulum with semi-implicit Euler on a fixed 1/60s\n * sub-step accumulator. Each segment's angular displacement θ (in radians\n * internally) is emitted in DEGREES on its output parameter, so `rotate = 0`\n * when the chain is at its authored rest pose.\n *\n * Usage:\n * const chains = new HairChainMotion(\n * model.physicsChains ?? [],\n * model.parameters,\n * model.deformers ?? [],\n * (id) => currentValue(id),\n * player.setParameter.bind(player),\n * );\n * // inside your rAF loop, right AFTER physics.update(now):\n * chains.update(performance.now());\n *\n * The host schedules updates; this class has no timers, rAF, DOM, or Date.now.\n */\nexport class HairChainMotion {\n private readonly chainData: ChainData[];\n private readonly params: Map<string, IkiParameter>;\n private readonly deformers: IkiDeformer[];\n private readonly store: ParameterStore;\n private readonly read: (id: string) => number;\n private readonly sink: (id: string, value: number) => void;\n\n private readonly clock = new FixedStepClock();\n\n constructor(\n chains: IkiPhysicsChain[],\n params: IkiParameter[],\n deformers: IkiDeformer[],\n read: (id: string) => number,\n sink: (id: string, value: number) => void,\n ) {\n this.params = new Map(params.map((p) => [p.id, p]));\n this.deformers = deformers;\n // Private ParameterStore reused every frame for anchor-world resolution.\n this.store = new ParameterStore(params);\n this.read = read;\n this.sink = sink;\n\n // Precompute rest angles in radians; preallocate segment state (no per-substep alloc).\n this.chainData = chains.map((chain) => ({\n chain,\n restAnglesRad: chain.segments.map((seg) =>\n seg.restAngle !== undefined ? seg.restAngle * DEG2RAD : 0,\n ),\n state: chain.segments.map(() => ({ angle: 0, angularVelocity: 0 })),\n }));\n }\n\n /**\n * Advance every chain to the given wall-clock timestamp (milliseconds).\n *\n * First call: seed every segment to θ=0/ω=0 (rest), emit the rest output\n * (outDefault + 0), and return without integrating — mirrors PhysicsMotion's\n * first-frame behavior so a model loaded in motion does not kick.\n *\n * Subsequent calls: dt = clamp(nowMs - prevNowMs, 0, MAX_DT_MS) → seconds into\n * accumulator. The per-frame world snapshot (anchor world angles) is taken ONCE\n * per update() — NOT per chain — so all chains share a consistent frame snapshot.\n * Fixed FIXED_DT_S sub-steps are run root→tip, capped at MAX_SUBSTEPS; leftover\n * time is carried to the next frame. Segments emit after substeps (even on zero\n * substeps) with a non-finite guard.\n */\n update(nowMs: number): void {\n if (this.clock.isSeedFrame) {\n // FIRST FRAME: seed rest, emit outDefault for every segment, NO integration.\n this.clock.advance(nowMs);\n for (const cd of this.chainData) {\n for (let i = 0; i < cd.chain.segments.length; i++) {\n cd.state[i].angle = 0;\n cd.state[i].angularVelocity = 0;\n this.emitSegment(cd.chain.segments[i], cd.state[i]);\n }\n }\n return;\n }\n\n const steps = this.clock.advance(nowMs);\n\n // Take the per-frame world snapshot ONCE per update() — NOT per chain.\n // Fill the private store from read, then resolve all deformer world matrices.\n for (const param of this.params.values()) {\n this.store.set(param.id, this.read(param.id));\n }\n const worldMap = resolveDeformerWorlds(this.deformers, this.store);\n\n // Read each chain's anchor world angle ONCE per frame (consistent across substeps,\n // like physics-motion.ts:122 `targets`).\n const anchorAnglesRad = this.chainData.map((cd) =>\n this.anchorWorldAngleRad(worldMap, cd.chain.anchorDeformer),\n );\n\n for (let s = 0; s < steps; s++) {\n for (let c = 0; c < this.chainData.length; c++) {\n this.stepChain(this.chainData[c], anchorAnglesRad[c]);\n }\n }\n\n // Emit always (even on zero substeps) so the sink stays in sync.\n for (const cd of this.chainData) {\n for (let i = 0; i < cd.chain.segments.length; i++) {\n const st = cd.state[i];\n // FINITENESS GUARD (mirror physics-motion.ts:141): a pathological rig\n // (tiny mass / huge stiffness) can push the fixed-substep integrator\n // past its explicit-stability limit. Snap the segment back to rest so\n // a validated model can never poison the sink with NaN/Infinity.\n if (\n !Number.isFinite(st.angle) ||\n !Number.isFinite(st.angularVelocity)\n ) {\n st.angle = 0;\n st.angularVelocity = 0;\n }\n this.emitSegment(cd.chain.segments[i], st);\n }\n }\n }\n\n /**\n * Extract world rotation (radians) from the anchor's Affine tuple.\n * Affine = [a,b,c,d,e,f]; rotation column = (a,b) → atan2(b,a).\n *\n * If the anchor id is absent from the map, THROWS an internal Error — the\n * format validator guarantees the anchor exists, so absence is an invariant\n * break (mirrors resolveDeformerWorlds' throw on an unresolved parent,\n * deform.ts:141).\n */\n private anchorWorldAngleRad(\n worldMap: Map<string, Affine>,\n anchorId: string,\n ): number {\n const world = worldMap.get(anchorId);\n if (!world) {\n throw new Error(\n `HairChainMotion: anchor deformer \"${anchorId}\" not found in resolved world map — model not validated?`,\n );\n }\n // Affine [a,b,c,d,e,f]: the first column (a,b) is the x-axis direction\n // after rotation, so atan2(b,a) gives the world rotation angle in radians.\n return Math.atan2(world[1], world[0]);\n }\n\n /**\n * One fixed sub-step of FIXED_DT_S seconds for all segments in a chain.\n *\n * Segments are integrated ROOT→TIP so each segment can read its upstream\n * neighbor's current-substep state when computing the world angle Φ_i.\n * (The chain is causal root-to-tip; reversing the order would use stale θ\n * values from the previous substep for Φ_i computation.)\n *\n * Per-segment semi-implicit (symplectic) Euler:\n * Φ_i = anchorWorldAngleRad + Σ_{j≤i}(restAngle_j + θ_j)\n * α_i = (−stiffness_i·θ_i − strength·sin(Φ_i − gravityAngle_rad) − damping_i·ω_i) / mass_i\n * ω_i += α_i · FIXED_DT_S (velocity updated FIRST = semi-implicit)\n * θ_i += ω_i · FIXED_DT_S (position updated from NEW velocity)\n *\n * The spring term is −stiffness·θ (restoring θ→0); restAngle does NOT appear\n * in the spring term, only in Φ_i for the gravity torque.\n */\n private stepChain(cd: ChainData, anchorAngleRad: number): void {\n const { chain, restAnglesRad, state } = cd;\n const gravityAngleRad = chain.gravity.angle * DEG2RAD;\n const strength = chain.gravity.strength;\n\n // Accumulate world angle root→tip as we go.\n let worldAngleAccumRad = anchorAngleRad;\n\n for (let i = 0; i < chain.segments.length; i++) {\n const seg: IkiPhysicsChainSegment = chain.segments[i];\n const st = state[i];\n\n // World angle of this segment: anchor + sum of all segments 0..i.\n worldAngleAccumRad += restAnglesRad[i] + st.angle;\n const phi = worldAngleAccumRad;\n\n const alpha =\n (-seg.stiffness * st.angle -\n strength * Math.sin(phi - gravityAngleRad) -\n seg.damping * st.angularVelocity) /\n seg.mass;\n\n // Semi-implicit Euler: update velocity first, then position with new velocity.\n st.angularVelocity += alpha * FIXED_DT_S;\n st.angle += st.angularVelocity * FIXED_DT_S;\n }\n }\n\n /** Emit outDefault + (θ_i · RAD2DEG) · scale for one segment. */\n private emitSegment(seg: IkiPhysicsChainSegment, st: SegmentState): void {\n const outParam = this.params.get(seg.output.parameter);\n const outDefault = outParam\n ? clamp(outParam.default, outParam.min, outParam.max)\n : 0;\n const value = outDefault + st.angle * RAD2DEG * seg.output.scale;\n // Final guard: even a finite-but-enormous θ could overflow to ±Infinity.\n this.sink(\n seg.output.parameter,\n Number.isFinite(value) ? value : outDefault,\n );\n }\n}\n"],"mappings":"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","names":["xRight","yBottom"]}
|
|
1
|
+
{"version":3,"sources":["../src/math.ts","../src/parameter-store.ts","../src/affine.ts","../src/deform.ts","../src/warp.ts","../src/warp-grid.ts","../src/player.ts","../src/idle-motion.ts","../src/frame-clock.ts","../src/physics-motion.ts","../src/hair-chain-motion.ts","../src/iki-motion.ts"],"sourcesContent":["/**\n * Clamp `value` into `[min, max]`.\n *\n * NaN in, NaN out — `Math.max(min, Math.min(max, NaN))` is `NaN`, so this\n * cannot be used to sanitize external input. Anything taking values from a\n * host must reject non-finite input itself before clamping (see\n * {@link ParameterStore.set}).\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n}\n","import type { IkiParameter } from \"@ikijs/format\";\nimport { clamp } from \"./math\";\n\n/**\n * Holds the live value of every model parameter, clamped to its declared\n * range. This is the single surface a host drives (lip-sync, gaze, blink) and\n * the engine reads each frame to evaluate bindings.\n */\nexport class ParameterStore {\n private readonly params = new Map<string, IkiParameter>();\n private readonly values = new Map<string, number>();\n /**\n * Resting value per id: the declared default clamped into range, resolved\n * ONCE here so `reset()` is a straight copy and a malformed descriptor is\n * reported once rather than on every reset.\n */\n private readonly defaults = new Map<string, number>();\n\n constructor(parameters: IkiParameter[]) {\n for (const param of parameters) {\n this.params.set(param.id, param);\n // A non-finite default would survive `clamp` and poison every read, so it\n // falls back to the neutral in-range value — but say so rather than\n // repairing a broken descriptor silently. `parseIkiModel` requires a\n // finite default, so this only reaches a host that skipped the validator.\n if (!Number.isFinite(param.default)) {\n console.error(\n `Iki: parameter \"${param.id}\" has a non-finite default; resting at the neutral in-range value instead`,\n );\n }\n const base = Number.isFinite(param.default) ? param.default : 0;\n this.defaults.set(param.id, clamp(base, param.min, param.max));\n }\n for (const [id, value] of this.defaults) this.values.set(id, value);\n }\n\n /**\n * Set a parameter's value, clamped to its range. Unknown ids are ignored, as\n * are non-finite values: this is the boundary a host drives with live signals,\n * and `clamp` cannot filter NaN (`Math.max(min, Math.min(max, NaN))` is NaN),\n * so one bad lip-sync/gaze frame would otherwise poison every binding that\n * reads the parameter. A dropped write holds the last good pose.\n */\n set(id: string, value: number): void {\n const param = this.params.get(id);\n if (!param) return;\n if (!Number.isFinite(value)) return;\n this.values.set(id, clamp(value, param.min, param.max));\n }\n\n /** Current value, or 0 if the id is unknown. */\n get(id: string): number {\n return this.values.get(id) ?? 0;\n }\n\n /** Position of a parameter within its range, 0..1. */\n normalized(id: string): number {\n const param = this.params.get(id);\n if (!param || param.max === param.min) return 0;\n return (this.get(id) - param.min) / (param.max - param.min);\n }\n\n /** Reset every parameter to its resting value (see `defaults`). */\n reset(): void {\n for (const [id, value] of this.defaults) this.values.set(id, value);\n }\n\n list(): IkiParameter[] {\n return [...this.params.values()];\n }\n}\n","// --- 2D affine helpers ------------------------------------------------------\n// Affine stored as [a, b, c, d, e, f] => | a c e |\n// | b d f |\n// | 0 0 1 |\nexport type Affine = [number, number, number, number, number, number];\n\nexport function translate(tx: number, ty: number): Affine {\n return [1, 0, 0, 1, tx, ty];\n}\n\nexport function scale(sx: number, sy: number): Affine {\n return [sx, 0, 0, sy, 0, 0];\n}\n\nexport function rotate(degrees: number): Affine {\n const r = (degrees * Math.PI) / 180;\n const c = Math.cos(r);\n const s = Math.sin(r);\n return [c, s, -s, c, 0, 0];\n}\n\nexport function multiply(a: Affine, b: Affine): Affine {\n return [\n a[0] * b[0] + a[2] * b[1],\n a[1] * b[0] + a[3] * b[1],\n a[0] * b[2] + a[2] * b[3],\n a[1] * b[2] + a[3] * b[3],\n a[0] * b[4] + a[2] * b[5] + a[4],\n a[1] * b[4] + a[3] * b[5] + a[5],\n ];\n}\n\n/** Expand a 2D affine into a column-major mat3 for `uniformMatrix3fv`. */\nexport function toMat3(a: Affine): Float32Array {\n return new Float32Array([a[0], a[1], 0, a[2], a[3], 0, a[4], a[5], 1]);\n}\n","import type {\n IkiBinding,\n IkiDeformer,\n IkiDeformerBinding,\n IkiDeformerTransform,\n IkiMatrixDeformer,\n IkiTransform,\n} from \"@ikijs/format\";\nimport { type Affine, multiply, rotate, scale, translate } from \"./affine\";\nimport type { ParameterStore } from \"./parameter-store\";\n\n/** Resolved TRS + opacity from a transform + bindings at current parameter values. */\nexport interface ResolvedTransform {\n x: number;\n y: number;\n rotation: number;\n scaleX: number;\n scaleY: number;\n opacity: number;\n}\n\nconst IDENTITY_TRANSFORM: Required<IkiTransform> = {\n x: 0,\n y: 0,\n rotation: 0,\n scaleX: 1,\n scaleY: 1,\n opacity: 1,\n};\n\n/**\n * Shared transform evaluator: resolves the effective TRS + opacity from a\n * (possibly absent) base transform plus bindings at current parameter values.\n *\n * - Part callers pass `IkiTransform` (which may include opacity) and use all 6\n * fields including `opacity`.\n * - Deformer callers pass `IkiDeformerTransform` (no opacity field) and\n * `IkiDeformerBinding[]` (no opacity channel); the returned `opacity` is\n * always 1 on the deformer path and should be ignored by the caller.\n */\nexport function evaluateTransform(\n transform: IkiTransform | IkiDeformerTransform | undefined,\n bindings: IkiBinding[] | IkiDeformerBinding[] | undefined,\n params: ParameterStore,\n): ResolvedTransform {\n const base = transform ?? IDENTITY_TRANSFORM;\n const result: ResolvedTransform = {\n x: base.x,\n y: base.y,\n rotation: base.rotation ?? 0,\n scaleX: base.scaleX ?? 1,\n scaleY: base.scaleY ?? 1,\n opacity: (base as IkiTransform).opacity ?? 1,\n };\n\n for (const binding of bindings ?? []) {\n const t = params.normalized(binding.parameter);\n const value = binding.from + (binding.to - binding.from) * t;\n switch (binding.channel) {\n case \"translateX\":\n result.x += value;\n break;\n case \"translateY\":\n result.y += value;\n break;\n case \"rotate\":\n result.rotation += value;\n break;\n case \"scaleX\":\n result.scaleX += value;\n break;\n case \"scaleY\":\n result.scaleY += value;\n break;\n case \"opacity\":\n result.opacity *= value;\n break;\n }\n }\n\n return result;\n}\n\n/**\n * Build the local deformer matrix about its pivot:\n * translate(pivot) · TRS · translate(-pivot)\n */\nfunction deformerLocalMatrix(\n d: IkiMatrixDeformer,\n params: ParameterStore,\n): Affine {\n const t = evaluateTransform(d.transform, d.bindings, params);\n const trs: Affine = multiply(\n multiply(translate(t.x, t.y), rotate(t.rotation)),\n scale(t.scaleX, t.scaleY),\n );\n return multiply(\n multiply(translate(d.pivot.x, d.pivot.y), trs),\n translate(-d.pivot.x, -d.pivot.y),\n );\n}\n\n/**\n * Resolve every deformer's world matrix in topological order, regardless of\n * array ordering. Returns a Map from deformer id to world-space Affine.\n *\n * The validator guarantees the hierarchy is acyclic and that every `parent`\n * id exists; the engine resolves on-demand with memoization so any valid\n * array order is handled correctly.\n *\n * Throws a clear internal Error if a parent is unexpectedly absent (defense-\n * in-depth — indicates an unvalidated model was passed to the engine).\n */\nexport function resolveDeformerWorlds(\n deformers: IkiDeformer[],\n params: ParameterStore,\n): Map<string, Affine> {\n // Warp deformers are non-affine; filter them out so matrix-only fields\n // (pivot/transform/bindings) are accessible and warp deformers are never\n // resolved as matrix deformers (which would produce NaN pivots).\n // resolveWarpGrids (warp-grid.ts) handles warp deformers separately.\n const matrixDeformers = deformers.filter(\n (d): d is IkiMatrixDeformer => d.kind === \"matrix\" || d.kind === undefined,\n );\n const byId = new Map<string, IkiMatrixDeformer>(\n matrixDeformers.map((d) => [d.id, d]),\n );\n const worldById = new Map<string, Affine>();\n\n function resolve(d: IkiMatrixDeformer): Affine {\n const cached = worldById.get(d.id);\n if (cached) return cached;\n\n const local = deformerLocalMatrix(d, params);\n let world: Affine;\n if (d.parent === undefined) {\n world = local;\n } else {\n const parentDef = byId.get(d.parent);\n if (!parentDef) {\n throw new Error(\n `unresolved deformer parent \"${d.parent}\" — model not validated?`,\n );\n }\n world = multiply(resolve(parentDef), local);\n }\n\n worldById.set(d.id, world);\n return world;\n }\n\n for (const d of matrixDeformers) {\n resolve(d);\n }\n\n return worldById;\n}\n","import type { IkiGrid2DKeyform, IkiWarp } from \"@ikijs/format\";\nimport type { ParameterStore } from \"./parameter-store\";\n\n/**\n * Accumulate one keyform set's interpolated offsets into `out` (out += ...).\n * Clamps `value` to [keyforms[0].value, keyforms[last].value] (no extrapolation),\n * linearly interpolates the bracketing pair, and adds. `out.length` must be >=\n * each keyform's `offsets.length`. Shared by part-local mesh warps and grid warps.\n */\nexport function accumulateKeyformOffsets(\n keyforms: { value: number; offsets: ArrayLike<number> }[],\n value: number,\n out: Float32Array,\n): void {\n const ks = keyforms;\n\n if (value <= ks[0].value) {\n // Clamp to first keyform.\n const { offsets } = ks[0];\n for (let i = 0; i < offsets.length; i++) {\n out[i] += offsets[i];\n }\n } else if (value >= ks[ks.length - 1].value) {\n // Clamp to last keyform.\n const { offsets } = ks[ks.length - 1];\n for (let i = 0; i < offsets.length; i++) {\n out[i] += offsets[i];\n }\n } else {\n // Find bracketing pair with a linear scan (keyforms are small in practice).\n let lo = ks[0];\n let hi = ks[1];\n for (let k = 1; k < ks.length - 1; k++) {\n if (ks[k].value <= value) {\n lo = ks[k];\n hi = ks[k + 1];\n }\n }\n const t = (value - lo.value) / (hi.value - lo.value);\n const loOff = lo.offsets;\n const hiOff = hi.offsets;\n for (let i = 0; i < loOff.length; i++) {\n out[i] += loOff[i] + (hiOff[i] - loOff[i]) * t;\n }\n }\n}\n\n/**\n * Accumulate a 2D keyform grid's bilinear-interpolated offsets into `out` (out += ...).\n *\n * This is PARAMETER bilinear interpolation driven by two live parameter values\n * (`vx` along `valuesX`, `vy` along `valuesY`). It is DISTINCT from\n * {@link sampleWarpGrid}'s spatial bilinear (which interpolates model-space\n * control-point positions). Name params `tx`/`ty` to avoid confusion with\n * the spatial `s`/`t` in sampleWarpGrid.\n *\n * Contract mirrors {@link accumulateKeyformOffsets}: `out += blended offsets`,\n * pure, deterministic, no allocation inside the function, O(points).\n *\n * Bracket rule (applied identically to X and Y; length ≥ 2 is validator-guaranteed):\n * - v <= values[0] → i = 0, t = 0\n * - v >= values[last] → i = length - 2, t = 1\n * - interior → i such that values[i] <= v < values[i+1], t = (v - values[i]) / (values[i+1] - values[i])\n * This keeps i ∈ [0, length-2] so i+1 is always in range.\n *\n * Corner index (row-major): k(i, j) = j * valuesX.length + i\n */\nexport function accumulate2DKeyformOffsets(\n valuesX: number[],\n valuesY: number[],\n keyforms2d: IkiGrid2DKeyform[],\n vx: number,\n vy: number,\n out: Float32Array,\n): void {\n const lastX = valuesX.length - 1;\n const lastY = valuesY.length - 1;\n\n // Per-axis bracket: clamp at both ends, interior normal scan.\n let ix: number;\n let tx: number;\n if (vx <= valuesX[0]) {\n ix = 0;\n tx = 0;\n } else if (vx >= valuesX[lastX]) {\n ix = lastX - 1;\n tx = 1;\n } else {\n ix = 0;\n for (let k = 0; k < lastX - 1; k++) {\n if (valuesX[k + 1] <= vx) ix = k + 1;\n }\n tx = (vx - valuesX[ix]) / (valuesX[ix + 1] - valuesX[ix]);\n }\n\n let iy: number;\n let ty: number;\n if (vy <= valuesY[0]) {\n iy = 0;\n ty = 0;\n } else if (vy >= valuesY[lastY]) {\n iy = lastY - 1;\n ty = 1;\n } else {\n iy = 0;\n for (let k = 0; k < lastY - 1; k++) {\n if (valuesY[k + 1] <= vy) iy = k + 1;\n }\n ty = (vy - valuesY[iy]) / (valuesY[iy + 1] - valuesY[iy]);\n }\n\n // Row-major corners: k(i, j) = j * valuesX.length + i.\n const W = valuesX.length;\n const c00 = keyforms2d[iy * W + ix];\n const c10 = keyforms2d[iy * W + ix + 1];\n const c01 = keyforms2d[(iy + 1) * W + ix];\n const c11 = keyforms2d[(iy + 1) * W + ix + 1];\n\n // Per-component bilinear: top/bot over tx, then ty into out.\n const o00 = c00.offsets;\n const o10 = c10.offsets;\n const o01 = c01.offsets;\n const o11 = c11.offsets;\n for (let n = 0; n < o00.length; n++) {\n const top = o00[n] + (o10[n] - o00[n]) * tx;\n const bot = o01[n] + (o11[n] - o01[n]) * tx;\n out[n] += top + (bot - top) * ty;\n }\n}\n\n/**\n * Apply all warps to `rest`, accumulating per-vertex offsets into `out`.\n *\n * - Copies `rest` into `out` first (out = rest).\n * - For each warp: looks up the live parameter value (REAL range, not\n * normalized), clamps to the keyform range (no extrapolation), linearly\n * interpolates the bracketing pair, and ADDS the result to `out`.\n * - No allocation: writes into the caller-supplied `out`. `rest` is never\n * mutated.\n * - `undefined` warps → identity copy (out equals rest).\n */\nexport function applyWarps(\n rest: Float32Array,\n warps: IkiWarp[] | undefined,\n params: ParameterStore,\n out: Float32Array,\n): void {\n out.set(rest);\n\n if (!warps || warps.length === 0) return;\n\n for (const warp of warps) {\n accumulateKeyformOffsets(warp.keyforms, params.get(warp.parameter), out);\n }\n}\n","import type { IkiDeformer, IkiWarpGrid } from \"@ikijs/format\";\nimport type { Affine } from \"./affine\";\nimport type { ParameterStore } from \"./parameter-store\";\nimport { clamp } from \"./math\";\nimport { accumulate2DKeyformOffsets, accumulateKeyformOffsets } from \"./warp\";\n\n/** A warp deformer's deformed control grid for one frame. */\nexport interface ResolvedWarpGrid {\n cols: number;\n rows: number;\n /** Deformed control points, MODEL space, length (cols+1)*(rows+1)*2. */\n points: Float32Array;\n}\n\n/**\n * For each warp deformer: take its rest `grid.points`, ADD the interpolated\n * grid-keyform offsets (accumulateKeyformOffsets) in the deformer's own rest\n * frame, THEN apply the parent matrix deformer's resolved world affine (if any)\n * — i.e. `parentAffine · (rest + offsets)`. Returns a Map from warp-deformer id\n * to its deformed grid (model space).\n *\n * `matrixWorlds` is the output of resolveDeformerWorlds (matrix deformers only);\n * warp deformers are skipped by that resolver (they are non-affine).\n *\n * ORDER IS CRITICAL: keyform offsets FIRST (curvature added in the rest frame),\n * parent affine SECOND — so the curvature rotates WITH the parent head rather\n * than staying pinned to world axes. The reversed order (affine then offsets)\n * pushes the bend along world-x even when the head is turned (coordinate bug).\n */\nexport function resolveWarpGrids(\n deformers: IkiDeformer[],\n params: ParameterStore,\n matrixWorlds: Map<string, Affine>,\n): Map<string, ResolvedWarpGrid> {\n const resolved = new Map<string, ResolvedWarpGrid>();\n\n for (const d of deformers) {\n if (d.kind !== \"warp\") continue;\n\n const { cols, rows, points: restPoints } = d.grid;\n const points = Float32Array.from(restPoints);\n\n // 1. Curvature in the rest frame: offsets += per-control-point deltas.\n for (const warp of d.warps ?? []) {\n accumulateKeyformOffsets(\n warp.keyforms,\n params.get(warp.parameter),\n points,\n );\n }\n // 1D xor 2D is validator-enforced; at most one branch contributes per deformer.\n if (d.warp2d !== undefined) {\n accumulate2DKeyformOffsets(\n d.warp2d.valuesX,\n d.warp2d.valuesY,\n d.warp2d.keyforms2d,\n params.get(d.warp2d.parameter),\n params.get(d.warp2d.parameterY),\n points,\n );\n }\n\n // 2. Parent matrix deformer's world affine (if any): parentAffine · (rest + offsets).\n if (d.parent !== undefined) {\n const parentAffine = matrixWorlds.get(d.parent);\n if (parentAffine) {\n for (let i = 0; i < points.length; i += 2) {\n const x = points[i];\n const y = points[i + 1];\n points[i] =\n parentAffine[0] * x + parentAffine[2] * y + parentAffine[4];\n points[i + 1] =\n parentAffine[1] * x + parentAffine[3] * y + parentAffine[5];\n }\n }\n }\n\n resolved.set(d.id, { cols, rows, points });\n }\n\n return resolved;\n}\n\n/** A model-space point bound to a rest-grid cell with within-cell (s,t). */\nexport interface GridBinding {\n /** row*cols + col index of the containing cell. */\n cell: number;\n /** [0,1] within-cell horizontal, 0 at the left (smaller-x) edge. */\n s: number;\n /** [0,1] within-cell vertical, 0 at the TOP (larger-y) edge. */\n t: number;\n}\n\n/**\n * Bind a model-space point to the REST grid: computes the containing cell +\n * local (s,t) by linear mapping over the grid's actual column/row boundaries.\n * Out-of-bounds points clamp to an edge cell with s/t pinned to 0/1. Never\n * returns NaN. Do NOT call against a deformed grid.\n *\n * The rest grid is validated to be a regular axis-aligned lattice with EXACT\n * ordering, so boundaries are read DIRECTLY from `restGrid.points` (no sorting):\n * - row-major, +y up; row 0 = TOP (LARGEST y, y DECREASES with row index);\n * - column 0 = LEFT (smallest x, x INCREASES with column index).\n * Column x-boundaries are row 0's x values `points[col*2]`; row y-boundaries are\n * column 0's y values `points[(row*(cols+1))*2 + 1]`. Maps x left→right and y\n * top→bottom: `s = (x - xLeft)/(xRight - xLeft)`, `t = (yTop - y)/(yTop - yBottom)`\n * (numerator `yTop - y`, NOT `y - minY`, so rows are not vertically flipped).\n */\nexport function bindPointToRestGrid(\n x: number,\n y: number,\n restGrid: IkiWarpGrid,\n): GridBinding {\n const { cols, rows, points } = restGrid;\n const stride = cols + 1;\n\n // Column boundaries: row 0's x values, increasing with column index.\n let col = cols - 1;\n for (let c = 0; c < cols; c++) {\n const xRight = points[(c + 1) * 2];\n if (x < xRight) {\n col = c;\n break;\n }\n }\n const xLeft = points[col * 2];\n const xRight = points[(col + 1) * 2];\n const s = clamp((x - xLeft) / (xRight - xLeft), 0, 1);\n\n // Row boundaries: column 0's y values, decreasing with row index (top→bottom).\n let row = rows - 1;\n for (let r = 0; r < rows; r++) {\n const yBottom = points[(r + 1) * stride * 2 + 1];\n if (y > yBottom) {\n row = r;\n break;\n }\n }\n const yTop = points[row * stride * 2 + 1];\n const yBottom = points[(row + 1) * stride * 2 + 1];\n const t = clamp((yTop - y) / (yTop - yBottom), 0, 1);\n\n return { cell: row * cols + col, s, t };\n}\n\n/**\n * Compute model-space positions for a warp-deformer child's mesh vertices:\n * transform each LOCAL-space vertex by `partAffine`, rebind to the RAW rest\n * grid, and bilinear-sample the deformed grid. Writes `out` (length ===\n * localVerts.length). Affine layout [a,b,c,d,e,f]: x'=a*x+c*y+e, y'=b*x+d*y+f.\n */\nexport function applyWarpToChild(\n localVerts: Float32Array | number[],\n partAffine: Affine,\n restGrid: IkiWarpGrid,\n deformedGrid: ResolvedWarpGrid,\n out: Float32Array,\n): void {\n const n = localVerts.length / 2;\n for (let v = 0; v < n; v++) {\n const lx = localVerts[v * 2];\n const ly = localVerts[v * 2 + 1];\n const mx = partAffine[0] * lx + partAffine[2] * ly + partAffine[4];\n const my = partAffine[1] * lx + partAffine[3] * ly + partAffine[5];\n const binding = bindPointToRestGrid(mx, my, restGrid);\n const [sx, sy] = sampleWarpGrid(deformedGrid, binding);\n out[v * 2] = sx;\n out[v * 2 + 1] = sy;\n }\n}\n\n/**\n * Bilinear-sample a deformed grid at a binding, returning model-space [x, y].\n * Reads the 4 corner control points of `binding.cell` from `grid.points`, using\n * the SAME row/col convention as `bindPointToRestGrid` (s left→right between\n * col and col+1, t top→bottom between row and row+1).\n */\nexport function sampleWarpGrid(\n grid: ResolvedWarpGrid,\n binding: GridBinding,\n): [number, number] {\n const { cols, points } = grid;\n const stride = cols + 1;\n const row = Math.floor(binding.cell / cols);\n const col = binding.cell % cols;\n const { s, t } = binding;\n\n const i00 = (row * stride + col) * 2;\n const i10 = (row * stride + col + 1) * 2;\n const i01 = ((row + 1) * stride + col) * 2;\n const i11 = ((row + 1) * stride + col + 1) * 2;\n\n // Top edge: lerp p00→p10 by s; bottom edge: lerp p01→p11 by s.\n const topX = points[i00] + (points[i10] - points[i00]) * s;\n const topY = points[i00 + 1] + (points[i10 + 1] - points[i00 + 1]) * s;\n const botX = points[i01] + (points[i11] - points[i01]) * s;\n const botY = points[i01 + 1] + (points[i11 + 1] - points[i01 + 1]) * s;\n\n // Vertical: lerp top→bottom by t.\n return [topX + (botX - topX) * t, topY + (botY - topY) * t];\n}\n","import type {\n IkiModel,\n IkiParameter,\n IkiPart,\n IkiWarp,\n IkiWarpDeformer,\n} from \"@ikijs/format\";\nimport { ParameterStore } from \"./parameter-store\";\nimport { multiply, rotate, scale, toMat3, translate } from \"./affine\";\nimport { evaluateTransform, resolveDeformerWorlds } from \"./deform\";\nimport { applyWarps } from \"./warp\";\nimport { applyWarpToChild, resolveWarpGrids } from \"./warp-grid\";\n\n/**\n * Alpha threshold used only during the stencil mask-write pass: a mask fragment\n * marks the stencil only where its coverage alpha is at least this. Keeps the\n * clip region to the mask's opaque body, not its anti-aliased fringe.\n */\nconst MASK_ALPHA_CUTOFF = 0.5;\n\n/**\n * Outcome of {@link IkiPlayer.load}: the indices into `model.textures` that\n * failed to decode or upload (empty = every declared texture loaded). The model\n * is still swapped in and rendered; parts using a failed texture are skipped.\n * A host can inspect this to detect and report a partial load.\n */\nexport interface IkiLoadResult {\n failedTextures: number[];\n /**\n * True when a newer `load()` (or `destroy()`) superseded this call before it\n * adopted anything — the model was NOT loaded and `failedTextures` is empty\n * because nothing was attempted, not because everything succeeded. Without\n * this flag a caller awaiting the losing promise cannot tell the two apart.\n */\n superseded: boolean;\n}\n\n/**\n * Engine-internal runtime representation of an uploaded mesh.\n *\n * `rest` is a copy of the authored vertices (Float32Array for direct GL upload);\n * `scratch` is a same-length preallocated buffer that the per-frame warp\n * pipeline writes morphed positions into before uploading.\n *\n * Index winding convention for an implicit-quad fixture: [0,1,2, 2,1,3]\n * (counter-clockwise from bottom-left). CULL_FACE is disabled, so winding\n * direction is not enforced, but mesh generators must match this.\n */\ninterface PartMesh {\n position: WebGLBuffer;\n uv: WebGLBuffer;\n index: WebGLBuffer;\n indexCount: number;\n rest: Float32Array;\n /** Preallocated warp output buffer; only present when `warps` is non-empty. */\n scratch?: Float32Array;\n warps?: IkiWarp[];\n /** The warp deformer this mesh hangs off, if any (its part.deformer is kind:\"warp\"). */\n warpDeformer?: IkiWarpDeformer;\n /** Local-space scratch (same length as `rest`) for the per-frame warp pipeline. */\n local?: Float32Array;\n}\n\n/**\n * Drives a single `.iki` model on a WebGL2 canvas.\n *\n * v1 scope: parts are solid-color or atlas-sampled textured quads or meshes,\n * transformed each frame by their base transform plus the sum of their parameter\n * bindings. `load()` is async — it decodes and uploads textures before swapping\n * the model in. Mesh parts additionally carry per-vertex UV and optional warp\n * keyforms, interpolated each frame on the CPU into a dynamic vertex buffer.\n */\nexport class IkiPlayer {\n private readonly gl: WebGL2RenderingContext;\n private readonly program: WebGLProgram;\n private readonly quad: WebGLBuffer;\n private readonly uMatrix: WebGLUniformLocation;\n private readonly uColor: WebGLUniformLocation;\n private readonly uUseTexture: WebGLUniformLocation;\n private readonly uTex: WebGLUniformLocation;\n private readonly uUvOffset: WebGLUniformLocation;\n private readonly uUvScale: WebGLUniformLocation;\n private readonly uUseMeshUv: WebGLUniformLocation;\n private readonly uAlphaCutoff: WebGLUniformLocation;\n private readonly aPos: number;\n private readonly aUv: number;\n /** True when the context granted a stencil buffer; clipping needs it. */\n private readonly stencilAvailable: boolean;\n\n private model?: IkiModel;\n private parts: IkiPart[] = [];\n private params = new ParameterStore([]);\n private rafId?: number;\n /** Uploaded textures, index-aligned with `model.textures`; `null` = unusable. */\n private textures: (WebGLTexture | null)[] = [];\n /** Bumped by every `load` and by `destroy`; lets a stale async load bail. */\n private loadGeneration = 0;\n /** True from the moment `load()` is entered until it resolves or throws. */\n private loadPending = false;\n /** Latches the un-awaited-load report, so a repeat caller says it once. */\n private warnedLoadUnfinished = false;\n private destroyed = false;\n /**\n * Engine-internal mesh buffers, keyed by the part's INDEX in `this.parts`\n * (NOT by part id — duplicate ids must not swap buffers).\n */\n private partMeshes = new Map<number, PartMesh>();\n /**\n * Clip groups resolved once per `load()`: consumer part index (into `this.parts`)\n * → its mask part indices. A part absent from this map is unclipped.\n */\n private partClipGroups = new Map<number, number[]>();\n\n constructor(private readonly canvas: HTMLCanvasElement) {\n const gl = canvas.getContext(\"webgl2\", {\n alpha: true,\n // The whole pipeline is premultiplied-alpha: the fragment shader\n // multiplies rgb by alpha, the blend function is ONE /\n // ONE_MINUS_SRC_ALPHA, and the page compositor reads the framebuffer\n // as premultiplied. Straight-alpha (`premultipliedAlpha: false`) cannot\n // be made consistent with SRC_ALPHA-style blending: semi-transparent\n // pixels over a transparent background come out premultiplied anyway\n // and composite too dark.\n premultipliedAlpha: true,\n // Stencil buffer backs clip masks (a part rendered only inside its masks'\n // coverage). Granted by every modern browser; the load() guard reports if not.\n stencil: true,\n });\n if (!gl) throw new Error(\"WebGL2 is not available in this browser\");\n this.gl = gl;\n this.stencilAvailable = gl.getContextAttributes()?.stencil ?? false;\n\n this.program = createProgram(gl, VERTEX_SHADER, FRAGMENT_SHADER);\n this.uMatrix = getUniform(gl, this.program, \"u_matrix\");\n this.uColor = getUniform(gl, this.program, \"u_color\");\n this.uUseTexture = getUniform(gl, this.program, \"u_useTexture\");\n this.uTex = getUniform(gl, this.program, \"u_tex\");\n this.uUvOffset = getUniform(gl, this.program, \"u_uvOffset\");\n this.uUvScale = getUniform(gl, this.program, \"u_uvScale\");\n this.uUseMeshUv = getUniform(gl, this.program, \"u_useMeshUv\");\n this.uAlphaCutoff = getUniform(gl, this.program, \"u_alphaCutoff\");\n // Fetch attribute locations here so renderFrame can set them explicitly\n // per draw path (mesh vs quad), rather than hiding the wiring in createUnitQuad.\n this.aPos = gl.getAttribLocation(this.program, \"a_pos\");\n this.aUv = gl.getAttribLocation(this.program, \"a_uv\");\n\n this.quad = createUnitQuad(gl);\n\n gl.enable(gl.BLEND);\n // Premultiplied-alpha \"over\": the shader already multiplied rgb by alpha.\n // (See the premultipliedAlpha context note above — SRC_ALPHA blending into\n // a straight-alpha canvas darkens semi-transparent parts.)\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n }\n\n /**\n * Load a model and reset parameters to their defaults. All textures are\n * decoded and uploaded before the model is swapped in — the swap is atomic,\n * so you never see a partially-textured frame. `start()` may be called any\n * time, but nothing renders until the first `load()` resolves. For an\n * embedded `data:` atlas this is near-instant.\n *\n * Individual texture decode/upload failures are non-fatal: they are logged\n * via `console.error`, the affected parts are skipped, and `load()` still\n * resolves — the returned {@link IkiLoadResult} lists the indices of any\n * textures that failed, so a host can detect and report a partial load. The\n * model is assumed already validated by `@ikijs/format`.\n *\n * Mesh buffer allocation failure IS fatal (unlike per-texture skip) because\n * textures have an `IkiLoadResult.failedTextures` reporting surface and mesh\n * buffers have none — there is no partial-mesh concept in the format.\n *\n * AWAIT THIS before reading {@link getParameters}. The swap happens after\n * texture decoding, so an un-awaited `load()` leaves the parameter store\n * empty for the rest of the tick; `getParameters` reports that case rather\n * than letting a host conclude the model drives nothing.\n */\n async load(model: IkiModel): Promise<IkiLoadResult> {\n // `finally` rather than a reset before each `return`: a fatal mesh-buffer\n // throw must not latch this on and make every later getParameters() a\n // false alarm.\n this.loadPending = true;\n try {\n return await this.adoptModel(model);\n } finally {\n this.loadPending = false;\n }\n }\n\n private async adoptModel(model: IkiModel): Promise<IkiLoadResult> {\n const { gl } = this;\n const generation = ++this.loadGeneration;\n\n // v1 decodes `data:` URIs only; external sources are skipped (resolver TBD).\n const sources = model.textures ?? [];\n const decoded = await Promise.allSettled(\n sources.map((tex) => decodeTexture(tex.source)),\n );\n\n // A newer load() or destroy() superseded us while decoding — bail without\n // creating GL textures or swapping any state.\n if (generation !== this.loadGeneration || this.destroyed) {\n for (const result of decoded) {\n if (result.status === \"fulfilled\" && result.value) result.value.close();\n }\n return { failedTextures: [], superseded: true };\n }\n\n // Queried once per load, not per texture: a GL parameter read is a driver\n // round-trip and this bound is constant for the context's lifetime.\n // `getParameter` yields null on a LOST context — reachable here, because\n // load() awaits decoding — and `width > null` coerces to `width > 0`, which\n // would reject every texture and blame a \"nullpx limit\". Skip the bound\n // instead when it is unavailable; the upload path below reports the real\n // failure.\n const maxTextureSizeRaw: unknown = gl.getParameter(gl.MAX_TEXTURE_SIZE);\n const maxTextureSize =\n typeof maxTextureSizeRaw === \"number\" ? maxTextureSizeRaw : undefined;\n if (maxTextureSize === undefined) {\n // A non-number here has one cause: the context is gone. Report it, because\n // the drain below consumes the single CONTEXT_LOST_WEBGL the spec\n // guarantees, and a model of implicit quads would otherwise adopt cleanly\n // and blame \"textures failed to load\".\n console.error(\"Iki: WebGL context lost during load()\");\n }\n\n // [7] getError returns the OLDEST latched error and says nothing about which\n // call produced it, so anything the render loop left pending during the\n // await would be pinned on the first texture. Start from a clean slate.\n drainGlErrors(gl);\n\n const uploaded: (WebGLTexture | null)[] = decoded.map((result, i) => {\n if (result.status === \"rejected\") {\n console.error(`Iki: failed to decode textures[${i}]`, result.reason);\n return null;\n }\n const bitmap = result.value;\n // External source was skipped during decode.\n if (!bitmap) return null;\n\n // An atlas wider than the driver's limit uploads as a GL error and leaves\n // a non-null but unsamplable texture, which would then render as black\n // rather than being reported. Reject it up front instead.\n if (\n maxTextureSize !== undefined &&\n (bitmap.width > maxTextureSize || bitmap.height > maxTextureSize)\n ) {\n console.error(\n `Iki: textures[${i}] is ${bitmap.width}x${bitmap.height}, over this device's ${maxTextureSize}px limit`,\n );\n bitmap.close();\n return null;\n }\n\n const texture = gl.createTexture();\n if (!texture) {\n bitmap.close();\n console.error(`Iki: failed to allocate GL texture for textures[${i}]`);\n return null;\n }\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n // Clear again so the check after texImage2D can only reflect texImage2D.\n drainGlErrors(gl);\n gl.texImage2D(\n gl.TEXTURE_2D,\n 0,\n gl.RGBA,\n gl.RGBA,\n gl.UNSIGNED_BYTE,\n bitmap,\n );\n bitmap.close();\n // Out-of-memory and other upload failures surface only here; without the\n // check the slot stays non-null and is reported as a successful load.\n const uploadError = gl.getError();\n if (uploadError !== gl.NO_ERROR) {\n gl.deleteTexture(texture);\n console.error(\n `Iki: failed to upload textures[${i}] (GL error 0x${uploadError.toString(16)})`,\n );\n return null;\n }\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n return texture;\n });\n\n // Build the new model's render state into LOCAL variables BEFORE adopting\n // anything. Do NOT read this.parts here — it still points at the PREVIOUS\n // model until the adoption swap below; reading it would build buffers for\n // old parts keyed by the new loop's indices.\n const nextParts = [...model.parts].sort((a, b) => a.order - b.order);\n const nextPartMeshes = new Map<number, PartMesh>();\n\n for (let i = 0; i < nextParts.length; i++) {\n const part = nextParts[i];\n if (!part.mesh) continue;\n\n const { mesh } = part;\n // Collect buffers as they are created so we can clean up on partial failure.\n const currentPartBuffers: WebGLBuffer[] = [];\n\n const positionBuf = gl.createBuffer();\n if (!positionBuf) {\n // Nothing created yet for this part — clean up prior parts + textures.\n deletePartMeshBuffers(gl, nextPartMeshes);\n deleteUploadedTextures(gl, uploaded);\n throw new Error(\"Iki: failed to allocate mesh buffer\");\n }\n currentPartBuffers.push(positionBuf);\n\n const uvBuf = gl.createBuffer();\n if (!uvBuf) {\n // position VBO created but uv VBO failed — delete position to avoid leak.\n for (const b of currentPartBuffers) gl.deleteBuffer(b);\n deletePartMeshBuffers(gl, nextPartMeshes);\n deleteUploadedTextures(gl, uploaded);\n throw new Error(\"Iki: failed to allocate mesh buffer\");\n }\n currentPartBuffers.push(uvBuf);\n\n const indexBuf = gl.createBuffer();\n if (!indexBuf) {\n // position + uv created but index failed — delete both.\n for (const b of currentPartBuffers) gl.deleteBuffer(b);\n deletePartMeshBuffers(gl, nextPartMeshes);\n deleteUploadedTextures(gl, uploaded);\n throw new Error(\"Iki: failed to allocate mesh buffer\");\n }\n\n // All three buffers allocated — upload data.\n const rest = new Float32Array(mesh.vertices);\n // A mesh part whose `deformer` references a kind:\"warp\" deformer is a warp\n // child: it morphs every frame (bind→sample against the grid) even with no\n // part-local warps, so it also needs DYNAMIC_DRAW + scratch + a local buffer.\n const warpDeformer =\n part.deformer !== undefined\n ? model.deformers?.find(\n (d): d is IkiWarpDeformer =>\n d.kind === \"warp\" && d.id === part.deformer,\n )\n : undefined;\n const isWarpChild = warpDeformer !== undefined;\n // Only allocate scratch and use DYNAMIC_DRAW when this part has warps or is\n // a warp child; warp-less static meshes never morph and skip per-frame upload.\n const hasWarps = (part.warps?.length ?? 0) > 0;\n const dynamic = hasWarps || isWarpChild;\n const scratch = dynamic\n ? new Float32Array(mesh.vertices.length)\n : undefined;\n // Warp children also need a second scratch for the part-local pipeline\n // (applyWarps + TRS) before grid binding.\n const local = isWarpChild\n ? new Float32Array(mesh.vertices.length)\n : undefined;\n\n // Clear first so the check after the three uploads reflects only them.\n drainGlErrors(gl);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, positionBuf);\n gl.bufferData(\n gl.ARRAY_BUFFER,\n rest,\n dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW,\n );\n\n gl.bindBuffer(gl.ARRAY_BUFFER, uvBuf);\n gl.bufferData(\n gl.ARRAY_BUFFER,\n new Float32Array(mesh.uvs),\n gl.STATIC_DRAW,\n );\n\n // Vertex count is validator-capped at 65536, so Uint16 cannot wrap.\n gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuf);\n gl.bufferData(\n gl.ELEMENT_ARRAY_BUFFER,\n new Uint16Array(mesh.indices),\n gl.STATIC_DRAW,\n );\n\n // Allocation was checked above, but `bufferData` can still raise\n // OUT_OF_MEMORY and leave a valid-but-EMPTY buffer, which draws garbage or\n // nothing at all. The contract on this class calls a mesh-buffer failure\n // fatal precisely because it has no `failedTextures`-style reporting\n // surface — which only holds if the failure is detected here.\n const meshUploadError = gl.getError();\n if (meshUploadError !== gl.NO_ERROR) {\n for (const b of currentPartBuffers) gl.deleteBuffer(b);\n gl.deleteBuffer(indexBuf);\n deletePartMeshBuffers(gl, nextPartMeshes);\n deleteUploadedTextures(gl, uploaded);\n throw new Error(\n `Iki: failed to upload mesh buffers for part \"${part.id}\" (GL error 0x${meshUploadError.toString(16)})`,\n );\n }\n\n nextPartMeshes.set(i, {\n position: positionBuf,\n uv: uvBuf,\n index: indexBuf,\n indexCount: mesh.indices.length,\n rest,\n scratch,\n warps: part.warps,\n warpDeformer,\n local,\n });\n }\n\n // Resolve clip groups once: consumer part index → mask part indices. The\n // model is already validated (every mask id maps to a unique mesh part), so\n // this is a plain id→index lookup, not a per-frame reconstruction.\n const nextClipGroups = new Map<number, number[]>();\n const indexById = new Map<string, number>();\n for (let i = 0; i < nextParts.length; i++) {\n indexById.set(nextParts[i].id, i);\n }\n for (let i = 0; i < nextParts.length; i++) {\n const clip = nextParts[i].clip;\n if (!clip) continue;\n const maskIndices = clip.masks\n .map((maskId) => indexById.get(maskId))\n .filter((mi): mi is number => mi !== undefined);\n if (maskIndices.length > 0) nextClipGroups.set(i, maskIndices);\n }\n if (nextClipGroups.size > 0 && !this.stencilAvailable) {\n console.error(\n \"Iki: model uses clip masks but this WebGL2 context has no stencil buffer; rendering unclipped\",\n );\n }\n\n // Atomic adoption: release the previous model's part-mesh buffers and textures,\n // then adopt the new model's state in a single block so the render loop\n // never sees a half-adopted model.\n deletePartMeshBuffers(gl, this.partMeshes);\n for (const texture of this.textures) {\n if (texture) gl.deleteTexture(texture);\n }\n\n this.model = model;\n this.params = new ParameterStore(model.parameters);\n this.parts = nextParts;\n this.partMeshes = nextPartMeshes;\n this.partClipGroups = nextClipGroups;\n this.textures = uploaded;\n\n return {\n failedTextures: uploaded.flatMap((t, i) => (t === null ? [i] : [])),\n superseded: false,\n };\n }\n\n /**\n * Start the render loop. Safe to call more than once, and a no-op after\n * {@link destroy} — the program and buffers the loop draws with are gone, so\n * restarting would only spray GL errors.\n */\n start(): void {\n if (this.rafId !== undefined || this.destroyed) return;\n const loop = (): void => {\n this.renderFrame();\n this.rafId = requestAnimationFrame(loop);\n };\n this.rafId = requestAnimationFrame(loop);\n }\n\n stop(): void {\n if (this.rafId === undefined) return;\n cancelAnimationFrame(this.rafId);\n this.rafId = undefined;\n }\n\n /**\n * Set a parameter value (clamped to its range). Unknown ids and non-finite\n * values are ignored — see {@link ParameterStore.set}.\n */\n setParameter(id: string, value: number): void {\n this.params.set(id, value);\n }\n\n /**\n * Current value of a parameter, or 0 for an unknown id.\n *\n * Hosts need this to avoid shadowing the engine's state: the motion drivers\n * read the live pose to compute the next one, and without a read accessor\n * every host has to keep its own mirror of what it last wrote — and keep that\n * mirror's clamping in step with {@link ParameterStore} by hand.\n */\n getParameter(id: string): number {\n return this.params.get(id);\n }\n\n /**\n * The model's parameter descriptors, for building UI or host wiring.\n *\n * Empty until the first {@link load} resolves. Reaching it through an\n * un-awaited `load()` is the one mistake in this class that produces no\n * error and no motion: the caller gets `[]`, concludes the model has no\n * parameters, and drives nothing — so that case is reported instead of\n * being indistinguishable from a model that really declares none. A\n * reload is deliberately NOT reported: those parameters are stale rather\n * than absent, and warning there would fire on legitimate concurrent reads.\n */\n getParameters(): IkiParameter[] {\n if (\n this.loadPending &&\n this.model === undefined &&\n !this.warnedLoadUnfinished\n ) {\n this.warnedLoadUnfinished = true;\n console.error(\n \"Iki: getParameters() ran before load() finished, so it returned an empty list — await load() before reading parameters.\",\n );\n }\n return this.params.list();\n }\n\n destroy(): void {\n this.stop();\n // Invalidate any in-flight load so it bails before touching GL state.\n this.destroyed = true;\n ++this.loadGeneration;\n const { gl } = this;\n for (const texture of this.textures) {\n if (texture) gl.deleteTexture(texture);\n }\n this.textures = [];\n deletePartMeshBuffers(gl, this.partMeshes);\n this.partMeshes = new Map();\n gl.deleteBuffer(this.quad);\n gl.deleteProgram(this.program);\n }\n\n private renderFrame(): void {\n const { gl, canvas } = this;\n\n const dpr = window.devicePixelRatio || 1;\n const width = Math.max(1, Math.floor(canvas.clientWidth * dpr));\n const height = Math.max(1, Math.floor(canvas.clientHeight * dpr));\n if (canvas.width !== width || canvas.height !== height) {\n canvas.width = width;\n canvas.height = height;\n }\n\n gl.viewport(0, 0, width, height);\n gl.clearColor(0, 0, 0, 0);\n gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);\n\n if (!this.model) return;\n\n // Fit the logical model canvas into the drawing buffer, preserving aspect,\n // and convert model units to clip space.\n const { width: modelW, height: modelH } = this.model.canvas;\n const fit = Math.min(width / modelW, height / modelH);\n const clipX = (fit * 2) / width;\n const clipY = (fit * 2) / height;\n\n gl.useProgram(this.program);\n\n const deformerWorlds =\n this.model.deformers && this.model.deformers.length > 0\n ? resolveDeformerWorlds(this.model.deformers, this.params)\n : undefined;\n\n // Resolve each warp deformer's deformed control grid for this frame (parent\n // matrix affine + grid keyforms). Warp-child mesh parts sample these grids\n // instead of riding the affine dWorld·TRS chain.\n const warpGrids = this.model.deformers?.some((d) => d.kind === \"warp\")\n ? resolveWarpGrids(\n this.model.deformers,\n this.params,\n deformerWorlds ?? new Map(),\n )\n : undefined;\n\n // u_alphaCutoff defaults to 0 (no fragment is discarded) for normal parts;\n // the mask-write pass raises it temporarily (see drawClipped).\n gl.uniform1f(this.uAlphaCutoff, 0);\n for (let index = 0; index < this.parts.length; index++) {\n const maskIndices = this.partClipGroups.get(index);\n if (maskIndices && this.stencilAvailable) {\n this.drawClipped(\n index,\n maskIndices,\n clipX,\n clipY,\n deformerWorlds,\n warpGrids,\n );\n } else {\n // Unclipped (or stencil unavailable — load() already reported it).\n this.drawPart(index, clipX, clipY, deformerWorlds, warpGrids);\n }\n }\n }\n\n /**\n * Draw a clipped part: stencil the union of its masks' alpha coverage, then\n * draw the part only where the stencil was written. The mask parts also draw\n * normally in their own `order` slot — this is an EXTRA, color-free pass over\n * the same per-frame deformed geometry. All stencil/colorMask state the pass\n * touches is restored before returning so later parts are unaffected.\n */\n private drawClipped(\n index: number,\n maskIndices: number[],\n clipX: number,\n clipY: number,\n deformerWorlds: ReturnType<typeof resolveDeformerWorlds> | undefined,\n warpGrids: ReturnType<typeof resolveWarpGrids> | undefined,\n ): void {\n const { gl } = this;\n\n // 1. Write coverage into the stencil (REPLACE 1), no color. Each mask is\n // drawn with its per-frame deformed geometry; u_alphaCutoff discards the\n // transparent fringe so only opaque coverage marks the stencil. Multiple\n // masks union naturally (all write 1).\n gl.clear(gl.STENCIL_BUFFER_BIT);\n gl.enable(gl.STENCIL_TEST);\n gl.colorMask(false, false, false, false);\n gl.stencilMask(0xff);\n gl.stencilFunc(gl.ALWAYS, 1, 0xff);\n gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);\n gl.uniform1f(this.uAlphaCutoff, MASK_ALPHA_CUTOFF);\n for (const maskIndex of maskIndices) {\n this.drawPart(maskIndex, clipX, clipY, deformerWorlds, warpGrids);\n }\n\n // 2. Draw the consumer only where stencil == 1; restore color writes first.\n gl.uniform1f(this.uAlphaCutoff, 0);\n gl.colorMask(true, true, true, true);\n gl.stencilMask(0x00);\n gl.stencilFunc(gl.EQUAL, 1, 0xff);\n gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);\n this.drawPart(index, clipX, clipY, deformerWorlds, warpGrids);\n\n // 3. Restore every stencil state this pass changed (colorMask + u_alphaCutoff\n // already restored above) so the next unmasked part renders normally.\n gl.disable(gl.STENCIL_TEST);\n gl.stencilMask(0xff);\n gl.stencilFunc(gl.ALWAYS, 0, 0xff);\n gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);\n }\n\n /**\n * Draw a single part with its full per-part material + geometry state. Shared\n * by the normal pass, the stencil mask-write pass, and the masked consumer\n * draw — so every path prepares the SAME complete uniform/texture/VBO state\n * (the caller only sets stencil/colorMask/u_alphaCutoff around it).\n */\n private drawPart(\n index: number,\n clipX: number,\n clipY: number,\n deformerWorlds: ReturnType<typeof resolveDeformerWorlds> | undefined,\n warpGrids: ReturnType<typeof resolveWarpGrids> | undefined,\n ): void {\n const { gl } = this;\n const part = this.parts[index];\n const texture = part.texture\n ? this.textures[part.texture.index]\n : undefined;\n // A textured part whose slot is null (skipped/failed) draws nothing.\n if (part.texture && !texture) return;\n\n const t = this.evaluate(part);\n // Warp-child mesh parts bypass the affine dWorld·TRS chain entirely: their\n // vertices are computed by the per-frame grid pipeline below (which bakes\n // part TRS into model-space positions), so u_matrix carries ONLY clip-scale.\n const warpChild = this.partMeshes.get(index)?.warpDeformer;\n // clip <- project <- [deformer?] <- translate <- rotate <- scale(size)\n let m: ReturnType<typeof multiply>;\n if (warpChild) {\n m = scale(clipX, clipY);\n } else if (part.deformer !== undefined) {\n const dWorld = deformerWorlds!.get(part.deformer);\n if (!dWorld) {\n throw new Error(\n `part \"${part.id}\" references unknown deformer \"${part.deformer}\"`,\n );\n }\n m = multiply(multiply(scale(clipX, clipY), dWorld), translate(t.x, t.y));\n m = multiply(m, rotate(t.rotation));\n m = multiply(m, scale(part.width * t.scaleX, part.height * t.scaleY));\n } else {\n m = multiply(scale(clipX, clipY), translate(t.x, t.y));\n m = multiply(m, rotate(t.rotation));\n m = multiply(m, scale(part.width * t.scaleX, part.height * t.scaleY));\n }\n\n const [r, g, b, a] = part.color;\n gl.uniformMatrix3fv(this.uMatrix, false, toMat3(m));\n gl.uniform4f(this.uColor, r, g, b, a * t.opacity);\n\n if (part.texture && texture) {\n const { uv } = part.texture;\n gl.uniform1i(this.uUseTexture, 1);\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.uniform1i(this.uTex, 0);\n gl.uniform2f(this.uUvOffset, uv.x, uv.y);\n gl.uniform2f(this.uUvScale, uv.width, uv.height);\n } else {\n gl.uniform1i(this.uUseTexture, 0);\n }\n\n if (part.mesh) {\n // --- Mesh draw path ---\n const pm = this.partMeshes.get(index);\n if (!pm) {\n // Impossible after the fatal-allocation rule in load(); throwing rather\n // than skipping matches the existing unknown-deformer-parent throw in\n // deform.ts and ensures engine bugs are never silently hidden.\n throw new Error(`Iki: mesh buffers missing for part \"${part.id}\"`);\n }\n\n gl.uniform1i(this.uUseMeshUv, 1);\n\n // For warped meshes, compute morphed positions and upload to the\n // DYNAMIC_DRAW VBO. Warp-less meshes skip this — their VBO already\n // holds `rest` from load().\n if (pm.warpDeformer) {\n // --- Warp-deformer (group warp) child pipeline ---\n // Coordinate invariant (top bug risk): BIND against the RAW rest grid\n // (warpDeformer.grid — no keyform offsets, no parent affine), SAMPLE\n // against the RESOLVED grid (resolveWarpGrids output: offsets added,\n // THEN parent affine). Never bind against a resolved/deformed grid.\n const warpDef = pm.warpDeformer;\n const grid = warpGrids!.get(warpDef.id)!;\n // 1. part-local mesh warps into the local scratch (no-op if none).\n applyWarps(pm.rest, pm.warps, this.params, pm.local!);\n // 2. live part TRS (eye/mouth open etc.), baked into the vertices.\n const trs = evaluateTransform(\n part.transform,\n part.bindings,\n this.params,\n );\n const partAffine = multiply(\n multiply(translate(trs.x, trs.y), rotate(trs.rotation)),\n scale(part.width * trs.scaleX, part.height * trs.scaleY),\n );\n // 2b+3+4. transform each local vertex by partAffine, rebind to the RAW\n // rest grid, and sample the RESOLVED (deformed) grid.\n applyWarpToChild(\n pm.local!,\n partAffine,\n warpDef.grid,\n grid,\n pm.scratch!,\n );\n gl.bindBuffer(gl.ARRAY_BUFFER, pm.position);\n gl.bufferSubData(gl.ARRAY_BUFFER, 0, pm.scratch!);\n } else if (pm.warps && pm.warps.length > 0) {\n // scratch is always allocated when warps is non-empty (see load())\n applyWarps(pm.rest, pm.warps, this.params, pm.scratch!);\n gl.bindBuffer(gl.ARRAY_BUFFER, pm.position);\n gl.bufferSubData(gl.ARRAY_BUFFER, 0, pm.scratch!);\n }\n\n // Position VBO (DYNAMIC_DRAW — morphed for warped parts, rest otherwise).\n gl.bindBuffer(gl.ARRAY_BUFFER, pm.position);\n gl.enableVertexAttribArray(this.aPos);\n gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0);\n\n // UV VBO (STATIC_DRAW — mesh UVs are passed straight through, no flip).\n gl.bindBuffer(gl.ARRAY_BUFFER, pm.uv);\n gl.enableVertexAttribArray(this.aUv);\n gl.vertexAttribPointer(this.aUv, 2, gl.FLOAT, false, 0, 0);\n\n gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, pm.index);\n gl.drawElements(gl.TRIANGLES, pm.indexCount, gl.UNSIGNED_SHORT, 0);\n } else {\n // --- Implicit-quad draw path ---\n // Disable a_uv so no stale mesh UV buffer from a preceding mesh part is\n // sourced. The quad shader branch derives UV from a_pos, not a_uv.\n gl.uniform1i(this.uUseMeshUv, 0);\n gl.disableVertexAttribArray(this.aUv);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, this.quad);\n gl.enableVertexAttribArray(this.aPos);\n gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0);\n\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n }\n }\n\n /** Resolve a part's effective transform from its base plus active bindings. */\n private evaluate(part: IkiPart): ReturnType<typeof evaluateTransform> {\n return evaluateTransform(part.transform, part.bindings, this.params);\n }\n}\n\n// --- WebGL plumbing ---------------------------------------------------------\nconst VERTEX_SHADER = `#version 300 es\nin vec2 a_pos;\nin vec2 a_uv;\nuniform mat3 u_matrix;\nuniform vec2 u_uvOffset;\nuniform vec2 u_uvScale;\nuniform bool u_useMeshUv;\nout vec2 v_uv;\nvoid main() {\n if (u_useMeshUv) {\n // Mesh path: UVs are already top-left atlas-space; pass straight through,\n // no flip (the only flip in the pipeline lives in the quad branch below).\n v_uv = a_uv;\n } else {\n // Quad path: a_pos corners are +/-0.5; lift to 0..1 (y-up), then map into\n // the atlas sub-rect with a single V flip so the result is top-left UVs.\n vec2 uvLocal = a_pos + 0.5;\n v_uv = vec2(\n u_uvOffset.x + uvLocal.x * u_uvScale.x,\n u_uvOffset.y + (1.0 - uvLocal.y) * u_uvScale.y\n );\n }\n vec3 p = u_matrix * vec3(a_pos, 1.0);\n gl_Position = vec4(p.xy, 0.0, 1.0);\n}`;\n\nconst FRAGMENT_SHADER = `#version 300 es\nprecision mediump float;\nuniform vec4 u_color;\nuniform bool u_useTexture;\nuniform sampler2D u_tex;\nuniform float u_alphaCutoff;\nin vec2 v_uv;\nout vec4 outColor;\nvoid main() {\n vec4 base = u_useTexture ? texture(u_tex, v_uv) : vec4(1.0);\n vec4 tinted = base * u_color;\n // 0 for normal draws (no-op); raised during the stencil mask-write pass so\n // only opaque mask coverage marks the stencil (the transparent fringe is cut).\n if (tinted.a < u_alphaCutoff) discard;\n // Premultiply: the blend function and the canvas compositing contract\n // (premultipliedAlpha: true) both expect rgb already scaled by alpha.\n outColor = vec4(tinted.rgb * tinted.a, tinted.a);\n}`;\n\n/**\n * Decode a texture source into an ImageBitmap, or `null` for an unsupported\n * (non-`data:`) source. v1 fetches `data:` URIs only — never arbitrary URLs.\n */\nasync function decodeTexture(source: string): Promise<ImageBitmap | null> {\n if (!source.startsWith(\"data:\")) {\n console.warn(\n \"Iki: external texture sources are unsupported in v1; skipping\",\n source.slice(0, 32),\n );\n return null;\n }\n const blob = await (await fetch(source)).blob();\n return createImageBitmap(blob, {\n imageOrientation: \"none\",\n premultiplyAlpha: \"none\",\n });\n}\n\n/** Create the shared unit-quad position VBO (centered, triangle-strip). */\nfunction createUnitQuad(gl: WebGL2RenderingContext): WebGLBuffer {\n const buffer = gl.createBuffer();\n if (!buffer) throw new Error(\"failed to allocate quad buffer\");\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n // Unit square centered on the origin, as a triangle strip.\n gl.bufferData(\n gl.ARRAY_BUFFER,\n new Float32Array([-0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5]),\n gl.STATIC_DRAW,\n );\n // Attribute pointers are set explicitly in renderFrame per draw path;\n // createUnitQuad only owns buffer allocation and data upload.\n return buffer;\n}\n\n/**\n * Clear every latched GL error so the next `getError()` reflects only the call\n * that follows this one. A lost context reports CONTEXT_LOST_WEBGL once and\n * NO_ERROR after, so this terminates.\n */\nfunction drainGlErrors(gl: WebGL2RenderingContext): void {\n // Bounded: the spec guarantees this terminates (a lost context reports\n // CONTEXT_LOST_WEBGL once, then NO_ERROR), but this is the one file with no\n // unit coverage, and an unbounded driver loop would hang the tab with no\n // stack against a wedged or mocked context.\n for (let i = 0; i < 32 && gl.getError() !== gl.NO_ERROR; i++) {\n /* discard */\n }\n}\n\n/** Delete all position/uv/index buffers stored in a PartMesh map. */\nfunction deletePartMeshBuffers(\n gl: WebGL2RenderingContext,\n meshes: Map<number, PartMesh>,\n): void {\n for (const pm of meshes.values()) {\n gl.deleteBuffer(pm.position);\n gl.deleteBuffer(pm.uv);\n gl.deleteBuffer(pm.index);\n }\n}\n\n/** Delete all non-null textures from an uploaded texture array. */\nfunction deleteUploadedTextures(\n gl: WebGL2RenderingContext,\n textures: (WebGLTexture | null)[],\n): void {\n for (const texture of textures) {\n if (texture) gl.deleteTexture(texture);\n }\n}\n\nfunction createProgram(\n gl: WebGL2RenderingContext,\n vertexSrc: string,\n fragmentSrc: string,\n): WebGLProgram {\n const program = gl.createProgram();\n if (!program) throw new Error(\"failed to allocate WebGL program\");\n gl.attachShader(program, compileShader(gl, gl.VERTEX_SHADER, vertexSrc));\n gl.attachShader(program, compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc));\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(program);\n gl.deleteProgram(program);\n throw new Error(`program link failed: ${log}`);\n }\n return program;\n}\n\nfunction compileShader(\n gl: WebGL2RenderingContext,\n type: number,\n source: string,\n): WebGLShader {\n const shader = gl.createShader(type);\n if (!shader) throw new Error(\"failed to allocate shader\");\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader);\n gl.deleteShader(shader);\n throw new Error(`shader compile failed: ${log}`);\n }\n return shader;\n}\n\nfunction getUniform(\n gl: WebGL2RenderingContext,\n program: WebGLProgram,\n name: string,\n): WebGLUniformLocation {\n const loc = gl.getUniformLocation(program, name);\n if (!loc) throw new Error(`uniform not found: ${name}`);\n return loc;\n}\n","import { StandardParameter } from \"@ikijs/format\";\nimport { MAX_DT_MS } from \"./frame-clock\";\nimport { clamp } from \"./math\";\n\n// --- Module-internal timing/easing constants -----------------------------------\n// These are intentionally private; tests assert observable behavior, not config.\n\nconst BLINK_INTERVAL_MIN_MS = 1500;\nconst BLINK_INTERVAL_MAX_MS = 6000;\nconst BLINK_DURATION_MS = 120; // full close+open cycle\n\nconst BREATH_PERIOD_MS = 3500;\n\nconst GAZE_RADIUS = 0.3;\nconst GAZE_RETARGET_MIN_MS = 1200;\nconst GAZE_RETARGET_MAX_MS = 3000;\n// Fraction of the gap to close each millisecond of clamped dt.\n// Chosen so a typical 16ms frame moves ~1.5 % of remaining distance.\nconst GAZE_EASE_RATE = 0.001;\n\n// Head sway: sums of slow sines with near-coprime periods, so the drift never\n// visibly repeats. Degrees — small against the ±30° standard AngleX/Y/Z range,\n// but enough continuous head motion to keep hair physics from sleeping.\nconst SWAY_X_AMP_A_DEG = 2.2;\nconst SWAY_X_PERIOD_A_MS = 6100;\nconst SWAY_X_AMP_B_DEG = 1.3;\nconst SWAY_X_PERIOD_B_MS = 9700;\nconst SWAY_Y_AMP_DEG = 1.6;\nconst SWAY_Y_PERIOD_MS = 7300;\n// Roll is the smallest and slowest of the three: an idle head barely tilts,\n// and a tilt reads much larger than a turn of the same degrees.\nconst SWAY_Z_AMP_DEG = 1.1;\nconst SWAY_Z_PERIOD_MS = 11300;\n\n// --- Small pure helpers -------------------------------------------------------\n\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\n/** Blink envelope: 1 at phase 0 and 1, 0 at the midpoint (triangle dip). */\nfunction blinkEnvelope(phase: number): number {\n // phase in [0,1]; map to a symmetric triangle: 0→1, 0.5→0, 1→1\n return 2 * Math.abs(phase - 0.5);\n}\n\n/** Pick a random point within a disk of `radius` using the injected rng. */\nfunction randomInDisk(rng: () => number, radius: number): [number, number] {\n // Polar sampling: bounded, no rejection loop, safe with any [0,1) rng.\n // r = sqrt(u) gives uniform area distribution; θ spans the full circle.\n const r = Math.sqrt(rng()) * radius;\n const theta = rng() * 2 * Math.PI;\n return [r * Math.cos(theta), r * Math.sin(theta)];\n}\n\n// --- Public API ---------------------------------------------------------------\n\nexport interface IdleMotionOptions {\n /** Inject a deterministic rng for testing. Defaults to Math.random. */\n rng?: () => number;\n}\n\n/**\n * Pure-logic idle-animation driver. Animates the eight \"life\" parameters\n * (eyes, breath, gaze, head sway) on an internal clock so tab-backgrounding\n * or irregular frame delivery can't produce teleports or snap-close blinks.\n *\n * Usage:\n * const idle = new IdleMotion(player.setParameter.bind(player));\n * // inside your rAF loop:\n * idle.update(performance.now());\n *\n * The host is responsible for scheduling; this class has no timers or rAF.\n */\nexport class IdleMotion {\n /**\n * Every parameter id {@link update} and `emitRestingPose` write, in write\n * order. Published so a host (or IkiMotion) can restore them after it\n * stops driving — keep in step with those two methods; the test suite\n * pins this list to the actual emissions.\n */\n readonly drivenParameterIds: readonly string[] = [\n StandardParameter.EyeOpenLeft,\n StandardParameter.EyeOpenRight,\n StandardParameter.Breath,\n StandardParameter.EyeballX,\n StandardParameter.EyeballY,\n StandardParameter.AngleX,\n StandardParameter.AngleY,\n StandardParameter.AngleZ,\n ];\n private readonly sink: (id: string, value: number) => void;\n private readonly rng: () => number;\n\n // Internal clock: advances by clamped dt, NOT by raw wall-clock jumps.\n // This is the only clock blink/breath/gaze scheduling reads.\n private clockMs = 0;\n private prevNowMs: number | undefined = undefined;\n\n // Blink state\n private nextBlinkAtMs: number;\n private blinkStartMs = -1; // -1 means not currently blinking\n\n // Gaze state\n private gazeCurrentX = 0;\n private gazeCurrentY = 0;\n private gazeTargetX = 0;\n private gazeTargetY = 0;\n private nextGazeRetargetMs: number;\n\n constructor(\n sink: (id: string, value: number) => void,\n options?: IdleMotionOptions,\n ) {\n this.sink = sink;\n // Math.random is the single allowed default reference; all other uses go\n // through this.rng so tests can inject a deterministic substitute.\n this.rng = options?.rng ?? Math.random;\n\n // Schedule the first blink and gaze retarget relative to clock zero.\n this.nextBlinkAtMs = lerp(\n BLINK_INTERVAL_MIN_MS,\n BLINK_INTERVAL_MAX_MS,\n this.rng(),\n );\n this.nextGazeRetargetMs = lerp(\n GAZE_RETARGET_MIN_MS,\n GAZE_RETARGET_MAX_MS,\n this.rng(),\n );\n }\n\n /**\n * Advance the idle animation to the given wall-clock timestamp (milliseconds).\n *\n * On the first call: record prevNowMs, emit the resting pose, and return —\n * no animation advance happens so there is no jump from time 0.\n *\n * On subsequent calls: compute dt = clamp(nowMs - prevNowMs, 0, MAX_DT_MS)\n * and advance the internal clock by dt. A non-monotonic nowMs produces a\n * negative raw delta that the clamp floors to 0 — no rewind.\n */\n update(nowMs: number): void {\n // Host-supplied timestamp, same boundary rule as FixedStepClock.advance and\n // ParameterStore.set: drop the frame, touch nothing. `clockMs += NaN` would\n // stick, and every blink/gaze schedule reads clockMs — the face would go\n // still permanently. A host driving from media time hands you NaN through an\n // ordinary path (`duration * pct` before metadata loads).\n if (!Number.isFinite(nowMs)) return;\n if (this.prevNowMs === undefined) {\n this.prevNowMs = nowMs;\n // Emit resting pose so the host mirror stays in sync from frame 1.\n this.emitRestingPose();\n return;\n }\n\n // Clamp dt so a backgrounded tab or long GC pause advances the internal\n // clock by at most MAX_DT_MS — blink can't snap shut, gaze can't teleport.\n const rawDt = nowMs - this.prevNowMs;\n const dt = clamp(rawDt, 0, MAX_DT_MS);\n this.prevNowMs = nowMs;\n this.clockMs += dt;\n\n // Both eyes always carry the identical value (unified blink).\n const eyeVal = this.advanceBlink();\n this.sink(StandardParameter.EyeOpenLeft, eyeVal);\n this.sink(StandardParameter.EyeOpenRight, eyeVal);\n\n const breath = this.advanceBreath();\n this.advanceGaze(dt);\n\n this.sink(StandardParameter.Breath, breath);\n this.sink(StandardParameter.EyeballX, this.gazeCurrentX);\n this.sink(StandardParameter.EyeballY, this.gazeCurrentY);\n\n // Head sway keeps the character (and any hair physics reading AngleX/Y/Z)\n // from freezing solid between blinks. Models without these parameters\n // ignore the writes (ParameterStore drops unknown ids).\n this.sink(StandardParameter.AngleX, this.swayX());\n this.sink(StandardParameter.AngleY, this.swayY());\n this.sink(StandardParameter.AngleZ, this.swayZ());\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private emitRestingPose(): void {\n this.sink(StandardParameter.EyeOpenLeft, 1);\n this.sink(StandardParameter.EyeOpenRight, 1);\n // Breath at phase 0: sin(0) = 0 → 0.5 + 0.5*0 = 0.5\n this.sink(StandardParameter.Breath, 0.5);\n this.sink(StandardParameter.EyeballX, 0);\n this.sink(StandardParameter.EyeballY, 0);\n // Sway sines are all 0 at clock 0 — the head starts centered.\n this.sink(StandardParameter.AngleX, 0);\n this.sink(StandardParameter.AngleY, 0);\n this.sink(StandardParameter.AngleZ, 0);\n }\n\n /** Returns the current eye-open value (0..1) and advances blink state. */\n private advanceBlink(): number {\n const { clockMs } = this;\n\n // If a blink is in progress, evaluate the envelope.\n if (this.blinkStartMs >= 0) {\n const phase = (clockMs - this.blinkStartMs) / BLINK_DURATION_MS;\n if (phase >= 1) {\n // Blink finished — schedule the next one on the internal clock.\n this.blinkStartMs = -1;\n this.nextBlinkAtMs =\n clockMs +\n lerp(BLINK_INTERVAL_MIN_MS, BLINK_INTERVAL_MAX_MS, this.rng());\n return 1;\n }\n return blinkEnvelope(phase);\n }\n\n // Check if it is time to start a new blink.\n if (clockMs >= this.nextBlinkAtMs) {\n this.blinkStartMs = clockMs;\n // Return the first envelope sample (exactly 1 at phase 0).\n return blinkEnvelope(0);\n }\n\n // Between blinks: eyes fully open.\n return 1;\n }\n\n private advanceBreath(): number {\n return (\n 0.5 + 0.5 * Math.sin((2 * Math.PI * this.clockMs) / BREATH_PERIOD_MS)\n );\n }\n\n /** Horizontal head sway in degrees, pure function of the internal clock. */\n private swayX(): number {\n const t = 2 * Math.PI * this.clockMs;\n return (\n SWAY_X_AMP_A_DEG * Math.sin(t / SWAY_X_PERIOD_A_MS) +\n SWAY_X_AMP_B_DEG * Math.sin(t / SWAY_X_PERIOD_B_MS)\n );\n }\n\n /** Vertical head sway in degrees, pure function of the internal clock. */\n private swayY(): number {\n return (\n SWAY_Y_AMP_DEG * Math.sin((2 * Math.PI * this.clockMs) / SWAY_Y_PERIOD_MS)\n );\n }\n\n /** Head roll sway in degrees, pure function of the internal clock. */\n private swayZ(): number {\n return (\n SWAY_Z_AMP_DEG * Math.sin((2 * Math.PI * this.clockMs) / SWAY_Z_PERIOD_MS)\n );\n }\n\n /** Ease gaze current toward target; pick a new target on the internal clock. */\n private advanceGaze(dt: number): void {\n // Retarget check reads clockMs so a clamped pause can't skip retargets.\n if (this.clockMs >= this.nextGazeRetargetMs) {\n const [tx, ty] = randomInDisk(this.rng, GAZE_RADIUS);\n this.gazeTargetX = tx;\n this.gazeTargetY = ty;\n this.nextGazeRetargetMs =\n this.clockMs +\n lerp(GAZE_RETARGET_MIN_MS, GAZE_RETARGET_MAX_MS, this.rng());\n }\n\n // Exponential ease toward target. The ease factor is derived from the\n // CLAMPED dt so a paused tab moves at most one normal step, never a teleport.\n const factor = 1 - Math.pow(1 - GAZE_EASE_RATE, dt);\n this.gazeCurrentX = lerp(this.gazeCurrentX, this.gazeTargetX, factor);\n this.gazeCurrentY = lerp(this.gazeCurrentY, this.gazeTargetY, factor);\n }\n}\n","import { clamp } from \"./math\";\n\n/** Per-frame dt ceiling: a backgrounded tab or a long GC pause must not snap state. */\nexport const MAX_DT_MS = 100;\n/** Fixed integration sub-step, in SECONDS. */\nexport const FIXED_DT_S = 1 / 60;\n/** Catch-up cap per frame (spiral-of-death guard). */\nexport const MAX_SUBSTEPS = 6;\n\n/**\n * Fixed-timestep accumulator shared by the physics drivers, so a stability fix\n * lands in one place instead of one of two near-identical copies.\n *\n * A driver calls {@link advance} once per frame and runs its integration body\n * that many times. Leftover time carries to the next frame, which is what keeps\n * the simulation frame-rate independent; the dt clamp and the sub-step cap keep\n * a hitch from either snapping the rig or spiralling into catch-up work.\n */\nexport class FixedStepClock {\n private prevNowMs: number | undefined = undefined;\n private accumulatorS = 0;\n\n /**\n * True until the first {@link advance}. Drivers seed their rest pose on that\n * frame and integrate nothing, so a model loaded mid-motion does not kick.\n */\n get isSeedFrame(): boolean {\n return this.prevNowMs === undefined;\n }\n\n /**\n * Fold `nowMs` into the accumulator and return how many {@link FIXED_DT_S}\n * sub-steps to run this frame. On the seed frame it only records the\n * timestamp and returns 0. A non-monotonic `nowMs` floors to 0 — no rewind.\n */\n advance(nowMs: number): number {\n // Host-supplied timestamp, so the same rule as ParameterStore.set: reject\n // non-finite input at the boundary. `clamp(NaN, ...)` is NaN, which would\n // make the accumulator NaN and every `NaN >= FIXED_DT_S` false FOREVER —\n // the rig would freeze silently with no way back. Pre-existing; hardened\n // here so both host entry points behave the same way.\n if (!Number.isFinite(nowMs)) return 0;\n if (this.prevNowMs === undefined) {\n this.prevNowMs = nowMs;\n return 0;\n }\n const dtMs = clamp(nowMs - this.prevNowMs, 0, MAX_DT_MS);\n this.prevNowMs = nowMs;\n this.accumulatorS += dtMs / 1000; // boundary ms->s conversion\n\n let steps = 0;\n while (this.accumulatorS >= FIXED_DT_S && steps < MAX_SUBSTEPS) {\n this.accumulatorS -= FIXED_DT_S;\n steps++;\n }\n return steps;\n }\n}\n","import type { IkiParameter, IkiPhysics } from \"@ikijs/format\";\nimport { FIXED_DT_S, FixedStepClock } from \"./frame-clock\";\nimport { clamp } from \"./math\";\n\n// --- Small pure helpers ------------------------------------------------------\n\n/**\n * Map a parameter value to [-1, 1] around its engine-effective default. Portable\n * across param ranges: the wider side (rest→max vs min→rest) sets the unit, so\n * ±1 lands at the farther extreme. Returns 0 for a zero-width range.\n *\n * The rest center is `clamp(default, min, max)` to match ParameterStore, which\n * clamps an out-of-range default — so the spring rests where the model actually\n * renders, not at a raw out-of-range default.\n */\nfunction signedNormalized(\n value: number,\n param: { min: number; max: number; default: number },\n): number {\n const rest = clamp(param.default, param.min, param.max);\n const den = Math.max(Math.abs(param.max - rest), Math.abs(rest - param.min));\n if (den === 0) return 0;\n return clamp((value - rest) / den, -1, 1);\n}\n\n// --- Public API --------------------------------------------------------------\n\ninterface RigState {\n x: number; // spring position (lagging, normalized-ish units)\n v: number; // spring velocity\n}\n\n/**\n * Host-agnostic 1D spring-mass-damper secondary-motion driver — the physics\n * peer of {@link IdleMotion}. For each rig it reads the input parameter,\n * signed-normalizes it around the input's default × `weight` to form a spring\n * target, integrates a lagging spring position with semi-implicit (symplectic)\n * Euler on a fixed 1/60s sub-step accumulator, and writes\n * `outputDefault + x * scale` onto the output parameter — so the output lags\n * and overshoots the input (hair/accessory sway).\n *\n * Usage:\n * const physics = new PhysicsMotion(\n * model.physics ?? [],\n * model.parameters,\n * (id) => currentValue(id),\n * player.setParameter.bind(player),\n * );\n * // inside your rAF loop, right AFTER idle.update(now):\n * physics.update(performance.now());\n *\n * The host schedules updates; this class has no timers, rAF, DOM, or Date.now.\n * Writes go through the sink, exactly like IdleMotion; the player renders the\n * updated params on its own render loop (drivers and rendering are decoupled).\n */\nexport class PhysicsMotion {\n /** The output parameter of every rig, in rig order — what `emit` writes. */\n readonly drivenParameterIds: readonly string[];\n private readonly rigs: readonly IkiPhysics[];\n private readonly read: (id: string) => number;\n private readonly sink: (id: string, value: number) => void;\n private readonly params: Map<string, IkiParameter>;\n\n // Integrator state, owned here — never stored in a ParameterStore.\n private readonly state: RigState[];\n private readonly clock = new FixedStepClock();\n\n constructor(\n rigs: IkiPhysics[],\n params: IkiParameter[],\n read: (id: string) => number,\n sink: (id: string, value: number) => void,\n ) {\n this.rigs = rigs;\n this.read = read;\n this.sink = sink;\n this.params = new Map(params.map((p) => [p.id, p]));\n this.state = rigs.map(() => ({ x: 0, v: 0 }));\n this.drivenParameterIds = rigs.map((rig) => rig.output.parameter);\n }\n\n /**\n * Advance every rig to the given wall-clock timestamp (milliseconds).\n *\n * First call: seed each spring to rest AT its current target (so a model\n * loaded with a nonzero input does not kick), emit the resting output, and\n * return without integrating — mirroring IdleMotion's first-frame behavior.\n *\n * Subsequent calls: {@link FixedStepClock} folds the clamped frame delta into\n * its accumulator and returns how many {@link FIXED_DT_S} sub-steps are due;\n * the spring advances that many semi-implicit Euler steps, then each rig emits\n * its output once. The clock's dt clamp and sub-step cap plus the symplectic\n * integrator are what keep it stable across hitches.\n */\n update(nowMs: number): void {\n if (this.clock.isSeedFrame) {\n this.clock.advance(nowMs);\n for (let i = 0; i < this.rigs.length; i++) {\n const st = this.state[i];\n st.x = this.targetFor(this.rigs[i]);\n st.v = 0;\n this.emit(this.rigs[i], st);\n }\n return;\n }\n\n const steps = this.clock.advance(nowMs);\n\n // update() is synchronous, so the input cannot change mid-loop — read each\n // rig's target ONCE per frame and reuse it across the sub-steps.\n const targets = this.rigs.map((rig) => this.targetFor(rig));\n\n for (let s = 0; s < steps; s++) {\n for (let i = 0; i < this.rigs.length; i++) {\n this.step(this.rigs[i], this.state[i], targets[i]);\n }\n }\n\n // Always emit (even when zero sub-steps ran) so the sink stays in sync.\n for (let i = 0; i < this.rigs.length; i++) {\n const st = this.state[i];\n // The fixed 1/60s sub-step can diverge for an extreme-but-parse-valid rig\n // (tiny mass / huge stiffness push ω·dt past the explicit-integrator\n // stability limit). If state goes non-finite, snap back to rest at the\n // current target: the store DROPS a non-finite write, so an un-reset rig\n // would emit NaN every frame and silently freeze the part at its last\n // good pose instead of visibly diverging.\n if (!Number.isFinite(st.x) || !Number.isFinite(st.v)) {\n st.x = Number.isFinite(targets[i]) ? targets[i] : 0;\n st.v = 0;\n }\n this.emit(this.rigs[i], st);\n }\n }\n\n /** Spring target = signed-normalized input value × weight. */\n private targetFor(rig: IkiPhysics): number {\n const param = this.params.get(rig.input.parameter);\n const value = this.read(rig.input.parameter);\n const norm = param ? signedNormalized(value, param) : 0;\n return norm * rig.input.weight;\n }\n\n /** One semi-implicit (symplectic) Euler sub-step of FIXED_DT_S seconds. */\n private step(rig: IkiPhysics, st: RigState, target: number): void {\n const accel =\n (rig.stiffness * (target - st.x) - rig.damping * st.v) / rig.mass;\n st.v += accel * FIXED_DT_S;\n st.x += st.v * FIXED_DT_S;\n }\n\n /** Write outputDefault + x * scale onto the output param via the sink. */\n private emit(rig: IkiPhysics, st: RigState): void {\n const outParam = this.params.get(rig.output.parameter);\n // clamp to match ParameterStore's engine-effective default (see signedNormalized).\n const outDefault = outParam\n ? clamp(outParam.default, outParam.min, outParam.max)\n : 0;\n const value = outDefault + st.x * rig.output.scale;\n // Final guard: even a finite-but-enormous `x` could overflow the product to\n // ±Infinity. Emit the rest pose rather than hand a non-finite value to the\n // sink, which would drop the write and leave the output stuck where it was.\n this.sink(\n rig.output.parameter,\n Number.isFinite(value) ? value : outDefault,\n );\n }\n}\n","import type {\n IkiDeformer,\n IkiParameter,\n IkiPhysicsChain,\n IkiPhysicsChainSegment,\n} from \"@ikijs/format\";\nimport type { Affine } from \"./affine\";\nimport { resolveDeformerWorlds } from \"./deform\";\n// The two physics drivers stay independent of EACH OTHER; the timing primitives\n// they both need live in frame-clock.ts so a stability fix lands once.\nimport { FIXED_DT_S, FixedStepClock } from \"./frame-clock\";\nimport { clamp } from \"./math\";\nimport { ParameterStore } from \"./parameter-store\";\n\nconst DEG2RAD = Math.PI / 180;\nconst RAD2DEG = 180 / Math.PI;\n\n// --- Per-chain / per-segment state -------------------------------------------\n\n/** Integrator state for one segment. angle = θ displacement in RADIANS. */\ninterface SegmentState {\n angle: number; // θ_i in radians (displacement from rest)\n angularVelocity: number; // ω_i in radians/s\n}\n\n/** All precomputed per-chain data (rest angles already in radians). */\ninterface ChainData {\n chain: IkiPhysicsChain;\n restAnglesRad: number[]; // restAngle_j in radians; 0 when omitted\n state: SegmentState[]; // preallocated, length = segments.length\n}\n\n// --- Public API --------------------------------------------------------------\n\n/**\n * Host-agnostic multi-segment angular-pendulum-chain secondary-motion driver.\n * Peer of {@link PhysicsMotion} and {@link IdleMotion}.\n *\n * Each chain anchors to a matrix deformer in the model hierarchy. The driver\n * self-computes the anchor's world rotation via `resolveDeformerWorlds` (a\n * private `ParameterStore` is filled from `read` ONCE per frame) and integrates\n * a per-segment angular pendulum with semi-implicit Euler on a fixed 1/60s\n * sub-step accumulator. Each segment's angular displacement θ (in radians\n * internally) is emitted in DEGREES on its output parameter, so `rotate = 0`\n * when the chain is at its authored rest pose.\n *\n * Usage:\n * const chains = new HairChainMotion(\n * model.physicsChains ?? [],\n * model.parameters,\n * model.deformers ?? [],\n * (id) => currentValue(id),\n * player.setParameter.bind(player),\n * );\n * // inside your rAF loop, right AFTER physics.update(now):\n * chains.update(performance.now());\n *\n * The host schedules updates; this class has no timers, rAF, DOM, or Date.now.\n */\nexport class HairChainMotion {\n /** Every segment's output, chain by chain — what `emitSegment` writes. */\n readonly drivenParameterIds: readonly string[];\n private readonly chainData: ChainData[];\n private readonly params: Map<string, IkiParameter>;\n private readonly deformers: IkiDeformer[];\n private readonly store: ParameterStore;\n private readonly read: (id: string) => number;\n private readonly sink: (id: string, value: number) => void;\n\n private readonly clock = new FixedStepClock();\n\n constructor(\n chains: IkiPhysicsChain[],\n params: IkiParameter[],\n deformers: IkiDeformer[],\n read: (id: string) => number,\n sink: (id: string, value: number) => void,\n ) {\n this.params = new Map(params.map((p) => [p.id, p]));\n this.deformers = deformers;\n // Private ParameterStore reused every frame for anchor-world resolution.\n this.store = new ParameterStore(params);\n this.read = read;\n this.sink = sink;\n\n // Precompute rest angles in radians; preallocate segment state (no per-substep alloc).\n this.chainData = chains.map((chain) => ({\n chain,\n restAnglesRad: chain.segments.map((seg) =>\n seg.restAngle !== undefined ? seg.restAngle * DEG2RAD : 0,\n ),\n state: chain.segments.map(() => ({ angle: 0, angularVelocity: 0 })),\n }));\n this.drivenParameterIds = chains.flatMap((c) =>\n c.segments.map((s) => s.output.parameter),\n );\n }\n\n /**\n * Advance every chain to the given wall-clock timestamp (milliseconds).\n *\n * First call: seed every segment to θ=0/ω=0 (rest), emit the rest output\n * (outDefault + 0), and return without integrating — mirrors PhysicsMotion's\n * first-frame behavior so a model loaded in motion does not kick.\n *\n * Subsequent calls: dt = clamp(nowMs - prevNowMs, 0, MAX_DT_MS) → seconds into\n * accumulator. The per-frame world snapshot (anchor world angles) is taken ONCE\n * per update() — NOT per chain — so all chains share a consistent frame snapshot.\n * Fixed FIXED_DT_S sub-steps are run root→tip, capped at MAX_SUBSTEPS; leftover\n * time is carried to the next frame. Segments emit after substeps (even on zero\n * substeps) with a non-finite guard.\n */\n update(nowMs: number): void {\n if (this.clock.isSeedFrame) {\n // FIRST FRAME: seed rest, emit outDefault for every segment, NO integration.\n this.clock.advance(nowMs);\n for (const cd of this.chainData) {\n for (let i = 0; i < cd.chain.segments.length; i++) {\n cd.state[i].angle = 0;\n cd.state[i].angularVelocity = 0;\n this.emitSegment(cd.chain.segments[i], cd.state[i]);\n }\n }\n return;\n }\n\n const steps = this.clock.advance(nowMs);\n\n // Take the per-frame world snapshot ONCE per update() — NOT per chain.\n // Fill the private store from read, then resolve all deformer world matrices.\n for (const param of this.params.values()) {\n this.store.set(param.id, this.read(param.id));\n }\n const worldMap = resolveDeformerWorlds(this.deformers, this.store);\n\n // Read each chain's anchor world angle ONCE per frame (consistent across substeps,\n // like physics-motion.ts:122 `targets`).\n const anchorAnglesRad = this.chainData.map((cd) =>\n this.anchorWorldAngleRad(worldMap, cd.chain.anchorDeformer),\n );\n\n for (let s = 0; s < steps; s++) {\n for (let c = 0; c < this.chainData.length; c++) {\n this.stepChain(this.chainData[c], anchorAnglesRad[c]);\n }\n }\n\n // Emit always (even on zero substeps) so the sink stays in sync.\n for (const cd of this.chainData) {\n for (let i = 0; i < cd.chain.segments.length; i++) {\n const st = cd.state[i];\n // FINITENESS GUARD (mirror physics-motion.ts:141): a pathological rig\n // (tiny mass / huge stiffness) can push the fixed-substep integrator\n // past its explicit-stability limit. Snap the segment back to rest so\n // a validated model can never poison the sink with NaN/Infinity.\n if (\n !Number.isFinite(st.angle) ||\n !Number.isFinite(st.angularVelocity)\n ) {\n st.angle = 0;\n st.angularVelocity = 0;\n }\n this.emitSegment(cd.chain.segments[i], st);\n }\n }\n }\n\n /**\n * Extract world rotation (radians) from the anchor's Affine tuple.\n * Affine = [a,b,c,d,e,f]; rotation column = (a,b) → atan2(b,a).\n *\n * If the anchor id is absent from the map, THROWS an internal Error — the\n * format validator guarantees the anchor exists, so absence is an invariant\n * break (mirrors resolveDeformerWorlds' throw on an unresolved parent,\n * deform.ts:141).\n */\n private anchorWorldAngleRad(\n worldMap: Map<string, Affine>,\n anchorId: string,\n ): number {\n const world = worldMap.get(anchorId);\n if (!world) {\n throw new Error(\n `HairChainMotion: anchor deformer \"${anchorId}\" not found in resolved world map — model not validated?`,\n );\n }\n // Affine [a,b,c,d,e,f]: the first column (a,b) is the x-axis direction\n // after rotation, so atan2(b,a) gives the world rotation angle in radians.\n return Math.atan2(world[1], world[0]);\n }\n\n /**\n * One fixed sub-step of FIXED_DT_S seconds for all segments in a chain.\n *\n * Segments are integrated ROOT→TIP so each segment can read its upstream\n * neighbor's current-substep state when computing the world angle Φ_i.\n * (The chain is causal root-to-tip; reversing the order would use stale θ\n * values from the previous substep for Φ_i computation.)\n *\n * Per-segment semi-implicit (symplectic) Euler:\n * Φ_i = anchorWorldAngleRad + Σ_{j≤i}(restAngle_j + θ_j)\n * α_i = (−stiffness_i·θ_i − strength·sin(Φ_i − gravityAngle_rad) − damping_i·ω_i) / mass_i\n * ω_i += α_i · FIXED_DT_S (velocity updated FIRST = semi-implicit)\n * θ_i += ω_i · FIXED_DT_S (position updated from NEW velocity)\n *\n * The spring term is −stiffness·θ (restoring θ→0); restAngle does NOT appear\n * in the spring term, only in Φ_i for the gravity torque.\n */\n private stepChain(cd: ChainData, anchorAngleRad: number): void {\n const { chain, restAnglesRad, state } = cd;\n const gravityAngleRad = chain.gravity.angle * DEG2RAD;\n const strength = chain.gravity.strength;\n\n // Accumulate world angle root→tip as we go.\n let worldAngleAccumRad = anchorAngleRad;\n\n for (let i = 0; i < chain.segments.length; i++) {\n const seg: IkiPhysicsChainSegment = chain.segments[i];\n const st = state[i];\n\n // World angle of this segment: anchor + sum of all segments 0..i.\n worldAngleAccumRad += restAnglesRad[i] + st.angle;\n const phi = worldAngleAccumRad;\n\n const alpha =\n (-seg.stiffness * st.angle -\n strength * Math.sin(phi - gravityAngleRad) -\n seg.damping * st.angularVelocity) /\n seg.mass;\n\n // Semi-implicit Euler: update velocity first, then position with new velocity.\n st.angularVelocity += alpha * FIXED_DT_S;\n st.angle += st.angularVelocity * FIXED_DT_S;\n }\n }\n\n /** Emit outDefault + (θ_i · RAD2DEG) · scale for one segment. */\n private emitSegment(seg: IkiPhysicsChainSegment, st: SegmentState): void {\n const outParam = this.params.get(seg.output.parameter);\n const outDefault = outParam\n ? clamp(outParam.default, outParam.min, outParam.max)\n : 0;\n const value = outDefault + st.angle * RAD2DEG * seg.output.scale;\n // Final guard: even a finite-but-enormous θ could overflow to ±Infinity.\n this.sink(\n seg.output.parameter,\n Number.isFinite(value) ? value : outDefault,\n );\n }\n}\n","import type { IkiModel } from \"@ikijs/format\";\nimport { HairChainMotion } from \"./hair-chain-motion\";\nimport { IdleMotion } from \"./idle-motion\";\nimport { PhysicsMotion } from \"./physics-motion\";\n\n/**\n * Bundles the three motion drivers — {@link IdleMotion}, {@link PhysicsMotion},\n * {@link HairChainMotion} — into the one loop every host otherwise hand-builds:\n * construct all three from the model, step them in the order physics and\n * chains depend on, and know what they wrote.\n *\n * Usage:\n * const motion = new IkiMotion(\n * model,\n * (id) => player.getParameter(id),\n * (id, value) => player.setParameter(id, value),\n * );\n * // inside your rAF loop:\n * motion.update(performance.now());\n *\n * The host schedules; this class has no timers, rAF, DOM, or Date.now.\n *\n * Stopping is the host's too: stop calling update(). The drivers leave the\n * pose where it was — a host that wants it back writes its own resting values\n * to `drivenParameterIds`.\n */\nexport class IkiMotion {\n /**\n * Idle ids, then rig outputs, then chain-segment outputs, deduplicated and\n * insertion-ordered. May name ids the model lacks (the player silently\n * drops writes to unknown ids) — intersect with the model's parameters if\n * you mirror into your own store.\n */\n readonly drivenParameterIds: readonly string[];\n private readonly idle: IdleMotion;\n private readonly physics: PhysicsMotion;\n private readonly chains: HairChainMotion;\n\n constructor(\n model: IkiModel,\n read: (id: string) => number,\n sink: (id: string, value: number) => void,\n ) {\n const rigs = model.physics ?? [];\n const chains = model.physicsChains ?? [];\n this.idle = new IdleMotion(sink);\n this.physics = new PhysicsMotion(rigs, model.parameters, read, sink);\n this.chains = new HairChainMotion(\n chains,\n model.parameters,\n model.deformers ?? [],\n read,\n sink,\n );\n this.drivenParameterIds = [\n ...new Set([\n ...this.idle.drivenParameterIds,\n ...this.physics.drivenParameterIds,\n ...this.chains.drivenParameterIds,\n ]),\n ];\n }\n\n /**\n * Advance idle, then physics, then chains to the same wall-clock timestamp\n * (milliseconds). Order is load-bearing: `PhysicsMotion` reads its input\n * parameters (typically `ParamAngleX/Z`) through `read`, and the head sway\n * idle wrote THIS frame is what the springs must lag behind; `HairChainMotion`\n * then resolves its anchor deformer's world rotation from that same\n * just-written pose (which may include a physics output). One timestamp for\n * all three keeps their dt in lockstep.\n */\n update(nowMs: number): void {\n this.idle.update(nowMs);\n this.physics.update(nowMs);\n this.chains.update(nowMs);\n }\n}\n"],"mappings":"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","names":["xRight","yBottom"]}
|