@ikijs/editor 0.8.0 → 0.8.1

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/document.ts","../src/mesh-uv.ts","../src/commands.ts","../src/grid-keyform.ts","../src/reparent.ts","../src/alpha-bbox.ts","../src/binding-capture.ts","../src/atlas.ts","../src/factories.ts","../src/auto-rig.ts"],"sourcesContent":["import { parseIkiModel } from \"@ikijs/format\";\nimport type {\n IkiBinding,\n IkiDeformer,\n IkiDeformerBinding,\n IkiDeformerTransform,\n IkiMatrixDeformer,\n IkiModel,\n IkiPart,\n IkiPhysics,\n IkiTexture,\n IkiTransform,\n IkiUvRect,\n IkiWarpDeformer,\n} from \"@ikijs/format\";\n\nimport type { EditCommand } from \"./commands\";\nimport { remapMeshUvsToRect } from \"./mesh-uv\";\n\n/**\n * One part mapped to an imported atlas source. `index` is always 0 because the\n * atlas is a single page; only the `uv` sub-rectangle varies per part.\n */\nexport interface AtlasAssignment {\n partId: string;\n uv: IkiUvRect;\n}\n\n/** Input to {@link EditorDocument.applyAtlas}: the new atlas table plus the\n * per-part UV assignments into it. */\nexport interface ApplyAtlasInput {\n textures: IkiTexture[];\n partTextureAssignments: AtlasAssignment[];\n}\n\n/**\n * In-memory editing session over a single {@link IkiModel}. The model is held\n * directly (no superset) and mutated in place by invertible {@link EditCommand}s\n * pushed through {@link execute}; undo/redo invert/re-apply them.\n *\n * The constructor `structuredClone`s the input so the caller's model is never\n * mutated. Parts are addressed by stable `id`, never by array index.\n */\nexport class EditorDocument {\n private readonly model: IkiModel;\n private readonly undoStack: EditCommand[] = [];\n private readonly redoStack: EditCommand[] = [];\n /** Editor-only session state, never serialized; keyed by stable part id. The\n * unmodified BASE local uvs of every mesh part, captured once at construction\n * so atlas remaps always derive from the original (idempotent). */\n private readonly baseMeshUvs = new Map<string, number[]>();\n\n constructor(model: IkiModel) {\n this.model = structuredClone(model);\n // Capture-once base UVs. Scope: assumes the loaded model's mesh parts carry\n // original LOCAL 0..1 uvs (true for the sample). Restoring base UVs after\n // reloading an already-textured exported model is out of scope (deferred —\n // needs project-file persistence).\n for (const part of this.model.parts) {\n if (part.mesh) {\n this.baseMeshUvs.set(part.id, part.mesh.uvs.slice());\n }\n }\n }\n\n /** Live reference to the working model — for READ access. Mutate it only\n * through {@link execute}/{@link undo}/{@link redo}. */\n getModel(): IkiModel {\n return this.model;\n }\n\n /** Resolve a part by stable id. Throws a plain `Error` (NOT an\n * `IkiFormatError`) with a path-qualified message if the id is unknown. */\n findPart(id: string): IkiPart {\n const part = this.model.parts.find((p) => p.id === id);\n if (!part) {\n throw new Error(`parts: no part with id \"${id}\"`);\n }\n return part;\n }\n\n /** Resolve a warp deformer by stable id. Throws a path-qualified plain\n * `Error` if no deformer matches the id or the match is not a warp deformer.\n * READ/mutate-through accessor, consistent with {@link findPart}. */\n findWarpDeformer(id: string): IkiWarpDeformer {\n const deformer = this.model.deformers?.find((d) => d.id === id);\n if (!deformer || deformer.kind !== \"warp\") {\n throw new Error(`deformers: no warp deformer with id \"${id}\"`);\n }\n return deformer;\n }\n\n /** Resolve a matrix deformer by stable id. Throws a path-qualified plain\n * `Error` if no deformer matches the id or the match is a warp deformer\n * (`kind === \"warp\"`). A `kind` of `\"matrix\"` or `undefined` is a matrix\n * deformer. READ/mutate-through accessor, consistent with {@link findPart}. */\n findMatrixDeformer(id: string): IkiMatrixDeformer {\n const deformer = this.model.deformers?.find((d) => d.id === id);\n if (!deformer || deformer.kind === \"warp\") {\n throw new Error(`deformers: no matrix deformer with id \"${id}\"`);\n }\n return deformer;\n }\n\n /** Resolve any deformer (matrix or warp) by stable id. Throws a\n * path-qualified plain `Error` if no deformer matches the id.\n * READ/mutate-through accessor, consistent with {@link findPart}. */\n findDeformer(id: string): IkiDeformer {\n const deformer = this.model.deformers?.find((d) => d.id === id);\n if (!deformer) {\n throw new Error(`deformers: no deformer with id \"${id}\"`);\n }\n return deformer;\n }\n\n /** Resolve a physics rig by stable id. Throws a path-qualified plain `Error`\n * if no rig matches the id. The `physics` array is optional, so guard it.\n * READ/mutate-through accessor, consistent with {@link findDeformer}. */\n findPhysicsRig(id: string): IkiPhysics {\n const rig = this.model.physics?.find((r) => r.id === id);\n if (!rig) {\n throw new Error(`physics: no physics rig with id \"${id}\"`);\n }\n return rig;\n }\n\n /**\n * Record the base mesh UVs for a part inserted AFTER construction (e.g. by\n * {@link AddPart}). Returns the PRIOR entry for that id (or `undefined` if\n * none existed) so the caller can restore it on undo. No-op for meshless\n * parts (returns `undefined`). Must be called by any command that pushes a\n * mesh part into the model so the part joins the constructor-captured base-UV\n * side state required by {@link applyAtlas}.\n *\n * The returned prior value is what {@link restoreBaseMeshUvs} expects on\n * undo — pass it verbatim. Hazard guarded: DeletePart(X) → AddPart(X′) →\n * undo(add) → undo(delete); without restore, X's constructor-captured entry\n * would be permanently gone after undo of the add, causing applyAtlas to\n * fail when X is restored.\n */\n captureBaseMeshUvs(partId: string): number[] | undefined {\n const prev = this.baseMeshUvs.get(partId);\n const part = this.model.parts.find((p) => p.id === partId);\n if (part?.mesh) {\n this.baseMeshUvs.set(partId, part.mesh.uvs.slice());\n }\n return prev;\n }\n\n /**\n * Restore the base-UV side state to exactly what it was before a\n * {@link captureBaseMeshUvs} call. Called from {@link AddPart.invert}:\n * pass the value returned by captureBaseMeshUvs on first apply.\n * - `prev` is `number[]` → sets the entry (restores a prior mesh's base).\n * - `prev` is `undefined` → deletes the entry (no entry existed before the\n * add, so a later different-mesh part reusing the id must not inherit this\n * one's base).\n * DeletePart does NOT call this — the entry persists across delete/undo so\n * the restored part still has its base available for applyAtlas.\n */\n restoreBaseMeshUvs(partId: string, prev: number[] | undefined): void {\n if (prev !== undefined) {\n this.baseMeshUvs.set(partId, prev);\n } else {\n this.baseMeshUvs.delete(partId);\n }\n }\n\n /** The construction-captured base UVs for a mesh part. Throws a path-qualified\n * plain `Error` if absent. SINGLE accessor for both apply branches — never\n * read `baseMeshUvs` with a bare `!` elsewhere. */\n private requireBaseUvs(partId: string): number[] {\n const base = this.baseMeshUvs.get(partId);\n if (!base) {\n throw new Error(`parts: no base mesh uvs captured for part \"${partId}\"`);\n }\n return base;\n }\n\n /**\n * Replace the atlas table and rewrite every part's texture reference in a\n * single atomic step. For every part in `partTextureAssignments` set\n * `texture = { index: 0, uv }`; CLEAR `texture` (delete the key) on every\n * other part.\n *\n * Mesh parts are textured as a MATCHED PAIR: an assigned mesh part also has\n * its per-vertex `mesh.uvs` remapped (from the construction-captured base)\n * into the same `uv` rect; an unassigned mesh part has its `mesh.uvs` restored\n * to that base. Quad parts carry `texture.uv` only and are untouched here.\n *\n * Deliberately NON-undoable: it does NOT push to or clear the undo/redo\n * stacks (texture/atlas state is not undoable in 5b — the unified\n * editor-state superset is deferred to 5d). `canUndo()`/`canRedo()` are\n * unchanged after a call.\n *\n * Validate-all-then-apply: structural input validation, per-partId\n * resolution, and a base-UV preflight over every mesh part all run BEFORE any\n * mutation, so a bad input (wrong shape, duplicate partId, unknown partId, or\n * a mesh part with no captured base) throws a plain `Error` and leaves the\n * model exactly as it was — never a partial application.\n */\n applyAtlas(input: ApplyAtlasInput): void {\n // Step A — structural validation of the input shape.\n const { texture, assignmentsByPart } = this.normalizeAtlasInput(input);\n\n // Step B — resolve every partId before mutating anything.\n const resolved = new Map<IkiPart, IkiUvRect>();\n for (const [partId, uv] of assignmentsByPart) {\n resolved.set(this.findPart(partId), uv);\n }\n\n // Step C — preflight base UVs for EVERY mesh part (assigned AND unassigned):\n // the mutate loop's clear/restore branch also reads the base of unassigned\n // mesh parts, so this guard must cover them before any write so atomicity\n // (validate-all-then-apply) holds.\n for (const part of this.model.parts) {\n if (part.mesh) {\n this.requireBaseUvs(part.id);\n }\n }\n\n // Mutate — only reached when A + B + C all pass.\n this.model.textures =\n texture === undefined ? undefined : [{ source: texture.source }];\n for (const part of this.model.parts) {\n const uv = resolved.get(part);\n if (uv) {\n part.texture = {\n index: 0,\n uv: { x: uv.x, y: uv.y, width: uv.width, height: uv.height },\n };\n if (part.mesh) {\n // Replace the mesh object so each part owns its own uvs array —\n // de-aliases parts that shared the same mesh object in the input model.\n part.mesh = {\n ...part.mesh,\n uvs: remapMeshUvsToRect(this.requireBaseUvs(part.id), uv),\n };\n }\n } else {\n delete part.texture;\n if (part.mesh) {\n // Same spread here to break aliasing on restore as well.\n part.mesh = {\n ...part.mesh,\n uvs: this.requireBaseUvs(part.id).slice(),\n };\n }\n }\n }\n }\n\n /**\n * Clear a model-committed texture reference from a single part. Deliberately\n * NON-undoable, matching {@link applyAtlas} — texture/atlas state is not in\n * the undo model (unified editor-state is deferred). Does NOT push to or\n * clear the undo/redo stacks. Does NOT touch `mesh.uvs` — atlas-space UVs on\n * an untextured mesh are inert for color rendering; a later atlas import\n * remaps from the base UVs anyway.\n *\n * This is the single consistent texture/atlas undo boundary: by the time a\n * part is deletable (no texture), its DeletePart snapshot carries no texture\n * reference, so no stale index can resurface on undo after a later atlas\n * repack.\n *\n * Throws a path-qualified plain `Error` (NOT `IkiFormatError`) if the part\n * id is unknown — same contract as {@link findPart}.\n */\n clearPartTextureRef(partId: string): void {\n const part = this.findPart(partId);\n delete part.texture;\n }\n\n /**\n * Validate the structural shape of an {@link ApplyAtlasInput} without\n * touching the model, returning a known-good `{ texture?, assignmentsByPart }`\n * for {@link applyAtlas} to resolve and apply.\n */\n private normalizeAtlasInput(input: ApplyAtlasInput): {\n texture?: IkiTexture;\n assignmentsByPart: Map<string, IkiUvRect>;\n } {\n if (input.textures.length > 1) {\n throw new Error(\n `applyAtlas: textures must be a single atlas page (got ${input.textures.length})`,\n );\n }\n if (\n input.partTextureAssignments.length > 0 &&\n input.textures.length !== 1\n ) {\n throw new Error(\n \"applyAtlas: partTextureAssignments require exactly one texture\",\n );\n }\n\n const assignmentsByPart = new Map<string, IkiUvRect>();\n for (const { partId, uv } of input.partTextureAssignments) {\n if (assignmentsByPart.has(partId)) {\n throw new Error(\n `applyAtlas: duplicate partId \"${partId}\" in partTextureAssignments`,\n );\n }\n assignmentsByPart.set(partId, uv);\n }\n\n return { texture: input.textures[0], assignmentsByPart };\n }\n\n /**\n * Overwrite a part's whole transform with a fresh copy of `transform`.\n * Replacing the whole object (rather than individual channels) preserves any\n * optional keys already absent from the incoming value.\n *\n * Deliberately NON-undoable — used ONLY for an editor app's transient capture\n * pose. Does NOT push to or clear undoStack/redoStack (sibling to\n * {@link applyAtlas}'s non-undoable boundary). The caller is responsible for\n * restoring the exact prior snapshot when the capture pose ends.\n *\n * `IkiTransform` is a flat number map, so a shallow spread is a sufficient\n * deep copy — no aliasing of the caller's object remains.\n */\n setPartTransformEphemeral(partId: string, transform: IkiTransform): void {\n const part = this.findPart(partId);\n part.transform = { ...transform };\n }\n\n /**\n * Overwrite a matrix deformer's optional transform with a fresh copy, or\n * delete it when `transform` is `undefined`.\n * `undefined` deletes the key, restoring the deformer to the same state as\n * one that never had a transform (absent-vs-present matters for downstream\n * renderers).\n *\n * Deliberately NON-undoable — used ONLY for an editor app's transient capture\n * pose. Does NOT push to or clear undoStack/redoStack (sibling to\n * {@link applyAtlas}'s non-undoable boundary). The caller is responsible for\n * restoring the exact prior snapshot when the capture pose ends.\n *\n * `IkiDeformerTransform` is a flat number map, so a shallow spread is a\n * sufficient deep copy — no aliasing of the caller's object remains.\n */\n setDeformerTransformEphemeral(\n deformerId: string,\n transform: IkiDeformerTransform | undefined,\n ): void {\n const deformer = this.findMatrixDeformer(deformerId);\n if (transform === undefined) {\n delete deformer.transform;\n } else {\n deformer.transform = { ...transform };\n }\n }\n\n /**\n * Overwrite a part's whole bindings array with a fresh deep copy, or delete\n * the key when `bindings` is empty.\n *\n * Deliberately NON-undoable — used ONLY for an editor app's transient capture\n * neutralization (zeroing the row being recaptured so the preview reflects\n * base-only during posing). Does NOT push to or clear undoStack/redoStack.\n * The caller is responsible for restoring the original bindings when the\n * capture session ends.\n */\n setPartBindingsEphemeral(partId: string, bindings: IkiBinding[]): void {\n const part = this.findPart(partId);\n if (bindings.length > 0) {\n part.bindings = bindings.map((b) => ({ ...b }));\n } else {\n delete part.bindings;\n }\n }\n\n /**\n * Overwrite a matrix deformer's whole bindings array with a fresh deep copy,\n * or delete the key when `bindings` is empty.\n *\n * Deliberately NON-undoable — used ONLY for an editor app's transient capture\n * neutralization (zeroing the row being recaptured so the preview reflects\n * base-only during posing). Does NOT push to or clear undoStack/redoStack.\n * The caller is responsible for restoring the original bindings when the\n * capture session ends.\n */\n setDeformerBindingsEphemeral(\n deformerId: string,\n bindings: IkiDeformerBinding[],\n ): void {\n const deformer = this.findMatrixDeformer(deformerId);\n if (bindings.length > 0) {\n deformer.bindings = bindings.map((b) => ({ ...b }));\n } else {\n delete deformer.bindings;\n }\n }\n\n /** Apply a command and record it as one undo step. Clears the redo stack. */\n execute(cmd: EditCommand): void {\n cmd.apply(this);\n this.undoStack.push(cmd);\n this.redoStack.length = 0;\n }\n\n /** Invert the most recent command and move it onto the redo stack. */\n undo(): void {\n const cmd = this.undoStack.pop();\n if (!cmd) return;\n cmd.invert(this);\n this.redoStack.push(cmd);\n }\n\n /** Re-apply the most recently undone command and move it back onto undo. */\n redo(): void {\n const cmd = this.redoStack.pop();\n if (!cmd) return;\n cmd.apply(this);\n this.undoStack.push(cmd);\n }\n\n canUndo(): boolean {\n return this.undoStack.length > 0;\n }\n\n canRedo(): boolean {\n return this.redoStack.length > 0;\n }\n\n /**\n * Validate and export the current working model by running it through\n * {@link parseIkiModel}. Uses `structuredClone` so the validator's\n * normalized output cannot alias the working model. Propagates\n * `IkiFormatError` unchanged on failure — callers surface `.message`.\n */\n toIkiModel(): IkiModel {\n return parseIkiModel(structuredClone(this.model));\n }\n\n /**\n * Pretty-print the validated model as a `.iki` JSON string. Always\n * validates first — invalid documents never reach a file.\n */\n serialize(): string {\n return JSON.stringify(this.toIkiModel(), null, 2);\n }\n}\n","import type { IkiUvRect } from \"@ikijs/format\";\n\n/**\n * Affinely map a mesh's BASE local UVs into an atlas sub-rectangle.\n *\n * Per the `@ikijs/format` UV convention (top-left origin, +y down, 0..1; see\n * {@link IkiUvRect}/{@link IkiMesh}), the input is the part's BASE local uvs and\n * the output places each component into `rect` with NO flip — base UV space and\n * `rect` share the same orientation:\n * out[2i] = rect.x + baseUvs[2i] * rect.width\n * out[2i+1] = rect.y + baseUvs[2i+1] * rect.height\n *\n * The caller guarantees `rect` is inset/clamped (via `uvRectFor`) so every\n * output component stays within 0..1 and passes the format validator.\n *\n * Returns a fresh array; `baseUvs` is never mutated.\n */\nexport function remapMeshUvsToRect(\n baseUvs: number[],\n rect: IkiUvRect,\n): number[] {\n if (baseUvs.length % 2 !== 0) {\n throw new Error(\n `remapMeshUvsToRect: baseUvs must have an even length (u,v pairs), got ${baseUvs.length}`,\n );\n }\n\n const out = new Array<number>(baseUvs.length);\n for (let i = 0; i < baseUvs.length; i += 2) {\n out[i] = rect.x + baseUvs[i] * rect.width;\n out[i + 1] = rect.y + baseUvs[i + 1] * rect.height;\n }\n return out;\n}\n","import type {\n IkiBinding,\n IkiDeformer,\n IkiDeformerBinding,\n IkiDeformerTransform,\n IkiGridKeyform,\n IkiMatrixDeformer,\n IkiMesh,\n IkiModel,\n IkiPart,\n IkiPhysics,\n} from \"@ikijs/format\";\nimport {\n IKI_FORMAT_VERSION,\n IkiFormatError,\n parseIkiModel,\n} from \"@ikijs/format\";\n\nimport type { EditorDocument } from \"./document\";\nimport { upsertGridKeyform } from \"./grid-keyform\";\nimport { remapMeshUvsToRect } from \"./mesh-uv\";\nimport {\n validateDeformerDelete,\n validateDeformerReparent,\n validatePartAttach,\n} from \"./reparent\";\n\n/**\n * Scan the id-flat namespace (parts first, then deformers) and return which\n * array already holds `id`, or `undefined` if the id is free. Parts and\n * deformers share a single flat id namespace, so both arrays must be checked\n * to produce a source-qualified collision message.\n */\nfunction findIdCollision(\n model: IkiModel,\n id: string,\n): \"part\" | \"deformer\" | undefined {\n for (const p of model.parts) {\n if (p.id === id) return \"part\";\n }\n for (const d of model.deformers ?? []) {\n if (d.id === id) return \"deformer\";\n }\n return undefined;\n}\n\n/**\n * One invertible edit. The document is passed IN at apply/invert time — a\n * command is constructed from `(partId, value)` alone, before any document\n * exists — so the same command object can be applied, inverted, and re-applied\n * by the undo/redo stack.\n *\n * Prior-value capture happens exactly ONCE, on the first {@link apply}. `redo`\n * (a second `apply`) reuses that captured value rather than re-reading the\n * current field, so undo always restores the original target.\n */\nexport interface EditCommand {\n apply(doc: EditorDocument): void;\n invert(doc: EditorDocument): void;\n readonly label: string;\n}\n\n/** Channels of {@link IkiTransform} this editor can edit (object-field names,\n * NOT the binding `IkiTransformChannel` vocabulary). */\nexport type EditTransformChannel =\n | \"x\"\n | \"y\"\n | \"rotation\"\n | \"scaleX\"\n | \"scaleY\"\n | \"opacity\";\n\n/**\n * Generic single-field command: reads/writes one field of the resolved part via\n * a getter/setter closure, capturing the prior value on the first `apply` and\n * restoring it on `invert`. `T` is the captured value's type; for cloned values\n * (the color tuple) the getter/setter perform the copy.\n */\nclass FieldCommand<T> implements EditCommand {\n readonly label: string;\n private captured = false;\n private prevValue!: T;\n\n constructor(\n private readonly partId: string,\n private readonly newValue: T,\n label: string,\n private readonly get: (part: IkiPart) => T,\n private readonly set: (part: IkiPart, value: T) => void,\n ) {\n this.label = label;\n }\n\n apply(doc: EditorDocument): void {\n const part = doc.findPart(this.partId);\n if (!this.captured) {\n this.prevValue = this.get(part);\n this.captured = true;\n }\n this.set(part, this.newValue);\n }\n\n invert(doc: EditorDocument): void {\n const part = doc.findPart(this.partId);\n this.set(part, this.prevValue);\n }\n}\n\n/** Edit a part's RGBA fill. The 4-tuple is mutable, so the command clones on\n * construction (caller's array), on capture (part's current color), and on\n * assign (writing to the part) — it never retains the caller's or model's\n * array by reference. */\nexport class SetPartColor extends FieldCommand<\n [number, number, number, number]\n> {\n constructor(partId: string, rgba: [number, number, number, number]) {\n super(\n partId,\n [...rgba] as [number, number, number, number],\n \"Set color\",\n (part) => [...part.color],\n (part, value) => {\n part.color = [...value] as [number, number, number, number];\n },\n );\n }\n}\n\n/** Edit a part's width (model-space units). */\nexport class SetPartWidth extends FieldCommand<number> {\n constructor(partId: string, value: number) {\n super(\n partId,\n value,\n \"Set width\",\n (part) => part.width,\n (part, v) => {\n part.width = v;\n },\n );\n }\n}\n\n/** Edit a part's height (model-space units). */\nexport class SetPartHeight extends FieldCommand<number> {\n constructor(partId: string, value: number) {\n super(\n partId,\n value,\n \"Set height\",\n (part) => part.height,\n (part, v) => {\n part.height = v;\n },\n );\n }\n}\n\n/** Edit a part's paint order. */\nexport class SetPartOrder extends FieldCommand<number> {\n constructor(partId: string, value: number) {\n super(\n partId,\n value,\n \"Set order\",\n (part) => part.order,\n (part, v) => {\n part.order = v;\n },\n );\n }\n}\n\n/**\n * Edit one channel of a part's base transform. `x`/`y` are required; the rest\n * are optional and may be absent on the part. For an optional channel the\n * command captures the raw current value INCLUDING `undefined`, and restoring\n * `undefined` DELETES the key so undo returns the part to its original\n * (possibly-omitted) shape. Engine defaults (rotation 0 / scale 1 / opacity 1)\n * are NOT substituted here.\n */\nexport class SetPartTransform extends FieldCommand<number | undefined> {\n constructor(partId: string, channel: EditTransformChannel, value: number) {\n super(\n partId,\n value,\n \"Set transform\",\n (part) => part.transform[channel],\n (part, v) => {\n if (v === undefined) {\n delete part.transform[channel];\n } else {\n part.transform[channel] = v;\n }\n },\n );\n }\n}\n\n/**\n * Capture the warp deformer's grid as one keyform at the driving parameter\n * `value`, upserting `offsets` into `warps[0].keyforms`. The 4-tuple-style\n * mutable `offsets` array is cloned on construction so a later caller mutation\n * cannot corrupt apply/redo (mirrors {@link SetPartColor}).\n *\n * `apply` validates BEFORE mutating: the deformer must have a grid warp, the\n * offsets length must equal `grid.points.length`, and `value` must lie within\n * the driving parameter's declared `[min,max]` (fail fast, before\n * `parseIkiModel` would reject it). Prior keyforms are deep-copied once on the\n * first `apply` (capture-once like {@link FieldCommand}); `invert` restores\n * that deep copy so re-apply after undo never aliases.\n */\nexport class CaptureGridKeyform implements EditCommand {\n readonly label = \"Capture grid keyform\";\n private readonly offsets: number[];\n private captured = false;\n private prevKeyforms!: IkiGridKeyform[];\n\n constructor(\n private readonly deformerId: string,\n private readonly value: number,\n offsets: number[],\n ) {\n this.offsets = [...offsets];\n }\n\n apply(doc: EditorDocument): void {\n const deformer = doc.findWarpDeformer(this.deformerId);\n const warp = deformer.warps?.[0];\n if (!warp) {\n throw new Error(\n `deformers.\"${this.deformerId}\".warps: no grid warp to capture into`,\n );\n }\n if (this.offsets.length !== deformer.grid.points.length) {\n throw new Error(\n `deformers.\"${this.deformerId}\".warps[0].keyforms.offsets length ${this.offsets.length} must equal grid.points length ${deformer.grid.points.length}`,\n );\n }\n const param = doc\n .getModel()\n .parameters.find((p) => p.id === warp.parameter);\n if (!param) {\n throw new Error(\n `deformers.\"${this.deformerId}\".warps[0].parameter \"${warp.parameter}\" is not a declared parameter`,\n );\n }\n if (this.value < param.min || this.value > param.max) {\n throw new Error(\n `deformers.\"${this.deformerId}\".warps[0].keyforms.value ${this.value} is outside parameter \"${warp.parameter}\" range [${param.min},${param.max}]`,\n );\n }\n\n if (!this.captured) {\n this.prevKeyforms = structuredClone(warp.keyforms);\n this.captured = true;\n }\n warp.keyforms = upsertGridKeyform(warp.keyforms, this.value, [\n ...this.offsets,\n ]);\n }\n\n invert(doc: EditorDocument): void {\n const warp = doc.findWarpDeformer(this.deformerId).warps?.[0];\n if (!warp) {\n throw new Error(\n `deformers.\"${this.deformerId}\".warps: no grid warp to restore into`,\n );\n }\n warp.keyforms = structuredClone(this.prevKeyforms);\n }\n}\n\n/**\n * Generic single-field command targeting a matrix deformer, mirroring\n * {@link FieldCommand} but resolving via `doc.findMatrixDeformer` instead of\n * `doc.findPart`. Capture-once on first `apply`; restore on `invert`.\n */\nclass DeformerFieldCommand<T> implements EditCommand {\n readonly label: string;\n private captured = false;\n private prevValue!: T;\n\n constructor(\n private readonly deformerId: string,\n private readonly newValue: T,\n label: string,\n private readonly get: (deformer: IkiMatrixDeformer) => T,\n private readonly set: (deformer: IkiMatrixDeformer, value: T) => void,\n ) {\n this.label = label;\n }\n\n apply(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n if (!this.captured) {\n this.prevValue = this.get(deformer);\n this.captured = true;\n }\n this.set(deformer, this.newValue);\n }\n\n invert(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n this.set(deformer, this.prevValue);\n }\n}\n\n/** Edit a matrix deformer's pivot x. */\nexport class SetDeformerPivotX extends DeformerFieldCommand<number> {\n constructor(deformerId: string, value: number) {\n super(\n deformerId,\n value,\n \"Set pivot x\",\n (d) => d.pivot.x,\n (d, v) => {\n d.pivot.x = v;\n },\n );\n }\n}\n\n/** Edit a matrix deformer's pivot y. */\nexport class SetDeformerPivotY extends DeformerFieldCommand<number> {\n constructor(deformerId: string, value: number) {\n super(\n deformerId,\n value,\n \"Set pivot y\",\n (d) => d.pivot.y,\n (d, v) => {\n d.pivot.y = v;\n },\n );\n }\n}\n\n/**\n * Set a matrix deformer's pivot x and y atomically (one drag = one undo step).\n * {@link SetDeformerPivotX} and {@link SetDeformerPivotY} remain for the\n * Inspector's single-axis number inputs.\n */\nexport class SetDeformerPivot implements EditCommand {\n readonly label = \"Set pivot\";\n private captured = false;\n private prevPivot!: { x: number; y: number };\n private readonly pivot: { x: number; y: number };\n\n constructor(\n private readonly deformerId: string,\n pivot: { x: number; y: number },\n ) {\n // Fresh clone so a caller mutating their arg after construction cannot\n // corrupt apply/redo.\n this.pivot = { x: pivot.x, y: pivot.y };\n }\n\n apply(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n if (!this.captured) {\n this.prevPivot = { x: deformer.pivot.x, y: deformer.pivot.y };\n this.captured = true;\n }\n deformer.pivot = { x: this.pivot.x, y: this.pivot.y };\n }\n\n invert(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n // Fresh clone — never alias the captured object so repeated undo/redo\n // cycles cannot corrupt the saved prior value.\n deformer.pivot = { x: this.prevPivot.x, y: this.prevPivot.y };\n }\n}\n\n/** Channels of {@link IkiDeformerTransform} this editor can edit. */\nexport type DeformerTransformChannel =\n | \"x\"\n | \"y\"\n | \"rotation\"\n | \"scaleX\"\n | \"scaleY\";\n\n/**\n * Edit one channel of a matrix deformer's base transform. Because\n * {@link IkiDeformerTransform} REQUIRES finite `x` and `y`, this command\n * captures and restores the WHOLE prior `transform` object (present or absent)\n * rather than a single channel, so undo can delete the transform when it did\n * not previously exist, and redo never produces a partial object missing `x`/`y`.\n *\n * When no `transform` is present on the deformer, `apply` creates one from\n * the identity base `{ x: 0, y: 0 }` — the minimal valid shape the validator\n * accepts — then writes the edited channel. For example, editing `rotation`\n * on a transform-less deformer yields `{ x: 0, y: 0, rotation: <value> }`.\n */\nexport class SetDeformerTransform implements EditCommand {\n readonly label = \"Set deformer transform\";\n private captured = false;\n private prevTransform!: IkiDeformerTransform | undefined;\n\n constructor(\n private readonly deformerId: string,\n private readonly channel: DeformerTransformChannel,\n private readonly value: number,\n ) {}\n\n apply(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n if (!this.captured) {\n // Shallow clone is sufficient — IkiDeformerTransform is a flat number map.\n this.prevTransform =\n deformer.transform === undefined\n ? undefined\n : { ...deformer.transform };\n this.captured = true;\n }\n // Start from the existing transform or the identity base. The identity base\n // is { x: 0, y: 0 } because the validator unconditionally requires finite\n // x and y whenever a transform object is present.\n const next: IkiDeformerTransform = {\n ...(deformer.transform ?? { x: 0, y: 0 }),\n };\n next[this.channel] = this.value;\n deformer.transform = next;\n }\n\n invert(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n if (this.prevTransform === undefined) {\n delete deformer.transform;\n } else {\n // Assign a fresh clone — never alias the captured object so repeated\n // undo/redo cycles cannot corrupt the saved prior value.\n deformer.transform = { ...this.prevTransform };\n }\n }\n}\n\n/**\n * Replace a matrix deformer's `bindings` array wholesale. A single command\n * covers add, edit, and remove (pass the desired final array; pass `[]` to\n * remove all). Mirrors {@link CaptureGridKeyform}'s deep-copy discipline:\n * clone-on-construction, capture-once, fresh deep copy on invert.\n *\n * Each {@link IkiDeformerBinding} is a flat object so a per-element spread\n * `{ ...b }` is a sufficient deep copy.\n */\nexport class SetDeformerBindings implements EditCommand {\n readonly label = \"Set deformer bindings\";\n private readonly bindings: IkiDeformerBinding[];\n private captured = false;\n private prevBindings!: IkiDeformerBinding[] | undefined;\n\n constructor(\n private readonly deformerId: string,\n bindings: IkiDeformerBinding[],\n ) {\n // Clone the caller's array on construction — prevents post-execute mutation\n // of the caller's array from corrupting apply/redo.\n this.bindings = bindings.map((b) => ({ ...b }));\n }\n\n apply(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n\n // Validate a NARROW synthetic candidate before capture/mutate, exactly as\n // {@link SetPartBindings} does and for the same reason: a full\n // structuredClone(doc.getModel()) would also validate parts and deformers\n // the user is not editing, and those routinely hold in-flight `NaN` from an\n // emptied numeric input — so an unrelated blank field would refuse this\n // edit, and report it against the wrong object. The Inspector offers every\n // parameter in the model, so an undeclared id, a non-finite endpoint, or a\n // non-matrix channel must still be caught here.\n //\n // NOT covered by this shape: a binding that feeds a physics chain's own\n // anchor. That rule needs the whole deformer hierarchy, and reproducing it\n // over a sanitized model would mean hand-maintaining a second model shape\n // beside the validator. It stays an export-time check in `toIkiModel()`.\n // Parts and deformers share one flat id namespace, so the two synthetic\n // objects below must not both be \"_\".\n const candidateDeformer: Record<string, unknown> = {\n kind: \"matrix\",\n id: \"_d\",\n pivot: { x: 0, y: 0 },\n };\n if (this.bindings.length > 0) {\n candidateDeformer.bindings = this.bindings.map((b) => ({ ...b }));\n }\n const candidate = {\n version: IKI_FORMAT_VERSION,\n name: \"_\",\n canvas: { width: 1, height: 1 },\n parameters: doc.getModel().parameters,\n parts: [\n {\n id: \"_\",\n color: [0, 0, 0, 1],\n width: 1,\n height: 1,\n transform: { x: 0, y: 0 },\n order: 0,\n },\n ],\n deformers: [candidateDeformer],\n };\n // The synthetic deformer is always at deformers[0]; rewrite that prefix so\n // the surfaced error names the real target.\n try {\n parseIkiModel(candidate);\n } catch (e) {\n if (e instanceof IkiFormatError) {\n throw new IkiFormatError(\n e.message.replace(\n /^deformers\\[0\\]/,\n `deformers.\"${this.deformerId}\"`,\n ),\n );\n }\n throw e;\n }\n\n if (!this.captured) {\n // Preserve the original absent-vs-empty distinction.\n this.prevBindings =\n deformer.bindings === undefined\n ? undefined\n : deformer.bindings.map((b) => ({ ...b }));\n this.captured = true;\n }\n if (this.bindings.length > 0) {\n // Assign a fresh deep copy — model must never alias the command's stored array.\n deformer.bindings = this.bindings.map((b) => ({ ...b }));\n } else {\n // Delete the key on empty: keeps the model shape minimal and correctly\n // represents \"no bindings\" as an absent key rather than an empty array.\n delete deformer.bindings;\n }\n }\n\n invert(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n if (this.prevBindings === undefined) {\n delete deformer.bindings;\n } else {\n // Assign a fresh deep copy — never alias the captured array so re-apply\n // after undo cannot corrupt the saved prior value.\n deformer.bindings = this.prevBindings.map((b) => ({ ...b }));\n }\n }\n}\n\n/**\n * Reparent a deformer (matrix or warp) under a new parent, or promote it to\n * root (`newParentId === undefined`). Calls {@link validateDeformerReparent}\n * FIRST so invalid reparents (cycles, warp parent, unknown id) throw before any\n * capture or mutation — a throwing apply leaves the model and undo stack\n * untouched.\n *\n * Absent-vs-present distinction: captures both the prior `parent` value AND\n * whether the key was present on the object, so `invert` can delete the key\n * (restore \"absent\") rather than blindly assigning `undefined`.\n */\nexport class SetDeformerParent implements EditCommand {\n readonly label = \"Set deformer parent\";\n private captured = false;\n private prevParent: string | undefined = undefined;\n private prevHadParent = false;\n\n constructor(\n private readonly deformerId: string,\n private readonly newParentId: string | undefined,\n ) {}\n\n apply(doc: EditorDocument): void {\n // Validate FIRST — throws before capture/mutate on any violation.\n validateDeformerReparent(\n doc.getModel().deformers ?? [],\n this.deformerId,\n this.newParentId,\n );\n\n // A new ANCESTOR can still bind a parameter that a physics chain anchored\n // here emits, which the format rejects as feedback. That rule needs the\n // whole hierarchy, and validating a full clone of the document here refuses\n // the edit whenever any unrelated part or deformer holds in-flight `NaN`\n // from an emptied numeric input — reporting it against the wrong object.\n // So the feedback case stays an export-time check in `toIkiModel()`, where\n // it was before, rather than trading a rare corruption for a common block.\n //\n // Known cost of that trade: the host surfaces the export error (the editor\n // app re-exports on a ~200ms debounce), but the message names the CHAIN, not\n // the edit — reparent `headDeformer` and you get\n // `physicsChains[0].segments[1].output.parameter \"...\" feeds its own anchor\n // deformer chain (feedback)`, which reads as a complaint about a hair lock.\n // If a friendlier error surface ever lands, this is the message to special-case.\n const deformer = doc.findDeformer(this.deformerId);\n if (!this.captured) {\n this.prevHadParent = Object.prototype.hasOwnProperty.call(\n deformer,\n \"parent\",\n );\n this.prevParent = deformer.parent;\n this.captured = true;\n }\n if (this.newParentId !== undefined) {\n deformer.parent = this.newParentId;\n } else {\n delete deformer.parent;\n }\n }\n\n invert(doc: EditorDocument): void {\n const deformer = doc.findDeformer(this.deformerId);\n if (this.prevHadParent) {\n deformer.parent = this.prevParent;\n } else {\n delete deformer.parent;\n }\n }\n}\n\n/**\n * Add a new part to the model. Validates the candidate model with\n * {@link parseIkiModel} BEFORE mutating, so a structurally invalid part\n * (bad color tuple, missing required fields, id collision with a deformer)\n * throws an `IkiFormatError` and leaves the model untouched.\n *\n * Captures the prior base-UV entry for the part's id on the FIRST apply so\n * invert can restore the exact pre-add state. This guards the id-reuse hazard:\n * DeletePart(X) → AddPart(X′, different mesh) → undo(add) → undo(delete) —\n * without restore, X's constructor-captured base would be gone, causing\n * applyAtlas to fail when X is restored by the undo of the delete.\n */\nexport class AddPart implements EditCommand {\n readonly label = \"Add part\";\n private readonly part: IkiPart;\n private captured = false;\n private prevBaseMeshUvs: number[] | undefined = undefined;\n\n constructor(part: IkiPart) {\n // Clone on construction — prevents caller mutation from corrupting apply/redo.\n this.part = structuredClone(part);\n }\n\n apply(doc: EditorDocument): void {\n // (a) Cheap id-uniqueness pre-check with a source-qualified message.\n const hit = findIdCollision(doc.getModel(), this.part.id);\n if (hit === \"part\") {\n throw new Error(\n `parts: id \"${this.part.id}\" collides with an existing part id`,\n );\n }\n if (hit === \"deformer\") {\n throw new Error(\n `parts: id \"${this.part.id}\" collides with an existing deformer id`,\n );\n }\n\n // (b) Full structural validation on a candidate clone — propagate\n // IkiFormatError unchanged so the caller gets a path-qualified message.\n const candidate = structuredClone(doc.getModel());\n candidate.parts.push(structuredClone(this.part));\n parseIkiModel(candidate);\n\n // (c) All checks pass — mutate the real model with a fresh clone so the\n // model never aliases the command's stored part.\n doc.getModel().parts.push(structuredClone(this.part));\n\n // Register base mesh UVs so applyAtlas can remap this part. Capture the\n // prior entry on the FIRST apply only — redo must NOT re-capture or it\n // would clobber the saved prior and make invert unable to restore correctly.\n const prev = doc.captureBaseMeshUvs(this.part.id);\n if (!this.captured) {\n this.prevBaseMeshUvs = prev;\n this.captured = true;\n }\n }\n\n invert(doc: EditorDocument): void {\n const parts = doc.getModel().parts;\n const i = parts.findIndex((p) => p.id === this.part.id);\n if (i !== -1) parts.splice(i, 1);\n // Restore the exact prior base-UV state so a constructor-captured entry for\n // a deleted part with this id survives the undo of the add.\n doc.restoreBaseMeshUvs(this.part.id, this.prevBaseMeshUvs);\n }\n}\n\n/**\n * Add a new deformer (matrix or warp) to the model. Validates the candidate\n * model with {@link parseIkiModel} BEFORE mutating — this enforces the warp\n * rest-grid invariant, points length, pivot, parent, and bindings without\n * hand-rolling partial checks. Mirrors {@link AddPart} but also tracks whether\n * `model.deformers` was absent before the first apply, so `invert` can restore\n * the exact key-absence state (mirrors {@link SetDeformerBindings}).\n */\nexport class AddDeformer implements EditCommand {\n readonly label = \"Add deformer\";\n private readonly deformer: IkiDeformer;\n private captured = false;\n private prevDeformersAbsent = false;\n\n constructor(deformer: IkiDeformer) {\n // Clone on construction — prevents caller mutation from corrupting apply/redo.\n this.deformer = structuredClone(deformer);\n }\n\n apply(doc: EditorDocument): void {\n const model = doc.getModel();\n\n // (a) Cheap id-uniqueness pre-check with a source-qualified message.\n const hit = findIdCollision(model, this.deformer.id);\n if (hit === \"deformer\") {\n throw new Error(\n `deformers: id \"${this.deformer.id}\" collides with an existing deformer id`,\n );\n }\n if (hit === \"part\") {\n throw new Error(\n `deformers: id \"${this.deformer.id}\" collides with an existing part id`,\n );\n }\n\n // (b) Full structural validation on a candidate clone.\n const candidate = structuredClone(model);\n candidate.deformers = [\n ...(candidate.deformers ?? []),\n structuredClone(this.deformer),\n ];\n parseIkiModel(candidate);\n\n // (c) All checks pass — capture-once, then mutate.\n if (!this.captured) {\n this.prevDeformersAbsent = model.deformers === undefined;\n this.captured = true;\n }\n if (model.deformers === undefined) {\n model.deformers = [];\n }\n model.deformers.push(structuredClone(this.deformer));\n }\n\n invert(doc: EditorDocument): void {\n const model = doc.getModel();\n const arr = model.deformers;\n if (!arr) return;\n const i = arr.findIndex((d) => d.id === this.deformer.id);\n if (i !== -1) arr.splice(i, 1);\n // Restore the absent-vs-present distinction. If deformers did not exist\n // before apply, delete the key once the array is empty again.\n if (this.prevDeformersAbsent && arr.length === 0) {\n delete model.deformers;\n }\n }\n}\n\n/**\n * Delete a part by id, preserving its original array slot so `invert` restores\n * it at the same position. Slot position is cosmetic (the renderer uses the\n * `order` field for paint ordering), but restoring the index keeps undo\n * visually predictable.\n *\n * No `parseIkiModel` pre-check is needed: removing an element from an already-\n * valid model cannot introduce a structural violation — EXCEPT for clip-mask\n * references, the one part→part reference in the contract. `apply` refuses to\n * delete a part still used as another part's `clip.masks` entry (guard below),\n * so it can never leave a dangling mask ref that would fail `toIkiModel()`.\n *\n * Texture-reference safety (package invariant): `apply` refuses to delete a\n * part that still carries `part.texture`. Texture/atlas state is non-undoable\n * per the 5b boundary; clear the texture first via\n * {@link EditorDocument.clearPartTextureRef} (model-committed) or\n * {@link EditorDocument.applyAtlas} with no assignment (imported) — both are\n * non-undoable. By the time a part is deletable it carries no texture, so\n * `invert` can never restore a stale texture index that would render the wrong\n * atlas region after a later atlas repack. No transactional atlas capture is\n * needed in this command.\n */\nexport class DeletePart implements EditCommand {\n readonly label = \"Delete part\";\n private captured = false;\n private removed!: IkiPart;\n private index!: number;\n\n constructor(private readonly partId: string) {}\n\n apply(doc: EditorDocument): void {\n // Validate FIRST — throws with path-qualified message if unknown.\n const part = doc.findPart(this.partId);\n\n // Texture guard — enforced at the `@ikijs/editor` boundary so public callers\n // cannot bypass it (the example store adds a friendly pre-check, but this\n // is the real invariant). Throw before any capture or mutation.\n if (part.texture !== undefined) {\n throw new Error(\n `parts.\"${this.partId}\": cannot delete — part has a texture reference; clear its texture first`,\n );\n }\n\n const parts = doc.getModel().parts;\n\n // Clip-mask guard — `clip.masks` is the one part→part reference in the model\n // contract. Deleting a referenced mask would leave a dangling ref that fails\n // parseIkiModel on toIkiModel(). Refuse rather than corrupt (mirrors the\n // texture guard above). Throw before any capture or mutation.\n const masker = parts.find(\n (p) => p.id !== this.partId && p.clip?.masks.includes(this.partId),\n );\n if (masker) {\n throw new Error(\n `parts.\"${this.partId}\": cannot delete — used as a clip mask by part \"${masker.id}\"; remove its clip first`,\n );\n }\n\n const i = parts.indexOf(part);\n if (!this.captured) {\n this.removed = structuredClone(part);\n this.index = i;\n this.captured = true;\n }\n parts.splice(i, 1);\n }\n\n invert(doc: EditorDocument): void {\n // Restore at the original slot — exact deep restore including bindings and\n // mesh. No texture key is present (apply enforced that invariant before\n // capture), so the snapshot is always atlas-safe on restore.\n doc.getModel().parts.splice(this.index, 0, structuredClone(this.removed));\n }\n}\n\n/**\n * Delete a deformer by id. Calls {@link validateDeformerDelete} FIRST so the\n * delete is refused while anything still references it — a child deformer, an\n * attached part, or a physics chain anchored to it — enforcing the same\n * referential safety as {@link SetDeformerParent} and {@link SetPartDeformer}.\n *\n * `invert` re-inserts the deformer at its original index.\n */\nexport class DeleteDeformer implements EditCommand {\n readonly label = \"Delete deformer\";\n private captured = false;\n private removed!: IkiDeformer;\n private index!: number;\n\n constructor(private readonly deformerId: string) {}\n\n apply(doc: EditorDocument): void {\n const model = doc.getModel();\n // Validate FIRST — throws before capture/mutate on any referential violation.\n validateDeformerDelete(\n model.deformers ?? [],\n model.parts,\n model.physicsChains ?? [],\n this.deformerId,\n );\n // validateDeformerDelete guarantees the deformer (and thus the array) exists.\n const arr = model.deformers!;\n const i = arr.findIndex((d) => d.id === this.deformerId);\n if (!this.captured) {\n this.removed = structuredClone(arr[i]);\n this.index = i;\n this.captured = true;\n }\n arr.splice(i, 1);\n }\n\n invert(doc: EditorDocument): void {\n // Re-insert at the original slot. `apply` only captures after\n // validateDeformerDelete passed, so the array it spliced out of is present:\n // fabricating one here would hide a broken undo stack and silently restore\n // just this node instead of surfacing the loss of the rest of the hierarchy.\n doc\n .getModel()\n .deformers!.splice(this.index, 0, structuredClone(this.removed));\n }\n}\n\n/**\n * Replace a part's `bindings` array wholesale. A single command covers add,\n * edit, and remove (pass the desired final array; pass `[]` to remove all).\n * Mirrors {@link SetDeformerBindings}'s deep-copy and absent-vs-empty discipline:\n * clone-on-construction, capture-once, fresh deep copy on every assign/invert.\n *\n * Each {@link IkiBinding} is a flat object, so a per-element spread `{ ...b }`\n * is a sufficient deep copy.\n *\n * Validates the WRITTEN bindings against the declared parameters via a narrow\n * synthetic {@link parseIkiModel} candidate (so unrelated in-flight invalid\n * editor state — e.g. a NaN width on another part — cannot false-reject a\n * binding edit). Validation runs BEFORE any mutation; on failure the model and\n * undo stack are left untouched.\n *\n * Empty bindings → omit the `bindings` key on the candidate AND delete\n * `part.bindings` on apply (keeps the model shape minimal; represents \"no\n * bindings\" as an absent key rather than an empty array).\n */\nexport class SetPartBindings implements EditCommand {\n readonly label = \"Set part bindings\";\n private readonly bindings: IkiBinding[];\n private captured = false;\n private prevBindings!: IkiBinding[] | undefined;\n\n constructor(\n private readonly partId: string,\n bindings: IkiBinding[],\n ) {\n // Clone the caller's array on construction — prevents post-execute mutation\n // of the caller's array from corrupting apply/redo.\n this.bindings = bindings.map((b) => ({ ...b }));\n }\n\n apply(doc: EditorDocument): void {\n // Build a narrow synthetic candidate carrying only the validation-relevant\n // context. A full structuredClone(doc.getModel()) is deliberately avoided\n // because unrelated parts may carry NaN in-flight values (e.g. from\n // NumberField.valueAsNumber), which would cause false-positive validation\n // failures. The synthetic model is the minimal shape parseIkiModel accepts.\n const candidatePart: Record<string, unknown> = {\n id: \"_\",\n color: [0, 0, 0, 1],\n width: 1,\n height: 1,\n transform: { x: 0, y: 0 },\n order: 0,\n };\n // Omit the bindings key entirely when empty — \"no bindings\" is represented\n // as key absence; an empty array is not a valid value in the format contract.\n if (this.bindings.length > 0) {\n candidatePart.bindings = this.bindings.map((b) => ({ ...b }));\n }\n const candidate = {\n version: IKI_FORMAT_VERSION,\n name: \"_\",\n canvas: { width: 1, height: 1 },\n parameters: doc.getModel().parameters,\n parts: [candidatePart],\n };\n // Validation before any mutation. The synthetic candidate always places the\n // part at parts[0], so the validator emits paths like \"parts[0].bindings[i]\".\n // Rewrite that prefix to name the real target so the surfaced error is\n // actionable (\"parts.\"part-a\".bindings[i]\" rather than \"parts[0].bindings[i]\").\n try {\n parseIkiModel(candidate);\n } catch (e) {\n if (e instanceof IkiFormatError) {\n throw new IkiFormatError(\n e.message.replace(/^parts\\[0\\]/, `parts.\"${this.partId}\"`),\n );\n }\n throw e;\n }\n\n // Resolution after validation so an unknown partId throws with a\n // path-qualified message (findPart throws) but only after the bindings\n // themselves are confirmed structurally valid.\n const part = doc.findPart(this.partId);\n if (!this.captured) {\n // Preserve the original absent-vs-empty distinction.\n this.prevBindings =\n part.bindings === undefined\n ? undefined\n : part.bindings.map((b) => ({ ...b }));\n this.captured = true;\n }\n if (this.bindings.length > 0) {\n // Assign a fresh deep copy — model must never alias the command's stored array.\n part.bindings = this.bindings.map((b) => ({ ...b }));\n } else {\n // Delete the key on empty: keeps the model shape minimal and correctly\n // represents \"no bindings\" as an absent key rather than an empty array.\n delete part.bindings;\n }\n }\n\n invert(doc: EditorDocument): void {\n const part = doc.findPart(this.partId);\n if (this.prevBindings === undefined) {\n delete part.bindings;\n } else {\n // Assign a fresh deep copy — never alias the captured array so re-apply\n // after undo cannot corrupt the saved prior value.\n part.bindings = this.prevBindings.map((b) => ({ ...b }));\n }\n }\n}\n\n/**\n * Attach a part to a deformer, or detach it (`newDeformerId === undefined`).\n * Calls {@link validatePartAttach} FIRST so invalid attachments (warp without\n * mesh, unknown ids) throw before any capture or mutation.\n *\n * Absent-vs-present distinction mirrors {@link SetDeformerParent}: captures\n * both the prior `deformer` value and whether the key was present, so `invert`\n * can delete the key rather than assigning `undefined`.\n */\nexport class SetPartDeformer implements EditCommand {\n readonly label = \"Set part deformer\";\n private captured = false;\n private prevDeformer: string | undefined = undefined;\n private prevHadDeformer = false;\n\n constructor(\n private readonly partId: string,\n private readonly newDeformerId: string | undefined,\n ) {}\n\n apply(doc: EditorDocument): void {\n // Validate FIRST — throws before capture/mutate on any violation.\n validatePartAttach(\n doc.getModel().deformers ?? [],\n this.partId,\n doc.getModel().parts,\n this.newDeformerId,\n );\n const part = doc.findPart(this.partId);\n if (!this.captured) {\n this.prevHadDeformer = Object.prototype.hasOwnProperty.call(\n part,\n \"deformer\",\n );\n this.prevDeformer = part.deformer;\n this.captured = true;\n }\n if (this.newDeformerId !== undefined) {\n part.deformer = this.newDeformerId;\n } else {\n delete part.deformer;\n }\n }\n\n invert(doc: EditorDocument): void {\n const part = doc.findPart(this.partId);\n if (this.prevHadDeformer) {\n part.deformer = this.prevDeformer;\n } else {\n delete part.deformer;\n }\n }\n}\n\n/**\n * Return true if the deformer identified by `deformerId` exists in the model\n * and has `kind === \"warp\"`. Used by SetPartMesh to detect warp-deformer\n * attachment without importing reparent.ts internals.\n */\nfunction isWarpDeformer(model: IkiModel, deformerId: string): boolean {\n const d = (model.deformers ?? []).find((x) => x.id === deformerId);\n return d?.kind === \"warp\";\n}\n\n/**\n * Add, regenerate, or remove the triangle mesh on a part.\n *\n * - `mesh !== undefined` → add or replace the mesh, registering the\n * unit-square base UVs in the side-table so {@link EditorDocument.applyAtlas}\n * can remap them later.\n * - `mesh === undefined` → delete `part.mesh` and remove the side-table entry.\n *\n * Fails fast (BEFORE any mutation) on warp-topology violations:\n * - REMOVE while `part.warps` is present (even empty) or the part is\n * attached to a warp deformer — the format rejects any `warps` key once\n * the mesh is gone.\n * - ADD/REPLACE while `part.warps` has authored offsets (`length > 0`) —\n * regenerating the mesh invalidates offset positions silently; the user\n * must remove the warps first.\n *\n * The remove guard checks PRESENCE of `part.warps` (not length) while the\n * add/replace guard checks LENGTH > 0. This asymmetry is intentional: the\n * format allows `warps: []` only when a mesh exists, so any present key\n * (even empty) would become invalid after mesh removal; but replacing a mesh\n * under an empty `warps: []` is harmless because there are no authored offsets.\n */\nexport class SetPartMesh implements EditCommand {\n readonly label = \"Set part mesh\";\n private readonly mesh: IkiMesh | undefined;\n private captured = false;\n private prevHadMesh = false;\n private prevMesh: IkiMesh | undefined;\n private prevBaseMeshUvs: number[] | undefined;\n\n constructor(\n private readonly partId: string,\n mesh: IkiMesh | undefined,\n ) {\n // Clone on construction — prevents caller mutation from corrupting apply/redo.\n this.mesh = mesh === undefined ? undefined : structuredClone(mesh);\n }\n\n apply(doc: EditorDocument): void {\n // (a) Resolve the live part up front — unlike SetPartBindings, which resolves\n // after parseIkiModel, we need the live part here because the warp-topology\n // guards at step (c) inspect part.warps / part.deformer before any mutation.\n const part = doc.findPart(this.partId);\n\n // (b) Structural validation — only needed when adding or replacing a mesh.\n // Build a NARROW synthetic candidate carrying only the new mesh so that\n // unrelated in-flight parts with NaN values cannot cause false failures.\n if (this.mesh !== undefined) {\n const candidatePart = {\n id: \"_\",\n color: [0, 0, 0, 1],\n width: 1,\n height: 1,\n transform: { x: 0, y: 0 },\n order: 0,\n mesh: structuredClone(this.mesh),\n };\n const candidate = {\n version: IKI_FORMAT_VERSION,\n name: \"_\",\n canvas: { width: 1, height: 1 },\n parameters: doc.getModel().parameters,\n parts: [candidatePart],\n };\n try {\n parseIkiModel(candidate);\n } catch (e) {\n if (e instanceof IkiFormatError) {\n throw new IkiFormatError(\n e.message.replace(/^parts\\[0\\]/, `parts.\"${this.partId}\"`),\n );\n }\n throw e;\n }\n }\n\n // (c) Warp-topology fail-fast — BEFORE any mutation, after structural validation.\n // The two paths key on DIFFERENT predicates; this asymmetry is intentional\n // (see class JSDoc above).\n const attachedToWarp =\n part.deformer !== undefined &&\n isWarpDeformer(doc.getModel(), part.deformer);\n\n if (this.mesh === undefined) {\n // REMOVE: guard on warps PRESENCE (even empty) OR warp-deformer attachment.\n // The format rejects ANY present `warps` key once the mesh is gone, so even\n // an empty array would make toIkiModel() throw.\n if (part.warps !== undefined || attachedToWarp) {\n throw new IkiFormatError(\n `parts.\"${this.partId}\": cannot remove mesh — part has warps or is attached to a warp deformer; remove its warps / detach from the warp deformer first`,\n );\n }\n // Clip-mask guard — the format requires a clip mask to be a mesh part, so\n // stripping the mesh from a referenced mask would dangle the ref and fail\n // toIkiModel(). Mirrors the DeletePart clip-mask guard.\n const masker = doc\n .getModel()\n .parts.find(\n (p) => p.id !== this.partId && p.clip?.masks.includes(this.partId),\n );\n if (masker) {\n throw new IkiFormatError(\n `parts.\"${this.partId}\": cannot remove mesh — used as a clip mask by part \"${masker.id}\" (masks require a mesh); remove its clip first`,\n );\n }\n } else {\n // ADD/REPLACE: guard on authored offsets (length > 0). An empty warps: []\n // has no offsets to invalidate, so replacing the mesh under it is harmless.\n if ((part.warps?.length ?? 0) > 0) {\n throw new IkiFormatError(\n `parts.\"${this.partId}\": cannot regenerate mesh — part has warps whose offsets are bound to the current rest mesh; remove its warps first`,\n );\n }\n }\n\n // (d) Mutate + side-table maintenance.\n\n // (i) Single first-apply capture block — captures BOTH prevMesh and\n // prevBaseMeshUvs together, BEFORE any mutation. captureBaseMeshUvs has\n // a side effect (registers current mesh.uvs); step (iii) overwrites it.\n if (!this.captured) {\n this.prevHadMesh = part.mesh !== undefined;\n this.prevMesh = part.mesh ? structuredClone(part.mesh) : undefined;\n this.prevBaseMeshUvs = doc.captureBaseMeshUvs(this.partId);\n this.captured = true;\n }\n // Redo: do NOT re-capture (would clobber the saved prior). The mutation\n // below is fully reconstructable from this.mesh + this.partId.\n\n // (ii) Apply the model mutation.\n if (this.mesh !== undefined) {\n // Compute stored UVs: if the part has an active texture, remap the\n // unit-square base UVs into the texture's atlas sub-rectangle.\n const storedUvs =\n part.texture !== undefined\n ? remapMeshUvsToRect(this.mesh.uvs, part.texture.uv)\n : this.mesh.uvs.slice();\n part.mesh = {\n vertices: this.mesh.vertices.slice(),\n uvs: storedUvs,\n indices: this.mesh.indices.slice(),\n };\n } else {\n delete part.mesh;\n }\n\n // (iii) Re-register the side-table to the correct final state, overwriting\n // the (i) read's incidental re-registration. Write ONLY via\n // restoreBaseMeshUvs — never via a new setBaseMeshUvs API.\n if (this.mesh !== undefined) {\n // Register the UNIT-SQUARE base (not the texture-remapped storedUvs) so\n // applyAtlas can always derive correct atlas-space UVs from the base.\n doc.restoreBaseMeshUvs(this.partId, this.mesh.uvs.slice());\n } else {\n doc.restoreBaseMeshUvs(this.partId, undefined);\n }\n }\n\n invert(doc: EditorDocument): void {\n const part = doc.findPart(this.partId);\n // Restore the mesh, preserving the absent-vs-present distinction.\n // applyAtlas (and texture changes generally) are NON-undoable and do NOT\n // clear the undo stack. So by the time undo() is called, part.texture may\n // have been changed by a later applyAtlas that this command never saw. To\n // avoid restoring stale texture-space UVs, rebuild uvs from the captured\n // unit-square base against the CURRENT texture rect rather than trusting\n // prevMesh.uvs verbatim.\n if (this.prevHadMesh) {\n // A mesh part ALWAYS has a registered unit-square base under the invariant\n // (applyAtlas preflights requireBaseUvs for every mesh part). If the base is\n // absent here the side-table invariant is broken — fail fast rather than\n // silently restoring stale texture-space uvs that would corrupt rendering.\n if (this.prevBaseMeshUvs === undefined) {\n throw new Error(\n `parts.\"${this.partId}\": cannot invert SetPartMesh — no base mesh uvs captured for a mesh part (broken side-table invariant)`,\n );\n }\n const restored = structuredClone(this.prevMesh!);\n // Remap the unit-square base into the current texture rect (if any).\n restored.uvs =\n part.texture !== undefined\n ? remapMeshUvsToRect(this.prevBaseMeshUvs, part.texture.uv)\n : this.prevBaseMeshUvs.slice();\n part.mesh = restored;\n } else {\n delete part.mesh;\n }\n // Restore the side-table to the exact prior state (unit-square base).\n doc.restoreBaseMeshUvs(this.partId, this.prevBaseMeshUvs);\n }\n}\n\n/**\n * Validate a candidate `physics` array against the model's declared parameters\n * via a NARROW synthetic model — full `parameters` + the full candidate physics\n * array + one trivial part. Mirrors {@link SetPartBindings}' synthetic-candidate\n * (avoids false-positives from unrelated in-flight NaN part numerics) but carries\n * the FULL physics array so the cross-rig rules (dup id / dup output / feedback)\n * run. On `IkiFormatError`, rewrite the failing `physics[n]` path to name the rig\n * AT THE FAILING INDEX — a cross-rig failure can surface at a different index than\n * the edited rig, so the rewrite follows the message index, not the command target.\n */\nfunction validatePhysicsCandidate(\n doc: EditorDocument,\n candidatePhysics: IkiPhysics[],\n): void {\n const candidatePart: Record<string, unknown> = {\n id: \"_\",\n color: [0, 0, 0, 1],\n width: 1,\n height: 1,\n transform: { x: 0, y: 0 },\n order: 0,\n };\n const candidate = {\n version: IKI_FORMAT_VERSION,\n name: \"_\",\n canvas: { width: 1, height: 1 },\n parameters: doc.getModel().parameters,\n parts: [candidatePart],\n deformers: doc.getModel().deformers,\n physics: candidatePhysics,\n physicsChains: doc.getModel().physicsChains,\n };\n try {\n parseIkiModel(candidate);\n } catch (e) {\n if (e instanceof IkiFormatError) {\n const m = e.message.match(/^physics\\[(\\d+)\\]/);\n const rigId = m ? candidatePhysics[Number(m[1])]?.id : undefined;\n if (rigId !== undefined) {\n throw new IkiFormatError(\n e.message.replace(/^physics\\[\\d+\\]/, `physics.\"${rigId}\"`),\n );\n }\n }\n throw e;\n }\n}\n\n/**\n * Add a physics rig to `model.physics`. Mirrors {@link AddDeformer}: clone on\n * construction, cheap friendly duplicate-id pre-check, full synthetic-candidate\n * validation before mutating, capture the absent-vs-present `physics` state once,\n * and on invert delete the key when the array empties back to its prior absence.\n */\nexport class AddPhysicsRig implements EditCommand {\n readonly label = \"Add physics rig\";\n private readonly rig: IkiPhysics;\n private captured = false;\n private prevPhysicsAbsent = false;\n\n constructor(rig: IkiPhysics) {\n // Deep clone — input/output are nested objects a shallow spread would alias.\n this.rig = structuredClone(rig);\n }\n\n apply(doc: EditorDocument): void {\n const model = doc.getModel();\n // (a) Friendly duplicate-id pre-check (the format check from validate.ts is\n // the real guard; this surfaces an actionable message before validate).\n if ((model.physics ?? []).some((r) => r.id === this.rig.id)) {\n throw new Error(\n `physics: id \"${this.rig.id}\" collides with an existing physics rig id`,\n );\n }\n // (b) Full-physics synthetic validation with the rig appended.\n validatePhysicsCandidate(doc, [\n ...(model.physics ?? []),\n structuredClone(this.rig),\n ]);\n // (c) Capture-once, then mutate with a fresh clone.\n if (!this.captured) {\n this.prevPhysicsAbsent = model.physics === undefined;\n this.captured = true;\n }\n if (model.physics === undefined) {\n model.physics = [];\n }\n model.physics.push(structuredClone(this.rig));\n }\n\n invert(doc: EditorDocument): void {\n const model = doc.getModel();\n const arr = model.physics;\n if (!arr) return;\n const i = arr.findIndex((r) => r.id === this.rig.id);\n if (i !== -1) arr.splice(i, 1);\n if (this.prevPhysicsAbsent && arr.length === 0) {\n delete model.physics;\n }\n }\n}\n\n/**\n * Delete a physics rig by id. Mirrors {@link DeleteDeformer}: resolve/validate\n * first, capture the removed rig + its index once, splice. Removing the LAST rig\n * deletes the `physics` key to keep the exported shape minimal; invert re-inserts\n * at the original index (recreating the array if it was deleted).\n */\nexport class DeletePhysicsRig implements EditCommand {\n readonly label = \"Delete physics rig\";\n private captured = false;\n private removed!: IkiPhysics;\n private index!: number;\n\n constructor(private readonly rigId: string) {}\n\n apply(doc: EditorDocument): void {\n const rig = doc.findPhysicsRig(this.rigId); // throws if unknown\n const arr = doc.getModel().physics!;\n const i = arr.indexOf(rig);\n if (!this.captured) {\n this.removed = structuredClone(rig);\n this.index = i;\n this.captured = true;\n }\n arr.splice(i, 1);\n // Minimal exported shape: drop the key once the last rig is gone.\n if (arr.length === 0) {\n delete doc.getModel().physics;\n }\n }\n\n invert(doc: EditorDocument): void {\n const model = doc.getModel();\n (model.physics ??= []).splice(this.index, 0, structuredClone(this.removed));\n }\n}\n\n/**\n * Replace a physics rig in place (tuning). Mirrors {@link SetDeformerBindings}\n * but DEEP-clones the nested `input`/`output` (a shallow spread would alias them).\n * Forbids rename (`rig.id` must equal the target `rigId`) — this command tunes a\n * rig, never re-keys it. Validates the whole candidate (the edited rig swapped in\n * at its index) so cross-rig rules still run, then captures the prior rig once.\n */\nexport class SetPhysicsRig implements EditCommand {\n readonly label = \"Set physics rig\";\n private readonly rig: IkiPhysics;\n private captured = false;\n private prevRig!: IkiPhysics;\n\n constructor(\n private readonly rigId: string,\n rig: IkiPhysics,\n ) {\n this.rig = structuredClone(rig);\n }\n\n apply(doc: EditorDocument): void {\n // No-rename guard — fail before mutating.\n if (this.rig.id !== this.rigId) {\n throw new Error(\n `physics.\"${this.rigId}\": cannot change rig id to \"${this.rig.id}\" (rename unsupported)`,\n );\n }\n const model = doc.getModel();\n const i = (model.physics ?? []).findIndex((r) => r.id === this.rigId);\n if (i === -1) {\n throw new Error(`physics: no physics rig with id \"${this.rigId}\"`);\n }\n // Validate a candidate with the edited rig swapped in at its index.\n const candidate = model.physics!.map((r, idx) =>\n idx === i ? structuredClone(this.rig) : r,\n );\n validatePhysicsCandidate(doc, candidate);\n if (!this.captured) {\n this.prevRig = structuredClone(model.physics![i]);\n this.captured = true;\n }\n model.physics![i] = structuredClone(this.rig);\n }\n\n invert(doc: EditorDocument): void {\n const arr = doc.getModel().physics;\n if (!arr) return;\n const i = arr.findIndex((r) => r.id === this.rigId);\n if (i !== -1) arr[i] = structuredClone(this.prevRig);\n }\n}\n","import type { IkiGridKeyform } from \"@ikijs/format\";\n\n/**\n * Pure, grid-size-agnostic keyform/offset math for authoring a warp-deformer\n * grid by dragging. No DOM, no `@ikijs/engine` — the load-bearing testable core.\n * Constraints derive only from the input array lengths, never the sample grid.\n */\n\n/**\n * Interpolate the grid offsets at `value`, mirroring the engine's\n * `accumulateKeyformOffsets` clamp+lerp semantics: clamp to the first/last\n * keyform (NO extrapolation) and linearly interpolate the bracketing pair.\n * Returns a NEW array of length `keyforms[0].offsets.length`. Throws on empty.\n */\nexport function interpolateGridOffsets(\n keyforms: { value: number; offsets: number[] }[],\n value: number,\n): number[] {\n if (keyforms.length === 0) {\n throw new Error(\"interpolateGridOffsets: keyforms must be non-empty\");\n }\n\n if (value <= keyforms[0].value) {\n return [...keyforms[0].offsets];\n }\n const last = keyforms[keyforms.length - 1];\n if (value >= last.value) {\n return [...last.offsets];\n }\n\n // Find the bracketing pair (keyforms are small in practice).\n let lo = keyforms[0];\n let hi = keyforms[1];\n for (let k = 1; k < keyforms.length - 1; k++) {\n if (keyforms[k].value <= value) {\n lo = keyforms[k];\n hi = keyforms[k + 1];\n }\n }\n const t = (value - lo.value) / (hi.value - lo.value);\n return lo.offsets.map((loOff, i) => loOff + (hi.offsets[i] - loOff) * t);\n}\n\n/**\n * Per-control-point delta of the dragged grid from the rest grid: for each\n * point `i`, `(draggedX_i - restX_i, draggedY_i - restY_i)`. The DOM layer\n * assembles the full `restFrameDraggedPoints` (including untouched points), so\n * this is a straight subtract — no prior-offset blending.\n *\n * Both arrays must have the SAME length and that length must be even (x,y\n * pairs). Returns a NEW array of length `restPoints.length`.\n */\nexport function computeGridOffsets(\n restPoints: number[],\n restFrameDraggedPoints: number[],\n): number[] {\n if (restPoints.length !== restFrameDraggedPoints.length) {\n throw new Error(\n `computeGridOffsets: restFrameDraggedPoints length ${restFrameDraggedPoints.length} must equal restPoints length ${restPoints.length}`,\n );\n }\n if (restPoints.length % 2 !== 0) {\n throw new Error(\n `computeGridOffsets: restPoints length ${restPoints.length} must be even (x,y pairs)`,\n );\n }\n return restPoints.map((rest, i) => restFrameDraggedPoints[i] - rest);\n}\n\n/**\n * Insert or replace the keyform at `value`, returning a NEW array. If a keyform\n * already exists with an exact-match `value`, REPLACE its offsets (with a copy);\n * otherwise INSERT `{ value, offsets: [...offsets] }` at the position that keeps\n * the array strictly ascending by value. The input array and its keyform objects\n * are never mutated, and `offsets` is copied so the result never aliases the\n * caller's array.\n *\n * Deliberately RANGE-FREE — a generic, reusable array op. Value-range\n * enforcement is the command's job, not this helper's.\n */\nexport function upsertGridKeyform(\n keyforms: IkiGridKeyform[],\n value: number,\n offsets: number[],\n): IkiGridKeyform[] {\n const result = keyforms.map((kf) => ({\n value: kf.value,\n offsets: [...kf.offsets],\n }));\n const existing = result.findIndex((kf) => kf.value === value);\n if (existing !== -1) {\n result[existing] = { value, offsets: [...offsets] };\n return result;\n }\n const insertAt = result.findIndex((kf) => kf.value > value);\n const entry: IkiGridKeyform = { value, offsets: [...offsets] };\n if (insertAt === -1) {\n result.push(entry);\n } else {\n result.splice(insertAt, 0, entry);\n }\n return result;\n}\n","import type { IkiDeformer, IkiPart, IkiPhysicsChain } from \"@ikijs/format\";\n\n/**\n * Pure, DOM-free validation helpers for deformer reparenting and part attachment.\n * Each function throws a path-qualified plain Error on rejection and returns void\n * on success. Neither function mutates the input arrays or objects.\n */\n\nfunction kindOf(d: IkiDeformer): \"warp\" | \"matrix\" {\n return d.kind === \"warp\" ? \"warp\" : \"matrix\";\n}\n\n/**\n * Validate that reparenting `deformerId` under `newParentId` keeps the deformer\n * hierarchy valid. Pass `newParentId === undefined` to move to root (always legal).\n * Checks: existence, self-reference, undeclared parent, kind constraint (warp\n * deformers cannot be parents), and cycle detection via the proposed edge.\n */\nexport function validateDeformerReparent(\n deformers: IkiDeformer[],\n deformerId: string,\n newParentId: string | undefined,\n): void {\n // (1) Target deformer must exist.\n const target = deformers.find((d) => d.id === deformerId);\n if (target === undefined) {\n throw new Error(`deformers: no deformer with id \"${deformerId}\"`);\n }\n\n // (2) Root is always legal.\n if (newParentId === undefined) return;\n\n // (3) Self-reference.\n if (newParentId === deformerId) {\n throw new Error(\n `deformers.\"${deformerId}\".parent \"${newParentId}\" is a self-reference`,\n );\n }\n\n // (4) Parent must be declared.\n const parent = deformers.find((d) => d.id === newParentId);\n if (parent === undefined) {\n throw new Error(\n `deformers.\"${deformerId}\".parent \"${newParentId}\" is not a declared deformer`,\n );\n }\n\n // (5) Parent must be a matrix deformer.\n if (kindOf(parent) === \"warp\") {\n throw new Error(\n `deformers.\"${deformerId}\".parent \"${newParentId}\" must be a matrix deformer (warp deformers cannot be parents)`,\n );\n }\n\n // (6) Cycle detection: build parentOf from current state, then override with\n // the proposed edge, and walk from deformerId following the chain.\n const parentOf = new Map<string, string>();\n for (const d of deformers) {\n if (d.parent !== undefined) parentOf.set(d.id, d.parent);\n }\n // Override with the proposed edge.\n parentOf.set(deformerId, newParentId);\n\n const visited = new Set<string>();\n let cur: string | undefined = deformerId;\n while (cur !== undefined) {\n if (visited.has(cur)) {\n throw new Error(\n `deformers: reparenting \"${deformerId}\" under \"${newParentId}\" would create a cycle`,\n );\n }\n visited.add(cur);\n cur = parentOf.get(cur);\n }\n}\n\n/**\n * Validate that deleting `deformerId` is safe. Throws when the deformer does\n * not exist, when another deformer is parented to it (must be reparented or\n * detached first), when a part is attached to it (must be detached first), or\n * when a physics chain anchors to it (must be re-anchored or deleted first).\n *\n * Every id that can reference a deformer must be covered here: the format\n * validator rejects a dangling reference at export, so a delete this function\n * lets through does not fail now — it strands the document in a state\n * `toIkiModel()` refuses.\n *\n * Note: there is no validatePartDelete here, but NOT because parts are\n * unreferenced — `clip.masks` names parts by id. That invariant is enforced\n * closer to the edits that could break it: `DeletePart` refuses to remove a\n * part still used as a mask, and `SetPartMesh` refuses to strip the mesh off\n * one (masks must be mesh parts). No command creates or edits `clip`.\n */\nexport function validateDeformerDelete(\n deformers: IkiDeformer[],\n parts: IkiPart[],\n physicsChains: IkiPhysicsChain[],\n deformerId: string,\n): void {\n // (1) Target deformer must exist.\n const target = deformers.find((d) => d.id === deformerId);\n if (target === undefined) {\n throw new Error(`deformers: no deformer with id \"${deformerId}\"`);\n }\n\n // (2) No other deformer may be parented to it.\n const childDeformer = deformers.find((d) => d.parent === deformerId);\n if (childDeformer !== undefined) {\n throw new Error(\n `deformers.\"${deformerId}\": cannot delete — deformer \"${childDeformer.id}\" is parented to it; reparent or detach it first`,\n );\n }\n\n // (3) No part may be attached to it.\n const attachedPart = parts.find((p) => p.deformer === deformerId);\n if (attachedPart !== undefined) {\n throw new Error(\n `deformers.\"${deformerId}\": cannot delete — part \"${attachedPart.id}\" is attached to it; detach it first`,\n );\n }\n\n // (4) No physics chain may anchor to it.\n const anchoredChain = physicsChains.find(\n (c) => c.anchorDeformer === deformerId,\n );\n if (anchoredChain !== undefined) {\n throw new Error(\n `deformers.\"${deformerId}\": cannot delete — physics chain \"${anchoredChain.id}\" anchors to it; re-anchor or delete the chain first`,\n );\n }\n}\n\n/**\n * Validate that attaching part `partId` to deformer `newDeformerId` is valid.\n * Pass `newDeformerId === undefined` to detach (always legal).\n * Checks: part existence, undeclared deformer, and mesh-required-for-warp.\n */\nexport function validatePartAttach(\n deformers: IkiDeformer[],\n partId: string,\n parts: IkiPart[],\n newDeformerId: string | undefined,\n): void {\n // (1) Part must exist.\n const part = parts.find((p) => p.id === partId);\n if (part === undefined) {\n throw new Error(`parts: no part with id \"${partId}\"`);\n }\n\n // (2) Detach is always legal.\n if (newDeformerId === undefined) return;\n\n // (3) Deformer must be declared.\n const deformer = deformers.find((d) => d.id === newDeformerId);\n if (deformer === undefined) {\n throw new Error(\n `parts.\"${partId}\".deformer \"${newDeformerId}\" is not a declared deformer`,\n );\n }\n\n // (4) Warp deformer requires a mesh on the part.\n if (kindOf(deformer) === \"warp\" && part.mesh === undefined) {\n throw new Error(\n `parts.\"${partId}\".deformer \"${newDeformerId}\" is a warp deformer and requires a mesh`,\n );\n }\n}\n","/**\n * Alpha bounding-box scan shared by every auto-rig ingestion path.\n *\n * The scan itself is environment-free — it only needs indexable RGBA bytes — so\n * a browser editor (canvas `ImageData`) and the Node MCP server (a `sharp`\n * raw buffer) run the SAME code instead of two copies that have to be kept\n * byte-identical by hand. Only decoding differs between them.\n */\n\n/** Alpha at or above this counts as coverage; below it is treated as empty. */\nexport const ALPHA_BBOX_THRESHOLD = 8;\n\n/** Top-left origin, +y down — image space, not model space. */\nexport interface AlphaBbox {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n/**\n * Tight bounding box of every pixel with alpha >= {@link ALPHA_BBOX_THRESHOLD},\n * expanded 1px on each side (clamped to the image) for AA / extrude margin.\n *\n * Returns `null` when no pixel passes the threshold, leaving the \"empty layer\"\n * error to the caller: each ingestion path reports it with its own error type\n * and message.\n */\nexport function detectAlphaBbox(\n rgba: ArrayLike<number>,\n width: number,\n height: number,\n): AlphaBbox | null {\n let minX = width;\n let maxX = -1;\n let minY = height;\n let maxY = -1;\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const alpha = rgba[(y * width + x) * 4 + 3];\n if (alpha >= ALPHA_BBOX_THRESHOLD) {\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n }\n }\n }\n\n if (maxX === -1) return null;\n\n // Expand 1px each side, clamped to image bounds; the x2/y2 clamps make w/h\n // implicitly in-bounds (no separate w/h clamp needed).\n const x = Math.max(0, minX - 1);\n const y = Math.max(0, minY - 1);\n const x2 = Math.min(width - 1, maxX + 1);\n const y2 = Math.min(height - 1, maxY + 1);\n\n return { x, y, w: x2 - x + 1, h: y2 - y + 1 };\n}\n","import type { IkiTransformChannel } from \"@ikijs/format\";\n\n/**\n * Pure, binding-value logic for computing an endpoint (rest-to-posed delta or\n * ratio) when capturing a transform channel binding. No DOM, no @ikijs/engine —\n * the single home of the additive-vs-multiplicative rule, reused by both the\n * part and deformer capture paths.\n *\n * Additive channels (translateX, translateY, rotate, scaleX, scaleY) return the\n * delta: `posedValue - restValue`. The binding will multiply that delta across\n * the driven range.\n *\n * Opacity is multiplicative: returns the ratio `posedValue / restValue`. The\n * binding will multiply by that ratio across the driven range. When `restValue`\n * is 0 (a degenerate case: base opacity cannot be represented multiplicatively\n * since 0 * x ≡ 0), this returns 0 as a documented fallback — it does NOT\n * recover `posedValue` and is NOT unit-tested as an identity. The store layer\n * additionally skips an opacity capture when rest opacity is 0, surfacing an\n * editError.\n *\n * Deformer channels never include opacity (they use `IkiMatrixChannel`), so the\n * opacity branch is reached only for parts.\n *\n * Finiteness of the captured value is NOT validated here; the store's\n * `captureEndpoint` finite-value guard is the appropriate layer for that check.\n */\nexport function captureBindingEndpoint(\n channel: IkiTransformChannel,\n restValue: number,\n posedValue: number,\n): number {\n if (channel === \"opacity\") {\n return restValue === 0 ? 0 : posedValue / restValue;\n }\n return posedValue - restValue;\n}\n","import type { IkiUvRect } from \"@ikijs/format\";\n\nexport const ATLAS_PADDING = 2;\nexport const UV_INSET_PX = 0.5;\n\n/** Intrinsic pixel size of one decoded image; id is an editor-only stable key. */\nexport interface AtlasSource {\n id: string;\n width: number;\n height: number;\n}\n\n/** Sub-image pixel rect within the page, top-left origin, EXCLUDING the gutter. */\nexport interface AtlasPlacement {\n id: string;\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface AtlasLayout {\n pageWidth: number;\n pageHeight: number;\n placements: AtlasPlacement[];\n padding: number;\n}\n\n/**\n * Deterministic shelf/row packer. Sources are sorted by id for stability so\n * identical inputs always produce an identical layout.\n *\n * Padding is one-sided: each placement reserves `padding` px on its RIGHT and\n * BOTTOM only. Page left/top edges need no gutter.\n *\n * pageWidth/pageHeight = tight bound (max x+width+padding, max y+height+padding).\n * Empty sources → { pageWidth: 0, pageHeight: 0, placements: [], padding }.\n * Throws a plain Error naming the offending source id on a non-finite or <= 0 dimension.\n */\nexport function packAtlas(\n sources: AtlasSource[],\n padding = ATLAS_PADDING,\n): AtlasLayout {\n for (const src of sources) {\n if (!isFinite(src.width) || src.width <= 0) {\n throw new Error(\n `packAtlas: source \"${src.id}\" has invalid width ${src.width}`,\n );\n }\n if (!isFinite(src.height) || src.height <= 0) {\n throw new Error(\n `packAtlas: source \"${src.id}\" has invalid height ${src.height}`,\n );\n }\n }\n\n if (sources.length === 0) {\n return { pageWidth: 0, pageHeight: 0, placements: [], padding };\n }\n\n // Stable sort by id so identical inputs always produce the same layout.\n const sorted = sources\n .slice()\n .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));\n\n // Target page width: ceil(sqrt(total padded area)), raised to a sane minimum.\n const totalArea = sorted.reduce(\n (sum, s) => sum + (s.width + padding) * (s.height + padding),\n 0,\n );\n const targetWidth = Math.max(\n Math.ceil(Math.sqrt(totalArea)),\n // Ensure at least the widest single source fits.\n Math.max(...sorted.map((s) => s.width + padding)),\n );\n\n const placements: AtlasPlacement[] = [];\n let shelfX = 0;\n let shelfY = 0;\n let shelfHeight = 0; // tallest padded item in the current row\n\n for (const src of sorted) {\n const paddedW = src.width + padding;\n const paddedH = src.height + padding;\n\n // Wrap to next row when the item doesn't fit on the current shelf.\n if (shelfX > 0 && shelfX + paddedW > targetWidth) {\n shelfY += shelfHeight;\n shelfX = 0;\n shelfHeight = 0;\n }\n\n placements.push({\n id: src.id,\n x: shelfX,\n y: shelfY,\n width: src.width,\n height: src.height,\n });\n\n shelfX += paddedW;\n if (paddedH > shelfHeight) shelfHeight = paddedH;\n }\n\n // Tight page bounds: max right/bottom padded edge across all placements.\n let pageWidth = 0;\n let pageHeight = 0;\n for (const p of placements) {\n const right = p.x + p.width + padding;\n const bottom = p.y + p.height + padding;\n if (right > pageWidth) pageWidth = right;\n if (bottom > pageHeight) pageHeight = bottom;\n }\n\n return { pageWidth, pageHeight, placements, padding };\n}\n\n/**\n * Convert a pixel placement into a UV rect, inset by `insetPx` on all four\n * edges and clamped to [0, 1] so the validator's bounds check always passes.\n */\nexport function uvRectFor(\n placement: AtlasPlacement,\n page: { width: number; height: number },\n insetPx = UV_INSET_PX,\n): IkiUvRect {\n const px = placement.x + insetPx;\n const py = placement.y + insetPx;\n const pw = placement.width - insetPx * 2;\n const ph = placement.height - insetPx * 2;\n\n const x = Math.max(0, px / page.width);\n const y = Math.max(0, py / page.height);\n const width = Math.min(1 - x, Math.max(0, pw / page.width));\n const height = Math.min(1 - y, Math.max(0, ph / page.height));\n\n return { x, y, width, height };\n}\n","import type {\n IkiMatrixDeformer,\n IkiMesh,\n IkiModel,\n IkiPart,\n IkiWarpDeformer,\n} from \"@ikijs/format\";\n\n/**\n * Pure, DOM-free factory helpers for creating new model objects from scratch.\n * Kept separate from `commands.ts` (which is edit-only) so that the \"add item\"\n * use-case has a dedicated, testable home with no mutation concerns.\n *\n * All factories accept the live model so they can derive a collision-free id and\n * a sensible draw-order / grid span without hard-coding sample assumptions.\n */\n\n/**\n * Return the first collision-free id in the shared part+deformer namespace.\n * Parts and deformers share a flat id namespace (validate.ts:692-706), so we\n * scan both arrays. Returns `base` if unused; otherwise tries `${base}_2`,\n * `${base}_3`, … until a free slot is found.\n *\n * Parameters are a separate namespace and are NOT included in the scan.\n */\nfunction generateUniqueId(model: IkiModel, base: string): string {\n const used = new Set<string>();\n for (const p of model.parts) {\n used.add(p.id);\n }\n for (const d of model.deformers ?? []) {\n used.add(d.id);\n }\n\n if (!used.has(base)) return base;\n\n let n = 2;\n while (true) {\n const candidate = `${base}_${n}`;\n if (!used.has(candidate)) return candidate;\n n++;\n }\n}\n\n/**\n * Generate the flat `[x0,y0, x1,y1, …]` rest-grid control points for a\n * regular axis-aligned lattice with `(cols+1)*(rows+1)` points, row-major.\n *\n * Row 0 is the TOP (y = maxY); y strictly decreases with row index.\n * Column 0 is left (x = minX); x strictly increases with column index.\n * This ordering satisfies the `IkiWarpGrid` rest-grid invariant\n * (packages/format/src/types.ts:178-196) required by the validator and\n * the engine's grid-sampling code.\n */\nfunction generateRegularGridPoints(\n cols: number,\n rows: number,\n minX: number,\n maxX: number,\n minY: number,\n maxY: number,\n): number[] {\n const pts: number[] = [];\n for (let row = 0; row <= rows; row++) {\n const t = row / rows;\n const y = maxY - t * (maxY - minY); // maxY at row 0, minY at row `rows`\n for (let col = 0; col <= cols; col++) {\n const s = col / cols;\n const x = minX + s * (maxX - minX); // minX at col 0, maxX at col `cols`\n pts.push(x, y);\n }\n }\n return pts;\n}\n\n/**\n * Create a minimal valid part with a collision-free id and a paint order one\n * above the current top-most part so it is immediately visible in the viewport.\n * Uses a distinct non-white blue tint so it is distinguishable from the canvas\n * background without requiring a texture.\n *\n * No optional keys (`texture`, `mesh`, `deformer`, `bindings`, `warps`) are set\n * — the format treats all of them as absent by default.\n */\nexport function createDefaultPart(model: IkiModel): IkiPart {\n const order = model.parts.length\n ? Math.max(...model.parts.map((p) => p.order)) + 1\n : 0;\n\n return {\n id: generateUniqueId(model, \"part\"),\n color: [0.45, 0.6, 0.85, 1],\n width: 150,\n height: 150,\n transform: { x: 0, y: 0 },\n order,\n };\n}\n\n/**\n * Create a minimal valid matrix deformer rooted at the canvas origin.\n * `kind` is omitted because the format treats its absence as \"matrix\" (the\n * default), keeping the serialised model compact.\n */\nexport function createDefaultMatrixDeformer(\n model: IkiModel,\n): IkiMatrixDeformer {\n return {\n id: generateUniqueId(model, \"deformer\"),\n pivot: { x: 0, y: 0 },\n };\n}\n\n/**\n * Create a regular grid mesh in part LOCAL space (±0.5 unit frame).\n *\n * Vertices span x ∈ [-0.5, 0.5] and y ∈ [-0.5, 0.5] (+y up, engine convention).\n * Row 0 is the TOP of the grid (y = +0.5); row index increases downward.\n * UVs are unit-square base coordinates: u = col/cols (0..1 left→right),\n * v = row/rows (0..1 top→bottom). The top row maps to v=0 because v and y\n * run in opposite directions — keeps textures upright without a post-flip.\n * The UV-to-texture remap (atlas rect) is applied later in SetPartMesh, not here.\n *\n * Index winding per cell: [BL, BR, TL] then [TL, BR, TR], matching the engine's\n * implicit-quad convention (see examples/editor/src/mesh-generator.ts).\n *\n * Bounds are validated BEFORE any array allocation because this factory runs\n * before SetPartMesh's parseIkiModel — an unbounded count would freeze the\n * editor before the format-level 65536 limit is ever reached.\n */\nexport function createGridMesh(cols: number, rows: number): IkiMesh {\n // Guard: cols/rows must be integers ≥1 and the vertex count must fit within\n // the format limit of 65536 vertices (packages/format/src/validate.ts parseMesh).\n if (\n !Number.isInteger(cols) ||\n cols < 1 ||\n !Number.isInteger(rows) ||\n rows < 1 ||\n (cols + 1) * (rows + 1) > 65536\n ) {\n throw new Error(\n \"createGridMesh: cols and rows must be integers >= 1 with (cols+1)*(rows+1) <= 65536\",\n );\n }\n\n const colVerts = cols + 1;\n const rowVerts = rows + 1;\n\n const vertices: number[] = [];\n const uvs: number[] = [];\n\n // Row 0 is TOP (y = +0.5). Row `rows` is BOTTOM (y = -0.5).\n // Column 0 is left (x = -0.5). Column `cols` is right (x = +0.5).\n for (let row = 0; row < rowVerts; row++) {\n const t = row / rows;\n const y = 0.5 - t; // +0.5 at row 0, -0.5 at row `rows`\n const v = t; // 0 at top row, 1 at bottom row\n\n for (let col = 0; col < colVerts; col++) {\n const s = col / cols;\n const x = -0.5 + s; // -0.5 at col 0, +0.5 at col `cols`\n const u = s; // 0 at left col, 1 at right col\n\n vertices.push(x, y);\n uvs.push(u, v);\n }\n }\n\n // Two triangles per cell: [BL, BR, TL] then [TL, BR, TR].\n const indices: number[] = [];\n for (let row = 0; row < rows; row++) {\n for (let col = 0; col < cols; col++) {\n const tl = row * colVerts + col;\n const tr = row * colVerts + col + 1;\n const bl = (row + 1) * colVerts + col;\n const br = (row + 1) * colVerts + col + 1;\n\n indices.push(bl, br, tl);\n indices.push(tl, br, tr);\n }\n }\n\n return { vertices, uvs, indices };\n}\n\n/**\n * Create a 4×4-cell warp deformer whose rest grid spans a quarter of the\n * canvas in each direction (`±canvas.width/4` × `±canvas.height/4`). For the\n * 1000-unit sample canvas this gives x,y ∈ [−250, 250], which is large enough\n * to cover a typical face part without spilling to the edge.\n *\n * `warps` is omitted — the format treats its absence as \"rest grid only\", so\n * the deformer is immediately usable without authored keyforms.\n * `parent` is omitted — the deformer is placed at root; the caller may\n * reparent it via `SetDeformerParent` after creation.\n */\nexport function createDefaultWarpDeformer(model: IkiModel): IkiWarpDeformer {\n const hx = model.canvas.width / 4;\n const hy = model.canvas.height / 4;\n\n return {\n kind: \"warp\",\n id: generateUniqueId(model, \"warp\"),\n grid: {\n cols: 4,\n rows: 4,\n points: generateRegularGridPoints(4, 4, -hx, hx, -hy, hy),\n },\n };\n}\n","/**\n * Role table, role parsing, bbox→transform math, and model assembly for the\n * AI auto-rig generator. All pure functions — no DOM, no canvas, no\n * crypto.randomUUID.\n *\n * L/R = CHARACTER frame: *_L is the character's left = screen right.\n */\n\nimport {\n IKI_FORMAT_VERSION,\n StandardParameter,\n parseIkiModel,\n type IkiBinding,\n type IkiDeformer,\n type IkiGrid2DWarp,\n type IkiGridWarp,\n type IkiMesh,\n type IkiModel,\n type IkiParameter,\n type IkiPart,\n type IkiWarp,\n type IkiWarpGrid,\n} from \"@ikijs/format\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\n/**\n * The warp deformers the face and its features turn on — one per group of\n * parts that share a turn depth and a place on the face: the plate, each\n * side's eye stack (white, iris, pupil, highlight, lash), the two mouth\n * drawings, the nose, each brow and each blush. Every one is a `warp2d` over\n * a grid sized to its own members, parented to `headDeformer`, baked from the\n * one solved surface and emitted only when a member layer exists\n * (`generateIkiFromLayerSet`). `faceWarp` keeps the id the shared face grid\n * had.\n */\nconst TURN_GROUP_IDS = [\n \"faceWarp\",\n \"eyeWarp_L\",\n \"eyeWarp_R\",\n \"mouthWarp\",\n \"noseWarp\",\n \"browWarp_L\",\n \"browWarp_R\",\n \"blushWarp_L\",\n \"blushWarp_R\",\n] as const;\n/** One of `TURN_GROUP_IDS`. */\nexport type TurnGroupId = (typeof TURN_GROUP_IDS)[number];\nconst TURN_GROUP_ID_SET: ReadonlySet<string> = new Set<string>(TURN_GROUP_IDS);\n\n/** Whether a `RoleSpec.deformer` names one of the per-group turn warps — the\n * test for \"a part painted on the face\" wherever the generator or the solve\n * needs it. Exported at module level for the tests, not from the package. */\nexport function isTurnGroup(id: string): id is TurnGroupId {\n return TURN_GROUP_ID_SET.has(id);\n}\n\nexport interface RoleSpec {\n /**\n * Which deformer the part is attached to in the generated rig: its turn\n * group's warp for anything painted on the face (`TurnGroupId`), the rigid\n * head or body otherwise. `\"none\"` attaches it to nothing, so it holds\n * still in world space; no shipped role uses it.\n */\n deformer: TurnGroupId | \"headDeformer\" | \"bodyDeformer\" | \"none\";\n /** Back-to-front draw order. Higher = in front. */\n order: number;\n /** Whether this part gets a warp mesh (true) or is a static quad (false). */\n mesh: boolean;\n /** Present only for eye-family roles. */\n eyeSide?: \"L\" | \"R\";\n}\n\n/**\n * Input contract from the host app to this package's auto-rig functions.\n * Passed in after the host has decoded PNGs, computed alpha bboxes, and\n * mapped filenames to canonical roles.\n */\nexport interface LayerInput {\n /** Canonical role, e.g. \"eye_L\". */\n role: string;\n /** Original file name — used in error messages and as a stable id. */\n fileName: string;\n /** Shared canvas width (all layers have the same canvas size). */\n canvasW: number;\n /** Shared canvas height. */\n canvasH: number;\n /** Alpha-tight bounding box, top-left origin, +y down (image coords). */\n bbox: { x: number; y: number; w: number; h: number };\n /** Cropped image width = bbox.w. */\n cropW: number;\n /** Cropped image height = bbox.h. */\n cropH: number;\n /**\n * The layer's painted half-width on every CROP row, top → bottom, one entry\n * per row (`cropH` of them): half the row's opaque span in canvas px under\n * the same alpha ≥ 128 rule the head's `headEdges` are measured with\n * (`@ikijs/mcp`'s `measure-turn`), 0 for a row with no opaque pixel.\n * Optional, and only the FACE's is read: it gives the face plate a\n * row-dependent turn radius and a chin swing (`faceRowProfile`,\n * `turnSurface`); absent — as the editor's own import leaves it — the plate\n * turns on one radius on every row. Validated before anything reads it: an\n * array of length `cropH` whose every entry is a finite number in\n * [0, cropW / 2], the most an inclusive span inside the crop can be.\n */\n rowHalfWidths?: number[];\n /**\n * The tight box of the layer's alpha ≥ 128 pixels, in image coords like\n * `bbox` and NOT grown: the drawing a viewer reads inside a soft feather,\n * which the crop's alpha ≥ 8 rule takes in whole. Optional, and only the\n * NOSE's is read: its centre and width are the nose's turn landmark\n * (`turnLandmarks`), so a shaded nose is fitted by the drawing rather than\n * by its feather, and its top edge at its centre x is the bridge top the\n * nose tilts about on the turn (`NOSE_TURN_TILT_DEG`). Absent — as the\n * editor's own import leaves it — the crop stands in for both. Validated\n * before anything reads it: a plain object whose x, y, w and h are finite,\n * w and h positive, and the box inside `bbox`.\n */\n denseCore?: { x: number; y: number; w: number; h: number };\n}\n\n// ── Role table ───────────────────────────────────────────────────────────────\n\n/**\n * Single source of truth for every role the auto-rig generator understands.\n * Order values define back-to-front compositing (higher = in front).\n *\n * Eye-family roles use eyeSide to pair blink/gaze bindings correctly:\n * eye_L = character's left eye = screen right side.\n * eye_R = character's right eye = screen left side.\n */\nexport const ROLE_TABLE: Record<string, RoleSpec> = {\n // The silhouette behind the face. It rides the rigid head (not faceWarp,\n // whose grid it would stretch across the shoulders) as a MESH, so its ends\n // can swing on the hair-sway warps. It holds the head's outline STILL\n // through the turn — the head it hangs off no longer travels and it has no\n // turn warp of its own — while the face slides inside it.\n hair_back: { deformer: \"headDeformer\", order: 0, mesh: true },\n // The torso. It rides its own rigid deformer, not the head's: the head turns\n // about the neck pivot while the shoulders follow at BODY_TURN_FOLLOW and\n // follow the head's breath bob, which is what keeps the character from\n // reading as a floating head. Drawn over the back hair so long hair falls\n // behind the shoulders.\n body: { deformer: \"bodyDeformer\", order: 5, mesh: false },\n // The face plate and everything painted on it ride per-group turn warps\n // under the head (TurnGroupId): the plate its own, each eye stack its\n // side's, both mouth drawings one, the nose, each brow and each blush their\n // own. A group's grid is sized to its members alone and baked from the one\n // solved surface with the family's turn depth in its keyforms, so a feature\n // slides across the plate on its own grid instead of a translate binding\n // (see generateIkiFromLayerSet).\n face: { deformer: \"faceWarp\", order: 10, mesh: true },\n nose: { deformer: \"noseWarp\", order: 15, mesh: true },\n blush_L: { deformer: \"blushWarp_L\", order: 20, mesh: true },\n blush_R: { deformer: \"blushWarp_R\", order: 20, mesh: true },\n mouth: { deformer: \"mouthWarp\", order: 25, mesh: true },\n // An OPTIONAL second mouth drawing, open. When present the two cross-fade on\n // MouthOpen instead of the closed one being stretched, which is the\n // difference between a portrait rig and one that can lip-sync.\n mouth_open: { deformer: \"mouthWarp\", order: 26, mesh: true },\n eye_L: { deformer: \"eyeWarp_L\", order: 30, mesh: true, eyeSide: \"L\" },\n eye_R: { deformer: \"eyeWarp_R\", order: 30, mesh: true, eyeSide: \"R\" },\n iris_L: { deformer: \"eyeWarp_L\", order: 31, mesh: true, eyeSide: \"L\" },\n iris_R: { deformer: \"eyeWarp_R\", order: 31, mesh: true, eyeSide: \"R\" },\n pupil_L: { deformer: \"eyeWarp_L\", order: 32, mesh: true, eyeSide: \"L\" },\n pupil_R: { deformer: \"eyeWarp_R\", order: 32, mesh: true, eyeSide: \"R\" },\n highlight_L: { deformer: \"eyeWarp_L\", order: 33, mesh: true, eyeSide: \"L\" },\n highlight_R: { deformer: \"eyeWarp_R\", order: 33, mesh: true, eyeSide: \"R\" },\n // Upper lashes: an OPTIONAL separate layer ABOVE the iris that folds down to\n // the closed-eye seam (the same crease the white folds to), covering the cut\n // eyeball cleanly. When absent, the white's own fold is the only closed line.\n lash_L: { deformer: \"eyeWarp_L\", order: 34, mesh: true, eyeSide: \"L\" },\n lash_R: { deformer: \"eyeWarp_R\", order: 34, mesh: true, eyeSide: \"R\" },\n brow_L: { deformer: \"browWarp_L\", order: 40, mesh: true },\n brow_R: { deformer: \"browWarp_R\", order: 40, mesh: true },\n // The bangs ride the rigid head, like the back hair, and bind to no grid:\n // every motion of their own — the silhouette hold, the turn lead, the nod\n // and the sway — is a per-vertex warp attached in generateIkiFromLayerSet,\n // so they draw the head's outline where the plate under them bends and\n // slides (see bakeHairFrontSilhouetteWarp).\n hair_front: { deformer: \"headDeformer\", order: 50, mesh: true },\n};\n\n/**\n * Roles the generator requires. BOTH eyes are mandatory because the rig pairs\n * left/right blink parameters — a one-eyed rig would produce mismatched bindings.\n */\nexport const REQUIRED_ROLES = [\"face\", \"eye_L\", \"eye_R\", \"mouth\"] as const;\n\n// ── Alias map ────────────────────────────────────────────────────────────────\n\n/**\n * Minimal spelling-variant aliases → canonical role.\n * Only covers real-world variants; grow this only when you have evidence.\n */\nconst ALIAS_MAP: Record<string, string> = {\n eyebrow_L: \"brow_L\",\n eyebrow_R: \"brow_R\",\n eye_white_L: \"eye_L\",\n eye_white_R: \"eye_R\",\n};\n\n// ── normalizeRole ─────────────────────────────────────────────────────────────\n\n/**\n * Convert a raw filename to a canonical role key:\n * 1. Strip file extension.\n * 2. Lowercase.\n * 3. Collapse hyphens and spaces to underscores.\n * 4. Uppercase a trailing `_l` or `_r` side suffix → `_L` / `_R`.\n * 5. Apply alias map for known spelling variants.\n *\n * Examples:\n * \"Eye-L.png\" → \"eye_L\"\n * \"Brow_R.png\" → \"brow_R\"\n * \"eyebrow_L.png\" → \"brow_L\"\n */\nexport function normalizeRole(raw: string): string {\n // Strip extension\n const noExt = raw.replace(/\\.[^.]+$/, \"\");\n // Lowercase, then collapse hyphens/spaces → underscores\n const collapsed = noExt.toLowerCase().replace(/[-\\s]+/g, \"_\");\n // Uppercase trailing _l / _r side suffix\n const sided = collapsed.replace(\n /_([lr])$/,\n (_, s: string) => `_${s.toUpperCase()}`,\n );\n // Alias map\n return ALIAS_MAP[sided] ?? sided;\n}\n\n// ── assertRoleSet ─────────────────────────────────────────────────────────────\n\n/**\n * Single home for the unknown/duplicate/required role contract. Takes CANONICAL\n * role names (already normalized). Throws on:\n * - Unknown role (not in ROLE_TABLE)\n * - Duplicate role\n * - Missing required role\n *\n * The \"unknown role\" message from this function is fileName-free. That is\n * intentional: callers that lack a fileName (e.g. a future\n * `validateLayerInputs` that receives pre-normalized roles) get a useful error\n * without needing to pre-check. Callers that DO have the original fileName\n * (e.g. `parseLayerRoles`) pre-check unknown roles themselves so they can embed\n * the fileName in the message — but that pre-check is an enrichment, not a\n * requirement for correctness.\n */\nexport function assertRoleSet(roles: string[]): void {\n const seen = new Set<string>();\n for (const role of roles) {\n if (!(role in ROLE_TABLE)) {\n throw new Error(`auto-rig: unknown role \"${role}\"`);\n }\n if (seen.has(role)) {\n throw new Error(`auto-rig: duplicate role \"${role}\"`);\n }\n seen.add(role);\n }\n for (const required of REQUIRED_ROLES) {\n if (!seen.has(required)) {\n throw new Error(`auto-rig: missing required role \"${required}\"`);\n }\n }\n}\n\n// ── parseLayerRoles ───────────────────────────────────────────────────────────\n\n/**\n * Map an array of raw filenames to canonical `{ role, fileName }` pairs.\n *\n * Steps:\n * 1. Normalize each filename → role (normalizeRole).\n * 2. Eagerly check each role against ROLE_TABLE — unknown roles throw early\n * with the offending fileName included in the message.\n * 3. Call assertRoleSet to check duplicates + required roles.\n *\n * Throws a path-qualified Error on any contract violation.\n */\nexport function parseLayerRoles(\n fileNames: string[],\n): { role: string; fileName: string }[] {\n const pairs = fileNames.map((fileName) => {\n const role = normalizeRole(fileName);\n if (!(role in ROLE_TABLE)) {\n throw new Error(\n `auto-rig: unknown role \"${role}\" from file \"${fileName}\"`,\n );\n }\n return { role, fileName };\n });\n\n assertRoleSet(pairs.map((p) => p.role));\n return pairs;\n}\n\n// ── bboxToTransform ───────────────────────────────────────────────────────────\n\n/**\n * Convert an alpha bounding box (image coordinates, +y down, top-left origin)\n * to a model-space translation (model coordinates, +y up, canvas-center origin).\n *\n * x = bbox.x + bbox.w/2 - canvasW/2 (center of bbox relative to canvas center)\n * y = canvasH/2 - (bbox.y + bbox.h/2) (flip axis: image +y down → model +y up)\n *\n * Result is NOT rounded — fractional .5 values must be preserved to avoid\n * sub-pixel jitter in blink/gaze animations when the eye center falls between\n * two canvas pixels.\n *\n * @param partLabel Optional label for error messages (role or part id).\n */\nexport function bboxToTransform(\n bbox: { x: number; y: number; w: number; h: number },\n canvasW: number,\n canvasH: number,\n partLabel?: string,\n): { x: number; y: number } {\n if (bbox.w <= 0 || bbox.h <= 0) {\n throw new Error(`auto-rig: empty bbox for ${partLabel ?? \"layer\"}`);\n }\n const x = bbox.x + bbox.w / 2 - canvasW / 2;\n const y = canvasH / 2 - (bbox.y + bbox.h / 2); // flip: image +y-down → model +y-up\n return { x, y };\n}\n\n// ── validateLayerInputs ───────────────────────────────────────────────────────\n\n/**\n * Validate a LayerInput array before assembly. Called first inside\n * `generateIkiFromLayerSet` — the public API validates before deriving anything.\n *\n * Checks (in order):\n * 1. Non-empty layer list.\n * 2. Unknown/duplicate/required role contract via `assertRoleSet` (single home).\n * 3. Non-positive bbox.w, bbox.h, cropW, cropH per layer.\n * 4. Per-layer canvas size vs. the supplied `canvas` argument.\n * Matching every layer to the `canvas` arg inherently guarantees all layers\n * agree with each other — no separate peer-comparison loop is needed.\n * 5. `rowHalfWidths`, when present: an array of `cropH` finite numbers in\n * [0, cropW / 2] — checked before anything reads its length or entries.\n * 6. `denseCore`, when present on any layer: a plain object whose x, y, w\n * and h are finite, with w and h > 0 and the box inside `bbox`.\n *\n * Validates `layer.role` DIRECTLY (not via fileName). A caller could supply\n * `fileName:\"face.png\"` with `role:\"bad_role\"` — a filename check would miss it.\n *\n * Throws a plain `Error` with a path-qualified message on the first violation.\n */\nexport function validateLayerInputs(\n layers: LayerInput[],\n canvas: { width: number; height: number },\n): void {\n if (layers.length === 0) {\n throw new Error(\"auto-rig: validateLayerInputs: layers must not be empty\");\n }\n\n // Unknown / duplicate / required — single home for this contract\n assertRoleSet(layers.map((l) => l.role));\n\n for (const layer of layers) {\n const { role, bbox, cropW, cropH, canvasW, canvasH } = layer;\n if (bbox.w <= 0) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" has non-positive bbox.w (${bbox.w})`,\n );\n }\n if (bbox.h <= 0) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" has non-positive bbox.h (${bbox.h})`,\n );\n }\n if (cropW <= 0) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" has non-positive cropW (${cropW})`,\n );\n }\n if (cropH <= 0) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" has non-positive cropH (${cropH})`,\n );\n }\n if (canvasW !== canvas.width || canvasH !== canvas.height) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" canvas size (${canvasW}×${canvasH}) does not match canvas arg (${canvas.width}×${canvas.height})`,\n );\n }\n // The shape is checked before `.length` or an entry is read, so a plain-JS\n // caller with a malformed profile gets the path-qualified message rather\n // than a TypeError out of faceRowProfile.\n if (layer.rowHalfWidths !== undefined) {\n const rows: unknown = layer.rowHalfWidths;\n if (!Array.isArray(rows)) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" rowHalfWidths must be an array with one entry per crop row`,\n );\n }\n if (rows.length !== cropH) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" rowHalfWidths has ${rows.length} entries, not one per crop row (cropH ${cropH})`,\n );\n }\n for (let i = 0; i < rows.length; i++) {\n const a: unknown = rows[i];\n if (\n typeof a !== \"number\" ||\n !Number.isFinite(a) ||\n a < 0 ||\n a > cropW / 2\n ) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" rowHalfWidths[${i}] is ${String(a)}, not a finite number in [0, ${cropW / 2}] (half the crop's width)`,\n );\n }\n }\n }\n // Checked on every layer that carries one, though only the nose's is read,\n // so a malformed box is refused rather than silently ignored.\n if (layer.denseCore !== undefined) {\n const where = `auto-rig: validateLayerInputs: role \"${role}\" denseCore`;\n const core: unknown = layer.denseCore;\n if (typeof core !== \"object\" || core === null || Array.isArray(core)) {\n throw new Error(`${where} must be a plain object { x, y, w, h }`);\n }\n for (const field of [\"x\", \"y\", \"w\", \"h\"] as const) {\n const v = (core as Record<string, unknown>)[field];\n const size = field === \"w\" || field === \"h\";\n if (typeof v !== \"number\" || !Number.isFinite(v) || (size && v <= 0)) {\n throw new Error(\n `${where}.${field} is ${String(v)}, not a finite number${size ? \" > 0\" : \"\"}`,\n );\n }\n }\n const { x, y, w, h } = layer.denseCore;\n const right = bbox.x + bbox.w;\n const bottom = bbox.y + bbox.h;\n if (x < bbox.x) {\n throw new Error(\n `${where}.x (${x}) lies left of the bbox's left edge (${bbox.x})`,\n );\n }\n if (y < bbox.y) {\n throw new Error(\n `${where}.y (${y}) lies above the bbox's top edge (${bbox.y})`,\n );\n }\n if (x + w > right) {\n throw new Error(\n `${where}.w (${w}) puts its right edge at ${x + w}, past the bbox's right edge (${right})`,\n );\n }\n if (y + h > bottom) {\n throw new Error(\n `${where}.h (${h}) puts its bottom edge at ${y + h}, past the bbox's bottom edge (${bottom})`,\n );\n }\n }\n }\n}\n\n// ── validateStrandEdges ───────────────────────────────────────────────────────\n\n/**\n * One side's iris and the bangs' run it would slide under on the turn, as\n * real pixel boundaries on ONE image row — the row holding that iris's centre\n * — in model coordinates: x is canvas x minus half the canvas width, like\n * `bboxToTransform`'s crop edges, and `y` is that row's centre. Every x is the\n * painted edge facing its clear neighbour, read under one opacity rule, so the\n * gap between an iris edge and a run edge is a real distance with no margin to\n * add. \"Outer\" is toward the head's edge on that side. The run is the one a\n * producer judges a strand: it may skip runs it judges hair detail\n * (`@ikijs/mcp` treats those narrower than half the iris's painted width on\n * the row as clear), so an iris under only such a wisp counts as clear.\n *\n * One shape covers three cases:\n * - an ordinary side strand outward of a clear iris (`runFace` outward of\n * `irisOuter`);\n * - a run over the iris centre that clears on the face side (`runFace`\n * inward of that centre);\n * - a fringe spanning the face: the run still covers the OTHER iris's\n * centre, so it has no face-side end on this side and `runFace` is\n * `null` — an absent edge, not an invented one.\n */\nexport interface IrisStrand {\n /** The iris row's centre, model y. */\n y: number;\n /** The outer end of that iris layer's opaque span on the row. */\n irisOuter: number;\n /** Its inner end, toward the face. */\n irisInner: number;\n /** The outer end of the `hair_front` run the iris would slide under. */\n runOuter: number;\n /** The run's face-side end, or `null` when it reaches the other iris's\n * centre. */\n runFace: number | null;\n}\n\n/**\n * Check `options.strandEdges` against the layers it claims to measure — called\n * right after `validateLayerInputs`, so nothing is derived from an unchecked\n * edge. Absent or `{}` returns at once, with no layer requirement: the rig is\n * then the one built without it, byte for byte.\n *\n * Otherwise the option has to be a plain object, and each PRESENT side (an\n * absent one is never checked) has to be a real measurement on these layers:\n * - a plain object whose `y`, `irisOuter`, `irisInner` and `runOuter` are\n * finite and whose `runFace` is finite or `null`;\n * - on a layer set with `hair_front`, `iris_L` and `iris_R` — both irises,\n * because a side's iris is whichever has its crop centre on that side,\n * paired by x rather than by role name;\n * - `y` inside that iris's crop rows and inside `hair_front`'s;\n * - both iris edges inside that iris's crop columns and `irisOuter`\n * strictly outward of `irisInner` — which ties them to their own iris\n * without asking where its span sits against the crop's centre, which a\n * row's span need not straddle;\n * - `runOuter` and a finite `runFace` inside `hair_front`'s crop columns;\n * - `runOuter` strictly outward of that iris's crop centre — a spanning run\n * too, so it is still anchored on its own side — and strictly outward of\n * a finite `runFace`;\n * - a finite `runFace` strictly on this side of the other iris's centre,\n * since a run that reaches it has no face-side end on this side.\n *\n * Throws a plain `Error` with a path-qualified message on the first violation.\n */\nfunction validateStrandEdges(\n layers: LayerInput[],\n strandEdges: { left?: IrisStrand; right?: IrisStrand } | undefined,\n): void {\n if (strandEdges === undefined) return;\n const where = \"auto-rig: validateStrandEdges: strandEdges\";\n const isPlainObject = (v: unknown): v is Record<string, unknown> =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\n if (!isPlainObject(strandEdges)) {\n throw new Error(\n `${where} must be a plain object with optional left / right sides`,\n );\n }\n for (const key of [\"left\", \"right\"] as const) {\n const raw: unknown = strandEdges[key];\n if (raw === undefined) continue;\n const path = `${where}.${key}`;\n if (!isPlainObject(raw)) {\n throw new Error(`${path} must be a plain object`);\n }\n for (const field of [\"y\", \"irisOuter\", \"irisInner\", \"runOuter\"] as const) {\n const v = raw[field];\n if (typeof v !== \"number\" || !Number.isFinite(v)) {\n throw new Error(\n `${path}.${field} is ${String(v)}, not a finite number`,\n );\n }\n }\n const rawFace = raw.runFace;\n if (\n rawFace !== null &&\n (typeof rawFace !== \"number\" || !Number.isFinite(rawFace))\n ) {\n throw new Error(\n `${path}.runFace is ${String(rawFace)}, not a finite number or null`,\n );\n }\n const { y, irisOuter, irisInner, runOuter, runFace } =\n raw as unknown as IrisStrand;\n\n const cropOf = (role: string) => {\n const layer = layers.find((l) => l.role === role);\n if (layer === undefined) {\n throw new Error(\n `${path} needs a \"${role}\" layer to be measured on, and this layer set has none`,\n );\n }\n const t = bboxToTransform(layer.bbox, layer.canvasW, layer.canvasH, role);\n return {\n role,\n x: t.x,\n left: t.x - layer.cropW / 2,\n right: t.x + layer.cropW / 2,\n bottom: t.y - layer.cropH / 2,\n top: t.y + layer.cropH / 2,\n };\n };\n const hair = cropOf(\"hair_front\");\n const [lowIris, highIris] = [cropOf(\"iris_L\"), cropOf(\"iris_R\")].sort(\n (a, b) => a.x - b.x,\n );\n // Outward is −x on the left side, +x on the right.\n const sign = key === \"left\" ? -1 : 1;\n const iris = key === \"left\" ? lowIris : highIris;\n const other = key === \"left\" ? highIris : lowIris;\n const fail = (field: keyof IrisStrand, value: number, why: string) =>\n new Error(`${path}.${field} (${value}) ${why}`);\n\n for (const crop of [iris, hair]) {\n if (y < crop.bottom || y > crop.top) {\n throw fail(\n \"y\",\n y,\n `lies outside the \"${crop.role}\" crop's rows (${crop.bottom}…${crop.top})`,\n );\n }\n }\n for (const [field, x] of [\n [\"irisOuter\", irisOuter],\n [\"irisInner\", irisInner],\n ] as const) {\n if (x < iris.left || x > iris.right) {\n throw fail(\n field,\n x,\n `lies outside the \"${iris.role}\" crop's columns (${iris.left}…${iris.right})`,\n );\n }\n }\n if (sign * (irisOuter - irisInner) <= 0) {\n throw fail(\n \"irisOuter\",\n irisOuter,\n `must lie strictly outward of strandEdges.${key}.irisInner (${irisInner})`,\n );\n }\n for (const [field, x] of [\n [\"runOuter\", runOuter],\n [\"runFace\", runFace],\n ] as const) {\n if (x !== null && (x < hair.left || x > hair.right)) {\n throw fail(\n field,\n x,\n `lies outside the \"hair_front\" crop's columns (${hair.left}…${hair.right})`,\n );\n }\n }\n if (sign * (runOuter - iris.x) <= 0) {\n throw fail(\n \"runOuter\",\n runOuter,\n `must lie strictly outward of the \"${iris.role}\" crop's centre (${iris.x})`,\n );\n }\n if (runFace !== null && sign * (runOuter - runFace) <= 0) {\n throw fail(\n \"runOuter\",\n runOuter,\n `must lie strictly outward of strandEdges.${key}.runFace (${runFace})`,\n );\n }\n if (runFace !== null && sign * (runFace - other.x) <= 0) {\n throw fail(\n \"runFace\",\n runFace,\n `must lie strictly on this side of the \"${other.role}\" crop's centre (${other.x}); a run that reaches it has no face-side end here, which is runFace null`,\n );\n }\n }\n}\n\n// ── generateGridPoints ────────────────────────────────────────────────────────\n\n/**\n * Generate the flat `[x0,y0, x1,y1, …]` rest-grid control points for a\n * regular axis-aligned lattice with `(cols+1)*(rows+1)` points, row-major.\n *\n * Row 0 is the TOP (y = maxY); y strictly decreases with row index.\n * Column 0 is left (x = minX); x strictly increases with column index.\n * This ordering satisfies `checkGridRegularity` in the format validator.\n *\n * Local copy — do NOT import the private `generateRegularGridPoints` from\n * factories.ts; that helper is private to this package's factory layer.\n */\nexport function generateGridPoints(\n cols: number,\n rows: number,\n minX: number,\n maxX: number,\n minY: number,\n maxY: number,\n): number[] {\n const pts: number[] = [];\n for (let row = 0; row <= rows; row++) {\n const t = row / rows;\n const y = maxY - t * (maxY - minY); // maxY at row 0, minY at row `rows`\n for (let col = 0; col <= cols; col++) {\n const s = col / cols;\n const x = minX + s * (maxX - minX); // minX at col 0, maxX at col `cols`\n pts.push(x, y);\n }\n }\n return pts;\n}\n\n// ── createPixelGridMesh ───────────────────────────────────────────────────────\n\n/**\n * Create a regular grid mesh in PIXEL space, with the local origin at the\n * crop center (matching the `feature(...)` convention in sample-model.ts).\n *\n * Callers set `part.width=1, part.height=1` so the engine's scale pipeline is\n * bypassed — the pixel coordinates ARE the final geometry, positioned only by\n * `part.transform`. scaleX/scaleY bindings then scale about each part's own\n * center without an additional unit-to-pixel conversion step.\n *\n * Vertices span x ∈ [-w/2, w/2] and y ∈ [-h/2, h/2] (+y up, engine convention).\n * Row 0 is the TOP of the grid (y = +h/2); row index increases downward.\n *\n * UVs are base unit-square coordinates: u = col/cols (0..1 left→right),\n * v = row/rows (0..1 top→bottom). Top row maps to v=0 (v and y run in\n * opposite directions — keeps textures upright). Atlas remapping is the\n * caller's responsibility (e.g. applyAtlas), not done here.\n *\n * Index winding per cell: [BL, BR, TL] then [TL, BR, TR] — same as\n * `createGridMesh` in factories.ts so the engine's implicit-quad convention\n * is preserved.\n */\nexport function createPixelGridMesh(\n cols: number,\n rows: number,\n w: number,\n h: number,\n): IkiMesh {\n const colVerts = cols + 1;\n const rowVerts = rows + 1;\n\n const vertices: number[] = [];\n const uvs: number[] = [];\n\n // Row 0 = TOP (y = +h/2). Row `rows` = BOTTOM (y = -h/2).\n // Col 0 = left (x = -w/2). Col `cols` = right (x = +w/2).\n for (let row = 0; row < rowVerts; row++) {\n const t = row / rows;\n const y = h / 2 - t * h; // +h/2 at row 0, -h/2 at row `rows`\n const v = t; // 0 at top, 1 at bottom\n\n for (let col = 0; col < colVerts; col++) {\n const s = col / cols;\n const x = -w / 2 + s * w; // -w/2 at col 0, +w/2 at col `cols`\n const u = s; // 0 at left, 1 at right\n\n vertices.push(x, y);\n uvs.push(u, v);\n }\n }\n\n // Two triangles per cell: [BL, BR, TL] then [TL, BR, TR]\n const indices: number[] = [];\n for (let row = 0; row < rows; row++) {\n for (let col = 0; col < cols; col++) {\n const tl = row * colVerts + col;\n const tr = row * colVerts + col + 1;\n const bl = (row + 1) * colVerts + col;\n const br = (row + 1) * colVerts + col + 1;\n\n indices.push(bl, br, tl);\n indices.push(tl, br, tr);\n }\n }\n\n return { vertices, uvs, indices };\n}\n\n// ── Mesh density ─────────────────────────────────────────────────────────────\n\n/** Target size of one mesh cell. A part's vertices sample its group's turn\n * grid (and its own per-vertex warps) at this spacing; the GPU is linear\n * between them, so the spacing bounds how smooth a bend can render. 64 px\n * keeps a face-mesh cell near a plate-grid cell on the hero (67 px against\n * the 45 px FACE_PLATE_CELLS gives its plate) and puts eight rows on the back\n * hair for the sway curl. */\nconst MESH_CELL_PX = 64;\n/** Cell-count floor / ceiling per axis: small parts stay at 4×4 (their\n * bakes fold/collapse them, they never bend), a canvas-spanning layer is\n * capped at 81 vertices so per-vertex warps stay cheap. */\nconst MESH_CELLS_MIN = 4;\nconst MESH_CELLS_MAX = 8;\n\n/** Cell counts for a pixel-grid mesh of a `w`×`h` crop: `round(size / MESH_CELL_PX)`\n * per axis, clamped to [MESH_CELLS_MIN, MESH_CELLS_MAX]. Exported for tests. */\nexport function meshCellsFor(\n w: number,\n h: number,\n): { cols: number; rows: number } {\n const cells = (px: number) =>\n Math.max(\n MESH_CELLS_MIN,\n Math.min(MESH_CELLS_MAX, Math.round(px / MESH_CELL_PX)),\n );\n return { cols: cells(w), rows: cells(h) };\n}\n\n/** Cells per axis of the face plate's turn grid (`faceWarp`). Bilinear cells\n * render the cylinder bend as a chord: the shared 6-cell grid over the whole\n * head put 137 px cells under the hero's plate; 10 cells over the plate's own\n * 450 px are 45 px chords, under 0.3 px of sag at full turn, and keep the\n * nod's chord under 0.35 px on the same rows. The plate is the one group\n * read at every guard row and under the bangs' join, so it gets the most\n * cells: 121 nodes × 25 stops × 2 is ≈6 000 keyform numbers, ≈0.1 MB at JSON\n * precision. 8 is the fallback if the byte budget (1.4 MB on the hero)\n * bites. The fold guard is the cylinder's own bound and does not depend on\n * this. */\nconst FACE_PLATE_CELLS = 10;\n/** Cells per axis of every feature group's turn grid. A feature grid spans a\n * part a few tens of px across plus its bindings' reach and its turn shift,\n * so 4 cells are 20–60 px chords — under 0.2 px of sag — and the hero's six\n * feature groups at 25 nodes each add ≈7 500 keyform numbers: with the\n * plate's, ≈0.2 MB over the shared grid they replace, which took the hero\n * from 1.09 MB to ≈1.3 MB. */\nconst FEATURE_GRID_CELLS = 4;\n/** Column pitch of the turn's VIRTUAL lattice — the one row of columns\n * `TurnSurface.mapAt` is piecewise-linear between, anchored on the face\n * centre (`faceCenterX ± k·16`) so the axis is a node and its dx the slide\n * exactly. Never shipped: every group grid's node samples it. At 16 px the\n * chord of the bend stays under 0.25 px at any radius the sweep tries\n * (≈0.2 px at the hero plate's edge, 0.0005 in cue units), so the map IS the\n * analytic bend as far as a cue can tell, where a coarser lattice would put\n * its kinks wherever a group grid's nodes happened to fall. */\nconst TURN_LATTICE_CELL_PX = 16;\n/** Margin a grid is grown by, as a fraction of its span per axis: the turn\n * family's union for the head's vertical reach, and every group grid on top\n * of the extent its members can reach (`groupGridFor`), so no vertex lands\n * on a grid boundary and gets clamped by `bindPointToRestGrid`. */\nconst GRID_MARGIN = 0.12;\n\n// ── Head-turn cylinder constants ─────────────────────────────────────────────\n\n/** Margin between a cylinder's radius and the reach it has to cover: at\n * |local|/radius <= 1/1.2 the asin stays clear of +/-1 and asin(1/1.2) + 30°\n * is still under 90°, so the bend never folds. The nod radius is derived from\n * the head's vertical reach this way; the turn radius is an input, and\n * radius/factor is the bound `boundedCylinderBend` clamps `local` to. */\nconst HEAD_CYLINDER_RADIUS_FACTOR = 0.6 / 0.5;\n/** Outer keyform stop of the head-turn bake, matching ParamAngleX's range. */\nconst HEAD_TURN_MAX_DEG = 30;\n\n/** Keyform stops of the head-turn / nod bakes, degrees. The engine blends\n * parameter-LINEARLY between stops but the cylinder bend is sin-based, so the\n * stops must be dense enough that the chord stays near the arc: at 15° spacing\n * the worst mid-stop error on the hero's grid is ≈4 px at the outer column\n * (3.7 px at 7.5°, 4.0 px at 22.5°) against 15.5 px with stops at ±30 only.\n * The bake is analytic, so extra stops cost only model bytes. Ascending,\n * symmetric, includes 0 (the rest cell). */\nconst HEAD_TURN_STOPS = [-30, -15, 0, 15, 30] as const;\n\n/**\n * Sideways slide, at full head turn, of a surface sitting one full cylinder\n * radius in front of the rotation axis — the unit of depth parallax.\n *\n * This is exactly the bulk `axisShift` the cylinder bake subtracts back out of\n * the face warp. Pinning it there was right: applied to the face IN FULL it\n * shoved the head off the shoulders. But it is also the whole depth cue, so\n * layers that do NOT sit on the cylinder's axis have to get their own share of\n * it back, scaled by how far in front of (or behind) the axis they sit — the\n * bangs (HAIR_FRONT_DEPTH) and the features on the face (the solved\n * TurnDepths). The head's own travel is not one of those shares: it rides the\n * same bake as a separate uniform slide over every column (`turnSlide`), sized\n * by what the held shell has room for rather than by a depth.\n * `headNodParallaxUnit` is the same quantity on the nod axis.\n *\n * Takes the cylinder radius the face warp was baked with, so the two cannot\n * drift apart: the unit IS that bake's pinned-out `axisShift`.\n */\nexport function headTurnParallaxUnit(radius: number): number {\n return radius * Math.sin(HEAD_TURN_MAX_DEG * (Math.PI / 180));\n}\n\n// ── bakeHeadTurnGridWarpCentered ──────────────────────────────────────────────\n\n/**\n * Bake a cylinder head-turn grid warp for ParamAngleX, center-relative.\n *\n * WHY a cylinder: rotating a flat face mesh looks right head-on but the\n * silhouette doesn't narrow at the sides; projecting each point onto a\n * cylinder and rotating makes the face foreshorten naturally as it turns.\n *\n * HOW (center-relative): the cylinder axis sits at `centerX` (the face center\n * in model space). Each grid point at absolute x has local x = x - centerX,\n * which maps onto the cylinder. After rotating by theta, the new absolute x is:\n * xPrime = centerX + radiusX * sin(asin(localX/radiusX) + theta)\n * dx = xPrime - x, dy = 0\n *\n * At theta=0 the center keyform is all-zero (xPrime === x by identity).\n *\n * The axis column is pinned: the bulk sideways slide a cylinder rotation\n * produces is subtracted out, leaving only the foreshortening. See the comment\n * at the subtraction for why.\n *\n * `radiusX` is the caller's: how round the head reads is a modelling choice,\n * not a property of how far the grid happens to reach. Columns beyond what the\n * radius can carry ride along rigidly — see `boundedCylinderBend`.\n *\n * No production caller since faceWarp moved to the 2D bake\n * (`bakeTurnGroupWarp2D`); kept as the PURE BEND reference that bake's tests\n * compare their AngleY = 0 row against, keyed on the same `HEAD_TURN_STOPS`.\n * That row equals this bake only at `travel = 0`: the bend is all the shipped\n * row has once its uniform sideways slide is taken back out.\n */\nexport function bakeHeadTurnGridWarpCentered(\n grid: IkiWarpGrid,\n parameter: string,\n centerX: number,\n radiusX: number,\n): IkiGridWarp {\n // Keyform stops (degrees), matching ParamAngleX's −30..30 range.\n const ANGLES = HEAD_TURN_STOPS;\n\n const pointCount = grid.points.length / 2;\n const DEG_TO_RAD = Math.PI / 180;\n\n const keyforms = ANGLES.map((angleDeg) => {\n const theta = angleDeg * DEG_TO_RAD;\n const offsets: number[] = [];\n for (let i = 0; i < pointCount; i++) {\n const dx = boundedCylinderBend(\n grid.points[i * 2] - centerX,\n radiusX,\n theta,\n );\n // dy is zero — cylinder bend only deforms horizontal position.\n offsets.push(dx, 0);\n }\n return { value: angleDeg, offsets };\n });\n\n // keyforms are sorted ascending by construction (HEAD_TURN_STOPS).\n return { parameter, keyforms };\n}\n\n/**\n * Displacement of a point at signed distance `local` from the cylinder axis\n * after the cylinder rotates by `theta`, with the axis column pinned.\n *\n * Rotating a cylinder slides its whole visible surface sideways by\n * RADIUS*sin(theta) — 170px on a 430px-wide face at 30 degrees — on top of the\n * foreshortening. That bulk slide is what shoves the head off the shoulders and\n * detaches the back hair; the foreshortening alone is what reads as a turn.\n * Subtracting it leaves the differential and keeps the offsets monotonic, so no\n * cell folds. Deliberate bulk head motion is added back on its own, where it can\n * be tuned independently — the turn's as the 2D bake's uniform `travel` slide,\n * the nod's as a headDeformer translate — and the bangs get their\n * depth-scaled share of it back through `headTurnParallaxUnit`.\n */\nfunction pinnedCylinderBend(\n local: number,\n radius: number,\n theta: number,\n): number {\n // Clamp local/radius to [-1,1] to keep asin defined at boundary points.\n const alpha = Math.asin(Math.max(-1, Math.min(1, local / radius)));\n return radius * Math.sin(alpha + theta) - local - radius * Math.sin(theta);\n}\n\n/**\n * `pinnedCylinderBend` with the surface bounded to the part of the cylinder it\n * can actually carry: |local| is clamped to radius/HEAD_CYLINDER_RADIUS_FACTOR\n * and the overshoot rides along unchanged.\n *\n * Beyond that bound the surface has wrapped past the cylinder's side and the\n * bend turns back on itself: once alpha + theta passes 90° a point further out\n * lands NEARER the axis than the one inside it, and once the asin saturates\n * every remaining point piles onto the SAME x (the `- local` term cancels).\n * Either way the grid folds. Holding the bound's displacement and adding the\n * overshoot back gives those columns slope 1: they follow the silhouette\n * rigidly, keeping their order and their spacing. Inside the bound nothing\n * changes — a radius scaled from the grid's own reach puts the bound exactly\n * on the outer columns, so the generated rig never leaves the surface.\n */\nfunction boundedCylinderBend(\n local: number,\n radius: number,\n theta: number,\n): number {\n const bound = radius / HEAD_CYLINDER_RADIUS_FACTOR;\n const onSurface = Math.max(-bound, Math.min(bound, local));\n // The return is a DISPLACEMENT, so `local + dx` already carries the\n // overshoot: holding the bound's dx is what makes the outside rigid.\n return pinnedCylinderBend(onSurface, radius, theta);\n}\n\n/** The head's uniform sideways slide at one turn stop: the whole `travel` at\n * ±HEAD_TURN_MAX_DEG, linear in between, none at rest. The bake and\n * `turnColumnMap` share it so the map cannot describe a slide the grid does\n * not carry. */\nfunction turnSlide(travel: number, angleX: number): number {\n return (travel * angleX) / HEAD_TURN_MAX_DEG;\n}\n\n// ── faceRowProfile ───────────────────────────────────────────────────────────\n\n/** Floor of a face row's half-width, as a fraction of the widest row's. The\n * rows under the chin are sub-threshold shading and near-empty rows whose\n * own span would bend them on a cylinder a few px wide; the floor keeps\n * every row's radius a usable fraction of the eye row's. */\nconst FACE_ROW_MIN_FRACTION = 0.15;\n/** Width of the centred moving average over the measured rows, as a fraction\n * of the crop's height: wide enough to take the pixel steps out of an\n * alpha-thresholded outline, narrow enough to leave the jaw's taper. */\nconst FACE_ROW_SMOOTHING = 0.05;\n/** How far the chin swings toward the NEAR side at full turn, as a fraction\n * of the plate's half-width, scaled by how much narrower than the widest row\n * a row is (`TurnSurface.swingAt`): a turned head's jaw comes round with it\n * while the cranium stays. Judged on the hero two-up (slice ④): 0.05\n * under-read the 30° reference's chin, 0.08 is the ladder value that reads. */\nconst CHIN_SWING = 0.08;\n\n/**\n * The face plate's painted half-width per row, read off its layer's\n * `rowHalfWidths` (`LayerInput`) in model y. Exported at module level for the\n * tests, not from the package.\n */\nexport interface FaceRowProfile {\n /** The painted half-width, px, on the row resting at model `y`: linear\n * between rows, the end rows beyond the crop. */\n at(y: number): number;\n /** The widest row's half-width — what every row above it reads. */\n aMax: number;\n /** Model y of the widest row (the lowest of them when several tie): rows\n * ABOVE it — greater y, model y running up — read `aMax`; rows at and\n * below it are the measured ones, and they alone taper and swing. */\n widestY: number;\n /** The crop's own half-width, px: the bound every entry respects and the\n * width the chin swing is a fraction of. */\n faceHalfWidth: number;\n}\n\n/**\n * The face's row profile, or `undefined` when its layer measured none — or\n * measured no opaque row at all, which is the same thing to the turn.\n *\n * The face layer's alpha is NOT the head's width on every row: the hairline\n * rows sit under the bangs (on the hero the top crop row reads 6 % of the\n * cheek's half-width) and the rows under the chin are neck shading below the\n * threshold. Bent on their own span those rows would ride a cylinder a few px\n * wide and the far forehead would land OUTSIDE the cheek at full turn — a\n * cranium bulging past the face. So: the widest row is found among the raw\n * positive entries; every row ABOVE it reads that width (the cranium is as\n * wide as the cheeks; only the jaw tapers); the rows at and below it are the\n * measured ones, an empty row filled from the nearest positive row above it,\n * smoothed by a centred moving average FACE_ROW_SMOOTHING of the crop's\n * height wide (an odd window of at least three rows, the end rows repeated\n * past the crop) and floored at FACE_ROW_MIN_FRACTION of the widest. `at` is\n * linear between the rows' centres and clamps to the end rows outside the\n * crop, so a grid node on the plate's margin reads the row nearest it.\n */\nexport function faceRowProfile(\n face: LayerInput,\n faceCenterY: number,\n): FaceRowProfile | undefined {\n const raw = face.rowHalfWidths;\n if (raw === undefined) return undefined;\n const n = raw.length;\n let aMax = 0;\n let widest = -1;\n for (let i = 0; i < n; i++) {\n if (raw[i] > 0 && raw[i] >= aMax) {\n aMax = raw[i];\n widest = i;\n }\n }\n if (widest < 0) return undefined;\n\n // Above the widest row aMax; at and below it the measurement, an empty row\n // taking the nearest positive row above it (the widest row is positive, so\n // there always is one).\n const filled: number[] = [];\n for (let i = 0; i < n; i++) {\n filled.push(i < widest ? aMax : raw[i] > 0 ? raw[i] : filled[i - 1]);\n }\n const half = Math.max(1, Math.round((FACE_ROW_SMOOTHING * n) / 2));\n const floor = FACE_ROW_MIN_FRACTION * aMax;\n const rows: number[] = [];\n for (let i = 0; i < n; i++) {\n if (i < widest) {\n rows.push(aMax);\n continue;\n }\n let sum = 0;\n for (let k = i - half; k <= i + half; k++) {\n sum += filled[Math.max(0, Math.min(n - 1, k))];\n }\n rows.push(Math.max(floor, sum / (2 * half + 1)));\n }\n\n // Crop row i is the pixel row whose centre rests at top − i − ½: image rows\n // run down where model y runs up.\n const top = faceCenterY + face.cropH / 2;\n const rowAt = (y: number) => Math.max(0, Math.min(n - 1, top - y - 0.5));\n return {\n at: (y) => {\n const r = rowAt(y);\n const i = Math.floor(r);\n if (i + 1 >= n) return rows[n - 1];\n return rows[i] + (rows[i + 1] - rows[i]) * (r - i);\n },\n aMax,\n widestY: top - widest - 0.5,\n faceHalfWidth: face.cropW / 2,\n };\n}\n\n// ── TurnSurface ───────────────────────────────────────────────────────────────\n\n/**\n * The analytic head every bake samples and the solve fits: a turn cylinder\n * about the vertical axis through `faceCenterX` and a nod cylinder about the\n * horizontal axis through `faceCenterY`, each read at a point's REST position.\n *\n * `mapAt` is the turn as `turnColumnMap` describes it on the `lattice`'s\n * columns — the bounded bend on the row's own radius plus the slide of\n * `travel` and the row's own chin swing — so `turnColumnMap` /\n * `pinnedCylinderBend` / `boundedCylinderBend` stay the primitive and\n * `solveTurnModel` stays the fitter: it fits `radius` and `travel` on these\n * very maps, and `bakeTurnGroupWarp2D` reads its dx off the same ones, which\n * is what makes a solved cue a promise about the shipped keyforms. The\n * lattice is VIRTUAL — a dense row of columns anchored on the face centre\n * (`TURN_LATTICE_CELL_PX`) that nothing ships: every group grid's node reads\n * the map at its own rest x, and a rendered vertex reads those nodes\n * bilinearly, which is what the solve reproduces through a landmark's carrier\n * (`TurnLandmark.carrier`).\n */\ninterface TurnSurface {\n faceCenterX: number;\n faceCenterY: number;\n /** Turn cylinder radius at the EYE ROW, px: solved, or the lattice's own\n * reach with the no-fold margin when there is nothing to solve against.\n * `radiusAt` scales it per row. */\n radius: number;\n /** The head's sideways travel at full turn, px — see `turnSlide`. */\n travel: number;\n /** Nod cylinder radius, px: the head's vertical reach about `faceCenterY`\n * with the no-fold margin — one value for the whole rig, sized where the\n * turn family's union is (`turnSetup`). */\n nodRadius: number;\n /** The columns `mapAt` is piecewise-linear between; row 0 is all it reads. */\n lattice: IkiWarpGrid;\n /** The turn cylinder's radius on the row resting at `y`: `radius` scaled by\n * that row's painted half-width over the eye row's (`FaceRowProfile`), so\n * every row's own painted edge lands at the SAME fraction of its\n * half-width; `radius` on every row without a profile. */\n radiusAt(y: number): number;\n /** The chin's swing on the row resting at `y`, as a signed addition to\n * `travel`: −CHIN_SWING · faceHalfWidth · (1 − a(y)/aMax) for a row BELOW\n * the widest (smaller y), 0 at and above it and everywhere without a\n * profile. Negative, so it opposes the slide — at −30° the far side is −x\n * and the slide is −travel, and the swing's `turnSlide` share is a POSITIVE\n * offset, toward the near side — and ∝ deg like the slide, so it vanishes\n * at rest. */\n swingAt(y: number): number;\n /** The turn's column map at `deg` for a point resting at `y`: the bend on\n * `radiusAt(y)` plus `turnSlide(travel + swingAt(y), deg)` — one map per\n * row (`rowMapsOf` memoises them), every row the same one without a\n * profile. */\n mapAt(deg: number, y: number): TurnColumnMap;\n /** The nod's vertical displacement of a point resting at `y`, at `angleY`\n * degrees of ParamAngleY: the pinned cylinder bend about `faceCenterY` at\n * NOD_BEND of the angle — see that constant for why a full nod is not a\n * full 30° bend. */\n nodBendAt(y: number, angleY: number): number;\n}\n\n/** A `TurnSurface` from its numbers; the samplers are derived from them and\n * nothing else is. Exported at module level for the bake tests, not from the\n * package. */\nexport function turnSurface(spec: {\n faceCenterX: number;\n faceCenterY: number;\n radius: number;\n travel: number;\n nodRadius: number;\n lattice: IkiWarpGrid;\n /** The face's own row profile, when its layer measured one. Without it\n * every row turns on `radius` with no swing — slice ③'s bake, byte for\n * byte. */\n profile?: FaceRowProfile;\n /** The row `radiusAt` is normalised at — the eye row the solve fits, so the\n * report stays a promise about that row. Read only with a `profile`; the\n * face centre when not given. */\n eyeRowY?: number;\n}): TurnSurface {\n const { faceCenterX, faceCenterY, radius, travel, nodRadius, lattice } = spec;\n const { profile } = spec;\n let radiusAt = (_y: number) => radius;\n let swingAt = (_y: number) => 0;\n if (profile !== undefined) {\n // Normalised at the eye row: a(y)/radiusAt(y) = a(eyeRow)/radius on every\n // row, and a(eyeRow) ≤ faceHalfWidth ≤ radius/HEAD_CYLINDER_RADIUS_FACTOR\n // (the sweep's floor), so every row's own painted edge stays on the\n // analytic branch of boundedCylinderBend. The ratio is taken first so a\n // row as wide as the eye row reads `radius` exactly, not to an ulp.\n const aEye = profile.at(spec.eyeRowY ?? faceCenterY);\n radiusAt = (y) => radius * (profile.at(y) / aEye);\n // Below the widest row only (Decision 5): the gate is what keeps the\n // cranium — every row reading aMax, where the term is 0 anyway — free of\n // the swing by construction. It bites only where the smoothing pulls the\n // widest row itself under aMax (two 192/96 bands: 145.5 there), so the\n // swing steps in across that one row by the smoothing's own amount.\n swingAt = (y) =>\n y < profile.widestY\n ? -CHIN_SWING *\n profile.faceHalfWidth *\n Math.max(0, 1 - profile.at(y) / profile.aMax)\n : 0;\n }\n return {\n faceCenterX,\n faceCenterY,\n radius,\n travel,\n nodRadius,\n lattice,\n radiusAt,\n swingAt,\n mapAt: (deg, y) =>\n turnColumnMap(\n lattice,\n faceCenterX,\n radiusAt(y),\n deg,\n travel + swingAt(y),\n ),\n nodBendAt: (y, angleY) =>\n pinnedCylinderBend(\n y - faceCenterY,\n nodRadius,\n angleY * NOD_BEND * (Math.PI / 180),\n ),\n };\n}\n\n// ── bakeTurnGroupWarp2D ───────────────────────────────────────────────────────\n\n/**\n * Bake one group's turn AND nod as a 2D grid warp over AngleX × AngleY, every\n * node of `grid` read off the `surface` at its own rest position: the turn's\n * offset is where `groupNodeLanding` lands the node on that stop's maps — the\n * family shifted by `shiftAt(deg)`, the group's own turn shift at that stop, 0\n * for a group with none, and turned about `anchor` when the group has one —\n * less the node's rest position, the same rule the solve reads these nodes\n * back by; the nod adds its bend at the node's rest y to dy.\n *\n * The turn and the nod add, the same convention the playground's 2D bake\n * ships: the row at AngleY = 0 is the turn alone and the column at AngleX = 0\n * the nod alone. At a turned stop a node's turn offset may depend on both of\n * its rest coordinates — dx through the map read on the node's own row (its\n * radius and chin swing), and an anchored group's dx and dy both through its\n * tilt — while the nod moves a node on y alone, by an amount set by its rest\n * y and the pitch.\n *\n * Each axis's term at its own rest stop — the landing's at AngleX 0, the\n * nod's at AngleY 0 — is written as a literal zero rather than evaluated, and\n * never added to the other axis's term, so a cell with one turned axis carries\n * exactly that axis's number: at rest each axis is the identity by definition,\n * and `R·sin(asin(l/R)) − l` leaves a ≈3e-14 residue that would otherwise\n * ship — the hero's rest cell used to carry seven of them. That rule discards\n * `shiftAt(0)` too, so it assumes `shiftAt(0) = 0` — which every turn shift\n * satisfies by construction, being proportional to the stop's own degrees\n * (`depth · unit · deg / 30`) — the row's chin swing with it, which rides\n * `turnSlide` and is 0 at the 0° stop the same way, and an anchored group's\n * tilt, which is proportional to the stop too.\n *\n * The surface's `travel` belongs in the grid rather than on a headDeformer\n * translate because a rigid head translate carries the hair shell along with\n * it: in the grid only what rides the grid slides, so the face plate and its\n * features travel inside a silhouette the bangs hold still\n * (`bakeHairFrontSilhouetteWarp`), which is what a turned head actually does.\n * Being uniform it bends nothing — the AngleX = 0 cells stay all-zero and dy\n * never sees it — and being linear in AngleX it is the one part of this bake\n * the engine's parameter-linear blend reproduces EXACTLY between the stops:\n * the chord error the HEAD_TURN_STOPS spacing bounds is all the bend's.\n *\n * Layout is the format's row-major `k(i, j) = j * valuesX.length + i`.\n */\nexport function bakeTurnGroupWarp2D(\n grid: IkiWarpGrid,\n parameterX: string,\n parameterY: string,\n surface: TurnSurface,\n shiftAt: (deg: number) => number,\n anchor?: TurnAnchor,\n): IkiGrid2DWarp {\n const STOPS = [...HEAD_TURN_STOPS];\n const pointCount = grid.points.length / 2;\n\n const keyforms2d: { offsets: number[] }[] = [];\n for (const angleY of STOPS) {\n for (const angleX of STOPS) {\n const shift = shiftAt(angleX);\n const mapAt = (nodeY: number) => surface.mapAt(angleX, nodeY);\n const offsets: number[] = [];\n for (let i = 0; i < pointCount; i++) {\n const x = grid.points[i * 2];\n const y = grid.points[i * 2 + 1];\n const nodDy = angleY === 0 ? 0 : surface.nodBendAt(y, angleY);\n if (angleX === 0) {\n offsets.push(0, nodDy);\n continue;\n }\n const landed = groupNodeLanding(anchor, mapAt, angleX, shift, x, y);\n const turnDy = landed.y - y;\n offsets.push(landed.x - x, angleY === 0 ? turnDy : turnDy + nodDy);\n }\n keyforms2d.push({ offsets });\n }\n }\n\n return {\n parameter: parameterX,\n parameterY,\n valuesX: STOPS,\n valuesY: STOPS,\n keyforms2d,\n };\n}\n\n// ── turnColumnMap ─────────────────────────────────────────────────────────────\n\n/** Where the head turn sends each column of a lattice, and back. */\nexport interface TurnColumnMap {\n /** Rest x of every lattice column, ascending — row 0 of `grid.points`. */\n restX: number[];\n /** Where the turn puts each of those columns: strictly increasing whenever\n * `restX` is, since the bound and the capped angle rule out a fold. */\n warpedX: number[];\n /** Rest x → turned x, for anything reading the lattice. */\n mapX(x: number): number;\n /** The inverse: which rest x lands on `X`. */\n invertX(X: number): number;\n}\n\n/**\n * The head turn as a 1D map on x: where the turn puts a point resting at x at\n * `angleX` — the bend AND the same uniform `turnSlide(travel, angleX)` the\n * bakes ship, so a caller passes the travel the rig was baked with or it\n * measures a head the rig never renders. headDeformer adds no turn translate\n * on top of it.\n *\n * It equals the shipped bakes AT the `HEAD_TURN_STOPS` on the nodes of their\n * grids, which is where callers should key: between stops the engine blends\n * the keyforms parameter-linearly (the chord the HEAD_TURN_STOPS comment\n * measures), while this is analytic, so off-stop the two differ by that chord\n * error.\n *\n * The bend depends only on a point's x, so a single row of columns describes\n * the whole surface, and the map is piecewise-linear between them the way a\n * grid child's read is: `bindPointToRestGrid` puts a point in the cell its\n * rest x falls in and `sampleWarpGrid` lerps between that cell's deformed\n * corners — so between two columns the map is a straight line, and outside the\n * outer columns the clamped (s, t) pin it to the edge column's warped x.\n * `mapX` reproduces that clamp rather than extrapolating, because a caller\n * measuring a silhouette needs where the geometry lands, not where an extended\n * cylinder would put it — and the virtual lattice is sized so nothing a bake\n * reads ever sits outside its columns (`turnSetup`).\n *\n * `invertX` answers the other direction — \"which rest x has to be here for the\n * turn to land it there\". No bake reads it any more: the bangs hold the\n * outline directly (`bakeHairFrontSilhouetteWarp`) rather than cancelling this\n * map on themselves. It clamps `X` to the warped column range, the only place\n * the inverse is defined.\n *\n * Both directions need the warped columns to stay ordered, which holds while\n * asin(1/HEAD_CYLINDER_RADIUS_FACTOR) + |theta| < 90°, i.e. |angleX| ≲ 33.6°.\n * Past that the outer columns fold and the cell search would silently pick\n * the wrong cell, so the range is capped at the parameter's own\n * HEAD_TURN_MAX_DEG rather than left to produce a quiet wrong answer.\n */\nexport function turnColumnMap(\n grid: IkiWarpGrid,\n faceCenterX: number,\n radiusX: number,\n angleX: number,\n travel: number,\n): TurnColumnMap {\n if (Math.abs(angleX) > HEAD_TURN_MAX_DEG) {\n throw new Error(\n `auto-rig: turnColumnMap: angleX ${angleX}° is outside the turn's ±${HEAD_TURN_MAX_DEG}° range`,\n );\n }\n const theta = angleX * (Math.PI / 180);\n const slide = turnSlide(travel, angleX);\n const restX: number[] = [];\n const warpedX: number[] = [];\n for (let col = 0; col <= grid.cols; col++) {\n const x = grid.points[col * 2];\n restX.push(x);\n warpedX.push(\n x + boundedCylinderBend(x - faceCenterX, radiusX, theta) + slide,\n );\n }\n\n // Same cell as bindPointToRestGrid picks: the first whose right edge is past\n // v, else the last one — found by bisection, the edges being ascending.\n const cellFor = (v: number, edges: number[]) => {\n let lo = 0;\n let hi = grid.cols - 1;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (v < edges[mid + 1]) hi = mid;\n else lo = mid + 1;\n }\n return lo;\n };\n // Piecewise-linear read of `values` over `edges` at `v`, the within-cell\n // fraction clamped to [0,1] as sampleWarpGrid clamps (s, t). A node reads\n // its own value: `values[c] + Δ·1` is `values[c + 1]` only to an ulp, and\n // the columns a bake reads are nodes.\n const sample = (edges: number[], values: number[], v: number): number => {\n const c = cellFor(v, edges);\n const s = Math.max(\n 0,\n Math.min(1, (v - edges[c]) / (edges[c + 1] - edges[c])),\n );\n if (s === 0) return values[c];\n if (s === 1) return values[c + 1];\n return values[c] + (values[c + 1] - values[c]) * s;\n };\n\n return {\n restX,\n warpedX,\n mapX: (x) => sample(restX, warpedX, x),\n invertX: (X) => sample(warpedX, restX, X),\n };\n}\n\n// ── renderedLandingX ─────────────────────────────────────────────────────────\n\n/** A mesh part as the renderer places it before any deformer moves it: its\n * rest centre and crop, `meshCellsFor`'s cell counts, and its DEFAULT-POSE\n * TRS — what `evaluateTransform` returns with every parameter at its default\n * (`defaultPoseOf`). The identity for the face and the bangs; the mouth\n * family rests at scaleX 1.1, its MouthForm range (−0.2…0.4) not being\n * centred on the parameter's default, so a caller reading it has to pass\n * that rather than assume 1. Exported at module level for the tests, not\n * from the package. */\nexport interface RenderedPart {\n x: number;\n y: number;\n cropW: number;\n cropH: number;\n cols: number;\n rows: number;\n scaleX: number;\n scaleY: number;\n rotation: number;\n}\n\n/**\n * Where a rest point `(px, py)` of a mesh part RENDERS on x, read the way the\n * engine and the GPU read it, in two stages. First `vertexLanding` lands each\n * MESH VERTEX at its PRE-BIND model position — the local vertex exactly as\n * `createPixelGridMesh` writes it, through the part's default-pose TRS in the\n * engine's own order (scale, rotate, translate): the position\n * `applyWarpToChild` binds. Then the point is interpolated over the mesh\n * TRIANGLE containing it — `createPixelGridMesh`'s [BL, BR, TL] / [TL, BR, TR]\n * split, the lower-left one when the column fraction ≤ the row fraction —\n * never bilinearly over the cell, which differs by its own cross term. A point\n * outside the mesh's rows or columns reads the end row or column with its\n * fraction clamped: the landing of the nearest boundary point, which is what a\n * silhouette point past the bangs' own crop renders as.\n *\n * Every production reader of a rendered point goes through this — the solve's\n * landmarks and silhouette candidates on their carriers, the bangs' own\n * silhouette landing, and the plate's rendered edge under the hold's join and\n * the fold guards — so a solved cue is a promise about the shipped mesh, not\n * about an analytic surface the mesh only chords.\n */\nfunction renderedLandingX(\n part: RenderedPart,\n vertexLanding: (preBindX: number, preBindY: number) => number,\n px: number,\n py: number,\n): number {\n const { cols, rows } = part;\n // Lattice fractions of the rest point: u along the columns (0 at the left\n // edge), v down the rows (0 at the top), each clamped into its end cell.\n const u = ((px - part.x + part.cropW / 2) / part.cropW) * cols;\n const v = ((part.y + part.cropH / 2 - py) / part.cropH) * rows;\n const col = Math.max(0, Math.min(cols - 1, Math.floor(u)));\n const row = Math.max(0, Math.min(rows - 1, Math.floor(v)));\n const fx = Math.max(0, Math.min(1, u - col));\n const fy = Math.max(0, Math.min(1, v - row));\n const cos = Math.cos(part.rotation * (Math.PI / 180));\n const sin = Math.sin(part.rotation * (Math.PI / 180));\n const landingAt = (c: number, r: number): number => {\n const lx = (-part.cropW / 2 + (c / cols) * part.cropW) * part.scaleX;\n const ly = (part.cropH / 2 - (r / rows) * part.cropH) * part.scaleY;\n return vertexLanding(\n part.x + cos * lx - sin * ly,\n part.y + sin * lx + cos * ly,\n );\n };\n // `fx` runs left→right and `fy` top→bottom, the axes the cell is built on,\n // so its TL→BR diagonal is `fx === fy` and the lower-left triangle is the\n // one with `fx <= fy`.\n const tl = landingAt(col, row);\n const br = landingAt(col + 1, row + 1);\n if (fx <= fy) {\n const bl = landingAt(col, row + 1);\n return tl + fy * (bl - tl) + fx * (br - bl);\n }\n const tr = landingAt(col + 1, row);\n return tl + fx * (tr - tl) + fy * (br - tr);\n}\n\n/**\n * Where a warp child's vertex at pre-bind `(x, y)` lands on x through its\n * deformer's grid: `bindPointToRestGrid`'s cell — the first whose right edge\n * is past x and the first whose bottom edge is below y, else the end cell —\n * and `sampleWarpGrid`'s bilinear blend of that cell's four corner nodes,\n * each landed by `nodeLanding`, with the within-cell fractions clamped to\n * [0, 1] as the engine clamps them. A node reads its own landing exactly.\n */\nfunction gridChildLandingX(\n grid: IkiWarpGrid,\n nodeLanding: (nodeX: number, nodeY: number) => number,\n x: number,\n y: number,\n): number {\n const { cols, rows, points } = grid;\n const stride = cols + 1;\n let col = cols - 1;\n for (let c = 0; c < cols; c++) {\n if (x < points[(c + 1) * 2]) {\n col = c;\n break;\n }\n }\n let row = rows - 1;\n for (let r = 0; r < rows; r++) {\n if (y > points[(r + 1) * stride * 2 + 1]) {\n row = r;\n break;\n }\n }\n const xLeft = points[col * 2];\n const xRight = points[(col + 1) * 2];\n const yTop = points[row * stride * 2 + 1];\n const yBottom = points[(row + 1) * stride * 2 + 1];\n const s = Math.max(0, Math.min(1, (x - xLeft) / (xRight - xLeft)));\n const t = Math.max(0, Math.min(1, (yTop - y) / (yTop - yBottom)));\n const at = (c: number, r: number) =>\n nodeLanding(points[(r * stride + c) * 2], points[(r * stride + c) * 2 + 1]);\n const top = at(col, row) + (at(col + 1, row) - at(col, row)) * s;\n const bot = at(col, row + 1) + (at(col + 1, row + 1) - at(col, row + 1)) * s;\n return top + (bot - top) * t;\n}\n\n/** The grid a face-family part binds to — its turn group's — and the part as\n * it renders on it: what a landmark, a silhouette candidate or the plate's\n * edge is read through (`carrierLandingX`), and what the generator ships as\n * that group's deformer and that part's mesh. Exported at module level for\n * the tests, not from the package. */\nexport interface TurnCarrier {\n grid: IkiWarpGrid;\n part: RenderedPart;\n /** The point the group's nodes turn about, when they turn as one feature\n * rather than each on its own column (`groupNodeLanding`). Only the mouth\n * family's carriers and the nose's carry one. */\n anchor?: TurnAnchor;\n}\n\n/** A turn group's anchor: its rest point `(x, y)` and how far the group tilts\n * about it at full turn, degrees. The mouth family's is the closed mouth's\n * rest centre, which is also the `mouth` landmark, tilting\n * `MOUTH_TURN_TILT_DEG`; the nose's is its bridge top, the top edge of its\n * `denseCore` (else its crop) at that box's centre x, tilting\n * `NOSE_TURN_TILT_DEG`. */\ninterface TurnAnchor {\n x: number;\n y: number;\n tiltDeg: number;\n}\n\n/** Where a rest point of a carried part renders on x: `renderedLandingX` on\n * the part, each mesh vertex landed through the carrier's grid\n * (`gridChildLandingX`) with `nodeLanding` at the grid's nodes — the two\n * stages the engine and the GPU draw a warp child in. */\nfunction carrierLandingX(\n carrier: TurnCarrier,\n nodeLanding: (nodeX: number, nodeY: number) => number,\n px: number,\n py: number,\n): number {\n return renderedLandingX(\n carrier.part,\n (vx, vy) => gridChildLandingX(carrier.grid, nodeLanding, vx, vy),\n px,\n py,\n );\n}\n\n/**\n * Where a turn-group grid node resting at `(nodeX, nodeY)` lands at the stop\n * `deg`, whose map for a point resting at `y` is `mapAt(y)`, its family\n * shifted by `shift` px (negative toward the far side): the rule the bake and\n * the carrier reads share. The bake writes it into the group's keyforms\n * (`bakeTurnGroupWarp2D`) and the solve reads the same nodes back through it\n * (`landmarkLandingX`, `landingOfRole`), so given the same anchor, stop and\n * shift the two cannot disagree; `plateLandingOn` reads the plate's nodes by\n * the unanchored rule at shift 0.\n *\n * Without an `anchor` each node reads the map at its own shifted x. With one\n * the group turns as one feature on the face, about the anchor `a`:\n * - the pivot P = M_{a.y}(a.x + shift) is where the unanchored rule puts\n * the anchor, so the anchor's landing — and what a family shift means —\n * is unchanged;\n * - each node takes the surface's own shape about the anchor's REST\n * position and slides with it as a whole,\n * X = M_{nodeY}(nodeX) + P − M_{a.y}(a.x), so the feature foreshortens\n * as the face under it does rather than as the face beside it;\n * - (X, nodeY) is then rotated about (P, a.y) by\n * φ = tiltDeg · deg / HEAD_TURN_MAX_DEG, counter-clockwise positive in\n * model y-up, so the near end goes down at either full turn (the +x end\n * at −30, the −x end at +30). This is the only reader of `deg`.\n *\n * The mouth reads φ on its width: its anchor is its centre, so the near end\n * goes down. The nose reads the same φ on its height: its anchor is its\n * bridge top, so the tip, below the pivot, swings toward the far side (−x at\n * −30, where φ is negative and clockwise; +x at +30). One sign serves both —\n * the one that puts the mouth's near end down.\n *\n * A rotation is affine, so it passes unchanged through the grid's bilinear\n * read and the mesh's barycentric read: the rendered tilt is exactly φ, and\n * every drawing on the grid turns about the same pivot.\n */\nfunction groupNodeLanding(\n anchor: TurnAnchor | undefined,\n mapAt: (y: number) => TurnColumnMap,\n deg: number,\n shift: number,\n nodeX: number,\n nodeY: number,\n): { x: number; y: number } {\n if (anchor === undefined) {\n return { x: mapAt(nodeY).mapX(nodeX + shift), y: nodeY };\n }\n const anchorRow = mapAt(anchor.y);\n const pivotX = anchorRow.mapX(anchor.x + shift);\n const dx = mapAt(nodeY).mapX(nodeX) - anchorRow.mapX(anchor.x);\n const dy = nodeY - anchor.y;\n const phi = ((anchor.tiltDeg * deg) / HEAD_TURN_MAX_DEG) * (Math.PI / 180);\n const cos = Math.cos(phi);\n const sin = Math.sin(phi);\n return {\n x: pivotX + cos * dx - sin * dy,\n y: anchor.y + sin * dx + cos * dy,\n };\n}\n\n/** `surface.mapAt`, memoised per stop and node row: the map depends on\n * nothing else, and a solve reads it at every node of every carrier at every\n * guard row of every stop of every hold it tries. */\nfunction rowMapsOf(\n surface: TurnSurface,\n): (deg: number, nodeY: number) => TurnColumnMap {\n const maps = new Map<number, Map<number, TurnColumnMap>>();\n return (deg, nodeY) => {\n let byRow = maps.get(deg);\n if (byRow === undefined) {\n byRow = new Map();\n maps.set(deg, byRow);\n }\n let map = byRow.get(nodeY);\n if (map === undefined) {\n map = surface.mapAt(deg, nodeY);\n byRow.set(nodeY, map);\n }\n return map;\n };\n}\n\n/** The face plate as the renderer places it — its rest centre and crop, the\n * mesh `meshCellsFor` gives that crop, and the identity TRS the generator\n * writes for it (`bboxToTransform` places it by translate alone, and it\n * carries no binding). A hand-built plate for the primitive tests; the\n * solver and the generator read theirs from `turnSetup`'s `face` member.\n * Exported at module level for the tests, not from the package. */\nexport function facePlate(\n faceCenterX: number,\n faceCenterY: number,\n faceHalfWidth: number,\n faceCropH: number,\n): RenderedPart {\n const cropW = faceHalfWidth * 2;\n return {\n x: faceCenterX,\n y: faceCenterY,\n cropW,\n cropH: faceCropH,\n ...meshCellsFor(cropW, faceCropH),\n scaleX: 1,\n scaleY: 1,\n rotation: 0,\n };\n}\n\n/**\n * The face plate's rendered landing on x at one stop, for ANY rest point of\n * it: `renderedLandingX` on the plate's own mesh, each vertex landed through\n * the carrier's grid — the plate's own `faceWarp` grid — as the bilinear\n * blend, over the cell that holds it, of the surface's map at the four corner\n * nodes, each node read at its own rest y (`gridChildLandingX`), which is\n * what the engine draws there. The plate has no turn shift of its own, so a\n * node reads the map at its rest x. Not the bare surface: the grid and the\n * mesh each chord it, and the bangs' join has to sit on what RENDERS.\n *\n * `mapAt` is the surface's map per stop and node row — a caller hands in the\n * memoised reader `rowMapsOf` gives a surface, so the plate's node rows are\n * built once and shared with everything else reading that surface.\n *\n * Exported at module level for the tests, not from the package.\n */\nexport function plateLandingOn(\n plate: TurnCarrier,\n mapAt: (deg: number, nodeY: number) => TurnColumnMap,\n): (deg: number, x: number, y: number) => number {\n return (deg, x, y) =>\n carrierLandingX(\n plate,\n (nodeX, nodeY) => mapAt(deg, nodeY).mapX(nodeX),\n x,\n y,\n );\n}\n\n/**\n * The rows the plate's rendered painted edge is read at by every guard that\n * keeps the bangs' hold fold-free: the face mesh's own vertex rows — between\n * two of them the edge column renders linear, so its extremes sit on them —\n * plus every hair_front mesh row's rest y clamped into the face mesh's span,\n * the rows the bangs' join actually samples the plate at (the face rows alone\n * without bangs). ONE list for the solver's `plateReachAt` and\n * `shellTravelCap` and for the bake's per-row guard, so the two cannot\n * disagree: a radius the solver accepts is one the bake can hold.\n *\n * Exported at module level for the tests, not from the package.\n */\nexport function plateGuardRowsFor(\n face: { y: number; cropW: number; cropH: number },\n hairFront?: { centerY: number; cropW: number; cropH: number },\n): number[] {\n // Each mesh row's absolute rest y, exactly as the bakes read a vertex's:\n // the part's y plus createPixelGridMesh's own row y.\n const rowsOf = (part: { cropW: number; cropH: number }, y: number) => {\n const { rows } = meshCellsFor(part.cropW, part.cropH);\n return Array.from(\n { length: rows + 1 },\n (_, r) => y + (part.cropH / 2 - (r / rows) * part.cropH),\n );\n };\n const faceRows = rowsOf(face, face.y);\n if (hairFront === undefined) return faceRows;\n const top = face.y + face.cropH / 2;\n const bottom = face.y - face.cropH / 2;\n return [\n ...faceRows,\n ...rowsOf(hairFront, hairFront.centerY).map((y) =>\n Math.max(bottom, Math.min(top, y)),\n ),\n ];\n}\n\n/**\n * The face plate as every guard reads it: its carrier — the `face` member's\n * grid and mesh — the painted half-width on each row, and the rows the\n * rendered edge is read at (`plateGuardRowsFor`). `edgeAt` is the profile's\n * own row when the face layer measured one (`faceRowProfile`), and the crop's\n * half-width on every row otherwise: the hold's join ends there and both\n * fold guards read the plate's edge there. Derived HERE and nowhere else —\n * `turnSetup` hands the generator these and `solveTurnModel` builds its\n * context from the same call on the same inputs — so the hold the solve fits\n * is the hold the bake builds.\n */\nfunction plateGuardsOf(\n carriers: ReadonlyMap<string, TurnCarrier>,\n faceHalfWidth: number,\n profile: FaceRowProfile | undefined,\n hairFront?: { centerY: number; cropW: number; cropH: number },\n): {\n plate: TurnCarrier;\n edgeAt: (y: number) => number;\n plateGuardRows: number[];\n} {\n const plate = carriers.get(\"face\");\n if (plate === undefined) {\n throw new Error(\"auto-rig: plateGuardsOf: the carriers have no face\");\n }\n return {\n plate,\n edgeAt: profile === undefined ? () => faceHalfWidth : profile.at,\n plateGuardRows: plateGuardRowsFor(plate.part, hairFront),\n };\n}\n\n// ── Turn targets ─────────────────────────────────────────────────────────────\n\n/**\n * What a head does at full turn, as the cues `measure_turn_reference` reads off\n * a front/turned reference pair. The turn is fitted to these — the cylinder's\n * radius and the features' depths are solved from them, not tuned.\n *\n * Every field is optional: `DEFAULT_TURN_TARGETS` supplies the three a\n * reference measures, and the nose and the mouth derive from the eyes when they\n * are not given.\n *\n * What happens when the layer set cannot reach a target depends on where the\n * target came from. A number the CALLER passed is a measurement, so an\n * unreachable one throws, naming the field and what was on offer. A number this\n * module filled in — a default, or a nose/mouth derived from the eyes — is a\n * style prior, not a promise about this character: it is CLAMPED to what the\n * layer set can do and the rig is built. The alternative is a generator that\n * refuses its own defaults, which would leave a perfectly good layer set with\n * no model at all. `solveTurnModel` reports which fields it clamped.\n *\n * One measurement is clamped too: an `eyeShift` past the room the art leaves\n * the far eye — the face plate's edge, and the bangs' side strand when\n * `strandEdges` is given — whoever passed it. That room is a fact about this\n * layer set, not about the reference the shift was measured on, so the rig\n * takes as much of the shift as the art allows and keeps fitting the other\n * cues. An `eyeShift` below the slide the face's own turn already gives the\n * eyes is still refused: no depth reaches it.\n */\nexport interface TurnTargets {\n /** How far the eye pair's centre slides toward the far side at full turn, as\n * a fraction of the head's half-width. The sign is ignored: which way is\n * the turn's own business. */\n eyeShift?: number;\n /** The far eye's width over the near eye's at full turn, divided by the same\n * ratio at rest. 1 is no foreshortening; 0 is an edge-on far eye. */\n farEyeRatio?: number;\n /** The head's half-width at full turn over its half-width at rest, as a\n * RENDER measures it — the outermost opaque edge on each side, whichever\n * part owns it. 1 holds the silhouette; below 1 narrows it. The hold that\n * produces it is fitted to it, so a measured one is met or refused, never\n * quietly rendered as something else. */\n silhouetteRatio?: number;\n /** `eyeShift` for the nose. Derived from `eyeShift` when absent. */\n noseShift?: number;\n /** `eyeShift` for the mouth. Derived from `eyeShift` when absent. */\n mouthShift?: number;\n /** The head's own half-width at the eye row, in canvas px: what the shift\n * fractions are fractions OF, and the rest distance the silhouette hold\n * pivots on. Without it both fall back to the face plate's half-width, which\n * is narrower than the head the hair draws, so the shifts land short — an\n * approximation for callers that have no pixels, not a default to prefer. */\n headHalfWidth?: number;\n}\n\n/**\n * A `turnTargets` field this generator will not build a rig from: not a number,\n * outside its range, unreachable on this layer set, or a measured head narrower\n * than the face plate it is drawn around. Every one of them carries a number\n * the CALLER passed, so a host can tell them apart from the invariant breaks the\n * rest of the generator throws and report them as bad input instead of failing.\n */\nexport class TurnTargetError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TurnTargetError\";\n }\n}\n\n/**\n * The reference the turn is fitted to when the caller measured none of its own:\n * a 3/4 portrait, which `measure_turn_reference` reads as 0.671 / −0.224 /\n * 1.012 (its raw turned far/near iris ratio is 0.71 — 0.67 is that ratio\n * normalised by the same ratio at rest, which is what the field means).\n * The silhouette is held exactly: a 1 % change is inside that tool's own noise.\n */\nexport const DEFAULT_TURN_TARGETS: Readonly<\n Required<Pick<TurnTargets, \"eyeShift\" | \"farEyeRatio\" | \"silhouetteRatio\">>\n> = {\n eyeShift: 0.22,\n farEyeRatio: 0.67,\n silhouetteRatio: 1,\n} as const;\n\n/** Share of the eye pair's slide the nose and the mouth take when a reference\n * measured only the eyes. Both stand proud of the surface the eyes sit on —\n * the nose furthest — so both travel further, in the same proportion the 3/4\n * reference above shows. */\nconst NOSE_SHIFT_SHARE = 1.36;\nconst MOUTH_SHIFT_SHARE = 1.18;\n\n/** Bounds on the ratio targets. Outside them the number is a typo, not a head:\n * a far eye wider than the near one by half already means the pair was\n * measured the wrong way round. */\nconst TURN_RATIO_MAX = 1.5;\nconst SILHOUETTE_RATIO_MIN = 0.5;\n\n/** `TurnTargets` with every target filled in — what the solver fits. */\nexport interface ResolvedTurnTargets {\n eyeShift: number;\n farEyeRatio: number;\n silhouetteRatio: number;\n noseShift: number;\n mouthShift: number;\n /** The caller's measured head half-width, checked. Absent when it has none. */\n headHalfWidth?: number;\n /** The fields the caller did NOT supply: filled from DEFAULT_TURN_TARGETS, or\n * derived from the eyes. Those are the ones the solver clamps wherever the\n * layer set cannot reach them; of the caller's own it clamps only an\n * `eyeShift` past the art's room (see TurnTargets). */\n defaulted: ReadonlySet<keyof TurnTargets>;\n}\n\n/** The turn depth of each feature family: the share of the pinned bulk slide\n * (`headTurnParallaxUnit`) it gets back at full turn. Solved, never tuned. */\nexport interface TurnDepths {\n eye: number;\n nose: number;\n mouth: number;\n}\n\n/**\n * Fill in the targets the caller left out and check the ones it gave.\n *\n * The defaults go through the same checks as a caller's numbers — they are\n * targets like any other, and a bad one has to fail where it is written, not\n * in the geometry three functions down. The GEOMETRIC bounds are not checked\n * here: whether a target is reachable is a property of the layer set, so it\n * belongs to `solveTurnModel`.\n */\nexport function resolveTurnTargets(\n targets: TurnTargets = {},\n): ResolvedTurnTargets {\n const eyeShift = targets.eyeShift ?? DEFAULT_TURN_TARGETS.eyeShift;\n const check = (\n field: keyof TurnTargets,\n value: number | undefined,\n ok: (v: number) => boolean,\n expected: string,\n ) => {\n if (value === undefined) return;\n if (!Number.isFinite(value) || !ok(value)) {\n // A DERIVED target says so: the number in the message is not one the\n // caller wrote, and the field to fix is the one it came from. Only the\n // two shares are derived; the rest fall back to a default, which is a\n // number in its own right.\n const derived =\n (field === \"noseShift\" || field === \"mouthShift\") &&\n targets[field] === undefined;\n const from = derived\n ? `, derived from turnTargets.eyeShift (${eyeShift}),`\n : \"\";\n throw new TurnTargetError(\n `auto-rig: turnTargets.${field} (${value})${from} must be ${expected}`,\n );\n }\n };\n\n const farEyeRatio = targets.farEyeRatio ?? DEFAULT_TURN_TARGETS.farEyeRatio;\n const silhouetteRatio =\n targets.silhouetteRatio ?? DEFAULT_TURN_TARGETS.silhouetteRatio;\n const noseShift = targets.noseShift ?? NOSE_SHIFT_SHARE * eyeShift;\n const mouthShift = targets.mouthShift ?? MOUTH_SHIFT_SHARE * eyeShift;\n\n const isShift = (v: number) => Math.abs(v) <= 1;\n const shiftRange = \"a fraction of the head half-width, |value| <= 1\";\n check(\"eyeShift\", eyeShift, isShift, shiftRange);\n check(\n \"farEyeRatio\",\n farEyeRatio,\n (v) => v > 0 && v <= TURN_RATIO_MAX,\n `in (0, ${TURN_RATIO_MAX}]`,\n );\n check(\n \"silhouetteRatio\",\n silhouetteRatio,\n (v) => v >= SILHOUETTE_RATIO_MIN && v <= TURN_RATIO_MAX,\n `in [${SILHOUETTE_RATIO_MIN}, ${TURN_RATIO_MAX}]`,\n );\n check(\"noseShift\", noseShift, isShift, shiftRange);\n check(\"mouthShift\", mouthShift, isShift, shiftRange);\n // headHalfWidth is checked against the layer set's own plate in solveTurnModel,\n // which is where the two meet.\n check(\"headHalfWidth\", targets.headHalfWidth, (v) => v > 0, \"positive\");\n\n const defaulted = new Set<keyof TurnTargets>(\n (\n [\n \"eyeShift\",\n \"farEyeRatio\",\n \"silhouetteRatio\",\n \"noseShift\",\n \"mouthShift\",\n ] as const\n ).filter((field) => targets[field] === undefined),\n );\n return {\n eyeShift,\n farEyeRatio,\n silhouetteRatio,\n noseShift,\n mouthShift,\n headHalfWidth: targets.headHalfWidth,\n defaulted,\n };\n}\n\n// ── Turn landmarks ───────────────────────────────────────────────────────────\n\n/** A feature the turn slides across the face: its rest centre and its width in\n * model px, which is all the cues measure. `y` is optional — the eye pair's\n * places the eye row (`solveTurnModel`'s `eyeRowY`), and every landmark's is\n * the row its points are read on — its own radius and swing once the face\n * has a row profile (`TurnSurface.mapAt`). A hand-built landmark without one\n * reads row 0, the same map as any other on a surface without a profile. */\nexport interface TurnLandmark {\n x: number;\n y?: number;\n w: number;\n /** The grid the part binds to and the part as it renders on it, when the\n * landmark is a rigged part's: its points are then read the way the\n * engine draws them — the mesh triangle over vertices landed bilinearly\n * on the grid (`landmarkLandingX`) — rather than straight off the\n * surface's map, which the grid and the mesh only chord. A landmark\n * without one (hand-built, in a test) reads the map itself. */\n carrier?: TurnCarrier;\n}\n\n/** The landmarks each solved depth is fitted to. The eye family is a PAIR — the\n * cues are about the two eyes against each other — the other two a single\n * part, kept in arrays so one solver serves all three. */\nexport interface TurnLandmarkSet {\n eye: TurnLandmark[];\n nose: TurnLandmark[];\n mouth: TurnLandmark[];\n}\n\n/** The box a nose's turn reads, its landmark (`turnLandmarks`) and its pivot\n * (`turnSetup`) alike, so the two cannot part: its `denseCore` when the\n * layer carries one, else its crop. */\nfunction noseDrawnBox(layer: LayerInput): {\n x: number;\n y: number;\n w: number;\n h: number;\n} {\n return layer.denseCore ?? layer.bbox;\n}\n\n/**\n * Pick the parts the turn cues are measured on out of a layer set, in absolute\n * model x.\n *\n * The eye pair is the WHITES, even when the layer set has irises. The white is\n * the eye's own extent — the thing that has to stay on the face plate as the\n * pair slides, and the widest part of the stack — while the iris is clipped to\n * it and slides with it, so the two foreshorten together: on the reference\n * character the far/near ratio read off the white and off the iris differ by\n * 0.01. Bounding the slide by the narrower iris instead would put the white's\n * outer edge over the side hair, where the render loses it.\n *\n * The nose is its `denseCore` when the layer carries one — the drawing, not\n * the soft feather its crop takes in — and its crop otherwise.\n *\n * With `carriers` (by role — what `turnSetup` builds) each landmark is read\n * through its own part's grid and mesh; without, straight off the map.\n */\nexport function turnLandmarks(\n layers: LayerInput[],\n carriers?: ReadonlyMap<string, TurnCarrier>,\n): TurnLandmarkSet {\n const markOf = (role: string): TurnLandmark | undefined => {\n const layer = layers.find((l) => l.role === role);\n if (!layer) return undefined;\n const drawn = role === \"nose\" ? noseDrawnBox(layer) : layer.bbox;\n const t = bboxToTransform(drawn, layer.canvasW, layer.canvasH, role);\n // A crop's width is its `cropW`; a dense core's, its own.\n const w = drawn === layer.bbox ? layer.cropW : drawn.w;\n const mark: TurnLandmark = { x: t.x, y: t.y, w };\n const carrier = carriers?.get(role);\n if (carrier !== undefined) mark.carrier = carrier;\n return mark;\n };\n const pairOf = (left: string, right: string) => {\n const l = markOf(left);\n const r = markOf(right);\n return l && r ? [l, r] : undefined;\n };\n const one = (role: string) => {\n const m = markOf(role);\n return m ? [m] : [];\n };\n return {\n eye: pairOf(\"eye_L\", \"eye_R\") ?? [],\n nose: one(\"nose\"),\n mouth: one(\"mouth\"),\n };\n}\n\n/** The row the cues are measured on and a row profile is normalised at: the\n * eye pair's mean y when both eyes carry one (every landmark `turnLandmarks`\n * builds does), else `fallback` — the face centre, for landmarks a\n * lower-level test built by hand without rows. One derivation for the solve\n * and `turnSetup`, so the generator's own surface and the solve's read the\n * same row. */\nfunction eyeRowOf(landmarks: TurnLandmarkSet, fallback: number): number {\n const [l, r] = landmarks.eye;\n return landmarks.eye.length === 2 && l.y !== undefined && r.y !== undefined\n ? (l.y + r.y) / 2\n : fallback;\n}\n\n// ── solveTurnDepth ───────────────────────────────────────────────────────────\n\n/** The depth that comes closest to a shift target at one radius. */\nexport interface TurnDepthSolution {\n /** Whether `depth` HITS the target, or the bounds cut it short. */\n reached: boolean;\n /** The depth to use: the target's own solution, or the nearest bound. */\n depth: number;\n /** The shift `depth` actually produces, px (negative = toward the far side). */\n achieved: number;\n /** Every shift this family could have had, px, ascending. */\n attainable: [number, number];\n /** Which end cut the target short, when `reached` is false. `\"cap\"`: it\n * asked for more far-side shift than the art leaves room for — the face\n * plate's edge, or the strand bound's depth (0 where that bound cannot\n * hold). `\"floor\"`: it asked for less than the family already has at\n * depth 0 — the face's own slide and bend, plus the silhouette centre's\n * drift once that is folded into the target — which no depth reaches,\n * a depth never being negative. Absent when `reached`. */\n limit?: \"cap\" | \"floor\";\n}\n\n/** Bisection steps for both solvers. The intervals start finite and halve, so\n * 40 is exact in double precision for any radius or depth a head has. */\nconst TURN_BISECT_STEPS = 40;\n\n/** Px slack on `solveTurnDepthSigned`'s own attainable bound, so a target\n * that round-trips through a reported cue and back — e.g. a caller\n * re-submitting the exact `attainable` boundary this function itself\n * reported, cue-scaled by `hh` and back — is not refused by a float ulp of\n * rounding. Far below any physically meaningful px difference. */\nconst TURN_DEPTH_EPS = 1e-6;\n\n/**\n * The depth that slides a landmark family by `targetPx` at full turn —\n * `targetPx` a MAGNITUDE, always toward the far side (see `TurnTargets`'\n * \"the sign is ignored\" contract). The nose/mouth shares and every existing\n * caller of this export want exactly that; `evaluateTurnCandidate`'s eye\n * solve does not (see `solveTurnDepthSigned`), so it calls that directly.\n */\nexport function solveTurnDepth(\n targetPx: number,\n landmarks: TurnLandmark[],\n unit: number,\n map: TurnColumnMap,\n plateEdgeX: number,\n): TurnDepthSolution {\n // The cues are signed toward the far side; the target's own sign is ignored.\n return solveTurnDepthSigned(\n -Math.abs(targetPx),\n landmarks,\n unit,\n () => map,\n plateEdgeX,\n );\n}\n\n/**\n * Where a landmark's rest point `(px, py)` lands on x at the stop `deg`, whose\n * map for a point resting at `y` is `mapAt(y)`, the family shifted by `shift`\n * px (negative toward the far side): through the landmark's carrier when it\n * has one — every node of its grid lands by `groupNodeLanding` about the\n * carrier's anchor, the rule its keyforms were baked by, the shift being that\n * grid's own keyform geometry, and the point is read over the part's mesh\n * the way the engine draws it (`carrierLandingX`) — or straight off the map,\n * `mapAt(py).mapX(px + shift)`, without one.\n */\nfunction landmarkLandingX(\n landmark: TurnLandmark,\n mapAt: (y: number) => TurnColumnMap,\n deg: number,\n shift: number,\n px: number,\n py: number,\n): number {\n const { carrier } = landmark;\n if (carrier === undefined) return mapAt(py).mapX(px + shift);\n return carrierLandingX(\n carrier,\n (nodeX, nodeY) =>\n groupNodeLanding(carrier.anchor, mapAt, deg, shift, nodeX, nodeY).x,\n px,\n py,\n );\n}\n\n/** How far a landmark family's solved depth may carry it toward the far side,\n * px: its landmarks' far edge to the plate's far edge, never negative — a\n * family that starts past the plate cannot be slid back in by a depth, and a\n * depth away from the turn is not a depth. The depth solver's own cap, and\n * what every group grid and the lattice are sized to before the solve\n * (`turnSetup`), so the two cannot disagree. For the mouth and the nose,\n * whose nodes turn about their anchors (`groupNodeLanding`), the cap bounds\n * the anchor's slide. The eye family's cap may be tightened per hold by the\n * strand bound (`strandDepthCap`) but never loosened, so grids sized to this\n * one cover every depth a solve returns. */\nfunction familyReachPx(\n landmarks: readonly TurnLandmark[],\n plateEdgeX: number,\n): number {\n const farEdge = Math.min(...landmarks.map((l) => l.x - l.w / 2));\n return Math.max(0, farEdge - plateEdgeX);\n}\n\n/** A family's depth cap at one radius: `familyReachPx` in depth units. The\n * one expression every reader of the plate's cap goes through — the depth\n * solver's own bound (`solveTurnDepthSigned`), and the strand bound and\n * Aitken's clamp in `evaluateTurnCandidate` — so the three cannot drift\n * apart, and a rig built without strands keeps its eye depth bit for bit. */\nfunction familyDepthCap(\n landmarks: readonly TurnLandmark[],\n plateEdgeX: number,\n unit: number,\n): number {\n return familyReachPx(landmarks, plateEdgeX) / unit;\n}\n\n/**\n * The depth that lands a landmark family's slide exactly on the already-SIGNED\n * `target` (negative toward the far side, positive toward the near one),\n * or the nearer bound when `target` sits outside what any non-negative depth\n * can reach — depth cannot go negative (a feature does not stand BEHIND the\n * surface), so a positive `target` past `atRest` is exactly as unreached as a\n * too-negative one past `atCap`.\n *\n * The family's grid carries the slide as keyform geometry: each node lands\n * where the map sends its rest x shifted by `depth * unit` toward the far side\n * (`bakeTurnGroupWarp2D`'s `shiftAt`) — or, for the mouth and nose families,\n * where their anchor so shifted lands, the group turned about it\n * (`groupNodeLanding`) — and the part binds at its rest position, so what a\n * reference measures is the grid's map OF the shifted position, not the shift:\n * `achieved(d) = mean(landing(x, −d·unit) − x)`, each landmark's centre read\n * through its own carrier and mesh (`landmarkLandingX`) — the very keyforms\n * and triangles the engine draws. The map compresses the far side and\n * stretches the near one, so the two are not the same number, and the far\n * side's compression makes `achieved` shrink faster than `d` grows. It is\n * monotone decreasing in `d` because the map is monotone increasing and the\n * two interpolations weight it non-negatively, which is what makes a\n * bisection valid; an anchored landmark depends on `d` only through its\n * pivot, which it rides one for one.\n *\n * The travel's upper bound is the FACE: every landmark's far edge has to stay\n * on the face plate, whose contour the turn only foreshortens. A feature past\n * it is drawn over the side hair or hangs over the cheek's edge with nothing\n * behind it — on the reference character the far eye went 35 px past the\n * contour and the render came back with an eye 17 px wide where the solver\n * had promised 53. The reference keeps the far eye whole. Since `mapX` is\n * monotone, staying inside the plate's MAPPED edge is the same as staying\n * inside its rest edge, so the bound is a plain rest distance. The held\n * silhouette is no bound of its own: the hold edge clears the plate's own\n * rendered reach at every stop (the fold guard, see `evaluateTurnCandidate`),\n * and that reach bounds every landmark's far edge already.\n *\n * The eye family can be bounded tighter, by the bangs' side strands\n * (`maxDepth`, from `strandDepthCap`): the deepest depth at which each far\n * iris, landed at every far stop, sits no deeper under the run it slides\n * under than it is painted. That bound is best effort — where no depth holds\n * it, the cap is 0, the least overlap any depth leaves — and it bounds the\n * eyes' depth only: the face's own sideways travel, the slide under every\n * feature, is not capped by it.\n *\n * Both ends of the interval matter, not just the far one: at `d = 0` a landmark\n * already drifts, because the map carries the face's own sideways slide as well\n * as the bend, and the slide is much the larger of the two — on the assembly\n * fixture at −30° the two together drift the eye pair ≈64 px toward the far\n * side, whereas the bend alone would have moved it ≈11 px toward the near one.\n * That drift therefore runs toward the FAR side, and IS the floor under every\n * shift cue: a target asking for LESS far-side shift than that drift does has\n * no non-negative depth either. Neither end is an error here — the solution is\n * reported with `reached: false` and the bound it stopped at (`limit`), and\n * whether that is a clamp or a failure is the caller's call (see\n * solveTurnModel).\n *\n * `evaluateTurnCandidate`'s eye solve calls this directly instead of going\n * through `solveTurnDepth`'s magnitude wrapper: once the silhouette centre's\n * own drift is folded into the target, that target can legitimately land on\n * either side of zero, and `-Math.abs` would silently answer a different\n * question than the one asked (an eyeShift and its negation producing\n * different rigs). Every other caller (the nose/mouth shares) keeps the\n * magnitude-only contract through the wrapper.\n */\nfunction solveTurnDepthSigned(\n target: number,\n landmarks: TurnLandmark[],\n unit: number,\n mapAt: (y: number) => TurnColumnMap,\n plateEdgeX: number,\n /** A tighter depth bound than the plate's, when the art sets one: the eye\n * family's strand bound (`strandDepthCap`), 0 where it cannot hold. */\n maxDepth?: number,\n): TurnDepthSolution {\n if (landmarks.length === 0) {\n throw new Error(\"auto-rig: solveTurnDepth: no landmark to slide\");\n }\n const plateCap = familyDepthCap(landmarks, plateEdgeX, unit);\n const cap = maxDepth === undefined ? plateCap : Math.min(plateCap, maxDepth);\n const achieved = (d: number) =>\n landmarks.reduce(\n (sum, l) =>\n sum +\n (landmarkLandingX(\n l,\n mapAt,\n -HEAD_TURN_MAX_DEG,\n -d * unit,\n l.x,\n l.y ?? 0,\n ) -\n l.x),\n 0,\n ) / landmarks.length;\n\n const atCap = achieved(cap);\n const atRest = achieved(0);\n const attainable: [number, number] = [atCap, atRest];\n if (target > atRest + TURN_DEPTH_EPS) {\n return {\n reached: false,\n depth: 0,\n achieved: atRest,\n attainable,\n limit: \"floor\",\n };\n }\n if (target < atCap - TURN_DEPTH_EPS) {\n return {\n reached: false,\n depth: cap,\n achieved: atCap,\n attainable,\n limit: \"cap\",\n };\n }\n // Within EPS of a bound but past it either way (the ulp this guards\n // against) — clamp before bisecting, rather than searching for a target\n // outside the interval `achieved` can actually produce.\n const clampedTarget = Math.min(Math.max(target, atCap), atRest);\n\n let lo = 0;\n let hi = cap;\n for (let i = 0; i < TURN_BISECT_STEPS; i++) {\n const mid = (lo + hi) / 2;\n if (achieved(mid) > clampedTarget) lo = mid;\n else hi = mid;\n }\n const depth = (lo + hi) / 2;\n return { reached: true, depth, achieved: achieved(depth), attainable };\n}\n\n// ── solveTurnModel ───────────────────────────────────────────────────────────\n\n/** What the turn solve settled on, for a caller that wants to see it — the\n * cues it reached and the targets it had to cut down to reach them. */\nexport interface TurnSolveReport {\n /** Cylinder radius the face-warp bake and the parallax unit were built on. */\n radius: number;\n /** Rest distance from the face centre the silhouette hold pivots on. */\n holdBase: number;\n depths: TurnDepths;\n /** What the rig actually reaches, in the targets' own units. */\n achieved: { eyeShift: number; farEyeRatio: number; silhouetteRatio: number };\n /** The targets the rig fell short of, cut down to what it can reach: every\n * defaulted field it could not reach, and an `eyeShift` — the caller's own\n * too — past the room the art leaves the far eye (the face plate's edge,\n * and the bangs' strand when `strandEdges` was given). Empty when every\n * target was met. */\n clamped: (keyof TurnTargets)[];\n /** Per side whose far iris the bangs' run covers at some far stop at the\n * fitted radius and depth (`options.strandEdges`): how much of it. Absent\n * when no side has an entry. Never a clamp: `clamped` still names an\n * `eyeShift` the strand bound cut short. */\n strandOverlap?: { left?: StrandOverlap; right?: StrandOverlap };\n}\n\n/**\n * How much of one side's far iris the bangs' run covers: the length of the\n * landed iris span `[irisOuter, irisInner]` on its row that lies inside the\n * landed run `[runOuter, runFace]` — face-ward without end for a run with no\n * face-side edge (`runFace: null`) — at the far stop where that is largest.\n * Always read off real landed boundaries (`IrisStrand`).\n */\nexport interface StrandOverlap {\n /** The far stop the covered width is largest at, degrees. */\n deg: number;\n /** That covered width, px. */\n px: number;\n /** `px` over the head half-width the shift cues are fractions of. */\n hh: number;\n /** The covered width as painted, at rest, px. */\n restPx: number;\n /** Whether this side's strand bound held at the fitted depth — the iris\n * kept no deeper under the run than it is painted, at every far stop. When\n * false the fitted radius could not hold it (always so for `runFace:\n * null`), the eyes' turn depth is 0, and `px` is the coverage that is\n * left. */\n held: boolean;\n}\n\n/** The turn the targets ask for, or the first CALLER target this layer set\n * cannot reach and what it could have had instead (in that target's own\n * units). Defaults never land in the second branch — they clamp — and nor\n * does a caller `eyeShift` past the room the art leaves the far eye, which\n * clamps the same way; one below the slide the face's own turn already\n * gives the eyes still does. */\nexport type TurnModelSolution =\n | (TurnSolveReport & {\n unreachable: false;\n /** Where the hold's boundary is sent at each stop. */\n holdEdgeAt: (deg: number) => number;\n /** How far the face slides sideways inside that held boundary at full\n * turn, px — what the face bake and the hold's own column map have to\n * be built with, rather than the plate's own uncapped ask. */\n travel: number;\n /** The head this solve fitted — `radius` and `travel` on the lattice it\n * was handed — for every bake to sample, so nothing renders a turn the\n * cues were not measured on. */\n surface: TurnSurface;\n })\n | {\n unreachable: true;\n field: keyof TurnTargets;\n /** The caller's own number — never a clamped stand-in. */\n value: number;\n /** What the caller could have asked for instead, inside the field's own\n * accepted domain. Absent only for `silhouetteRatio`, and only when\n * nothing that domain accepts renders on this layer set at all. */\n attainable?: [number, number];\n };\n\n/** Radius sweep, as multiples of the face plate's half-width. The floor is the\n * no-fold margin itself: at it the bend's bound sits exactly on the plate's\n * edge, and below it the plate — features included — leaves the analytic part\n * of the cylinder and rides along rigidly instead of foreshortening. The\n * ceiling is all but a plate: at 8 half-widths the face covers ±7° of the\n * cylinder and the turn is nearly a slide — the far/near ratio it can still\n * produce, 0.93 on the reference character, is the top of the attainable range\n * the solver reports. A head that wants less foreshortening wants none. */\nconst TURN_SWEEP_MAX_FACTOR = 8;\n/** Sample count of that sweep. The ratio curve is smooth in log-radius, so this\n * only has to be dense enough to bracket the target, not to resolve it. */\nconst TURN_SWEEP_SAMPLES = 32;\n/** How far past the plate's own reach a hold edge has to sit, px. The ramp onto\n * the strands runs outward, so a hold edge ON the reach is a zero-width ramp;\n * one pixel of clearance is what makes it a ramp. */\nconst HOLD_CLEARANCE = 1;\n\n/** Sampling of the hold's own usable range, per radius (see `holdRange`). What\n * a render shows moves smoothly with the hold but not monotonically, so the\n * range is sampled densely enough to bracket the turn-back where the travel's\n * own feedback reverses it, not to resolve the ask — the bisection inside the\n * bracketing interval does that. */\nconst TURN_HOLD_SAMPLES = 16;\n\n/** Passes `evaluateTurnCandidate` gives the silhouette and the depths it rides\n * on to settle on each other before it gives the radius up as a `settle`\n * miss (`TurnCandidateMiss`). The passes are\n * Aitken-accelerated: a contracting fixed point settles in one to three on\n * the fixtures, eleven at the slowest seen (a depth that pins at a bound on\n * some passes and not others defeats the extrapolation) — twice that is\n * the margin. One that does not contract no count would settle. */\nconst TURN_SETTLE_PASSES = 24;\n\n/** A radius that can carry the turn, with everything the fit reads off it. */\ninterface TurnCandidate {\n radius: number;\n /** The far/near eye width ratio it produces, at the eye depth below. */\n ratio: number;\n unit: number;\n /** The sideways travel it carries at full turn, px: `ctx.travel` capped to\n * what this radius' own held shell can swallow (`shellTravelCap`). The face\n * bake and the bangs' hold have to be built with THIS number — `mapAt`\n * already is. */\n travel: number;\n /** Its column map at the −30° stop, where every cue is measured, for a\n * point resting at `y`. */\n mapAt: (y: number) => TurnColumnMap;\n /** The surface `mapAt` is read off: this radius, that travel. */\n surface: TurnSurface;\n holdBase: number;\n holdEdgeAt: (deg: number) => number;\n eye: TurnDepthSolution;\n /** The nose's and the mouth's depth on this radius' hold, solved on the\n * same terms (`solveFeatureDepths`): the depths their group grids ship\n * with, and so the depths a `headEdges` owner in either family is landed\n * at. Whether a bound that cut one short is a clamp or a refusal is\n * `solveTurnModel`'s call, once a radius is chosen. */\n nose: TurnDepthSolution;\n mouth: TurnDepthSolution;\n /** Whether the ratio this radius ships had to be cut to the nearest one a\n * render of it can show. Only a DEFAULTED target ever gets here cut down —\n * a caller-measured one the range cannot render is refused instead. */\n silhouetteClamped: boolean;\n /** How far the silhouette's own centre drifts from the face centre at full\n * turn, against rest — each side's real landing (whichever part owns that\n * edge actually carries it there; see `landingAt`) minus how far its rest\n * x already sat off the face centre. `measure_turn_reference` reads the\n * eye pair against THIS moved centre, not the face centre, so it is what\n * `eye`'s target and `achieved.eyeShift` are corrected by. */\n silhouetteCenterShift: number;\n /** The silhouette ratio a render actually shows: each side's real LANDING\n * span over its rest span (`landingAt`/`restAt`, the same pair\n * `silhouetteCenterShift` reads) — as `measure_turn_reference` would\n * measure it off the two images, and the measure the hold above was FITTED\n * against. It is the target itself whenever one was reachable, so it is\n * what `TurnSolveReport.achieved.silhouetteRatio` reports. */\n renderedSilhouetteRatio: number;\n /** Whether some eye depth keeps every far iris no deeper under the bangs'\n * run than it is painted at this radius (`strandDepthCap`) — `eye` is\n * capped to the deepest one — or none does and `eye` has depth 0. True\n * without strands. What `solveTurnModel` prefers radii by. */\n strandFeasible: boolean;\n /** How much of each far iris the run covers at `eye`'s depth, per side that\n * has any (see `TurnSolveReport.strandOverlap`), on the settled pass. */\n strandOverlap?: { left?: StrandOverlap; right?: StrandOverlap };\n}\n\n/** Why a radius yielded no candidate, in the blocked target's own terms. */\ntype TurnCandidateMiss =\n | {\n blocked: \"silhouetteRatio\";\n /** The ratios a render of this radius can actually SHOW, ascending —\n * `renderedSilhouetteRatio`'s own measure, which is the measure a\n * caller's own number came from: the narrowest and the widest over the\n * holds this radius can ship (`holdRange`'s samples), clipped to the\n * ratios `resolveTurnTargets` accepts. The rendered ratio is continuous\n * in the hold, so every ratio between them is one the fit lands on the\n * shipped mesh's own terms (`hairFrontLandingAt`, the bangs' vertices\n * landed as the bake lands them and read over the mesh's own\n * triangles), and both ends are values a caller can resubmit and have\n * rigged. Infinity and -Infinity when\n * nothing inside that domain renders here: the neutral pair\n * `sweepTurnRadii`'s own min/max ignore. */\n lower: number;\n upper: number;\n }\n | {\n /** A CALLER's eye shift below the floor: less far-side slide than the\n * eye pair already has at depth 0 on this radius (`limit: \"floor\"`),\n * which no depth reaches. A shift the art's room cuts short is not a\n * miss — the candidate keeps the capped depth, and the shift is\n * clamped whoever passed it. */\n blocked: \"eyeShift\";\n /** The px interval it offered the eyes instead, ascending. */\n offeredShift: [number, number];\n }\n | {\n /** The silhouette and the depths its `headEdges` owners ride on did\n * not settle on each other within TURN_SETTLE_PASSES at this radius\n * (see evaluateTurnCandidate): nothing to offer in any target's terms.\n * The sweep skips it like any refused radius, and `solveTurnModel`\n * refuses the layer set outright when that leaves it nothing. */\n blocked: \"settle\";\n };\n\n/** Everything a candidate is evaluated against that does not vary with the\n * radius. One pass of the solve holds one of these. */\ninterface TurnSolveContext {\n /** Each family's landmarks, every one carrying the carrier its part renders\n * on (`turnSetup`). */\n landmarks: TurnLandmarkSet;\n /** The columns every candidate's maps are piecewise-linear between — the\n * virtual lattice every group grid's node reads (`TurnSurface`). */\n lattice: IkiWarpGrid;\n faceCenterX: number;\n faceCenterY: number;\n /** The nod cylinder's radius, the head's vertical reach with the no-fold\n * margin: not fitted, carried so every candidate's surface is complete. */\n nodRadius: number;\n /** The row the cues are measured on — the eye pair's mean y, else the face\n * centre — where every candidate's maps are sampled, and where the bangs'\n * own silhouette landing is read. */\n eyeRowY: number;\n faceHalfWidth: number;\n /** Rest x of the plate's far edge — the slide's hard stop. */\n plateEdgeX: number;\n /** The face plate as it renders — its own group grid and mesh\n * (`facePlate`): what the plate's rendered edge under the hold's join and\n * the fold guards is read through (`plateLandingOn`). */\n plate: TurnCarrier;\n /** Every face-family part's carrier by role, the plate's under `face`:\n * what a `headEdges` candidate naming one of them is landed through. */\n carriers: ReadonlyMap<string, TurnCarrier>;\n /** The face's own row profile, when its layer measured one: every\n * candidate's surface bends each row on its own radius and swings the chin\n * by it (`turnSurface`). Absent, one radius on every row. */\n profile?: FaceRowProfile;\n /** The plate's PAINTED half-width at row `y` — where the hold's join and\n * the guards read its edge: the profile's row, else the crop's own\n * half-width on every row (`plateGuardsOf`). */\n edgeAt: (y: number) => number;\n /** The rows every plate guard reads the rendered painted edge at — see\n * `plateGuardRowsFor`; the bake iterates the identical list. */\n plateGuardRows: number[];\n /** The sideways travel this layer set ASKS the turn for, px at full turn:\n * `headTurnTravel` on its own plate. What a candidate actually carries is\n * this capped to what its own held shell can swallow — see\n * `TurnCandidate.travel`. */\n travel: number;\n /** The caller's measured head half-width, when it has one. */\n headHalfWidth?: number;\n /** What the shift fractions are fractions of, px. */\n hh: number;\n /** The silhouette ratio this pass is solving for. */\n silhouetteRatio: number;\n /** The MAGNITUDE of the eye shift this pass is solving for — `TurnTargets`'\n * own \"the sign is ignored\" contract taken at the door, so a request and\n * its negation reach identically here. */\n eyeShift: number;\n /** Whether an eye shift below the floor — less than the pair already\n * slides at depth 0 on a radius — is a clamp (a default) or refuses that\n * radius (the caller's). Only the floor's verdict: a shift the art's room\n * cuts short at the cap is clamped whoever passed it. */\n clampEyeShift: boolean;\n /** The nose's and the mouth's shift MAGNITUDES when the caller measured\n * them — `TurnTargets`' \"the sign is ignored\", taken at the door like\n * `eyeShift`'s. Absent when defaulted: the family then takes its share of\n * the eyes' ACHIEVED shift, known only per candidate\n * (`solveFeatureDepths`), and a bound that cuts it short is a clamp rather\n * than a refusal. */\n noseShift?: number;\n mouthShift?: number;\n /** Whether the ratio is this generator's own default — carried through as\n * the hold's own ratio and clamped to what the grid can reach — or a\n * caller's render measurement, which the hold is fitted to and which is\n * rejected when no hold renders it. Same split as `clampEyeShift`, for the\n * same reason. */\n clampSilhouetteRatio: boolean;\n /** hair_front's own rest centre and crop, for `hairFrontLandingAt` — its\n * own mesh geometry (`meshCellsFor`), the rows its lead and its join with\n * the plate are baked at — and the silhouette point used when\n * `headHalfWidth` is not measured (see `evaluateTurnCandidate`'s\n * silhouette-centre correction). Undefined when there is no hair_front\n * layer, in which case that correction falls back to the ideal hold\n * destinations, symmetric about the face centre, and no lead is modelled:\n * there are no bangs to carry one. */\n hairFrontSilhouette?: {\n x: number;\n centerY: number;\n cropW: number;\n cropH: number;\n };\n /** Every role with an opaque pixel in the eye-row band, per side, each\n * with its OWN rest x there — as the mcp layer measures it (`rowSpansByRole`),\n * a companion to a MEASURED `headHalfWidth`, not a caller-facing target.\n * `evaluateTurnCandidate` takes the OUTERMOST *landing* across a side's own\n * list, not the outermost REST x: which part ends up furthest out after\n * the turn can differ from which one drew furthest out at rest (the bangs'\n * own lead carrying them past a held back-hair edge, say). Absent on the\n * fallback path (no measured head to report edges for), which keeps\n * hair_front's own crop edge as the silhouette point on both sides, as if\n * it always owned them. */\n headEdges?: {\n left: { role: string; x: number }[];\n right: { role: string; x: number }[];\n };\n /** The bangs' side strand against each far iris, per side the caller\n * measured one (`options.strandEdges`): the side's sign — −1 for the −x\n * side, whose far stops are −15 and −30 — the carrier its iris renders on\n * (the eye grid of that side, the irises paired to sides by `part.x`, not\n * by role name), and its iris and run edges on the iris row. What caps the\n * eye family's depth below the plate's (`evaluateTurnCandidate`). Absent\n * without the option, when the plate alone caps it. */\n strands?: (IrisStrand & { side: -1 | 1; carrier: TurnCarrier })[];\n}\n\n/**\n * Where a hair_front vertex resting at `(x, y)` is sent at one turn stop — the\n * three-zone rule `bakeHairFrontSilhouetteWarp` bakes into a per-vertex\n * offset and `evaluateTurnCandidate` reads the composed landing of, factored\n * into one place so the two cannot drift apart. The zones are REST distances\n * from the face centre, so they do not move with the stop; only the\n * destinations do:\n *\n * - within the plate's painted half-width on this row, `edgeAt(y)`: where\n * the RENDERED plate lands that very point (`plateLandingAt` — the face's\n * own mesh and grid, not the bare surface), so the bangs sit on the face\n * they cover through the whole turn;\n * - out to `holdBase`: a straight ramp from the painted edge's rendered\n * landing onto `holdEdge`, the hold's own destination at this stop;\n * - beyond `holdBase`: `holdEdge`'s own displacement, slope 1 in REST x, so\n * the outer strands carry whatever silhouette change was asked for and\n * keep their spacing.\n *\n * A vertex exactly on the axis never leaves the first zone, so its zero\n * `side` is never read.\n */\nfunction hairFrontHoldTarget(\n x: number,\n y: number,\n deg: number,\n faceCenterX: number,\n plateLandingAt: (deg: number, x: number, y: number) => number,\n edgeAt: (y: number) => number,\n holdBase: number,\n holdEdge: number,\n): number {\n const local = x - faceCenterX;\n const dist = Math.abs(local);\n const side = Math.sign(local);\n const painted = edgeAt(y);\n if (dist <= painted) return plateLandingAt(deg, x, y);\n if (dist <= holdBase) {\n const inner = plateLandingAt(deg, faceCenterX + side * painted, y);\n const u = (dist - painted) / (holdBase - painted);\n return inner + (faceCenterX + side * holdEdge - inner) * u;\n }\n return x + side * (holdEdge - holdBase);\n}\n\n/**\n * hair_front's own silhouette landing at an ARBITRARY rest point — not the\n * analytic hold + lead at that point, but what the renderer actually draws\n * there, on the mesh the bake ships: `renderedLandingX` on the bangs' own\n * mesh, every vertex landed as the two warps land it — the hold's own target\n * for that vertex (`holdTargetAt`, the rule the bake writes) plus its row's\n * share of the turn lead — and the point read over the triangle the mesh\n * draws it with. Interpolating anything earlier — the rest xs, or an analytic\n * lead at a row the mesh has no vertex on — is a different number wherever a\n * hold zone's boundary falls between two columns, or the lead's own\n * `u^HAIR_SWAY_CURL` curve bends between two rows.\n *\n * `leadShift` is the bangs' SIGNED tip lead in px at the stop being read: the\n * lead warp is keyed at ±30 (`0 − tipShift·u^CURL` at −30, `tipShift·u^CURL`\n * at +30, see `bakeHairSwayWarp`) and blends linearly between, so at stop d it\n * is `tipShift·d/30` — `−tipShift` at full far turn. Each vertex lands at its\n * hold target plus its own row's share of it, `leadShift·u^HAIR_SWAY_CURL`:\n * `u` is its distance from the pinned root as a fraction of the crop's height,\n * the shipped lead warp's own shape.\n *\n * The point need not be inside hair_front's own crop at all — past its outer\n * column or row this reads the end column or row, the landing of the nearest\n * point the mesh actually draws.\n */\nfunction hairFrontLandingAt(\n x: number,\n y: number,\n hairFront: { x: number; centerY: number; cropW: number; cropH: number },\n holdTargetAt: (restX: number, restY: number) => number,\n leadShift: number,\n): number {\n const { cols, rows } = meshCellsFor(hairFront.cropW, hairFront.cropH);\n const top = hairFront.centerY + hairFront.cropH / 2;\n return renderedLandingX(\n {\n x: hairFront.x,\n y: hairFront.centerY,\n cropW: hairFront.cropW,\n cropH: hairFront.cropH,\n cols,\n rows,\n scaleX: 1,\n scaleY: 1,\n rotation: 0,\n },\n (vx, vy) =>\n holdTargetAt(vx, vy) +\n leadShift * Math.pow((top - vy) / hairFront.cropH, HAIR_SWAY_CURL),\n x,\n y,\n );\n}\n\n/**\n * The most sideways travel a MEASURED shell can swallow at this radius: how far\n * the face may slide before the plate's own edge reaches the hold's boundary.\n *\n * Read off the BEND ALONE (`bendOnlyLandingAt` — the rendered plate at a stop\n * with no travel in it; each row's own chin swing, which does not scale with\n * the travel, stays in), the slide being what is solved for here: at each\n * turned stop the plate's painted edges land either side of the face centre —\n * read at every guard row, kept SIGNED — and the slide then pushes the edge on\n * the side it moves toward further out while pulling the other one in. So\n * only the TOWARD edge binds, at whichever row it lands furthest. A\n * max-absolute reach would bind on the near edge instead, which a tight bend\n * throws wide precisely BEFORE the slide pulls it back inside the shell, and\n * would refuse the radius the slide was about to rescue.\n *\n * Each stop carries |deg|/30 of the travel, so the room one stop has left is\n * scaled back up by 30/|deg| to say what it allows at FULL turn, and the\n * tightest stop wins. Floored at 0: a plate already reaching past the shell\n * cannot be slid any further out.\n *\n * `holdEdgeAt` is asked for the boundary on the side the slide moves TOWARD,\n * because that is the only side the plate can breach: a measured shell need\n * not sit centred on the face (`TurnSolveContext.headEdges` records each\n * side's own extreme), and a shell 300 px out on one side and 500 on the\n * other has 300 px of room for a −30° slide however much it has for a +30°\n * one. Taking one symmetric number for both directions would let the plate\n * slide straight out of the narrow side and become the silhouette there.\n *\n * `askedTravel` is only read for the slide's SIGN at each stop, which is the\n * turn's own; the cap it returns is the whole ceiling, for the caller to take\n * a minimum with.\n */\nfunction shellTravelCap(\n faceCenterX: number,\n edgeAt: (y: number) => number,\n plateGuardRows: number[],\n askedTravel: number,\n bendOnlyLandingAt: (deg: number, x: number, y: number) => number,\n holdEdgeAt: (deg: number, side: -1 | 1) => number,\n): number {\n let cap = Infinity;\n for (const deg of HEAD_TURN_STOPS) {\n if (deg === 0) continue; // nothing slides at rest, so nothing to cap\n const slideSign: -1 | 1 = turnSlide(askedTravel, deg) < 0 ? -1 : 1;\n // The toward edge's furthest landing over the guard rows, on the bend\n // alone.\n const towardReach = plateSideReachAt(\n bendOnlyLandingAt,\n faceCenterX,\n edgeAt,\n plateGuardRows,\n deg,\n slideSign,\n );\n cap = Math.min(\n cap,\n (Math.max(0, holdEdgeAt(deg, slideSign) - HOLD_CLEARANCE - towardReach) *\n HEAD_TURN_MAX_DEG) /\n Math.abs(deg),\n );\n }\n return cap;\n}\n\n/** Everything ONE hold ratio settles inside `evaluateTurnCandidate`: the\n * travel that ratio leaves the sliding plate, the map that carries it, and\n * where each side's silhouette lands through that map, every `headEdges`\n * owner of the face family at the depths the evaluation was handed. The\n * ratio the rig ships is picked among these — fitted until\n * `renderedSilhouetteRatio` is the target, whether that target was measured\n * or defaulted. */\ninterface TurnHoldEval {\n /** The hold's own ratio: where its boundary is sent at full turn, as a\n * fraction of the rest distance it keeps. */\n ratio: number;\n /** The sideways travel it leaves the face at full turn, px. */\n travel: number;\n /** Its column map at the −30° stop, where every cue is measured, for a\n * point resting at `y`. */\n mapAt: (y: number) => TurnColumnMap;\n /** The surface `mapAt` is read off: the candidate's radius, this travel. */\n surface: TurnSurface;\n holdBase: number;\n holdEdgeAt: (deg: number) => number;\n /** Whether any stop sends the hold edge inside the plate's own reach, which\n * runs the ramp between them backwards — never acceptable, see\n * `bakeHairFrontSilhouetteWarp`. */\n folds: boolean;\n /** The ratio at which the tightest stop's hold edge would exactly clear the\n * plate ON THIS HOLD's own slide — a seed for the range's floor, not the\n * floor itself, see `holdRange`. */\n foldLowerBound: number;\n silhouetteCenterShift: number;\n renderedSilhouetteRatio: number;\n /** The silhouette's own rest span, px: what the rendered ratio is a\n * fraction of, and so the scale a px tolerance on it converts through. */\n restSpan: number;\n}\n\n/** Aitken's Δ² extrapolation of a geometrically contracting sequence from\n * three consecutive terms — the fixed point itself when the contraction is\n * linear — clamped into [0, cap], the depths a solve can return and so where\n * the fixed point lies. The last term as it stands when the sequence has\n * stopped moving (a depth pinned at a bound) or its steps do not contract,\n * where the formula has nothing to extrapolate from. */\nfunction aitken(x0: number, x1: number, x2: number, cap: number): number {\n const d1 = x1 - x0;\n const d2 = x2 - x1;\n const denominator = d2 - d1;\n if (denominator === 0) return x2;\n const x = x2 - (d2 * d2) / denominator;\n return Number.isFinite(x) ? Math.min(cap, Math.max(0, x)) : x2;\n}\n\n/**\n * The deepest eye depth in [0, `plateCap`] at which every strand margin holds\n * — each of `margins` one far stop of one side with a face-side edge, mapping\n * a depth to that stop's clearance less its allowance (`evaluateTurnCandidate`):\n * - `plateCap` itself when every margin is ≥ 0 there — the plate binds\n * first, and the rig is the one built without the strand, byte for byte;\n * - `undefined` when some margin is < 0 at depth 0 — no depth keeps that\n * iris clear, so the bound cannot hold at this radius;\n * - otherwise the satisfying end of a TURN_BISECT_STEPS bisection.\n * The bisection is valid because every margin is non-increasing in the depth:\n * the iris's landing moves toward the far side with it (`mapX` is monotone,\n * and both interpolations weight it non-negatively), while within one hold the\n * run's landing does not depend on the eye depth at all.\n */\nfunction strandDepthCap(\n margins: ((depth: number) => number)[],\n plateCap: number,\n): number | undefined {\n const holds = (depth: number) => margins.every((m) => m(depth) >= 0);\n if (holds(plateCap)) return plateCap;\n if (!holds(0)) return undefined;\n let lo = 0;\n let hi = plateCap;\n for (let i = 0; i < TURN_BISECT_STEPS; i++) {\n const mid = (lo + hi) / 2;\n if (holds(mid)) lo = mid;\n else hi = mid;\n }\n return lo;\n}\n\n/**\n * One radius, evaluated against the cues: the candidate it yields, or which\n * target blocked it and what it could have done instead.\n *\n * Everything hangs off ONE number here, the hold's own ratio: it sizes the\n * face's sideways travel (`shellTravelCap` measures the shell's room against\n * where the hold sends its boundary), and that travel builds the map every\n * landing is read through — so a slide that pulls an over-bent near edge back\n * inside the shell rescues a radius the bend alone would have folded, and a\n * bend that folds on its own still loses one. `evaluateHold` is that whole\n * chain for one ratio, and nothing outside it may assume a hold.\n *\n * The silhouette target is a measurement of a RENDER — the outermost opaque\n * edge on each side, whichever part owns it — so the hold is FITTED to it\n * rather than set from it: `holdRange` samples the holds this radius can ship\n * and the fit bisects the sampled interval that straddles the ask, which lands\n * on it exactly. What a render shows is NOT monotone in the hold (see\n * `holdRange`), so no sample's orientation is assumed. Where the target came\n * from decides only what happens when the range cannot render it: a\n * CALLER-measured one is refused, naming that range, and a DEFAULTED one takes\n * the nearest end of it and is reported in `TurnSolveReport.clamped`. Folding\n * is never acceptable either way.\n *\n * The silhouette and the depths are ONE fixed point. A `headEdges` owner of\n * the face family — a brow, a blush, a white, the nose, a mouth — lands\n * through its group grid, whose keyforms carry its family's solved depth\n * (`bakeTurnGroupWarp2D`'s `shiftAt`); the eye depth is solved against the\n * silhouette's centre, and the nose's and the mouth's against that centre and\n * the eyes' achieved shift. So each pass fits the hold with every owner landed\n * at the pass before's depths (none on the first — the read a hold-owned\n * silhouette gets whatever the depths), solves the three depths on it, and\n * RE-READS that same hold at the depths it solved: when the re-read moves\n * neither the centre nor the rendered span by more than TURN_DEPTH_EPS px,\n * the pass is the fixed point, and the candidate reports the re-read — the\n * numbers the emitted grids render — with the depths that produced it. A\n * silhouette owned by the bangs, the back hair, the body or the plate does\n * not move with a depth, so it settles on the first pass, the single-pass\n * solve to the bit. Otherwise the passes contract geometrically (an owner's\n * landing moves by a fraction of the depth it feeds back into), accelerated by\n * Aitken's Δ² on the three depths every second pass; a radius that has not\n * settled within TURN_SETTLE_PASSES yields a `settle` miss rather than a\n * candidate reported off its render, and the sweep moves on to the next\n * radius — an edge owned on BOTH sides by the family the eye cue slides\n * leaves that cue nearly independent of the depth, which no pass count\n * settles, and a layer set no sampled radius settles is refused by\n * `solveTurnModel`, naming that. The refusals — a caller's silhouette the\n * range cannot render, a caller's eye shift below the floor, less than the\n * pair already slides at depth 0 — are issued at the fixed point too, on the\n * range and bounds the shipped depths have, not on a pass's guess. An eye\n * shift the art's room cuts short at the cap (the plate's edge, or the strand\n * bound on the far irises, best effort) is no refusal, whoever passed it: the\n * candidate keeps the capped depth, and says whether the strand bound could\n * hold here at all (`strandFeasible`) and how much of each far iris the\n * bangs still cover (`strandOverlap`).\n */\nfunction evaluateTurnCandidate(\n ctx: TurnSolveContext,\n radius: number,\n): TurnCandidate | TurnCandidateMiss {\n // This radius as a head: the surface every map below is read off, at the\n // travel the hold under evaluation leaves the face.\n const surfaceAt = (travel: number) =>\n turnSurface({\n faceCenterX: ctx.faceCenterX,\n faceCenterY: ctx.faceCenterY,\n radius,\n travel,\n nodRadius: ctx.nodRadius,\n lattice: ctx.lattice,\n profile: ctx.profile,\n eyeRowY: ctx.eyeRowY,\n });\n // The travel is settled BEFORE the map that carries it, the map being built\n // from it: the face slides INSIDE a shell the bangs hold, so what it may\n // slide is what that shell has room for — measured on the bend alone, as\n // the plate renders it (the chin swing rides the rows, not the travel, so\n // it is in this landing too: what the travel has to fit beside).\n const bendOnlyLandingAt = plateLandingOn(ctx.plate, rowMapsOf(surfaceAt(0)));\n // The measured silhouette's own rest extreme on one side, when `headEdges`\n // recorded one there. Read both by the travel cap below — which side of the\n // shell the sliding plate has to stay inside — and by `restAt`, so the two\n // cannot disagree about where the shell's boundary actually is.\n const measuredEdgeAt = (side: -1 | 1): number | undefined => {\n const candidates = side < 0 ? ctx.headEdges?.left : ctx.headEdges?.right;\n if (candidates === undefined || candidates.length === 0) return undefined;\n const xs = candidates.map((c) => c.x);\n return side < 0 ? Math.min(...xs) : Math.max(...xs);\n };\n // That side's own half-width: a measured shell can sit off centre, and the\n // narrow side is the one the plate can breach. Without an edge list there\n // the measured half-width stands in on both sides, as it always did.\n const shellHalfWidthAt = (side: -1 | 1): number => {\n const edge = measuredEdgeAt(side);\n return edge === undefined\n ? ctx.headHalfWidth!\n : side * (edge - ctx.faceCenterX);\n };\n const unit = headTurnParallaxUnit(radius);\n // The eyes' depth cap at this radius (`familyDepthCap`), read once for the\n // strand bound — which only ever tightens it, and is handed back to the\n // eye solve — and Aitken's clamp below.\n const plateCapOf = (marks: TurnLandmark[]) =>\n familyDepthCap(marks, ctx.plateEdgeX, unit);\n const eyePlateCap = plateCapOf(ctx.landmarks.eye);\n // The bangs' own root-pinned turn lead (bakeHairSwayWarp, shared with the\n // sway): the shipped warp's own full TIP shift, which each row takes its\n // `u^HAIR_SWAY_CURL` share of. It moves every row toward the far side by\n // that row's amount regardless of which side of the axis a column sits on —\n // a raw offset summed onto the hold's own target at each vertex inside\n // `hairFrontLandingAt`, exactly like `bakeHairSwayWarp` and\n // `bakeHairFrontSilhouetteWarp` sum their offsets on the shipped mesh; the\n // rows the renderer interpolates between are the MESH's, so the row goes\n // down with it rather than a lead pre-sampled at the measured row. Only\n // hair_front leads — a role `landingOfRole` sends down any other path does\n // not use this at all. The hold does not move it, so it is settled once,\n // outside the fit.\n const leadTipShift =\n ctx.hairFrontSilhouette === undefined ? 0 : HAIR_FRONT_DEPTH * unit;\n\n // Where the bangs render a rest point `(x, y)` at the stop `deg` on one\n // hold: their own mesh, each vertex holding the outline where the bake\n // sends it (`hairFrontHoldTarget`, over the plate as it renders on that\n // hold's slide, `plateLandingAt`) plus its row's share of the SIGNED tip\n // lead at that stop, `leadShift` (`hairFrontLandingAt`). Read only on a\n // layer set with bangs: the silhouette's own landing checks for them, and\n // solveTurnModel builds strands only where they exist.\n const bangsLandingAt = (\n hold: { holdBase: number; holdEdgeAt: (deg: number) => number },\n plateLandingAt: (deg: number, x: number, y: number) => number,\n deg: number,\n leadShift: number,\n x: number,\n y: number,\n ): number =>\n hairFrontLandingAt(\n x,\n y,\n ctx.hairFrontSilhouette!,\n (vx, vy) =>\n hairFrontHoldTarget(\n vx,\n vy,\n deg,\n ctx.faceCenterX,\n plateLandingAt,\n ctx.edgeAt,\n hold.holdBase,\n hold.holdEdgeAt(deg),\n ),\n leadShift,\n );\n\n // The strand bound's reads on one hold (`ctx.strands`), at each FAR stop of\n // each measured side — σ = −1 at −15 and −30, +1 at +15 and +30; depth\n // carries the near iris away from its own strand, so only the far side is\n // read. Every edge is a rest point on the iris row, landed exactly as it\n // renders at that stop with AngleY, gaze, the sway and MouthForm at rest:\n // the run's two edges on the bangs' own mesh — the hold's target at that\n // stop plus the lead there, `tipShift·deg/30` (the lead warp is keyed at\n // ±30 and blends linearly) — neither of which moves with the eye depth; the\n // iris's through its own carrier on that stop's map, the eye grid's node\n // rule shifted by the depth's share at that stop, `depth·unit·deg/30`. The\n // bangs and the iris ride different deformations, so neither is assumed to\n // follow the other.\n //\n // Checking the stops is exact: at AngleY 0 every vertex's landing is linear\n // in AngleX between two stops — the grid's nodes blend parameter-linearly,\n // the bangs' hold is keyed at the same stops and their lead at ±30, and a\n // warp child binds at its rest pose, so the bilinear and the barycentric\n // weights are fixed — so a clearance that meets its allowance at rest and\n // at every stop meets it everywhere between them.\n const strandStopsOn = (hold: TurnHoldEval) => {\n const rowMaps = rowMapsOf(hold.surface);\n const plateLandingAt = plateLandingOn(ctx.plate, rowMaps);\n return (ctx.strands ?? []).map((strand) => {\n const iris: TurnLandmark = {\n x: strand.carrier.part.x,\n w: strand.carrier.part.cropW,\n carrier: strand.carrier,\n };\n const stops = HEAD_TURN_STOPS.filter(\n (deg) => Math.sign(deg) === strand.side,\n ).map((deg) => {\n const runAt = (x: number) =>\n bangsLandingAt(\n hold,\n plateLandingAt,\n deg,\n (leadTipShift * deg) / HEAD_TURN_MAX_DEG,\n x,\n strand.y,\n );\n return {\n deg,\n runOuter: runAt(strand.runOuter),\n runFace: strand.runFace === null ? null : runAt(strand.runFace),\n irisAt: (depth: number, x: number) =>\n landmarkLandingX(\n iris,\n (y) => rowMaps(deg, y),\n deg,\n (depth * unit * deg) / HEAD_TURN_MAX_DEG,\n x,\n strand.y,\n ),\n };\n });\n // Clearance = σ·(landed runFace − landed irisOuter), and the allowance\n // is the painted clearance capped at 0 (the rest landing is the\n // identity): an iris painted clear of the run may close to touching,\n // one painted under it — a covered centre included — keeps its rest\n // overlap but does not deepen it. No pixel margin: both are pixel\n // boundaries on one row. Why not the full painted gap: the face's own\n // foreshortening already closes it at depth 0, so that rule would leave\n // most characters with no depth at all. Undefined without a face-side\n // edge, which has nothing to keep the iris clear of.\n const { runFace } = strand;\n if (runFace === null) return { strand, stops, margins: undefined };\n const allowance = Math.min(0, strand.side * (runFace - strand.irisOuter));\n const margins = stops.map(\n (stop) => (depth: number) =>\n strand.side * (stop.runFace! - stop.irisAt(depth, strand.irisOuter)) -\n allowance,\n );\n return { strand, stops, margins };\n });\n };\n\n /** How much of each far iris the run covers at eye depth `depth` — the\n * landed iris span inside the landed run, at the far stop where that is\n * largest — per side that has any: `TurnSolveReport.strandOverlap`. */\n const strandOverlapOf = (\n sides: ReturnType<typeof strandStopsOn>,\n depth: number,\n ): TurnCandidate[\"strandOverlap\"] => {\n const overlap: NonNullable<TurnCandidate[\"strandOverlap\"]> = {};\n for (const { strand, stops, margins } of sides) {\n const s = strand.side;\n // In outward-positive x: the iris spans [s·inner, s·outer], the run\n // [s·face, s·outer] — face-ward without end when it has no face edge.\n const covered = (\n irisOuter: number,\n irisInner: number,\n runOuter: number,\n runFace: number | null,\n ) =>\n Math.max(\n 0,\n Math.min(s * irisOuter, s * runOuter) -\n Math.max(s * irisInner, runFace === null ? -Infinity : s * runFace),\n );\n let worst: { deg: number; px: number } | undefined;\n for (const stop of stops) {\n const px = covered(\n stop.irisAt(depth, strand.irisOuter),\n stop.irisAt(depth, strand.irisInner),\n stop.runOuter,\n stop.runFace,\n );\n if (px > 0 && (worst === undefined || px > worst.px)) {\n worst = { deg: stop.deg, px };\n }\n }\n if (worst === undefined) continue;\n overlap[s < 0 ? \"left\" : \"right\"] = {\n deg: worst.deg,\n px: worst.px,\n hh: worst.px / ctx.hh,\n restPx: covered(\n strand.irisOuter,\n strand.irisInner,\n strand.runOuter,\n strand.runFace,\n ),\n held: margins !== undefined && margins.every((m) => m(depth) >= 0),\n };\n }\n return overlap.left === undefined && overlap.right === undefined\n ? undefined\n : overlap;\n };\n\n const evaluateHold = (ratio: number, depths: TurnDepths): TurnHoldEval => {\n // The boundary stays put and its DESTINATION moves: the full ratio at the\n // outer stops, none of it at rest, linear in between.\n const holdEdgeFrom = (base: number) => (deg: number) =>\n base * (1 + ((ratio - 1) * Math.abs(deg)) / HEAD_TURN_MAX_DEG);\n // A measured head IS the hold's boundary, so the plate has to stay inside\n // it once slid — `shellTravelCap` is what keeps it there. A hold base\n // derived from the plate instead (below) is measured on the SLID maps and\n // clears the slid plate by construction, so there the whole ask stands.\n const travel =\n ctx.headHalfWidth === undefined\n ? ctx.travel\n : Math.min(\n ctx.travel,\n shellTravelCap(\n ctx.faceCenterX,\n ctx.edgeAt,\n ctx.plateGuardRows,\n ctx.travel,\n bendOnlyLandingAt,\n (deg, side) => {\n const rest = shellHalfWidthAt(side);\n // Never past the shell's own REST edge, whatever the hold\n // does: a hold wider than it (the fit sends one there to keep\n // a rendered ratio of 1 against the bangs' own lead) moves the\n // bangs, and the parts that draw the outline beside them — a\n // static hair_back above all — stay where they are drawn.\n return Math.min(rest, holdEdgeFrom(rest)(deg));\n },\n ),\n );\n const surface = surfaceAt(travel);\n // One memoised reader of this surface's maps for every carrier read on\n // it: the −30° map for a point resting at `y`, where every cue is\n // measured, is what each landmark's and candidate's nodes are read off.\n const rowMaps = rowMapsOf(surface);\n const mapAt = (y: number) => rowMaps(-HEAD_TURN_MAX_DEG, y);\n // The plate as it renders on THIS hold's slide: what its painted edge is\n // read off below, and what the bangs' join sits on.\n const plateLandingAt = plateLandingOn(ctx.plate, rowMaps);\n // Without a measured head the boundary is the outermost the slid plate\n // ever reaches, clear of it by HOLD_CLEARANCE.\n const holdBase =\n ctx.headHalfWidth ??\n plateReach(\n plateLandingAt,\n ctx.faceCenterX,\n ctx.edgeAt,\n ctx.plateGuardRows,\n ) + HOLD_CLEARANCE;\n const holdEdgeAt = holdEdgeFrom(holdBase);\n\n // The floor of the bracket this hold could have been fitted in: the ratio\n // the plate-fold geometry allows. Computed EVERY time, whether or not it\n // binds, so a refusal never advertises a range it would have narrowed.\n //\n // SAFE but not TIGHT: `reach` is read off `plateLandingAt`, the slid\n // plate, and with a measured head the ratio sizes that slide as well\n // (`shellTravelCap` reads `holdEdgeAt`). At the binding stop the cap\n // moves with the ratio at `holdBase` per unit, i.e. the reach moves at\n // `holdBase·|deg|/30` — exactly the rate the hold edge itself moves — so a\n // ratio narrower than this bound arrives with a smaller slide and can\n // still clear the plate. Everything at or above the bound holds; some\n // below it do too. The reach is the same list of rows through the same\n // function the bake's own guard iterates (`bakeHairFrontSilhouetteWarp`),\n // so a hold that clears it here clears it there.\n let foldLowerBound = -Infinity;\n let folds = false;\n for (const deg of HEAD_TURN_STOPS) {\n if (deg === 0) continue; // proven never to bind — holdBase always clears the rest reach\n const reach = plateReachAt(\n plateLandingAt,\n ctx.faceCenterX,\n ctx.edgeAt,\n ctx.plateGuardRows,\n deg,\n );\n // What the ratio would have to be for the hold edge to clear the plate\n // at this stop, given the stop gets |deg|/30 of the ratio's travel.\n foldLowerBound = Math.max(\n foldLowerBound,\n 1 +\n ((reach + HOLD_CLEARANCE) / holdBase - 1) *\n (HEAD_TURN_MAX_DEG / Math.abs(deg)),\n );\n if (holdEdgeAt(deg) <= reach) folds = true;\n }\n\n // The silhouette point each side's centre is read from:\n // `measure_turn_reference` reads the eye pair against the OPAQUE UNION's\n // own centre, not an assumed faceCenterX-symmetric one, so with\n // `headEdges` this is that same union's own extreme — the min (−x side) /\n // max (+x side) of every candidate's own rest x, not `holdBase`'s\n // idealised, centred stand-in for it (`holdBase` still sizes the HOLD\n // itself; this is only where the DRIFT is measured from). Without an edge\n // list for this side — no measured head at all, or one measured but with\n // nothing recorded there — the measured head's own edge (`holdBase` IS\n // that edge then, on the nose by construction), or hair_front's own crop\n // edge otherwise: the best available stand-in for where a render's\n // outermost opaque pixel sits when nothing measured it.\n const restAt = (side: -1 | 1): number => {\n const edge = measuredEdgeAt(side);\n if (edge !== undefined) return edge;\n return ctx.headHalfWidth !== undefined ||\n ctx.hairFrontSilhouette === undefined\n ? ctx.faceCenterX + side * holdBase\n : ctx.hairFrontSilhouette.x +\n (side * ctx.hairFrontSilhouette.cropW) / 2;\n };\n // Where the bangs land a point on the eye row at full turn, on this hold\n // and the rendered plate, with the full far lead.\n const hairFrontAt = (x: number): number =>\n ctx.hairFrontSilhouette === undefined\n ? x\n : bangsLandingAt(\n { holdBase, holdEdgeAt },\n plateLandingAt,\n -HEAD_TURN_MAX_DEG,\n -leadTipShift,\n x,\n ctx.eyeRowY,\n );\n // Where a named role's OWN rest x lands after the turn — the mcp measures\n // the union of every layer's opaque pixels, so an edge can belong to any\n // role, not just the bangs. `face` and the FEATURE_NOD_DEPTH family (the\n // eye stack, lashes, brows, blush, the nose, both mouths) each ride their\n // own group grid, so each is read through its own carrier — the grid and\n // mesh it renders with — at the shift that grid's keyforms carry: its\n // turn family's depth in `depths` times the unit, toward the far side\n // (`bakeTurnGroupWarp2D`'s `shiftAt` at this stop), none for the plate,\n // which IS the surface. Those depths are what this very hold is being\n // solved for, which is why the candidate is a fixed point (see this\n // function's doc). `hair_back` and `body` ride their own rigid deformers,\n // not a group grid, so neither reads a map at all. Anything not in\n // `ROLE_TABLE` is a bug, not a role to render as unmoved.\n const landingOfRole = (role: string, x: number): number => {\n if (role === \"hair_front\") return hairFrontAt(x);\n if (role === \"hair_back\") {\n // It holds the head's outline: it rides a head that no longer travels\n // and has no turn binding or warp of its own, so it lands where it\n // sits.\n return x;\n }\n if (role === \"body\") {\n // The head no longer translates on the turn — its travel is in the\n // group grids, where only what rides a grid carries it — so the head's\n // frame is the rest frame and the body lands at its own AngleX travel\n // alone: the \"from\" value of its own translateX binding at this stop.\n return x - BODY_TURN_FOLLOW * travel;\n }\n if (\n role === \"face\" ||\n FEATURE_NOD_DEPTH[roleFamily(role)] !== undefined\n ) {\n const carrier = ctx.carriers.get(role);\n if (carrier === undefined) {\n // The mcp measures edges off the layers it rigs, so a face-family\n // role it names is one this layer set has — anything else is a\n // caller's list that does not belong to these layers.\n throw new Error(\n `auto-rig: evaluateTurnCandidate: headEdges names \"${role}\", which this layer set has no layer for`,\n );\n }\n const shift = role === \"face\" ? 0 : -depths[turnFamily(role)] * unit;\n return carrierLandingX(\n carrier,\n (nodeX, nodeY) =>\n groupNodeLanding(\n carrier.anchor,\n mapAt,\n -HEAD_TURN_MAX_DEG,\n shift,\n nodeX,\n nodeY,\n ).x,\n x,\n ctx.eyeRowY,\n );\n }\n throw new Error(\n `auto-rig: evaluateTurnCandidate: unrecognised role \"${role}\"`,\n );\n };\n const landingAt = (side: -1 | 1): number => {\n const candidates = side < 0 ? ctx.headEdges?.left : ctx.headEdges?.right;\n if (candidates === undefined || candidates.length === 0) {\n return hairFrontAt(restAt(side));\n }\n const landings = candidates.map((c) => landingOfRole(c.role, c.x));\n return side < 0 ? Math.min(...landings) : Math.max(...landings);\n };\n return {\n ratio,\n travel,\n mapAt,\n surface,\n holdBase,\n holdEdgeAt,\n folds,\n foldLowerBound,\n // How far the silhouette's own centre drifts off the face centre at full\n // turn, against how far it already sat off centre at rest (zero unless\n // hair_front's crop is itself off-centre) — the held shell keeps its own\n // place while the face slides inside it, so this is the drift the hold\n // and the bangs' lead leave behind, and the slide itself shows up in the\n // eye cue instead of cancelling out of it.\n // `measure_turn_reference` reads the eye pair against THIS moved centre,\n // not the face centre, so it is what the eye cue is corrected by.\n silhouetteCenterShift:\n (landingAt(1) + landingAt(-1)) / 2 - (restAt(1) + restAt(-1)) / 2,\n // What a render actually shows: the real landing span over the real rest\n // span, the same pair silhouetteCenterShift reads — the measure the\n // hold is FITTED against, see this function's own doc.\n renderedSilhouetteRatio:\n (landingAt(1) - landingAt(-1)) / (restAt(1) - restAt(-1)),\n restSpan: restAt(1) - restAt(-1),\n };\n };\n\n // Every hold this radius can actually ship, sampled across its own usable\n // range: from the narrowest that does not fold the ramp onto the strands up\n // to the widest ratio the targets accept, the bangs being free to hold the\n // outline wherever they are sent now that no grid edge stops them. What a\n // render SHOWS is not assumed monotone in the hold — a wider hold buys more\n // travel (`shellTravelCap`), and the travel moves the plate's own edge that\n // the bangs' inner strands ride — so the range is sampled rather than read\n // off its two ends, and the fit below works in whichever sampled interval\n // actually straddles the ask. Every sample is fold-free, so anything the\n // fit returns from one can be shipped.\n //\n // `foldLowerBound` is a SEED for the floor, not the floor: it reads the\n // plate's reach off one hold's own slide, and that reach does not move with\n // the hold the way it assumes. A narrower hold buys a smaller slide, which\n // pulls the plate's toward edge in but pushes its AWAY edge out, and\n // `plateReachAt` takes the larger of the two — so the seed can itself fold,\n // and the real floor is bisected between it and the ceiling, where the\n // slide is largest and the hold furthest out. Undefined when even the\n // ceiling folds: the radius can hold nothing, so it has nothing to offer.\n const holdRange = (depths: TurnDepths): TurnHoldEval[] | undefined => {\n const ceiling = evaluateHold(TURN_RATIO_MAX, depths);\n if (ceiling.folds) return undefined;\n let floor = evaluateHold(\n Math.min(ceiling.foldLowerBound, ceiling.ratio),\n depths,\n );\n if (floor.folds) {\n let lo = floor;\n let hi = ceiling;\n for (let i = 0; i < TURN_BISECT_STEPS; i++) {\n const mid = evaluateHold((lo.ratio + hi.ratio) / 2, depths);\n if (mid.folds) lo = mid;\n else hi = mid;\n }\n floor = hi;\n }\n const samples = [floor];\n for (let i = 1; i < TURN_HOLD_SAMPLES; i++) {\n const sample = evaluateHold(\n floor.ratio + ((ceiling.ratio - floor.ratio) * i) / TURN_HOLD_SAMPLES,\n depths,\n );\n if (!sample.folds) samples.push(sample);\n }\n samples.push(ceiling);\n return samples;\n };\n\n /** What this radius offers a refused caller: the ratios a render of it can\n * show, clipped to the domain `resolveTurnTargets` accepts — offering one\n * outside it would be offering a number the validator throws on. Clipped\n * PER RADIUS, before `sweepTurnRadii` unions them, so each end of what the\n * sweep advertises is an end some radius can really be fitted to. The\n * neutral pair when nothing survives that clip (or there was nothing to\n * clip): `sweepTurnRadii`'s own min/max ignore it. */\n const silhouetteMiss = (span?: [number, number]): TurnCandidateMiss => {\n const lower = Math.max(span?.[0] ?? Infinity, SILHOUETTE_RATIO_MIN);\n const upper = Math.min(span?.[1] ?? -Infinity, TURN_RATIO_MAX);\n return lower > upper\n ? { blocked: \"silhouetteRatio\", lower: Infinity, upper: -Infinity }\n : { blocked: \"silhouetteRatio\", lower, upper };\n };\n\n const asked = ctx.silhouetteRatio;\n\n /**\n * The hold fitted to the ask with every `headEdges` owner landed at\n * `depths`: the hold, whether it had to be cut to the nearest end of the\n * range this radius renders, and that range when it was — the refusal a\n * CALLER-measured ask earns for that is issued at the fixed point below,\n * on the range the shipped depths have, not here on a pass's guess.\n * Undefined when even the widest hold folds, which no depth changes.\n */\n const fitHold = (\n depths: TurnDepths,\n ):\n | {\n hold: TurnHoldEval;\n silhouetteClamped: boolean;\n span?: [number, number];\n }\n | undefined => {\n // The hold the target names outright, tried first. Where it already\n // renders the target it IS the answer: a static shell renders its own\n // rest span whatever the hold does, so every hold there is equally\n // exact, and the one that leaves the bangs where the target says is the\n // one to ship rather than an arbitrarily narrower one a search would\n // settle on first.\n const natural = evaluateHold(asked, depths);\n // solveTurnDepthSigned's own px slack, on the span the rendered ratio is\n // a fraction of: the fit stops there, and a caller resubmitting the exact\n // interval a refusal reported is not refused again by a float ulp of the\n // division that reported it.\n const eps = TURN_DEPTH_EPS / natural.restSpan;\n if (\n !natural.folds &&\n Math.abs(natural.renderedSilhouetteRatio - asked) <= eps\n ) {\n return { hold: natural, silhouetteClamped: false };\n }\n const samples = holdRange(depths);\n if (samples === undefined) return undefined;\n // Every ratio between the extreme samples is on offer, the rendered ratio\n // being continuous in the hold: the sampled values are all attainable and\n // so is everything they straddle.\n const rendered = samples.map((h) => h.renderedSilhouetteRatio);\n const span: [number, number] = [\n Math.min(...rendered),\n Math.max(...rendered),\n ];\n // Of two holds, the one whose render is nearer the target — and between\n // two that both hit it, the one whose own ratio is nearer what was asked,\n // so a flat stretch of the range does not move the bangs for nothing.\n const nearer = (a: TurnHoldEval, b: TurnHoldEval) => {\n const ea = Math.abs(a.renderedSilhouetteRatio - asked);\n const eb = Math.abs(b.renderedSilhouetteRatio - asked);\n if (ea <= eps && eb <= eps) {\n return Math.abs(a.ratio - asked) <= Math.abs(b.ratio - asked) ? a : b;\n }\n return ea <= eb ? a : b;\n };\n if (asked < span[0] - eps || asked > span[1] + eps) {\n // No hold renders the ask: the nearest end of the rendered range stands\n // in, and says so. A DEFAULTED ask ships it and reports it — see\n // solveTurnModel's report; a CALLER-measured one is refused in the\n // range's own terms once the depths have settled.\n const wanted = asked < span[0] ? span[0] : span[1];\n const hold = samples.reduce((a, b) =>\n Math.abs(b.renderedSilhouetteRatio - wanted) <\n Math.abs(a.renderedSilhouetteRatio - wanted)\n ? b\n : a,\n );\n return { hold, silhouetteClamped: true, span };\n }\n // The sampled interval the target actually sits in, bisected on the\n // HOLD: the rendered ratio is continuous in it, so an interval whose\n // ends straddle the target contains a hold that renders it — whichever\n // way round those ends sit, which is why the direction is read off the\n // interval rather than assumed.\n let best = samples.reduce(nearer);\n let lo: TurnHoldEval | undefined;\n let hi: TurnHoldEval | undefined;\n for (let i = 0; i + 1 < samples.length; i++) {\n const a = samples[i].renderedSilhouetteRatio - asked;\n const b = samples[i + 1].renderedSilhouetteRatio - asked;\n if (a * b <= 0) {\n lo = samples[i];\n hi = samples[i + 1];\n break;\n }\n }\n for (let i = 0; lo !== undefined && hi !== undefined; i++) {\n if (\n i >= TURN_BISECT_STEPS ||\n Math.abs(best.renderedSilhouetteRatio - asked) <= eps\n ) {\n break;\n }\n const mid = evaluateHold((lo.ratio + hi.ratio) / 2, depths);\n if (!mid.folds) best = nearer(best, mid);\n // Keep the half the target is still inside, in the orientation these\n // two ends establish.\n if (\n (mid.renderedSilhouetteRatio - asked) *\n (lo.renderedSilhouetteRatio - asked) >\n 0\n ) {\n lo = mid;\n } else {\n hi = mid;\n }\n }\n return { hold: best, silhouetteClamped: false };\n };\n\n // The fixed point (see this function's doc): the depths every owner is\n // landed at on this pass, none on the first, and the pass before's when\n // the pass after next is Aitken-extrapolated from the two.\n let depths: TurnDepths = { eye: 0, nose: 0, mouth: 0 };\n let seed: TurnDepths | undefined;\n for (let pass = 0; ; pass++) {\n const fitted = fitHold(depths);\n if (fitted === undefined) return silhouetteMiss();\n const { hold, silhouetteClamped, span } = fitted;\n // Folding is never acceptable, fitted or clamped: `natural` is only kept\n // when it does not fold, every `holdRange()` sample is fold-free, and the\n // fit only ever replaces `best` with a non-folding evaluation — so a\n // folding hold here is a broken invariant, not a case to handle.\n if (hold.folds) {\n throw new Error(\n \"auto-rig: evaluateTurnCandidate: the hold it settled on folds\",\n );\n }\n // measure_turn_reference reads the eye pair against each pose's OWN\n // silhouette centre, not the face centre — at rest that centre IS the\n // face centre, but at full turn it has moved by `silhouetteCenterShift`.\n // The raw landmark slide `solveTurnDepthSigned` solves for is measured\n // against the face centre, so its target has to be `silhouetteCenterShift`\n // MINUS the requested magnitude's own px: adding the two back together at\n // measurement time (solveTurnModel's `achieved.eyeShift`) lands back on\n // the requested cue.\n //\n // That target can legitimately be positive (the centre's own drift\n // already meets or exceeds the request, so the eyes need to move toward\n // the NEAR side to land on it) as well as negative, which is exactly why\n // this calls the SIGNED solver directly: `solveTurnDepth`'s\n // magnitude-only wrapper would silently flip a negative request back to\n // positive, quietly hitting a different cue than the one asked for (a\n // request and its negation must produce the identical rig —\n // `TurnTargets.eyeShift`'s own \"the sign is ignored\" contract, taken\n // here, not by folding an already-negative result back to positive\n // later).\n //\n // With strands the eye family's depth is also capped by the strand bound\n // on this hold, best effort: the deepest depth that keeps every far iris\n // no deeper under its run than it is painted, or 0 — the least overlap\n // any depth leaves, each margin being non-increasing in the depth — where\n // no depth does, a face-spanning run (no face-side edge) included. The\n // rig is built either way; `strandFeasible` says which, for the fit to\n // prefer the radii where it holds.\n const strandSides =\n ctx.strands === undefined ? undefined : strandStopsOn(hold);\n let strandFeasible = true;\n let maxDepth: number | undefined;\n if (strandSides !== undefined) {\n const margins = strandSides.map((s) => s.margins);\n const cap = margins.every((m) => m !== undefined)\n ? strandDepthCap(\n margins.flatMap((m) => m!),\n eyePlateCap,\n )\n : undefined;\n strandFeasible = cap !== undefined;\n maxDepth = cap ?? 0;\n }\n const eye = solveTurnDepthSigned(\n hold.silhouetteCenterShift - ctx.eyeShift * ctx.hh,\n ctx.landmarks.eye,\n unit,\n hold.mapAt,\n ctx.plateEdgeX,\n maxDepth,\n );\n const features = solveFeatureDepths(\n ctx,\n (-eye.achieved + hold.silhouetteCenterShift) / ctx.hh,\n unit,\n hold.mapAt,\n hold.silhouetteCenterShift,\n );\n const solved: TurnDepths = {\n eye: eye.depth,\n nose: features.nose.depth,\n mouth: features.mouth.depth,\n };\n // The same hold, re-read with every owner at the depths it just solved:\n // what the emitted grids render. Settled when that read is the one the\n // hold was fitted on, to the solver's own px slack on the centre and on\n // the span.\n const settled = evaluateHold(hold.ratio, solved);\n if (\n Math.abs(settled.silhouetteCenterShift - hold.silhouetteCenterShift) >\n TURN_DEPTH_EPS ||\n Math.abs(settled.renderedSilhouetteRatio - hold.renderedSilhouetteRatio) *\n hold.restSpan >\n TURN_DEPTH_EPS\n ) {\n if (pass + 1 >= TURN_SETTLE_PASSES) return { blocked: \"settle\" };\n if (seed === undefined) {\n seed = depths;\n depths = solved;\n } else {\n // The plate's cap, even for eyes a strand bound caps tighter: an\n // extrapolated depth only lands the `headEdges` owners the next pass\n // fits its hold on, and that pass re-solves the eye depth under the\n // strand cap of its own hold — the solved depth, never this guess, is\n // what a candidate ships.\n depths = {\n eye: aitken(seed.eye, depths.eye, solved.eye, eyePlateCap),\n nose: aitken(\n seed.nose,\n depths.nose,\n solved.nose,\n plateCapOf(ctx.landmarks.nose),\n ),\n mouth: aitken(\n seed.mouth,\n depths.mouth,\n solved.mouth,\n plateCapOf(ctx.landmarks.mouth),\n ),\n };\n seed = undefined;\n }\n continue;\n }\n\n const {\n travel,\n mapAt,\n surface,\n holdBase,\n holdEdgeAt,\n silhouetteCenterShift,\n renderedSilhouetteRatio,\n } = settled;\n if (silhouetteClamped && !ctx.clampSilhouetteRatio) {\n return silhouetteMiss(span);\n }\n // A measured shift below the floor — less than the pair already slides\n // at depth 0 here — disqualifies the radius; a defaulted one takes what\n // the radius offers. A shift the art's room cuts short (the plate's edge,\n // or the strand bound) keeps the capped depth whoever passed it, and\n // `solveTurnModel` reports it clamped.\n if (!eye.reached && eye.limit === \"floor\" && !ctx.clampEyeShift) {\n return {\n blocked: \"eyeShift\",\n // eyeShift is a MAGNITUDE (the sign is ignored — see TurnTargets), so\n // a negative lower bound here would advertise a value the field's own\n // contract already rules out; 0 is the true floor.\n offeredShift: [\n Math.max(0, (-eye.attainable[1] + silhouetteCenterShift) / ctx.hh),\n (-eye.attainable[0] + silhouetteCenterShift) / ctx.hh,\n ],\n };\n }\n\n // Each eye's rest width against its turned width, where the slide put\n // it: its two edges on its own centre row, read as its mesh renders them.\n const eyeShiftPx = -eye.depth * unit;\n const scaleOf = (l: TurnLandmark) =>\n (landmarkLandingX(\n l,\n mapAt,\n -HEAD_TURN_MAX_DEG,\n eyeShiftPx,\n l.x + l.w / 2,\n l.y ?? 0,\n ) -\n landmarkLandingX(\n l,\n mapAt,\n -HEAD_TURN_MAX_DEG,\n eyeShiftPx,\n l.x - l.w / 2,\n l.y ?? 0,\n )) /\n l.w;\n // A −30° turn foreshortens the −x side: that eye is the far one. Dividing\n // the two rest-normalised scales IS the cue — the reference's own far/near\n // ratio is already divided by its rest one.\n const far = ctx.landmarks.eye.reduce((a, b) => (b.x < a.x ? b : a));\n const near = ctx.landmarks.eye.reduce((a, b) => (b.x > a.x ? b : a));\n // Read on this settled pass's hold — the settled re-read differs from it\n // only in the silhouette, which no strand edge rides — at the depth the\n // eyes ship with.\n const strandOverlap =\n strandSides === undefined\n ? undefined\n : strandOverlapOf(strandSides, eye.depth);\n return {\n radius,\n ratio: scaleOf(far) / scaleOf(near),\n unit,\n travel,\n mapAt,\n surface,\n holdBase,\n holdEdgeAt,\n eye,\n nose: features.nose,\n mouth: features.mouth,\n silhouetteClamped,\n silhouetteCenterShift,\n renderedSilhouetteRatio,\n strandFeasible,\n ...(strandOverlap === undefined ? {} : { strandOverlap }),\n };\n }\n}\n\n/** A swept radius: the candidate it yielded and WHERE in the sweep it sat.\n * The feasible radii are not one unbroken run any more — a caller-measured\n * silhouette refuses a radius whose own rendered range cannot be fitted to\n * it, and that can bite in the middle of the sweep — so which candidates are\n * NEIGHBOURS has to be read off the sweep rather than off the array. */\ntype SweptCandidate = TurnCandidate & { sweepIndex: number };\n\n/** The radii that can carry the turn, and what blocked the ones that cannot. */\ninterface TurnSweep {\n candidates: SweptCandidate[];\n /** The narrowest silhouette a render of the radii blocked for the silhouette\n * could have shown, inside the ratios the targets accept (see\n * `TurnCandidateMiss.lower`); Infinity when no radius was blocked for the\n * silhouette at all, or when none of the blocked ones renders anything a\n * caller is allowed to ask for. */\n heldRatioLower: number;\n /** The widest silhouette a render of any radius blocked for the silhouette\n * could have shown — an UPPER bound, same convention; -Infinity in the same\n * two cases. */\n heldRatioUpper: number;\n /** The widest eye shift the blocked radii offered, in shift units; absent\n * when the slide blocked none. */\n offeredShift?: [number, number];\n /** How many radii the fixed point never settled at (`TurnCandidateMiss`'s\n * `settle`): skipped like any refused radius, and the cause named when\n * the sweep is left empty. */\n unsettled: number;\n}\n\n/** Log-spaced sweep of the radius: the shape of the head is not known to any\n * finer resolution than the cues themselves, so the search starts by looking\n * at the whole plausible range. */\nfunction sweepTurnRadii(ctx: TurnSolveContext): TurnSweep {\n const minRadius = ctx.faceHalfWidth * HEAD_CYLINDER_RADIUS_FACTOR;\n const maxRadius = ctx.faceHalfWidth * TURN_SWEEP_MAX_FACTOR;\n const candidates: SweptCandidate[] = [];\n let heldRatioLower = Infinity;\n let heldRatioUpper = -Infinity;\n let offeredShift: [number, number] | undefined;\n let unsettled = 0;\n for (let i = 0; i < TURN_SWEEP_SAMPLES; i++) {\n const radius =\n minRadius * Math.pow(maxRadius / minRadius, i / (TURN_SWEEP_SAMPLES - 1));\n const result = evaluateTurnCandidate(ctx, radius);\n if (!(\"blocked\" in result)) {\n candidates.push({ ...result, sweepIndex: i });\n } else if (result.blocked === \"silhouetteRatio\") {\n heldRatioLower = Math.min(heldRatioLower, result.lower);\n heldRatioUpper = Math.max(heldRatioUpper, result.upper);\n } else if (result.blocked === \"settle\") {\n unsettled++;\n } else {\n offeredShift = offeredShift\n ? [\n Math.min(offeredShift[0], result.offeredShift[0]),\n Math.max(offeredShift[1], result.offeredShift[1]),\n ]\n : result.offeredShift;\n }\n }\n return {\n candidates,\n heldRatioLower,\n heldRatioUpper,\n offeredShift,\n unsettled,\n };\n}\n\n/** One unbroken run of the sweep's feasible radii, and the far/near ratios it\n * covers: the ratio is continuous in the radius, so a run spans every ratio\n * between its own extremes, and a target BETWEEN two runs is one no radius in\n * the sweep produces. */\ninterface TurnRatioCluster<\n C extends { sweepIndex: number; ratio: number } = SweptCandidate,\n> {\n candidates: C[];\n /** Ascending. */\n ratios: [number, number];\n}\n\n/** Whether a run holds a far/near `ratio`: inside its own range, to the\n * solver's own slack read in the ratio's units (see solveTurnModel's miss\n * check). */\nfunction runHolds(run: { ratios: [number, number] }, ratio: number): boolean {\n return (\n ratio >= run.ratios[0] - TURN_DEPTH_EPS &&\n ratio <= run.ratios[1] + TURN_DEPTH_EPS\n );\n}\n\n/** The sweep's feasible radii, split where it skipped one. A caller-measured\n * silhouette can refuse a radius in the middle of the range — the rendered\n * ratios a radius can be fitted to move with it — and a far/near ratio that\n * falls in the gap between two runs is attainable on neither, so the two must\n * not be bracketed across. */\nfunction ratioClusters<C extends { sweepIndex: number; ratio: number }>(\n candidates: C[],\n): TurnRatioCluster<C>[] {\n const clusters: TurnRatioCluster<C>[] = [];\n let run: C[] = [];\n const close = () => {\n if (run.length === 0) return;\n const ratios = run.map((c) => c.ratio);\n clusters.push({\n candidates: run,\n ratios: [Math.min(...ratios), Math.max(...ratios)],\n });\n run = [];\n };\n for (const candidate of candidates) {\n const previous = run[run.length - 1];\n if (\n previous !== undefined &&\n candidate.sweepIndex !== previous.sweepIndex + 1\n ) {\n close();\n }\n run.push(candidate);\n }\n close();\n return clusters;\n}\n\n/**\n * The radius whose far/near ratio comes nearest `target`, inside ONE cluster\n * of sweep-adjacent radii.\n *\n * The ratio is not monotone in the radius — a flatter cylinder foreshortens\n * less but needs a deeper slide to move the eyes as far, and the grid's cells\n * are straight lines the landmarks cross at different radii — so the cluster's\n * samples are scanned from the LARGEST radius down for the first adjacent pair\n * that brackets the target: where two radii both fit, the flatter head is the\n * one that keeps more of the face on the analytic part of the cylinder. The\n * bisection inside that pair is geometric, matching the log-spaced sweep.\n *\n * A pair that is adjacent in the array is adjacent in the SWEEP here, the\n * cluster being one unbroken run of it (see `ratioClusters`) — that is what\n * keeps the bisection out of a gap the sweep already found. Finer gaps inside\n * a pair it cannot see: the bisection stops when it lands on a refused radius,\n * and `solveTurnModel` checks what this actually reached against the target\n * rather than trusting it.\n *\n * `accept` is which candidates the caller will ship (`bisectTurnRadius`):\n * strand feasibility when the cluster is `strandPreferredCandidates`' pool,\n * every candidate otherwise.\n */\nfunction fitTurnRadius(\n ctx: TurnSolveContext,\n candidates: SweptCandidate[],\n target: number,\n accept: (candidate: TurnCandidate) => boolean,\n): TurnCandidate {\n // The cluster's whole span, narrowed to the bracketing pair — there is one\n // whenever the target sits inside its own ratio range.\n let lo: TurnCandidate = candidates[0];\n let hi: TurnCandidate = candidates[candidates.length - 1];\n for (let i = candidates.length - 2; i >= 0; i--) {\n if (\n (candidates[i].ratio - target) * (candidates[i + 1].ratio - target) <=\n 0\n ) {\n lo = candidates[i];\n hi = candidates[i + 1];\n break;\n }\n }\n return bisectTurnRadius(\n lo,\n hi,\n target,\n (radius) => {\n const mid = evaluateTurnCandidate(ctx, radius);\n return \"blocked\" in mid ? undefined : mid;\n },\n accept,\n );\n}\n\n/**\n * The geometric bisection inside a bracketing pair of radii: each mid is the\n * two ends' geometric mean, matching the log-spaced sweep, and replaces the\n * end on its own side of `target`'s ratio.\n *\n * The ratio steers every step, whether or not the caller would ship the mid\n * (`accept`): a mid it rejects still has a far/near ratio, so it still says\n * which half holds the target, and a rejected stretch between two accepted\n * ends cannot end the search early — it converges on the radius whose ratio\n * IS the target, wherever that lies in the bracket. Only a mid that yields no\n * candidate at all (`evaluate` returns undefined — a radius the targets\n * refuse) stops it and keeps the pair it has.\n *\n * It returns the nearer of the final two ends when both are accepted — the\n * whole answer when every candidate is — and otherwise the accepted candidate\n * it evaluated whose ratio came nearest, ends included; the caller checks\n * that against the target rather than trusting it. The two ends passed in\n * have to be accepted.\n *\n * Exported at module level for its own test, not from the package: that test\n * needs a rejected stretch strictly inside an accepted bracket around an\n * accepted target radius, which no layer fixture can be built to guarantee —\n * where a strand bound holds along the radius falls out of the whole solve.\n */\nexport function bisectTurnRadius<C extends { radius: number; ratio: number }>(\n lo: C,\n hi: C,\n target: number,\n evaluate: (radius: number) => C | undefined,\n accept: (candidate: C) => boolean,\n): C {\n if (!accept(lo) || !accept(hi)) {\n throw new Error(\n \"auto-rig: bisectTurnRadius: a bracket end is not accepted\",\n );\n }\n const miss = (c: C) => Math.abs(c.ratio - target);\n let nearest = miss(lo) <= miss(hi) ? lo : hi;\n for (let i = 0; i < TURN_BISECT_STEPS; i++) {\n const mid = evaluate(Math.sqrt(lo.radius * hi.radius));\n // A gap in the feasible set inside the bracket: keep the pair we have.\n if (mid === undefined) break;\n if (accept(mid) && miss(mid) < miss(nearest)) nearest = mid;\n if ((mid.ratio - target) * (lo.ratio - target) <= 0) hi = mid;\n else lo = mid;\n }\n if (accept(lo) && accept(hi)) return miss(lo) <= miss(hi) ? lo : hi;\n return nearest;\n}\n\n/**\n * The candidates the far/near fit should try first: the strand-feasible ones\n * (`TurnCandidate.strandFeasible`) when one of their own unbroken sweep runs\n * (`ratioClusters`) holds `farEyeRatio`, and every candidate otherwise — the\n * whole array itself when all are feasible, as they are without strands. The\n * pool only chooses the bracket's two ends; `solveTurnModel` keeps what it\n * fits only when that meets the ratio, so the preference picks among radii\n * that render the same far/near ratio and never costs a target.\n *\n * Exported at module level for its own test, not from the package: the pool\n * changes which radius is fitted only when two sweep runs hold the same\n * far/near ratio, or the ratio turns back on itself inside one — within a run\n * whose ratio is monotone in the radius both pools bracket the target with\n * the same two samples — and on every layer fixture probed the ratio rose\n * monotonically with the radius, so none reaches it through the solver.\n */\nexport function strandPreferredCandidates<\n C extends { sweepIndex: number; ratio: number; strandFeasible: boolean },\n>(candidates: C[], farEyeRatio: number): C[] {\n const feasible = candidates.filter((c) => c.strandFeasible);\n if (feasible.length === candidates.length) return candidates;\n const holds = ratioClusters(feasible).some((run) =>\n runHolds(run, farEyeRatio),\n );\n return holds ? feasible : candidates;\n}\n\n/**\n * The nose's and the mouth's depth on one candidate's hold, on the same terms\n * as the eyes': a measured target is aimed at exactly, a derived one takes its\n * family's share of the eyes' ACHIEVED shift — not the shift they were asked\n * for: the shares are proportions BETWEEN the three features, so against a\n * clamped eye pair the un-scaled ones would send the nose across the far eye.\n * Both share eyeShift's own head-relative contract (a fraction of `hh`, sign\n * ignored), so each target gets the SAME silhouette-centre correction the\n * eye's own solve uses (see evaluateTurnCandidate) — measured against the\n * pose's own silhouette centre, not the raw landmark slide against the fixed\n * face centre.\n *\n * Solved on every candidate, not once the radius is chosen: a `headEdges`\n * owner in either family lands through its depth, so the silhouette a\n * candidate is fitted on needs both. Whether a bound that cut a solution short\n * is a clamp or a refusal is `solveTurnModel`'s call.\n */\nfunction solveFeatureDepths(\n ctx: TurnSolveContext,\n /** The eyes' achieved shift on this hold, in the cue's own units. */\n eyeShift: number,\n unit: number,\n mapAt: (y: number) => TurnColumnMap,\n silhouetteCenterShift: number,\n): { nose: TurnDepthSolution; mouth: TurnDepthSolution } {\n const solve = (\n measured: number | undefined,\n marks: TurnLandmark[],\n share: number,\n ) =>\n solveTurnDepthSigned(\n silhouetteCenterShift - (measured ?? Math.abs(share * eyeShift)) * ctx.hh,\n marks,\n unit,\n mapAt,\n ctx.plateEdgeX,\n );\n return {\n nose: solve(ctx.noseShift, ctx.landmarks.nose, NOSE_SHIFT_SHARE),\n mouth: solve(ctx.mouthShift, ctx.landmarks.mouth, MOUTH_SHIFT_SHARE),\n };\n}\n\n/**\n * Solve the head turn from its measured cues: one radius and three depths.\n *\n * The radius and the eye depth are solved TOGETHER, because the two cues do not\n * separate: how far the eye pair slides depends on the radius (through the unit\n * and the map), and how much the far eye foreshortens depends on where the\n * slide put it. For a candidate radius the eye depth is whatever hits\n * `eyeShift` on that radius' own map — or, for an eyeShift the art's room cuts\n * short (and a DEFAULTED one below the floor), as much of it as that radius\n * allows — and the far/near width ratio that falls out of it is the residual\n * `fitTurnRadius` drives to `farEyeRatio`.\n *\n * Everything is measured on the SAME surface the rig is baked from — the\n * piecewise-linear map `turnColumnMap` builds from the lattice's own columns\n * (`TurnSurface.mapAt`), at the −30° stop, read through the very grids and\n * meshes the parts render with (`carriers`) — so a solved target is a promise\n * about the shipped keyforms, not about an idealised cylinder the engine never\n * evaluates. The solved surface is returned for the bakes to sample.\n *\n * A target that does not fit is clamped when it was a default and refused when\n * the caller measured it — except an eye shift the art's room cuts short,\n * which is clamped whoever passed it; see TurnTargets. Clamping the EYE SHIFT\n * keeps the radius search intact on purpose: the shift is bounded by the art\n * (the far eye has to stay on the plate, and with `strandEdges` out from under\n * the bangs' side strands), so trading the foreshortening cue away to buy the\n * last few pixels of slide would flatten the cylinder — on the reference\n * character, to the sweep's ceiling — and a head that does not foreshorten does\n * not read as turning at all.\n *\n * With `strandEdges`, the far/near ratio is fitted among the strand-feasible\n * radii first — the ones where some eye depth keeps every far iris no deeper\n * under its run than it is painted — when one of their unbroken runs holds the\n * ratio (`strandPreferredCandidates`). That fit is kept only when it meets the\n * ratio; otherwise the fit on every radius stands, where an infeasible radius\n * ships with the eyes' depth at 0 and `strandOverlap` saying how much of each\n * far iris the bangs still cover. So the preference never costs a target and\n * never refuses a rig: it only picks among radii that render the same ratio.\n */\nexport function solveTurnModel(\n targets: ResolvedTurnTargets,\n landmarks: TurnLandmarkSet,\n lattice: IkiWarpGrid,\n faceCenterX: number,\n faceHalfWidth: number,\n /** The nod axis. */\n faceCenterY: number,\n /** The nod cylinder's radius — the head's vertical reach with the no-fold\n * margin, sized once by `turnSetup` for the solve and the generator alike\n * (the virtual lattice has no height to read it from). */\n nodRadius: number,\n /** Every face-family part's carrier by role — the group grid it binds to\n * and the part as it renders on it — the plate's under `face`: what the\n * landmarks, the silhouette candidates, the bangs' join and the plate\n * guards read the RENDERED parts through. */\n carriers: ReadonlyMap<string, TurnCarrier>,\n /** The face's own row profile (`faceRowProfile`), when its layer measured\n * one: every candidate's surface bends each row on its own radius,\n * normalised at the eye row the cues are fitted on, and the plate's\n * painted edge is read at each row's own half-width. Absent, the plate is\n * painted out to its crop and turns on one radius. */\n profile?: FaceRowProfile,\n /** hair_front's own transform x/y and crop width/height, when the layer set\n * has one — see `TurnSolveContext.hairFrontSilhouette`. Absent skips the\n * turn-lead correction and falls the silhouette-centre correction back to\n * the ideal hold destinations, which is also correct for a layer set with\n * no hair_front: there is then no bangs edge for either to read. */\n hairFront?: { x: number; centerY: number; cropW: number; cropH: number },\n /** Every role with an opaque pixel in the eye-row band, per side, each with\n * its own rest x there — see `TurnSolveContext.headEdges`. Absent (the\n * usual case for a caller with no measured head, or one built by hand)\n * falls the silhouette-centre correction back to hair_front on both sides. */\n headEdges?: {\n left: { role: string; x: number }[];\n right: { role: string; x: number }[];\n },\n /** Each side's iris and the bangs' run it would slide under, as measured\n * pixel edges on the iris row (`IrisStrand`) — what the strand bound caps\n * the eye family's depth by. See `TurnSolveContext.strands`. */\n strandEdges?: { left?: IrisStrand; right?: IrisStrand },\n): TurnModelSolution {\n // A measured head narrower than the face it is drawn around is not a head the\n // rest of this can make sense of: the hold's whole zone lives outside the\n // plate's edge, so the plate would be sticking out of the silhouette at rest.\n if (\n targets.headHalfWidth !== undefined &&\n targets.headHalfWidth <= faceHalfWidth\n ) {\n throw new TurnTargetError(\n `auto-rig: turnTargets.headHalfWidth (${targets.headHalfWidth}) must be wider than the face plate's own half-width (${faceHalfWidth})`,\n );\n }\n\n const eyeRowY = eyeRowOf(landmarks, faceCenterY);\n\n // Each measured side on its own iris's carrier, the irises paired to sides\n // by where they render rather than by role name. The generator validated\n // the option against the layers first, so a side with no iris to land or a\n // run with no bangs to land it on is a broken invariant here.\n let strands: TurnSolveContext[\"strands\"];\n const strandSides = ([\"left\", \"right\"] as const).filter(\n (key) => strandEdges?.[key] !== undefined,\n );\n if (strandSides.length > 0) {\n if (hairFront === undefined) {\n throw new Error(\n \"auto-rig: solveTurnModel: strandEdges was given, but there is no hair_front to land its runs on\",\n );\n }\n const irises = [\"iris_L\", \"iris_R\"]\n .map((role) => carriers.get(role))\n .filter((c): c is TurnCarrier => c !== undefined)\n .sort((a, b) => a.part.x - b.part.x);\n strands = strandSides.map((key) => {\n const carrier =\n irises.length === 2 ? irises[key === \"left\" ? 0 : 1] : undefined;\n if (carrier === undefined) {\n throw new Error(\n `auto-rig: solveTurnModel: strandEdges.${key} has no iris carrier to land`,\n );\n }\n return {\n ...strandEdges![key]!,\n side: key === \"left\" ? -1 : 1,\n carrier,\n };\n });\n }\n\n const ctx: TurnSolveContext = {\n landmarks,\n lattice,\n faceCenterX,\n faceCenterY,\n nodRadius,\n eyeRowY,\n faceHalfWidth,\n // The slide's hard stop is the CROP's edge, profile or not: the far eye\n // may sit on the plate's transparent margin, as it does on the hero.\n plateEdgeX: faceCenterX - faceHalfWidth,\n carriers,\n profile,\n ...plateGuardsOf(carriers, faceHalfWidth, profile, hairFront),\n // What this plate asks the turn for; each candidate caps it to its own\n // shell — see TurnSolveContext.travel.\n travel: headTurnTravel(faceHalfWidth),\n headHalfWidth: targets.headHalfWidth,\n // The pixels the shift fractions are fractions of. Without a measured head\n // the face plate stands in for it — see TurnTargets.headHalfWidth.\n hh: targets.headHalfWidth ?? faceHalfWidth,\n silhouetteRatio: targets.silhouetteRatio,\n // The sign is ignored (see TurnTargets.eyeShift) — taken here, once, so\n // every downstream use already has the magnitude.\n eyeShift: Math.abs(targets.eyeShift),\n clampEyeShift: targets.defaulted.has(\"eyeShift\"),\n noseShift: targets.defaulted.has(\"noseShift\")\n ? undefined\n : Math.abs(targets.noseShift),\n mouthShift: targets.defaulted.has(\"mouthShift\")\n ? undefined\n : Math.abs(targets.mouthShift),\n clampSilhouetteRatio: targets.defaulted.has(\"silhouetteRatio\"),\n hairFrontSilhouette: hairFront,\n // A companion to a MEASURED headHalfWidth only — see its own doc.\n headEdges: targets.headHalfWidth === undefined ? undefined : headEdges,\n strands,\n };\n const clampInto = (value: number, [lo, hi]: [number, number]) =>\n Math.min(Math.max(value, lo), hi);\n\n const pass = sweepTurnRadii(ctx);\n if (pass.candidates.length === 0) {\n // A radius the fixed point never settled at offered nothing in any\n // target's terms (see evaluateTurnCandidate), so a sweep one of those\n // leaves empty is refused as such, naming the cause — the ranges below\n // are read off the radii the TARGETS refused and would say nothing of\n // the ones nothing settled at.\n if (pass.unsettled > 0) {\n const refused = TURN_SWEEP_SAMPLES - pass.unsettled;\n throw new TurnTargetError(\n `auto-rig: headEdges: no sampled radius carries the turn — the silhouette and the depths its headEdges owners ride on did not settle within ${TURN_SETTLE_PASSES} passes at ${pass.unsettled} of the ${TURN_SWEEP_SAMPLES} radii${refused > 0 ? `, and the targets refused the other ${refused}` : \"\"}`,\n );\n }\n // Either gate can empty the sweep, and only a MEASURED target can. The\n // shift, only by asking for LESS than the pair's own drift at depth 0\n // (the floor): a defaulted one is clamped per radius rather than gated,\n // and one the art's room cuts short at the cap is clamped whoever passed\n // it. The silhouette, because a defaulted ratio of 1 leaves the hold edge\n // at its own rest distance — without a measured head that distance is the\n // plate's own scanned reach plus a pixel (`plateReach`), which clears every\n // stop by construction, and with one it is the head the slide is capped to\n // stay inside (`shellTravelCap`). So both values below are the caller's\n // own, never a clamped stand-in.\n return pass.offeredShift\n ? {\n unreachable: true,\n field: \"eyeShift\",\n value: targets.eyeShift,\n attainable: pass.offeredShift,\n }\n : {\n unreachable: true,\n field: \"silhouetteRatio\",\n value: targets.silhouetteRatio,\n // Two different bounds, not one number twice, and computed the\n // same way regardless of which ratio was actually rejected: both\n // are ratios a RENDER could have shown (the measure the caller's\n // own number came from), the narrowest and the widest any radius\n // could be fitted to, with every ratio between them one it can be\n // fitted to exactly; see evaluateTurnCandidate, which clips each\n // radius' own range to the domain `resolveTurnTargets` accepts\n // before it gets here. Absent when that left every radius with\n // nothing: no ratio a caller is allowed to ask for renders at all.\n attainable:\n pass.heldRatioLower === Infinity ||\n pass.heldRatioUpper === -Infinity\n ? undefined\n : [pass.heldRatioLower, pass.heldRatioUpper],\n };\n }\n\n /** The whole post-sweep fit on one pool of candidates — its runs, the\n * radius fitted inside the one that holds the far/near ratio, and every\n * verdict on what that radius reached — with `accept` the candidates the\n * bisection may ship (`fitTurnRadius`). */\n const fitFrom = (\n pool: SweptCandidate[],\n accept: (candidate: TurnCandidate) => boolean,\n ): TurnModelSolution => {\n const clamped: (keyof TurnTargets)[] = [];\n // The radii that survived, in unbroken runs of the sweep: a far/near\n // ratio between two runs is one no radius produces, so the fit is\n // confined to the run that covers the target and a target no run covers\n // is out of reach rather than bisected across the gap. Defaults leave\n // every radius standing and so leave exactly one run — the gaps are what\n // a CALLER-measured cue opens (see evaluateTurnCandidate): a silhouette\n // no hold at that radius renders, or an eye shift below the pair's own\n // drift there, neither of which is monotone in the radius. (A caller's\n // shift the art's room cuts short opens none: it is clamped, so it\n // leaves the far/near ratio fitted across the same radii a default\n // does.)\n const clusters = ratioClusters(pool);\n const distanceTo = (c: TurnRatioCluster) =>\n Math.max(\n c.ratios[0] - targets.farEyeRatio,\n targets.farEyeRatio - c.ratios[1],\n 0,\n );\n let farEyeRatio = targets.farEyeRatio;\n let cluster = clusters.find((c) => runHolds(c, farEyeRatio));\n if (cluster === undefined) {\n // What a caller could have asked for instead is one run's own range,\n // not the span across the gaps: every ratio inside it is one this\n // sweep reaches. The nearest run is the one to name.\n cluster = clusters.reduce((a, b) =>\n distanceTo(b) < distanceTo(a) ? b : a,\n );\n if (!targets.defaulted.has(\"farEyeRatio\")) {\n return {\n unreachable: true,\n field: \"farEyeRatio\",\n value: targets.farEyeRatio,\n attainable: cluster.ratios,\n };\n }\n farEyeRatio = clampInto(farEyeRatio, cluster.ratios);\n clamped.push(\"farEyeRatio\");\n }\n\n const best = fitTurnRadius(ctx, cluster.candidates, farEyeRatio, accept);\n // What the fit actually reached, against what it was asked for: inside a\n // run the ratio is continuous, but the bisection can still stop on a\n // radius the silhouette gate refuses — a gap finer than the sweep\n // resolves — and land on a bracket end instead, or converge on a radius\n // `accept` turns down and fall back to the nearest one it accepts. A\n // miss is refused or reported, never passed off as met. The tolerance is\n // solveTurnDepthSigned's own slack read in this cue's own units, which\n // are already dimensionless: the far/near ratio is a width over a width,\n // so there is no px scale to divide it by.\n if (Math.abs(best.ratio - farEyeRatio) > TURN_DEPTH_EPS) {\n if (!targets.defaulted.has(\"farEyeRatio\")) {\n return {\n unreachable: true,\n field: \"farEyeRatio\",\n value: targets.farEyeRatio,\n attainable: cluster.ratios,\n };\n }\n if (!clamped.includes(\"farEyeRatio\")) clamped.push(\"farEyeRatio\");\n }\n // Cut short at either end: a default at the floor or the cap, or a\n // caller's at the cap — the plate's edge or the strand bound, depth 0\n // where the bound cannot hold (a caller's below the floor never gets\n // here; its radii were refused).\n if (!best.eye.reached) clamped.push(\"eyeShift\");\n // Only a DEFAULTED silhouette can be here cut down: the fit lands a\n // caller-measured one or refuses the radius outright\n // (evaluateTurnCandidate), so a ratio that reached this point was either\n // rendered as asked — nothing to report — or is this generator's own\n // default, cut to the nearest ratio a render of this layer set can show.\n if (best.silhouetteClamped) clamped.push(\"silhouetteRatio\");\n // Undo the same silhouette-centre correction the depth was solved with\n // (see evaluateTurnCandidate), so this reports the cue\n // measure_turn_reference would read off a render — eye position against\n // that pose's OWN silhouette centre — not the raw landmark slide against\n // the fixed face centre.\n const eyeShift = (-best.eye.achieved + best.silhouetteCenterShift) / ctx.hh;\n\n // The nose's and the mouth's depths were solved on this candidate along\n // with the eyes' (solveFeatureDepths); what is left is their verdict — a\n // measured target the bounds cut short, at either end, is refused, a\n // derived one is clamped and says so.\n for (const [field, solution] of [\n [\"noseShift\", best.nose],\n [\"mouthShift\", best.mouth],\n ] as const) {\n if (solution.reached) continue;\n if (!targets.defaulted.has(field)) {\n return {\n unreachable: true,\n field,\n value: targets[field],\n // noseShift/mouthShift are MAGNITUDES too (see\n // solveFeatureDepths), so a negative lower bound here would\n // advertise a value the field's own contract already rules out; 0\n // is the true floor.\n attainable: [\n Math.max(\n 0,\n (-solution.attainable[1] + best.silhouetteCenterShift) / ctx.hh,\n ),\n (-solution.attainable[0] + best.silhouetteCenterShift) / ctx.hh,\n ],\n };\n }\n clamped.push(field);\n }\n\n return {\n unreachable: false,\n radius: best.radius,\n holdBase: best.holdBase,\n holdEdgeAt: best.holdEdgeAt,\n travel: best.travel,\n surface: best.surface,\n depths: {\n eye: best.eye.depth,\n nose: best.nose.depth,\n mouth: best.mouth.depth,\n },\n achieved: {\n eyeShift,\n farEyeRatio: best.ratio,\n silhouetteRatio: best.renderedSilhouetteRatio,\n },\n clamped,\n ...(best.strandOverlap === undefined\n ? {}\n : { strandOverlap: best.strandOverlap }),\n };\n };\n\n // The strand-feasible radii first, when one of their runs holds the\n // far/near ratio. That pool only chooses the bracket; what it fits is kept\n // only when it meets the ratio — neither refused nor clamping farEyeRatio,\n // i.e. the radius that renders the target is itself feasible — and\n // otherwise the fit on every candidate stands, which is the fit without\n // the preference. So the preference never costs a target and never\n // refuses: it only chooses among radii that render the same far/near\n // ratio. Without strands every candidate is feasible and this is that fit.\n const preferred = strandPreferredCandidates(\n pass.candidates,\n targets.farEyeRatio,\n );\n if (preferred !== pass.candidates) {\n const fitted = fitFrom(preferred, (c) => c.strandFeasible);\n if (!fitted.unreachable && !fitted.clamped.includes(\"farEyeRatio\")) {\n return fitted;\n }\n }\n return fitFrom(pass.candidates, () => true);\n}\n\n// ── bindingsForRole ───────────────────────────────────────────────────────────\n\n// Role prefixes that belong to the eye stack (blink + optional gaze bindings).\n// Hoisted to module scope so it is not reallocated on every bindingsForRole call.\nconst EYE_STACK_PREFIXES = [\"eye_\", \"iris_\", \"pupil_\", \"highlight_\"] as const;\n\n/** Signed depth of the bangs from the head cylinder's axis, as a fraction of\n * the cylinder radius; positive is toward the viewer. Tuned by eye against\n * the rendered turn, not derived. The back hair takes no share of it: it\n * holds the head's outline while the face slides inside it.\n *\n * The bangs lead that sliding face by a little — this is the FRINGE TIPS'\n * lead specifically, not the whole sheet's: the crown is root-pinned by the\n * warp that applies it, not a binding (see bindingsForRole's doc for why).\n * 0.16 was right while the shift was rigid and the whole head travelled\n * with it, the lead the only depth cue; with the head no longer travelling,\n * the lead is the bangs' only motion against a held shell, and that much of\n * it reads as them running ahead, so it came down. At 0.06 the layering all\n * but vanishes. 0.1 is settled with the lead root-pinned: the fringe tips get\n * it, the crown gets none. */\nconst HAIR_FRONT_DEPTH = 0.1;\n\n/** How far the mouth tilts at full turn, degrees, its near end down: the\n * mouth family's `TurnAnchor.tiltDeg`. The reference 3/4 portrait's mouth\n * line tilts 5.2° near end down, where the rig's, untilted, came out at\n * −1.1°. A style prior, not fitted: `measure_turn_reference` reads no\n * mouth. */\nconst MOUTH_TURN_TILT_DEG = 5;\n\n/** How far the nose tilts at full turn, degrees, its tip toward the far side:\n * the nose's `TurnAnchor.tiltDeg`. A ladder of tilts rendered on the hero\n * read: 3° only cancels the lean-back the chin swing gives the nose, 10°\n * reads as a nose leaning over, and the reverse sign is plainly wrong, while\n * 6° echoes the \"<\" the reference 3/4 portrait's nose draws. A style prior,\n * not fitted: `measure_turn_reference` reads no nose. */\nconst NOSE_TURN_TILT_DEG = 6;\n\n/** Rigid vertical travel of the head at full nod (px at AngleY = ±30). */\nconst NOD_TRAVEL = 30;\n/** Geometric pitch per degree of ParamAngleY: a full ±30 nod bends the face\n * cylinder by ±15°. The nod cylinder — sized to the turn family's union,\n * bangs included (`turnSetup`) — reaches up to the hair crown, which sits\n * near 45° on it; a full 30° pitch there drops the\n * bangs' crown ~140px while the rigid back hair stays put, and the back\n * hair's crown pops out above it as a second silhouette. At half pitch the\n * drop stays under the back hair and the nod still foreshortens visibly. */\nconst NOD_BEND = 0.5;\n/** Vertical share of the nod for hair_back, as a fraction of\n * `headNodParallaxUnit`. The back hair gets no vertical bend of its own, so\n * it only needs to follow the bent crown down to stay tucked beneath it.\n * Tuned against renders as −0.07 of a turn-sized unit (sin 30°), before the\n * nod had its own; the same pixels in the nod unit (sin 15°). */\nconst HAIR_BACK_NOD_DEPTH = -0.135;\n\n/**\n * The nod's counterpart of `headTurnParallaxUnit`: the bulk vertical slide the\n * 2D bakes pin out of every group grid at full nod (`TurnSurface.nodBendAt`).\n * The nod bends at NOD_BEND of the angle, so this is\n * nodRadius · sin(30° · NOD_BEND), not nodRadius · sin(30°), with nodRadius\n * the head's half-height about the nod axis (`gridHalfHeight`, the turn\n * family's margined union — see `turnSetup`) with the no-fold margin.\n */\nexport function headNodParallaxUnit(gridHalfHeight: number): number {\n return (\n gridHalfHeight *\n HEAD_CYLINDER_RADIUS_FACTOR *\n Math.sin(HEAD_TURN_MAX_DEG * NOD_BEND * (Math.PI / 180))\n );\n}\n\n/** Depth of the features in front of the head cylinder's NOD axis, as a\n * fraction of its radius — the share of the bulk vertical slide the 2D bake\n * pins out of the face warp (`headNodParallaxUnit`) that each part gets back.\n *\n * The face contour is a cylinder rotating about its own axis: its silhouette\n * stays put and only foreshortens, which is what the pinned bake gives it.\n * The features painted on that surface do not stay put — they slide across it\n * toward the far side, and a nose that stands off the surface slides further.\n * Without their share they sat still while the outline squeezed around them,\n * and the head read as a flat sheet bending rather than pitching.\n *\n * The nose is the anchor of the whole read, so the parallax exists only when\n * it is a layer of its own (the composer cuts it out of the face). With the\n * nose painted on the face plate, any lead on the eyes and mouth inverts the\n * 3/4 view — the nose lands nearer the NEAR eye and the mouth hangs off its\n * far side — and a blind review ranked that below no parallax at all.\n *\n * Keyed by role family (`eye_L` → `eye`). The whole eye stack shares one\n * depth because iris/pupil/highlight clip to the white, and the lashes fold\n * onto it. The magnitudes are small on purpose, and judged by eye across a\n * ladder of rigs: the nose at twice the surface's share and the mouth on the\n * surface with the eyes is where the nod stopped looking like a plate without\n * looking like a slide. The TURN's depths are not tuned here at all — they are\n * solved per layer set from a measured reference (`solveTurnModel`); no nodded\n * reference has been measured, so this axis is still by eye. */\nconst FEATURE_NOD_DEPTH: Readonly<Record<string, number>> = {\n eye: 0.04,\n iris: 0.04,\n pupil: 0.04,\n highlight: 0.04,\n lash: 0.04,\n brow: 0.04,\n blush: 0.04,\n mouth: 0.04,\n mouth_open: 0.04,\n nose: 0.08,\n};\n\n/** The bangs' share of the nod: the brows'. They hang from the hairline, which\n * sits on the same surface as the brows, so they slide with them — held still\n * while the brows slid, the fringe closed onto the brows looking up and left\n * a bare forehead looking down. (Before the brows slid, a lead here lifted\n * the bangs clear off them, which is why they had none.) Riding the rigid\n * head rather than a grid, the bangs take it as a per-vertex warp that bakes\n * the slide AND the surface's bend where the slide puts each vertex\n * (`bakeNodWarp`), the composition a grid child gets from its binding and\n * the grid. The turn lead stays a root-pinned warp: on the turn the crown\n * sits on the axis and must not slide. */\nconst HAIR_FRONT_NOD_DEPTH = FEATURE_NOD_DEPTH.brow;\n\n/** `eye_L` → `eye`; an unsided role is its own family. */\nfunction roleFamily(role: string): string {\n return role.replace(/_[LR]$/, \"\");\n}\n\n/** Which solved turn depth a feature family slides on: the nose has its own,\n * the two mouth drawings share one, and everything else painted on the face —\n * the eye stack, the lashes, the brows, the blush — rides with the eyes. */\nfunction turnFamily(role: string): keyof TurnDepths {\n const family = roleFamily(role);\n if (family === \"nose\") return \"nose\";\n if (family === \"mouth\" || family === \"mouth_open\") return \"mouth\";\n return \"eye\";\n}\n\n/**\n * The nod's depth-parallax translate binding of a facial feature, or [] for\n * any other role, whenever the unit is absent (a unit-less call is a rig\n * without a head to nod on), and whenever the rig has no nose layer to lead\n * (see FEATURE_NOD_DEPTH). Symmetric about zero, so the rest pose — the one\n * every proportion is judged on — is untouched.\n *\n * The TURN has no binding here: its depth is SOLVED per layer set from the\n * measured cues and baked into the feature's own group grid as keyform\n * geometry (`bakeTurnGroupWarp2D`'s `shiftAt`), so the part binds at its rest\n * position and the grid carries it — the landing a translate-then-bind gave,\n * now read by the solve through that very grid (`TurnLandmark.carrier`). The\n * nod's depth is the tuned table and stays a translate: it binds a nodded\n * vertex at `y + t` on its group grid exactly as it did on the shared one.\n */\nfunction featureParallaxBindings(\n role: string,\n options: {\n parallaxUnitY?: number;\n hasNose?: boolean;\n },\n): IkiBinding[] {\n if (!options.hasNose) return [];\n // The nod's table is also the role gate: a family with no depth in it is not\n // a feature painted on the face, and does not slide.\n const nodDepth = FEATURE_NOD_DEPTH[roleFamily(role)];\n if (nodDepth === undefined) return [];\n const shiftY = nodDepth * (options.parallaxUnitY ?? 0);\n if (shiftY === 0) return [];\n return [\n {\n parameter: StandardParameter.AngleY,\n channel: \"translateY\",\n from: -shiftY,\n to: shiftY,\n },\n ];\n}\n\n/**\n * Derive the IkiBinding[] for a part from its role spec and crop dimensions.\n *\n * - face → no bindings: the contour is the cylinder itself, pinned by the bake\n * - every feature on that contour — eye stack, lashes, brows, both mouths,\n * nose, blush — carries the AngleY translateY nod parallax from\n * FEATURE_NOD_DEPTH (needs `parallaxUnitY` and `hasNose`) on top of\n * whatever its role adds below; its TURN parallax is no binding but its\n * group grid's own keyform geometry (see featureParallaxBindings)\n * - hair_front: no bindings. Every motion of its own is a per-vertex warp\n * attached in generateIkiFromLayerSet: the sway and the AngleX turn lead\n * are root-pinned (a rigid translate/rotate would carry the whole sheet,\n * crown included, with the fringe tips, instead of leading from them),\n * the silhouette hold sits on the rendered plate, and the nod follow at\n * the brows' depth (HAIR_FRONT_NOD_DEPTH, same `hasNose` gate as the\n * brows) is baked with the head's own bend (`bakeNodWarp`)\n * - hair_back: nothing on the turn — it rides a head that no longer travels,\n * so it holds the head's outline while the face slides inside it — and an\n * AngleY translateY that tucks its crown under the bent front hair (needs\n * `parallaxUnitY`)\n * - brow_L/R: BrowLeftY/RightY translateY (raise/lower) + BrowLeftAngle/RightAngle rotate\n * (each brow rotates its own, CCW-positive)\n * - eye-stack:\n * iris_/pupil_/highlight_ → gaze translateX + translateY (no blink binding)\n * eye_ (white) → none here; its blink is a fold warp attached in assembly,\n * and iris/pupil/highlight clip to it so the closing white CUTS them away\n * - mouth: MouthForm scaleX (-0.2 to 0.4), plus MouthOpen as scaleY (0 to 3)\n * without a mouth_open layer, or as an opacity fade-out with one\n * - mouth_open: MouthOpen opacity fade-in + the same MouthForm scaleX\n *\n * Returns [] when the role has no bindings (callers skip the bindings key when\n * the array is empty).\n */\nexport function bindingsForRole(\n spec: RoleSpec,\n role: string,\n cropW: number,\n cropH: number,\n options: {\n hasMouthOpen?: boolean;\n parallaxUnitY?: number;\n /** Whether the layer set has a `nose` role — the feature parallax's gate. */\n hasNose?: boolean;\n } = {},\n): IkiBinding[] {\n return [\n ...roleOwnBindings(spec, role, cropW, cropH, options),\n ...featureParallaxBindings(role, options),\n ];\n}\n\n/** The bindings a role has for its own expression or motion — everything in\n * `bindingsForRole`'s list except the shared feature parallax. */\nfunction roleOwnBindings(\n spec: RoleSpec,\n role: string,\n cropW: number,\n cropH: number,\n options: {\n hasMouthOpen?: boolean;\n parallaxUnitY?: number;\n },\n): IkiBinding[] {\n const isEyeStack = EYE_STACK_PREFIXES.some((p) => role.startsWith(p));\n\n if (isEyeStack && spec.eyeSide !== undefined) {\n // Only iris/pupil/highlight move with gaze; the white (eye_) gets nothing.\n const isGazeRole =\n role.startsWith(\"iris_\") ||\n role.startsWith(\"pupil_\") ||\n role.startsWith(\"highlight_\");\n if (!isGazeRole) return [];\n\n // Gaze range: proportional to crop size, capped to avoid over-travel.\n const gx = Math.min(cropW * 0.18, 22);\n const gy = Math.min(cropH * 0.18, 16);\n return [\n {\n parameter: StandardParameter.EyeballX,\n channel: \"translateX\",\n from: -gx,\n to: gx,\n },\n {\n parameter: StandardParameter.EyeballY,\n channel: \"translateY\",\n from: -gy,\n to: gy,\n },\n ];\n }\n\n // Mouth form applies to whichever mouth drawing is showing.\n const mouthForm = {\n // Mouth form: scaleX from -0.2 (pursed, param=-1) to 0.4 (wide, param=1).\n parameter: StandardParameter.MouthForm,\n channel: \"scaleX\" as const,\n from: -0.2,\n to: 0.4,\n };\n\n if (role === \"mouth\") {\n // With an open-mouth drawing present, the closed one fades out rather than\n // being stretched. Stretching a closed mouth is what produced a smear: the\n // art is a ~15px-tall line and scaleY 3 blows it up to a blurred band.\n if (options.hasMouthOpen) {\n return [\n {\n parameter: StandardParameter.MouthOpen,\n channel: \"opacity\",\n from: 1,\n to: 0,\n },\n mouthForm,\n ];\n }\n return [\n // Mouth open: scaleY from 0 (closed, param=0) to 3 (wide open, param=1).\n // Only a fallback — it distorts, but it is better than a mouth that\n // cannot open at all when the layer set has no open drawing.\n {\n parameter: StandardParameter.MouthOpen,\n channel: \"scaleY\",\n from: 0,\n to: 3,\n },\n mouthForm,\n ];\n }\n\n if (role === \"mouth_open\") {\n return [\n {\n parameter: StandardParameter.MouthOpen,\n channel: \"opacity\",\n from: 0,\n to: 1,\n },\n mouthForm,\n ];\n }\n\n if (role === \"brow_L\" || role === \"brow_R\") {\n // Raise/lower capped to avoid over-travel; tilt is a fixed ±12° range.\n const ty = Math.min(cropH * 0.8, 18);\n const deg = 12;\n if (role === \"brow_L\") {\n return [\n {\n parameter: StandardParameter.BrowLeftY,\n channel: \"translateY\",\n from: -ty,\n to: ty,\n },\n {\n parameter: StandardParameter.BrowLeftAngle,\n channel: \"rotate\",\n from: -deg,\n to: deg,\n },\n ];\n } else {\n return [\n {\n parameter: StandardParameter.BrowRightY,\n channel: \"translateY\",\n from: -ty,\n to: ty,\n },\n {\n parameter: StandardParameter.BrowRightAngle,\n channel: \"rotate\",\n from: -deg,\n to: deg,\n },\n ];\n }\n }\n\n if (role === \"hair_back\") {\n // Nothing on the TURN: it holds the head's outline still while the face\n // slides inside it. On the nod it follows the bent crown down to stay\n // tucked beneath it — see HAIR_BACK_NOD_DEPTH.\n const shiftY = HAIR_BACK_NOD_DEPTH * (options.parallaxUnitY ?? 0);\n if (shiftY === 0) return [];\n return [\n {\n parameter: StandardParameter.AngleY,\n channel: \"translateY\",\n from: -shiftY,\n to: shiftY,\n },\n ];\n }\n\n // face, blush_*, nose → nothing of their own; hair_front's every motion is\n // a warp (see the doc above)\n return [];\n}\n\n// ── bakeEyelidFoldWarp ─────────────────────────────────────────────────────\n\n/** Crease sits this fraction of the eye height BELOW the white's center. */\nconst EYELID_FOLD_CREASE = 0.15;\n/** Without separate lashes, retain a thin band of the eye's own line art. */\nconst EYELID_FOLD_K = 0.04;\n/** The lash keeps a thicker band than the white when closed, so it reads as a\n * visible dark closed-eye line and covers the cut eyeball/seam. */\nconst LASH_FOLD_K = 0.2;\n\n/**\n * Live2D-style eyelid FOLD blink for the eye-white. Two EyeOpen keyforms collapse\n * the white toward a crease line `creaseOffsetY` below its center while scaling\n * its height by `k`: as EyeOpen → 0 the white folds shut. Because iris/pupil/\n * highlight CLIP to the white, the closing clip region CUTS the (static, round)\n * iris away instead of squashing it — unlike the old scaleY-collapse blink.\n * EyeOpen=1 → rest (zero offsets = the authored open art); =0 → folded.\n *\n * Offsets are authored in the mesh's own pixel frame (+y up, centered), matching\n * `createPixelGridMesh`, so the SAME mesh must be passed that the part renders.\n */\nexport function bakeEyelidFoldWarp(\n mesh: IkiMesh,\n parameter: string,\n creaseOffsetY: number,\n k: number,\n): IkiWarp {\n const closed: number[] = [];\n const zeros: number[] = [];\n for (let i = 0; i < mesh.vertices.length; i += 2) {\n const vy = mesh.vertices[i + 1];\n // closed y = creaseOffsetY + vy*k → dy added to the rest vertex vy.\n closed.push(0, creaseOffsetY - (1 - k) * vy);\n zeros.push(0, 0);\n }\n return {\n parameter,\n keyforms: [\n { value: 0, offsets: closed },\n { value: 1, offsets: zeros },\n ],\n };\n}\n\n// ── bakeNodWarp ──────────────────────────────────────────────────────────────\n\n/**\n * The nod of a part that rides the rigid head rather than a grid, as a\n * per-vertex AngleY warp keyed on HEAD_TURN_STOPS: what a grid child gets from\n * its nod translate binding and the grid together — translated by `t`, the\n * part's own share of the nod at that stop (`nodTravel · deg / 30`), THEN bent\n * where the translate put it, `surface.nodBendAt(y + t)` — baked at each\n * vertex's own rest y, `partY + vy`. Off the grid the bend is the analytic\n * surface itself, not a grid's chord of it. dx is zero; the rest keyform is a\n * literal zero rather than the bend's ≈3e-14 residue at 0 (see\n * `bakeTurnGroupWarp2D`).\n *\n * Offsets are in the mesh's own pixel frame — pass the SAME mesh the part\n * renders. headDeformer's own rigid nod translate rides on top, as it does for\n * every child of the head. Exported at module level for the tests, not from\n * the package.\n */\nexport function bakeNodWarp(\n mesh: IkiMesh,\n partY: number,\n surface: TurnSurface,\n nodTravel: number,\n): IkiWarp {\n const keyforms = HEAD_TURN_STOPS.map((deg) => {\n const t = (nodTravel * deg) / HEAD_TURN_MAX_DEG;\n const offsets: number[] = [];\n for (let i = 0; i < mesh.vertices.length; i += 2) {\n const y = partY + mesh.vertices[i + 1];\n offsets.push(0, deg === 0 ? 0 : t + surface.nodBendAt(y + t, deg));\n }\n return { value: deg, offsets };\n });\n return { parameter: StandardParameter.AngleY, keyforms };\n}\n\n// ── bakeHairFrontSilhouetteWarp ──────────────────────────────────────────────\n\n/**\n * How far the face plate's painted edge EVER lands from the face centre over\n * the turn, either side, as the plate RENDERS — the floor under any hold edge.\n *\n * It scans both sides at EVERY stop instead of taking the rest half-width or\n * the full-turn one, because the plate slides as well as bends, and the two\n * pull against each other on the edge the slide pushes out while the bend\n * foreshortens it in — the FAR one, whichever physical side that is at this\n * turn's sign, which is why the scan takes both. The slide's push is linear in\n * the angle and the same at any radius; the bend's pull is the cylinder's own,\n * so it shrinks as the radius flattens and gains on the slide as the angle\n * grows. Which stop reaches furthest therefore moves with the radius: on the\n * assembly fixture, whose plate is 300 px half-wide, the bend leads from the\n * first stop at a radius of 1.2 half-widths (REST wins, 300 px), the slide\n * leads until the bend catches it at 4 (a MID stop, 317 px), and is never\n * caught at 8 (FULL turn, 325 px). A hold edge inside that reach would make\n * the ramp between the two run backwards.\n */\nexport function plateReach(\n plateLandingAt: (deg: number, x: number, y: number) => number,\n faceCenterX: number,\n edgeAt: (y: number) => number,\n plateGuardRows: number[],\n): number {\n let reach = 0;\n for (const deg of HEAD_TURN_STOPS) {\n reach = Math.max(\n reach,\n plateReachAt(plateLandingAt, faceCenterX, edgeAt, plateGuardRows, deg),\n );\n }\n return reach;\n}\n\n/** How far the plate's painted edge on ONE side lands from the face centre at\n * one stop: the furthest over the guard rows, each row's own painted edge\n * read where the rendered plate lands it. */\nfunction plateSideReachAt(\n plateLandingAt: (deg: number, x: number, y: number) => number,\n faceCenterX: number,\n edgeAt: (y: number) => number,\n plateGuardRows: number[],\n deg: number,\n side: -1 | 1,\n): number {\n let reach = 0;\n for (const y of plateGuardRows) {\n const dest = plateLandingAt(deg, faceCenterX + side * edgeAt(y), y);\n reach = Math.max(reach, Math.abs(dest - faceCenterX));\n }\n return reach;\n}\n\n/**\n * `plateReach` at ONE stop: how far the plate's painted edge lands from the\n * face centre, whichever side and whichever guard row lands further out.\n *\n * Every gate on the hold edge is this number — the bake's own guard, and the\n * solver's check that a radius can hold the silhouette it was asked for — read\n * at ONE list of rows (`plateGuardRowsFor`) through ONE rendered plate. They\n * have to agree: a radius the solver accepts and the bake then refuses is a\n * generator that throws from inside its own answer.\n */\nexport function plateReachAt(\n plateLandingAt: (deg: number, x: number, y: number) => number,\n faceCenterX: number,\n edgeAt: (y: number) => number,\n plateGuardRows: number[],\n deg: number,\n): number {\n return Math.max(\n plateSideReachAt(\n plateLandingAt,\n faceCenterX,\n edgeAt,\n plateGuardRows,\n deg,\n -1,\n ),\n plateSideReachAt(\n plateLandingAt,\n faceCenterX,\n edgeAt,\n plateGuardRows,\n deg,\n 1,\n ),\n );\n}\n\n/**\n * The head's OUTLINE, held through the turn by the part that draws it — the\n * bangs — as a per-vertex AngleX warp on hair_front, keyed on HEAD_TURN_STOPS.\n *\n * The face warp bends AND slides everything riding its grid, so side strands\n * riding it would squeeze in with the plate and travel off with it: the head\n * would narrow and shift instead of turning. hair_front therefore binds to no\n * grid — it rides the rigid head — and this warp sends each vertex DIRECTLY to\n * where the outline wants it, in ABSOLUTE model x (`partX + vx`) throughout.\n *\n * The destination is `hairFrontHoldTarget`'s MONOTONE three-zone function of\n * the vertex's REST distance from the face centre, so the warp can never fold\n * a hair cell: over the painted plate (within `edgeAt(y)` on the vertex's own\n * row) a vertex lands where the RENDERED plate lands that point —\n * `plateLandingAt`, the face's own mesh and grid, so the bangs sit on the face\n * they cover through the whole turn — from there out to `holdBase` a straight\n * ramp onto the hold edge's destination `holdEdgeAt(deg)`, and beyond it the\n * hold edge's own displacement, slope 1, so the outer strands carry whatever\n * silhouette change the caller asked for. The zone boundaries are REST\n * distances and do not move with the stop — only the destinations do, which\n * is what keeps the ramp and the outer zone continuous at every stop. A\n * destination that MOVES is the point of `holdEdgeAt`: a caller that has\n * measured the head's width and wants a specific silhouette ratio at full\n * turn narrows it per stop, while the boundary it pivots on stays put.\n * `holdEdgeAt(0)` must therefore be `holdBase` — enforced, since anything\n * else displaces the bangs in the rest pose, where every proportion was\n * judged — and the rest keyform is written as a literal zero: every zone is\n * the identity there, and the plate's own map leaves a bend residue at 0.\n *\n * The fold guard: at every stop, on both sides, the hold edge has to sit\n * outside the plate's rendered painted edge on EVERY row of `plateGuardRows`\n * — the same list, through the same `plateLandingAt`, that\n * `evaluateTurnCandidate` fitted the hold against, so a hold the solver ships\n * is one this bake can build. A vertex exactly on the axis never leaves the\n * first zone, so its zero `side` is never read.\n */\nexport function bakeHairFrontSilhouetteWarp(\n mesh: IkiMesh,\n partX: number,\n partY: number,\n faceCenterX: number,\n plateLandingAt: (deg: number, x: number, y: number) => number,\n edgeAt: (y: number) => number,\n holdBase: number,\n holdEdgeAt: (deg: number) => number,\n plateGuardRows: number[],\n): IkiWarp {\n if (holdEdgeAt(0) !== holdBase) {\n throw new Error(\n `auto-rig: bakeHairFrontSilhouetteWarp: holdEdgeAt(0) is ${holdEdgeAt(0)}, not the hold edge's own rest distance ${holdBase}, so the bangs would move in the rest pose`,\n );\n }\n const keyforms = HEAD_TURN_STOPS.map((deg) => {\n const holdEdge = holdEdgeAt(deg);\n // A hold edge inside the plate's rendered painted edge on any row would\n // run the ramp between them backwards and fold the strands onto the\n // cheek. Which stop catches it moves with the radius — see plateReach.\n const reach = ([-1, 1] as const).map((side) =>\n plateSideReachAt(\n plateLandingAt,\n faceCenterX,\n edgeAt,\n plateGuardRows,\n deg,\n side,\n ),\n );\n const reached = Math.max(reach[0], reach[1]);\n if (holdEdge <= reached) {\n const side = reach[1] > reach[0] ? 1 : -1;\n throw new Error(\n `auto-rig: bakeHairFrontSilhouetteWarp: at ${deg}° on the ${side < 0 ? \"-x\" : \"+x\"} side the hold edge sits ${holdEdge} from the face centre but the plate's edge maps to ${reached}, so the ramp between them would fold`,\n );\n }\n const offsets: number[] = [];\n for (let i = 0; i < mesh.vertices.length; i += 2) {\n const x = partX + mesh.vertices[i];\n const y = partY + mesh.vertices[i + 1];\n // Shared with evaluateTurnCandidate's own silhouette-centre reading, so\n // the two cannot drift apart on what a vertex's target actually is. dy\n // is zero — the hold is horizontal.\n offsets.push(\n deg === 0\n ? 0\n : hairFrontHoldTarget(\n x,\n y,\n deg,\n faceCenterX,\n plateLandingAt,\n edgeAt,\n holdBase,\n holdEdge,\n ) - x,\n 0,\n );\n }\n return { value: deg, offsets };\n });\n return { parameter: StandardParameter.AngleX, keyforms };\n}\n\n// ── bakeHairSwayWarp ───────────────────────────────────────────────────────────\n\n/** Tip travel of a swaying hair part at full sway, as a fraction of its own\n * height — \"the ends swing 9% of the hair's length\". Long back hair therefore\n * swings further than the bangs in pixels, as it should. */\nconst HAIR_SWAY_TIP_FRACTION = 0.09;\n/** Exponent on distance-from-root. 1 would be a hinge (straight shear); hair\n * bends, so the swing grows faster toward the ends. */\nconst HAIR_SWAY_CURL = 1.5;\n/** Range of the HairSwayX / HairSwayZ output parameters. */\nconst HAIR_SWAY_RANGE = 20;\n\n/**\n * Root-pinned sideways sway for a hair part, as a per-vertex warp.\n *\n * Parts have no pivot — a `rotate` binding turns a part about its crop centre.\n * Used for sway that swung the bangs' ROOTS off the hairline as much as their\n * ends, which is exactly how hair does not move. This warp instead leaves the\n * top row of the mesh where it is and displaces each row sideways by\n * `tipShift · u^HAIR_SWAY_CURL`, with `u` the row's distance from the top as a\n * fraction of the height: 0 at the roots, 1 at the ends.\n *\n * Keyforms sit at ±range with the rest pose in between, so a zero parameter is\n * zero offsets. Offsets are in the mesh's own pixel frame (+y up, centered),\n * matching `createPixelGridMesh` — pass the SAME mesh the part renders.\n *\n * Also reused for the bangs' AngleX turn lead: same root-pinned shape, driven\n * by the turn instead of a sway spring.\n */\nexport function bakeHairSwayWarp(\n mesh: IkiMesh,\n parameter: string,\n tipShift: number,\n range: number,\n): IkiWarp {\n let top = -Infinity;\n let bottom = Infinity;\n for (let i = 1; i < mesh.vertices.length; i += 2) {\n top = Math.max(top, mesh.vertices[i]);\n bottom = Math.min(bottom, mesh.vertices[i]);\n }\n const height = top - bottom;\n const swing: number[] = [];\n for (let i = 0; i < mesh.vertices.length; i += 2) {\n const u = height > 0 ? (top - mesh.vertices[i + 1]) / height : 0;\n swing.push(tipShift * Math.pow(u, HAIR_SWAY_CURL), 0);\n }\n return {\n parameter,\n keyforms: [\n // `0 - v`, not `-v`: the pinned root row must stay a plain +0.\n { value: -range, offsets: swing.map((v) => 0 - v) },\n { value: range, offsets: swing },\n ],\n };\n}\n\n// ── Head turn travel + body follow ──────────────────────────────────────────\n\n/**\n * The head's sideways travel at full turn (AngleX = ±30), as a fraction of the\n * face plate's own half-width.\n *\n * A fraction, not px, because the travel no longer cancels out of what the turn\n * is fitted to. It used to ride headDeformer, moving the eyes and the silhouette\n * together, so the measured `eyeShift` cue never saw it; baked into the face\n * grid (`TurnSurface.travel`, sampled by `bakeTurnGroupWarp2D`) it moves the\n * face inside a held silhouette and IS the floor under that cue — an absolute\n * px value would be a quarter of one plate's half-width and the whole of a\n * smaller one's. 0.25 was judged in the playground on a 400 px-wide plate,\n * where it is the 50 px that shipped.\n */\nconst HEAD_TURN_TRAVEL_RATIO = 0.25;\n\n/** That travel in px, for a plate of this half-width: what a layer set ASKS\n * the turn for. A solve with a measured head cuts it down to what the held\n * silhouette has room for — see `shellTravelCap`. */\nfunction headTurnTravel(faceHalfWidth: number): number {\n return HEAD_TURN_TRAVEL_RATIO * faceHalfWidth;\n}\n\n/** The torso's share of that travel. Below ~0.2 the shoulders still read as\n * bolted down; at 1 the neck stops articulating. 0.3 keeps 70 % of the turn\n * in the neck while the shoulders visibly come along. Judged in the\n * playground, not derived. */\nconst BODY_TURN_FOLLOW = 0.3;\n/** Vertical travel of the head at full breath (px, +y up; negative = down). */\nconst HEAD_BREATH_BOB = -12;\n/** The torso's share of the head's breath bob, SAME direction. In phase with\n * the head (not the sample model's counter-phase +6/−12, which reads as a\n * shrug: chest up while the head settles) so the neck compresses by 6 px, not\n * 18. Moving DOWN also keeps the torso's flat canvas-bottom cut off-canvas —\n * a rise would lift that hard edge into view every breath. */\nconst BODY_BREATH_FOLLOW = 0.5;\n\n// ── turnSolveInputs ───────────────────────────────────────────────────────────\n\n/** What `solveTurnModel` is handed after the targets, in its own parameter\n * order, so it spreads straight in. */\ntype TurnSolveInputs = [\n landmarks: TurnLandmarkSet,\n lattice: IkiWarpGrid,\n faceCenterX: number,\n faceHalfWidth: number,\n faceCenterY: number,\n nodRadius: number,\n carriers: ReadonlyMap<string, TurnCarrier>,\n profile: FaceRowProfile | undefined,\n hairFront:\n | { x: number; centerY: number; cropW: number; cropH: number }\n | undefined,\n];\n\n/** The standard parameters every generated rig declares — ids and ranges\n * verbatim from sample-model.ts; the hair-sway pair joins them per rig\n * (`generateIkiFromLayerSet`). Also where a binding's default-pose value is\n * read from (`defaultPoseOf`). */\nconst STANDARD_PARAMETERS: readonly IkiParameter[] = [\n {\n id: StandardParameter.MouthOpen,\n name: \"Mouth Open\",\n min: 0,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.MouthForm,\n name: \"Mouth Form\",\n min: -1,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.EyeOpenLeft,\n name: \"Eye L\",\n min: 0,\n max: 1,\n default: 1,\n },\n {\n id: StandardParameter.EyeOpenRight,\n name: \"Eye R\",\n min: 0,\n max: 1,\n default: 1,\n },\n {\n id: StandardParameter.EyeballX,\n name: \"Gaze X\",\n min: -1,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.EyeballY,\n name: \"Gaze Y\",\n min: -1,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.AngleX,\n name: \"Head Angle\",\n min: -30,\n max: 30,\n default: 0,\n },\n {\n id: StandardParameter.AngleY,\n name: \"Head Angle Y\",\n min: -30,\n max: 30,\n default: 0,\n },\n {\n id: StandardParameter.AngleZ,\n name: \"Head Angle Z\",\n min: -30,\n max: 30,\n default: 0,\n },\n {\n id: StandardParameter.Breath,\n name: \"Breath\",\n min: 0,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.BrowLeftY,\n name: \"Brow L Y\",\n min: -1,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.BrowRightY,\n name: \"Brow R Y\",\n min: -1,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.BrowLeftAngle,\n name: \"Brow L Angle\",\n min: -1,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.BrowRightAngle,\n name: \"Brow R Angle\",\n min: -1,\n max: 1,\n default: 0,\n },\n];\n\n/**\n * `evaluateTransform`'s additive rule, mirrored (the engine is not a\n * dependency of this package): scale starts at 1 and adds each binding's\n * value, rotation and translate sum, opacity moves nothing. `valueOf` picks\n * each binding's value — a `from`/`to` extreme for a bound, the default-pose\n * value for the pose the renderer rests in.\n */\nfunction transformUnder(\n bindings: readonly IkiBinding[],\n valueOf: (binding: IkiBinding, index: number) => number,\n): { x: number; y: number; rotation: number; scaleX: number; scaleY: number } {\n const trs = { x: 0, y: 0, rotation: 0, scaleX: 1, scaleY: 1 };\n bindings.forEach((binding, i) => {\n const value = valueOf(binding, i);\n switch (binding.channel) {\n case \"translateX\":\n trs.x += value;\n break;\n case \"translateY\":\n trs.y += value;\n break;\n case \"rotate\":\n trs.rotation += value;\n break;\n case \"scaleX\":\n trs.scaleX += value;\n break;\n case \"scaleY\":\n trs.scaleY += value;\n break;\n case \"opacity\":\n break;\n }\n });\n return trs;\n}\n\n/** The TRS a part's bindings give it with every parameter at its declared\n * default — what `evaluateTransform` returns on a freshly loaded rig, the\n * pose every `RenderedPart` is read in. A binding's value there is its\n * range at `ParameterStore.normalized`'s own `(default − min) / (max − min)`\n * (no standard parameter is zero-width). */\nfunction defaultPoseOf(bindings: readonly IkiBinding[]) {\n return transformUnder(bindings, (binding) => {\n const p = STANDARD_PARAMETERS.find((p) => p.id === binding.parameter);\n if (p === undefined) {\n throw new Error(\n `auto-rig: defaultPoseOf: \"${binding.parameter}\" is not a standard parameter`,\n );\n }\n const t = (p.default - p.min) / (p.max - p.min);\n return binding.from + (binding.to - binding.from) * t;\n });\n}\n\n/** A face-family layer as the generator rigs it: the crop's placement, the\n * part as it renders (`RenderedPart`, the default pose on that placement),\n * the mesh it renders with, the role's OWN bindings — `bindingsForRole`\n * without the parallax units, the expression it has whatever the turn does\n * — and its part warps (the eyelid fold on a white or a lash, else none).\n * Everything its group grid is sized from (`groupGridFor`) and its carrier\n * reads with, built once for the solve and the parts alike. Exported at\n * module level for the tests, not from the package. */\nexport interface TurnGroupMember {\n role: string;\n group: TurnGroupId;\n /** The crop's own placement (`bboxToTransform`), what the bindings add to. */\n transform: { x: number; y: number };\n part: RenderedPart;\n mesh: IkiMesh;\n bindings: readonly IkiBinding[];\n warps: readonly IkiWarp[];\n}\n\n/**\n * One turn group's grid: the members' PRE-BIND extent — where the engine\n * binds their vertices to it (`applyWarpToChild`: part warps, then the TRS\n * the bindings give) — grown by the family's turn and nod shifts and then by\n * GRID_MARGIN of its span per axis plus a pixel, over `cells` × `cells`\n * uniform cells (the format allows uneven columns; nothing here needs them).\n *\n * The extent is the union, over every member, of its mesh under each part\n * warp's keyforms — the rest mesh, and the mesh with each keyform's offsets\n * added (the fold's closed shape; a blend of two keyforms lies inside the box\n * of their union, and a member carries one warp at most) — placed by EVERY\n * combination of its geometric bindings' `from`/`to` extremes through\n * `evaluateTransform`'s additive rule (`transformUnder`: scale = 1 + Σ\n * values, so a `mouth` without an open drawing reaches scaleY 4 and scaleX\n * 1.4; rotation = Σ, so a ±12° brow grows its y extent by its half-width's\n * sine; a translate sums; opacity moves nothing and is skipped). The extremes\n * are at the corners: each channel is affine in its bindings, and the\n * rotations are small enough that a corner's x and y are monotone in the\n * angle to within a fraction of a pixel (a brow flatter than tan 12° peaks a\n * little inside ±12° and overshoots its corner by under 0.1 px), far under\n * the margin. `turnShiftPx` is the furthest the family's solved depth can carry a\n * node — the depth solver's own cap in px — and `nodShiftPx` its nod\n * translate; both are added on both sides, every stop being ±.\n *\n * The margin is what keeps a vertex off the grid boundary, where\n * `bindPointToRestGrid` would clamp it. The face — no binding, no warp, no\n * shift — gets its crop plus the margin and nothing else, symmetric about its\n * centre, so the plate's axis is a node. Exported at module level for the\n * tests, not from the package.\n */\nexport function groupGridFor(\n members: readonly TurnGroupMember[],\n cells: number,\n turnShiftPx: number,\n nodShiftPx: number,\n): IkiWarpGrid {\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n for (const member of members) {\n const rest = member.mesh.vertices;\n const shapes = [rest];\n for (const warp of member.warps) {\n for (const keyform of warp.keyforms) {\n shapes.push(rest.map((v, i) => v + keyform.offsets[i]));\n }\n }\n const geometric = member.bindings.filter((b) => b.channel !== \"opacity\");\n for (let corner = 0; corner < 1 << geometric.length; corner++) {\n const trs = transformUnder(geometric, (b, i) =>\n (corner & (1 << i)) === 0 ? b.from : b.to,\n );\n const cos = Math.cos(trs.rotation * (Math.PI / 180));\n const sin = Math.sin(trs.rotation * (Math.PI / 180));\n for (const shape of shapes) {\n for (let i = 0; i < shape.length; i += 2) {\n // The engine's order: scale, rotate, translate — about the part's\n // own centre, where the crop places it.\n const sx = shape[i] * trs.scaleX;\n const sy = shape[i + 1] * trs.scaleY;\n const x = member.transform.x + trs.x + cos * sx - sin * sy;\n const y = member.transform.y + trs.y + sin * sx + cos * sy;\n minX = Math.min(minX, x);\n maxX = Math.max(maxX, x);\n minY = Math.min(minY, y);\n maxY = Math.max(maxY, y);\n }\n }\n }\n }\n minX -= turnShiftPx;\n maxX += turnShiftPx;\n minY -= nodShiftPx;\n maxY += nodShiftPx;\n const marginX = (maxX - minX) * GRID_MARGIN + 1;\n const marginY = (maxY - minY) * GRID_MARGIN + 1;\n return {\n cols: cells,\n rows: cells,\n points: generateGridPoints(\n cells,\n cells,\n minX - marginX,\n maxX + marginX,\n minY - marginY,\n maxY + marginY,\n ),\n };\n}\n\n/** The eyelid fold of an eye white or a lash, or undefined for any other\n * role: both fold to the WHITE's crease on that side — EYELID_FOLD_CREASE\n * below the white's own centre — on that side's EyeOpen, so the lash lands\n * on top of the cut eyeball and covers the seam. The lash keeps LASH_FOLD_K\n * of its height; the white EYELID_FOLD_K, or nothing at all when a lash\n * supplies the closed line (a strip of iris must not show under it). Both\n * whites are REQUIRED_ROLES, so the crease always has one to read. `t` is\n * the part's placement and `mesh` the mesh it renders with. */\nfunction eyelidFoldFor(\n layers: LayerInput[],\n layer: LayerInput,\n mesh: IkiMesh,\n t: { x: number; y: number },\n): IkiWarp | undefined {\n const side = ROLE_TABLE[layer.role].eyeSide;\n const isLash = layer.role.startsWith(\"lash_\");\n if (side === undefined || !(layer.role.startsWith(\"eye_\") || isLash)) {\n return undefined;\n }\n const white = layers.find((l) => l.role === `eye_${side}`)!;\n const creaseWorldY =\n bboxToTransform(white.bbox, white.canvasW, white.canvasH, white.role).y -\n EYELID_FOLD_CREASE * white.cropH;\n const hasLash = layers.some((l) => l.role === `lash_${side}`);\n return bakeEyelidFoldWarp(\n mesh,\n side === \"L\"\n ? StandardParameter.EyeOpenLeft\n : StandardParameter.EyeOpenRight,\n creaseWorldY - t.y,\n isLash ? LASH_FOLD_K : hasLash ? 0 : EYELID_FOLD_K,\n );\n}\n\n/** Everything `turnSetup` derives from a layer set, by name: what the solve\n * is handed (`solveInputsOf` lines the first nine up in `solveTurnModel`'s\n * order), the plate guards derived once for the solve and the bake alike,\n * and what the generator ships and bakes from — the members' meshes and\n * warps, each group's grid, the nod's parallax unit off the same head\n * half-height as the nod radius — so the two read one set of values. */\ninterface TurnSetup {\n landmarks: TurnLandmarkSet;\n lattice: IkiWarpGrid;\n faceCenterX: number;\n faceHalfWidth: number;\n faceCenterY: number;\n nodRadius: number;\n carriers: ReadonlyMap<string, TurnCarrier>;\n /** The face layer's own row profile, when it measured one. */\n profile: FaceRowProfile | undefined;\n hairFront:\n | { x: number; centerY: number; cropW: number; cropH: number }\n | undefined;\n /** The eye pair's mean y — the row the cues are measured on and a row\n * profile is normalised at, as `solveTurnModel` reads it off the same\n * landmarks; the generator's own surface, when nothing is solved, is\n * normalised there too. */\n eyeRowY: number;\n /** The plate and its guards, `plateGuardsOf` — see that function. */\n plate: TurnCarrier;\n edgeAt: (y: number) => number;\n plateGuardRows: number[];\n members: readonly TurnGroupMember[];\n groupGrids: ReadonlyMap<TurnGroupId, IkiWarpGrid>;\n parallaxUnitY: number;\n hasNose: boolean;\n hasMouthOpen: boolean;\n}\n\n/**\n * The turn's geometry for a layer set, in the order nothing may depend on the\n * solve: every face-family layer's placement, role bindings and part warps\n * (pure — no unit, no depth) → each group's grid from its members\n * (`groupGridFor`) → the virtual lattice from those grids → the landmarks on\n * their carriers. The solve then runs on exactly these, and the bakes sample\n * exactly these, which is what lets the report equal the render.\n *\n * The head's vertical reach — the nod cylinder's radius and its parallax unit\n * — is the TURN FAMILY's union on y (every group member plus the bangs,\n * transform ± crop/2, the centred pixel-mesh convention) grown by GRID_MARGIN,\n * about the face centre: the rows the shared face grid had, kept as the one\n * head-level value however the groups are cut.\n *\n * A family's shift bound is the depth solver's own cap in px — its landmarks'\n * far edge to the plate's edge (`solveTurnDepthSigned`) — and 0 when nothing\n * is solved (no nose, FEATURE_NOD_DEPTH's gate); the brows and the blush ride\n * with the eyes (`turnFamily`), the plate has none. Each group's grid takes its\n * family's, and the lattice — one row of TURN_LATTICE_CELL_PX columns\n * anchored on the face centre — is widened so every grid's every node,\n * shifted by the LARGEST bound either way, sits strictly inside its outer\n * columns, snapped outward to the next anchored column: `mapX` pins anything\n * outside onto the edge column, which would deform a node even at rest. The\n * lattice's height is the union's; `turnColumnMap` reads row 0 only.\n *\n * The face's row profile (`faceRowProfile`), when its layer carries one, is\n * read here and handed to the solve and the generator alike, and the plate's\n * guards are derived from it once (`plateGuardsOf`).\n */\nfunction turnSetup(layers: LayerInput[]): TurnSetup {\n // validateLayerInputs guarantees \"face\" is present — safe to assert here.\n const faceLayer = layers.find((l) => l.role === \"face\")!;\n const faceTransform = bboxToTransform(\n faceLayer.bbox,\n faceLayer.canvasW,\n faceLayer.canvasH,\n \"face\",\n );\n // Source-placed face centre in model space (unshifted).\n const faceCenterX = faceTransform.x;\n const faceCenterY = faceTransform.y;\n const faceHalfWidth = faceLayer.cropW / 2;\n const plateEdgeX = faceCenterX - faceHalfWidth;\n // The feature parallax's gate — see FEATURE_NOD_DEPTH.\n const hasNose = layers.some((l) => l.role === \"nose\");\n const hasMouthOpen = layers.some((l) => l.role === \"mouth_open\");\n\n // ── The head's vertical reach ─────────────────────────────────────────────\n // The face is required and a group member, so the union is never empty.\n const turnLayers = layers.filter(\n (l) => isTurnGroup(ROLE_TABLE[l.role].deformer) || l.role === \"hair_front\",\n );\n const turnYs = turnLayers.map((l) => ({\n y: bboxToTransform(l.bbox, l.canvasW, l.canvasH, l.role).y,\n h: l.cropH,\n }));\n let unionMinY = Math.min(...turnYs.map(({ y, h }) => y - h / 2));\n let unionMaxY = Math.max(...turnYs.map(({ y, h }) => y + h / 2));\n const spanY = unionMaxY - unionMinY;\n unionMinY -= spanY * GRID_MARGIN;\n unionMaxY += spanY * GRID_MARGIN;\n // The larger of the two distances from the nod axis, so a union that is not\n // symmetric about it still keeps every row within the cylinder's bound.\n const halfH = Math.max(faceCenterY - unionMinY, unionMaxY - faceCenterY);\n const nodRadius = halfH * HEAD_CYLINDER_RADIUS_FACTOR;\n const parallaxUnitY = headNodParallaxUnit(halfH);\n\n // ── The members ───────────────────────────────────────────────────────────\n const members: TurnGroupMember[] = [];\n for (const layer of layers) {\n const spec = ROLE_TABLE[layer.role];\n if (!isTurnGroup(spec.deformer)) continue;\n const { role, cropW, cropH } = layer;\n const t = bboxToTransform(layer.bbox, layer.canvasW, layer.canvasH, role);\n const { cols, rows } = meshCellsFor(cropW, cropH);\n const mesh = createPixelGridMesh(cols, rows, cropW, cropH);\n const bindings = bindingsForRole(spec, role, cropW, cropH, {\n hasMouthOpen,\n });\n const fold = eyelidFoldFor(layers, layer, mesh, t);\n const pose = defaultPoseOf(bindings);\n members.push({\n role,\n group: spec.deformer,\n transform: t,\n part: {\n x: t.x + pose.x,\n y: t.y + pose.y,\n cropW,\n cropH,\n cols,\n rows,\n scaleX: pose.scaleX,\n scaleY: pose.scaleY,\n rotation: pose.rotation,\n },\n mesh,\n bindings,\n warps: fold === undefined ? [] : [fold],\n });\n }\n\n // ── Each family's shift bounds ────────────────────────────────────────────\n const landmarks = turnLandmarks(layers);\n const turnShiftOf = (marks: TurnLandmark[]) =>\n hasNose && marks.length > 0 ? familyReachPx(marks, plateEdgeX) : 0;\n const turnShift: Record<keyof TurnDepths, number> = {\n eye: turnShiftOf(landmarks.eye),\n nose: turnShiftOf(landmarks.nose),\n mouth: turnShiftOf(landmarks.mouth),\n };\n\n // ── The group grids and the carriers ──────────────────────────────────────\n const groupGrids = new Map<TurnGroupId, IkiWarpGrid>();\n for (const { group, role } of members) {\n if (groupGrids.has(group)) continue;\n const own = members.filter((m) => m.group === group);\n const isPlate = group === \"faceWarp\";\n // The nod translate the members will carry (featureParallaxBindings) —\n // one depth per group, taken as the members' largest all the same.\n const nodShiftPx = hasNose\n ? Math.max(\n ...own.map((m) => FEATURE_NOD_DEPTH[roleFamily(m.role)] ?? 0),\n ) * parallaxUnitY\n : 0;\n groupGrids.set(\n group,\n groupGridFor(\n own,\n isPlate ? FACE_PLATE_CELLS : FEATURE_GRID_CELLS,\n isPlate ? 0 : turnShift[turnFamily(role)],\n nodShiftPx,\n ),\n );\n }\n // The mouth family turns about the closed mouth's rest centre, its source\n // placement — the one its landmark reads (`turnLandmarks`) — with or\n // without a nose, the tilt being no slide. validateLayerInputs guarantees\n // \"mouth\" is present.\n const mouthAt = members.find((m) => m.role === \"mouth\")!.transform;\n const mouthAnchor: TurnAnchor = {\n x: mouthAt.x,\n y: mouthAt.y,\n tiltDeg: MOUTH_TURN_TILT_DEG,\n };\n // The nose turns about its bridge top — the top edge of the box its\n // landmark reads (`noseDrawnBox`), at that box's centre x — so the bridge\n // rides the slide and the tip, the end furthest from the pivot, carries the\n // tilt.\n const noseLayer = layers.find((l) => l.role === \"nose\");\n let noseAnchor: TurnAnchor | undefined;\n if (noseLayer) {\n const drawn = noseDrawnBox(noseLayer);\n const at = bboxToTransform(\n drawn,\n noseLayer.canvasW,\n noseLayer.canvasH,\n \"nose\",\n );\n noseAnchor = {\n x: at.x,\n y: at.y + drawn.h / 2,\n tiltDeg: NOSE_TURN_TILT_DEG,\n };\n }\n const carriers = new Map<string, TurnCarrier>(\n members.map((m) => {\n const grid = groupGrids.get(m.group)!;\n const anchor =\n m.group === \"mouthWarp\"\n ? mouthAnchor\n : m.group === \"noseWarp\"\n ? noseAnchor\n : undefined;\n return [\n m.role,\n anchor === undefined\n ? { grid, part: m.part }\n : { grid, part: m.part, anchor },\n ];\n }),\n );\n\n // ── The lattice ───────────────────────────────────────────────────────────\n const maxShift = Math.max(turnShift.eye, turnShift.nose, turnShift.mouth);\n let reach = 0;\n for (const grid of groupGrids.values()) {\n reach = Math.max(\n reach,\n faceCenterX - grid.points[0] + maxShift,\n grid.points[grid.cols * 2] - faceCenterX + maxShift,\n );\n }\n const halfCells = Math.floor(reach / TURN_LATTICE_CELL_PX) + 1;\n // Columns at faceCenterX ± k·pitch outright, not a span divided up, so the\n // axis column IS faceCenterX to the last bit; row 0 is the top row.\n const points: number[] = [];\n for (const y of [unionMaxY, unionMinY]) {\n for (let k = -halfCells; k <= halfCells; k++) {\n points.push(faceCenterX + k * TURN_LATTICE_CELL_PX, y);\n }\n }\n const lattice: IkiWarpGrid = { cols: 2 * halfCells, rows: 1, points };\n\n const hairFrontLayer = layers.find((l) => l.role === \"hair_front\");\n const hairFront =\n hairFrontLayer &&\n (() => {\n const t = bboxToTransform(\n hairFrontLayer.bbox,\n hairFrontLayer.canvasW,\n hairFrontLayer.canvasH,\n \"hair_front\",\n );\n return {\n x: t.x,\n centerY: t.y,\n cropW: hairFrontLayer.cropW,\n cropH: hairFrontLayer.cropH,\n };\n })();\n\n const eyeRowY = eyeRowOf(landmarks, faceCenterY);\n const profile = faceRowProfile(faceLayer, faceCenterY);\n\n return {\n landmarks: turnLandmarks(layers, carriers),\n lattice,\n faceCenterX,\n faceHalfWidth,\n faceCenterY,\n nodRadius,\n carriers,\n profile,\n hairFront,\n eyeRowY,\n ...plateGuardsOf(carriers, faceHalfWidth, profile, hairFront),\n members,\n groupGrids,\n parallaxUnitY,\n hasNose,\n hasMouthOpen,\n };\n}\n\n/**\n * Everything `generateIkiFromLayerSet` hands `solveTurnModel` for a layer set,\n * the targets and the caller's `headEdges` excepted (they are the caller's\n * own) — built by `turnSetup` and nowhere else, so a test that solves a layer\n * set directly solves exactly the turn the generator solves, on the very\n * grids and carriers it ships. Exported at module level for those tests, not\n * from the package.\n */\nexport function turnSolveInputs(layers: LayerInput[]): TurnSolveInputs {\n return solveInputsOf(turnSetup(layers));\n}\n\n/** A setup's fields in `solveTurnModel`'s positional order — spelled here\n * once, since four adjacent numbers in it are not told apart by type. */\nfunction solveInputsOf(s: TurnSetup): TurnSolveInputs {\n return [\n s.landmarks,\n s.lattice,\n s.faceCenterX,\n s.faceHalfWidth,\n s.faceCenterY,\n s.nodRadius,\n s.carriers,\n s.profile,\n s.hairFront,\n ];\n}\n\n// ── generateIkiFromLayerSet ───────────────────────────────────────────────────\n\n/**\n * Auto-rig: given decoded layer inputs and the shared canvas size, produce a\n * valid IkiModel ready for parseIkiModel.\n *\n * - Validate all inputs before deriving anything.\n * - Place parts at source-derived positions (bboxToTransform, unshifted).\n * - Emit the standard parameters (STANDARD_PARAMETERS), plus a conditional\n * HairSwayX/Z pair + hair-sway physics rigs when a hair_front layer is\n * present.\n * - Size every turn group's grid to its own members and the virtual lattice\n * to those grids, then solve the turn on them (turnSetup, solveTurnModel):\n * one surface — radius, travel, the head-level nod radius — that every\n * bake below samples.\n * - Build headDeformer (matrix, neck pivot, nod/tilt/breath bindings — the\n * turn translates nothing rigidly; its travel is in the group grids), one\n * warp deformer per turn group present — `faceWarp` for the plate,\n * `eyeWarp_L/R`, `mouthWarp`, `noseWarp`, `browWarp_L/R`, `blushWarp_L/R`\n * — each a warp2d over its own grid baked from that surface with the\n * family's solved depth as keyform geometry (bakeTurnGroupWarp2D), and\n * bodyDeformer when a body layer is present (matrix, torso-base pivot,\n * AngleX follow + Breath follow).\n * - Mesh parts (spec.mesh===true) → width:1, height:1, pixel grid mesh sized by\n * meshCellsFor + role bindings, each face-family part on its group's warp.\n * - Static parts (spec.mesh===false) → width:cropW, height:cropH, no mesh.\n * - Part ids equal the role string (deterministic, no crypto.randomUUID).\n * - Return parseIkiModel(structuredClone(model)) — every caller gets a\n * validated model; bad assembly fails loudly.\n *\n * `options.turnTargets` is what the head turn is fitted to: the cues a 30°\n * reference measures, defaulting to DEFAULT_TURN_TARGETS. A target the CALLER\n * passed that this layer set cannot reach throws; one that came from the\n * defaults is clamped to what it can do, and so is a caller's `eyeShift` past\n * the room the art leaves the far eye — see TurnTargets for why, and pass\n * `options.onTurnSolved` to see which ones were clamped and what the turn\n * ended up reaching.\n *\n * `options.strandEdges` gives each side's iris and the bangs' run it would\n * slide under as measured pixel edges (`IrisStrand`), and with it the turn\n * keeps each far iris from sliding under that run any further than it is\n * painted, best effort: where no radius the fit can use holds that, the rig\n * is still built, with the eyes' depth at 0, and the report's\n * `strandOverlap` says how much of the iris the bangs cover.\n */\nexport function generateIkiFromLayerSet(\n layers: LayerInput[],\n canvas: { width: number; height: number },\n options: {\n turnTargets?: TurnTargets;\n /** Called once, after a turn is solved, with what it settled on. Not called\n * for a layer set without a nose, which solves no turn at all. */\n onTurnSolved?: (report: TurnSolveReport) => void;\n /** Every role with an opaque pixel in the eye-row band, per side, when\n * `turnTargets.headHalfWidth` was measured off that same union — a\n * companion to it, not a turn target itself (the caller-facing contract\n * stays the three cues). See `TurnSolveContext.headEdges`. */\n headEdges?: {\n left: { role: string; x: number }[];\n right: { role: string; x: number }[];\n };\n /** Per side (`left` the −x side, `right` the +x side, like `headEdges`),\n * that side's iris and the bangs' run it would slide under on the turn,\n * as real pixel edges on the row holding the iris's centre — see\n * `IrisStrand` for the contract and its three cases (a strand outward of\n * a clear iris, a run over the iris centre that clears on the face side,\n * a fringe spanning the face). A side's iris is whichever of `iris_L` /\n * `iris_R` sits on that side; the sides are independent, so either may\n * be absent. With it the turn keeps each far iris from sliding under its\n * run any further than it is painted, best effort: never a refusal. The\n * report's `strandOverlap` gives, per side whose far iris the run still\n * covers at some far stop, the covered width at the stop where it is\n * largest, next to the painted one, and whether the bound held — `true`\n * for an iris painted under its run that the turn took no deeper,\n * `false` where the fitted radius could not hold it and the eyes' turn\n * depth is 0. It is always\n * validated against the layers first (`validateStrandEdges`), but only\n * the turn solve reads it, so like `headEdges` it has no effect on the\n * rig without a nose; absent or `{}`, the rig is the one built without\n * it. */\n strandEdges?: { left?: IrisStrand; right?: IrisStrand };\n } = {},\n): IkiModel {\n // Validate first — never derive anything from unchecked input.\n validateLayerInputs(layers, canvas);\n validateStrandEdges(layers, options.strandEdges);\n\n // Hair-sway secondary motion is gated on a front-hair layer being present.\n const hasHair = layers.some((l) => l.role === \"hair_front\");\n const bodyLayer = layers.find((l) => l.role === \"body\");\n\n // ── Standard parameters ───────────────────────────────────────────────────\n const parameters: IkiParameter[] = [...STANDARD_PARAMETERS];\n\n // Hair-sway output params (physics-driven), declared only when there is front\n // hair to drive — keeps no-hair models free of unused parameters.\n if (hasHair) {\n parameters.push(\n {\n id: StandardParameter.HairSwayX,\n name: \"Hair Sway X\",\n min: -HAIR_SWAY_RANGE,\n max: HAIR_SWAY_RANGE,\n default: 0,\n },\n {\n id: StandardParameter.HairSwayZ,\n name: \"Hair Sway Z\",\n min: -HAIR_SWAY_RANGE,\n max: HAIR_SWAY_RANGE,\n default: 0,\n },\n );\n }\n\n // ── The face, its groups, the lattice and the turn ────────────────────────\n // Exactly what the solve is handed, and the members and group grids it was\n // built from — built once in turnSetup, so a test that solves this layer\n // set directly (turnSolveInputs) solves this very turn on these very grids.\n const setup = turnSetup(layers);\n const {\n lattice,\n faceCenterX,\n faceHalfWidth,\n faceCenterY,\n nodRadius,\n profile,\n eyeRowY,\n plate,\n edgeAt,\n plateGuardRows,\n members,\n groupGrids,\n carriers,\n parallaxUnitY,\n hasNose,\n hasMouthOpen,\n } = setup;\n const faceCropH = plate.part.cropH;\n\n // The head cylinder's turn radius, the face's sideways travel and how far in\n // front of the axis each feature sits: SOLVED from the turn cues, on the\n // very surface every group grid is baked from, so what the targets promise\n // is what the keyforms do. Without a nose there is no feature slide to fit\n // (see FEATURE_NOD_DEPTH) and nothing to solve the radius against.\n const turn = hasNose\n ? solveTurnModel(\n resolveTurnTargets(options.turnTargets),\n ...solveInputsOf(setup),\n options.headEdges,\n options.strandEdges,\n )\n : undefined;\n if (turn?.unreachable) {\n throw new TurnTargetError(\n turn.attainable === undefined\n ? // Only a silhouette can land here: nothing the field's own accepted\n // range allows renders on this layer set, so there is no interval to\n // offer instead — see `silhouetteMiss` in evaluateTurnCandidate.\n `auto-rig: turnTargets.${turn.field} ${turn.value} is unreachable for this layer set, and so is every ${turn.field} in [${SILHOUETTE_RATIO_MIN}, ${TURN_RATIO_MAX}]`\n : `auto-rig: turnTargets.${turn.field} ${turn.value} is unreachable for this layer set (attainable ${turn.attainable[0]}…${turn.attainable[1]})`,\n );\n }\n if (turn) {\n options.onTurnSolved?.({\n radius: turn.radius,\n holdBase: turn.holdBase,\n depths: turn.depths,\n achieved: turn.achieved,\n clamped: turn.clamped,\n ...(turn.strandOverlap === undefined\n ? {}\n : { strandOverlap: turn.strandOverlap }),\n });\n }\n // The analytic head every bake below samples — every group's grid, the\n // bangs' hold and the body's follow all read this ONE surface, so nothing\n // renders a turn the cues were not measured on. Solved when there was a\n // nose; without one, the lattice's own reach about the face centre with the\n // no-fold margin (on a row reading aMax the bound lands beyond every node\n // any grid reads, so those rows never leave the surface; a narrower profile\n // row's bound is nearer — ≈ 150 px on a 96 px band, against plate nodes at\n // ±250 — so its transparent-margin nodes ride the rigid branch: monotone,\n // so fold-free, and beyond the painted edge, so nothing reads them, as\n // Decision 5 accepts) and the plate's own uncut travel ask, there being no\n // held silhouette to size it against; the face's own row profile, when it\n // has one, bends that surface's rows the same way it would a solved one's.\n // Its nod radius is the head's, the one the solve was handed. The turn's\n // depth-parallax unit comes off the same cylinder the bakes bend.\n const latticeReach = Math.max(\n faceCenterX - lattice.points[0],\n lattice.points[lattice.cols * 2] - faceCenterX,\n );\n const surface =\n turn?.surface ??\n turnSurface({\n faceCenterX,\n faceCenterY,\n radius: latticeReach * HEAD_CYLINDER_RADIUS_FACTOR,\n travel: headTurnTravel(faceHalfWidth),\n nodRadius,\n lattice,\n profile,\n eyeRowY,\n });\n const parallaxUnit = headTurnParallaxUnit(surface.radius);\n // The plate as it RENDERS on that surface — its own mesh over its own grid,\n // the carrier. Its painted edge per row (`edgeAt`) and the rows it is\n // guarded at (`plateGuardRows`) are the setup's own, the very ones the solve\n // read (`plateGuardsOf`), so the hold the solve fitted is the hold the bake\n // below can build.\n const plateLandingAt = plateLandingOn(plate, rowMapsOf(surface));\n\n // ── headDeformer pivot (neck): slightly below the face bottom ─────────────\n // faceBottom is the model-space y of the bottom edge of the face crop.\n // The neck pivot sits 15% of the face crop height below the face bottom.\n const faceBottom = faceCenterY - faceCropH / 2;\n const neckPivot = {\n x: faceCenterX,\n y: faceBottom - faceCropH * 0.15, // 15% below face bottom = neck\n };\n\n // ── Deformers ─────────────────────────────────────────────────────────────\n const deformers: IkiDeformer[] = [\n // headDeformer: rigid matrix rotating/translating the whole head about the\n // neck pivot. Same bindings as sample-model.ts apart from the turn, which\n // moves the face inside the head rather than the head itself (see below).\n {\n id: \"headDeformer\",\n pivot: neckPivot,\n bindings: [\n // No AngleX binding: the turn moves NOTHING rigidly. Its sideways\n // travel is baked into every group grid (the surface's `travel`, see\n // bakeTurnGroupWarp2D) so the face slides inside a silhouette the\n // bangs hold; a translate here would take the hair shell with it. Nor\n // is the turn a roll — the sample model's ±6° \"lean into the turn\" was\n // tried here and dropped, since rotating about the neck pivot swings\n // the crown (~500px above it) far more than the chin, so the top of\n // the head appeared to lunge ahead of the face on every turn. Roll is\n // AngleZ's job, below.\n\n // Nod: a vertical translate only. No rotate — a pitch expressed as a\n // rigid rotation would sum with the AngleZ roll below at diagonal\n // poses, collapsing pitch into roll.\n {\n parameter: StandardParameter.AngleY,\n channel: \"translateY\" as const,\n from: -NOD_TRAVEL,\n to: NOD_TRAVEL,\n },\n // Tilt: the whole head rolls about the neck pivot, one degree per\n // degree. Positive AngleZ is clockwise on screen (engine rotate is\n // CCW-positive, hence the flipped from/to) — Live2D's convention: in\n // its own sample motions AngleZ carries the sign of AngleX 214 times\n // out of 222, i.e. a head that leans into its turn tilts clockwise on\n // a turn to the viewer's right. The only rotate binding on the head.\n {\n parameter: StandardParameter.AngleZ,\n channel: \"rotate\" as const,\n from: 30,\n to: -30,\n },\n {\n parameter: StandardParameter.Breath,\n channel: \"translateY\" as const,\n from: 0,\n to: HEAD_BREATH_BOB,\n },\n ],\n },\n ];\n\n // One cylinder-bend warp per turn group present, parented to headDeformer:\n // the group's own grid (`groupGridFor`, the very carrier the solve read its\n // parts through), every node sampling the surface at its own rest position\n // — shifted, at each turn stop, by the family's solved depth share of the\n // parallax unit, so the depth is the grid's keyform geometry rather than a\n // translate on the parts (see featureParallaxBindings); the mouth's and the\n // nose's nodes turn about their carrier's anchor, the one the solve read\n // them by. The plate has no depth: it IS the cylinder. Without a solve no\n // depth is known and every group reads the surface unshifted. One 2D warp\n // carries both the turn and the nod (a deformer holds either `warps` or\n // `warp2d`, never both).\n for (const [group, grid] of groupGrids) {\n const { role } = members.find((m) => m.group === group)!;\n const family = turnFamily(role);\n const depth = group === \"faceWarp\" ? 0 : (turn?.depths[family] ?? 0);\n deformers.push({\n kind: \"warp\" as const,\n id: group,\n parent: \"headDeformer\",\n grid,\n warp2d: bakeTurnGroupWarp2D(\n grid,\n StandardParameter.AngleX,\n StandardParameter.AngleY,\n surface,\n (deg) => (depth * parallaxUnit * deg) / HEAD_TURN_MAX_DEG,\n carriers.get(role)!.anchor,\n ),\n });\n }\n\n // bodyDeformer: the torso's own rigid deformer. A sibling of headDeformer, not\n // its parent, so the head's own bindings stay untouched; reparent the head\n // under it once the torso gains a rotation. Translate-only, so the pivot is\n // inert today; it sits at the torso base, where a future lean would rock from.\n if (bodyLayer) {\n const bt = bboxToTransform(\n bodyLayer.bbox,\n bodyLayer.canvasW,\n bodyLayer.canvasH,\n \"body\",\n );\n deformers.push({\n id: \"bodyDeformer\",\n pivot: { x: bt.x, y: bt.y - bodyLayer.cropH / 2 },\n bindings: [\n // Low-weight follow of the turn, same direction as the face's own\n // slide — the only rigid part of the travel left.\n {\n parameter: StandardParameter.AngleX,\n channel: \"translateX\" as const,\n from: -BODY_TURN_FOLLOW * surface.travel,\n to: BODY_TURN_FOLLOW * surface.travel,\n },\n // Breath: follow the head's bob, same direction, half amplitude.\n {\n parameter: StandardParameter.Breath,\n channel: \"translateY\" as const,\n from: 0,\n to: BODY_BREATH_FOLLOW * HEAD_BREATH_BOB,\n },\n ],\n });\n }\n\n // ── Parts ─────────────────────────────────────────────────────────────────\n const memberByRole = new Map(members.map((m) => [m.role, m]));\n const parts: IkiPart[] = layers.map((layer) => {\n const { role, bbox, cropW, cropH, canvasW, canvasH } = layer;\n const spec = ROLE_TABLE[role];\n const t = bboxToTransform(bbox, canvasW, canvasH, role);\n const roleBindings = bindingsForRole(spec, role, cropW, cropH, {\n hasMouthOpen,\n parallaxUnitY,\n hasNose,\n });\n // `IkiPart.deformer` is optional, so a \"none\" role states its detachment by\n // leaving the field off rather than naming a deformer that must exist.\n const deformerId = spec.deformer === \"none\" ? undefined : spec.deformer;\n\n if (spec.mesh) {\n // Mesh part: width:1, height:1 with a pixel grid mesh centered at the\n // crop center; the engine applies the part transform to position it. A\n // group member renders on the very mesh its carrier was read with.\n const member = memberByRole.get(role);\n const { cols, rows } = meshCellsFor(cropW, cropH);\n const mesh =\n member?.mesh ?? createPixelGridMesh(cols, rows, cropW, cropH);\n const part: IkiPart = {\n id: role,\n color: [1, 1, 1, 1] as [number, number, number, number],\n width: 1,\n height: 1,\n order: spec.order,\n transform: t,\n deformer: deformerId,\n mesh,\n };\n if (roleBindings.length > 0) {\n part.bindings = roleBindings;\n }\n // Hair sway and turn lead: the PhysicsMotion springs lag AngleX / AngleZ\n // onto HairSwayX / HairSwayZ (rigs and params exist only with front\n // hair), and both hair parts swing their ends on them with the roots\n // pinned. The back hair is longer, so the same fraction of its height is\n // a bigger swing.\n if ((role === \"hair_front\" || role === \"hair_back\") && hasHair) {\n // Both hair parts ride the rigid head, with no grid edge under them\n // to clamp a swing, so the full fraction of the height swings on\n // both. Warp order in the array doesn't matter; they sum.\n const tipShift = HAIR_SWAY_TIP_FRACTION * cropH;\n const ownWarps: IkiWarp[] = [];\n if (role === \"hair_front\") {\n // The bangs' turn lead: root-pinned like the sway, keyed to\n // HEAD_TURN_MAX_DEG instead of a sway spring's range — see\n // bindingsForRole's doc for why this is a warp, not a binding.\n ownWarps.push(\n bakeHairSwayWarp(\n mesh,\n StandardParameter.AngleX,\n HAIR_FRONT_DEPTH * parallaxUnit,\n HEAD_TURN_MAX_DEG,\n ),\n );\n // The bangs draw the head's outline, so they hold it through the\n // turn instead of squeezing in with the plate beneath them — their\n // inner strands on the RENDERED plate, their outer ones on the hold\n // edge. The solve already picked the hold's zone for the radius it\n // picked: the head's measured half-width when it had one, and a\n // destination that carries the silhouette ratio through the turn.\n // Without a solve nothing here has measured where the outline\n // actually is, so the hold edge is the outermost the plate ever\n // reaches, clear of it by a pixel, and it holds its rest position:\n // the silhouette stops narrowing without being asked to move. That\n // reach already covers the rest pose (its 0° stop is the identity),\n // and anything inside it would fold the ramp.\n const holdBase =\n turn?.holdBase ??\n plateReach(plateLandingAt, faceCenterX, edgeAt, plateGuardRows) +\n HOLD_CLEARANCE;\n ownWarps.push(\n bakeHairFrontSilhouetteWarp(\n mesh,\n t.x,\n t.y,\n faceCenterX,\n plateLandingAt,\n edgeAt,\n holdBase,\n turn?.holdEdgeAt ?? (() => holdBase),\n plateGuardRows,\n ),\n );\n // On the nod the bangs slide with the brows they hang over — only\n // when the brows slide, i.e. with a nose to lead them — and bend on\n // the same surface the plate does; see HAIR_FRONT_NOD_DEPTH.\n ownWarps.push(\n bakeNodWarp(\n mesh,\n t.y,\n surface,\n hasNose ? HAIR_FRONT_NOD_DEPTH * parallaxUnitY : 0,\n ),\n );\n }\n part.warps = [\n bakeHairSwayWarp(\n mesh,\n StandardParameter.HairSwayX,\n tipShift,\n HAIR_SWAY_RANGE,\n ),\n bakeHairSwayWarp(\n mesh,\n StandardParameter.HairSwayZ,\n tipShift,\n HAIR_SWAY_RANGE,\n ),\n ...ownWarps,\n ];\n }\n // Eye blink = fold: the white (eye_) and the lash (lash_) fold shut via a\n // warp toward the shared crease — the one their member was built with\n // (eyelidFoldFor), so the grid that bounds them saw the same fold;\n // iris/pupil/highlight clip to the white, so the closing white CUTS them\n // away (round, not squashed) and the lash lands on top to cover the\n // seam. The white is a required role (clip mask exists).\n if (spec.eyeSide !== undefined) {\n if (role.startsWith(\"eye_\") || role.startsWith(\"lash_\")) {\n part.warps = [...member!.warps];\n } else {\n part.clip = { masks: [`eye_${spec.eyeSide}`] };\n }\n }\n return part;\n } else {\n // Static quad: no mesh, sized to the crop. It still takes bindings: no\n // static role carries any today, but this branch once dropped them\n // silently, and the next static role with a binding must not hit that.\n const part: IkiPart = {\n id: role,\n color: [1, 1, 1, 1] as [number, number, number, number],\n width: cropW,\n height: cropH,\n order: spec.order,\n transform: t,\n deformer: deformerId,\n };\n if (roleBindings.length > 0) {\n part.bindings = roleBindings;\n }\n return part;\n }\n });\n\n const model = {\n version: IKI_FORMAT_VERSION,\n name: \"Auto-Rigged Model\",\n canvas: { width: canvas.width, height: canvas.height },\n textures: [],\n parameters,\n deformers,\n parts,\n // Secondary motion: one spring lags AngleX onto HairSwayX so front hair\n // sways behind the head turn (same constants as the hand-authored sample),\n // and a second lags AngleZ onto HairSwayZ so it swings behind a tilt. Two\n // rigs because a rig has one input and one output (validator-enforced).\n // Both hair parts read the outputs through root-pinned sway warps.\n // Omitted when there is no front hair to drive.\n physics: hasHair\n ? [\n {\n id: \"hairSway\",\n input: { parameter: StandardParameter.AngleX, weight: 1 },\n output: { parameter: StandardParameter.HairSwayX, scale: -10 },\n mass: 1,\n stiffness: 80,\n damping: 10,\n },\n {\n id: \"hairTilt\",\n input: { parameter: StandardParameter.AngleZ, weight: 1 },\n output: { parameter: StandardParameter.HairSwayZ, scale: -10 },\n mass: 1,\n stiffness: 80,\n damping: 10,\n },\n ]\n : undefined,\n };\n\n // Gate: run through parseIkiModel so bad assembly fails loudly at the source.\n // structuredClone prevents the validator's normalizing output from aliasing\n // the local object, and ensures the returned model is fully independent.\n return parseIkiModel(structuredClone(model));\n}\n"],"mappings":"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","names":["parseIkiModel","parseIkiModel","y","x","IKI_FORMAT_VERSION","parseIkiModel","depths","p","IKI_FORMAT_VERSION","parseIkiModel"]}
1
+ {"version":3,"sources":["../src/document.ts","../src/mesh-uv.ts","../src/commands.ts","../src/grid-keyform.ts","../src/reparent.ts","../src/alpha-bbox.ts","../src/binding-capture.ts","../src/atlas.ts","../src/factories.ts","../src/auto-rig.ts"],"sourcesContent":["import { parseIkiModel } from \"@ikijs/format\";\nimport type {\n IkiBinding,\n IkiDeformer,\n IkiDeformerBinding,\n IkiDeformerTransform,\n IkiMatrixDeformer,\n IkiModel,\n IkiPart,\n IkiPhysics,\n IkiTexture,\n IkiTransform,\n IkiUvRect,\n IkiWarpDeformer,\n} from \"@ikijs/format\";\n\nimport type { EditCommand } from \"./commands\";\nimport { remapMeshUvsToRect } from \"./mesh-uv\";\n\n/**\n * One part mapped to an imported atlas source. `index` is always 0 because the\n * atlas is a single page; only the `uv` sub-rectangle varies per part.\n */\nexport interface AtlasAssignment {\n partId: string;\n uv: IkiUvRect;\n}\n\n/** Input to {@link EditorDocument.applyAtlas}: the new atlas table plus the\n * per-part UV assignments into it. */\nexport interface ApplyAtlasInput {\n textures: IkiTexture[];\n partTextureAssignments: AtlasAssignment[];\n}\n\n/**\n * In-memory editing session over a single {@link IkiModel}. The model is held\n * directly (no superset) and mutated in place by invertible {@link EditCommand}s\n * pushed through {@link execute}; undo/redo invert/re-apply them.\n *\n * The constructor `structuredClone`s the input so the caller's model is never\n * mutated. Parts are addressed by stable `id`, never by array index.\n */\nexport class EditorDocument {\n private readonly model: IkiModel;\n private readonly undoStack: EditCommand[] = [];\n private readonly redoStack: EditCommand[] = [];\n /** Editor-only session state, never serialized; keyed by stable part id. The\n * unmodified BASE local uvs of every mesh part, captured once at construction\n * so atlas remaps always derive from the original (idempotent). */\n private readonly baseMeshUvs = new Map<string, number[]>();\n\n constructor(model: IkiModel) {\n this.model = structuredClone(model);\n // Capture-once base UVs. Scope: assumes the loaded model's mesh parts carry\n // original LOCAL 0..1 uvs (true for the sample). Restoring base UVs after\n // reloading an already-textured exported model is out of scope (deferred —\n // needs project-file persistence).\n for (const part of this.model.parts) {\n if (part.mesh) {\n this.baseMeshUvs.set(part.id, part.mesh.uvs.slice());\n }\n }\n }\n\n /** Live reference to the working model — for READ access. Mutate it only\n * through {@link execute}/{@link undo}/{@link redo}. */\n getModel(): IkiModel {\n return this.model;\n }\n\n /** Resolve a part by stable id. Throws a plain `Error` (NOT an\n * `IkiFormatError`) with a path-qualified message if the id is unknown. */\n findPart(id: string): IkiPart {\n const part = this.model.parts.find((p) => p.id === id);\n if (!part) {\n throw new Error(`parts: no part with id \"${id}\"`);\n }\n return part;\n }\n\n /** Resolve a warp deformer by stable id. Throws a path-qualified plain\n * `Error` if no deformer matches the id or the match is not a warp deformer.\n * READ/mutate-through accessor, consistent with {@link findPart}. */\n findWarpDeformer(id: string): IkiWarpDeformer {\n const deformer = this.model.deformers?.find((d) => d.id === id);\n if (!deformer || deformer.kind !== \"warp\") {\n throw new Error(`deformers: no warp deformer with id \"${id}\"`);\n }\n return deformer;\n }\n\n /** Resolve a matrix deformer by stable id. Throws a path-qualified plain\n * `Error` if no deformer matches the id or the match is a warp deformer\n * (`kind === \"warp\"`). A `kind` of `\"matrix\"` or `undefined` is a matrix\n * deformer. READ/mutate-through accessor, consistent with {@link findPart}. */\n findMatrixDeformer(id: string): IkiMatrixDeformer {\n const deformer = this.model.deformers?.find((d) => d.id === id);\n if (!deformer || deformer.kind === \"warp\") {\n throw new Error(`deformers: no matrix deformer with id \"${id}\"`);\n }\n return deformer;\n }\n\n /** Resolve any deformer (matrix or warp) by stable id. Throws a\n * path-qualified plain `Error` if no deformer matches the id.\n * READ/mutate-through accessor, consistent with {@link findPart}. */\n findDeformer(id: string): IkiDeformer {\n const deformer = this.model.deformers?.find((d) => d.id === id);\n if (!deformer) {\n throw new Error(`deformers: no deformer with id \"${id}\"`);\n }\n return deformer;\n }\n\n /** Resolve a physics rig by stable id. Throws a path-qualified plain `Error`\n * if no rig matches the id. The `physics` array is optional, so guard it.\n * READ/mutate-through accessor, consistent with {@link findDeformer}. */\n findPhysicsRig(id: string): IkiPhysics {\n const rig = this.model.physics?.find((r) => r.id === id);\n if (!rig) {\n throw new Error(`physics: no physics rig with id \"${id}\"`);\n }\n return rig;\n }\n\n /**\n * Record the base mesh UVs for a part inserted AFTER construction (e.g. by\n * {@link AddPart}). Returns the PRIOR entry for that id (or `undefined` if\n * none existed) so the caller can restore it on undo. No-op for meshless\n * parts (returns `undefined`). Must be called by any command that pushes a\n * mesh part into the model so the part joins the constructor-captured base-UV\n * side state required by {@link applyAtlas}.\n *\n * The returned prior value is what {@link restoreBaseMeshUvs} expects on\n * undo — pass it verbatim. Hazard guarded: DeletePart(X) → AddPart(X′) →\n * undo(add) → undo(delete); without restore, X's constructor-captured entry\n * would be permanently gone after undo of the add, causing applyAtlas to\n * fail when X is restored.\n */\n captureBaseMeshUvs(partId: string): number[] | undefined {\n const prev = this.baseMeshUvs.get(partId);\n const part = this.model.parts.find((p) => p.id === partId);\n if (part?.mesh) {\n this.baseMeshUvs.set(partId, part.mesh.uvs.slice());\n }\n return prev;\n }\n\n /**\n * Restore the base-UV side state to exactly what it was before a\n * {@link captureBaseMeshUvs} call. Called from {@link AddPart.invert}:\n * pass the value returned by captureBaseMeshUvs on first apply.\n * - `prev` is `number[]` → sets the entry (restores a prior mesh's base).\n * - `prev` is `undefined` → deletes the entry (no entry existed before the\n * add, so a later different-mesh part reusing the id must not inherit this\n * one's base).\n * DeletePart does NOT call this — the entry persists across delete/undo so\n * the restored part still has its base available for applyAtlas.\n */\n restoreBaseMeshUvs(partId: string, prev: number[] | undefined): void {\n if (prev !== undefined) {\n this.baseMeshUvs.set(partId, prev);\n } else {\n this.baseMeshUvs.delete(partId);\n }\n }\n\n /** The construction-captured base UVs for a mesh part. Throws a path-qualified\n * plain `Error` if absent. SINGLE accessor for both apply branches — never\n * read `baseMeshUvs` with a bare `!` elsewhere. */\n private requireBaseUvs(partId: string): number[] {\n const base = this.baseMeshUvs.get(partId);\n if (!base) {\n throw new Error(`parts: no base mesh uvs captured for part \"${partId}\"`);\n }\n return base;\n }\n\n /**\n * Replace the atlas table and rewrite every part's texture reference in a\n * single atomic step. For every part in `partTextureAssignments` set\n * `texture = { index: 0, uv }`; CLEAR `texture` (delete the key) on every\n * other part.\n *\n * Mesh parts are textured as a MATCHED PAIR: an assigned mesh part also has\n * its per-vertex `mesh.uvs` remapped (from the construction-captured base)\n * into the same `uv` rect; an unassigned mesh part has its `mesh.uvs` restored\n * to that base. Quad parts carry `texture.uv` only and are untouched here.\n *\n * Deliberately NON-undoable: it does NOT push to or clear the undo/redo\n * stacks (texture/atlas state is not undoable in 5b — the unified\n * editor-state superset is deferred to 5d). `canUndo()`/`canRedo()` are\n * unchanged after a call.\n *\n * Validate-all-then-apply: structural input validation, per-partId\n * resolution, and a base-UV preflight over every mesh part all run BEFORE any\n * mutation, so a bad input (wrong shape, duplicate partId, unknown partId, or\n * a mesh part with no captured base) throws a plain `Error` and leaves the\n * model exactly as it was — never a partial application.\n */\n applyAtlas(input: ApplyAtlasInput): void {\n // Step A — structural validation of the input shape.\n const { texture, assignmentsByPart } = this.normalizeAtlasInput(input);\n\n // Step B — resolve every partId before mutating anything.\n const resolved = new Map<IkiPart, IkiUvRect>();\n for (const [partId, uv] of assignmentsByPart) {\n resolved.set(this.findPart(partId), uv);\n }\n\n // Step C — preflight base UVs for EVERY mesh part (assigned AND unassigned):\n // the mutate loop's clear/restore branch also reads the base of unassigned\n // mesh parts, so this guard must cover them before any write so atomicity\n // (validate-all-then-apply) holds.\n for (const part of this.model.parts) {\n if (part.mesh) {\n this.requireBaseUvs(part.id);\n }\n }\n\n // Mutate — only reached when A + B + C all pass.\n this.model.textures =\n texture === undefined ? undefined : [{ source: texture.source }];\n for (const part of this.model.parts) {\n const uv = resolved.get(part);\n if (uv) {\n part.texture = {\n index: 0,\n uv: { x: uv.x, y: uv.y, width: uv.width, height: uv.height },\n };\n if (part.mesh) {\n // Replace the mesh object so each part owns its own uvs array —\n // de-aliases parts that shared the same mesh object in the input model.\n part.mesh = {\n ...part.mesh,\n uvs: remapMeshUvsToRect(this.requireBaseUvs(part.id), uv),\n };\n }\n } else {\n delete part.texture;\n if (part.mesh) {\n // Same spread here to break aliasing on restore as well.\n part.mesh = {\n ...part.mesh,\n uvs: this.requireBaseUvs(part.id).slice(),\n };\n }\n }\n }\n }\n\n /**\n * Clear a model-committed texture reference from a single part. Deliberately\n * NON-undoable, matching {@link applyAtlas} — texture/atlas state is not in\n * the undo model (unified editor-state is deferred). Does NOT push to or\n * clear the undo/redo stacks. Does NOT touch `mesh.uvs` — atlas-space UVs on\n * an untextured mesh are inert for color rendering; a later atlas import\n * remaps from the base UVs anyway.\n *\n * This is the single consistent texture/atlas undo boundary: by the time a\n * part is deletable (no texture), its DeletePart snapshot carries no texture\n * reference, so no stale index can resurface on undo after a later atlas\n * repack.\n *\n * Throws a path-qualified plain `Error` (NOT `IkiFormatError`) if the part\n * id is unknown — same contract as {@link findPart}.\n */\n clearPartTextureRef(partId: string): void {\n const part = this.findPart(partId);\n delete part.texture;\n }\n\n /**\n * Validate the structural shape of an {@link ApplyAtlasInput} without\n * touching the model, returning a known-good `{ texture?, assignmentsByPart }`\n * for {@link applyAtlas} to resolve and apply.\n */\n private normalizeAtlasInput(input: ApplyAtlasInput): {\n texture?: IkiTexture;\n assignmentsByPart: Map<string, IkiUvRect>;\n } {\n if (input.textures.length > 1) {\n throw new Error(\n `applyAtlas: textures must be a single atlas page (got ${input.textures.length})`,\n );\n }\n if (\n input.partTextureAssignments.length > 0 &&\n input.textures.length !== 1\n ) {\n throw new Error(\n \"applyAtlas: partTextureAssignments require exactly one texture\",\n );\n }\n\n const assignmentsByPart = new Map<string, IkiUvRect>();\n for (const { partId, uv } of input.partTextureAssignments) {\n if (assignmentsByPart.has(partId)) {\n throw new Error(\n `applyAtlas: duplicate partId \"${partId}\" in partTextureAssignments`,\n );\n }\n assignmentsByPart.set(partId, uv);\n }\n\n return { texture: input.textures[0], assignmentsByPart };\n }\n\n /**\n * Overwrite a part's whole transform with a fresh copy of `transform`.\n * Replacing the whole object (rather than individual channels) preserves any\n * optional keys already absent from the incoming value.\n *\n * Deliberately NON-undoable — used ONLY for an editor app's transient capture\n * pose. Does NOT push to or clear undoStack/redoStack (sibling to\n * {@link applyAtlas}'s non-undoable boundary). The caller is responsible for\n * restoring the exact prior snapshot when the capture pose ends.\n *\n * `IkiTransform` is a flat number map, so a shallow spread is a sufficient\n * deep copy — no aliasing of the caller's object remains.\n */\n setPartTransformEphemeral(partId: string, transform: IkiTransform): void {\n const part = this.findPart(partId);\n part.transform = { ...transform };\n }\n\n /**\n * Overwrite a matrix deformer's optional transform with a fresh copy, or\n * delete it when `transform` is `undefined`.\n * `undefined` deletes the key, restoring the deformer to the same state as\n * one that never had a transform (absent-vs-present matters for downstream\n * renderers).\n *\n * Deliberately NON-undoable — used ONLY for an editor app's transient capture\n * pose. Does NOT push to or clear undoStack/redoStack (sibling to\n * {@link applyAtlas}'s non-undoable boundary). The caller is responsible for\n * restoring the exact prior snapshot when the capture pose ends.\n *\n * `IkiDeformerTransform` is a flat number map, so a shallow spread is a\n * sufficient deep copy — no aliasing of the caller's object remains.\n */\n setDeformerTransformEphemeral(\n deformerId: string,\n transform: IkiDeformerTransform | undefined,\n ): void {\n const deformer = this.findMatrixDeformer(deformerId);\n if (transform === undefined) {\n delete deformer.transform;\n } else {\n deformer.transform = { ...transform };\n }\n }\n\n /**\n * Overwrite a part's whole bindings array with a fresh deep copy, or delete\n * the key when `bindings` is empty.\n *\n * Deliberately NON-undoable — used ONLY for an editor app's transient capture\n * neutralization (zeroing the row being recaptured so the preview reflects\n * base-only during posing). Does NOT push to or clear undoStack/redoStack.\n * The caller is responsible for restoring the original bindings when the\n * capture session ends.\n */\n setPartBindingsEphemeral(partId: string, bindings: IkiBinding[]): void {\n const part = this.findPart(partId);\n if (bindings.length > 0) {\n part.bindings = bindings.map((b) => ({ ...b }));\n } else {\n delete part.bindings;\n }\n }\n\n /**\n * Overwrite a matrix deformer's whole bindings array with a fresh deep copy,\n * or delete the key when `bindings` is empty.\n *\n * Deliberately NON-undoable — used ONLY for an editor app's transient capture\n * neutralization (zeroing the row being recaptured so the preview reflects\n * base-only during posing). Does NOT push to or clear undoStack/redoStack.\n * The caller is responsible for restoring the original bindings when the\n * capture session ends.\n */\n setDeformerBindingsEphemeral(\n deformerId: string,\n bindings: IkiDeformerBinding[],\n ): void {\n const deformer = this.findMatrixDeformer(deformerId);\n if (bindings.length > 0) {\n deformer.bindings = bindings.map((b) => ({ ...b }));\n } else {\n delete deformer.bindings;\n }\n }\n\n /** Apply a command and record it as one undo step. Clears the redo stack. */\n execute(cmd: EditCommand): void {\n cmd.apply(this);\n this.undoStack.push(cmd);\n this.redoStack.length = 0;\n }\n\n /** Invert the most recent command and move it onto the redo stack. */\n undo(): void {\n const cmd = this.undoStack.pop();\n if (!cmd) return;\n cmd.invert(this);\n this.redoStack.push(cmd);\n }\n\n /** Re-apply the most recently undone command and move it back onto undo. */\n redo(): void {\n const cmd = this.redoStack.pop();\n if (!cmd) return;\n cmd.apply(this);\n this.undoStack.push(cmd);\n }\n\n canUndo(): boolean {\n return this.undoStack.length > 0;\n }\n\n canRedo(): boolean {\n return this.redoStack.length > 0;\n }\n\n /**\n * Validate and export the current working model by running it through\n * {@link parseIkiModel}. Uses `structuredClone` so the validator's\n * normalized output cannot alias the working model. Propagates\n * `IkiFormatError` unchanged on failure — callers surface `.message`.\n */\n toIkiModel(): IkiModel {\n return parseIkiModel(structuredClone(this.model));\n }\n\n /**\n * Pretty-print the validated model as a `.iki` JSON string. Always\n * validates first — invalid documents never reach a file.\n */\n serialize(): string {\n return JSON.stringify(this.toIkiModel(), null, 2);\n }\n}\n","import type { IkiUvRect } from \"@ikijs/format\";\n\n/**\n * Affinely map a mesh's BASE local UVs into an atlas sub-rectangle.\n *\n * Per the `@ikijs/format` UV convention (top-left origin, +y down, 0..1; see\n * {@link IkiUvRect}/{@link IkiMesh}), the input is the part's BASE local uvs and\n * the output places each component into `rect` with NO flip — base UV space and\n * `rect` share the same orientation:\n * out[2i] = rect.x + baseUvs[2i] * rect.width\n * out[2i+1] = rect.y + baseUvs[2i+1] * rect.height\n *\n * The caller guarantees `rect` is inset/clamped (via `uvRectFor`) so every\n * output component stays within 0..1 and passes the format validator.\n *\n * Returns a fresh array; `baseUvs` is never mutated.\n */\nexport function remapMeshUvsToRect(\n baseUvs: number[],\n rect: IkiUvRect,\n): number[] {\n if (baseUvs.length % 2 !== 0) {\n throw new Error(\n `remapMeshUvsToRect: baseUvs must have an even length (u,v pairs), got ${baseUvs.length}`,\n );\n }\n\n const out = new Array<number>(baseUvs.length);\n for (let i = 0; i < baseUvs.length; i += 2) {\n out[i] = rect.x + baseUvs[i] * rect.width;\n out[i + 1] = rect.y + baseUvs[i + 1] * rect.height;\n }\n return out;\n}\n","import type {\n IkiBinding,\n IkiDeformer,\n IkiDeformerBinding,\n IkiDeformerTransform,\n IkiGridKeyform,\n IkiMatrixDeformer,\n IkiMesh,\n IkiModel,\n IkiPart,\n IkiPhysics,\n} from \"@ikijs/format\";\nimport {\n IKI_FORMAT_VERSION,\n IkiFormatError,\n parseIkiModel,\n} from \"@ikijs/format\";\n\nimport type { EditorDocument } from \"./document\";\nimport { upsertGridKeyform } from \"./grid-keyform\";\nimport { remapMeshUvsToRect } from \"./mesh-uv\";\nimport {\n validateDeformerDelete,\n validateDeformerReparent,\n validatePartAttach,\n} from \"./reparent\";\n\n/**\n * Scan the id-flat namespace (parts first, then deformers) and return which\n * array already holds `id`, or `undefined` if the id is free. Parts and\n * deformers share a single flat id namespace, so both arrays must be checked\n * to produce a source-qualified collision message.\n */\nfunction findIdCollision(\n model: IkiModel,\n id: string,\n): \"part\" | \"deformer\" | undefined {\n for (const p of model.parts) {\n if (p.id === id) return \"part\";\n }\n for (const d of model.deformers ?? []) {\n if (d.id === id) return \"deformer\";\n }\n return undefined;\n}\n\n/**\n * One invertible edit. The document is passed IN at apply/invert time — a\n * command is constructed from `(partId, value)` alone, before any document\n * exists — so the same command object can be applied, inverted, and re-applied\n * by the undo/redo stack.\n *\n * Prior-value capture happens exactly ONCE, on the first {@link apply}. `redo`\n * (a second `apply`) reuses that captured value rather than re-reading the\n * current field, so undo always restores the original target.\n */\nexport interface EditCommand {\n apply(doc: EditorDocument): void;\n invert(doc: EditorDocument): void;\n readonly label: string;\n}\n\n/** Channels of {@link IkiTransform} this editor can edit (object-field names,\n * NOT the binding `IkiTransformChannel` vocabulary). */\nexport type EditTransformChannel =\n | \"x\"\n | \"y\"\n | \"rotation\"\n | \"scaleX\"\n | \"scaleY\"\n | \"opacity\";\n\n/**\n * Generic single-field command: reads/writes one field of the resolved part via\n * a getter/setter closure, capturing the prior value on the first `apply` and\n * restoring it on `invert`. `T` is the captured value's type; for cloned values\n * (the color tuple) the getter/setter perform the copy.\n */\nclass FieldCommand<T> implements EditCommand {\n readonly label: string;\n private captured = false;\n private prevValue!: T;\n\n constructor(\n private readonly partId: string,\n private readonly newValue: T,\n label: string,\n private readonly get: (part: IkiPart) => T,\n private readonly set: (part: IkiPart, value: T) => void,\n ) {\n this.label = label;\n }\n\n apply(doc: EditorDocument): void {\n const part = doc.findPart(this.partId);\n if (!this.captured) {\n this.prevValue = this.get(part);\n this.captured = true;\n }\n this.set(part, this.newValue);\n }\n\n invert(doc: EditorDocument): void {\n const part = doc.findPart(this.partId);\n this.set(part, this.prevValue);\n }\n}\n\n/** Edit a part's RGBA fill. The 4-tuple is mutable, so the command clones on\n * construction (caller's array), on capture (part's current color), and on\n * assign (writing to the part) — it never retains the caller's or model's\n * array by reference. */\nexport class SetPartColor extends FieldCommand<\n [number, number, number, number]\n> {\n constructor(partId: string, rgba: [number, number, number, number]) {\n super(\n partId,\n [...rgba] as [number, number, number, number],\n \"Set color\",\n (part) => [...part.color],\n (part, value) => {\n part.color = [...value] as [number, number, number, number];\n },\n );\n }\n}\n\n/** Edit a part's width (model-space units). */\nexport class SetPartWidth extends FieldCommand<number> {\n constructor(partId: string, value: number) {\n super(\n partId,\n value,\n \"Set width\",\n (part) => part.width,\n (part, v) => {\n part.width = v;\n },\n );\n }\n}\n\n/** Edit a part's height (model-space units). */\nexport class SetPartHeight extends FieldCommand<number> {\n constructor(partId: string, value: number) {\n super(\n partId,\n value,\n \"Set height\",\n (part) => part.height,\n (part, v) => {\n part.height = v;\n },\n );\n }\n}\n\n/** Edit a part's paint order. */\nexport class SetPartOrder extends FieldCommand<number> {\n constructor(partId: string, value: number) {\n super(\n partId,\n value,\n \"Set order\",\n (part) => part.order,\n (part, v) => {\n part.order = v;\n },\n );\n }\n}\n\n/**\n * Edit one channel of a part's base transform. `x`/`y` are required; the rest\n * are optional and may be absent on the part. For an optional channel the\n * command captures the raw current value INCLUDING `undefined`, and restoring\n * `undefined` DELETES the key so undo returns the part to its original\n * (possibly-omitted) shape. Engine defaults (rotation 0 / scale 1 / opacity 1)\n * are NOT substituted here.\n */\nexport class SetPartTransform extends FieldCommand<number | undefined> {\n constructor(partId: string, channel: EditTransformChannel, value: number) {\n super(\n partId,\n value,\n \"Set transform\",\n (part) => part.transform[channel],\n (part, v) => {\n if (v === undefined) {\n delete part.transform[channel];\n } else {\n part.transform[channel] = v;\n }\n },\n );\n }\n}\n\n/**\n * Capture the warp deformer's grid as one keyform at the driving parameter\n * `value`, upserting `offsets` into `warps[0].keyforms`. The 4-tuple-style\n * mutable `offsets` array is cloned on construction so a later caller mutation\n * cannot corrupt apply/redo (mirrors {@link SetPartColor}).\n *\n * `apply` validates BEFORE mutating: the deformer must have a grid warp, the\n * offsets length must equal `grid.points.length`, and `value` must lie within\n * the driving parameter's declared `[min,max]` (fail fast, before\n * `parseIkiModel` would reject it). Prior keyforms are deep-copied once on the\n * first `apply` (capture-once like {@link FieldCommand}); `invert` restores\n * that deep copy so re-apply after undo never aliases.\n */\nexport class CaptureGridKeyform implements EditCommand {\n readonly label = \"Capture grid keyform\";\n private readonly offsets: number[];\n private captured = false;\n private prevKeyforms!: IkiGridKeyform[];\n\n constructor(\n private readonly deformerId: string,\n private readonly value: number,\n offsets: number[],\n ) {\n this.offsets = [...offsets];\n }\n\n apply(doc: EditorDocument): void {\n const deformer = doc.findWarpDeformer(this.deformerId);\n const warp = deformer.warps?.[0];\n if (!warp) {\n throw new Error(\n `deformers.\"${this.deformerId}\".warps: no grid warp to capture into`,\n );\n }\n if (this.offsets.length !== deformer.grid.points.length) {\n throw new Error(\n `deformers.\"${this.deformerId}\".warps[0].keyforms.offsets length ${this.offsets.length} must equal grid.points length ${deformer.grid.points.length}`,\n );\n }\n const param = doc\n .getModel()\n .parameters.find((p) => p.id === warp.parameter);\n if (!param) {\n throw new Error(\n `deformers.\"${this.deformerId}\".warps[0].parameter \"${warp.parameter}\" is not a declared parameter`,\n );\n }\n if (this.value < param.min || this.value > param.max) {\n throw new Error(\n `deformers.\"${this.deformerId}\".warps[0].keyforms.value ${this.value} is outside parameter \"${warp.parameter}\" range [${param.min},${param.max}]`,\n );\n }\n\n if (!this.captured) {\n this.prevKeyforms = structuredClone(warp.keyforms);\n this.captured = true;\n }\n warp.keyforms = upsertGridKeyform(warp.keyforms, this.value, [\n ...this.offsets,\n ]);\n }\n\n invert(doc: EditorDocument): void {\n const warp = doc.findWarpDeformer(this.deformerId).warps?.[0];\n if (!warp) {\n throw new Error(\n `deformers.\"${this.deformerId}\".warps: no grid warp to restore into`,\n );\n }\n warp.keyforms = structuredClone(this.prevKeyforms);\n }\n}\n\n/**\n * Generic single-field command targeting a matrix deformer, mirroring\n * {@link FieldCommand} but resolving via `doc.findMatrixDeformer` instead of\n * `doc.findPart`. Capture-once on first `apply`; restore on `invert`.\n */\nclass DeformerFieldCommand<T> implements EditCommand {\n readonly label: string;\n private captured = false;\n private prevValue!: T;\n\n constructor(\n private readonly deformerId: string,\n private readonly newValue: T,\n label: string,\n private readonly get: (deformer: IkiMatrixDeformer) => T,\n private readonly set: (deformer: IkiMatrixDeformer, value: T) => void,\n ) {\n this.label = label;\n }\n\n apply(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n if (!this.captured) {\n this.prevValue = this.get(deformer);\n this.captured = true;\n }\n this.set(deformer, this.newValue);\n }\n\n invert(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n this.set(deformer, this.prevValue);\n }\n}\n\n/** Edit a matrix deformer's pivot x. */\nexport class SetDeformerPivotX extends DeformerFieldCommand<number> {\n constructor(deformerId: string, value: number) {\n super(\n deformerId,\n value,\n \"Set pivot x\",\n (d) => d.pivot.x,\n (d, v) => {\n d.pivot.x = v;\n },\n );\n }\n}\n\n/** Edit a matrix deformer's pivot y. */\nexport class SetDeformerPivotY extends DeformerFieldCommand<number> {\n constructor(deformerId: string, value: number) {\n super(\n deformerId,\n value,\n \"Set pivot y\",\n (d) => d.pivot.y,\n (d, v) => {\n d.pivot.y = v;\n },\n );\n }\n}\n\n/**\n * Set a matrix deformer's pivot x and y atomically (one drag = one undo step).\n * {@link SetDeformerPivotX} and {@link SetDeformerPivotY} remain for the\n * Inspector's single-axis number inputs.\n */\nexport class SetDeformerPivot implements EditCommand {\n readonly label = \"Set pivot\";\n private captured = false;\n private prevPivot!: { x: number; y: number };\n private readonly pivot: { x: number; y: number };\n\n constructor(\n private readonly deformerId: string,\n pivot: { x: number; y: number },\n ) {\n // Fresh clone so a caller mutating their arg after construction cannot\n // corrupt apply/redo.\n this.pivot = { x: pivot.x, y: pivot.y };\n }\n\n apply(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n if (!this.captured) {\n this.prevPivot = { x: deformer.pivot.x, y: deformer.pivot.y };\n this.captured = true;\n }\n deformer.pivot = { x: this.pivot.x, y: this.pivot.y };\n }\n\n invert(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n // Fresh clone — never alias the captured object so repeated undo/redo\n // cycles cannot corrupt the saved prior value.\n deformer.pivot = { x: this.prevPivot.x, y: this.prevPivot.y };\n }\n}\n\n/** Channels of {@link IkiDeformerTransform} this editor can edit. */\nexport type DeformerTransformChannel =\n | \"x\"\n | \"y\"\n | \"rotation\"\n | \"scaleX\"\n | \"scaleY\";\n\n/**\n * Edit one channel of a matrix deformer's base transform. Because\n * {@link IkiDeformerTransform} REQUIRES finite `x` and `y`, this command\n * captures and restores the WHOLE prior `transform` object (present or absent)\n * rather than a single channel, so undo can delete the transform when it did\n * not previously exist, and redo never produces a partial object missing `x`/`y`.\n *\n * When no `transform` is present on the deformer, `apply` creates one from\n * the identity base `{ x: 0, y: 0 }` — the minimal valid shape the validator\n * accepts — then writes the edited channel. For example, editing `rotation`\n * on a transform-less deformer yields `{ x: 0, y: 0, rotation: <value> }`.\n */\nexport class SetDeformerTransform implements EditCommand {\n readonly label = \"Set deformer transform\";\n private captured = false;\n private prevTransform!: IkiDeformerTransform | undefined;\n\n constructor(\n private readonly deformerId: string,\n private readonly channel: DeformerTransformChannel,\n private readonly value: number,\n ) {}\n\n apply(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n if (!this.captured) {\n // Shallow clone is sufficient — IkiDeformerTransform is a flat number map.\n this.prevTransform =\n deformer.transform === undefined\n ? undefined\n : { ...deformer.transform };\n this.captured = true;\n }\n // Start from the existing transform or the identity base. The identity base\n // is { x: 0, y: 0 } because the validator unconditionally requires finite\n // x and y whenever a transform object is present.\n const next: IkiDeformerTransform = {\n ...(deformer.transform ?? { x: 0, y: 0 }),\n };\n next[this.channel] = this.value;\n deformer.transform = next;\n }\n\n invert(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n if (this.prevTransform === undefined) {\n delete deformer.transform;\n } else {\n // Assign a fresh clone — never alias the captured object so repeated\n // undo/redo cycles cannot corrupt the saved prior value.\n deformer.transform = { ...this.prevTransform };\n }\n }\n}\n\n/**\n * Replace a matrix deformer's `bindings` array wholesale. A single command\n * covers add, edit, and remove (pass the desired final array; pass `[]` to\n * remove all). Mirrors {@link CaptureGridKeyform}'s deep-copy discipline:\n * clone-on-construction, capture-once, fresh deep copy on invert.\n *\n * Each {@link IkiDeformerBinding} is a flat object so a per-element spread\n * `{ ...b }` is a sufficient deep copy.\n */\nexport class SetDeformerBindings implements EditCommand {\n readonly label = \"Set deformer bindings\";\n private readonly bindings: IkiDeformerBinding[];\n private captured = false;\n private prevBindings!: IkiDeformerBinding[] | undefined;\n\n constructor(\n private readonly deformerId: string,\n bindings: IkiDeformerBinding[],\n ) {\n // Clone the caller's array on construction — prevents post-execute mutation\n // of the caller's array from corrupting apply/redo.\n this.bindings = bindings.map((b) => ({ ...b }));\n }\n\n apply(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n\n // Validate a NARROW synthetic candidate before capture/mutate, exactly as\n // {@link SetPartBindings} does and for the same reason: a full\n // structuredClone(doc.getModel()) would also validate parts and deformers\n // the user is not editing, and those routinely hold in-flight `NaN` from an\n // emptied numeric input — so an unrelated blank field would refuse this\n // edit, and report it against the wrong object. The Inspector offers every\n // parameter in the model, so an undeclared id, a non-finite endpoint, or a\n // non-matrix channel must still be caught here.\n //\n // NOT covered by this shape: a binding that feeds a physics chain's own\n // anchor. That rule needs the whole deformer hierarchy, and reproducing it\n // over a sanitized model would mean hand-maintaining a second model shape\n // beside the validator. It stays an export-time check in `toIkiModel()`.\n // Parts and deformers share one flat id namespace, so the two synthetic\n // objects below must not both be \"_\".\n const candidateDeformer: Record<string, unknown> = {\n kind: \"matrix\",\n id: \"_d\",\n pivot: { x: 0, y: 0 },\n };\n if (this.bindings.length > 0) {\n candidateDeformer.bindings = this.bindings.map((b) => ({ ...b }));\n }\n const candidate = {\n version: IKI_FORMAT_VERSION,\n name: \"_\",\n canvas: { width: 1, height: 1 },\n parameters: doc.getModel().parameters,\n parts: [\n {\n id: \"_\",\n color: [0, 0, 0, 1],\n width: 1,\n height: 1,\n transform: { x: 0, y: 0 },\n order: 0,\n },\n ],\n deformers: [candidateDeformer],\n };\n // The synthetic deformer is always at deformers[0]; rewrite that prefix so\n // the surfaced error names the real target.\n try {\n parseIkiModel(candidate);\n } catch (e) {\n if (e instanceof IkiFormatError) {\n throw new IkiFormatError(\n e.message.replace(\n /^deformers\\[0\\]/,\n `deformers.\"${this.deformerId}\"`,\n ),\n );\n }\n throw e;\n }\n\n if (!this.captured) {\n // Preserve the original absent-vs-empty distinction.\n this.prevBindings =\n deformer.bindings === undefined\n ? undefined\n : deformer.bindings.map((b) => ({ ...b }));\n this.captured = true;\n }\n if (this.bindings.length > 0) {\n // Assign a fresh deep copy — model must never alias the command's stored array.\n deformer.bindings = this.bindings.map((b) => ({ ...b }));\n } else {\n // Delete the key on empty: keeps the model shape minimal and correctly\n // represents \"no bindings\" as an absent key rather than an empty array.\n delete deformer.bindings;\n }\n }\n\n invert(doc: EditorDocument): void {\n const deformer = doc.findMatrixDeformer(this.deformerId);\n if (this.prevBindings === undefined) {\n delete deformer.bindings;\n } else {\n // Assign a fresh deep copy — never alias the captured array so re-apply\n // after undo cannot corrupt the saved prior value.\n deformer.bindings = this.prevBindings.map((b) => ({ ...b }));\n }\n }\n}\n\n/**\n * Reparent a deformer (matrix or warp) under a new parent, or promote it to\n * root (`newParentId === undefined`). Calls {@link validateDeformerReparent}\n * FIRST so invalid reparents (cycles, warp parent, unknown id) throw before any\n * capture or mutation — a throwing apply leaves the model and undo stack\n * untouched.\n *\n * Absent-vs-present distinction: captures both the prior `parent` value AND\n * whether the key was present on the object, so `invert` can delete the key\n * (restore \"absent\") rather than blindly assigning `undefined`.\n */\nexport class SetDeformerParent implements EditCommand {\n readonly label = \"Set deformer parent\";\n private captured = false;\n private prevParent: string | undefined = undefined;\n private prevHadParent = false;\n\n constructor(\n private readonly deformerId: string,\n private readonly newParentId: string | undefined,\n ) {}\n\n apply(doc: EditorDocument): void {\n // Validate FIRST — throws before capture/mutate on any violation.\n validateDeformerReparent(\n doc.getModel().deformers ?? [],\n this.deformerId,\n this.newParentId,\n );\n\n // A new ANCESTOR can still bind a parameter that a physics chain anchored\n // here emits, which the format rejects as feedback. That rule needs the\n // whole hierarchy, and validating a full clone of the document here refuses\n // the edit whenever any unrelated part or deformer holds in-flight `NaN`\n // from an emptied numeric input — reporting it against the wrong object.\n // So the feedback case stays an export-time check in `toIkiModel()`, where\n // it was before, rather than trading a rare corruption for a common block.\n //\n // Known cost of that trade: the host surfaces the export error (the editor\n // app re-exports on a ~200ms debounce), but the message names the CHAIN, not\n // the edit — reparent `headDeformer` and you get\n // `physicsChains[0].segments[1].output.parameter \"...\" feeds its own anchor\n // deformer chain (feedback)`, which reads as a complaint about a hair lock.\n // If a friendlier error surface ever lands, this is the message to special-case.\n const deformer = doc.findDeformer(this.deformerId);\n if (!this.captured) {\n this.prevHadParent = Object.prototype.hasOwnProperty.call(\n deformer,\n \"parent\",\n );\n this.prevParent = deformer.parent;\n this.captured = true;\n }\n if (this.newParentId !== undefined) {\n deformer.parent = this.newParentId;\n } else {\n delete deformer.parent;\n }\n }\n\n invert(doc: EditorDocument): void {\n const deformer = doc.findDeformer(this.deformerId);\n if (this.prevHadParent) {\n deformer.parent = this.prevParent;\n } else {\n delete deformer.parent;\n }\n }\n}\n\n/**\n * Add a new part to the model. Validates the candidate model with\n * {@link parseIkiModel} BEFORE mutating, so a structurally invalid part\n * (bad color tuple, missing required fields, id collision with a deformer)\n * throws an `IkiFormatError` and leaves the model untouched.\n *\n * Captures the prior base-UV entry for the part's id on the FIRST apply so\n * invert can restore the exact pre-add state. This guards the id-reuse hazard:\n * DeletePart(X) → AddPart(X′, different mesh) → undo(add) → undo(delete) —\n * without restore, X's constructor-captured base would be gone, causing\n * applyAtlas to fail when X is restored by the undo of the delete.\n */\nexport class AddPart implements EditCommand {\n readonly label = \"Add part\";\n private readonly part: IkiPart;\n private captured = false;\n private prevBaseMeshUvs: number[] | undefined = undefined;\n\n constructor(part: IkiPart) {\n // Clone on construction — prevents caller mutation from corrupting apply/redo.\n this.part = structuredClone(part);\n }\n\n apply(doc: EditorDocument): void {\n // (a) Cheap id-uniqueness pre-check with a source-qualified message.\n const hit = findIdCollision(doc.getModel(), this.part.id);\n if (hit === \"part\") {\n throw new Error(\n `parts: id \"${this.part.id}\" collides with an existing part id`,\n );\n }\n if (hit === \"deformer\") {\n throw new Error(\n `parts: id \"${this.part.id}\" collides with an existing deformer id`,\n );\n }\n\n // (b) Full structural validation on a candidate clone — propagate\n // IkiFormatError unchanged so the caller gets a path-qualified message.\n const candidate = structuredClone(doc.getModel());\n candidate.parts.push(structuredClone(this.part));\n parseIkiModel(candidate);\n\n // (c) All checks pass — mutate the real model with a fresh clone so the\n // model never aliases the command's stored part.\n doc.getModel().parts.push(structuredClone(this.part));\n\n // Register base mesh UVs so applyAtlas can remap this part. Capture the\n // prior entry on the FIRST apply only — redo must NOT re-capture or it\n // would clobber the saved prior and make invert unable to restore correctly.\n const prev = doc.captureBaseMeshUvs(this.part.id);\n if (!this.captured) {\n this.prevBaseMeshUvs = prev;\n this.captured = true;\n }\n }\n\n invert(doc: EditorDocument): void {\n const parts = doc.getModel().parts;\n const i = parts.findIndex((p) => p.id === this.part.id);\n if (i !== -1) parts.splice(i, 1);\n // Restore the exact prior base-UV state so a constructor-captured entry for\n // a deleted part with this id survives the undo of the add.\n doc.restoreBaseMeshUvs(this.part.id, this.prevBaseMeshUvs);\n }\n}\n\n/**\n * Add a new deformer (matrix or warp) to the model. Validates the candidate\n * model with {@link parseIkiModel} BEFORE mutating — this enforces the warp\n * rest-grid invariant, points length, pivot, parent, and bindings without\n * hand-rolling partial checks. Mirrors {@link AddPart} but also tracks whether\n * `model.deformers` was absent before the first apply, so `invert` can restore\n * the exact key-absence state (mirrors {@link SetDeformerBindings}).\n */\nexport class AddDeformer implements EditCommand {\n readonly label = \"Add deformer\";\n private readonly deformer: IkiDeformer;\n private captured = false;\n private prevDeformersAbsent = false;\n\n constructor(deformer: IkiDeformer) {\n // Clone on construction — prevents caller mutation from corrupting apply/redo.\n this.deformer = structuredClone(deformer);\n }\n\n apply(doc: EditorDocument): void {\n const model = doc.getModel();\n\n // (a) Cheap id-uniqueness pre-check with a source-qualified message.\n const hit = findIdCollision(model, this.deformer.id);\n if (hit === \"deformer\") {\n throw new Error(\n `deformers: id \"${this.deformer.id}\" collides with an existing deformer id`,\n );\n }\n if (hit === \"part\") {\n throw new Error(\n `deformers: id \"${this.deformer.id}\" collides with an existing part id`,\n );\n }\n\n // (b) Full structural validation on a candidate clone.\n const candidate = structuredClone(model);\n candidate.deformers = [\n ...(candidate.deformers ?? []),\n structuredClone(this.deformer),\n ];\n parseIkiModel(candidate);\n\n // (c) All checks pass — capture-once, then mutate.\n if (!this.captured) {\n this.prevDeformersAbsent = model.deformers === undefined;\n this.captured = true;\n }\n if (model.deformers === undefined) {\n model.deformers = [];\n }\n model.deformers.push(structuredClone(this.deformer));\n }\n\n invert(doc: EditorDocument): void {\n const model = doc.getModel();\n const arr = model.deformers;\n if (!arr) return;\n const i = arr.findIndex((d) => d.id === this.deformer.id);\n if (i !== -1) arr.splice(i, 1);\n // Restore the absent-vs-present distinction. If deformers did not exist\n // before apply, delete the key once the array is empty again.\n if (this.prevDeformersAbsent && arr.length === 0) {\n delete model.deformers;\n }\n }\n}\n\n/**\n * Delete a part by id, preserving its original array slot so `invert` restores\n * it at the same position. Slot position is cosmetic (the renderer uses the\n * `order` field for paint ordering), but restoring the index keeps undo\n * visually predictable.\n *\n * No `parseIkiModel` pre-check is needed: removing an element from an already-\n * valid model cannot introduce a structural violation — EXCEPT for clip-mask\n * references, the one part→part reference in the contract. `apply` refuses to\n * delete a part still used as another part's `clip.masks` entry (guard below),\n * so it can never leave a dangling mask ref that would fail `toIkiModel()`.\n *\n * Texture-reference safety (package invariant): `apply` refuses to delete a\n * part that still carries `part.texture`. Texture/atlas state is non-undoable\n * per the 5b boundary; clear the texture first via\n * {@link EditorDocument.clearPartTextureRef} (model-committed) or\n * {@link EditorDocument.applyAtlas} with no assignment (imported) — both are\n * non-undoable. By the time a part is deletable it carries no texture, so\n * `invert` can never restore a stale texture index that would render the wrong\n * atlas region after a later atlas repack. No transactional atlas capture is\n * needed in this command.\n */\nexport class DeletePart implements EditCommand {\n readonly label = \"Delete part\";\n private captured = false;\n private removed!: IkiPart;\n private index!: number;\n\n constructor(private readonly partId: string) {}\n\n apply(doc: EditorDocument): void {\n // Validate FIRST — throws with path-qualified message if unknown.\n const part = doc.findPart(this.partId);\n\n // Texture guard — enforced at the `@ikijs/editor` boundary so public callers\n // cannot bypass it (the example store adds a friendly pre-check, but this\n // is the real invariant). Throw before any capture or mutation.\n if (part.texture !== undefined) {\n throw new Error(\n `parts.\"${this.partId}\": cannot delete — part has a texture reference; clear its texture first`,\n );\n }\n\n const parts = doc.getModel().parts;\n\n // Clip-mask guard — `clip.masks` is the one part→part reference in the model\n // contract. Deleting a referenced mask would leave a dangling ref that fails\n // parseIkiModel on toIkiModel(). Refuse rather than corrupt (mirrors the\n // texture guard above). Throw before any capture or mutation.\n const masker = parts.find(\n (p) => p.id !== this.partId && p.clip?.masks.includes(this.partId),\n );\n if (masker) {\n throw new Error(\n `parts.\"${this.partId}\": cannot delete — used as a clip mask by part \"${masker.id}\"; remove its clip first`,\n );\n }\n\n const i = parts.indexOf(part);\n if (!this.captured) {\n this.removed = structuredClone(part);\n this.index = i;\n this.captured = true;\n }\n parts.splice(i, 1);\n }\n\n invert(doc: EditorDocument): void {\n // Restore at the original slot — exact deep restore including bindings and\n // mesh. No texture key is present (apply enforced that invariant before\n // capture), so the snapshot is always atlas-safe on restore.\n doc.getModel().parts.splice(this.index, 0, structuredClone(this.removed));\n }\n}\n\n/**\n * Delete a deformer by id. Calls {@link validateDeformerDelete} FIRST so the\n * delete is refused while anything still references it — a child deformer, an\n * attached part, or a physics chain anchored to it — enforcing the same\n * referential safety as {@link SetDeformerParent} and {@link SetPartDeformer}.\n *\n * `invert` re-inserts the deformer at its original index.\n */\nexport class DeleteDeformer implements EditCommand {\n readonly label = \"Delete deformer\";\n private captured = false;\n private removed!: IkiDeformer;\n private index!: number;\n\n constructor(private readonly deformerId: string) {}\n\n apply(doc: EditorDocument): void {\n const model = doc.getModel();\n // Validate FIRST — throws before capture/mutate on any referential violation.\n validateDeformerDelete(\n model.deformers ?? [],\n model.parts,\n model.physicsChains ?? [],\n this.deformerId,\n );\n // validateDeformerDelete guarantees the deformer (and thus the array) exists.\n const arr = model.deformers!;\n const i = arr.findIndex((d) => d.id === this.deformerId);\n if (!this.captured) {\n this.removed = structuredClone(arr[i]);\n this.index = i;\n this.captured = true;\n }\n arr.splice(i, 1);\n }\n\n invert(doc: EditorDocument): void {\n // Re-insert at the original slot. `apply` only captures after\n // validateDeformerDelete passed, so the array it spliced out of is present:\n // fabricating one here would hide a broken undo stack and silently restore\n // just this node instead of surfacing the loss of the rest of the hierarchy.\n doc\n .getModel()\n .deformers!.splice(this.index, 0, structuredClone(this.removed));\n }\n}\n\n/**\n * Replace a part's `bindings` array wholesale. A single command covers add,\n * edit, and remove (pass the desired final array; pass `[]` to remove all).\n * Mirrors {@link SetDeformerBindings}'s deep-copy and absent-vs-empty discipline:\n * clone-on-construction, capture-once, fresh deep copy on every assign/invert.\n *\n * Each {@link IkiBinding} is a flat object, so a per-element spread `{ ...b }`\n * is a sufficient deep copy.\n *\n * Validates the WRITTEN bindings against the declared parameters via a narrow\n * synthetic {@link parseIkiModel} candidate (so unrelated in-flight invalid\n * editor state — e.g. a NaN width on another part — cannot false-reject a\n * binding edit). Validation runs BEFORE any mutation; on failure the model and\n * undo stack are left untouched.\n *\n * Empty bindings → omit the `bindings` key on the candidate AND delete\n * `part.bindings` on apply (keeps the model shape minimal; represents \"no\n * bindings\" as an absent key rather than an empty array).\n */\nexport class SetPartBindings implements EditCommand {\n readonly label = \"Set part bindings\";\n private readonly bindings: IkiBinding[];\n private captured = false;\n private prevBindings!: IkiBinding[] | undefined;\n\n constructor(\n private readonly partId: string,\n bindings: IkiBinding[],\n ) {\n // Clone the caller's array on construction — prevents post-execute mutation\n // of the caller's array from corrupting apply/redo.\n this.bindings = bindings.map((b) => ({ ...b }));\n }\n\n apply(doc: EditorDocument): void {\n // Build a narrow synthetic candidate carrying only the validation-relevant\n // context. A full structuredClone(doc.getModel()) is deliberately avoided\n // because unrelated parts may carry NaN in-flight values (e.g. from\n // NumberField.valueAsNumber), which would cause false-positive validation\n // failures. The synthetic model is the minimal shape parseIkiModel accepts.\n const candidatePart: Record<string, unknown> = {\n id: \"_\",\n color: [0, 0, 0, 1],\n width: 1,\n height: 1,\n transform: { x: 0, y: 0 },\n order: 0,\n };\n // Omit the bindings key entirely when empty — \"no bindings\" is represented\n // as key absence; an empty array is not a valid value in the format contract.\n if (this.bindings.length > 0) {\n candidatePart.bindings = this.bindings.map((b) => ({ ...b }));\n }\n const candidate = {\n version: IKI_FORMAT_VERSION,\n name: \"_\",\n canvas: { width: 1, height: 1 },\n parameters: doc.getModel().parameters,\n parts: [candidatePart],\n };\n // Validation before any mutation. The synthetic candidate always places the\n // part at parts[0], so the validator emits paths like \"parts[0].bindings[i]\".\n // Rewrite that prefix to name the real target so the surfaced error is\n // actionable (\"parts.\"part-a\".bindings[i]\" rather than \"parts[0].bindings[i]\").\n try {\n parseIkiModel(candidate);\n } catch (e) {\n if (e instanceof IkiFormatError) {\n throw new IkiFormatError(\n e.message.replace(/^parts\\[0\\]/, `parts.\"${this.partId}\"`),\n );\n }\n throw e;\n }\n\n // Resolution after validation so an unknown partId throws with a\n // path-qualified message (findPart throws) but only after the bindings\n // themselves are confirmed structurally valid.\n const part = doc.findPart(this.partId);\n if (!this.captured) {\n // Preserve the original absent-vs-empty distinction.\n this.prevBindings =\n part.bindings === undefined\n ? undefined\n : part.bindings.map((b) => ({ ...b }));\n this.captured = true;\n }\n if (this.bindings.length > 0) {\n // Assign a fresh deep copy — model must never alias the command's stored array.\n part.bindings = this.bindings.map((b) => ({ ...b }));\n } else {\n // Delete the key on empty: keeps the model shape minimal and correctly\n // represents \"no bindings\" as an absent key rather than an empty array.\n delete part.bindings;\n }\n }\n\n invert(doc: EditorDocument): void {\n const part = doc.findPart(this.partId);\n if (this.prevBindings === undefined) {\n delete part.bindings;\n } else {\n // Assign a fresh deep copy — never alias the captured array so re-apply\n // after undo cannot corrupt the saved prior value.\n part.bindings = this.prevBindings.map((b) => ({ ...b }));\n }\n }\n}\n\n/**\n * Attach a part to a deformer, or detach it (`newDeformerId === undefined`).\n * Calls {@link validatePartAttach} FIRST so invalid attachments (warp without\n * mesh, unknown ids) throw before any capture or mutation.\n *\n * Absent-vs-present distinction mirrors {@link SetDeformerParent}: captures\n * both the prior `deformer` value and whether the key was present, so `invert`\n * can delete the key rather than assigning `undefined`.\n */\nexport class SetPartDeformer implements EditCommand {\n readonly label = \"Set part deformer\";\n private captured = false;\n private prevDeformer: string | undefined = undefined;\n private prevHadDeformer = false;\n\n constructor(\n private readonly partId: string,\n private readonly newDeformerId: string | undefined,\n ) {}\n\n apply(doc: EditorDocument): void {\n // Validate FIRST — throws before capture/mutate on any violation.\n validatePartAttach(\n doc.getModel().deformers ?? [],\n this.partId,\n doc.getModel().parts,\n this.newDeformerId,\n );\n const part = doc.findPart(this.partId);\n if (!this.captured) {\n this.prevHadDeformer = Object.prototype.hasOwnProperty.call(\n part,\n \"deformer\",\n );\n this.prevDeformer = part.deformer;\n this.captured = true;\n }\n if (this.newDeformerId !== undefined) {\n part.deformer = this.newDeformerId;\n } else {\n delete part.deformer;\n }\n }\n\n invert(doc: EditorDocument): void {\n const part = doc.findPart(this.partId);\n if (this.prevHadDeformer) {\n part.deformer = this.prevDeformer;\n } else {\n delete part.deformer;\n }\n }\n}\n\n/**\n * Return true if the deformer identified by `deformerId` exists in the model\n * and has `kind === \"warp\"`. Used by SetPartMesh to detect warp-deformer\n * attachment without importing reparent.ts internals.\n */\nfunction isWarpDeformer(model: IkiModel, deformerId: string): boolean {\n const d = (model.deformers ?? []).find((x) => x.id === deformerId);\n return d?.kind === \"warp\";\n}\n\n/**\n * Add, regenerate, or remove the triangle mesh on a part.\n *\n * - `mesh !== undefined` → add or replace the mesh, registering the\n * unit-square base UVs in the side-table so {@link EditorDocument.applyAtlas}\n * can remap them later.\n * - `mesh === undefined` → delete `part.mesh` and remove the side-table entry.\n *\n * Fails fast (BEFORE any mutation) on warp-topology violations:\n * - REMOVE while `part.warps` is present (even empty) or the part is\n * attached to a warp deformer — the format rejects any `warps` key once\n * the mesh is gone.\n * - ADD/REPLACE while `part.warps` has authored offsets (`length > 0`) —\n * regenerating the mesh invalidates offset positions silently; the user\n * must remove the warps first.\n *\n * The remove guard checks PRESENCE of `part.warps` (not length) while the\n * add/replace guard checks LENGTH > 0. This asymmetry is intentional: the\n * format allows `warps: []` only when a mesh exists, so any present key\n * (even empty) would become invalid after mesh removal; but replacing a mesh\n * under an empty `warps: []` is harmless because there are no authored offsets.\n */\nexport class SetPartMesh implements EditCommand {\n readonly label = \"Set part mesh\";\n private readonly mesh: IkiMesh | undefined;\n private captured = false;\n private prevHadMesh = false;\n private prevMesh: IkiMesh | undefined;\n private prevBaseMeshUvs: number[] | undefined;\n\n constructor(\n private readonly partId: string,\n mesh: IkiMesh | undefined,\n ) {\n // Clone on construction — prevents caller mutation from corrupting apply/redo.\n this.mesh = mesh === undefined ? undefined : structuredClone(mesh);\n }\n\n apply(doc: EditorDocument): void {\n // (a) Resolve the live part up front — unlike SetPartBindings, which resolves\n // after parseIkiModel, we need the live part here because the warp-topology\n // guards at step (c) inspect part.warps / part.deformer before any mutation.\n const part = doc.findPart(this.partId);\n\n // (b) Structural validation — only needed when adding or replacing a mesh.\n // Build a NARROW synthetic candidate carrying only the new mesh so that\n // unrelated in-flight parts with NaN values cannot cause false failures.\n if (this.mesh !== undefined) {\n const candidatePart = {\n id: \"_\",\n color: [0, 0, 0, 1],\n width: 1,\n height: 1,\n transform: { x: 0, y: 0 },\n order: 0,\n mesh: structuredClone(this.mesh),\n };\n const candidate = {\n version: IKI_FORMAT_VERSION,\n name: \"_\",\n canvas: { width: 1, height: 1 },\n parameters: doc.getModel().parameters,\n parts: [candidatePart],\n };\n try {\n parseIkiModel(candidate);\n } catch (e) {\n if (e instanceof IkiFormatError) {\n throw new IkiFormatError(\n e.message.replace(/^parts\\[0\\]/, `parts.\"${this.partId}\"`),\n );\n }\n throw e;\n }\n }\n\n // (c) Warp-topology fail-fast — BEFORE any mutation, after structural validation.\n // The two paths key on DIFFERENT predicates; this asymmetry is intentional\n // (see class JSDoc above).\n const attachedToWarp =\n part.deformer !== undefined &&\n isWarpDeformer(doc.getModel(), part.deformer);\n\n if (this.mesh === undefined) {\n // REMOVE: guard on warps PRESENCE (even empty) OR warp-deformer attachment.\n // The format rejects ANY present `warps` key once the mesh is gone, so even\n // an empty array would make toIkiModel() throw.\n if (part.warps !== undefined || attachedToWarp) {\n throw new IkiFormatError(\n `parts.\"${this.partId}\": cannot remove mesh — part has warps or is attached to a warp deformer; remove its warps / detach from the warp deformer first`,\n );\n }\n // Clip-mask guard — the format requires a clip mask to be a mesh part, so\n // stripping the mesh from a referenced mask would dangle the ref and fail\n // toIkiModel(). Mirrors the DeletePart clip-mask guard.\n const masker = doc\n .getModel()\n .parts.find(\n (p) => p.id !== this.partId && p.clip?.masks.includes(this.partId),\n );\n if (masker) {\n throw new IkiFormatError(\n `parts.\"${this.partId}\": cannot remove mesh — used as a clip mask by part \"${masker.id}\" (masks require a mesh); remove its clip first`,\n );\n }\n } else {\n // ADD/REPLACE: guard on authored offsets (length > 0). An empty warps: []\n // has no offsets to invalidate, so replacing the mesh under it is harmless.\n if ((part.warps?.length ?? 0) > 0) {\n throw new IkiFormatError(\n `parts.\"${this.partId}\": cannot regenerate mesh — part has warps whose offsets are bound to the current rest mesh; remove its warps first`,\n );\n }\n }\n\n // (d) Mutate + side-table maintenance.\n\n // (i) Single first-apply capture block — captures BOTH prevMesh and\n // prevBaseMeshUvs together, BEFORE any mutation. captureBaseMeshUvs has\n // a side effect (registers current mesh.uvs); step (iii) overwrites it.\n if (!this.captured) {\n this.prevHadMesh = part.mesh !== undefined;\n this.prevMesh = part.mesh ? structuredClone(part.mesh) : undefined;\n this.prevBaseMeshUvs = doc.captureBaseMeshUvs(this.partId);\n this.captured = true;\n }\n // Redo: do NOT re-capture (would clobber the saved prior). The mutation\n // below is fully reconstructable from this.mesh + this.partId.\n\n // (ii) Apply the model mutation.\n if (this.mesh !== undefined) {\n // Compute stored UVs: if the part has an active texture, remap the\n // unit-square base UVs into the texture's atlas sub-rectangle.\n const storedUvs =\n part.texture !== undefined\n ? remapMeshUvsToRect(this.mesh.uvs, part.texture.uv)\n : this.mesh.uvs.slice();\n part.mesh = {\n vertices: this.mesh.vertices.slice(),\n uvs: storedUvs,\n indices: this.mesh.indices.slice(),\n };\n } else {\n delete part.mesh;\n }\n\n // (iii) Re-register the side-table to the correct final state, overwriting\n // the (i) read's incidental re-registration. Write ONLY via\n // restoreBaseMeshUvs — never via a new setBaseMeshUvs API.\n if (this.mesh !== undefined) {\n // Register the UNIT-SQUARE base (not the texture-remapped storedUvs) so\n // applyAtlas can always derive correct atlas-space UVs from the base.\n doc.restoreBaseMeshUvs(this.partId, this.mesh.uvs.slice());\n } else {\n doc.restoreBaseMeshUvs(this.partId, undefined);\n }\n }\n\n invert(doc: EditorDocument): void {\n const part = doc.findPart(this.partId);\n // Restore the mesh, preserving the absent-vs-present distinction.\n // applyAtlas (and texture changes generally) are NON-undoable and do NOT\n // clear the undo stack. So by the time undo() is called, part.texture may\n // have been changed by a later applyAtlas that this command never saw. To\n // avoid restoring stale texture-space UVs, rebuild uvs from the captured\n // unit-square base against the CURRENT texture rect rather than trusting\n // prevMesh.uvs verbatim.\n if (this.prevHadMesh) {\n // A mesh part ALWAYS has a registered unit-square base under the invariant\n // (applyAtlas preflights requireBaseUvs for every mesh part). If the base is\n // absent here the side-table invariant is broken — fail fast rather than\n // silently restoring stale texture-space uvs that would corrupt rendering.\n if (this.prevBaseMeshUvs === undefined) {\n throw new Error(\n `parts.\"${this.partId}\": cannot invert SetPartMesh — no base mesh uvs captured for a mesh part (broken side-table invariant)`,\n );\n }\n const restored = structuredClone(this.prevMesh!);\n // Remap the unit-square base into the current texture rect (if any).\n restored.uvs =\n part.texture !== undefined\n ? remapMeshUvsToRect(this.prevBaseMeshUvs, part.texture.uv)\n : this.prevBaseMeshUvs.slice();\n part.mesh = restored;\n } else {\n delete part.mesh;\n }\n // Restore the side-table to the exact prior state (unit-square base).\n doc.restoreBaseMeshUvs(this.partId, this.prevBaseMeshUvs);\n }\n}\n\n/**\n * Validate a candidate `physics` array against the model's declared parameters\n * via a NARROW synthetic model — full `parameters` + the full candidate physics\n * array + one trivial part. Mirrors {@link SetPartBindings}' synthetic-candidate\n * (avoids false-positives from unrelated in-flight NaN part numerics) but carries\n * the FULL physics array so the cross-rig rules (dup id / dup output / feedback)\n * run. On `IkiFormatError`, rewrite the failing `physics[n]` path to name the rig\n * AT THE FAILING INDEX — a cross-rig failure can surface at a different index than\n * the edited rig, so the rewrite follows the message index, not the command target.\n */\nfunction validatePhysicsCandidate(\n doc: EditorDocument,\n candidatePhysics: IkiPhysics[],\n): void {\n const candidatePart: Record<string, unknown> = {\n id: \"_\",\n color: [0, 0, 0, 1],\n width: 1,\n height: 1,\n transform: { x: 0, y: 0 },\n order: 0,\n };\n const candidate = {\n version: IKI_FORMAT_VERSION,\n name: \"_\",\n canvas: { width: 1, height: 1 },\n parameters: doc.getModel().parameters,\n parts: [candidatePart],\n deformers: doc.getModel().deformers,\n physics: candidatePhysics,\n physicsChains: doc.getModel().physicsChains,\n };\n try {\n parseIkiModel(candidate);\n } catch (e) {\n if (e instanceof IkiFormatError) {\n const m = e.message.match(/^physics\\[(\\d+)\\]/);\n const rigId = m ? candidatePhysics[Number(m[1])]?.id : undefined;\n if (rigId !== undefined) {\n throw new IkiFormatError(\n e.message.replace(/^physics\\[\\d+\\]/, `physics.\"${rigId}\"`),\n );\n }\n }\n throw e;\n }\n}\n\n/**\n * Add a physics rig to `model.physics`. Mirrors {@link AddDeformer}: clone on\n * construction, cheap friendly duplicate-id pre-check, full synthetic-candidate\n * validation before mutating, capture the absent-vs-present `physics` state once,\n * and on invert delete the key when the array empties back to its prior absence.\n */\nexport class AddPhysicsRig implements EditCommand {\n readonly label = \"Add physics rig\";\n private readonly rig: IkiPhysics;\n private captured = false;\n private prevPhysicsAbsent = false;\n\n constructor(rig: IkiPhysics) {\n // Deep clone — input/output are nested objects a shallow spread would alias.\n this.rig = structuredClone(rig);\n }\n\n apply(doc: EditorDocument): void {\n const model = doc.getModel();\n // (a) Friendly duplicate-id pre-check (the format check from validate.ts is\n // the real guard; this surfaces an actionable message before validate).\n if ((model.physics ?? []).some((r) => r.id === this.rig.id)) {\n throw new Error(\n `physics: id \"${this.rig.id}\" collides with an existing physics rig id`,\n );\n }\n // (b) Full-physics synthetic validation with the rig appended.\n validatePhysicsCandidate(doc, [\n ...(model.physics ?? []),\n structuredClone(this.rig),\n ]);\n // (c) Capture-once, then mutate with a fresh clone.\n if (!this.captured) {\n this.prevPhysicsAbsent = model.physics === undefined;\n this.captured = true;\n }\n if (model.physics === undefined) {\n model.physics = [];\n }\n model.physics.push(structuredClone(this.rig));\n }\n\n invert(doc: EditorDocument): void {\n const model = doc.getModel();\n const arr = model.physics;\n if (!arr) return;\n const i = arr.findIndex((r) => r.id === this.rig.id);\n if (i !== -1) arr.splice(i, 1);\n if (this.prevPhysicsAbsent && arr.length === 0) {\n delete model.physics;\n }\n }\n}\n\n/**\n * Delete a physics rig by id. Mirrors {@link DeleteDeformer}: resolve/validate\n * first, capture the removed rig + its index once, splice. Removing the LAST rig\n * deletes the `physics` key to keep the exported shape minimal; invert re-inserts\n * at the original index (recreating the array if it was deleted).\n */\nexport class DeletePhysicsRig implements EditCommand {\n readonly label = \"Delete physics rig\";\n private captured = false;\n private removed!: IkiPhysics;\n private index!: number;\n\n constructor(private readonly rigId: string) {}\n\n apply(doc: EditorDocument): void {\n const rig = doc.findPhysicsRig(this.rigId); // throws if unknown\n const arr = doc.getModel().physics!;\n const i = arr.indexOf(rig);\n if (!this.captured) {\n this.removed = structuredClone(rig);\n this.index = i;\n this.captured = true;\n }\n arr.splice(i, 1);\n // Minimal exported shape: drop the key once the last rig is gone.\n if (arr.length === 0) {\n delete doc.getModel().physics;\n }\n }\n\n invert(doc: EditorDocument): void {\n const model = doc.getModel();\n (model.physics ??= []).splice(this.index, 0, structuredClone(this.removed));\n }\n}\n\n/**\n * Replace a physics rig in place (tuning). Mirrors {@link SetDeformerBindings}\n * but DEEP-clones the nested `input`/`output` (a shallow spread would alias them).\n * Forbids rename (`rig.id` must equal the target `rigId`) — this command tunes a\n * rig, never re-keys it. Validates the whole candidate (the edited rig swapped in\n * at its index) so cross-rig rules still run, then captures the prior rig once.\n */\nexport class SetPhysicsRig implements EditCommand {\n readonly label = \"Set physics rig\";\n private readonly rig: IkiPhysics;\n private captured = false;\n private prevRig!: IkiPhysics;\n\n constructor(\n private readonly rigId: string,\n rig: IkiPhysics,\n ) {\n this.rig = structuredClone(rig);\n }\n\n apply(doc: EditorDocument): void {\n // No-rename guard — fail before mutating.\n if (this.rig.id !== this.rigId) {\n throw new Error(\n `physics.\"${this.rigId}\": cannot change rig id to \"${this.rig.id}\" (rename unsupported)`,\n );\n }\n const model = doc.getModel();\n const i = (model.physics ?? []).findIndex((r) => r.id === this.rigId);\n if (i === -1) {\n throw new Error(`physics: no physics rig with id \"${this.rigId}\"`);\n }\n // Validate a candidate with the edited rig swapped in at its index.\n const candidate = model.physics!.map((r, idx) =>\n idx === i ? structuredClone(this.rig) : r,\n );\n validatePhysicsCandidate(doc, candidate);\n if (!this.captured) {\n this.prevRig = structuredClone(model.physics![i]);\n this.captured = true;\n }\n model.physics![i] = structuredClone(this.rig);\n }\n\n invert(doc: EditorDocument): void {\n const arr = doc.getModel().physics;\n if (!arr) return;\n const i = arr.findIndex((r) => r.id === this.rigId);\n if (i !== -1) arr[i] = structuredClone(this.prevRig);\n }\n}\n","import type { IkiGridKeyform } from \"@ikijs/format\";\n\n/**\n * Pure, grid-size-agnostic keyform/offset math for authoring a warp-deformer\n * grid by dragging. No DOM, no `@ikijs/engine` — the load-bearing testable core.\n * Constraints derive only from the input array lengths, never the sample grid.\n */\n\n/**\n * Interpolate the grid offsets at `value`, mirroring the engine's\n * `accumulateKeyformOffsets` clamp+lerp semantics: clamp to the first/last\n * keyform (NO extrapolation) and linearly interpolate the bracketing pair.\n * Returns a NEW array of length `keyforms[0].offsets.length`. Throws on empty.\n */\nexport function interpolateGridOffsets(\n keyforms: { value: number; offsets: number[] }[],\n value: number,\n): number[] {\n if (keyforms.length === 0) {\n throw new Error(\"interpolateGridOffsets: keyforms must be non-empty\");\n }\n\n if (value <= keyforms[0].value) {\n return [...keyforms[0].offsets];\n }\n const last = keyforms[keyforms.length - 1];\n if (value >= last.value) {\n return [...last.offsets];\n }\n\n // Find the bracketing pair (keyforms are small in practice).\n let lo = keyforms[0];\n let hi = keyforms[1];\n for (let k = 1; k < keyforms.length - 1; k++) {\n if (keyforms[k].value <= value) {\n lo = keyforms[k];\n hi = keyforms[k + 1];\n }\n }\n const t = (value - lo.value) / (hi.value - lo.value);\n return lo.offsets.map((loOff, i) => loOff + (hi.offsets[i] - loOff) * t);\n}\n\n/**\n * Per-control-point delta of the dragged grid from the rest grid: for each\n * point `i`, `(draggedX_i - restX_i, draggedY_i - restY_i)`. The DOM layer\n * assembles the full `restFrameDraggedPoints` (including untouched points), so\n * this is a straight subtract — no prior-offset blending.\n *\n * Both arrays must have the SAME length and that length must be even (x,y\n * pairs). Returns a NEW array of length `restPoints.length`.\n */\nexport function computeGridOffsets(\n restPoints: number[],\n restFrameDraggedPoints: number[],\n): number[] {\n if (restPoints.length !== restFrameDraggedPoints.length) {\n throw new Error(\n `computeGridOffsets: restFrameDraggedPoints length ${restFrameDraggedPoints.length} must equal restPoints length ${restPoints.length}`,\n );\n }\n if (restPoints.length % 2 !== 0) {\n throw new Error(\n `computeGridOffsets: restPoints length ${restPoints.length} must be even (x,y pairs)`,\n );\n }\n return restPoints.map((rest, i) => restFrameDraggedPoints[i] - rest);\n}\n\n/**\n * Insert or replace the keyform at `value`, returning a NEW array. If a keyform\n * already exists with an exact-match `value`, REPLACE its offsets (with a copy);\n * otherwise INSERT `{ value, offsets: [...offsets] }` at the position that keeps\n * the array strictly ascending by value. The input array and its keyform objects\n * are never mutated, and `offsets` is copied so the result never aliases the\n * caller's array.\n *\n * Deliberately RANGE-FREE — a generic, reusable array op. Value-range\n * enforcement is the command's job, not this helper's.\n */\nexport function upsertGridKeyform(\n keyforms: IkiGridKeyform[],\n value: number,\n offsets: number[],\n): IkiGridKeyform[] {\n const result = keyforms.map((kf) => ({\n value: kf.value,\n offsets: [...kf.offsets],\n }));\n const existing = result.findIndex((kf) => kf.value === value);\n if (existing !== -1) {\n result[existing] = { value, offsets: [...offsets] };\n return result;\n }\n const insertAt = result.findIndex((kf) => kf.value > value);\n const entry: IkiGridKeyform = { value, offsets: [...offsets] };\n if (insertAt === -1) {\n result.push(entry);\n } else {\n result.splice(insertAt, 0, entry);\n }\n return result;\n}\n","import type { IkiDeformer, IkiPart, IkiPhysicsChain } from \"@ikijs/format\";\n\n/**\n * Pure, DOM-free validation helpers for deformer reparenting and part attachment.\n * Each function throws a path-qualified plain Error on rejection and returns void\n * on success. Neither function mutates the input arrays or objects.\n */\n\nfunction kindOf(d: IkiDeformer): \"warp\" | \"matrix\" {\n return d.kind === \"warp\" ? \"warp\" : \"matrix\";\n}\n\n/**\n * Validate that reparenting `deformerId` under `newParentId` keeps the deformer\n * hierarchy valid. Pass `newParentId === undefined` to move to root (always legal).\n * Checks: existence, self-reference, undeclared parent, kind constraint (warp\n * deformers cannot be parents), and cycle detection via the proposed edge.\n */\nexport function validateDeformerReparent(\n deformers: IkiDeformer[],\n deformerId: string,\n newParentId: string | undefined,\n): void {\n // (1) Target deformer must exist.\n const target = deformers.find((d) => d.id === deformerId);\n if (target === undefined) {\n throw new Error(`deformers: no deformer with id \"${deformerId}\"`);\n }\n\n // (2) Root is always legal.\n if (newParentId === undefined) return;\n\n // (3) Self-reference.\n if (newParentId === deformerId) {\n throw new Error(\n `deformers.\"${deformerId}\".parent \"${newParentId}\" is a self-reference`,\n );\n }\n\n // (4) Parent must be declared.\n const parent = deformers.find((d) => d.id === newParentId);\n if (parent === undefined) {\n throw new Error(\n `deformers.\"${deformerId}\".parent \"${newParentId}\" is not a declared deformer`,\n );\n }\n\n // (5) Parent must be a matrix deformer.\n if (kindOf(parent) === \"warp\") {\n throw new Error(\n `deformers.\"${deformerId}\".parent \"${newParentId}\" must be a matrix deformer (warp deformers cannot be parents)`,\n );\n }\n\n // (6) Cycle detection: build parentOf from current state, then override with\n // the proposed edge, and walk from deformerId following the chain.\n const parentOf = new Map<string, string>();\n for (const d of deformers) {\n if (d.parent !== undefined) parentOf.set(d.id, d.parent);\n }\n // Override with the proposed edge.\n parentOf.set(deformerId, newParentId);\n\n const visited = new Set<string>();\n let cur: string | undefined = deformerId;\n while (cur !== undefined) {\n if (visited.has(cur)) {\n throw new Error(\n `deformers: reparenting \"${deformerId}\" under \"${newParentId}\" would create a cycle`,\n );\n }\n visited.add(cur);\n cur = parentOf.get(cur);\n }\n}\n\n/**\n * Validate that deleting `deformerId` is safe. Throws when the deformer does\n * not exist, when another deformer is parented to it (must be reparented or\n * detached first), when a part is attached to it (must be detached first), or\n * when a physics chain anchors to it (must be re-anchored or deleted first).\n *\n * Every id that can reference a deformer must be covered here: the format\n * validator rejects a dangling reference at export, so a delete this function\n * lets through does not fail now — it strands the document in a state\n * `toIkiModel()` refuses.\n *\n * Note: there is no validatePartDelete here, but NOT because parts are\n * unreferenced — `clip.masks` names parts by id. That invariant is enforced\n * closer to the edits that could break it: `DeletePart` refuses to remove a\n * part still used as a mask, and `SetPartMesh` refuses to strip the mesh off\n * one (masks must be mesh parts). No command creates or edits `clip`.\n */\nexport function validateDeformerDelete(\n deformers: IkiDeformer[],\n parts: IkiPart[],\n physicsChains: IkiPhysicsChain[],\n deformerId: string,\n): void {\n // (1) Target deformer must exist.\n const target = deformers.find((d) => d.id === deformerId);\n if (target === undefined) {\n throw new Error(`deformers: no deformer with id \"${deformerId}\"`);\n }\n\n // (2) No other deformer may be parented to it.\n const childDeformer = deformers.find((d) => d.parent === deformerId);\n if (childDeformer !== undefined) {\n throw new Error(\n `deformers.\"${deformerId}\": cannot delete — deformer \"${childDeformer.id}\" is parented to it; reparent or detach it first`,\n );\n }\n\n // (3) No part may be attached to it.\n const attachedPart = parts.find((p) => p.deformer === deformerId);\n if (attachedPart !== undefined) {\n throw new Error(\n `deformers.\"${deformerId}\": cannot delete — part \"${attachedPart.id}\" is attached to it; detach it first`,\n );\n }\n\n // (4) No physics chain may anchor to it.\n const anchoredChain = physicsChains.find(\n (c) => c.anchorDeformer === deformerId,\n );\n if (anchoredChain !== undefined) {\n throw new Error(\n `deformers.\"${deformerId}\": cannot delete — physics chain \"${anchoredChain.id}\" anchors to it; re-anchor or delete the chain first`,\n );\n }\n}\n\n/**\n * Validate that attaching part `partId` to deformer `newDeformerId` is valid.\n * Pass `newDeformerId === undefined` to detach (always legal).\n * Checks: part existence, undeclared deformer, and mesh-required-for-warp.\n */\nexport function validatePartAttach(\n deformers: IkiDeformer[],\n partId: string,\n parts: IkiPart[],\n newDeformerId: string | undefined,\n): void {\n // (1) Part must exist.\n const part = parts.find((p) => p.id === partId);\n if (part === undefined) {\n throw new Error(`parts: no part with id \"${partId}\"`);\n }\n\n // (2) Detach is always legal.\n if (newDeformerId === undefined) return;\n\n // (3) Deformer must be declared.\n const deformer = deformers.find((d) => d.id === newDeformerId);\n if (deformer === undefined) {\n throw new Error(\n `parts.\"${partId}\".deformer \"${newDeformerId}\" is not a declared deformer`,\n );\n }\n\n // (4) Warp deformer requires a mesh on the part.\n if (kindOf(deformer) === \"warp\" && part.mesh === undefined) {\n throw new Error(\n `parts.\"${partId}\".deformer \"${newDeformerId}\" is a warp deformer and requires a mesh`,\n );\n }\n}\n","/**\n * Alpha bounding-box scan shared by every auto-rig ingestion path.\n *\n * The scan itself is environment-free — it only needs indexable RGBA bytes — so\n * a browser editor (canvas `ImageData`) and the Node MCP server (a `sharp`\n * raw buffer) run the SAME code instead of two copies that have to be kept\n * byte-identical by hand. Only decoding differs between them.\n */\n\n/** Alpha at or above this counts as coverage; below it is treated as empty. */\nexport const ALPHA_BBOX_THRESHOLD = 8;\n\n/** Top-left origin, +y down — image space, not model space. */\nexport interface AlphaBbox {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n/**\n * Tight bounding box of every pixel with alpha >= {@link ALPHA_BBOX_THRESHOLD},\n * expanded 1px on each side (clamped to the image) for AA / extrude margin.\n *\n * Returns `null` when no pixel passes the threshold, leaving the \"empty layer\"\n * error to the caller: each ingestion path reports it with its own error type\n * and message.\n */\nexport function detectAlphaBbox(\n rgba: ArrayLike<number>,\n width: number,\n height: number,\n): AlphaBbox | null {\n let minX = width;\n let maxX = -1;\n let minY = height;\n let maxY = -1;\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const alpha = rgba[(y * width + x) * 4 + 3];\n if (alpha >= ALPHA_BBOX_THRESHOLD) {\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n }\n }\n }\n\n if (maxX === -1) return null;\n\n // Expand 1px each side, clamped to image bounds; the x2/y2 clamps make w/h\n // implicitly in-bounds (no separate w/h clamp needed).\n const x = Math.max(0, minX - 1);\n const y = Math.max(0, minY - 1);\n const x2 = Math.min(width - 1, maxX + 1);\n const y2 = Math.min(height - 1, maxY + 1);\n\n return { x, y, w: x2 - x + 1, h: y2 - y + 1 };\n}\n","import type { IkiTransformChannel } from \"@ikijs/format\";\n\n/**\n * Pure, binding-value logic for computing an endpoint (rest-to-posed delta or\n * ratio) when capturing a transform channel binding. No DOM, no @ikijs/engine —\n * the single home of the additive-vs-multiplicative rule, reused by both the\n * part and deformer capture paths.\n *\n * Additive channels (translateX, translateY, rotate, scaleX, scaleY) return the\n * delta: `posedValue - restValue`. The binding will multiply that delta across\n * the driven range.\n *\n * Opacity is multiplicative: returns the ratio `posedValue / restValue`. The\n * binding will multiply by that ratio across the driven range. When `restValue`\n * is 0 (a degenerate case: base opacity cannot be represented multiplicatively\n * since 0 * x ≡ 0), this returns 0 as a documented fallback — it does NOT\n * recover `posedValue` and is NOT unit-tested as an identity. The store layer\n * additionally skips an opacity capture when rest opacity is 0, surfacing an\n * editError.\n *\n * Deformer channels never include opacity (they use `IkiMatrixChannel`), so the\n * opacity branch is reached only for parts.\n *\n * Finiteness of the captured value is NOT validated here; the store's\n * `captureEndpoint` finite-value guard is the appropriate layer for that check.\n */\nexport function captureBindingEndpoint(\n channel: IkiTransformChannel,\n restValue: number,\n posedValue: number,\n): number {\n if (channel === \"opacity\") {\n return restValue === 0 ? 0 : posedValue / restValue;\n }\n return posedValue - restValue;\n}\n","import type { IkiUvRect } from \"@ikijs/format\";\n\nexport const ATLAS_PADDING = 2;\nexport const UV_INSET_PX = 0.5;\n\n/** Intrinsic pixel size of one decoded image; id is an editor-only stable key. */\nexport interface AtlasSource {\n id: string;\n width: number;\n height: number;\n}\n\n/** Sub-image pixel rect within the page, top-left origin, EXCLUDING the gutter. */\nexport interface AtlasPlacement {\n id: string;\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface AtlasLayout {\n pageWidth: number;\n pageHeight: number;\n placements: AtlasPlacement[];\n padding: number;\n}\n\n/**\n * Deterministic shelf/row packer. Sources are sorted by id for stability so\n * identical inputs always produce an identical layout.\n *\n * Padding is one-sided: each placement reserves `padding` px on its RIGHT and\n * BOTTOM only. Page left/top edges need no gutter.\n *\n * pageWidth/pageHeight = tight bound (max x+width+padding, max y+height+padding).\n * Empty sources → { pageWidth: 0, pageHeight: 0, placements: [], padding }.\n * Throws a plain Error naming the offending source id on a non-finite or <= 0 dimension.\n */\nexport function packAtlas(\n sources: AtlasSource[],\n padding = ATLAS_PADDING,\n): AtlasLayout {\n for (const src of sources) {\n if (!isFinite(src.width) || src.width <= 0) {\n throw new Error(\n `packAtlas: source \"${src.id}\" has invalid width ${src.width}`,\n );\n }\n if (!isFinite(src.height) || src.height <= 0) {\n throw new Error(\n `packAtlas: source \"${src.id}\" has invalid height ${src.height}`,\n );\n }\n }\n\n if (sources.length === 0) {\n return { pageWidth: 0, pageHeight: 0, placements: [], padding };\n }\n\n // Stable sort by id so identical inputs always produce the same layout.\n const sorted = sources\n .slice()\n .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));\n\n // Target page width: ceil(sqrt(total padded area)), raised to a sane minimum.\n const totalArea = sorted.reduce(\n (sum, s) => sum + (s.width + padding) * (s.height + padding),\n 0,\n );\n const targetWidth = Math.max(\n Math.ceil(Math.sqrt(totalArea)),\n // Ensure at least the widest single source fits.\n Math.max(...sorted.map((s) => s.width + padding)),\n );\n\n const placements: AtlasPlacement[] = [];\n let shelfX = 0;\n let shelfY = 0;\n let shelfHeight = 0; // tallest padded item in the current row\n\n for (const src of sorted) {\n const paddedW = src.width + padding;\n const paddedH = src.height + padding;\n\n // Wrap to next row when the item doesn't fit on the current shelf.\n if (shelfX > 0 && shelfX + paddedW > targetWidth) {\n shelfY += shelfHeight;\n shelfX = 0;\n shelfHeight = 0;\n }\n\n placements.push({\n id: src.id,\n x: shelfX,\n y: shelfY,\n width: src.width,\n height: src.height,\n });\n\n shelfX += paddedW;\n if (paddedH > shelfHeight) shelfHeight = paddedH;\n }\n\n // Tight page bounds: max right/bottom padded edge across all placements.\n let pageWidth = 0;\n let pageHeight = 0;\n for (const p of placements) {\n const right = p.x + p.width + padding;\n const bottom = p.y + p.height + padding;\n if (right > pageWidth) pageWidth = right;\n if (bottom > pageHeight) pageHeight = bottom;\n }\n\n return { pageWidth, pageHeight, placements, padding };\n}\n\n/**\n * Convert a pixel placement into a UV rect, inset by `insetPx` on all four\n * edges and clamped to [0, 1] so the validator's bounds check always passes.\n */\nexport function uvRectFor(\n placement: AtlasPlacement,\n page: { width: number; height: number },\n insetPx = UV_INSET_PX,\n): IkiUvRect {\n const px = placement.x + insetPx;\n const py = placement.y + insetPx;\n const pw = placement.width - insetPx * 2;\n const ph = placement.height - insetPx * 2;\n\n const x = Math.max(0, px / page.width);\n const y = Math.max(0, py / page.height);\n const width = Math.min(1 - x, Math.max(0, pw / page.width));\n const height = Math.min(1 - y, Math.max(0, ph / page.height));\n\n return { x, y, width, height };\n}\n","import type {\n IkiMatrixDeformer,\n IkiMesh,\n IkiModel,\n IkiPart,\n IkiWarpDeformer,\n} from \"@ikijs/format\";\n\n/**\n * Pure, DOM-free factory helpers for creating new model objects from scratch.\n * Kept separate from `commands.ts` (which is edit-only) so that the \"add item\"\n * use-case has a dedicated, testable home with no mutation concerns.\n *\n * All factories accept the live model so they can derive a collision-free id and\n * a sensible draw-order / grid span without hard-coding sample assumptions.\n */\n\n/**\n * Return the first collision-free id in the shared part+deformer namespace.\n * Parts and deformers share a flat id namespace (validate.ts:692-706), so we\n * scan both arrays. Returns `base` if unused; otherwise tries `${base}_2`,\n * `${base}_3`, … until a free slot is found.\n *\n * Parameters are a separate namespace and are NOT included in the scan.\n */\nfunction generateUniqueId(model: IkiModel, base: string): string {\n const used = new Set<string>();\n for (const p of model.parts) {\n used.add(p.id);\n }\n for (const d of model.deformers ?? []) {\n used.add(d.id);\n }\n\n if (!used.has(base)) return base;\n\n let n = 2;\n while (true) {\n const candidate = `${base}_${n}`;\n if (!used.has(candidate)) return candidate;\n n++;\n }\n}\n\n/**\n * Generate the flat `[x0,y0, x1,y1, …]` rest-grid control points for a\n * regular axis-aligned lattice with `(cols+1)*(rows+1)` points, row-major.\n *\n * Row 0 is the TOP (y = maxY); y strictly decreases with row index.\n * Column 0 is left (x = minX); x strictly increases with column index.\n * This ordering satisfies the `IkiWarpGrid` rest-grid invariant\n * (packages/format/src/types.ts:178-196) required by the validator and\n * the engine's grid-sampling code.\n */\nfunction generateRegularGridPoints(\n cols: number,\n rows: number,\n minX: number,\n maxX: number,\n minY: number,\n maxY: number,\n): number[] {\n const pts: number[] = [];\n for (let row = 0; row <= rows; row++) {\n const t = row / rows;\n const y = maxY - t * (maxY - minY); // maxY at row 0, minY at row `rows`\n for (let col = 0; col <= cols; col++) {\n const s = col / cols;\n const x = minX + s * (maxX - minX); // minX at col 0, maxX at col `cols`\n pts.push(x, y);\n }\n }\n return pts;\n}\n\n/**\n * Create a minimal valid part with a collision-free id and a paint order one\n * above the current top-most part so it is immediately visible in the viewport.\n * Uses a distinct non-white blue tint so it is distinguishable from the canvas\n * background without requiring a texture.\n *\n * No optional keys (`texture`, `mesh`, `deformer`, `bindings`, `warps`) are set\n * — the format treats all of them as absent by default.\n */\nexport function createDefaultPart(model: IkiModel): IkiPart {\n const order = model.parts.length\n ? Math.max(...model.parts.map((p) => p.order)) + 1\n : 0;\n\n return {\n id: generateUniqueId(model, \"part\"),\n color: [0.45, 0.6, 0.85, 1],\n width: 150,\n height: 150,\n transform: { x: 0, y: 0 },\n order,\n };\n}\n\n/**\n * Create a minimal valid matrix deformer rooted at the canvas origin.\n * `kind` is omitted because the format treats its absence as \"matrix\" (the\n * default), keeping the serialised model compact.\n */\nexport function createDefaultMatrixDeformer(\n model: IkiModel,\n): IkiMatrixDeformer {\n return {\n id: generateUniqueId(model, \"deformer\"),\n pivot: { x: 0, y: 0 },\n };\n}\n\n/**\n * Create a regular grid mesh in part LOCAL space (±0.5 unit frame).\n *\n * Vertices span x ∈ [-0.5, 0.5] and y ∈ [-0.5, 0.5] (+y up, engine convention).\n * Row 0 is the TOP of the grid (y = +0.5); row index increases downward.\n * UVs are unit-square base coordinates: u = col/cols (0..1 left→right),\n * v = row/rows (0..1 top→bottom). The top row maps to v=0 because v and y\n * run in opposite directions — keeps textures upright without a post-flip.\n * The UV-to-texture remap (atlas rect) is applied later in SetPartMesh, not here.\n *\n * Index winding per cell: [BL, BR, TL] then [TL, BR, TR], matching the engine's\n * implicit-quad convention (see examples/editor/src/mesh-generator.ts).\n *\n * Bounds are validated BEFORE any array allocation because this factory runs\n * before SetPartMesh's parseIkiModel — an unbounded count would freeze the\n * editor before the format-level 65536 limit is ever reached.\n */\nexport function createGridMesh(cols: number, rows: number): IkiMesh {\n // Guard: cols/rows must be integers ≥1 and the vertex count must fit within\n // the format limit of 65536 vertices (packages/format/src/validate.ts parseMesh).\n if (\n !Number.isInteger(cols) ||\n cols < 1 ||\n !Number.isInteger(rows) ||\n rows < 1 ||\n (cols + 1) * (rows + 1) > 65536\n ) {\n throw new Error(\n \"createGridMesh: cols and rows must be integers >= 1 with (cols+1)*(rows+1) <= 65536\",\n );\n }\n\n const colVerts = cols + 1;\n const rowVerts = rows + 1;\n\n const vertices: number[] = [];\n const uvs: number[] = [];\n\n // Row 0 is TOP (y = +0.5). Row `rows` is BOTTOM (y = -0.5).\n // Column 0 is left (x = -0.5). Column `cols` is right (x = +0.5).\n for (let row = 0; row < rowVerts; row++) {\n const t = row / rows;\n const y = 0.5 - t; // +0.5 at row 0, -0.5 at row `rows`\n const v = t; // 0 at top row, 1 at bottom row\n\n for (let col = 0; col < colVerts; col++) {\n const s = col / cols;\n const x = -0.5 + s; // -0.5 at col 0, +0.5 at col `cols`\n const u = s; // 0 at left col, 1 at right col\n\n vertices.push(x, y);\n uvs.push(u, v);\n }\n }\n\n // Two triangles per cell: [BL, BR, TL] then [TL, BR, TR].\n const indices: number[] = [];\n for (let row = 0; row < rows; row++) {\n for (let col = 0; col < cols; col++) {\n const tl = row * colVerts + col;\n const tr = row * colVerts + col + 1;\n const bl = (row + 1) * colVerts + col;\n const br = (row + 1) * colVerts + col + 1;\n\n indices.push(bl, br, tl);\n indices.push(tl, br, tr);\n }\n }\n\n return { vertices, uvs, indices };\n}\n\n/**\n * Create a 4×4-cell warp deformer whose rest grid spans a quarter of the\n * canvas in each direction (`±canvas.width/4` × `±canvas.height/4`). For the\n * 1000-unit sample canvas this gives x,y ∈ [−250, 250], which is large enough\n * to cover a typical face part without spilling to the edge.\n *\n * `warps` is omitted — the format treats its absence as \"rest grid only\", so\n * the deformer is immediately usable without authored keyforms.\n * `parent` is omitted — the deformer is placed at root; the caller may\n * reparent it via `SetDeformerParent` after creation.\n */\nexport function createDefaultWarpDeformer(model: IkiModel): IkiWarpDeformer {\n const hx = model.canvas.width / 4;\n const hy = model.canvas.height / 4;\n\n return {\n kind: \"warp\",\n id: generateUniqueId(model, \"warp\"),\n grid: {\n cols: 4,\n rows: 4,\n points: generateRegularGridPoints(4, 4, -hx, hx, -hy, hy),\n },\n };\n}\n","/**\n * Role table, role parsing, bbox→transform math, and model assembly for the\n * AI auto-rig generator. All pure functions — no DOM, no canvas, no\n * crypto.randomUUID.\n *\n * L/R = CHARACTER frame: *_L is the character's left = screen right.\n */\n\nimport {\n IKI_FORMAT_VERSION,\n StandardParameter,\n parseIkiModel,\n type IkiBinding,\n type IkiDeformer,\n type IkiGrid2DWarp,\n type IkiMesh,\n type IkiModel,\n type IkiParameter,\n type IkiPart,\n type IkiWarp,\n type IkiWarpGrid,\n} from \"@ikijs/format\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\n/**\n * The warp deformers the face and its features turn on — one per group of\n * parts that share a turn depth and a place on the face: the plate, each\n * side's eye stack (white, iris, pupil, highlight, lash), the two mouth\n * drawings, the nose, each brow and each blush. Every one is a `warp2d` over\n * a grid sized to its own members, parented to `headDeformer`, baked from the\n * one solved surface and emitted only when a member layer exists\n * (`generateIkiFromLayerSet`). `faceWarp` keeps the id the shared face grid\n * had.\n */\nconst TURN_GROUP_IDS = [\n \"faceWarp\",\n \"eyeWarp_L\",\n \"eyeWarp_R\",\n \"mouthWarp\",\n \"noseWarp\",\n \"browWarp_L\",\n \"browWarp_R\",\n \"blushWarp_L\",\n \"blushWarp_R\",\n] as const;\n/** One of `TURN_GROUP_IDS`. */\nexport type TurnGroupId = (typeof TURN_GROUP_IDS)[number];\nconst TURN_GROUP_ID_SET: ReadonlySet<string> = new Set<string>(TURN_GROUP_IDS);\n\n/** Whether a `RoleSpec.deformer` names one of the per-group turn warps — the\n * test for \"a part painted on the face\" wherever the generator or the solve\n * needs it. Exported at module level for the tests, not from the package. */\nexport function isTurnGroup(id: string): id is TurnGroupId {\n return TURN_GROUP_ID_SET.has(id);\n}\n\nexport interface RoleSpec {\n /**\n * Which deformer the part is attached to in the generated rig: its turn\n * group's warp for anything painted on the face (`TurnGroupId`), the rigid\n * head or body otherwise. `\"none\"` attaches it to nothing, so it holds\n * still in world space; no shipped role uses it.\n */\n deformer: TurnGroupId | \"headDeformer\" | \"bodyDeformer\" | \"none\";\n /** Back-to-front draw order. Higher = in front. */\n order: number;\n /** Whether this part gets a warp mesh (true) or is a static quad (false). */\n mesh: boolean;\n /** Present only for eye-family roles. */\n eyeSide?: \"L\" | \"R\";\n}\n\n/**\n * Input contract from the host app to this package's auto-rig functions.\n * Passed in after the host has decoded PNGs, computed alpha bboxes, and\n * mapped filenames to canonical roles.\n */\nexport interface LayerInput {\n /** Canonical role, e.g. \"eye_L\". */\n role: string;\n /** Original file name — used in error messages and as a stable id. */\n fileName: string;\n /** Shared canvas width (all layers have the same canvas size). */\n canvasW: number;\n /** Shared canvas height. */\n canvasH: number;\n /** Alpha-tight bounding box, top-left origin, +y down (image coords). */\n bbox: { x: number; y: number; w: number; h: number };\n /** Cropped image width = bbox.w. */\n cropW: number;\n /** Cropped image height = bbox.h. */\n cropH: number;\n /**\n * The layer's painted half-width on every CROP row, top → bottom, one entry\n * per row (`cropH` of them): half the row's opaque span in canvas px under\n * the same alpha ≥ 128 rule the head's `headEdges` are measured with\n * (`@ikijs/mcp`'s `measure-turn`), 0 for a row with no opaque pixel.\n * Optional, and only the FACE's is read: it gives the face plate a\n * row-dependent turn radius and a chin swing (`faceRowProfile`,\n * `turnSurface`); absent — as the editor's own import leaves it — the plate\n * turns on one radius on every row. Validated before anything reads it: an\n * array of length `cropH` whose every entry is a finite number in\n * [0, cropW / 2], the most an inclusive span inside the crop can be.\n */\n rowHalfWidths?: number[];\n /**\n * The tight box of the layer's alpha ≥ 128 pixels, in image coords like\n * `bbox` and NOT grown: the drawing a viewer reads inside a soft feather,\n * which the crop's alpha ≥ 8 rule takes in whole. Optional, and only the\n * NOSE's is read: its centre and width are the nose's turn landmark\n * (`turnLandmarks`), so a shaded nose is fitted by the drawing rather than\n * by its feather, and its top edge at its centre x is the bridge top the\n * nose tilts about on the turn (`NOSE_TURN_TILT_DEG`). Absent — as the\n * editor's own import leaves it — the crop stands in for both. Validated\n * before anything reads it: a plain object whose x, y, w and h are finite,\n * w and h positive, and the box inside `bbox`.\n */\n denseCore?: { x: number; y: number; w: number; h: number };\n}\n\n// ── Role table ───────────────────────────────────────────────────────────────\n\n/**\n * Single source of truth for every role the auto-rig generator understands.\n * Order values define back-to-front compositing (higher = in front).\n *\n * Eye-family roles use eyeSide to pair blink/gaze bindings correctly:\n * eye_L = character's left eye = screen right side.\n * eye_R = character's right eye = screen left side.\n */\nexport const ROLE_TABLE: Record<string, RoleSpec> = {\n // The silhouette behind the face. It rides the rigid head (not faceWarp,\n // whose grid it would stretch across the shoulders) as a MESH, so its ends\n // can swing on the hair-sway warps. It holds the head's outline STILL\n // through the turn — the head it hangs off no longer travels and it has no\n // turn warp of its own — while the face slides inside it.\n hair_back: { deformer: \"headDeformer\", order: 0, mesh: true },\n // The torso. It rides its own rigid deformer, not the head's: the head turns\n // about the neck pivot while the shoulders follow at BODY_TURN_FOLLOW and\n // follow the head's breath bob, which is what keeps the character from\n // reading as a floating head. Drawn over the back hair so long hair falls\n // behind the shoulders.\n body: { deformer: \"bodyDeformer\", order: 5, mesh: false },\n // The face plate and everything painted on it ride per-group turn warps\n // under the head (TurnGroupId): the plate its own, each eye stack its\n // side's, both mouth drawings one, the nose, each brow and each blush their\n // own. A group's grid is sized to its members alone and baked from the one\n // solved surface with the family's turn depth in its keyforms, so a feature\n // slides across the plate on its own grid instead of a translate binding\n // (see generateIkiFromLayerSet).\n face: { deformer: \"faceWarp\", order: 10, mesh: true },\n nose: { deformer: \"noseWarp\", order: 15, mesh: true },\n blush_L: { deformer: \"blushWarp_L\", order: 20, mesh: true },\n blush_R: { deformer: \"blushWarp_R\", order: 20, mesh: true },\n mouth: { deformer: \"mouthWarp\", order: 25, mesh: true },\n // An OPTIONAL second mouth drawing, open. When present the two cross-fade on\n // MouthOpen instead of the closed one being stretched, which is the\n // difference between a portrait rig and one that can lip-sync.\n mouth_open: { deformer: \"mouthWarp\", order: 26, mesh: true },\n eye_L: { deformer: \"eyeWarp_L\", order: 30, mesh: true, eyeSide: \"L\" },\n eye_R: { deformer: \"eyeWarp_R\", order: 30, mesh: true, eyeSide: \"R\" },\n iris_L: { deformer: \"eyeWarp_L\", order: 31, mesh: true, eyeSide: \"L\" },\n iris_R: { deformer: \"eyeWarp_R\", order: 31, mesh: true, eyeSide: \"R\" },\n pupil_L: { deformer: \"eyeWarp_L\", order: 32, mesh: true, eyeSide: \"L\" },\n pupil_R: { deformer: \"eyeWarp_R\", order: 32, mesh: true, eyeSide: \"R\" },\n highlight_L: { deformer: \"eyeWarp_L\", order: 33, mesh: true, eyeSide: \"L\" },\n highlight_R: { deformer: \"eyeWarp_R\", order: 33, mesh: true, eyeSide: \"R\" },\n // Upper lashes: an OPTIONAL separate layer ABOVE the iris that folds down to\n // the closed-eye seam (the same crease the white folds to), covering the cut\n // eyeball cleanly. When absent, the white's own fold is the only closed line.\n lash_L: { deformer: \"eyeWarp_L\", order: 34, mesh: true, eyeSide: \"L\" },\n lash_R: { deformer: \"eyeWarp_R\", order: 34, mesh: true, eyeSide: \"R\" },\n brow_L: { deformer: \"browWarp_L\", order: 40, mesh: true },\n brow_R: { deformer: \"browWarp_R\", order: 40, mesh: true },\n // The bangs ride the rigid head, like the back hair, and bind to no grid:\n // every motion of their own — the silhouette hold, the turn lead, the nod\n // and the sway — is a per-vertex warp attached in generateIkiFromLayerSet,\n // so they draw the head's outline where the plate under them bends and\n // slides (see bakeHairFrontSilhouetteWarp).\n hair_front: { deformer: \"headDeformer\", order: 50, mesh: true },\n};\n\n/**\n * Roles the generator requires. BOTH eyes are mandatory because the rig pairs\n * left/right blink parameters — a one-eyed rig would produce mismatched bindings.\n */\nexport const REQUIRED_ROLES = [\"face\", \"eye_L\", \"eye_R\", \"mouth\"] as const;\n\n// ── Alias map ────────────────────────────────────────────────────────────────\n\n/**\n * Minimal spelling-variant aliases → canonical role.\n * Only covers real-world variants; grow this only when you have evidence.\n */\nconst ALIAS_MAP: Record<string, string> = {\n eyebrow_L: \"brow_L\",\n eyebrow_R: \"brow_R\",\n eye_white_L: \"eye_L\",\n eye_white_R: \"eye_R\",\n};\n\n// ── normalizeRole ─────────────────────────────────────────────────────────────\n\n/**\n * Convert a raw filename to a canonical role key:\n * 1. Strip file extension.\n * 2. Lowercase.\n * 3. Collapse hyphens and spaces to underscores.\n * 4. Uppercase a trailing `_l` or `_r` side suffix → `_L` / `_R`.\n * 5. Apply alias map for known spelling variants.\n *\n * Examples:\n * \"Eye-L.png\" → \"eye_L\"\n * \"Brow_R.png\" → \"brow_R\"\n * \"eyebrow_L.png\" → \"brow_L\"\n */\nexport function normalizeRole(raw: string): string {\n // Strip extension\n const noExt = raw.replace(/\\.[^.]+$/, \"\");\n // Lowercase, then collapse hyphens/spaces → underscores\n const collapsed = noExt.toLowerCase().replace(/[-\\s]+/g, \"_\");\n // Uppercase trailing _l / _r side suffix\n const sided = collapsed.replace(\n /_([lr])$/,\n (_, s: string) => `_${s.toUpperCase()}`,\n );\n // Alias map\n return ALIAS_MAP[sided] ?? sided;\n}\n\n// ── assertRoleSet ─────────────────────────────────────────────────────────────\n\n/**\n * Single home for the unknown/duplicate/required role contract. Takes CANONICAL\n * role names (already normalized). Throws on:\n * - Unknown role (not in ROLE_TABLE)\n * - Duplicate role\n * - Missing required role\n *\n * The \"unknown role\" message from this function is fileName-free. That is\n * intentional: callers that lack a fileName (e.g. a future\n * `validateLayerInputs` that receives pre-normalized roles) get a useful error\n * without needing to pre-check. Callers that DO have the original fileName\n * (e.g. `parseLayerRoles`) pre-check unknown roles themselves so they can embed\n * the fileName in the message — but that pre-check is an enrichment, not a\n * requirement for correctness.\n */\nexport function assertRoleSet(roles: string[]): void {\n const seen = new Set<string>();\n for (const role of roles) {\n if (!(role in ROLE_TABLE)) {\n throw new Error(`auto-rig: unknown role \"${role}\"`);\n }\n if (seen.has(role)) {\n throw new Error(`auto-rig: duplicate role \"${role}\"`);\n }\n seen.add(role);\n }\n for (const required of REQUIRED_ROLES) {\n if (!seen.has(required)) {\n throw new Error(`auto-rig: missing required role \"${required}\"`);\n }\n }\n}\n\n// ── parseLayerRoles ───────────────────────────────────────────────────────────\n\n/**\n * Map an array of raw filenames to canonical `{ role, fileName }` pairs.\n *\n * Steps:\n * 1. Normalize each filename → role (normalizeRole).\n * 2. Eagerly check each role against ROLE_TABLE — unknown roles throw early\n * with the offending fileName included in the message.\n * 3. Call assertRoleSet to check duplicates + required roles.\n *\n * Throws a path-qualified Error on any contract violation.\n */\nexport function parseLayerRoles(\n fileNames: string[],\n): { role: string; fileName: string }[] {\n const pairs = fileNames.map((fileName) => {\n const role = normalizeRole(fileName);\n if (!(role in ROLE_TABLE)) {\n throw new Error(\n `auto-rig: unknown role \"${role}\" from file \"${fileName}\"`,\n );\n }\n return { role, fileName };\n });\n\n assertRoleSet(pairs.map((p) => p.role));\n return pairs;\n}\n\n// ── bboxToTransform ───────────────────────────────────────────────────────────\n\n/**\n * Convert an alpha bounding box (image coordinates, +y down, top-left origin)\n * to a model-space translation (model coordinates, +y up, canvas-center origin).\n *\n * x = bbox.x + bbox.w/2 - canvasW/2 (center of bbox relative to canvas center)\n * y = canvasH/2 - (bbox.y + bbox.h/2) (flip axis: image +y down → model +y up)\n *\n * Result is NOT rounded — fractional .5 values must be preserved to avoid\n * sub-pixel jitter in blink/gaze animations when the eye center falls between\n * two canvas pixels.\n *\n * @param partLabel Optional label for error messages (role or part id).\n */\nexport function bboxToTransform(\n bbox: { x: number; y: number; w: number; h: number },\n canvasW: number,\n canvasH: number,\n partLabel?: string,\n): { x: number; y: number } {\n if (bbox.w <= 0 || bbox.h <= 0) {\n throw new Error(`auto-rig: empty bbox for ${partLabel ?? \"layer\"}`);\n }\n const x = bbox.x + bbox.w / 2 - canvasW / 2;\n const y = canvasH / 2 - (bbox.y + bbox.h / 2); // flip: image +y-down → model +y-up\n return { x, y };\n}\n\n// ── validateLayerInputs ───────────────────────────────────────────────────────\n\n/**\n * Validate a LayerInput array before assembly. Called first inside\n * `generateIkiFromLayerSet` — the public API validates before deriving anything.\n *\n * Checks (in order):\n * 1. Non-empty layer list.\n * 2. Unknown/duplicate/required role contract via `assertRoleSet` (single home).\n * 3. Non-positive bbox.w, bbox.h, cropW, cropH per layer.\n * 4. Per-layer canvas size vs. the supplied `canvas` argument.\n * Matching every layer to the `canvas` arg inherently guarantees all layers\n * agree with each other — no separate peer-comparison loop is needed.\n * 5. `rowHalfWidths`, when present: an array of `cropH` finite numbers in\n * [0, cropW / 2] — checked before anything reads its length or entries.\n * 6. `denseCore`, when present on any layer: a plain object whose x, y, w\n * and h are finite, with w and h > 0 and the box inside `bbox`.\n *\n * Validates `layer.role` DIRECTLY (not via fileName). A caller could supply\n * `fileName:\"face.png\"` with `role:\"bad_role\"` — a filename check would miss it.\n *\n * Throws a plain `Error` with a path-qualified message on the first violation.\n */\nexport function validateLayerInputs(\n layers: LayerInput[],\n canvas: { width: number; height: number },\n): void {\n if (layers.length === 0) {\n throw new Error(\"auto-rig: validateLayerInputs: layers must not be empty\");\n }\n\n // Unknown / duplicate / required — single home for this contract\n assertRoleSet(layers.map((l) => l.role));\n\n for (const layer of layers) {\n const { role, bbox, cropW, cropH, canvasW, canvasH } = layer;\n if (bbox.w <= 0) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" has non-positive bbox.w (${bbox.w})`,\n );\n }\n if (bbox.h <= 0) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" has non-positive bbox.h (${bbox.h})`,\n );\n }\n if (cropW <= 0) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" has non-positive cropW (${cropW})`,\n );\n }\n if (cropH <= 0) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" has non-positive cropH (${cropH})`,\n );\n }\n if (canvasW !== canvas.width || canvasH !== canvas.height) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" canvas size (${canvasW}×${canvasH}) does not match canvas arg (${canvas.width}×${canvas.height})`,\n );\n }\n // The shape is checked before `.length` or an entry is read, so a plain-JS\n // caller with a malformed profile gets the path-qualified message rather\n // than a TypeError out of faceRowProfile.\n if (layer.rowHalfWidths !== undefined) {\n const rows: unknown = layer.rowHalfWidths;\n if (!Array.isArray(rows)) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" rowHalfWidths must be an array with one entry per crop row`,\n );\n }\n if (rows.length !== cropH) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" rowHalfWidths has ${rows.length} entries, not one per crop row (cropH ${cropH})`,\n );\n }\n for (let i = 0; i < rows.length; i++) {\n const a: unknown = rows[i];\n if (\n typeof a !== \"number\" ||\n !Number.isFinite(a) ||\n a < 0 ||\n a > cropW / 2\n ) {\n throw new Error(\n `auto-rig: validateLayerInputs: role \"${role}\" rowHalfWidths[${i}] is ${String(a)}, not a finite number in [0, ${cropW / 2}] (half the crop's width)`,\n );\n }\n }\n }\n // Checked on every layer that carries one, though only the nose's is read,\n // so a malformed box is refused rather than silently ignored.\n if (layer.denseCore !== undefined) {\n const where = `auto-rig: validateLayerInputs: role \"${role}\" denseCore`;\n const core: unknown = layer.denseCore;\n if (typeof core !== \"object\" || core === null || Array.isArray(core)) {\n throw new Error(`${where} must be a plain object { x, y, w, h }`);\n }\n for (const field of [\"x\", \"y\", \"w\", \"h\"] as const) {\n const v = (core as Record<string, unknown>)[field];\n const size = field === \"w\" || field === \"h\";\n if (typeof v !== \"number\" || !Number.isFinite(v) || (size && v <= 0)) {\n throw new Error(\n `${where}.${field} is ${String(v)}, not a finite number${size ? \" > 0\" : \"\"}`,\n );\n }\n }\n const { x, y, w, h } = layer.denseCore;\n const right = bbox.x + bbox.w;\n const bottom = bbox.y + bbox.h;\n if (x < bbox.x) {\n throw new Error(\n `${where}.x (${x}) lies left of the bbox's left edge (${bbox.x})`,\n );\n }\n if (y < bbox.y) {\n throw new Error(\n `${where}.y (${y}) lies above the bbox's top edge (${bbox.y})`,\n );\n }\n if (x + w > right) {\n throw new Error(\n `${where}.w (${w}) puts its right edge at ${x + w}, past the bbox's right edge (${right})`,\n );\n }\n if (y + h > bottom) {\n throw new Error(\n `${where}.h (${h}) puts its bottom edge at ${y + h}, past the bbox's bottom edge (${bottom})`,\n );\n }\n }\n }\n}\n\n// ── validateStrandEdges ───────────────────────────────────────────────────────\n\n/**\n * One side's iris and the bangs' run it would slide under on the turn, as\n * real pixel boundaries on ONE image row — the row holding that iris's centre\n * — in model coordinates: x is canvas x minus half the canvas width, like\n * `bboxToTransform`'s crop edges, and `y` is that row's centre. Every x is the\n * painted edge facing its clear neighbour, read under one opacity rule, so the\n * gap between an iris edge and a run edge is a real distance with no margin to\n * add. \"Outer\" is toward the head's edge on that side. The run is the one a\n * producer judges a strand: it may skip runs it judges hair detail\n * (`@ikijs/mcp` treats those narrower than half the iris's painted width on\n * the row as clear), so an iris under only such a wisp counts as clear.\n *\n * One shape covers three cases:\n * - an ordinary side strand outward of a clear iris (`runFace` outward of\n * `irisOuter`);\n * - a run over the iris centre that clears on the face side (`runFace`\n * inward of that centre);\n * - a fringe spanning the face: the run still covers the OTHER iris's\n * centre, so it has no face-side end on this side and `runFace` is\n * `null` — an absent edge, not an invented one.\n */\nexport interface IrisStrand {\n /** The iris row's centre, model y. */\n y: number;\n /** The outer end of that iris layer's opaque span on the row. */\n irisOuter: number;\n /** Its inner end, toward the face. */\n irisInner: number;\n /** The outer end of the `hair_front` run the iris would slide under. */\n runOuter: number;\n /** The run's face-side end, or `null` when it reaches the other iris's\n * centre. */\n runFace: number | null;\n}\n\n/**\n * Check `options.strandEdges` against the layers it claims to measure — called\n * right after `validateLayerInputs`, so nothing is derived from an unchecked\n * edge. Absent or `{}` returns at once, with no layer requirement: the rig is\n * then the one built without it, byte for byte.\n *\n * Otherwise the option has to be a plain object, and each PRESENT side (an\n * absent one is never checked) has to be a real measurement on these layers:\n * - a plain object whose `y`, `irisOuter`, `irisInner` and `runOuter` are\n * finite and whose `runFace` is finite or `null`;\n * - on a layer set with `hair_front`, `iris_L` and `iris_R` — both irises,\n * because a side's iris is whichever has its crop centre on that side,\n * paired by x rather than by role name;\n * - `y` inside that iris's crop rows and inside `hair_front`'s;\n * - both iris edges inside that iris's crop columns and `irisOuter`\n * strictly outward of `irisInner` — which ties them to their own iris\n * without asking where its span sits against the crop's centre, which a\n * row's span need not straddle;\n * - `runOuter` and a finite `runFace` inside `hair_front`'s crop columns;\n * - `runOuter` strictly outward of that iris's crop centre — a spanning run\n * too, so it is still anchored on its own side — and strictly outward of\n * a finite `runFace`;\n * - a finite `runFace` strictly on this side of the other iris's centre,\n * since a run that reaches it has no face-side end on this side.\n *\n * Throws a plain `Error` with a path-qualified message on the first violation.\n */\nfunction validateStrandEdges(\n layers: LayerInput[],\n strandEdges: { left?: IrisStrand; right?: IrisStrand } | undefined,\n): void {\n if (strandEdges === undefined) return;\n const where = \"auto-rig: validateStrandEdges: strandEdges\";\n const isPlainObject = (v: unknown): v is Record<string, unknown> =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\n if (!isPlainObject(strandEdges)) {\n throw new Error(\n `${where} must be a plain object with optional left / right sides`,\n );\n }\n for (const key of [\"left\", \"right\"] as const) {\n const raw: unknown = strandEdges[key];\n if (raw === undefined) continue;\n const path = `${where}.${key}`;\n if (!isPlainObject(raw)) {\n throw new Error(`${path} must be a plain object`);\n }\n for (const field of [\"y\", \"irisOuter\", \"irisInner\", \"runOuter\"] as const) {\n const v = raw[field];\n if (typeof v !== \"number\" || !Number.isFinite(v)) {\n throw new Error(\n `${path}.${field} is ${String(v)}, not a finite number`,\n );\n }\n }\n const rawFace = raw.runFace;\n if (\n rawFace !== null &&\n (typeof rawFace !== \"number\" || !Number.isFinite(rawFace))\n ) {\n throw new Error(\n `${path}.runFace is ${String(rawFace)}, not a finite number or null`,\n );\n }\n const { y, irisOuter, irisInner, runOuter, runFace } =\n raw as unknown as IrisStrand;\n\n const cropOf = (role: string) => {\n const layer = layers.find((l) => l.role === role);\n if (layer === undefined) {\n throw new Error(\n `${path} needs a \"${role}\" layer to be measured on, and this layer set has none`,\n );\n }\n const t = bboxToTransform(layer.bbox, layer.canvasW, layer.canvasH, role);\n return {\n role,\n x: t.x,\n left: t.x - layer.cropW / 2,\n right: t.x + layer.cropW / 2,\n bottom: t.y - layer.cropH / 2,\n top: t.y + layer.cropH / 2,\n };\n };\n const hair = cropOf(\"hair_front\");\n const [lowIris, highIris] = [cropOf(\"iris_L\"), cropOf(\"iris_R\")].sort(\n (a, b) => a.x - b.x,\n );\n // Outward is −x on the left side, +x on the right.\n const sign = key === \"left\" ? -1 : 1;\n const iris = key === \"left\" ? lowIris : highIris;\n const other = key === \"left\" ? highIris : lowIris;\n const fail = (field: keyof IrisStrand, value: number, why: string) =>\n new Error(`${path}.${field} (${value}) ${why}`);\n\n for (const crop of [iris, hair]) {\n if (y < crop.bottom || y > crop.top) {\n throw fail(\n \"y\",\n y,\n `lies outside the \"${crop.role}\" crop's rows (${crop.bottom}…${crop.top})`,\n );\n }\n }\n for (const [field, x] of [\n [\"irisOuter\", irisOuter],\n [\"irisInner\", irisInner],\n ] as const) {\n if (x < iris.left || x > iris.right) {\n throw fail(\n field,\n x,\n `lies outside the \"${iris.role}\" crop's columns (${iris.left}…${iris.right})`,\n );\n }\n }\n if (sign * (irisOuter - irisInner) <= 0) {\n throw fail(\n \"irisOuter\",\n irisOuter,\n `must lie strictly outward of strandEdges.${key}.irisInner (${irisInner})`,\n );\n }\n for (const [field, x] of [\n [\"runOuter\", runOuter],\n [\"runFace\", runFace],\n ] as const) {\n if (x !== null && (x < hair.left || x > hair.right)) {\n throw fail(\n field,\n x,\n `lies outside the \"hair_front\" crop's columns (${hair.left}…${hair.right})`,\n );\n }\n }\n if (sign * (runOuter - iris.x) <= 0) {\n throw fail(\n \"runOuter\",\n runOuter,\n `must lie strictly outward of the \"${iris.role}\" crop's centre (${iris.x})`,\n );\n }\n if (runFace !== null && sign * (runOuter - runFace) <= 0) {\n throw fail(\n \"runOuter\",\n runOuter,\n `must lie strictly outward of strandEdges.${key}.runFace (${runFace})`,\n );\n }\n if (runFace !== null && sign * (runFace - other.x) <= 0) {\n throw fail(\n \"runFace\",\n runFace,\n `must lie strictly on this side of the \"${other.role}\" crop's centre (${other.x}); a run that reaches it has no face-side end here, which is runFace null`,\n );\n }\n }\n}\n\n// ── generateGridPoints ────────────────────────────────────────────────────────\n\n/**\n * Generate the flat `[x0,y0, x1,y1, …]` rest-grid control points for a\n * regular axis-aligned lattice with `(cols+1)*(rows+1)` points, row-major.\n *\n * Row 0 is the TOP (y = maxY); y strictly decreases with row index.\n * Column 0 is left (x = minX); x strictly increases with column index.\n * This ordering satisfies `checkGridRegularity` in the format validator.\n *\n * Local copy — do NOT import the private `generateRegularGridPoints` from\n * factories.ts; that helper is private to this package's factory layer.\n */\nexport function generateGridPoints(\n cols: number,\n rows: number,\n minX: number,\n maxX: number,\n minY: number,\n maxY: number,\n): number[] {\n const pts: number[] = [];\n for (let row = 0; row <= rows; row++) {\n const t = row / rows;\n const y = maxY - t * (maxY - minY); // maxY at row 0, minY at row `rows`\n for (let col = 0; col <= cols; col++) {\n const s = col / cols;\n const x = minX + s * (maxX - minX); // minX at col 0, maxX at col `cols`\n pts.push(x, y);\n }\n }\n return pts;\n}\n\n// ── createPixelGridMesh ───────────────────────────────────────────────────────\n\n/**\n * Create a regular grid mesh in PIXEL space, with the local origin at the\n * crop center (matching the `feature(...)` convention in sample-model.ts).\n *\n * Callers set `part.width=1, part.height=1` so the engine's scale pipeline is\n * bypassed — the pixel coordinates ARE the final geometry, positioned only by\n * `part.transform`. scaleX/scaleY bindings then scale about each part's own\n * center without an additional unit-to-pixel conversion step.\n *\n * Vertices span x ∈ [-w/2, w/2] and y ∈ [-h/2, h/2] (+y up, engine convention).\n * Row 0 is the TOP of the grid (y = +h/2); row index increases downward.\n *\n * UVs are base unit-square coordinates: u = col/cols (0..1 left→right),\n * v = row/rows (0..1 top→bottom). Top row maps to v=0 (v and y run in\n * opposite directions — keeps textures upright). Atlas remapping is the\n * caller's responsibility (e.g. applyAtlas), not done here.\n *\n * Index winding per cell: [BL, BR, TL] then [TL, BR, TR] — same as\n * `createGridMesh` in factories.ts so the engine's implicit-quad convention\n * is preserved.\n */\nexport function createPixelGridMesh(\n cols: number,\n rows: number,\n w: number,\n h: number,\n): IkiMesh {\n const colVerts = cols + 1;\n const rowVerts = rows + 1;\n\n const vertices: number[] = [];\n const uvs: number[] = [];\n\n // Row 0 = TOP (y = +h/2). Row `rows` = BOTTOM (y = -h/2).\n // Col 0 = left (x = -w/2). Col `cols` = right (x = +w/2).\n for (let row = 0; row < rowVerts; row++) {\n const t = row / rows;\n const y = h / 2 - t * h; // +h/2 at row 0, -h/2 at row `rows`\n const v = t; // 0 at top, 1 at bottom\n\n for (let col = 0; col < colVerts; col++) {\n const s = col / cols;\n const x = -w / 2 + s * w; // -w/2 at col 0, +w/2 at col `cols`\n const u = s; // 0 at left, 1 at right\n\n vertices.push(x, y);\n uvs.push(u, v);\n }\n }\n\n // Two triangles per cell: [BL, BR, TL] then [TL, BR, TR]\n const indices: number[] = [];\n for (let row = 0; row < rows; row++) {\n for (let col = 0; col < cols; col++) {\n const tl = row * colVerts + col;\n const tr = row * colVerts + col + 1;\n const bl = (row + 1) * colVerts + col;\n const br = (row + 1) * colVerts + col + 1;\n\n indices.push(bl, br, tl);\n indices.push(tl, br, tr);\n }\n }\n\n return { vertices, uvs, indices };\n}\n\n// ── Mesh density ─────────────────────────────────────────────────────────────\n\n/** Target size of one mesh cell. A part's vertices sample its group's turn\n * grid (and its own per-vertex warps) at this spacing; the GPU is linear\n * between them, so the spacing bounds how smooth a bend can render. 64 px\n * keeps a face-mesh cell near a plate-grid cell on the hero (67 px against\n * the 45 px FACE_PLATE_CELLS gives its plate) and puts eight rows on the back\n * hair for the sway curl. */\nconst MESH_CELL_PX = 64;\n/** Cell-count floor / ceiling per axis: small parts stay at 4×4 (their\n * bakes fold/collapse them, they never bend), a canvas-spanning layer is\n * capped at 81 vertices so per-vertex warps stay cheap. */\nconst MESH_CELLS_MIN = 4;\nconst MESH_CELLS_MAX = 8;\n\n/** Cell counts for a pixel-grid mesh of a `w`×`h` crop: `round(size / MESH_CELL_PX)`\n * per axis, clamped to [MESH_CELLS_MIN, MESH_CELLS_MAX]. Exported for tests. */\nexport function meshCellsFor(\n w: number,\n h: number,\n): { cols: number; rows: number } {\n const cells = (px: number) =>\n Math.max(\n MESH_CELLS_MIN,\n Math.min(MESH_CELLS_MAX, Math.round(px / MESH_CELL_PX)),\n );\n return { cols: cells(w), rows: cells(h) };\n}\n\n/** Cells per axis of the face plate's turn grid (`faceWarp`). Bilinear cells\n * render the cylinder bend as a chord: the shared 6-cell grid over the whole\n * head put 137 px cells under the hero's plate; 10 cells over the plate's own\n * 450 px are 45 px chords, under 0.3 px of sag at full turn, and keep the\n * nod's chord under 0.35 px on the same rows. The plate is the one group\n * read at every guard row and under the bangs' join, so it gets the most\n * cells: 121 nodes × 25 stops × 2 is ≈6 000 keyform numbers, ≈0.1 MB at JSON\n * precision. 8 is the fallback if the byte budget (1.4 MB on the hero)\n * bites. The fold guard is the cylinder's own bound and does not depend on\n * this. */\nconst FACE_PLATE_CELLS = 10;\n/** Cells per axis of every feature group's turn grid. A feature grid spans a\n * part a few tens of px across plus its bindings' reach and its turn shift,\n * so on the eyes and brows 4 cells are ≈60 px chords — under 0.2 px of sag;\n * a grid its family's depth cap widens has far wider cells, which the mouth\n * refines over its own drawings (MOUTH_GRID_CELL_PX) — and the hero's six\n * feature groups, five at 25 nodes and the mouth's at 45\n * (MOUTH_GRID_CELL_PX), add ≈8 500 keyform numbers: with the plate's,\n * ≈0.2 MB over the shared grid they replace, which took the hero from\n * 1.09 MB to ≈1.3 MB. */\nconst FEATURE_GRID_CELLS = 4;\n/** The widest cell the mouth's turn grid keeps over its own drawings. A\n * feature grid is sized by its family's depth cap (`familyReachPx`), and the\n * mouth's — 166 px on the hero — is about 7× the 24 px it actually shifts,\n * so its FEATURE_GRID_CELLS cells come out 133.8 px wide and a 77 px mouth\n * spans two of them. A cell's chord misses the bend in proportion to its\n * width² over the row's radius, and the jaw's taper cuts the mouth row's to 220 px on the\n * hero: 133.8 px cells missed the turn surface by up to 5.3 model px at\n * ±30, 45.7 px cells by 0.63, so 32 px bounds it near 0.3 and the hero's\n * 24.5 px cells predict ≈0.2. `groupGridFor` adds columns over the members\n * alone, so the grid keeps the depth cap's reach. */\nconst MOUTH_GRID_CELL_PX = 32;\n/** Column pitch of the turn's VIRTUAL lattice — the one row of columns\n * `TurnSurface.mapAt` is piecewise-linear between, anchored on the face\n * centre (`faceCenterX ± k·16`) so the axis is a node and its dx the slide\n * exactly. Never shipped: every group grid's node samples it. At 16 px the\n * chord of the bend stays under 0.25 px at any radius the sweep tries\n * (≈0.2 px at the hero plate's edge, 0.0005 in cue units), so the map IS the\n * analytic bend as far as a cue can tell, where a coarser lattice would put\n * its kinks wherever a group grid's nodes happened to fall. */\nconst TURN_LATTICE_CELL_PX = 16;\n/** Margin a grid is grown by, as a fraction of its span per axis: the turn\n * family's union for the head's vertical reach, and every group grid on top\n * of the extent its members can reach (`groupGridFor`), so no vertex lands\n * on a grid boundary and gets clamped by `bindPointToRestGrid`. */\nconst GRID_MARGIN = 0.12;\n\n// ── Head-turn cylinder constants ─────────────────────────────────────────────\n\n/** Margin between a cylinder's radius and the reach it has to cover: at\n * |local|/radius <= 1/1.2 the asin stays clear of +/-1 and asin(1/1.2) + 30°\n * is still under 90°, so the bend never folds. The nod radius is derived from\n * the head's vertical reach this way; the turn radius is an input, and\n * radius/factor is the bound `boundedCylinderBend` clamps `local` to. */\nconst HEAD_CYLINDER_RADIUS_FACTOR = 0.6 / 0.5;\n/** Outer keyform stop of the head-turn bake, matching ParamAngleX's range. */\nconst HEAD_TURN_MAX_DEG = 30;\n\n/** Keyform stops of the head-turn / nod bakes, degrees. The engine blends\n * parameter-LINEARLY between stops but the cylinder bend is sin-based, so the\n * stops must be dense enough that the chord stays near the arc: at 15° spacing\n * the worst mid-stop error on the hero's grid is ≈4 px at the outer column\n * (3.7 px at 7.5°, 4.0 px at 22.5°) against 15.5 px with stops at ±30 only.\n * The bake is analytic, so extra stops cost only model bytes. Ascending,\n * symmetric, includes 0 (the rest cell). */\nconst HEAD_TURN_STOPS = [-30, -15, 0, 15, 30] as const;\n\n/**\n * Sideways slide, at full head turn, of a surface sitting one full cylinder\n * radius in front of the rotation axis — the unit of depth parallax.\n *\n * This is exactly the bulk `axisShift` the cylinder bake subtracts back out of\n * the face warp. Pinning it there was right: applied to the face IN FULL it\n * shoved the head off the shoulders. But it is also the whole depth cue, so\n * layers that do NOT sit on the cylinder's axis have to get their own share of\n * it back, scaled by how far in front of (or behind) the axis they sit — the\n * bangs (HAIR_FRONT_DEPTH) and the features on the face (the solved\n * TurnDepths). The head's own travel is not one of those shares: it rides the\n * same bake as a separate uniform slide over every column (`turnSlide`), sized\n * by what the held shell has room for rather than by a depth.\n * `headNodParallaxUnit` is the same quantity on the nod axis.\n *\n * Takes the cylinder radius the face warp was baked with, so the two cannot\n * drift apart: the unit IS that bake's pinned-out `axisShift`.\n */\nexport function headTurnParallaxUnit(radius: number): number {\n return radius * Math.sin(HEAD_TURN_MAX_DEG * (Math.PI / 180));\n}\n\n/**\n * Displacement of a point at signed distance `local` from the cylinder axis\n * after the cylinder rotates by `theta`, with the axis column pinned.\n *\n * Rotating a cylinder slides its whole visible surface sideways by\n * RADIUS*sin(theta) — 170px on a 430px-wide face at 30 degrees — on top of the\n * foreshortening. That bulk slide is what shoves the head off the shoulders and\n * detaches the back hair; the foreshortening alone is what reads as a turn.\n * Subtracting it leaves the differential and keeps the offsets monotonic, so no\n * cell folds. Deliberate bulk head motion is added back on its own, where it can\n * be tuned independently — the turn's as the 2D bake's uniform `travel` slide,\n * the nod's as a headDeformer translate — and the bangs get their\n * depth-scaled share of it back through `headTurnParallaxUnit`.\n */\nfunction pinnedCylinderBend(\n local: number,\n radius: number,\n theta: number,\n): number {\n // Clamp local/radius to [-1,1] to keep asin defined at boundary points.\n const alpha = Math.asin(Math.max(-1, Math.min(1, local / radius)));\n return radius * Math.sin(alpha + theta) - local - radius * Math.sin(theta);\n}\n\n/**\n * `pinnedCylinderBend` with the surface bounded to the part of the cylinder it\n * can actually carry: |local| is clamped to radius/HEAD_CYLINDER_RADIUS_FACTOR\n * and the overshoot rides along unchanged.\n *\n * Beyond that bound the surface has wrapped past the cylinder's side and the\n * bend turns back on itself: once alpha + theta passes 90° a point further out\n * lands NEARER the axis than the one inside it, and once the asin saturates\n * every remaining point piles onto the SAME x (the `- local` term cancels).\n * Either way the grid folds. Holding the bound's displacement and adding the\n * overshoot back gives those columns slope 1: they follow the silhouette\n * rigidly, keeping their order and their spacing. Inside the bound nothing\n * changes — a radius scaled from the grid's own reach puts the bound exactly\n * on the outer columns, so the generated rig never leaves the surface.\n */\nfunction boundedCylinderBend(\n local: number,\n radius: number,\n theta: number,\n): number {\n const bound = radius / HEAD_CYLINDER_RADIUS_FACTOR;\n const onSurface = Math.max(-bound, Math.min(bound, local));\n // The return is a DISPLACEMENT, so `local + dx` already carries the\n // overshoot: holding the bound's dx is what makes the outside rigid.\n return pinnedCylinderBend(onSurface, radius, theta);\n}\n\n/** The head's uniform sideways slide at one turn stop: the whole `travel` at\n * ±HEAD_TURN_MAX_DEG, linear in between, none at rest. The bake and\n * `turnColumnMap` share it so the map cannot describe a slide the grid does\n * not carry. */\nfunction turnSlide(travel: number, angleX: number): number {\n return (travel * angleX) / HEAD_TURN_MAX_DEG;\n}\n\n// ── faceRowProfile ───────────────────────────────────────────────────────────\n\n/** Floor of a face row's half-width, as a fraction of the widest row's. The\n * rows under the chin are sub-threshold shading and near-empty rows whose\n * own span would bend them on a cylinder a few px wide; the floor keeps\n * every row's radius a usable fraction of the eye row's. */\nconst FACE_ROW_MIN_FRACTION = 0.15;\n/** Width of the centred moving average over the measured rows, as a fraction\n * of the crop's height: wide enough to take the pixel steps out of an\n * alpha-thresholded outline, narrow enough to leave the jaw's taper. */\nconst FACE_ROW_SMOOTHING = 0.05;\n/** How far the chin swings toward the NEAR side at full turn, as a fraction\n * of the plate's half-width, scaled by how much narrower than the widest row\n * a row is (`TurnSurface.swingAt`): a turned head's jaw comes round with it\n * while the cranium stays. Judged on the hero two-up (slice ④): 0.05\n * under-read the 30° reference's chin, 0.08 is the ladder value that reads. */\nconst CHIN_SWING = 0.08;\n\n/**\n * The face plate's painted half-width per row, read off its layer's\n * `rowHalfWidths` (`LayerInput`) in model y. Exported at module level for the\n * tests, not from the package.\n */\nexport interface FaceRowProfile {\n /** The painted half-width, px, on the row resting at model `y`: linear\n * between rows, the end rows beyond the crop. */\n at(y: number): number;\n /** The widest row's half-width — what every row above it reads. */\n aMax: number;\n /** Model y of the widest row (the lowest of them when several tie): rows\n * ABOVE it — greater y, model y running up — read `aMax`; rows at and\n * below it are the measured ones, and they alone taper and swing. The\n * bangs hanging below it hold as it does (`hairFrontHoldTarget`). */\n widestY: number;\n /** The crop's own half-width, px: the bound every entry respects and the\n * width the chin swing is a fraction of. */\n faceHalfWidth: number;\n}\n\n/**\n * The face's row profile, or `undefined` when its layer measured none — or\n * measured no opaque row at all, which is the same thing to the turn.\n *\n * The face layer's alpha is NOT the head's width on every row: the hairline\n * rows sit under the bangs (on the hero the top crop row reads 6 % of the\n * cheek's half-width) and the rows under the chin are neck shading below the\n * threshold. Bent on their own span those rows would ride a cylinder a few px\n * wide and the far forehead would land OUTSIDE the cheek at full turn — a\n * cranium bulging past the face. So: the widest row is found among the raw\n * positive entries; every row ABOVE it reads that width (the cranium is as\n * wide as the cheeks; only the jaw tapers); the rows at and below it are the\n * measured ones, an empty row filled from the nearest positive row above it,\n * smoothed by a centred moving average FACE_ROW_SMOOTHING of the crop's\n * height wide (an odd window of at least three rows, the end rows repeated\n * past the crop) and floored at FACE_ROW_MIN_FRACTION of the widest. `at` is\n * linear between the rows' centres and clamps to the end rows outside the\n * crop, so a grid node on the plate's margin reads the row nearest it.\n */\nexport function faceRowProfile(\n face: LayerInput,\n faceCenterY: number,\n): FaceRowProfile | undefined {\n const raw = face.rowHalfWidths;\n if (raw === undefined) return undefined;\n const n = raw.length;\n let aMax = 0;\n let widest = -1;\n for (let i = 0; i < n; i++) {\n if (raw[i] > 0 && raw[i] >= aMax) {\n aMax = raw[i];\n widest = i;\n }\n }\n if (widest < 0) return undefined;\n\n // Above the widest row aMax; at and below it the measurement, an empty row\n // taking the nearest positive row above it (the widest row is positive, so\n // there always is one).\n const filled: number[] = [];\n for (let i = 0; i < n; i++) {\n filled.push(i < widest ? aMax : raw[i] > 0 ? raw[i] : filled[i - 1]);\n }\n const half = Math.max(1, Math.round((FACE_ROW_SMOOTHING * n) / 2));\n const floor = FACE_ROW_MIN_FRACTION * aMax;\n const rows: number[] = [];\n for (let i = 0; i < n; i++) {\n if (i < widest) {\n rows.push(aMax);\n continue;\n }\n let sum = 0;\n for (let k = i - half; k <= i + half; k++) {\n sum += filled[Math.max(0, Math.min(n - 1, k))];\n }\n rows.push(Math.max(floor, sum / (2 * half + 1)));\n }\n\n // Crop row i is the pixel row whose centre rests at top − i − ½: image rows\n // run down where model y runs up.\n const top = faceCenterY + face.cropH / 2;\n const rowAt = (y: number) => Math.max(0, Math.min(n - 1, top - y - 0.5));\n return {\n at: (y) => {\n const r = rowAt(y);\n const i = Math.floor(r);\n if (i + 1 >= n) return rows[n - 1];\n return rows[i] + (rows[i + 1] - rows[i]) * (r - i);\n },\n aMax,\n widestY: top - widest - 0.5,\n faceHalfWidth: face.cropW / 2,\n };\n}\n\n// ── TurnSurface ───────────────────────────────────────────────────────────────\n\n/**\n * The analytic head every bake samples and the solve fits: a turn cylinder\n * about the vertical axis through `faceCenterX` and a nod cylinder about the\n * horizontal axis through `faceCenterY`, each read at a point's REST position.\n *\n * `mapAt` is the turn as `turnColumnMap` describes it on the `lattice`'s\n * columns — the bounded bend on the row's own radius plus the slide of\n * `travel` and the row's own chin swing — so `turnColumnMap` /\n * `pinnedCylinderBend` / `boundedCylinderBend` stay the primitive and\n * `solveTurnModel` stays the fitter: it fits `radius` and `travel` on these\n * very maps, and `bakeTurnGroupWarp2D` reads its dx off the same ones, which\n * is what makes a solved cue a promise about the shipped keyforms. The\n * lattice is VIRTUAL — a dense row of columns anchored on the face centre\n * (`TURN_LATTICE_CELL_PX`) that nothing ships: every group grid's node reads\n * the map at its own rest x, and a rendered vertex reads those nodes\n * bilinearly, which is what the solve reproduces through a landmark's carrier\n * (`TurnLandmark.carrier`).\n */\ninterface TurnSurface {\n faceCenterX: number;\n faceCenterY: number;\n /** Turn cylinder radius at the EYE ROW, px: solved, or the lattice's own\n * reach with the no-fold margin when there is nothing to solve against.\n * `radiusAt` scales it per row. */\n radius: number;\n /** The head's sideways travel at full turn, px — see `turnSlide`. */\n travel: number;\n /** Nod cylinder radius, px: the head's vertical reach about `faceCenterY`\n * with the no-fold margin — one value for the whole rig, sized where the\n * turn family's union is (`turnSetup`). */\n nodRadius: number;\n /** The columns `mapAt` is piecewise-linear between; row 0 is all it reads. */\n lattice: IkiWarpGrid;\n /** The turn cylinder's radius on the row resting at `y`: `radius` scaled by\n * that row's painted half-width over the eye row's (`FaceRowProfile`), so\n * every row's own painted edge lands at the SAME fraction of its\n * half-width; `radius` on every row without a profile. */\n radiusAt(y: number): number;\n /** The chin's swing on the row resting at `y`, as a signed addition to\n * `travel`: −CHIN_SWING · faceHalfWidth · (1 − a(y)/aMax) for a row BELOW\n * the widest (smaller y), 0 at and above it and everywhere without a\n * profile. Negative, so it opposes the slide — at −30° the far side is −x\n * and the slide is −travel, and the swing's `turnSlide` share is a POSITIVE\n * offset, toward the near side — and ∝ deg like the slide, so it vanishes\n * at rest. */\n swingAt(y: number): number;\n /** The turn's column map at `deg` for a point resting at `y`: the bend on\n * `radiusAt(y)` plus `turnSlide(travel + swingAt(y), deg)` — one map per\n * row (`rowMapsOf` memoises them), every row the same one without a\n * profile. */\n mapAt(deg: number, y: number): TurnColumnMap;\n /** The nod's vertical displacement of a point resting at `y`, at `angleY`\n * degrees of ParamAngleY: the pinned cylinder bend about `faceCenterY` at\n * NOD_BEND of the angle — see that constant for why a full nod is not a\n * full 30° bend. */\n nodBendAt(y: number, angleY: number): number;\n}\n\n/** A `TurnSurface` from its numbers; the samplers are derived from them and\n * nothing else is. Exported at module level for the bake tests, not from the\n * package. */\nexport function turnSurface(spec: {\n faceCenterX: number;\n faceCenterY: number;\n radius: number;\n travel: number;\n nodRadius: number;\n lattice: IkiWarpGrid;\n /** The face's own row profile, when its layer measured one. Without it\n * every row turns on `radius` with no swing — slice ③'s bake, byte for\n * byte. */\n profile?: FaceRowProfile;\n /** The row `radiusAt` is normalised at — the eye row the solve fits, so the\n * report stays a promise about that row. Read only with a `profile`; the\n * face centre when not given. */\n eyeRowY?: number;\n}): TurnSurface {\n const { faceCenterX, faceCenterY, radius, travel, nodRadius, lattice } = spec;\n const { profile } = spec;\n let radiusAt = (_y: number) => radius;\n let swingAt = (_y: number) => 0;\n if (profile !== undefined) {\n // Normalised at the eye row: a(y)/radiusAt(y) = a(eyeRow)/radius on every\n // row, and a(eyeRow) ≤ faceHalfWidth ≤ radius/HEAD_CYLINDER_RADIUS_FACTOR\n // (the sweep's floor), so every row's own painted edge stays on the\n // analytic branch of boundedCylinderBend. The ratio is taken first so a\n // row as wide as the eye row reads `radius` exactly, not to an ulp.\n const aEye = profile.at(spec.eyeRowY ?? faceCenterY);\n radiusAt = (y) => radius * (profile.at(y) / aEye);\n // Below the widest row only (Decision 5): the gate is what keeps the\n // cranium — every row reading aMax, where the term is 0 anyway — free of\n // the swing by construction. It bites only where the smoothing pulls the\n // widest row itself under aMax (two 192/96 bands: 145.5 there), so the\n // swing steps in across that one row by the smoothing's own amount.\n swingAt = (y) =>\n y < profile.widestY\n ? -CHIN_SWING *\n profile.faceHalfWidth *\n Math.max(0, 1 - profile.at(y) / profile.aMax)\n : 0;\n }\n return {\n faceCenterX,\n faceCenterY,\n radius,\n travel,\n nodRadius,\n lattice,\n radiusAt,\n swingAt,\n mapAt: (deg, y) =>\n turnColumnMap(\n lattice,\n faceCenterX,\n radiusAt(y),\n deg,\n travel + swingAt(y),\n ),\n nodBendAt: (y, angleY) =>\n pinnedCylinderBend(\n y - faceCenterY,\n nodRadius,\n angleY * NOD_BEND * (Math.PI / 180),\n ),\n };\n}\n\n// ── bakeTurnGroupWarp2D ───────────────────────────────────────────────────────\n\n/**\n * Bake one group's turn AND nod as a 2D grid warp over AngleX × AngleY, every\n * node of `grid` read off the `surface` at its own rest position: the turn's\n * offset is where `groupNodeLanding` lands the node on that stop's maps — the\n * family shifted by `shiftAt(deg)`, the group's own turn shift at that stop, 0\n * for a group with none, and turned about `anchor` when the group has one —\n * less the node's rest position, the same rule the solve reads these nodes\n * back by; the nod adds its bend at the node's rest y to dy.\n *\n * The turn and the nod add, the same convention the playground's 2D bake\n * ships: the row at AngleY = 0 is the turn alone and the column at AngleX = 0\n * the nod alone. At a turned stop a node's turn offset may depend on both of\n * its rest coordinates — dx through the map read on the node's own row (its\n * radius and chin swing), and an anchored group's dx and dy both through its\n * tilt — while the nod moves a node on y alone, by an amount set by its rest\n * y and the pitch.\n *\n * Each axis's term at its own rest stop — the landing's at AngleX 0, the\n * nod's at AngleY 0 — is written as a literal zero rather than evaluated, and\n * never added to the other axis's term, so a cell with one turned axis carries\n * exactly that axis's number: at rest each axis is the identity by definition,\n * and `R·sin(asin(l/R)) − l` leaves a ≈3e-14 residue that would otherwise\n * ship — the hero's rest cell used to carry seven of them. That rule discards\n * `shiftAt(0)` too, so it assumes `shiftAt(0) = 0` — which every turn shift\n * satisfies by construction, being proportional to the stop's own degrees\n * (`depth · unit · deg / 30`) — the row's chin swing with it, which rides\n * `turnSlide` and is 0 at the 0° stop the same way, and an anchored group's\n * tilt, which is proportional to the stop too.\n *\n * The surface's `travel` belongs in the grid rather than on a headDeformer\n * translate because a rigid head translate carries the hair shell along with\n * it: in the grid only what rides the grid slides, so the face plate and its\n * features travel inside a silhouette the bangs hold still\n * (`bakeHairFrontSilhouetteWarp`), which is what a turned head actually does.\n * Being uniform it bends nothing — the AngleX = 0 cells stay all-zero and dy\n * never sees it — and being linear in AngleX it is the one part of this bake\n * the engine's parameter-linear blend reproduces EXACTLY between the stops:\n * the chord error the HEAD_TURN_STOPS spacing bounds is all the bend's.\n *\n * Layout is the format's row-major `k(i, j) = j * valuesX.length + i`.\n */\nexport function bakeTurnGroupWarp2D(\n grid: IkiWarpGrid,\n parameterX: string,\n parameterY: string,\n surface: TurnSurface,\n shiftAt: (deg: number) => number,\n anchor?: TurnAnchor,\n): IkiGrid2DWarp {\n const STOPS = [...HEAD_TURN_STOPS];\n const pointCount = grid.points.length / 2;\n\n const keyforms2d: { offsets: number[] }[] = [];\n for (const angleY of STOPS) {\n for (const angleX of STOPS) {\n const shift = shiftAt(angleX);\n const mapAt = (nodeY: number) => surface.mapAt(angleX, nodeY);\n const offsets: number[] = [];\n for (let i = 0; i < pointCount; i++) {\n const x = grid.points[i * 2];\n const y = grid.points[i * 2 + 1];\n const nodDy = angleY === 0 ? 0 : surface.nodBendAt(y, angleY);\n if (angleX === 0) {\n offsets.push(0, nodDy);\n continue;\n }\n const landed = groupNodeLanding(anchor, mapAt, angleX, shift, x, y);\n const turnDy = landed.y - y;\n offsets.push(landed.x - x, angleY === 0 ? turnDy : turnDy + nodDy);\n }\n keyforms2d.push({ offsets });\n }\n }\n\n return {\n parameter: parameterX,\n parameterY,\n valuesX: STOPS,\n valuesY: STOPS,\n keyforms2d,\n };\n}\n\n// ── turnColumnMap ─────────────────────────────────────────────────────────────\n\n/** Where the head turn sends each column of a lattice. */\nexport interface TurnColumnMap {\n /** Rest x of every lattice column, ascending — row 0 of `grid.points`. */\n restX: number[];\n /** Where the turn puts each of those columns: strictly increasing whenever\n * `restX` is, since the bound and the capped angle rule out a fold. */\n warpedX: number[];\n /** Rest x → turned x, for anything reading the lattice. */\n mapX(x: number): number;\n}\n\n/**\n * The head turn as a 1D map on x: where the turn puts a point resting at x at\n * `angleX` — the bend AND the same uniform `turnSlide(travel, angleX)` the\n * bakes ship, so a caller passes the travel the rig was baked with or it\n * measures a head the rig never renders. headDeformer adds no turn translate\n * on top of it.\n *\n * It equals the shipped bakes AT the `HEAD_TURN_STOPS` on the nodes of their\n * grids, which is where callers should key: between stops the engine blends\n * the keyforms parameter-linearly (the chord the HEAD_TURN_STOPS comment\n * measures), while this is analytic, so off-stop the two differ by that chord\n * error.\n *\n * The bend depends only on a point's x, so a single row of columns describes\n * the whole surface, and the map is piecewise-linear between them the way a\n * grid child's read is: `bindPointToRestGrid` puts a point in the cell its\n * rest x falls in and `sampleWarpGrid` lerps between that cell's deformed\n * corners — so between two columns the map is a straight line, and outside the\n * outer columns the clamped (s, t) pin it to the edge column's warped x.\n * `mapX` reproduces that clamp rather than extrapolating, because a caller\n * measuring a silhouette needs where the geometry lands, not where an extended\n * cylinder would put it — and the virtual lattice is sized so nothing a bake\n * reads ever sits outside its columns (`turnSetup`).\n *\n * `warpedX` stays ordered while asin(1/HEAD_CYLINDER_RADIUS_FACTOR) + |theta|\n * < 90°, i.e. |angleX| ≲ 33.6°. Past that the outer columns fold — a column\n * further out lands nearer the axis, so `mapX` would silently hand back a turn\n * that crosses itself — and the range is capped at the parameter's own\n * HEAD_TURN_MAX_DEG rather than returning a folded map.\n */\nexport function turnColumnMap(\n grid: IkiWarpGrid,\n faceCenterX: number,\n radiusX: number,\n angleX: number,\n travel: number,\n): TurnColumnMap {\n if (Math.abs(angleX) > HEAD_TURN_MAX_DEG) {\n throw new Error(\n `auto-rig: turnColumnMap: angleX ${angleX}° is outside the turn's ±${HEAD_TURN_MAX_DEG}° range`,\n );\n }\n const theta = angleX * (Math.PI / 180);\n const slide = turnSlide(travel, angleX);\n const restX: number[] = [];\n const warpedX: number[] = [];\n for (let col = 0; col <= grid.cols; col++) {\n const x = grid.points[col * 2];\n restX.push(x);\n warpedX.push(\n x + boundedCylinderBend(x - faceCenterX, radiusX, theta) + slide,\n );\n }\n\n // Same cell as bindPointToRestGrid picks: the first whose right edge is past\n // x, else the last one — found by bisection, the rest columns being\n // ascending.\n const cellFor = (x: number) => {\n let lo = 0;\n let hi = grid.cols - 1;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (x < restX[mid + 1]) hi = mid;\n else lo = mid + 1;\n }\n return lo;\n };\n\n return {\n restX,\n warpedX,\n // Piecewise-linear read of `warpedX` over `restX`, the within-cell\n // fraction clamped to [0,1] as sampleWarpGrid clamps (s, t). A node reads\n // its own warped x: `warpedX[c] + Δ·1` is `warpedX[c + 1]` only to an\n // ulp, and the columns a bake reads are nodes.\n mapX: (x) => {\n const c = cellFor(x);\n const s = Math.max(\n 0,\n Math.min(1, (x - restX[c]) / (restX[c + 1] - restX[c])),\n );\n if (s === 0) return warpedX[c];\n if (s === 1) return warpedX[c + 1];\n return warpedX[c] + (warpedX[c + 1] - warpedX[c]) * s;\n },\n };\n}\n\n// ── renderedLandingX ─────────────────────────────────────────────────────────\n\n/** A mesh part as the renderer places it before any deformer moves it: its\n * rest centre and crop, `meshCellsFor`'s cell counts, and its DEFAULT-POSE\n * TRS — what `evaluateTransform` returns with every parameter at its default\n * (`defaultPoseOf`). The identity for the face and the bangs; the mouth\n * family rests at scaleX 1.1, its MouthForm range (−0.2…0.4) not being\n * centred on the parameter's default, so a caller reading it has to pass\n * that rather than assume 1. Exported at module level for the tests, not\n * from the package. */\nexport interface RenderedPart {\n x: number;\n y: number;\n cropW: number;\n cropH: number;\n cols: number;\n rows: number;\n scaleX: number;\n scaleY: number;\n rotation: number;\n}\n\n/**\n * Where a rest point `(px, py)` of a mesh part RENDERS on x, read the way the\n * engine and the GPU read it, in two stages. First `vertexLanding` lands each\n * MESH VERTEX at its PRE-BIND model position — the local vertex exactly as\n * `createPixelGridMesh` writes it, through the part's default-pose TRS in the\n * engine's own order (scale, rotate, translate): the position\n * `applyWarpToChild` binds. Then the point is interpolated over the mesh\n * TRIANGLE containing it — `createPixelGridMesh`'s [BL, BR, TL] / [TL, BR, TR]\n * split, the lower-left one when the column fraction ≤ the row fraction —\n * never bilinearly over the cell, which differs by its own cross term. A point\n * outside the mesh's rows or columns reads the end row or column with its\n * fraction clamped: the landing of the nearest boundary point, which is what a\n * silhouette point past the bangs' own crop renders as.\n *\n * Every production reader of a rendered point goes through this — the solve's\n * landmarks and silhouette candidates on their carriers, the bangs' own\n * silhouette landing, and the plate's rendered edge under the hold's join and\n * the fold guards — so a solved cue is a promise about the shipped mesh, not\n * about an analytic surface the mesh only chords.\n */\nfunction renderedLandingX(\n part: RenderedPart,\n vertexLanding: (preBindX: number, preBindY: number) => number,\n px: number,\n py: number,\n): number {\n const { cols, rows } = part;\n // Lattice fractions of the rest point: u along the columns (0 at the left\n // edge), v down the rows (0 at the top), each clamped into its end cell.\n const u = ((px - part.x + part.cropW / 2) / part.cropW) * cols;\n const v = ((part.y + part.cropH / 2 - py) / part.cropH) * rows;\n const col = Math.max(0, Math.min(cols - 1, Math.floor(u)));\n const row = Math.max(0, Math.min(rows - 1, Math.floor(v)));\n const fx = Math.max(0, Math.min(1, u - col));\n const fy = Math.max(0, Math.min(1, v - row));\n const cos = Math.cos(part.rotation * (Math.PI / 180));\n const sin = Math.sin(part.rotation * (Math.PI / 180));\n const landingAt = (c: number, r: number): number => {\n const lx = (-part.cropW / 2 + (c / cols) * part.cropW) * part.scaleX;\n const ly = (part.cropH / 2 - (r / rows) * part.cropH) * part.scaleY;\n return vertexLanding(\n part.x + cos * lx - sin * ly,\n part.y + sin * lx + cos * ly,\n );\n };\n // `fx` runs left→right and `fy` top→bottom, the axes the cell is built on,\n // so its TL→BR diagonal is `fx === fy` and the lower-left triangle is the\n // one with `fx <= fy`.\n const tl = landingAt(col, row);\n const br = landingAt(col + 1, row + 1);\n if (fx <= fy) {\n const bl = landingAt(col, row + 1);\n return tl + fy * (bl - tl) + fx * (br - bl);\n }\n const tr = landingAt(col + 1, row);\n return tl + fx * (tr - tl) + fy * (br - tr);\n}\n\n/**\n * Where a warp child's vertex at pre-bind `(x, y)` lands on x through its\n * deformer's grid: `bindPointToRestGrid`'s cell — the first whose right edge\n * is past x and the first whose bottom edge is below y, else the end cell —\n * and `sampleWarpGrid`'s bilinear blend of that cell's four corner nodes,\n * each landed by `nodeLanding`, with the within-cell fractions clamped to\n * [0, 1] as the engine clamps them. A node reads its own landing exactly.\n */\nfunction gridChildLandingX(\n grid: IkiWarpGrid,\n nodeLanding: (nodeX: number, nodeY: number) => number,\n x: number,\n y: number,\n): number {\n const { cols, rows, points } = grid;\n const stride = cols + 1;\n let col = cols - 1;\n for (let c = 0; c < cols; c++) {\n if (x < points[(c + 1) * 2]) {\n col = c;\n break;\n }\n }\n let row = rows - 1;\n for (let r = 0; r < rows; r++) {\n if (y > points[(r + 1) * stride * 2 + 1]) {\n row = r;\n break;\n }\n }\n const xLeft = points[col * 2];\n const xRight = points[(col + 1) * 2];\n const yTop = points[row * stride * 2 + 1];\n const yBottom = points[(row + 1) * stride * 2 + 1];\n const s = Math.max(0, Math.min(1, (x - xLeft) / (xRight - xLeft)));\n const t = Math.max(0, Math.min(1, (yTop - y) / (yTop - yBottom)));\n const at = (c: number, r: number) =>\n nodeLanding(points[(r * stride + c) * 2], points[(r * stride + c) * 2 + 1]);\n const top = at(col, row) + (at(col + 1, row) - at(col, row)) * s;\n const bot = at(col, row + 1) + (at(col + 1, row + 1) - at(col, row + 1)) * s;\n return top + (bot - top) * t;\n}\n\n/** The grid a face-family part binds to — its turn group's — and the part as\n * it renders on it: what a landmark, a silhouette candidate or the plate's\n * edge is read through (`carrierLandingX`), and what the generator ships as\n * that group's deformer and that part's mesh. Exported at module level for\n * the tests, not from the package. */\nexport interface TurnCarrier {\n grid: IkiWarpGrid;\n part: RenderedPart;\n /** The point the group's nodes turn about, when they turn as one feature\n * rather than each on its own column (`groupNodeLanding`). Only the mouth\n * family's carriers and the nose's carry one. */\n anchor?: TurnAnchor;\n}\n\n/** A turn group's anchor: its rest point `(x, y)` and how far the group tilts\n * about it at full turn, degrees. The mouth family's is the closed mouth's\n * rest centre, which is also the `mouth` landmark, tilting\n * `MOUTH_TURN_TILT_DEG`; the nose's is its bridge top, the top edge of its\n * `denseCore` (else its crop) at that box's centre x, tilting\n * `NOSE_TURN_TILT_DEG`. */\ninterface TurnAnchor {\n x: number;\n y: number;\n tiltDeg: number;\n}\n\n/** Where a rest point of a carried part renders on x: `renderedLandingX` on\n * the part, each mesh vertex landed through the carrier's grid\n * (`gridChildLandingX`) with `nodeLanding` at the grid's nodes — the two\n * stages the engine and the GPU draw a warp child in. */\nfunction carrierLandingX(\n carrier: TurnCarrier,\n nodeLanding: (nodeX: number, nodeY: number) => number,\n px: number,\n py: number,\n): number {\n return renderedLandingX(\n carrier.part,\n (vx, vy) => gridChildLandingX(carrier.grid, nodeLanding, vx, vy),\n px,\n py,\n );\n}\n\n/**\n * Where a turn-group grid node resting at `(nodeX, nodeY)` lands at the stop\n * `deg`, whose map for a point resting at `y` is `mapAt(y)`, its family\n * shifted by `shift` px (negative toward the far side): the rule the bake and\n * the carrier reads share. The bake writes it into the group's keyforms\n * (`bakeTurnGroupWarp2D`) and the solve reads the same nodes back through it\n * (`landmarkLandingX`, `landingOfRole`), so given the same anchor, stop and\n * shift the two cannot disagree; `plateLandingOn` reads the plate's nodes by\n * the unanchored rule at shift 0.\n *\n * Without an `anchor` each node reads the map at its own shifted x. With one\n * the group turns as one feature on the face, about the anchor `a`:\n * - the pivot P = M_{a.y}(a.x + shift) is where the unanchored rule puts\n * the anchor, so the anchor's landing — and what a family shift means —\n * is unchanged;\n * - each node takes the surface's own shape about the anchor's REST\n * position and slides with it as a whole,\n * X = M_{nodeY}(nodeX) + P − M_{a.y}(a.x), so the feature foreshortens\n * as the face under it does rather than as the face beside it;\n * - (X, nodeY) is then rotated about (P, a.y) by\n * φ = tiltDeg · deg / HEAD_TURN_MAX_DEG, counter-clockwise positive in\n * model y-up, so the near end goes down at either full turn (the +x end\n * at −30, the −x end at +30). This is the only reader of `deg`.\n *\n * The mouth reads φ on its width: its anchor is its centre, so the near end\n * goes down. The nose reads the same φ on its height: its anchor is its\n * bridge top, so the tip, below the pivot, swings toward the far side (−x at\n * −30, where φ is negative and clockwise; +x at +30). One sign serves both —\n * the one that puts the mouth's near end down.\n *\n * A rotation is affine, so it passes unchanged through the grid's bilinear\n * read and the mesh's barycentric read: the rendered tilt is exactly φ, and\n * every drawing on the grid turns about the same pivot.\n */\nfunction groupNodeLanding(\n anchor: TurnAnchor | undefined,\n mapAt: (y: number) => TurnColumnMap,\n deg: number,\n shift: number,\n nodeX: number,\n nodeY: number,\n): { x: number; y: number } {\n if (anchor === undefined) {\n return { x: mapAt(nodeY).mapX(nodeX + shift), y: nodeY };\n }\n const anchorRow = mapAt(anchor.y);\n const pivotX = anchorRow.mapX(anchor.x + shift);\n const dx = mapAt(nodeY).mapX(nodeX) - anchorRow.mapX(anchor.x);\n const dy = nodeY - anchor.y;\n const phi = ((anchor.tiltDeg * deg) / HEAD_TURN_MAX_DEG) * (Math.PI / 180);\n const cos = Math.cos(phi);\n const sin = Math.sin(phi);\n return {\n x: pivotX + cos * dx - sin * dy,\n y: anchor.y + sin * dx + cos * dy,\n };\n}\n\n/** `surface.mapAt`, memoised per stop and node row: the map depends on\n * nothing else, and a solve reads it at every node of every carrier at every\n * guard row of every stop of every hold it tries. */\nfunction rowMapsOf(\n surface: TurnSurface,\n): (deg: number, nodeY: number) => TurnColumnMap {\n const maps = new Map<number, Map<number, TurnColumnMap>>();\n return (deg, nodeY) => {\n let byRow = maps.get(deg);\n if (byRow === undefined) {\n byRow = new Map();\n maps.set(deg, byRow);\n }\n let map = byRow.get(nodeY);\n if (map === undefined) {\n map = surface.mapAt(deg, nodeY);\n byRow.set(nodeY, map);\n }\n return map;\n };\n}\n\n/** The face plate as the renderer places it — its rest centre and crop, the\n * mesh `meshCellsFor` gives that crop, and the identity TRS the generator\n * writes for it (`bboxToTransform` places it by translate alone, and it\n * carries no binding). A hand-built plate for the primitive tests; the\n * solver and the generator read theirs from `turnSetup`'s `face` member.\n * Exported at module level for the tests, not from the package. */\nexport function facePlate(\n faceCenterX: number,\n faceCenterY: number,\n faceHalfWidth: number,\n faceCropH: number,\n): RenderedPart {\n const cropW = faceHalfWidth * 2;\n return {\n x: faceCenterX,\n y: faceCenterY,\n cropW,\n cropH: faceCropH,\n ...meshCellsFor(cropW, faceCropH),\n scaleX: 1,\n scaleY: 1,\n rotation: 0,\n };\n}\n\n/**\n * The face plate's rendered landing on x at one stop, for ANY rest point of\n * it: `renderedLandingX` on the plate's own mesh, each vertex landed through\n * the carrier's grid — the plate's own `faceWarp` grid — as the bilinear\n * blend, over the cell that holds it, of the surface's map at the four corner\n * nodes, each node read at its own rest y (`gridChildLandingX`), which is\n * what the engine draws there. The plate has no turn shift of its own, so a\n * node reads the map at its rest x. Not the bare surface: the grid and the\n * mesh each chord it, and the bangs' join has to sit on what RENDERS.\n *\n * `mapAt` is the surface's map per stop and node row — a caller hands in the\n * memoised reader `rowMapsOf` gives a surface, so the plate's node rows are\n * built once and shared with everything else reading that surface.\n *\n * Exported at module level for the tests, not from the package.\n */\nexport function plateLandingOn(\n plate: TurnCarrier,\n mapAt: (deg: number, nodeY: number) => TurnColumnMap,\n): (deg: number, x: number, y: number) => number {\n return (deg, x, y) =>\n carrierLandingX(\n plate,\n (nodeX, nodeY) => mapAt(deg, nodeY).mapX(nodeX),\n x,\n y,\n );\n}\n\n/**\n * The rows the plate's rendered painted edge is read at by every guard that\n * keeps the bangs' hold fold-free: the face mesh's own vertex rows — between\n * two of them the edge column renders linear, so its extremes sit on them —\n * plus every hair_front mesh row's hold row clamped into the face mesh's\n * span, the rows the bangs' join actually samples the plate at (the face rows\n * alone without bangs). A bangs row's hold row is its rest y, or `widestY`\n * for a row resting below the face's widest row — the row\n * `hairFrontHoldTarget` reads every lower vertex's ramp at — so the guard\n * reads the plate exactly where every ramp's inner end is read. ONE list for\n * the solver's `plateReachAt` and `shellTravelCap` and for the bake's per-row\n * guard, so the two cannot disagree: a radius the solver accepts is one the\n * bake can hold.\n *\n * Exported at module level for the tests, not from the package.\n */\nexport function plateGuardRowsFor(\n face: { y: number; cropW: number; cropH: number },\n hairFront?: { centerY: number; cropW: number; cropH: number },\n widestY = -Infinity,\n): number[] {\n // Each mesh row's absolute rest y, exactly as the bakes read a vertex's:\n // the part's y plus createPixelGridMesh's own row y.\n const rowsOf = (part: { cropW: number; cropH: number }, y: number) => {\n const { rows } = meshCellsFor(part.cropW, part.cropH);\n return Array.from(\n { length: rows + 1 },\n (_, r) => y + (part.cropH / 2 - (r / rows) * part.cropH),\n );\n };\n const faceRows = rowsOf(face, face.y);\n if (hairFront === undefined) return faceRows;\n const top = face.y + face.cropH / 2;\n const bottom = face.y - face.cropH / 2;\n return [\n ...faceRows,\n ...rowsOf(hairFront, hairFront.centerY).map((y) =>\n Math.max(bottom, Math.min(top, Math.max(y, widestY))),\n ),\n ];\n}\n\n/**\n * The face plate as every guard reads it: its carrier — the `face` member's\n * grid and mesh — the painted half-width on each row, and the rows the\n * rendered edge is read at (`plateGuardRowsFor`, the bangs' rows below the\n * profile's widest row read at it, as the hold reads them). `edgeAt` is the\n * profile's own row when the face layer measured one (`faceRowProfile`), and\n * the crop's half-width on every row otherwise: the hold's join ends there\n * and both fold guards read the plate's edge there. Derived HERE and nowhere\n * else — `turnSetup` hands the generator these and `solveTurnModel` builds\n * its context from the same call on the same inputs — so the hold the solve\n * fits is the hold the bake builds.\n */\nfunction plateGuardsOf(\n carriers: ReadonlyMap<string, TurnCarrier>,\n faceHalfWidth: number,\n profile: FaceRowProfile | undefined,\n hairFront?: { centerY: number; cropW: number; cropH: number },\n): {\n plate: TurnCarrier;\n edgeAt: (y: number) => number;\n plateGuardRows: number[];\n} {\n const plate = carriers.get(\"face\");\n if (plate === undefined) {\n throw new Error(\"auto-rig: plateGuardsOf: the carriers have no face\");\n }\n return {\n plate,\n edgeAt: profile === undefined ? () => faceHalfWidth : profile.at,\n plateGuardRows: plateGuardRowsFor(plate.part, hairFront, profile?.widestY),\n };\n}\n\n// ── Turn targets ─────────────────────────────────────────────────────────────\n\n/**\n * What a head does at full turn, as the cues `measure_turn_reference` reads off\n * a front/turned reference pair. The turn is fitted to these — the cylinder's\n * radius and the features' depths are solved from them, not tuned.\n *\n * Every field is optional: `DEFAULT_TURN_TARGETS` supplies the three a\n * reference measures, and the nose and the mouth derive from the eyes when they\n * are not given.\n *\n * What happens when the layer set cannot reach a target depends on where the\n * target came from. A number the CALLER passed is a measurement, so an\n * unreachable one throws, naming the field and what was on offer — except a\n * `silhouetteRatio` when no ratio in its own [0.5, 1.5] range renders on this\n * layer set: that throw names the field and the whole range instead of an\n * offer, since there is none narrower. Leaving the field out still rigs,\n * because a defaulted one clamps.\n *\n * A number this module filled in — a default, or a nose/mouth derived from the\n * eyes — is a style prior, not a promise about this character: it is CLAMPED\n * to what the layer set can do and the rig is built. The alternative is a\n * generator that refuses its own defaults, which would leave a perfectly good\n * layer set with no model at all. `solveTurnModel` reports which fields it\n * clamped.\n *\n * One measurement is clamped too: an `eyeShift` past the room the art leaves\n * the far eye — the face plate's edge, and the bangs' side strand when\n * `strandEdges` is given — whoever passed it. That room is a fact about this\n * layer set, not about the reference the shift was measured on, so the rig\n * takes as much of the shift as the art allows and keeps fitting the other\n * cues. An `eyeShift` below the slide the face's own turn already gives the\n * eyes is still refused: no depth reaches it.\n */\nexport interface TurnTargets {\n /** How far the eye pair's centre slides toward the far side at full turn, as\n * a fraction of the head's half-width. The sign is ignored: which way is\n * the turn's own business. */\n eyeShift?: number;\n /** The far eye's width over the near eye's at full turn, divided by the same\n * ratio at rest. 1 is no foreshortening; 0 is an edge-on far eye. */\n farEyeRatio?: number;\n /** The head's half-width at full turn over its half-width at rest, as a\n * RENDER measures it — the outermost opaque edge on each side, whichever\n * part owns it. 1 holds the silhouette; below 1 narrows it. The hold that\n * produces it is fitted to it, so a measured one is met or refused, never\n * quietly rendered as something else. */\n silhouetteRatio?: number;\n /** `eyeShift` for the nose. Derived from `eyeShift` when absent. */\n noseShift?: number;\n /** `eyeShift` for the mouth. Derived from `eyeShift` when absent. */\n mouthShift?: number;\n /** The head's own half-width at the eye row, in canvas px: what the shift\n * fractions are fractions OF, and the rest distance the silhouette hold\n * pivots on. Without it both fall back to the face plate's half-width, which\n * is narrower than the head the hair draws, so the shifts land short — an\n * approximation for callers that have no pixels, not a default to prefer. */\n headHalfWidth?: number;\n}\n\n/**\n * A `turnTargets` field this generator will not build a rig from: not a number,\n * outside its range, unreachable on this layer set, or a measured head narrower\n * than the face plate it is drawn around. Every one of them carries a number\n * the CALLER passed, so a host can tell them apart from the invariant breaks the\n * rest of the generator throws and report them as bad input instead of failing.\n */\nexport class TurnTargetError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TurnTargetError\";\n }\n}\n\n/**\n * The reference the turn is fitted to when the caller measured none of its own:\n * a 3/4 portrait, which `measure_turn_reference` reads as 0.671 / −0.224 /\n * 1.012 (its raw turned far/near iris ratio is 0.71 — 0.67 is that ratio\n * normalised by the same ratio at rest, which is what the field means).\n * The silhouette is held exactly: a 1 % change is inside that tool's own noise.\n */\nexport const DEFAULT_TURN_TARGETS: Readonly<\n Required<Pick<TurnTargets, \"eyeShift\" | \"farEyeRatio\" | \"silhouetteRatio\">>\n> = {\n eyeShift: 0.22,\n farEyeRatio: 0.67,\n silhouetteRatio: 1,\n} as const;\n\n/** Share of the eye pair's slide the nose and the mouth take when a reference\n * measured only the eyes. Both stand proud of the surface the eyes sit on —\n * the nose furthest — so both travel further, in the same proportion the 3/4\n * reference above shows. */\nconst NOSE_SHIFT_SHARE = 1.36;\nconst MOUTH_SHIFT_SHARE = 1.18;\n\n/** Bounds on the ratio targets. Outside them the number is a typo, not a head:\n * a far eye wider than the near one by half already means the pair was\n * measured the wrong way round. */\nconst TURN_RATIO_MAX = 1.5;\nconst SILHOUETTE_RATIO_MIN = 0.5;\n\n/** `TurnTargets` with every target filled in — what the solver fits. */\nexport interface ResolvedTurnTargets {\n eyeShift: number;\n farEyeRatio: number;\n silhouetteRatio: number;\n noseShift: number;\n mouthShift: number;\n /** The caller's measured head half-width, checked. Absent when it has none. */\n headHalfWidth?: number;\n /** The fields the caller did NOT supply: filled from DEFAULT_TURN_TARGETS, or\n * derived from the eyes. Those are the ones the solver clamps wherever the\n * layer set cannot reach them; of the caller's own it clamps only an\n * `eyeShift` past the art's room (see TurnTargets). */\n defaulted: ReadonlySet<keyof TurnTargets>;\n}\n\n/** The turn depth of each feature family: the share of the pinned bulk slide\n * (`headTurnParallaxUnit`) it gets back at full turn. Solved, never tuned. */\nexport interface TurnDepths {\n eye: number;\n nose: number;\n mouth: number;\n}\n\n/**\n * Fill in the targets the caller left out and check the ones it gave.\n *\n * The defaults go through the same checks as a caller's numbers — they are\n * targets like any other, and a bad one has to fail where it is written, not\n * in the geometry three functions down. The GEOMETRIC bounds are not checked\n * here: whether a target is reachable is a property of the layer set, so it\n * belongs to `solveTurnModel`.\n */\nexport function resolveTurnTargets(\n targets: TurnTargets = {},\n): ResolvedTurnTargets {\n const eyeShift = targets.eyeShift ?? DEFAULT_TURN_TARGETS.eyeShift;\n const check = (\n field: keyof TurnTargets,\n value: number | undefined,\n ok: (v: number) => boolean,\n expected: string,\n ) => {\n if (value === undefined) return;\n if (!Number.isFinite(value) || !ok(value)) {\n // A DERIVED target says so: the number in the message is not one the\n // caller wrote, and the field to fix is the one it came from. Only the\n // two shares are derived; the rest fall back to a default, which is a\n // number in its own right.\n const derived =\n (field === \"noseShift\" || field === \"mouthShift\") &&\n targets[field] === undefined;\n const from = derived\n ? `, derived from turnTargets.eyeShift (${eyeShift}),`\n : \"\";\n throw new TurnTargetError(\n `auto-rig: turnTargets.${field} (${value})${from} must be ${expected}`,\n );\n }\n };\n\n const farEyeRatio = targets.farEyeRatio ?? DEFAULT_TURN_TARGETS.farEyeRatio;\n const silhouetteRatio =\n targets.silhouetteRatio ?? DEFAULT_TURN_TARGETS.silhouetteRatio;\n const noseShift = targets.noseShift ?? NOSE_SHIFT_SHARE * eyeShift;\n const mouthShift = targets.mouthShift ?? MOUTH_SHIFT_SHARE * eyeShift;\n\n const isShift = (v: number) => Math.abs(v) <= 1;\n const shiftRange = \"a fraction of the head half-width, |value| <= 1\";\n check(\"eyeShift\", eyeShift, isShift, shiftRange);\n check(\n \"farEyeRatio\",\n farEyeRatio,\n (v) => v > 0 && v <= TURN_RATIO_MAX,\n `in (0, ${TURN_RATIO_MAX}]`,\n );\n check(\n \"silhouetteRatio\",\n silhouetteRatio,\n (v) => v >= SILHOUETTE_RATIO_MIN && v <= TURN_RATIO_MAX,\n `in [${SILHOUETTE_RATIO_MIN}, ${TURN_RATIO_MAX}]`,\n );\n check(\"noseShift\", noseShift, isShift, shiftRange);\n check(\"mouthShift\", mouthShift, isShift, shiftRange);\n // headHalfWidth is checked against the layer set's own plate in solveTurnModel,\n // which is where the two meet.\n check(\"headHalfWidth\", targets.headHalfWidth, (v) => v > 0, \"positive\");\n\n const defaulted = new Set<keyof TurnTargets>(\n (\n [\n \"eyeShift\",\n \"farEyeRatio\",\n \"silhouetteRatio\",\n \"noseShift\",\n \"mouthShift\",\n ] as const\n ).filter((field) => targets[field] === undefined),\n );\n return {\n eyeShift,\n farEyeRatio,\n silhouetteRatio,\n noseShift,\n mouthShift,\n headHalfWidth: targets.headHalfWidth,\n defaulted,\n };\n}\n\n// ── Turn landmarks ───────────────────────────────────────────────────────────\n\n/** A feature the turn slides across the face: its rest centre and its width in\n * model px, which is all the cues measure. `y` is optional — the eye pair's\n * places the eye row (`solveTurnModel`'s `eyeRowY`), and every landmark's is\n * the row its points are read on — its own radius and swing once the face\n * has a row profile (`TurnSurface.mapAt`). A hand-built landmark without one\n * reads row 0, the same map as any other on a surface without a profile. */\nexport interface TurnLandmark {\n x: number;\n y?: number;\n w: number;\n /** The grid the part binds to and the part as it renders on it, when the\n * landmark is a rigged part's: its points are then read the way the\n * engine draws them — the mesh triangle over vertices landed bilinearly\n * on the grid (`landmarkLandingX`) — rather than straight off the\n * surface's map, which the grid and the mesh only chord. A landmark\n * without one (hand-built, in a test) reads the map itself. */\n carrier?: TurnCarrier;\n}\n\n/** The landmarks each solved depth is fitted to. The eye family is a PAIR — the\n * cues are about the two eyes against each other — the other two a single\n * part, kept in arrays so one solver serves all three. */\nexport interface TurnLandmarkSet {\n eye: TurnLandmark[];\n nose: TurnLandmark[];\n mouth: TurnLandmark[];\n}\n\n/** The box a nose's turn reads, its landmark (`turnLandmarks`) and its pivot\n * (`turnSetup`) alike, so the two cannot part: its `denseCore` when the\n * layer carries one, else its crop. */\nfunction noseDrawnBox(layer: LayerInput): {\n x: number;\n y: number;\n w: number;\n h: number;\n} {\n return layer.denseCore ?? layer.bbox;\n}\n\n/**\n * Pick the parts the turn cues are measured on out of a layer set, in absolute\n * model x.\n *\n * The eye pair is the WHITES, even when the layer set has irises. The white is\n * the eye's own extent — the thing that has to stay on the face plate as the\n * pair slides, and the widest part of the stack — while the iris is clipped to\n * it and slides with it, so the two foreshorten together: on the reference\n * character the far/near ratio read off the white and off the iris differ by\n * 0.01. Bounding the slide by the narrower iris instead would put the white's\n * outer edge over the side hair, where the render loses it.\n *\n * The nose is its `denseCore` when the layer carries one — the drawing, not\n * the soft feather its crop takes in — and its crop otherwise.\n *\n * With `carriers` (by role — what `turnSetup` builds) each landmark is read\n * through its own part's grid and mesh; without, straight off the map.\n */\nexport function turnLandmarks(\n layers: LayerInput[],\n carriers?: ReadonlyMap<string, TurnCarrier>,\n): TurnLandmarkSet {\n const markOf = (role: string): TurnLandmark | undefined => {\n const layer = layers.find((l) => l.role === role);\n if (!layer) return undefined;\n const drawn = role === \"nose\" ? noseDrawnBox(layer) : layer.bbox;\n const t = bboxToTransform(drawn, layer.canvasW, layer.canvasH, role);\n // A crop's width is its `cropW`; a dense core's, its own.\n const w = drawn === layer.bbox ? layer.cropW : drawn.w;\n const mark: TurnLandmark = { x: t.x, y: t.y, w };\n const carrier = carriers?.get(role);\n if (carrier !== undefined) mark.carrier = carrier;\n return mark;\n };\n const pairOf = (left: string, right: string) => {\n const l = markOf(left);\n const r = markOf(right);\n return l && r ? [l, r] : undefined;\n };\n const one = (role: string) => {\n const m = markOf(role);\n return m ? [m] : [];\n };\n return {\n eye: pairOf(\"eye_L\", \"eye_R\") ?? [],\n nose: one(\"nose\"),\n mouth: one(\"mouth\"),\n };\n}\n\n/** The row the cues are measured on and a row profile is normalised at: the\n * eye pair's mean y when both eyes carry one (every landmark `turnLandmarks`\n * builds does), else `fallback` — the face centre, for landmarks a\n * lower-level test built by hand without rows. One derivation for the solve\n * and `turnSetup`, so the generator's own surface and the solve's read the\n * same row. */\nfunction eyeRowOf(landmarks: TurnLandmarkSet, fallback: number): number {\n const [l, r] = landmarks.eye;\n return landmarks.eye.length === 2 && l.y !== undefined && r.y !== undefined\n ? (l.y + r.y) / 2\n : fallback;\n}\n\n// ── solveTurnDepth ───────────────────────────────────────────────────────────\n\n/** The depth that comes closest to a shift target at one radius. */\nexport interface TurnDepthSolution {\n /** Whether `depth` HITS the target, or the bounds cut it short. */\n reached: boolean;\n /** The depth to use: the target's own solution, or the nearest bound. */\n depth: number;\n /** The shift `depth` actually produces, px (negative = toward the far side). */\n achieved: number;\n /** Every shift this family could have had, px, ascending. */\n attainable: [number, number];\n /** Which end cut the target short, when `reached` is false. `\"cap\"`: it\n * asked for more far-side shift than the art leaves room for — the face\n * plate's edge, or the strand bound's depth (0 where that bound cannot\n * hold). `\"floor\"`: it asked for less than the family already has at\n * depth 0 — the face's own slide and bend, plus the silhouette centre's\n * drift once that is folded into the target — which no depth reaches,\n * a depth never being negative. Absent when `reached`. */\n limit?: \"cap\" | \"floor\";\n}\n\n/** Bisection steps for both solvers. The intervals start finite and halve, so\n * 40 is exact in double precision for any radius or depth a head has. */\nconst TURN_BISECT_STEPS = 40;\n\n/** Px slack on `solveTurnDepthSigned`'s own attainable bound, so a target\n * that round-trips through a reported cue and back — e.g. a caller\n * re-submitting the exact `attainable` boundary this function itself\n * reported, cue-scaled by `hh` and back — is not refused by a float ulp of\n * rounding. Far below any physically meaningful px difference. */\nconst TURN_DEPTH_EPS = 1e-6;\n\n/**\n * The depth that slides a landmark family by `targetPx` at full turn —\n * `targetPx` a MAGNITUDE, always toward the far side (see `TurnTargets`'\n * \"the sign is ignored\" contract). The nose/mouth shares and every existing\n * caller of this export want exactly that; `evaluateTurnCandidate`'s eye\n * solve does not (see `solveTurnDepthSigned`), so it calls that directly.\n */\nexport function solveTurnDepth(\n targetPx: number,\n landmarks: TurnLandmark[],\n unit: number,\n map: TurnColumnMap,\n plateEdgeX: number,\n): TurnDepthSolution {\n // The cues are signed toward the far side; the target's own sign is ignored.\n return solveTurnDepthSigned(\n -Math.abs(targetPx),\n landmarks,\n unit,\n () => map,\n plateEdgeX,\n );\n}\n\n/**\n * Where a landmark's rest point `(px, py)` lands on x at the stop `deg`, whose\n * map for a point resting at `y` is `mapAt(y)`, the family shifted by `shift`\n * px (negative toward the far side): through the landmark's carrier when it\n * has one — every node of its grid lands by `groupNodeLanding` about the\n * carrier's anchor, the rule its keyforms were baked by, the shift being that\n * grid's own keyform geometry, and the point is read over the part's mesh\n * the way the engine draws it (`carrierLandingX`) — or straight off the map,\n * `mapAt(py).mapX(px + shift)`, without one.\n */\nfunction landmarkLandingX(\n landmark: TurnLandmark,\n mapAt: (y: number) => TurnColumnMap,\n deg: number,\n shift: number,\n px: number,\n py: number,\n): number {\n const { carrier } = landmark;\n if (carrier === undefined) return mapAt(py).mapX(px + shift);\n return carrierLandingX(\n carrier,\n (nodeX, nodeY) =>\n groupNodeLanding(carrier.anchor, mapAt, deg, shift, nodeX, nodeY).x,\n px,\n py,\n );\n}\n\n/** How far a landmark family's solved depth may carry it toward the far side,\n * px: its landmarks' far edge to the plate's far edge, never negative — a\n * family that starts past the plate cannot be slid back in by a depth, and a\n * depth away from the turn is not a depth. The depth solver's own cap, and\n * what every group grid and the lattice are sized to before the solve\n * (`turnSetup`), so the two cannot disagree. For the mouth and the nose,\n * whose nodes turn about their anchors (`groupNodeLanding`), the cap bounds\n * the anchor's slide. The eye family's cap may be tightened per hold by the\n * strand bound (`strandDepthCap`) but never loosened, so grids sized to this\n * one cover every depth a solve returns. */\nfunction familyReachPx(\n landmarks: readonly TurnLandmark[],\n plateEdgeX: number,\n): number {\n const farEdge = Math.min(...landmarks.map((l) => l.x - l.w / 2));\n return Math.max(0, farEdge - plateEdgeX);\n}\n\n/** A family's depth cap at one radius: `familyReachPx` in depth units. The\n * one expression every reader of the plate's cap goes through — the depth\n * solver's own bound (`solveTurnDepthSigned`), and the strand bound and\n * Aitken's clamp in `evaluateTurnCandidate` — so the three cannot drift\n * apart, and a rig built without strands keeps its eye depth bit for bit. */\nfunction familyDepthCap(\n landmarks: readonly TurnLandmark[],\n plateEdgeX: number,\n unit: number,\n): number {\n return familyReachPx(landmarks, plateEdgeX) / unit;\n}\n\n/**\n * The depth that lands a landmark family's slide exactly on the already-SIGNED\n * `target` (negative toward the far side, positive toward the near one),\n * or the nearer bound when `target` sits outside what any non-negative depth\n * can reach — depth cannot go negative (a feature does not stand BEHIND the\n * surface), so a positive `target` past `atRest` is exactly as unreached as a\n * too-negative one past `atCap`.\n *\n * The family's grid carries the slide as keyform geometry: each node lands\n * where the map sends its rest x shifted by `depth * unit` toward the far side\n * (`bakeTurnGroupWarp2D`'s `shiftAt`) — or, for the mouth and nose families,\n * where their anchor so shifted lands, the group turned about it\n * (`groupNodeLanding`) — and the part binds at its rest position, so what a\n * reference measures is the grid's map OF the shifted position, not the shift:\n * `achieved(d) = mean(landing(x, −d·unit) − x)`, each landmark's centre read\n * through its own carrier and mesh (`landmarkLandingX`) — the very keyforms\n * and triangles the engine draws. The map compresses the far side and\n * stretches the near one, so the two are not the same number, and the far\n * side's compression makes `achieved` shrink faster than `d` grows. It is\n * monotone decreasing in `d` because the map is monotone increasing and the\n * two interpolations weight it non-negatively, which is what makes a\n * bisection valid; an anchored landmark depends on `d` only through its\n * pivot, which it rides one for one.\n *\n * The travel's upper bound is the FACE: every landmark's far edge has to stay\n * on the face plate, whose contour the turn only foreshortens. A feature past\n * it is drawn over the side hair or hangs over the cheek's edge with nothing\n * behind it — on the reference character the far eye went 35 px past the\n * contour and the render came back with an eye 17 px wide where the solver\n * had promised 53. The reference keeps the far eye whole. Since `mapX` is\n * monotone, staying inside the plate's MAPPED edge is the same as staying\n * inside its rest edge, so the bound is a plain rest distance. The held\n * silhouette is no bound of its own: the hold edge clears the plate's own\n * rendered reach at every stop (the fold guard, see `evaluateTurnCandidate`),\n * and that reach bounds every landmark's far edge already.\n *\n * The eye family can be bounded tighter, by the bangs' side strands\n * (`maxDepth`, from `strandDepthCap`): the deepest depth at which each far\n * iris, landed at every far stop, sits no deeper under the run it slides\n * under than it is painted. That bound is best effort — where no depth holds\n * it, the cap is 0, the least overlap any depth leaves — and it bounds the\n * eyes' depth only: the face's own sideways travel, the slide under every\n * feature, is not capped by it.\n *\n * Both ends of the interval matter, not just the far one: at `d = 0` a landmark\n * already drifts, because the map carries the face's own sideways slide as well\n * as the bend, and the slide is much the larger of the two — on the assembly\n * fixture at −30° the two together drift the eye pair ≈64 px toward the far\n * side, whereas the bend alone would have moved it ≈11 px toward the near one.\n * That drift therefore runs toward the FAR side, and IS the floor under every\n * shift cue: a target asking for LESS far-side shift than that drift does has\n * no non-negative depth either. Neither end is an error here — the solution is\n * reported with `reached: false` and the bound it stopped at (`limit`), and\n * whether that is a clamp or a failure is the caller's call (see\n * solveTurnModel).\n *\n * `evaluateTurnCandidate`'s eye solve calls this directly instead of going\n * through `solveTurnDepth`'s magnitude wrapper: once the silhouette centre's\n * own drift is folded into the target, that target can legitimately land on\n * either side of zero, and `-Math.abs` would silently answer a different\n * question than the one asked (an eyeShift and its negation producing\n * different rigs). Every other caller (the nose/mouth shares) keeps the\n * magnitude-only contract through the wrapper.\n */\nfunction solveTurnDepthSigned(\n target: number,\n landmarks: TurnLandmark[],\n unit: number,\n mapAt: (y: number) => TurnColumnMap,\n plateEdgeX: number,\n /** A tighter depth bound than the plate's, when the art sets one: the eye\n * family's strand bound (`strandDepthCap`), 0 where it cannot hold. */\n maxDepth?: number,\n): TurnDepthSolution {\n if (landmarks.length === 0) {\n throw new Error(\"auto-rig: solveTurnDepth: no landmark to slide\");\n }\n const plateCap = familyDepthCap(landmarks, plateEdgeX, unit);\n const cap = maxDepth === undefined ? plateCap : Math.min(plateCap, maxDepth);\n const achieved = (d: number) =>\n landmarks.reduce(\n (sum, l) =>\n sum +\n (landmarkLandingX(\n l,\n mapAt,\n -HEAD_TURN_MAX_DEG,\n -d * unit,\n l.x,\n l.y ?? 0,\n ) -\n l.x),\n 0,\n ) / landmarks.length;\n\n const atCap = achieved(cap);\n const atRest = achieved(0);\n const attainable: [number, number] = [atCap, atRest];\n if (target > atRest + TURN_DEPTH_EPS) {\n return {\n reached: false,\n depth: 0,\n achieved: atRest,\n attainable,\n limit: \"floor\",\n };\n }\n if (target < atCap - TURN_DEPTH_EPS) {\n return {\n reached: false,\n depth: cap,\n achieved: atCap,\n attainable,\n limit: \"cap\",\n };\n }\n // Within EPS of a bound but past it either way (the ulp this guards\n // against) — clamp before bisecting, rather than searching for a target\n // outside the interval `achieved` can actually produce.\n const clampedTarget = Math.min(Math.max(target, atCap), atRest);\n\n let lo = 0;\n let hi = cap;\n for (let i = 0; i < TURN_BISECT_STEPS; i++) {\n const mid = (lo + hi) / 2;\n if (achieved(mid) > clampedTarget) lo = mid;\n else hi = mid;\n }\n const depth = (lo + hi) / 2;\n return { reached: true, depth, achieved: achieved(depth), attainable };\n}\n\n// ── solveTurnModel ───────────────────────────────────────────────────────────\n\n/** What the turn solve settled on, for a caller that wants to see it — the\n * cues it reached and the targets it had to cut down to reach them. */\nexport interface TurnSolveReport {\n /** Cylinder radius the face-warp bake and the parallax unit were built on. */\n radius: number;\n /** Rest distance from the face centre the silhouette hold pivots on. */\n holdBase: number;\n depths: TurnDepths;\n /** What the rig actually reaches, in the targets' own units. */\n achieved: { eyeShift: number; farEyeRatio: number; silhouetteRatio: number };\n /** The targets the rig fell short of, cut down to what it can reach: every\n * defaulted field it could not reach, and an `eyeShift` — the caller's own\n * too — past the room the art leaves the far eye (the face plate's edge,\n * and the bangs' strand when `strandEdges` was given). Empty when every\n * target was met. */\n clamped: (keyof TurnTargets)[];\n /** Per side whose far iris the bangs' run covers at some far stop at the\n * fitted radius and depth (`options.strandEdges`): how much of it. Absent\n * when no side has an entry. Never a clamp: `clamped` still names an\n * `eyeShift` the strand bound cut short. */\n strandOverlap?: { left?: StrandOverlap; right?: StrandOverlap };\n}\n\n/**\n * How much of one side's far iris the bangs' run covers: the length of the\n * landed iris span `[irisOuter, irisInner]` on its row that lies inside the\n * landed run `[runOuter, runFace]` — face-ward without end for a run with no\n * face-side edge (`runFace: null`) — at the far stop where that is largest.\n * Always read off real landed boundaries (`IrisStrand`).\n */\nexport interface StrandOverlap {\n /** The far stop the covered width is largest at, degrees. */\n deg: number;\n /** That covered width, px. */\n px: number;\n /** `px` over the head half-width the shift cues are fractions of. */\n hh: number;\n /** The covered width as painted, at rest, px. */\n restPx: number;\n /** Whether this side's strand bound held at the fitted depth — the iris\n * kept no deeper under the run than it is painted, at every far stop. When\n * false the fitted radius could not hold it (always so for `runFace:\n * null`), the eyes' turn depth is 0, and `px` is the coverage that is\n * left. */\n held: boolean;\n}\n\n/** The turn the targets ask for, or the first CALLER target this layer set\n * cannot reach and what it could have had instead (in that target's own\n * units). Defaults never land in the second branch — they clamp — and nor\n * does a caller `eyeShift` past the room the art leaves the far eye, which\n * clamps the same way; one below the slide the face's own turn already\n * gives the eyes still does. */\nexport type TurnModelSolution =\n | (TurnSolveReport & {\n unreachable: false;\n /** Where the hold's boundary is sent at each stop. */\n holdEdgeAt: (deg: number) => number;\n /** How far the face slides sideways inside that held boundary at full\n * turn, px — what the face bake and the hold's own column map have to\n * be built with, rather than the plate's own uncapped ask. */\n travel: number;\n /** The head this solve fitted — `radius` and `travel` on the lattice it\n * was handed — for every bake to sample, so nothing renders a turn the\n * cues were not measured on. */\n surface: TurnSurface;\n })\n | {\n unreachable: true;\n field: keyof TurnTargets;\n /** The caller's own number — never a clamped stand-in. */\n value: number;\n /** What the caller could have asked for instead, inside the field's own\n * accepted domain. Absent only for `silhouetteRatio`, and only when\n * nothing that domain accepts renders on this layer set at all. */\n attainable?: [number, number];\n };\n\n/** Radius sweep, as multiples of the face plate's half-width. The floor is the\n * no-fold margin itself: at it the bend's bound sits exactly on the plate's\n * edge, and below it the plate — features included — leaves the analytic part\n * of the cylinder and rides along rigidly instead of foreshortening. The\n * ceiling is all but a plate: at 8 half-widths the face covers ±7° of the\n * cylinder and the turn is nearly a slide — the far/near ratio it can still\n * produce, 0.93 on the reference character, is the top of the attainable range\n * the solver reports. A head that wants less foreshortening wants none. */\nconst TURN_SWEEP_MAX_FACTOR = 8;\n/** Sample count of that sweep. The ratio curve is smooth in log-radius, so this\n * only has to be dense enough to bracket the target, not to resolve it. */\nconst TURN_SWEEP_SAMPLES = 32;\n/** How far past the plate's own reach a hold edge has to sit, px. The ramp onto\n * the strands runs outward, so a hold edge ON the reach is a zero-width ramp;\n * one pixel of clearance is what makes it a ramp. */\nconst HOLD_CLEARANCE = 1;\n\n/** Sampling of the hold's own usable range, per radius (see `holdRange`). What\n * a render shows moves smoothly with the hold but not monotonically, so the\n * range is sampled densely enough to bracket the turn-back where the travel's\n * own feedback reverses it, not to resolve the ask — the bisection inside the\n * bracketing interval does that. */\nconst TURN_HOLD_SAMPLES = 16;\n\n/** Passes `evaluateTurnCandidate` gives the silhouette and the depths it rides\n * on to settle on each other before it gives the radius up as a `settle`\n * miss (`TurnCandidateMiss`). The passes are\n * Aitken-accelerated: a contracting fixed point settles in one to three on\n * the fixtures, eleven at the slowest seen (a depth that pins at a bound on\n * some passes and not others defeats the extrapolation) — twice that is\n * the margin. One that does not contract no count would settle. */\nconst TURN_SETTLE_PASSES = 24;\n\n/** A radius that can carry the turn, with everything the fit reads off it. */\ninterface TurnCandidate {\n radius: number;\n /** The far/near eye width ratio it produces, at the eye depth below. */\n ratio: number;\n unit: number;\n /** The sideways travel it carries at full turn, px: `ctx.travel` capped to\n * what this radius' own held shell can swallow (`shellTravelCap`). The face\n * bake and the bangs' hold have to be built with THIS number — `mapAt`\n * already is. */\n travel: number;\n /** Its column map at the −30° stop, where every cue is measured, for a\n * point resting at `y`. */\n mapAt: (y: number) => TurnColumnMap;\n /** The surface `mapAt` is read off: this radius, that travel. */\n surface: TurnSurface;\n holdBase: number;\n holdEdgeAt: (deg: number) => number;\n eye: TurnDepthSolution;\n /** The nose's and the mouth's depth on this radius' hold, solved on the\n * same terms (`solveFeatureDepths`): the depths their group grids ship\n * with, and so the depths a `headEdges` owner in either family is landed\n * at. Whether a bound that cut one short is a clamp or a refusal is\n * `solveTurnModel`'s call, once a radius is chosen. */\n nose: TurnDepthSolution;\n mouth: TurnDepthSolution;\n /** Whether the ratio this radius ships had to be cut to the nearest one a\n * render of it can show. Only a DEFAULTED target ever gets here cut down —\n * a caller-measured one the range cannot render is refused instead. */\n silhouetteClamped: boolean;\n /** How far the silhouette's own centre drifts from the face centre at full\n * turn, against rest — each side's real landing (whichever part owns that\n * edge actually carries it there; see `landingAt`) minus how far its rest\n * x already sat off the face centre. `measure_turn_reference` reads the\n * eye pair against THIS moved centre, not the face centre, so it is what\n * `eye`'s target and `achieved.eyeShift` are corrected by. */\n silhouetteCenterShift: number;\n /** The silhouette ratio a render actually shows: each side's real LANDING\n * span over its rest span (`landingAt`/`restAt`, the same pair\n * `silhouetteCenterShift` reads) — as `measure_turn_reference` would\n * measure it off the two images, and the measure the hold above was FITTED\n * against. It is the target itself whenever one was reachable, so it is\n * what `TurnSolveReport.achieved.silhouetteRatio` reports. */\n renderedSilhouetteRatio: number;\n /** Whether some eye depth keeps every far iris no deeper under the bangs'\n * run than it is painted at this radius (`strandDepthCap`) — `eye` is\n * capped to the deepest one — or none does and `eye` has depth 0. True\n * without strands. What `solveTurnModel` prefers radii by. */\n strandFeasible: boolean;\n /** How much of each far iris the run covers at `eye`'s depth, per side that\n * has any (see `TurnSolveReport.strandOverlap`), on the settled pass. */\n strandOverlap?: { left?: StrandOverlap; right?: StrandOverlap };\n}\n\n/** Why a radius yielded no candidate, in the blocked target's own terms. */\ntype TurnCandidateMiss =\n | {\n blocked: \"silhouetteRatio\";\n /** The ratios a render of this radius can actually SHOW, ascending —\n * `renderedSilhouetteRatio`'s own measure, which is the measure a\n * caller's own number came from: the narrowest and the widest over the\n * holds this radius can ship (`holdRange`'s samples), clipped to the\n * ratios `resolveTurnTargets` accepts. The rendered ratio is continuous\n * in the hold, so every ratio between them is one the fit lands on the\n * shipped mesh's own terms (`hairFrontLandingAt`, the bangs' vertices\n * landed as the bake lands them and read over the mesh's own\n * triangles), and both ends are values a caller can resubmit and have\n * rigged. Infinity and -Infinity when\n * nothing inside that domain renders here: the neutral pair\n * `sweepTurnRadii`'s own min/max ignore. */\n lower: number;\n upper: number;\n }\n | {\n /** A CALLER's eye shift below the floor: less far-side slide than the\n * eye pair already has at depth 0 on this radius (`limit: \"floor\"`),\n * which no depth reaches. A shift the art's room cuts short is not a\n * miss — the candidate keeps the capped depth, and the shift is\n * clamped whoever passed it. */\n blocked: \"eyeShift\";\n /** The px interval it offered the eyes instead, ascending. */\n offeredShift: [number, number];\n }\n | {\n /** The silhouette and the depths its `headEdges` owners ride on did\n * not settle on each other within TURN_SETTLE_PASSES at this radius\n * (see evaluateTurnCandidate): nothing to offer in any target's terms.\n * The sweep skips it like any refused radius, and `solveTurnModel`\n * refuses the layer set outright when that leaves it nothing. */\n blocked: \"settle\";\n };\n\n/** Everything a candidate is evaluated against that does not vary with the\n * radius. One pass of the solve holds one of these. */\ninterface TurnSolveContext {\n /** Each family's landmarks, every one carrying the carrier its part renders\n * on (`turnSetup`). */\n landmarks: TurnLandmarkSet;\n /** The columns every candidate's maps are piecewise-linear between — the\n * virtual lattice every group grid's node reads (`TurnSurface`). */\n lattice: IkiWarpGrid;\n faceCenterX: number;\n faceCenterY: number;\n /** The nod cylinder's radius, the head's vertical reach with the no-fold\n * margin: not fitted, carried so every candidate's surface is complete. */\n nodRadius: number;\n /** The row the cues are measured on — the eye pair's mean y, else the face\n * centre — where every candidate's maps are sampled, and where the bangs'\n * own silhouette landing is read. */\n eyeRowY: number;\n faceHalfWidth: number;\n /** Rest x of the plate's far edge — the slide's hard stop. */\n plateEdgeX: number;\n /** The face plate as it renders — its own group grid and mesh\n * (`facePlate`): what the plate's rendered edge under the hold's join and\n * the fold guards is read through (`plateLandingOn`). */\n plate: TurnCarrier;\n /** Every face-family part's carrier by role, the plate's under `face`:\n * what a `headEdges` candidate naming one of them is landed through. */\n carriers: ReadonlyMap<string, TurnCarrier>;\n /** The face's own row profile, when its layer measured one: every\n * candidate's surface bends each row on its own radius and swings the chin\n * by it (`turnSurface`). Absent, one radius on every row. */\n profile?: FaceRowProfile;\n /** The plate's PAINTED half-width at row `y` — where the hold's join and\n * the guards read its edge: the profile's row, else the crop's own\n * half-width on every row (`plateGuardsOf`). */\n edgeAt: (y: number) => number;\n /** The rows every plate guard reads the rendered painted edge at — see\n * `plateGuardRowsFor`; the bake iterates the identical list. */\n plateGuardRows: number[];\n /** The sideways travel this layer set ASKS the turn for, px at full turn:\n * `headTurnTravel` on its own plate. What a candidate actually carries is\n * this capped to what its own held shell can swallow — see\n * `TurnCandidate.travel`. */\n travel: number;\n /** The caller's measured head half-width, when it has one. */\n headHalfWidth?: number;\n /** What the shift fractions are fractions of, px. */\n hh: number;\n /** The silhouette ratio this pass is solving for. */\n silhouetteRatio: number;\n /** The MAGNITUDE of the eye shift this pass is solving for — `TurnTargets`'\n * own \"the sign is ignored\" contract taken at the door, so a request and\n * its negation reach identically here. */\n eyeShift: number;\n /** Whether an eye shift below the floor — less than the pair already\n * slides at depth 0 on a radius — is a clamp (a default) or refuses that\n * radius (the caller's). Only the floor's verdict: a shift the art's room\n * cuts short at the cap is clamped whoever passed it. */\n clampEyeShift: boolean;\n /** The nose's and the mouth's shift MAGNITUDES when the caller measured\n * them — `TurnTargets`' \"the sign is ignored\", taken at the door like\n * `eyeShift`'s. Absent when defaulted: the family then takes its share of\n * the eyes' ACHIEVED shift, known only per candidate\n * (`solveFeatureDepths`), and a bound that cuts it short is a clamp rather\n * than a refusal. */\n noseShift?: number;\n mouthShift?: number;\n /** Whether the ratio is this generator's own default — carried through as\n * the hold's own ratio and clamped to what the grid can reach — or a\n * caller's render measurement, which the hold is fitted to and which is\n * rejected when no hold renders it. Same split as `clampEyeShift`, for the\n * same reason. */\n clampSilhouetteRatio: boolean;\n /** hair_front's own rest centre and crop, for `hairFrontLandingAt` — its\n * own mesh geometry (`meshCellsFor`), the rows its lead and its join with\n * the plate are baked at — and the silhouette point used when\n * `headHalfWidth` is not measured (see `evaluateTurnCandidate`'s\n * silhouette-centre correction). Undefined when there is no hair_front\n * layer, in which case that correction falls back to the ideal hold\n * destinations, symmetric about the face centre, and no lead is modelled:\n * there are no bangs to carry one. */\n hairFrontSilhouette?: {\n x: number;\n centerY: number;\n cropW: number;\n cropH: number;\n };\n /** Every role with an opaque pixel in the eye-row band, per side, each\n * with its OWN rest x there — as the mcp layer measures it (`rowSpansByRole`),\n * a companion to a MEASURED `headHalfWidth`, not a caller-facing target.\n * `evaluateTurnCandidate` takes the OUTERMOST *landing* across a side's own\n * list, not the outermost REST x: which part ends up furthest out after\n * the turn can differ from which one drew furthest out at rest (the bangs'\n * own lead carrying them past a held back-hair edge, say). Absent on the\n * fallback path (no measured head to report edges for), which keeps\n * hair_front's own crop edge as the silhouette point on both sides, as if\n * it always owned them. */\n headEdges?: {\n left: { role: string; x: number }[];\n right: { role: string; x: number }[];\n };\n /** The bangs' side strand against each far iris, per side the caller\n * measured one (`options.strandEdges`): the side's sign — −1 for the −x\n * side, whose far stops are −15 and −30 — the carrier its iris renders on\n * (the eye grid of that side, the irises paired to sides by `part.x`, not\n * by role name), and its iris and run edges on the iris row. What caps the\n * eye family's depth below the plate's (`evaluateTurnCandidate`). Absent\n * without the option, when the plate alone caps it. */\n strands?: (IrisStrand & { side: -1 | 1; carrier: TurnCarrier })[];\n}\n\n/**\n * Where a hair_front vertex resting at `(x, y)` is sent at one turn stop — the\n * three-zone rule `bakeHairFrontSilhouetteWarp` bakes into a per-vertex\n * offset and `evaluateTurnCandidate` reads the composed landing of, factored\n * into one place so the two cannot drift apart. The zones are REST distances\n * from the face centre, so they do not move with the stop; only the\n * destinations do:\n *\n * - within the plate's painted half-width on the hold row, `edgeAt(row)`:\n * where the RENDERED plate lands that very x on that row\n * (`plateLandingAt` — the face's own mesh and grid, not the bare\n * surface), so on rows at and above the widest the bangs sit on the face\n * they cover through the whole turn;\n * - out to `holdBase`: a straight ramp from the painted edge's rendered\n * landing on the hold row onto `holdEdge`, the hold's own destination at\n * this stop;\n * - beyond `holdBase`: `holdEdge`'s own displacement, slope 1 in REST x, so\n * the outer strands carry whatever silhouette change was asked for and\n * keep their spacing.\n *\n * The hold row is the vertex's own row, except below the face's widest row\n * (`widestY`, when the face measured a row profile): a vertex resting lower\n * holds exactly as one at the same x on the widest row does. Read on its own\n * row, a side lock's column crossed from the plate zone onto the ramp\n * wherever the jaw's painted edge tapered in past it between two mesh rows,\n * and the lock creased there at every turned stop. Held at the widest row,\n * every hanging row of a column carries the same hold, and what varies down\n * the lock is the root-pinned lead alone: the lock follows its root, as a\n * lock hanging straight down does. So below that row the bangs no longer\n * ride the jaw they overlap — the jaw slides under a near-side lock and away\n * from a far-side one. Without a profile `widestY` is −∞ and every vertex\n * holds on its own row.\n *\n * A vertex exactly on the axis never leaves the first zone, so its zero\n * `side` is never read.\n */\nfunction hairFrontHoldTarget(\n x: number,\n y: number,\n deg: number,\n faceCenterX: number,\n plateLandingAt: (deg: number, x: number, y: number) => number,\n edgeAt: (y: number) => number,\n holdBase: number,\n holdEdge: number,\n widestY = -Infinity,\n): number {\n const local = x - faceCenterX;\n const dist = Math.abs(local);\n const side = Math.sign(local);\n const row = Math.max(y, widestY);\n const painted = edgeAt(row);\n if (dist <= painted) return plateLandingAt(deg, x, row);\n if (dist <= holdBase) {\n const inner = plateLandingAt(deg, faceCenterX + side * painted, row);\n const u = (dist - painted) / (holdBase - painted);\n return inner + (faceCenterX + side * holdEdge - inner) * u;\n }\n return x + side * (holdEdge - holdBase);\n}\n\n/**\n * hair_front's own silhouette landing at an ARBITRARY rest point — not the\n * analytic hold + lead at that point, but what the renderer actually draws\n * there, on the mesh the bake ships: `renderedLandingX` on the bangs' own\n * mesh, every vertex landed as the two warps land it — the hold's own target\n * for that vertex (`holdTargetAt`, the rule the bake writes) plus its row's\n * share of the turn lead — and the point read over the triangle the mesh\n * draws it with. Interpolating anything earlier — the rest xs, or an analytic\n * lead at a row the mesh has no vertex on — is a different number wherever a\n * hold zone's boundary falls between two columns, or the lead's own\n * `u^HAIR_SWAY_CURL` curve bends between two rows.\n *\n * `leadShift` is the bangs' SIGNED tip lead in px at the stop being read: the\n * lead warp is keyed at ±30 (`0 − tipShift·u^CURL` at −30, `tipShift·u^CURL`\n * at +30, see `bakeHairSwayWarp`) and blends linearly between, so at stop d it\n * is `tipShift·d/30` — `−tipShift` at full far turn. Each vertex lands at its\n * hold target plus its own row's share of it, `leadShift·u^HAIR_SWAY_CURL`:\n * `u` is its distance from the pinned root as a fraction of the crop's height,\n * the shipped lead warp's own shape.\n *\n * The point need not be inside hair_front's own crop at all — past its outer\n * column or row this reads the end column or row, the landing of the nearest\n * point the mesh actually draws.\n */\nfunction hairFrontLandingAt(\n x: number,\n y: number,\n hairFront: { x: number; centerY: number; cropW: number; cropH: number },\n holdTargetAt: (restX: number, restY: number) => number,\n leadShift: number,\n): number {\n const { cols, rows } = meshCellsFor(hairFront.cropW, hairFront.cropH);\n const top = hairFront.centerY + hairFront.cropH / 2;\n return renderedLandingX(\n {\n x: hairFront.x,\n y: hairFront.centerY,\n cropW: hairFront.cropW,\n cropH: hairFront.cropH,\n cols,\n rows,\n scaleX: 1,\n scaleY: 1,\n rotation: 0,\n },\n (vx, vy) =>\n holdTargetAt(vx, vy) +\n leadShift * Math.pow((top - vy) / hairFront.cropH, HAIR_SWAY_CURL),\n x,\n y,\n );\n}\n\n/**\n * The most sideways travel a MEASURED shell can swallow at this radius: how far\n * the face may slide before the plate's own edge reaches the hold's boundary.\n *\n * Read off the BEND ALONE (`bendOnlyLandingAt` — the rendered plate at a stop\n * with no travel in it; each row's own chin swing, which does not scale with\n * the travel, stays in), the slide being what is solved for here: at each\n * turned stop the plate's painted edges land either side of the face centre —\n * read at every guard row, kept SIGNED — and the slide then pushes the edge on\n * the side it moves toward further out while pulling the other one in. So\n * only the TOWARD edge binds, at whichever row it lands furthest. A\n * max-absolute reach would bind on the near edge instead, which a tight bend\n * throws wide precisely BEFORE the slide pulls it back inside the shell, and\n * would refuse the radius the slide was about to rescue.\n *\n * Each stop carries |deg|/30 of the travel, so the room one stop has left is\n * scaled back up by 30/|deg| to say what it allows at FULL turn, and the\n * tightest stop wins. Floored at 0: a plate already reaching past the shell\n * cannot be slid any further out.\n *\n * `holdEdgeAt` is asked for the boundary on the side the slide moves TOWARD,\n * because that is the only side the plate can breach: a measured shell need\n * not sit centred on the face (`TurnSolveContext.headEdges` records each\n * side's own extreme), and a shell 300 px out on one side and 500 on the\n * other has 300 px of room for a −30° slide however much it has for a +30°\n * one. Taking one symmetric number for both directions would let the plate\n * slide straight out of the narrow side and become the silhouette there.\n *\n * `askedTravel` is only read for the slide's SIGN at each stop, which is the\n * turn's own; the cap it returns is the whole ceiling, for the caller to take\n * a minimum with.\n */\nfunction shellTravelCap(\n faceCenterX: number,\n edgeAt: (y: number) => number,\n plateGuardRows: number[],\n askedTravel: number,\n bendOnlyLandingAt: (deg: number, x: number, y: number) => number,\n holdEdgeAt: (deg: number, side: -1 | 1) => number,\n): number {\n let cap = Infinity;\n for (const deg of HEAD_TURN_STOPS) {\n if (deg === 0) continue; // nothing slides at rest, so nothing to cap\n const slideSign: -1 | 1 = turnSlide(askedTravel, deg) < 0 ? -1 : 1;\n // The toward edge's furthest landing over the guard rows, on the bend\n // alone.\n const towardReach = plateSideReachAt(\n bendOnlyLandingAt,\n faceCenterX,\n edgeAt,\n plateGuardRows,\n deg,\n slideSign,\n );\n cap = Math.min(\n cap,\n (Math.max(0, holdEdgeAt(deg, slideSign) - HOLD_CLEARANCE - towardReach) *\n HEAD_TURN_MAX_DEG) /\n Math.abs(deg),\n );\n }\n return cap;\n}\n\n/** Everything ONE hold ratio settles inside `evaluateTurnCandidate`: the\n * travel that ratio leaves the sliding plate, the map that carries it, and\n * where each side's silhouette lands through that map, every `headEdges`\n * owner of the face family at the depths the evaluation was handed. The\n * ratio the rig ships is picked among these — fitted until\n * `renderedSilhouetteRatio` is the target, whether that target was measured\n * or defaulted. */\ninterface TurnHoldEval {\n /** The hold's own ratio: where its boundary is sent at full turn, as a\n * fraction of the rest distance it keeps. */\n ratio: number;\n /** The sideways travel it leaves the face at full turn, px. */\n travel: number;\n /** Its column map at the −30° stop, where every cue is measured, for a\n * point resting at `y`. */\n mapAt: (y: number) => TurnColumnMap;\n /** The surface `mapAt` is read off: the candidate's radius, this travel. */\n surface: TurnSurface;\n holdBase: number;\n holdEdgeAt: (deg: number) => number;\n /** Whether any stop sends the hold edge inside the plate's own reach, which\n * runs the ramp between them backwards — never acceptable, see\n * `bakeHairFrontSilhouetteWarp`. */\n folds: boolean;\n /** The ratio at which the tightest stop's hold edge would exactly clear the\n * plate ON THIS HOLD's own slide — a seed for the range's floor, not the\n * floor itself, see `holdRange`. */\n foldLowerBound: number;\n silhouetteCenterShift: number;\n renderedSilhouetteRatio: number;\n /** The silhouette's own rest span, px: what the rendered ratio is a\n * fraction of, and so the scale a px tolerance on it converts through. */\n restSpan: number;\n}\n\n/** Aitken's Δ² extrapolation of a geometrically contracting sequence from\n * three consecutive terms — the fixed point itself when the contraction is\n * linear — clamped into [0, cap], the depths a solve can return and so where\n * the fixed point lies. The last term as it stands when the sequence has\n * stopped moving (a depth pinned at a bound) or its steps do not contract,\n * where the formula has nothing to extrapolate from. */\nfunction aitken(x0: number, x1: number, x2: number, cap: number): number {\n const d1 = x1 - x0;\n const d2 = x2 - x1;\n const denominator = d2 - d1;\n if (denominator === 0) return x2;\n const x = x2 - (d2 * d2) / denominator;\n return Number.isFinite(x) ? Math.min(cap, Math.max(0, x)) : x2;\n}\n\n/**\n * The deepest eye depth in [0, `plateCap`] at which every strand margin holds\n * — each of `margins` one far stop of one side with a face-side edge, mapping\n * a depth to that stop's clearance less its allowance (`evaluateTurnCandidate`):\n * - `plateCap` itself when every margin is ≥ 0 there — the plate binds\n * first, and the rig is the one built without the strand, byte for byte;\n * - `undefined` when some margin is < 0 at depth 0 — no depth keeps that\n * iris clear, so the bound cannot hold at this radius;\n * - otherwise the satisfying end of a TURN_BISECT_STEPS bisection.\n * The bisection is valid because every margin is non-increasing in the depth:\n * the iris's landing moves toward the far side with it (`mapX` is monotone,\n * and both interpolations weight it non-negatively), while within one hold the\n * run's landing does not depend on the eye depth at all.\n */\nfunction strandDepthCap(\n margins: ((depth: number) => number)[],\n plateCap: number,\n): number | undefined {\n const holds = (depth: number) => margins.every((m) => m(depth) >= 0);\n if (holds(plateCap)) return plateCap;\n if (!holds(0)) return undefined;\n let lo = 0;\n let hi = plateCap;\n for (let i = 0; i < TURN_BISECT_STEPS; i++) {\n const mid = (lo + hi) / 2;\n if (holds(mid)) lo = mid;\n else hi = mid;\n }\n return lo;\n}\n\n/**\n * One radius, evaluated against the cues: the candidate it yields, or which\n * target blocked it and what it could have done instead.\n *\n * Everything hangs off ONE number here, the hold's own ratio: it sizes the\n * face's sideways travel (`shellTravelCap` measures the shell's room against\n * where the hold sends its boundary), and that travel builds the map every\n * landing is read through — so a slide that pulls an over-bent near edge back\n * inside the shell rescues a radius the bend alone would have folded, and a\n * bend that folds on its own still loses one. `evaluateHold` is that whole\n * chain for one ratio, and nothing outside it may assume a hold.\n *\n * The silhouette target is a measurement of a RENDER — the outermost opaque\n * edge on each side, whichever part owns it — so the hold is FITTED to it\n * rather than set from it: `holdRange` samples the holds this radius can ship\n * and the fit bisects the sampled interval that straddles the ask, which lands\n * on it exactly. What a render shows is NOT monotone in the hold (see\n * `holdRange`), so no sample's orientation is assumed. Where the target came\n * from decides only what happens when the range cannot render it: a\n * CALLER-measured one is refused, naming that range, and a DEFAULTED one takes\n * the nearest end of it and is reported in `TurnSolveReport.clamped`. Folding\n * is never acceptable either way.\n *\n * The silhouette and the depths are ONE fixed point. A `headEdges` owner of\n * the face family — a brow, a blush, a white, the nose, a mouth — lands\n * through its group grid, whose keyforms carry its family's solved depth\n * (`bakeTurnGroupWarp2D`'s `shiftAt`); the eye depth is solved against the\n * silhouette's centre, and the nose's and the mouth's against that centre and\n * the eyes' achieved shift. So each pass fits the hold with every owner landed\n * at the pass before's depths (none on the first — the read a hold-owned\n * silhouette gets whatever the depths), solves the three depths on it, and\n * RE-READS that same hold at the depths it solved: when the re-read moves\n * neither the centre nor the rendered span by more than TURN_DEPTH_EPS px,\n * the pass is the fixed point, and the candidate reports the re-read — the\n * numbers the emitted grids render — with the depths that produced it. A\n * silhouette owned by the bangs, the back hair, the body or the plate does\n * not move with a depth, so it settles on the first pass, the single-pass\n * solve to the bit. Otherwise the passes contract geometrically (an owner's\n * landing moves by a fraction of the depth it feeds back into), accelerated by\n * Aitken's Δ² on the three depths every second pass; a radius that has not\n * settled within TURN_SETTLE_PASSES yields a `settle` miss rather than a\n * candidate reported off its render, and the sweep moves on to the next\n * radius — an edge owned on BOTH sides by the family the eye cue slides\n * leaves that cue nearly independent of the depth, which no pass count\n * settles, and a layer set no sampled radius settles is refused by\n * `solveTurnModel`, naming that. The refusals — a caller's silhouette the\n * range cannot render, a caller's eye shift below the floor, less than the\n * pair already slides at depth 0 — are issued at the fixed point too, on the\n * range and bounds the shipped depths have, not on a pass's guess. An eye\n * shift the art's room cuts short at the cap (the plate's edge, or the strand\n * bound on the far irises, best effort) is no refusal, whoever passed it: the\n * candidate keeps the capped depth, and says whether the strand bound could\n * hold here at all (`strandFeasible`) and how much of each far iris the\n * bangs still cover (`strandOverlap`).\n */\nfunction evaluateTurnCandidate(\n ctx: TurnSolveContext,\n radius: number,\n): TurnCandidate | TurnCandidateMiss {\n // This radius as a head: the surface every map below is read off, at the\n // travel the hold under evaluation leaves the face.\n const surfaceAt = (travel: number) =>\n turnSurface({\n faceCenterX: ctx.faceCenterX,\n faceCenterY: ctx.faceCenterY,\n radius,\n travel,\n nodRadius: ctx.nodRadius,\n lattice: ctx.lattice,\n profile: ctx.profile,\n eyeRowY: ctx.eyeRowY,\n });\n // The travel is settled BEFORE the map that carries it, the map being built\n // from it: the face slides INSIDE a shell the bangs hold, so what it may\n // slide is what that shell has room for — measured on the bend alone, as\n // the plate renders it (the chin swing rides the rows, not the travel, so\n // it is in this landing too: what the travel has to fit beside).\n const bendOnlyLandingAt = plateLandingOn(ctx.plate, rowMapsOf(surfaceAt(0)));\n // The measured silhouette's own rest extreme on one side, when `headEdges`\n // recorded one there. Read both by the travel cap below — which side of the\n // shell the sliding plate has to stay inside — and by `restAt`, so the two\n // cannot disagree about where the shell's boundary actually is.\n const measuredEdgeAt = (side: -1 | 1): number | undefined => {\n const candidates = side < 0 ? ctx.headEdges?.left : ctx.headEdges?.right;\n if (candidates === undefined || candidates.length === 0) return undefined;\n const xs = candidates.map((c) => c.x);\n return side < 0 ? Math.min(...xs) : Math.max(...xs);\n };\n // That side's own half-width: a measured shell can sit off centre, and the\n // narrow side is the one the plate can breach. Without an edge list there\n // the measured half-width stands in on both sides, as it always did.\n const shellHalfWidthAt = (side: -1 | 1): number => {\n const edge = measuredEdgeAt(side);\n return edge === undefined\n ? ctx.headHalfWidth!\n : side * (edge - ctx.faceCenterX);\n };\n const unit = headTurnParallaxUnit(radius);\n // The eyes' depth cap at this radius (`familyDepthCap`), read once for the\n // strand bound — which only ever tightens it, and is handed back to the\n // eye solve — and Aitken's clamp below.\n const plateCapOf = (marks: TurnLandmark[]) =>\n familyDepthCap(marks, ctx.plateEdgeX, unit);\n const eyePlateCap = plateCapOf(ctx.landmarks.eye);\n // The bangs' own root-pinned turn lead (bakeHairSwayWarp, shared with the\n // sway): the shipped warp's own full TIP shift, which each row takes its\n // `u^HAIR_SWAY_CURL` share of. It moves every row toward the far side by\n // that row's amount regardless of which side of the axis a column sits on —\n // a raw offset summed onto the hold's own target at each vertex inside\n // `hairFrontLandingAt`, exactly like `bakeHairSwayWarp` and\n // `bakeHairFrontSilhouetteWarp` sum their offsets on the shipped mesh; the\n // rows the renderer interpolates between are the MESH's, so the row goes\n // down with it rather than a lead pre-sampled at the measured row. Only\n // hair_front leads — a role `landingOfRole` sends down any other path does\n // not use this at all. The hold does not move it, so it is settled once,\n // outside the fit.\n const leadTipShift =\n ctx.hairFrontSilhouette === undefined ? 0 : HAIR_FRONT_DEPTH * unit;\n\n // Where the bangs render a rest point `(x, y)` at the stop `deg` on one\n // hold: their own mesh, each vertex holding the outline where the bake\n // sends it (`hairFrontHoldTarget`, over the plate as it renders on that\n // hold's slide, `plateLandingAt`, a vertex below the profile's widest row\n // held as that row is) plus its row's share of the SIGNED tip lead at that\n // stop, `leadShift` (`hairFrontLandingAt`). Read only on a layer set with\n // bangs: the silhouette's own landing checks for them, and solveTurnModel\n // builds strands only where they exist.\n const bangsLandingAt = (\n hold: { holdBase: number; holdEdgeAt: (deg: number) => number },\n plateLandingAt: (deg: number, x: number, y: number) => number,\n deg: number,\n leadShift: number,\n x: number,\n y: number,\n ): number =>\n hairFrontLandingAt(\n x,\n y,\n ctx.hairFrontSilhouette!,\n (vx, vy) =>\n hairFrontHoldTarget(\n vx,\n vy,\n deg,\n ctx.faceCenterX,\n plateLandingAt,\n ctx.edgeAt,\n hold.holdBase,\n hold.holdEdgeAt(deg),\n ctx.profile?.widestY,\n ),\n leadShift,\n );\n\n // The strand bound's reads on one hold (`ctx.strands`), at each FAR stop of\n // each measured side — σ = −1 at −15 and −30, +1 at +15 and +30; depth\n // carries the near iris away from its own strand, so only the far side is\n // read. Every edge is a rest point on the iris row, landed exactly as it\n // renders at that stop with AngleY, gaze, the sway and MouthForm at rest:\n // the run's two edges on the bangs' own mesh — the hold's target at that\n // stop plus the lead there, `tipShift·deg/30` (the lead warp is keyed at\n // ±30 and blends linearly) — neither of which moves with the eye depth; the\n // iris's through its own carrier on that stop's map, the eye grid's node\n // rule shifted by the depth's share at that stop, `depth·unit·deg/30`. The\n // bangs and the iris ride different deformations, so neither is assumed to\n // follow the other.\n //\n // Checking the stops is exact: at AngleY 0 every vertex's landing is linear\n // in AngleX between two stops — the grid's nodes blend parameter-linearly,\n // the bangs' hold is keyed at the same stops and their lead at ±30, and a\n // warp child binds at its rest pose, so the bilinear and the barycentric\n // weights are fixed — so a clearance that meets its allowance at rest and\n // at every stop meets it everywhere between them.\n const strandStopsOn = (hold: TurnHoldEval) => {\n const rowMaps = rowMapsOf(hold.surface);\n const plateLandingAt = plateLandingOn(ctx.plate, rowMaps);\n return (ctx.strands ?? []).map((strand) => {\n const iris: TurnLandmark = {\n x: strand.carrier.part.x,\n w: strand.carrier.part.cropW,\n carrier: strand.carrier,\n };\n const stops = HEAD_TURN_STOPS.filter(\n (deg) => Math.sign(deg) === strand.side,\n ).map((deg) => {\n const runAt = (x: number) =>\n bangsLandingAt(\n hold,\n plateLandingAt,\n deg,\n (leadTipShift * deg) / HEAD_TURN_MAX_DEG,\n x,\n strand.y,\n );\n return {\n deg,\n runOuter: runAt(strand.runOuter),\n runFace: strand.runFace === null ? null : runAt(strand.runFace),\n irisAt: (depth: number, x: number) =>\n landmarkLandingX(\n iris,\n (y) => rowMaps(deg, y),\n deg,\n (depth * unit * deg) / HEAD_TURN_MAX_DEG,\n x,\n strand.y,\n ),\n };\n });\n // Clearance = σ·(landed runFace − landed irisOuter), and the allowance\n // is the painted clearance capped at 0 (the rest landing is the\n // identity): an iris painted clear of the run may close to touching,\n // one painted under it — a covered centre included — keeps its rest\n // overlap but does not deepen it. No pixel margin: both are pixel\n // boundaries on one row. Why not the full painted gap: the face's own\n // foreshortening already closes it at depth 0, so that rule would leave\n // most characters with no depth at all. Undefined without a face-side\n // edge, which has nothing to keep the iris clear of.\n const { runFace } = strand;\n if (runFace === null) return { strand, stops, margins: undefined };\n const allowance = Math.min(0, strand.side * (runFace - strand.irisOuter));\n const margins = stops.map(\n (stop) => (depth: number) =>\n strand.side * (stop.runFace! - stop.irisAt(depth, strand.irisOuter)) -\n allowance,\n );\n return { strand, stops, margins };\n });\n };\n\n /** How much of each far iris the run covers at eye depth `depth` — the\n * landed iris span inside the landed run, at the far stop where that is\n * largest — per side that has any: `TurnSolveReport.strandOverlap`. */\n const strandOverlapOf = (\n sides: ReturnType<typeof strandStopsOn>,\n depth: number,\n ): TurnCandidate[\"strandOverlap\"] => {\n const overlap: NonNullable<TurnCandidate[\"strandOverlap\"]> = {};\n for (const { strand, stops, margins } of sides) {\n const s = strand.side;\n // In outward-positive x: the iris spans [s·inner, s·outer], the run\n // [s·face, s·outer] — face-ward without end when it has no face edge.\n const covered = (\n irisOuter: number,\n irisInner: number,\n runOuter: number,\n runFace: number | null,\n ) =>\n Math.max(\n 0,\n Math.min(s * irisOuter, s * runOuter) -\n Math.max(s * irisInner, runFace === null ? -Infinity : s * runFace),\n );\n let worst: { deg: number; px: number } | undefined;\n for (const stop of stops) {\n const px = covered(\n stop.irisAt(depth, strand.irisOuter),\n stop.irisAt(depth, strand.irisInner),\n stop.runOuter,\n stop.runFace,\n );\n if (px > 0 && (worst === undefined || px > worst.px)) {\n worst = { deg: stop.deg, px };\n }\n }\n if (worst === undefined) continue;\n overlap[s < 0 ? \"left\" : \"right\"] = {\n deg: worst.deg,\n px: worst.px,\n hh: worst.px / ctx.hh,\n restPx: covered(\n strand.irisOuter,\n strand.irisInner,\n strand.runOuter,\n strand.runFace,\n ),\n held: margins !== undefined && margins.every((m) => m(depth) >= 0),\n };\n }\n return overlap.left === undefined && overlap.right === undefined\n ? undefined\n : overlap;\n };\n\n const evaluateHold = (ratio: number, depths: TurnDepths): TurnHoldEval => {\n // The boundary stays put and its DESTINATION moves: the full ratio at the\n // outer stops, none of it at rest, linear in between.\n const holdEdgeFrom = (base: number) => (deg: number) =>\n base * (1 + ((ratio - 1) * Math.abs(deg)) / HEAD_TURN_MAX_DEG);\n // A measured head IS the hold's boundary, so the plate has to stay inside\n // it once slid — `shellTravelCap` is what keeps it there. A hold base\n // derived from the plate instead (below) is measured on the SLID maps and\n // clears the slid plate by construction, so there the whole ask stands.\n const travel =\n ctx.headHalfWidth === undefined\n ? ctx.travel\n : Math.min(\n ctx.travel,\n shellTravelCap(\n ctx.faceCenterX,\n ctx.edgeAt,\n ctx.plateGuardRows,\n ctx.travel,\n bendOnlyLandingAt,\n (deg, side) => {\n const rest = shellHalfWidthAt(side);\n // Never past the shell's own REST edge, whatever the hold\n // does: a hold wider than it (the fit sends one there to keep\n // a rendered ratio of 1 against the bangs' own lead) moves the\n // bangs, and the parts that draw the outline beside them — a\n // static hair_back above all — stay where they are drawn.\n return Math.min(rest, holdEdgeFrom(rest)(deg));\n },\n ),\n );\n const surface = surfaceAt(travel);\n // One memoised reader of this surface's maps for every carrier read on\n // it: the −30° map for a point resting at `y`, where every cue is\n // measured, is what each landmark's and candidate's nodes are read off.\n const rowMaps = rowMapsOf(surface);\n const mapAt = (y: number) => rowMaps(-HEAD_TURN_MAX_DEG, y);\n // The plate as it renders on THIS hold's slide: what its painted edge is\n // read off below, and what the bangs' join sits on.\n const plateLandingAt = plateLandingOn(ctx.plate, rowMaps);\n // Without a measured head the boundary is the outermost the slid plate\n // ever reaches, clear of it by HOLD_CLEARANCE.\n const holdBase =\n ctx.headHalfWidth ??\n plateReach(\n plateLandingAt,\n ctx.faceCenterX,\n ctx.edgeAt,\n ctx.plateGuardRows,\n ) + HOLD_CLEARANCE;\n const holdEdgeAt = holdEdgeFrom(holdBase);\n\n // The floor of the bracket this hold could have been fitted in: the ratio\n // the plate-fold geometry allows. Computed EVERY time, whether or not it\n // binds, so a refusal never advertises a range it would have narrowed.\n //\n // SAFE but not TIGHT: `reach` is read off `plateLandingAt`, the slid\n // plate, and with a measured head the ratio sizes that slide as well\n // (`shellTravelCap` reads `holdEdgeAt`). At the binding stop the cap\n // moves with the ratio at `holdBase` per unit, i.e. the reach moves at\n // `holdBase·|deg|/30` — exactly the rate the hold edge itself moves — so a\n // ratio narrower than this bound arrives with a smaller slide and can\n // still clear the plate. Everything at or above the bound holds; some\n // below it do too. The reach is the same list of rows through the same\n // function the bake's own guard iterates (`bakeHairFrontSilhouetteWarp`),\n // so a hold that clears it here clears it there.\n let foldLowerBound = -Infinity;\n let folds = false;\n for (const deg of HEAD_TURN_STOPS) {\n if (deg === 0) continue; // proven never to bind — holdBase always clears the rest reach\n const reach = plateReachAt(\n plateLandingAt,\n ctx.faceCenterX,\n ctx.edgeAt,\n ctx.plateGuardRows,\n deg,\n );\n // What the ratio would have to be for the hold edge to clear the plate\n // at this stop, given the stop gets |deg|/30 of the ratio's travel.\n foldLowerBound = Math.max(\n foldLowerBound,\n 1 +\n ((reach + HOLD_CLEARANCE) / holdBase - 1) *\n (HEAD_TURN_MAX_DEG / Math.abs(deg)),\n );\n if (holdEdgeAt(deg) <= reach) folds = true;\n }\n\n // The silhouette point each side's centre is read from:\n // `measure_turn_reference` reads the eye pair against the OPAQUE UNION's\n // own centre, not an assumed faceCenterX-symmetric one, so with\n // `headEdges` this is that same union's own extreme — the min (−x side) /\n // max (+x side) of every candidate's own rest x, not `holdBase`'s\n // idealised, centred stand-in for it (`holdBase` still sizes the HOLD\n // itself; this is only where the DRIFT is measured from). Without an edge\n // list for this side — no measured head at all, or one measured but with\n // nothing recorded there — the measured head's own edge (`holdBase` IS\n // that edge then, on the nose by construction), or hair_front's own crop\n // edge otherwise: the best available stand-in for where a render's\n // outermost opaque pixel sits when nothing measured it.\n const restAt = (side: -1 | 1): number => {\n const edge = measuredEdgeAt(side);\n if (edge !== undefined) return edge;\n return ctx.headHalfWidth !== undefined ||\n ctx.hairFrontSilhouette === undefined\n ? ctx.faceCenterX + side * holdBase\n : ctx.hairFrontSilhouette.x +\n (side * ctx.hairFrontSilhouette.cropW) / 2;\n };\n // Where the bangs land a point on the eye row at full turn, on this hold\n // and the rendered plate, with the full far lead.\n const hairFrontAt = (x: number): number =>\n ctx.hairFrontSilhouette === undefined\n ? x\n : bangsLandingAt(\n { holdBase, holdEdgeAt },\n plateLandingAt,\n -HEAD_TURN_MAX_DEG,\n -leadTipShift,\n x,\n ctx.eyeRowY,\n );\n // Where a named role's OWN rest x lands after the turn — the mcp measures\n // the union of every layer's opaque pixels, so an edge can belong to any\n // role, not just the bangs. `face` and the FEATURE_NOD_DEPTH family (the\n // eye stack, lashes, brows, blush, the nose, both mouths) each ride their\n // own group grid, so each is read through its own carrier — the grid and\n // mesh it renders with — at the shift that grid's keyforms carry: its\n // turn family's depth in `depths` times the unit, toward the far side\n // (`bakeTurnGroupWarp2D`'s `shiftAt` at this stop), none for the plate,\n // which IS the surface. Those depths are what this very hold is being\n // solved for, which is why the candidate is a fixed point (see this\n // function's doc). `hair_back` and `body` ride their own rigid deformers,\n // not a group grid, so neither reads a map at all. Anything not in\n // `ROLE_TABLE` is a bug, not a role to render as unmoved.\n const landingOfRole = (role: string, x: number): number => {\n if (role === \"hair_front\") return hairFrontAt(x);\n if (role === \"hair_back\") {\n // It holds the head's outline: it rides a head that no longer travels\n // and has no turn binding or warp of its own, so it lands where it\n // sits.\n return x;\n }\n if (role === \"body\") {\n // The head no longer translates on the turn — its travel is in the\n // group grids, where only what rides a grid carries it — so the head's\n // frame is the rest frame and the body lands at its own AngleX travel\n // alone: the \"from\" value of its own translateX binding at this stop.\n return x - BODY_TURN_FOLLOW * travel;\n }\n if (\n role === \"face\" ||\n FEATURE_NOD_DEPTH[roleFamily(role)] !== undefined\n ) {\n const carrier = ctx.carriers.get(role);\n if (carrier === undefined) {\n // The mcp measures edges off the layers it rigs, so a face-family\n // role it names is one this layer set has — anything else is a\n // caller's list that does not belong to these layers.\n throw new Error(\n `auto-rig: evaluateTurnCandidate: headEdges names \"${role}\", which this layer set has no layer for`,\n );\n }\n const shift = role === \"face\" ? 0 : -depths[turnFamily(role)] * unit;\n return carrierLandingX(\n carrier,\n (nodeX, nodeY) =>\n groupNodeLanding(\n carrier.anchor,\n mapAt,\n -HEAD_TURN_MAX_DEG,\n shift,\n nodeX,\n nodeY,\n ).x,\n x,\n ctx.eyeRowY,\n );\n }\n throw new Error(\n `auto-rig: evaluateTurnCandidate: unrecognised role \"${role}\"`,\n );\n };\n const landingAt = (side: -1 | 1): number => {\n const candidates = side < 0 ? ctx.headEdges?.left : ctx.headEdges?.right;\n if (candidates === undefined || candidates.length === 0) {\n return hairFrontAt(restAt(side));\n }\n const landings = candidates.map((c) => landingOfRole(c.role, c.x));\n return side < 0 ? Math.min(...landings) : Math.max(...landings);\n };\n return {\n ratio,\n travel,\n mapAt,\n surface,\n holdBase,\n holdEdgeAt,\n folds,\n foldLowerBound,\n // How far the silhouette's own centre drifts off the face centre at full\n // turn, against how far it already sat off centre at rest (zero unless\n // hair_front's crop is itself off-centre) — the held shell keeps its own\n // place while the face slides inside it, so this is the drift the hold\n // and the bangs' lead leave behind, and the slide itself shows up in the\n // eye cue instead of cancelling out of it.\n // `measure_turn_reference` reads the eye pair against THIS moved centre,\n // not the face centre, so it is what the eye cue is corrected by.\n silhouetteCenterShift:\n (landingAt(1) + landingAt(-1)) / 2 - (restAt(1) + restAt(-1)) / 2,\n // What a render actually shows: the real landing span over the real rest\n // span, the same pair silhouetteCenterShift reads — the measure the\n // hold is FITTED against, see this function's own doc.\n renderedSilhouetteRatio:\n (landingAt(1) - landingAt(-1)) / (restAt(1) - restAt(-1)),\n restSpan: restAt(1) - restAt(-1),\n };\n };\n\n // Every hold this radius can actually ship, sampled across its own usable\n // range: from the narrowest that does not fold the ramp onto the strands up\n // to the widest ratio the targets accept, the bangs being free to hold the\n // outline wherever they are sent now that no grid edge stops them. What a\n // render SHOWS is not assumed monotone in the hold — a wider hold buys more\n // travel (`shellTravelCap`), and the travel moves the plate's own edge that\n // the bangs' inner strands ride — so the range is sampled rather than read\n // off its two ends, and the fit below works in whichever sampled interval\n // actually straddles the ask. Every sample is fold-free, so anything the\n // fit returns from one can be shipped.\n //\n // `foldLowerBound` is a SEED for the floor, not the floor: it reads the\n // plate's reach off one hold's own slide, and that reach does not move with\n // the hold the way it assumes. A narrower hold buys a smaller slide, which\n // pulls the plate's toward edge in but pushes its AWAY edge out, and\n // `plateReachAt` takes the larger of the two — so the seed can itself fold,\n // and the real floor is bisected between it and the ceiling, where the\n // slide is largest and the hold furthest out. Undefined when even the\n // ceiling folds: the radius can hold nothing, so it has nothing to offer.\n const holdRange = (depths: TurnDepths): TurnHoldEval[] | undefined => {\n const ceiling = evaluateHold(TURN_RATIO_MAX, depths);\n if (ceiling.folds) return undefined;\n let floor = evaluateHold(\n Math.min(ceiling.foldLowerBound, ceiling.ratio),\n depths,\n );\n if (floor.folds) {\n let lo = floor;\n let hi = ceiling;\n for (let i = 0; i < TURN_BISECT_STEPS; i++) {\n const mid = evaluateHold((lo.ratio + hi.ratio) / 2, depths);\n if (mid.folds) lo = mid;\n else hi = mid;\n }\n floor = hi;\n }\n const samples = [floor];\n for (let i = 1; i < TURN_HOLD_SAMPLES; i++) {\n const sample = evaluateHold(\n floor.ratio + ((ceiling.ratio - floor.ratio) * i) / TURN_HOLD_SAMPLES,\n depths,\n );\n if (!sample.folds) samples.push(sample);\n }\n samples.push(ceiling);\n return samples;\n };\n\n /** What this radius offers a refused caller: the ratios a render of it can\n * show, clipped to the domain `resolveTurnTargets` accepts — offering one\n * outside it would be offering a number the validator throws on. Clipped\n * PER RADIUS, before `sweepTurnRadii` unions them, so each end of what the\n * sweep advertises is an end some radius can really be fitted to. The\n * neutral pair when nothing survives that clip (or there was nothing to\n * clip): `sweepTurnRadii`'s own min/max ignore it. */\n const silhouetteMiss = (span?: [number, number]): TurnCandidateMiss => {\n const lower = Math.max(span?.[0] ?? Infinity, SILHOUETTE_RATIO_MIN);\n const upper = Math.min(span?.[1] ?? -Infinity, TURN_RATIO_MAX);\n return lower > upper\n ? { blocked: \"silhouetteRatio\", lower: Infinity, upper: -Infinity }\n : { blocked: \"silhouetteRatio\", lower, upper };\n };\n\n const asked = ctx.silhouetteRatio;\n\n /**\n * The hold fitted to the ask with every `headEdges` owner landed at\n * `depths`: the hold, whether it had to be cut to the nearest end of the\n * range this radius renders, and that range when it was — the refusal a\n * CALLER-measured ask earns for that is issued at the fixed point below,\n * on the range the shipped depths have, not here on a pass's guess.\n * Undefined when even the widest hold folds, which no depth changes.\n */\n const fitHold = (\n depths: TurnDepths,\n ):\n | {\n hold: TurnHoldEval;\n silhouetteClamped: boolean;\n span?: [number, number];\n }\n | undefined => {\n // The hold the target names outright, tried first. Where it already\n // renders the target it IS the answer: a static shell renders its own\n // rest span whatever the hold does, so every hold there is equally\n // exact, and the one that leaves the bangs where the target says is the\n // one to ship rather than an arbitrarily narrower one a search would\n // settle on first.\n const natural = evaluateHold(asked, depths);\n // solveTurnDepthSigned's own px slack, on the span the rendered ratio is\n // a fraction of: the fit stops there, and a caller resubmitting the exact\n // interval a refusal reported is not refused again by a float ulp of the\n // division that reported it.\n const eps = TURN_DEPTH_EPS / natural.restSpan;\n if (\n !natural.folds &&\n Math.abs(natural.renderedSilhouetteRatio - asked) <= eps\n ) {\n return { hold: natural, silhouetteClamped: false };\n }\n const samples = holdRange(depths);\n if (samples === undefined) return undefined;\n // Every ratio between the extreme samples is on offer, the rendered ratio\n // being continuous in the hold: the sampled values are all attainable and\n // so is everything they straddle.\n const rendered = samples.map((h) => h.renderedSilhouetteRatio);\n const span: [number, number] = [\n Math.min(...rendered),\n Math.max(...rendered),\n ];\n // Of two holds, the one whose render is nearer the target — and between\n // two that both hit it, the one whose own ratio is nearer what was asked,\n // so a flat stretch of the range does not move the bangs for nothing.\n const nearer = (a: TurnHoldEval, b: TurnHoldEval) => {\n const ea = Math.abs(a.renderedSilhouetteRatio - asked);\n const eb = Math.abs(b.renderedSilhouetteRatio - asked);\n if (ea <= eps && eb <= eps) {\n return Math.abs(a.ratio - asked) <= Math.abs(b.ratio - asked) ? a : b;\n }\n return ea <= eb ? a : b;\n };\n if (asked < span[0] - eps || asked > span[1] + eps) {\n // No hold renders the ask: the nearest end of the rendered range stands\n // in, and says so. A DEFAULTED ask ships it and reports it — see\n // solveTurnModel's report; a CALLER-measured one is refused in the\n // range's own terms once the depths have settled.\n const wanted = asked < span[0] ? span[0] : span[1];\n const hold = samples.reduce((a, b) =>\n Math.abs(b.renderedSilhouetteRatio - wanted) <\n Math.abs(a.renderedSilhouetteRatio - wanted)\n ? b\n : a,\n );\n return { hold, silhouetteClamped: true, span };\n }\n // The sampled interval the target actually sits in, bisected on the\n // HOLD: the rendered ratio is continuous in it, so an interval whose\n // ends straddle the target contains a hold that renders it — whichever\n // way round those ends sit, which is why the direction is read off the\n // interval rather than assumed.\n let best = samples.reduce(nearer);\n let lo: TurnHoldEval | undefined;\n let hi: TurnHoldEval | undefined;\n for (let i = 0; i + 1 < samples.length; i++) {\n const a = samples[i].renderedSilhouetteRatio - asked;\n const b = samples[i + 1].renderedSilhouetteRatio - asked;\n if (a * b <= 0) {\n lo = samples[i];\n hi = samples[i + 1];\n break;\n }\n }\n for (let i = 0; lo !== undefined && hi !== undefined; i++) {\n if (\n i >= TURN_BISECT_STEPS ||\n Math.abs(best.renderedSilhouetteRatio - asked) <= eps\n ) {\n break;\n }\n const mid = evaluateHold((lo.ratio + hi.ratio) / 2, depths);\n if (!mid.folds) best = nearer(best, mid);\n // Keep the half the target is still inside, in the orientation these\n // two ends establish.\n if (\n (mid.renderedSilhouetteRatio - asked) *\n (lo.renderedSilhouetteRatio - asked) >\n 0\n ) {\n lo = mid;\n } else {\n hi = mid;\n }\n }\n return { hold: best, silhouetteClamped: false };\n };\n\n // The fixed point (see this function's doc): the depths every owner is\n // landed at on this pass, none on the first, and the pass before's when\n // the pass after next is Aitken-extrapolated from the two.\n let depths: TurnDepths = { eye: 0, nose: 0, mouth: 0 };\n let seed: TurnDepths | undefined;\n for (let pass = 0; ; pass++) {\n const fitted = fitHold(depths);\n if (fitted === undefined) return silhouetteMiss();\n const { hold, silhouetteClamped, span } = fitted;\n // Folding is never acceptable, fitted or clamped: `natural` is only kept\n // when it does not fold, every `holdRange()` sample is fold-free, and the\n // fit only ever replaces `best` with a non-folding evaluation — so a\n // folding hold here is a broken invariant, not a case to handle.\n if (hold.folds) {\n throw new Error(\n \"auto-rig: evaluateTurnCandidate: the hold it settled on folds\",\n );\n }\n // measure_turn_reference reads the eye pair against each pose's OWN\n // silhouette centre, not the face centre — at rest that centre IS the\n // face centre, but at full turn it has moved by `silhouetteCenterShift`.\n // The raw landmark slide `solveTurnDepthSigned` solves for is measured\n // against the face centre, so its target has to be `silhouetteCenterShift`\n // MINUS the requested magnitude's own px: adding the two back together at\n // measurement time (solveTurnModel's `achieved.eyeShift`) lands back on\n // the requested cue.\n //\n // That target can legitimately be positive (the centre's own drift\n // already meets or exceeds the request, so the eyes need to move toward\n // the NEAR side to land on it) as well as negative, which is exactly why\n // this calls the SIGNED solver directly: `solveTurnDepth`'s\n // magnitude-only wrapper would silently flip a negative request back to\n // positive, quietly hitting a different cue than the one asked for (a\n // request and its negation must produce the identical rig —\n // `TurnTargets.eyeShift`'s own \"the sign is ignored\" contract, taken\n // here, not by folding an already-negative result back to positive\n // later).\n //\n // With strands the eye family's depth is also capped by the strand bound\n // on this hold, best effort: the deepest depth that keeps every far iris\n // no deeper under its run than it is painted, or 0 — the least overlap\n // any depth leaves, each margin being non-increasing in the depth — where\n // no depth does, a face-spanning run (no face-side edge) included. The\n // rig is built either way; `strandFeasible` says which, for the fit to\n // prefer the radii where it holds.\n const strandSides =\n ctx.strands === undefined ? undefined : strandStopsOn(hold);\n let strandFeasible = true;\n let maxDepth: number | undefined;\n if (strandSides !== undefined) {\n const margins = strandSides.map((s) => s.margins);\n const cap = margins.every((m) => m !== undefined)\n ? strandDepthCap(\n margins.flatMap((m) => m!),\n eyePlateCap,\n )\n : undefined;\n strandFeasible = cap !== undefined;\n maxDepth = cap ?? 0;\n }\n const eye = solveTurnDepthSigned(\n hold.silhouetteCenterShift - ctx.eyeShift * ctx.hh,\n ctx.landmarks.eye,\n unit,\n hold.mapAt,\n ctx.plateEdgeX,\n maxDepth,\n );\n const features = solveFeatureDepths(\n ctx,\n (-eye.achieved + hold.silhouetteCenterShift) / ctx.hh,\n unit,\n hold.mapAt,\n hold.silhouetteCenterShift,\n );\n const solved: TurnDepths = {\n eye: eye.depth,\n nose: features.nose.depth,\n mouth: features.mouth.depth,\n };\n // The same hold, re-read with every owner at the depths it just solved:\n // what the emitted grids render. Settled when that read is the one the\n // hold was fitted on, to the solver's own px slack on the centre and on\n // the span.\n const settled = evaluateHold(hold.ratio, solved);\n if (\n Math.abs(settled.silhouetteCenterShift - hold.silhouetteCenterShift) >\n TURN_DEPTH_EPS ||\n Math.abs(settled.renderedSilhouetteRatio - hold.renderedSilhouetteRatio) *\n hold.restSpan >\n TURN_DEPTH_EPS\n ) {\n if (pass + 1 >= TURN_SETTLE_PASSES) return { blocked: \"settle\" };\n if (seed === undefined) {\n seed = depths;\n depths = solved;\n } else {\n // The plate's cap, even for eyes a strand bound caps tighter: an\n // extrapolated depth only lands the `headEdges` owners the next pass\n // fits its hold on, and that pass re-solves the eye depth under the\n // strand cap of its own hold — the solved depth, never this guess, is\n // what a candidate ships.\n depths = {\n eye: aitken(seed.eye, depths.eye, solved.eye, eyePlateCap),\n nose: aitken(\n seed.nose,\n depths.nose,\n solved.nose,\n plateCapOf(ctx.landmarks.nose),\n ),\n mouth: aitken(\n seed.mouth,\n depths.mouth,\n solved.mouth,\n plateCapOf(ctx.landmarks.mouth),\n ),\n };\n seed = undefined;\n }\n continue;\n }\n\n const {\n travel,\n mapAt,\n surface,\n holdBase,\n holdEdgeAt,\n silhouetteCenterShift,\n renderedSilhouetteRatio,\n } = settled;\n if (silhouetteClamped && !ctx.clampSilhouetteRatio) {\n return silhouetteMiss(span);\n }\n // A measured shift below the floor — less than the pair already slides\n // at depth 0 here — disqualifies the radius; a defaulted one takes what\n // the radius offers. A shift the art's room cuts short (the plate's edge,\n // or the strand bound) keeps the capped depth whoever passed it, and\n // `solveTurnModel` reports it clamped.\n if (!eye.reached && eye.limit === \"floor\" && !ctx.clampEyeShift) {\n return {\n blocked: \"eyeShift\",\n // eyeShift is a MAGNITUDE (the sign is ignored — see TurnTargets), so\n // a negative lower bound here would advertise a value the field's own\n // contract already rules out; 0 is the true floor.\n offeredShift: [\n Math.max(0, (-eye.attainable[1] + silhouetteCenterShift) / ctx.hh),\n (-eye.attainable[0] + silhouetteCenterShift) / ctx.hh,\n ],\n };\n }\n\n // Each eye's rest width against its turned width, where the slide put\n // it: its two edges on its own centre row, read as its mesh renders them.\n const eyeShiftPx = -eye.depth * unit;\n const scaleOf = (l: TurnLandmark) =>\n (landmarkLandingX(\n l,\n mapAt,\n -HEAD_TURN_MAX_DEG,\n eyeShiftPx,\n l.x + l.w / 2,\n l.y ?? 0,\n ) -\n landmarkLandingX(\n l,\n mapAt,\n -HEAD_TURN_MAX_DEG,\n eyeShiftPx,\n l.x - l.w / 2,\n l.y ?? 0,\n )) /\n l.w;\n // A −30° turn foreshortens the −x side: that eye is the far one. Dividing\n // the two rest-normalised scales IS the cue — the reference's own far/near\n // ratio is already divided by its rest one.\n const far = ctx.landmarks.eye.reduce((a, b) => (b.x < a.x ? b : a));\n const near = ctx.landmarks.eye.reduce((a, b) => (b.x > a.x ? b : a));\n // Read on this settled pass's hold — the settled re-read differs from it\n // only in the silhouette, which no strand edge rides — at the depth the\n // eyes ship with.\n const strandOverlap =\n strandSides === undefined\n ? undefined\n : strandOverlapOf(strandSides, eye.depth);\n return {\n radius,\n ratio: scaleOf(far) / scaleOf(near),\n unit,\n travel,\n mapAt,\n surface,\n holdBase,\n holdEdgeAt,\n eye,\n nose: features.nose,\n mouth: features.mouth,\n silhouetteClamped,\n silhouetteCenterShift,\n renderedSilhouetteRatio,\n strandFeasible,\n ...(strandOverlap === undefined ? {} : { strandOverlap }),\n };\n }\n}\n\n/** A swept radius: the candidate it yielded and WHERE in the sweep it sat.\n * The feasible radii are not one unbroken run any more — a caller-measured\n * silhouette refuses a radius whose own rendered range cannot be fitted to\n * it, and that can bite in the middle of the sweep — so which candidates are\n * NEIGHBOURS has to be read off the sweep rather than off the array. */\ntype SweptCandidate = TurnCandidate & { sweepIndex: number };\n\n/** The radii that can carry the turn, and what blocked the ones that cannot. */\ninterface TurnSweep {\n candidates: SweptCandidate[];\n /** The narrowest silhouette a render of the radii blocked for the silhouette\n * could have shown, inside the ratios the targets accept (see\n * `TurnCandidateMiss.lower`); Infinity when no radius was blocked for the\n * silhouette at all, or when none of the blocked ones renders anything a\n * caller is allowed to ask for. */\n heldRatioLower: number;\n /** The widest silhouette a render of any radius blocked for the silhouette\n * could have shown — an UPPER bound, same convention; -Infinity in the same\n * two cases. */\n heldRatioUpper: number;\n /** The widest eye shift the blocked radii offered, in shift units; absent\n * when the slide blocked none. */\n offeredShift?: [number, number];\n /** How many radii the fixed point never settled at (`TurnCandidateMiss`'s\n * `settle`): skipped like any refused radius, and the cause named when\n * the sweep is left empty. */\n unsettled: number;\n}\n\n/** Log-spaced sweep of the radius: the shape of the head is not known to any\n * finer resolution than the cues themselves, so the search starts by looking\n * at the whole plausible range. */\nfunction sweepTurnRadii(ctx: TurnSolveContext): TurnSweep {\n const minRadius = ctx.faceHalfWidth * HEAD_CYLINDER_RADIUS_FACTOR;\n const maxRadius = ctx.faceHalfWidth * TURN_SWEEP_MAX_FACTOR;\n const candidates: SweptCandidate[] = [];\n let heldRatioLower = Infinity;\n let heldRatioUpper = -Infinity;\n let offeredShift: [number, number] | undefined;\n let unsettled = 0;\n for (let i = 0; i < TURN_SWEEP_SAMPLES; i++) {\n const radius =\n minRadius * Math.pow(maxRadius / minRadius, i / (TURN_SWEEP_SAMPLES - 1));\n const result = evaluateTurnCandidate(ctx, radius);\n if (!(\"blocked\" in result)) {\n candidates.push({ ...result, sweepIndex: i });\n } else if (result.blocked === \"silhouetteRatio\") {\n heldRatioLower = Math.min(heldRatioLower, result.lower);\n heldRatioUpper = Math.max(heldRatioUpper, result.upper);\n } else if (result.blocked === \"settle\") {\n unsettled++;\n } else {\n offeredShift = offeredShift\n ? [\n Math.min(offeredShift[0], result.offeredShift[0]),\n Math.max(offeredShift[1], result.offeredShift[1]),\n ]\n : result.offeredShift;\n }\n }\n return {\n candidates,\n heldRatioLower,\n heldRatioUpper,\n offeredShift,\n unsettled,\n };\n}\n\n/** One unbroken run of the sweep's feasible radii, and the far/near ratios it\n * covers: the ratio is continuous in the radius, so a run spans every ratio\n * between its own extremes, and a target BETWEEN two runs is one no radius in\n * the sweep produces. */\ninterface TurnRatioCluster<\n C extends { sweepIndex: number; ratio: number } = SweptCandidate,\n> {\n candidates: C[];\n /** Ascending. */\n ratios: [number, number];\n}\n\n/** Whether a run holds a far/near `ratio`: inside its own range, to the\n * solver's own slack read in the ratio's units (see solveTurnModel's miss\n * check). */\nfunction runHolds(run: { ratios: [number, number] }, ratio: number): boolean {\n return (\n ratio >= run.ratios[0] - TURN_DEPTH_EPS &&\n ratio <= run.ratios[1] + TURN_DEPTH_EPS\n );\n}\n\n/** The sweep's feasible radii, split where it skipped one. A caller-measured\n * silhouette can refuse a radius in the middle of the range — the rendered\n * ratios a radius can be fitted to move with it — and a far/near ratio that\n * falls in the gap between two runs is attainable on neither, so the two must\n * not be bracketed across. */\nfunction ratioClusters<C extends { sweepIndex: number; ratio: number }>(\n candidates: C[],\n): TurnRatioCluster<C>[] {\n const clusters: TurnRatioCluster<C>[] = [];\n let run: C[] = [];\n const close = () => {\n if (run.length === 0) return;\n const ratios = run.map((c) => c.ratio);\n clusters.push({\n candidates: run,\n ratios: [Math.min(...ratios), Math.max(...ratios)],\n });\n run = [];\n };\n for (const candidate of candidates) {\n const previous = run[run.length - 1];\n if (\n previous !== undefined &&\n candidate.sweepIndex !== previous.sweepIndex + 1\n ) {\n close();\n }\n run.push(candidate);\n }\n close();\n return clusters;\n}\n\n/**\n * The radius whose far/near ratio comes nearest `target`, inside ONE cluster\n * of sweep-adjacent radii.\n *\n * The ratio is not monotone in the radius — a flatter cylinder foreshortens\n * less but needs a deeper slide to move the eyes as far, and the grid's cells\n * are straight lines the landmarks cross at different radii — so the cluster's\n * samples are scanned from the LARGEST radius down for the first adjacent pair\n * that brackets the target: where two radii both fit, the flatter head is the\n * one that keeps more of the face on the analytic part of the cylinder. The\n * bisection inside that pair is geometric, matching the log-spaced sweep.\n *\n * A pair that is adjacent in the array is adjacent in the SWEEP here, the\n * cluster being one unbroken run of it (see `ratioClusters`) — that is what\n * keeps the bisection out of a gap the sweep already found. Finer gaps inside\n * a pair it cannot see: the bisection stops when it lands on a refused radius,\n * and `solveTurnModel` checks what this actually reached against the target\n * rather than trusting it.\n *\n * `accept` is which candidates the caller will ship (`bisectTurnRadius`):\n * strand feasibility when the cluster is `strandPreferredCandidates`' pool,\n * every candidate otherwise.\n */\nfunction fitTurnRadius(\n ctx: TurnSolveContext,\n candidates: SweptCandidate[],\n target: number,\n accept: (candidate: TurnCandidate) => boolean,\n): TurnCandidate {\n // The cluster's whole span, narrowed to the bracketing pair — there is one\n // whenever the target sits inside its own ratio range.\n let lo: TurnCandidate = candidates[0];\n let hi: TurnCandidate = candidates[candidates.length - 1];\n for (let i = candidates.length - 2; i >= 0; i--) {\n if (\n (candidates[i].ratio - target) * (candidates[i + 1].ratio - target) <=\n 0\n ) {\n lo = candidates[i];\n hi = candidates[i + 1];\n break;\n }\n }\n return bisectTurnRadius(\n lo,\n hi,\n target,\n (radius) => {\n const mid = evaluateTurnCandidate(ctx, radius);\n return \"blocked\" in mid ? undefined : mid;\n },\n accept,\n );\n}\n\n/**\n * The geometric bisection inside a bracketing pair of radii: each mid is the\n * two ends' geometric mean, matching the log-spaced sweep, and replaces the\n * end on its own side of `target`'s ratio.\n *\n * The ratio steers every step, whether or not the caller would ship the mid\n * (`accept`): a mid it rejects still has a far/near ratio, so it still says\n * which half holds the target, and a rejected stretch between two accepted\n * ends cannot end the search early — it converges on the radius whose ratio\n * IS the target, wherever that lies in the bracket. Only a mid that yields no\n * candidate at all (`evaluate` returns undefined — a radius the targets\n * refuse) stops it and keeps the pair it has.\n *\n * It returns the nearer of the final two ends when both are accepted — the\n * whole answer when every candidate is — and otherwise the accepted candidate\n * it evaluated whose ratio came nearest, ends included; the caller checks\n * that against the target rather than trusting it. The two ends passed in\n * have to be accepted.\n *\n * Exported at module level for its own test, not from the package: that test\n * needs a rejected stretch strictly inside an accepted bracket around an\n * accepted target radius, which no layer fixture can be built to guarantee —\n * where a strand bound holds along the radius falls out of the whole solve.\n */\nexport function bisectTurnRadius<C extends { radius: number; ratio: number }>(\n lo: C,\n hi: C,\n target: number,\n evaluate: (radius: number) => C | undefined,\n accept: (candidate: C) => boolean,\n): C {\n if (!accept(lo) || !accept(hi)) {\n throw new Error(\n \"auto-rig: bisectTurnRadius: a bracket end is not accepted\",\n );\n }\n const miss = (c: C) => Math.abs(c.ratio - target);\n let nearest = miss(lo) <= miss(hi) ? lo : hi;\n for (let i = 0; i < TURN_BISECT_STEPS; i++) {\n const mid = evaluate(Math.sqrt(lo.radius * hi.radius));\n // A gap in the feasible set inside the bracket: keep the pair we have.\n if (mid === undefined) break;\n if (accept(mid) && miss(mid) < miss(nearest)) nearest = mid;\n if ((mid.ratio - target) * (lo.ratio - target) <= 0) hi = mid;\n else lo = mid;\n }\n if (accept(lo) && accept(hi)) return miss(lo) <= miss(hi) ? lo : hi;\n return nearest;\n}\n\n/**\n * The candidates the far/near fit should try first: the strand-feasible ones\n * (`TurnCandidate.strandFeasible`) when one of their own unbroken sweep runs\n * (`ratioClusters`) holds `farEyeRatio`, and every candidate otherwise — the\n * whole array itself when all are feasible, as they are without strands. The\n * pool only chooses the bracket's two ends; `solveTurnModel` keeps what it\n * fits only when that meets the ratio, so the preference picks among radii\n * that render the same far/near ratio and never costs a target.\n *\n * Exported at module level for its own test, not from the package: across the\n * probed layer family — a constant-width face profile, one side strand, no\n * measured `headEdges` — the far/near ratio rises monotonically with the\n * radius, so the preferred pool always brackets the target with the same\n * pair, and the preference never changed the fitted radius (24 narrowed\n * pools, 0 changed radii). It is kept as a guard for a non-monotone sweep,\n * which a tapering profile, a measured `headEdges` near its silhouette\n * limit, or strands on both sides might produce, and is covered by its unit\n * tests.\n */\nexport function strandPreferredCandidates<\n C extends { sweepIndex: number; ratio: number; strandFeasible: boolean },\n>(candidates: C[], farEyeRatio: number): C[] {\n const feasible = candidates.filter((c) => c.strandFeasible);\n if (feasible.length === candidates.length) return candidates;\n const holds = ratioClusters(feasible).some((run) =>\n runHolds(run, farEyeRatio),\n );\n return holds ? feasible : candidates;\n}\n\n/**\n * The nose's and the mouth's depth on one candidate's hold, on the same terms\n * as the eyes': a measured target is aimed at exactly, a derived one takes its\n * family's share of the eyes' ACHIEVED shift — not the shift they were asked\n * for: the shares are proportions BETWEEN the three features, so against a\n * clamped eye pair the un-scaled ones would send the nose across the far eye.\n * Both share eyeShift's own head-relative contract (a fraction of `hh`, sign\n * ignored), so each target gets the SAME silhouette-centre correction the\n * eye's own solve uses (see evaluateTurnCandidate) — measured against the\n * pose's own silhouette centre, not the raw landmark slide against the fixed\n * face centre.\n *\n * Solved on every candidate, not once the radius is chosen: a `headEdges`\n * owner in either family lands through its depth, so the silhouette a\n * candidate is fitted on needs both. Whether a bound that cut a solution short\n * is a clamp or a refusal is `solveTurnModel`'s call.\n */\nfunction solveFeatureDepths(\n ctx: TurnSolveContext,\n /** The eyes' achieved shift on this hold, in the cue's own units. */\n eyeShift: number,\n unit: number,\n mapAt: (y: number) => TurnColumnMap,\n silhouetteCenterShift: number,\n): { nose: TurnDepthSolution; mouth: TurnDepthSolution } {\n const solve = (\n measured: number | undefined,\n marks: TurnLandmark[],\n share: number,\n ) =>\n solveTurnDepthSigned(\n silhouetteCenterShift - (measured ?? Math.abs(share * eyeShift)) * ctx.hh,\n marks,\n unit,\n mapAt,\n ctx.plateEdgeX,\n );\n return {\n nose: solve(ctx.noseShift, ctx.landmarks.nose, NOSE_SHIFT_SHARE),\n mouth: solve(ctx.mouthShift, ctx.landmarks.mouth, MOUTH_SHIFT_SHARE),\n };\n}\n\n/**\n * Solve the head turn from its measured cues: one radius and three depths.\n *\n * The radius and the eye depth are solved TOGETHER, because the two cues do not\n * separate: how far the eye pair slides depends on the radius (through the unit\n * and the map), and how much the far eye foreshortens depends on where the\n * slide put it. For a candidate radius the eye depth is whatever hits\n * `eyeShift` on that radius' own map — or, for an eyeShift the art's room cuts\n * short (and a DEFAULTED one below the floor), as much of it as that radius\n * allows — and the far/near width ratio that falls out of it is the residual\n * `fitTurnRadius` drives to `farEyeRatio`.\n *\n * Everything is measured on the SAME surface the rig is baked from — the\n * piecewise-linear map `turnColumnMap` builds from the lattice's own columns\n * (`TurnSurface.mapAt`), at the −30° stop, read through the very grids and\n * meshes the parts render with (`carriers`) — so a solved target is a promise\n * about the shipped keyforms, not about an idealised cylinder the engine never\n * evaluates. The solved surface is returned for the bakes to sample.\n *\n * A target that does not fit is clamped when it was a default and refused when\n * the caller measured it — except an eye shift the art's room cuts short,\n * which is clamped whoever passed it; see TurnTargets. Clamping the EYE SHIFT\n * keeps the radius search intact on purpose: the shift is bounded by the art\n * (the far eye has to stay on the plate, and with `strandEdges` out from under\n * the bangs' side strands), so trading the foreshortening cue away to buy the\n * last few pixels of slide would flatten the cylinder — on the reference\n * character, to the sweep's ceiling — and a head that does not foreshorten does\n * not read as turning at all.\n *\n * With `strandEdges`, the far/near ratio is fitted among the strand-feasible\n * radii first — the ones where some eye depth keeps every far iris no deeper\n * under its run than it is painted — when one of their unbroken runs holds the\n * ratio (`strandPreferredCandidates`). That fit is kept only when it meets the\n * ratio; otherwise the fit on every radius stands, where an infeasible radius\n * ships with the eyes' depth at 0 and `strandOverlap` saying how much of each\n * far iris the bangs still cover. So the preference never costs a target and\n * never refuses a rig: it only picks among radii that render the same ratio.\n */\nexport function solveTurnModel(\n targets: ResolvedTurnTargets,\n landmarks: TurnLandmarkSet,\n lattice: IkiWarpGrid,\n faceCenterX: number,\n faceHalfWidth: number,\n /** The nod axis. */\n faceCenterY: number,\n /** The nod cylinder's radius — the head's vertical reach with the no-fold\n * margin, sized once by `turnSetup` for the solve and the generator alike\n * (the virtual lattice has no height to read it from). */\n nodRadius: number,\n /** Every face-family part's carrier by role — the group grid it binds to\n * and the part as it renders on it — the plate's under `face`: what the\n * landmarks, the silhouette candidates, the bangs' join and the plate\n * guards read the RENDERED parts through. */\n carriers: ReadonlyMap<string, TurnCarrier>,\n /** The face's own row profile (`faceRowProfile`), when its layer measured\n * one: every candidate's surface bends each row on its own radius,\n * normalised at the eye row the cues are fitted on, and the plate's\n * painted edge is read at each row's own half-width. Absent, the plate is\n * painted out to its crop and turns on one radius. */\n profile?: FaceRowProfile,\n /** hair_front's own transform x/y and crop width/height, when the layer set\n * has one — see `TurnSolveContext.hairFrontSilhouette`. Absent skips the\n * turn-lead correction and falls the silhouette-centre correction back to\n * the ideal hold destinations, which is also correct for a layer set with\n * no hair_front: there is then no bangs edge for either to read. */\n hairFront?: { x: number; centerY: number; cropW: number; cropH: number },\n /** Every role with an opaque pixel in the eye-row band, per side, each with\n * its own rest x there — see `TurnSolveContext.headEdges`. Absent (the\n * usual case for a caller with no measured head, or one built by hand)\n * falls the silhouette-centre correction back to hair_front on both sides. */\n headEdges?: {\n left: { role: string; x: number }[];\n right: { role: string; x: number }[];\n },\n /** Each side's iris and the bangs' run it would slide under, as measured\n * pixel edges on the iris row (`IrisStrand`) — what the strand bound caps\n * the eye family's depth by. See `TurnSolveContext.strands`. */\n strandEdges?: { left?: IrisStrand; right?: IrisStrand },\n): TurnModelSolution {\n // A measured head narrower than the face it is drawn around is not a head the\n // rest of this can make sense of: the hold's whole zone lives outside the\n // plate's edge, so the plate would be sticking out of the silhouette at rest.\n if (\n targets.headHalfWidth !== undefined &&\n targets.headHalfWidth <= faceHalfWidth\n ) {\n throw new TurnTargetError(\n `auto-rig: turnTargets.headHalfWidth (${targets.headHalfWidth}) must be wider than the face plate's own half-width (${faceHalfWidth})`,\n );\n }\n\n const eyeRowY = eyeRowOf(landmarks, faceCenterY);\n\n // Each measured side on its own iris's carrier, the irises paired to sides\n // by where they render rather than by role name. The generator validated\n // the option against the layers first, so a side with no iris to land or a\n // run with no bangs to land it on is a broken invariant here.\n let strands: TurnSolveContext[\"strands\"];\n const strandSides = ([\"left\", \"right\"] as const).filter(\n (key) => strandEdges?.[key] !== undefined,\n );\n if (strandSides.length > 0) {\n if (hairFront === undefined) {\n throw new Error(\n \"auto-rig: solveTurnModel: strandEdges was given, but there is no hair_front to land its runs on\",\n );\n }\n const irises = [\"iris_L\", \"iris_R\"]\n .map((role) => carriers.get(role))\n .filter((c): c is TurnCarrier => c !== undefined)\n .sort((a, b) => a.part.x - b.part.x);\n strands = strandSides.map((key) => {\n const carrier =\n irises.length === 2 ? irises[key === \"left\" ? 0 : 1] : undefined;\n if (carrier === undefined) {\n throw new Error(\n `auto-rig: solveTurnModel: strandEdges.${key} has no iris carrier to land`,\n );\n }\n return {\n ...strandEdges![key]!,\n side: key === \"left\" ? -1 : 1,\n carrier,\n };\n });\n }\n\n const ctx: TurnSolveContext = {\n landmarks,\n lattice,\n faceCenterX,\n faceCenterY,\n nodRadius,\n eyeRowY,\n faceHalfWidth,\n // The slide's hard stop is the CROP's edge, profile or not: the far eye\n // may sit on the plate's transparent margin, as it does on the hero.\n plateEdgeX: faceCenterX - faceHalfWidth,\n carriers,\n profile,\n ...plateGuardsOf(carriers, faceHalfWidth, profile, hairFront),\n // What this plate asks the turn for; each candidate caps it to its own\n // shell — see TurnSolveContext.travel.\n travel: headTurnTravel(faceHalfWidth),\n headHalfWidth: targets.headHalfWidth,\n // The pixels the shift fractions are fractions of. Without a measured head\n // the face plate stands in for it — see TurnTargets.headHalfWidth.\n hh: targets.headHalfWidth ?? faceHalfWidth,\n silhouetteRatio: targets.silhouetteRatio,\n // The sign is ignored (see TurnTargets.eyeShift) — taken here, once, so\n // every downstream use already has the magnitude.\n eyeShift: Math.abs(targets.eyeShift),\n clampEyeShift: targets.defaulted.has(\"eyeShift\"),\n noseShift: targets.defaulted.has(\"noseShift\")\n ? undefined\n : Math.abs(targets.noseShift),\n mouthShift: targets.defaulted.has(\"mouthShift\")\n ? undefined\n : Math.abs(targets.mouthShift),\n clampSilhouetteRatio: targets.defaulted.has(\"silhouetteRatio\"),\n hairFrontSilhouette: hairFront,\n // A companion to a MEASURED headHalfWidth only — see its own doc.\n headEdges: targets.headHalfWidth === undefined ? undefined : headEdges,\n strands,\n };\n const clampInto = (value: number, [lo, hi]: [number, number]) =>\n Math.min(Math.max(value, lo), hi);\n\n const pass = sweepTurnRadii(ctx);\n if (pass.candidates.length === 0) {\n // A radius the fixed point never settled at offered nothing in any\n // target's terms (see evaluateTurnCandidate), so a sweep one of those\n // leaves empty is refused as such, naming the cause — the ranges below\n // are read off the radii the TARGETS refused and would say nothing of\n // the ones nothing settled at.\n if (pass.unsettled > 0) {\n const refused = TURN_SWEEP_SAMPLES - pass.unsettled;\n throw new TurnTargetError(\n `auto-rig: headEdges: no sampled radius carries the turn — the silhouette and the depths its headEdges owners ride on did not settle within ${TURN_SETTLE_PASSES} passes at ${pass.unsettled} of the ${TURN_SWEEP_SAMPLES} radii${refused > 0 ? `, and the targets refused the other ${refused}` : \"\"}`,\n );\n }\n // Either gate can empty the sweep, and only a MEASURED target can. The\n // shift, only by asking for LESS than the pair's own drift at depth 0\n // (the floor): a defaulted one is clamped per radius rather than gated,\n // and one the art's room cuts short at the cap is clamped whoever passed\n // it. The silhouette, because a defaulted ratio of 1 leaves the hold edge\n // at its own rest distance — without a measured head that distance is the\n // plate's own scanned reach plus a pixel (`plateReach`), which clears every\n // stop by construction, and with one it is the head the slide is capped to\n // stay inside (`shellTravelCap`). So both values below are the caller's\n // own, never a clamped stand-in.\n return pass.offeredShift\n ? {\n unreachable: true,\n field: \"eyeShift\",\n value: targets.eyeShift,\n attainable: pass.offeredShift,\n }\n : {\n unreachable: true,\n field: \"silhouetteRatio\",\n value: targets.silhouetteRatio,\n // Two different bounds, not one number twice, and computed the\n // same way regardless of which ratio was actually rejected: both\n // are ratios a RENDER could have shown (the measure the caller's\n // own number came from), the narrowest and the widest any radius\n // could be fitted to, with every ratio between them one it can be\n // fitted to exactly; see evaluateTurnCandidate, which clips each\n // radius' own range to the domain `resolveTurnTargets` accepts\n // before it gets here. Absent when that left every radius with\n // nothing: no ratio a caller is allowed to ask for renders at all.\n attainable:\n pass.heldRatioLower === Infinity ||\n pass.heldRatioUpper === -Infinity\n ? undefined\n : [pass.heldRatioLower, pass.heldRatioUpper],\n };\n }\n\n /** The whole post-sweep fit on one pool of candidates — its runs, the\n * radius fitted inside the one that holds the far/near ratio, and every\n * verdict on what that radius reached — with `accept` the candidates the\n * bisection may ship (`fitTurnRadius`). */\n const fitFrom = (\n pool: SweptCandidate[],\n accept: (candidate: TurnCandidate) => boolean,\n ): TurnModelSolution => {\n const clamped: (keyof TurnTargets)[] = [];\n // The radii that survived, in unbroken runs of the sweep: a far/near\n // ratio between two runs is one no radius produces, so the fit is\n // confined to the run that covers the target and a target no run covers\n // is out of reach rather than bisected across the gap. Defaults leave\n // every radius standing and so leave exactly one run — the gaps are what\n // a CALLER-measured cue opens (see evaluateTurnCandidate): a silhouette\n // no hold at that radius renders, or an eye shift below the pair's own\n // drift there, neither of which is monotone in the radius. (A caller's\n // shift the art's room cuts short opens none: it is clamped, so it\n // leaves the far/near ratio fitted across the same radii a default\n // does.)\n const clusters = ratioClusters(pool);\n const distanceTo = (c: TurnRatioCluster) =>\n Math.max(\n c.ratios[0] - targets.farEyeRatio,\n targets.farEyeRatio - c.ratios[1],\n 0,\n );\n let farEyeRatio = targets.farEyeRatio;\n let cluster = clusters.find((c) => runHolds(c, farEyeRatio));\n if (cluster === undefined) {\n // What a caller could have asked for instead is one run's own range,\n // not the span across the gaps: every ratio inside it is one this\n // sweep reaches. The nearest run is the one to name.\n cluster = clusters.reduce((a, b) =>\n distanceTo(b) < distanceTo(a) ? b : a,\n );\n if (!targets.defaulted.has(\"farEyeRatio\")) {\n return {\n unreachable: true,\n field: \"farEyeRatio\",\n value: targets.farEyeRatio,\n attainable: cluster.ratios,\n };\n }\n farEyeRatio = clampInto(farEyeRatio, cluster.ratios);\n clamped.push(\"farEyeRatio\");\n }\n\n const best = fitTurnRadius(ctx, cluster.candidates, farEyeRatio, accept);\n // What the fit actually reached, against what it was asked for: inside a\n // run the ratio is continuous, but the bisection can still stop on a\n // radius the silhouette gate refuses — a gap finer than the sweep\n // resolves — and land on a bracket end instead, or converge on a radius\n // `accept` turns down and fall back to the nearest one it accepts. A\n // miss is refused or reported, never passed off as met. The tolerance is\n // solveTurnDepthSigned's own slack read in this cue's own units, which\n // are already dimensionless: the far/near ratio is a width over a width,\n // so there is no px scale to divide it by.\n if (Math.abs(best.ratio - farEyeRatio) > TURN_DEPTH_EPS) {\n if (!targets.defaulted.has(\"farEyeRatio\")) {\n return {\n unreachable: true,\n field: \"farEyeRatio\",\n value: targets.farEyeRatio,\n attainable: cluster.ratios,\n };\n }\n if (!clamped.includes(\"farEyeRatio\")) clamped.push(\"farEyeRatio\");\n }\n // Cut short at either end: a default at the floor or the cap, or a\n // caller's at the cap — the plate's edge or the strand bound, depth 0\n // where the bound cannot hold (a caller's below the floor never gets\n // here; its radii were refused).\n if (!best.eye.reached) clamped.push(\"eyeShift\");\n // Only a DEFAULTED silhouette can be here cut down: the fit lands a\n // caller-measured one or refuses the radius outright\n // (evaluateTurnCandidate), so a ratio that reached this point was either\n // rendered as asked — nothing to report — or is this generator's own\n // default, cut to the nearest ratio a render of this layer set can show.\n if (best.silhouetteClamped) clamped.push(\"silhouetteRatio\");\n // Undo the same silhouette-centre correction the depth was solved with\n // (see evaluateTurnCandidate), so this reports the cue\n // measure_turn_reference would read off a render — eye position against\n // that pose's OWN silhouette centre — not the raw landmark slide against\n // the fixed face centre.\n const eyeShift = (-best.eye.achieved + best.silhouetteCenterShift) / ctx.hh;\n\n // The nose's and the mouth's depths were solved on this candidate along\n // with the eyes' (solveFeatureDepths); what is left is their verdict — a\n // measured target the bounds cut short, at either end, is refused, a\n // derived one is clamped and says so.\n for (const [field, solution] of [\n [\"noseShift\", best.nose],\n [\"mouthShift\", best.mouth],\n ] as const) {\n if (solution.reached) continue;\n if (!targets.defaulted.has(field)) {\n return {\n unreachable: true,\n field,\n value: targets[field],\n // noseShift/mouthShift are MAGNITUDES too (see\n // solveFeatureDepths), so a negative lower bound here would\n // advertise a value the field's own contract already rules out; 0\n // is the true floor.\n attainable: [\n Math.max(\n 0,\n (-solution.attainable[1] + best.silhouetteCenterShift) / ctx.hh,\n ),\n (-solution.attainable[0] + best.silhouetteCenterShift) / ctx.hh,\n ],\n };\n }\n clamped.push(field);\n }\n\n return {\n unreachable: false,\n radius: best.radius,\n holdBase: best.holdBase,\n holdEdgeAt: best.holdEdgeAt,\n travel: best.travel,\n surface: best.surface,\n depths: {\n eye: best.eye.depth,\n nose: best.nose.depth,\n mouth: best.mouth.depth,\n },\n achieved: {\n eyeShift,\n farEyeRatio: best.ratio,\n silhouetteRatio: best.renderedSilhouetteRatio,\n },\n clamped,\n ...(best.strandOverlap === undefined\n ? {}\n : { strandOverlap: best.strandOverlap }),\n };\n };\n\n // The strand-feasible radii first, when one of their runs holds the\n // far/near ratio. That pool only chooses the bracket; what it fits is kept\n // only when it meets the ratio — neither refused nor clamping farEyeRatio,\n // i.e. the radius that renders the target is itself feasible — and\n // otherwise the fit on every candidate stands, which is the fit without\n // the preference. So the preference never costs a target and never\n // refuses: it only chooses among radii that render the same far/near\n // ratio. Without strands every candidate is feasible and this is that fit.\n const preferred = strandPreferredCandidates(\n pass.candidates,\n targets.farEyeRatio,\n );\n if (preferred !== pass.candidates) {\n const fitted = fitFrom(preferred, (c) => c.strandFeasible);\n if (!fitted.unreachable && !fitted.clamped.includes(\"farEyeRatio\")) {\n return fitted;\n }\n }\n return fitFrom(pass.candidates, () => true);\n}\n\n// ── bindingsForRole ───────────────────────────────────────────────────────────\n\n// Role prefixes that belong to the eye stack (blink + optional gaze bindings).\n// Hoisted to module scope so it is not reallocated on every bindingsForRole call.\nconst EYE_STACK_PREFIXES = [\"eye_\", \"iris_\", \"pupil_\", \"highlight_\"] as const;\n\n/** Signed depth of the bangs from the head cylinder's axis, as a fraction of\n * the cylinder radius; positive is toward the viewer. Tuned by eye against\n * the rendered turn, not derived. The back hair takes no share of it: it\n * holds the head's outline while the face slides inside it.\n *\n * The bangs lead that sliding face by a little — this is the FRINGE TIPS'\n * lead specifically, not the whole sheet's: the crown is root-pinned by the\n * warp that applies it, not a binding (see bindingsForRole's doc for why).\n * 0.16 was right while the shift was rigid and the whole head travelled\n * with it, the lead the only depth cue; with the head no longer travelling,\n * the lead is the bangs' only motion against a held shell, and that much of\n * it reads as them running ahead, so it came down. At 0.06 the layering all\n * but vanishes. 0.1 is settled with the lead root-pinned: the fringe tips get\n * it, the crown gets none. */\nconst HAIR_FRONT_DEPTH = 0.1;\n\n/** How far the mouth tilts at full turn, degrees, its near end down: the\n * mouth family's `TurnAnchor.tiltDeg`. The reference 3/4 portrait's mouth\n * line tilts 5.2° near end down, where the rig's, untilted, came out at\n * −1.1°. A style prior, not fitted: `measure_turn_reference` reads no\n * mouth. */\nconst MOUTH_TURN_TILT_DEG = 5;\n\n/** How far the nose tilts at full turn, degrees, its tip toward the far side:\n * the nose's `TurnAnchor.tiltDeg`. A ladder of tilts rendered on the hero\n * read: 3° only cancels the lean-back the chin swing gives the nose, 10°\n * reads as a nose leaning over, and the reverse sign is plainly wrong, while\n * 6° echoes the \"<\" the reference 3/4 portrait's nose draws. A style prior,\n * not fitted: `measure_turn_reference` reads no nose. */\nconst NOSE_TURN_TILT_DEG = 6;\n\n/** Rigid vertical travel of the head at full nod (px at AngleY = ±30). */\nconst NOD_TRAVEL = 30;\n/** Geometric pitch per degree of ParamAngleY: a full ±30 nod bends the face\n * cylinder by ±15°. The nod cylinder — sized to the turn family's union,\n * bangs included (`turnSetup`) — reaches up to the hair crown, which sits\n * near 45° on it; a full 30° pitch there drops the\n * bangs' crown ~140px while the rigid back hair stays put, and the back\n * hair's crown pops out above it as a second silhouette. At half pitch the\n * drop stays under the back hair and the nod still foreshortens visibly. */\nconst NOD_BEND = 0.5;\n/** Vertical share of the nod for hair_back, as a fraction of\n * `headNodParallaxUnit`. The back hair gets no vertical bend of its own, so\n * it only needs to follow the bent crown down to stay tucked beneath it.\n * Tuned against renders as −0.07 of a turn-sized unit (sin 30°), before the\n * nod had its own; the same pixels in the nod unit (sin 15°). */\nconst HAIR_BACK_NOD_DEPTH = -0.135;\n\n/**\n * The nod's counterpart of `headTurnParallaxUnit`: the bulk vertical slide the\n * 2D bakes pin out of every group grid at full nod (`TurnSurface.nodBendAt`).\n * The nod bends at NOD_BEND of the angle, so this is\n * nodRadius · sin(30° · NOD_BEND), not nodRadius · sin(30°), with nodRadius\n * the head's half-height about the nod axis (`gridHalfHeight`, the turn\n * family's margined union — see `turnSetup`) with the no-fold margin.\n */\nexport function headNodParallaxUnit(gridHalfHeight: number): number {\n return (\n gridHalfHeight *\n HEAD_CYLINDER_RADIUS_FACTOR *\n Math.sin(HEAD_TURN_MAX_DEG * NOD_BEND * (Math.PI / 180))\n );\n}\n\n/** Depth of the features in front of the head cylinder's NOD axis, as a\n * fraction of its radius — the share of the bulk vertical slide the 2D bake\n * pins out of the face warp (`headNodParallaxUnit`) that each part gets back.\n *\n * The face contour is a cylinder rotating about its own axis: its silhouette\n * stays put and only foreshortens, which is what the pinned bake gives it.\n * The features painted on that surface do not stay put — they slide across it\n * toward the far side, and a nose that stands off the surface slides further.\n * Without their share they sat still while the outline squeezed around them,\n * and the head read as a flat sheet bending rather than pitching.\n *\n * The nose is the anchor of the whole read, so the parallax exists only when\n * it is a layer of its own (the composer cuts it out of the face). With the\n * nose painted on the face plate, any lead on the eyes and mouth inverts the\n * 3/4 view — the nose lands nearer the NEAR eye and the mouth hangs off its\n * far side — and a blind review ranked that below no parallax at all.\n *\n * Keyed by role family (`eye_L` → `eye`). The whole eye stack shares one\n * depth because iris/pupil/highlight clip to the white, and the lashes fold\n * onto it. The magnitudes are small on purpose, and judged by eye across a\n * ladder of rigs: the nose at twice the surface's share and the mouth on the\n * surface with the eyes is where the nod stopped looking like a plate without\n * looking like a slide. The TURN's depths are not tuned here at all — they are\n * solved per layer set from a measured reference (`solveTurnModel`); no nodded\n * reference has been measured, so this axis is still by eye. */\nconst FEATURE_NOD_DEPTH: Readonly<Record<string, number>> = {\n eye: 0.04,\n iris: 0.04,\n pupil: 0.04,\n highlight: 0.04,\n lash: 0.04,\n brow: 0.04,\n blush: 0.04,\n mouth: 0.04,\n mouth_open: 0.04,\n nose: 0.08,\n};\n\n/** The bangs' share of the nod: the brows'. They hang from the hairline, which\n * sits on the same surface as the brows, so they slide with them — held still\n * while the brows slid, the fringe closed onto the brows looking up and left\n * a bare forehead looking down. (Before the brows slid, a lead here lifted\n * the bangs clear off them, which is why they had none.) Riding the rigid\n * head rather than a grid, the bangs take it as a per-vertex warp that bakes\n * the slide AND the surface's bend where the slide puts each vertex\n * (`bakeNodWarp`), the composition a grid child gets from its binding and\n * the grid. The turn lead stays a root-pinned warp: on the turn the crown\n * sits on the axis and must not slide. */\nconst HAIR_FRONT_NOD_DEPTH = FEATURE_NOD_DEPTH.brow;\n\n/** `eye_L` → `eye`; an unsided role is its own family. */\nfunction roleFamily(role: string): string {\n return role.replace(/_[LR]$/, \"\");\n}\n\n/** Which solved turn depth a feature family slides on: the nose has its own,\n * the two mouth drawings share one, and everything else painted on the face —\n * the eye stack, the lashes, the brows, the blush — rides with the eyes. */\nfunction turnFamily(role: string): keyof TurnDepths {\n const family = roleFamily(role);\n if (family === \"nose\") return \"nose\";\n if (family === \"mouth\" || family === \"mouth_open\") return \"mouth\";\n return \"eye\";\n}\n\n/**\n * The nod's depth-parallax translate binding of a facial feature, or [] for\n * any other role, whenever the unit is absent (a unit-less call is a rig\n * without a head to nod on), and whenever the rig has no nose layer to lead\n * (see FEATURE_NOD_DEPTH). Symmetric about zero, so the rest pose — the one\n * every proportion is judged on — is untouched.\n *\n * The TURN has no binding here: its depth is SOLVED per layer set from the\n * measured cues and baked into the feature's own group grid as keyform\n * geometry (`bakeTurnGroupWarp2D`'s `shiftAt`), so the part binds at its rest\n * position and the grid carries it — the landing a translate-then-bind gave,\n * now read by the solve through that very grid (`TurnLandmark.carrier`). The\n * nod's depth is the tuned table and stays a translate: it binds a nodded\n * vertex at `y + t` on its group grid exactly as it did on the shared one.\n */\nfunction featureParallaxBindings(\n role: string,\n options: {\n parallaxUnitY?: number;\n hasNose?: boolean;\n },\n): IkiBinding[] {\n if (!options.hasNose) return [];\n // The nod's table is also the role gate: a family with no depth in it is not\n // a feature painted on the face, and does not slide.\n const nodDepth = FEATURE_NOD_DEPTH[roleFamily(role)];\n if (nodDepth === undefined) return [];\n const shiftY = nodDepth * (options.parallaxUnitY ?? 0);\n if (shiftY === 0) return [];\n return [\n {\n parameter: StandardParameter.AngleY,\n channel: \"translateY\",\n from: -shiftY,\n to: shiftY,\n },\n ];\n}\n\n/**\n * Derive the IkiBinding[] for a part from its role spec and crop dimensions.\n *\n * - face → no bindings: the contour is the cylinder itself, pinned by the bake\n * - every feature on that contour — eye stack, lashes, brows, both mouths,\n * nose, blush — carries the AngleY translateY nod parallax from\n * FEATURE_NOD_DEPTH (needs `parallaxUnitY` and `hasNose`) on top of\n * whatever its role adds below; its TURN parallax is no binding but its\n * group grid's own keyform geometry (see featureParallaxBindings)\n * - hair_front: no bindings. Every motion of its own is a per-vertex warp\n * attached in generateIkiFromLayerSet: the sway and the AngleX turn lead\n * are root-pinned (a rigid translate/rotate would carry the whole sheet,\n * crown included, with the fringe tips, instead of leading from them),\n * the silhouette hold sits on the rendered plate, and the nod follow at\n * the brows' depth (HAIR_FRONT_NOD_DEPTH, same `hasNose` gate as the\n * brows) is baked with the head's own bend (`bakeNodWarp`)\n * - hair_back: nothing on the turn — it rides a head that no longer travels,\n * so it holds the head's outline while the face slides inside it — and an\n * AngleY translateY that tucks its crown under the bent front hair (needs\n * `parallaxUnitY`)\n * - brow_L/R: BrowLeftY/RightY translateY (raise/lower) + BrowLeftAngle/RightAngle rotate\n * (each brow rotates its own, CCW-positive)\n * - eye-stack:\n * iris_/pupil_/highlight_ → gaze translateX + translateY (no blink binding)\n * eye_ (white) → none here; its blink is a fold warp attached in assembly,\n * and iris/pupil/highlight clip to it so the closing white CUTS them away\n * - mouth: MouthForm scaleX (-0.2 to 0.4), plus MouthOpen as scaleY (0 to 3)\n * without a mouth_open layer, or as an opacity fade-out with one\n * - mouth_open: MouthOpen opacity fade-in + the same MouthForm scaleX\n *\n * Returns [] when the role has no bindings (callers skip the bindings key when\n * the array is empty).\n */\nexport function bindingsForRole(\n spec: RoleSpec,\n role: string,\n cropW: number,\n cropH: number,\n options: {\n hasMouthOpen?: boolean;\n parallaxUnitY?: number;\n /** Whether the layer set has a `nose` role — the feature parallax's gate. */\n hasNose?: boolean;\n } = {},\n): IkiBinding[] {\n return [\n ...roleOwnBindings(spec, role, cropW, cropH, options),\n ...featureParallaxBindings(role, options),\n ];\n}\n\n/** The bindings a role has for its own expression or motion — everything in\n * `bindingsForRole`'s list except the shared feature parallax. */\nfunction roleOwnBindings(\n spec: RoleSpec,\n role: string,\n cropW: number,\n cropH: number,\n options: {\n hasMouthOpen?: boolean;\n parallaxUnitY?: number;\n },\n): IkiBinding[] {\n const isEyeStack = EYE_STACK_PREFIXES.some((p) => role.startsWith(p));\n\n if (isEyeStack && spec.eyeSide !== undefined) {\n // Only iris/pupil/highlight move with gaze; the white (eye_) gets nothing.\n const isGazeRole =\n role.startsWith(\"iris_\") ||\n role.startsWith(\"pupil_\") ||\n role.startsWith(\"highlight_\");\n if (!isGazeRole) return [];\n\n // Gaze range: proportional to crop size, capped to avoid over-travel.\n const gx = Math.min(cropW * 0.18, 22);\n const gy = Math.min(cropH * 0.18, 16);\n return [\n {\n parameter: StandardParameter.EyeballX,\n channel: \"translateX\",\n from: -gx,\n to: gx,\n },\n {\n parameter: StandardParameter.EyeballY,\n channel: \"translateY\",\n from: -gy,\n to: gy,\n },\n ];\n }\n\n // Mouth form applies to whichever mouth drawing is showing.\n const mouthForm = {\n // Mouth form: scaleX from -0.2 (pursed, param=-1) to 0.4 (wide, param=1).\n parameter: StandardParameter.MouthForm,\n channel: \"scaleX\" as const,\n from: -0.2,\n to: 0.4,\n };\n\n if (role === \"mouth\") {\n // With an open-mouth drawing present, the closed one fades out rather than\n // being stretched. Stretching a closed mouth is what produced a smear: the\n // art is a ~15px-tall line and scaleY 3 blows it up to a blurred band.\n if (options.hasMouthOpen) {\n return [\n {\n parameter: StandardParameter.MouthOpen,\n channel: \"opacity\",\n from: 1,\n to: 0,\n },\n mouthForm,\n ];\n }\n return [\n // Mouth open: scaleY from 0 (closed, param=0) to 3 (wide open, param=1).\n // Only a fallback — it distorts, but it is better than a mouth that\n // cannot open at all when the layer set has no open drawing.\n {\n parameter: StandardParameter.MouthOpen,\n channel: \"scaleY\",\n from: 0,\n to: 3,\n },\n mouthForm,\n ];\n }\n\n if (role === \"mouth_open\") {\n return [\n {\n parameter: StandardParameter.MouthOpen,\n channel: \"opacity\",\n from: 0,\n to: 1,\n },\n mouthForm,\n ];\n }\n\n if (role === \"brow_L\" || role === \"brow_R\") {\n // Raise/lower capped to avoid over-travel; tilt is a fixed ±12° range.\n const ty = Math.min(cropH * 0.8, 18);\n const deg = 12;\n if (role === \"brow_L\") {\n return [\n {\n parameter: StandardParameter.BrowLeftY,\n channel: \"translateY\",\n from: -ty,\n to: ty,\n },\n {\n parameter: StandardParameter.BrowLeftAngle,\n channel: \"rotate\",\n from: -deg,\n to: deg,\n },\n ];\n } else {\n return [\n {\n parameter: StandardParameter.BrowRightY,\n channel: \"translateY\",\n from: -ty,\n to: ty,\n },\n {\n parameter: StandardParameter.BrowRightAngle,\n channel: \"rotate\",\n from: -deg,\n to: deg,\n },\n ];\n }\n }\n\n if (role === \"hair_back\") {\n // Nothing on the TURN: it holds the head's outline still while the face\n // slides inside it. On the nod it follows the bent crown down to stay\n // tucked beneath it — see HAIR_BACK_NOD_DEPTH.\n const shiftY = HAIR_BACK_NOD_DEPTH * (options.parallaxUnitY ?? 0);\n if (shiftY === 0) return [];\n return [\n {\n parameter: StandardParameter.AngleY,\n channel: \"translateY\",\n from: -shiftY,\n to: shiftY,\n },\n ];\n }\n\n // face, blush_*, nose → nothing of their own; hair_front's every motion is\n // a warp (see the doc above)\n return [];\n}\n\n// ── bakeEyelidFoldWarp ─────────────────────────────────────────────────────\n\n/** Crease sits this fraction of the eye height BELOW the white's center. */\nconst EYELID_FOLD_CREASE = 0.15;\n/** Without separate lashes, retain a thin band of the eye's own line art. */\nconst EYELID_FOLD_K = 0.04;\n/** The lash keeps a thicker band than the white when closed, so it reads as a\n * visible dark closed-eye line and covers the cut eyeball/seam. */\nconst LASH_FOLD_K = 0.2;\n\n/**\n * Live2D-style eyelid FOLD blink for the eye-white. Two EyeOpen keyforms collapse\n * the white toward a crease line `creaseOffsetY` below its center while scaling\n * its height by `k`: as EyeOpen → 0 the white folds shut. Because iris/pupil/\n * highlight CLIP to the white, the closing clip region CUTS the (static, round)\n * iris away instead of squashing it — unlike the old scaleY-collapse blink.\n * EyeOpen=1 → rest (zero offsets = the authored open art); =0 → folded.\n *\n * Offsets are authored in the mesh's own pixel frame (+y up, centered), matching\n * `createPixelGridMesh`, so the SAME mesh must be passed that the part renders.\n */\nexport function bakeEyelidFoldWarp(\n mesh: IkiMesh,\n parameter: string,\n creaseOffsetY: number,\n k: number,\n): IkiWarp {\n const closed: number[] = [];\n const zeros: number[] = [];\n for (let i = 0; i < mesh.vertices.length; i += 2) {\n const vy = mesh.vertices[i + 1];\n // closed y = creaseOffsetY + vy*k → dy added to the rest vertex vy.\n closed.push(0, creaseOffsetY - (1 - k) * vy);\n zeros.push(0, 0);\n }\n return {\n parameter,\n keyforms: [\n { value: 0, offsets: closed },\n { value: 1, offsets: zeros },\n ],\n };\n}\n\n// ── bakeNodWarp ──────────────────────────────────────────────────────────────\n\n/**\n * The nod of a part that rides the rigid head rather than a grid, as a\n * per-vertex AngleY warp keyed on HEAD_TURN_STOPS: what a grid child gets from\n * its nod translate binding and the grid together — translated by `t`, the\n * part's own share of the nod at that stop (`nodTravel · deg / 30`), THEN bent\n * where the translate put it, `surface.nodBendAt(y + t)` — baked at each\n * vertex's own rest y, `partY + vy`. Off the grid the bend is the analytic\n * surface itself, not a grid's chord of it. dx is zero; the rest keyform is a\n * literal zero rather than the bend's ≈3e-14 residue at 0 (see\n * `bakeTurnGroupWarp2D`).\n *\n * Offsets are in the mesh's own pixel frame — pass the SAME mesh the part\n * renders. headDeformer's own rigid nod translate rides on top, as it does for\n * every child of the head. Exported at module level for the tests, not from\n * the package.\n */\nexport function bakeNodWarp(\n mesh: IkiMesh,\n partY: number,\n surface: TurnSurface,\n nodTravel: number,\n): IkiWarp {\n const keyforms = HEAD_TURN_STOPS.map((deg) => {\n const t = (nodTravel * deg) / HEAD_TURN_MAX_DEG;\n const offsets: number[] = [];\n for (let i = 0; i < mesh.vertices.length; i += 2) {\n const y = partY + mesh.vertices[i + 1];\n offsets.push(0, deg === 0 ? 0 : t + surface.nodBendAt(y + t, deg));\n }\n return { value: deg, offsets };\n });\n return { parameter: StandardParameter.AngleY, keyforms };\n}\n\n// ── bakeHairFrontSilhouetteWarp ──────────────────────────────────────────────\n\n/**\n * How far the face plate's painted edge EVER lands from the face centre over\n * the turn, either side, as the plate RENDERS — the floor under any hold edge.\n *\n * It scans both sides at EVERY stop instead of taking the rest half-width or\n * the full-turn one, because the plate slides as well as bends, and the two\n * pull against each other on the edge the slide pushes out while the bend\n * foreshortens it in — the FAR one, whichever physical side that is at this\n * turn's sign, which is why the scan takes both. The slide's push is linear in\n * the angle and the same at any radius; the bend's pull is the cylinder's own,\n * so it shrinks as the radius flattens and gains on the slide as the angle\n * grows. Which stop reaches furthest therefore moves with the radius: on the\n * assembly fixture, whose plate is 300 px half-wide, the bend leads from the\n * first stop at a radius of 1.2 half-widths (REST wins, 300 px), the slide\n * leads until the bend catches it at 4 (a MID stop, 317 px), and is never\n * caught at 8 (FULL turn, 325 px). A hold edge inside that reach would make\n * the ramp between the two run backwards.\n */\nexport function plateReach(\n plateLandingAt: (deg: number, x: number, y: number) => number,\n faceCenterX: number,\n edgeAt: (y: number) => number,\n plateGuardRows: number[],\n): number {\n let reach = 0;\n for (const deg of HEAD_TURN_STOPS) {\n reach = Math.max(\n reach,\n plateReachAt(plateLandingAt, faceCenterX, edgeAt, plateGuardRows, deg),\n );\n }\n return reach;\n}\n\n/** How far the plate's painted edge on ONE side lands from the face centre at\n * one stop: the furthest over the guard rows, each row's own painted edge\n * read where the rendered plate lands it. */\nfunction plateSideReachAt(\n plateLandingAt: (deg: number, x: number, y: number) => number,\n faceCenterX: number,\n edgeAt: (y: number) => number,\n plateGuardRows: number[],\n deg: number,\n side: -1 | 1,\n): number {\n let reach = 0;\n for (const y of plateGuardRows) {\n const dest = plateLandingAt(deg, faceCenterX + side * edgeAt(y), y);\n reach = Math.max(reach, Math.abs(dest - faceCenterX));\n }\n return reach;\n}\n\n/**\n * `plateReach` at ONE stop: how far the plate's painted edge lands from the\n * face centre, whichever side and whichever guard row lands further out.\n *\n * Every gate on the hold edge is this number — the bake's own guard, and the\n * solver's check that a radius can hold the silhouette it was asked for — read\n * at ONE list of rows (`plateGuardRowsFor`) through ONE rendered plate. They\n * have to agree: a radius the solver accepts and the bake then refuses is a\n * generator that throws from inside its own answer.\n */\nexport function plateReachAt(\n plateLandingAt: (deg: number, x: number, y: number) => number,\n faceCenterX: number,\n edgeAt: (y: number) => number,\n plateGuardRows: number[],\n deg: number,\n): number {\n return Math.max(\n plateSideReachAt(\n plateLandingAt,\n faceCenterX,\n edgeAt,\n plateGuardRows,\n deg,\n -1,\n ),\n plateSideReachAt(\n plateLandingAt,\n faceCenterX,\n edgeAt,\n plateGuardRows,\n deg,\n 1,\n ),\n );\n}\n\n/**\n * The head's OUTLINE, held through the turn by the part that draws it — the\n * bangs — as a per-vertex AngleX warp on hair_front, keyed on HEAD_TURN_STOPS.\n *\n * The face warp bends AND slides everything riding its grid, so side strands\n * riding it would squeeze in with the plate and travel off with it: the head\n * would narrow and shift instead of turning. hair_front therefore binds to no\n * grid — it rides the rigid head — and this warp sends each vertex DIRECTLY to\n * where the outline wants it, in ABSOLUTE model x (`partX + vx`) throughout.\n *\n * The destination is `hairFrontHoldTarget`'s MONOTONE three-zone function of\n * the vertex's REST distance from the face centre, so the warp can never fold\n * a hair cell: over the painted plate (within `edgeAt` on the vertex's hold\n * row — its own, or `widestY` for a vertex resting below the face's widest\n * row, see `hairFrontHoldTarget`) a vertex lands where the RENDERED plate\n * lands that x on that row — `plateLandingAt`, the face's own mesh and grid,\n * so on rows at and above the widest the bangs sit on the face they cover\n * through the whole turn — from there out to `holdBase` a straight ramp onto\n * the hold edge's destination `holdEdgeAt(deg)`, and beyond it the hold\n * edge's own displacement, slope 1, so the outer strands carry whatever\n * silhouette change the caller asked for. The zone boundaries are REST\n * distances and do not move with the stop — only the destinations do, which\n * is what keeps the ramp and the outer zone continuous at every stop. A\n * destination that MOVES is the point of `holdEdgeAt`: a caller that has\n * measured the head's width and wants a specific silhouette ratio at full\n * turn narrows it per stop, while the boundary it pivots on stays put.\n * `holdEdgeAt(0)` must therefore be `holdBase` — enforced, since anything\n * else displaces the bangs in the rest pose, where every proportion was\n * judged — and the rest keyform is written as a literal zero: every zone is\n * the identity there, and the plate's own map leaves a bend residue at 0.\n *\n * The fold guard: at every stop, on both sides, the hold edge has to sit\n * outside the plate's rendered painted edge on EVERY row of `plateGuardRows`\n * — the same list, through the same `plateLandingAt`, that\n * `evaluateTurnCandidate` fitted the hold against, built on the same\n * `widestY` (`plateGuardRowsFor`), so a hold the solver ships is one this\n * bake can build. A vertex exactly on the axis never leaves the first zone,\n * so its zero `side` is never read.\n */\nexport function bakeHairFrontSilhouetteWarp(\n mesh: IkiMesh,\n partX: number,\n partY: number,\n faceCenterX: number,\n plateLandingAt: (deg: number, x: number, y: number) => number,\n edgeAt: (y: number) => number,\n holdBase: number,\n holdEdgeAt: (deg: number) => number,\n plateGuardRows: number[],\n widestY = -Infinity,\n): IkiWarp {\n if (holdEdgeAt(0) !== holdBase) {\n throw new Error(\n `auto-rig: bakeHairFrontSilhouetteWarp: holdEdgeAt(0) is ${holdEdgeAt(0)}, not the hold edge's own rest distance ${holdBase}, so the bangs would move in the rest pose`,\n );\n }\n const keyforms = HEAD_TURN_STOPS.map((deg) => {\n const holdEdge = holdEdgeAt(deg);\n // A hold edge inside the plate's rendered painted edge on any row would\n // run the ramp between them backwards and fold the strands onto the\n // cheek. Which stop catches it moves with the radius — see plateReach.\n const reach = ([-1, 1] as const).map((side) =>\n plateSideReachAt(\n plateLandingAt,\n faceCenterX,\n edgeAt,\n plateGuardRows,\n deg,\n side,\n ),\n );\n const reached = Math.max(reach[0], reach[1]);\n if (holdEdge <= reached) {\n const side = reach[1] > reach[0] ? 1 : -1;\n throw new Error(\n `auto-rig: bakeHairFrontSilhouetteWarp: at ${deg}° on the ${side < 0 ? \"-x\" : \"+x\"} side the hold edge sits ${holdEdge} from the face centre but the plate's edge maps to ${reached}, so the ramp between them would fold`,\n );\n }\n const offsets: number[] = [];\n for (let i = 0; i < mesh.vertices.length; i += 2) {\n const x = partX + mesh.vertices[i];\n const y = partY + mesh.vertices[i + 1];\n // Shared with evaluateTurnCandidate's own silhouette-centre reading, so\n // the two cannot drift apart on what a vertex's target actually is. dy\n // is zero — the hold is horizontal.\n offsets.push(\n deg === 0\n ? 0\n : hairFrontHoldTarget(\n x,\n y,\n deg,\n faceCenterX,\n plateLandingAt,\n edgeAt,\n holdBase,\n holdEdge,\n widestY,\n ) - x,\n 0,\n );\n }\n return { value: deg, offsets };\n });\n return { parameter: StandardParameter.AngleX, keyforms };\n}\n\n// ── bakeHairSwayWarp ───────────────────────────────────────────────────────────\n\n/** Tip travel of a swaying hair part at full sway, as a fraction of its own\n * height — \"the ends swing 9% of the hair's length\". Long back hair therefore\n * swings further than the bangs in pixels, as it should. */\nconst HAIR_SWAY_TIP_FRACTION = 0.09;\n/** Exponent on distance-from-root. 1 would be a hinge (straight shear); hair\n * bends, so the swing grows faster toward the ends. */\nconst HAIR_SWAY_CURL = 1.5;\n/** Range of the HairSwayX / HairSwayZ output parameters. */\nconst HAIR_SWAY_RANGE = 20;\n\n/**\n * Root-pinned sideways sway for a hair part, as a per-vertex warp.\n *\n * Parts have no pivot — a `rotate` binding turns a part about its crop centre.\n * Used for sway that swung the bangs' ROOTS off the hairline as much as their\n * ends, which is exactly how hair does not move. This warp instead leaves the\n * top row of the mesh where it is and displaces each row sideways by\n * `tipShift · u^HAIR_SWAY_CURL`, with `u` the row's distance from the top as a\n * fraction of the height: 0 at the roots, 1 at the ends.\n *\n * Keyforms sit at ±range with the rest pose in between, so a zero parameter is\n * zero offsets. Offsets are in the mesh's own pixel frame (+y up, centered),\n * matching `createPixelGridMesh` — pass the SAME mesh the part renders.\n *\n * Also reused for the bangs' AngleX turn lead: same root-pinned shape, driven\n * by the turn instead of a sway spring.\n */\nexport function bakeHairSwayWarp(\n mesh: IkiMesh,\n parameter: string,\n tipShift: number,\n range: number,\n): IkiWarp {\n let top = -Infinity;\n let bottom = Infinity;\n for (let i = 1; i < mesh.vertices.length; i += 2) {\n top = Math.max(top, mesh.vertices[i]);\n bottom = Math.min(bottom, mesh.vertices[i]);\n }\n const height = top - bottom;\n const swing: number[] = [];\n for (let i = 0; i < mesh.vertices.length; i += 2) {\n const u = height > 0 ? (top - mesh.vertices[i + 1]) / height : 0;\n swing.push(tipShift * Math.pow(u, HAIR_SWAY_CURL), 0);\n }\n return {\n parameter,\n keyforms: [\n // `0 - v`, not `-v`: the pinned root row must stay a plain +0.\n { value: -range, offsets: swing.map((v) => 0 - v) },\n { value: range, offsets: swing },\n ],\n };\n}\n\n// ── Head turn travel + body follow ──────────────────────────────────────────\n\n/**\n * The head's sideways travel at full turn (AngleX = ±30), as a fraction of the\n * face plate's own half-width.\n *\n * A fraction, not px, because the travel no longer cancels out of what the turn\n * is fitted to. It used to ride headDeformer, moving the eyes and the silhouette\n * together, so the measured `eyeShift` cue never saw it; baked into the face\n * grid (`TurnSurface.travel`, sampled by `bakeTurnGroupWarp2D`) it moves the\n * face inside a held silhouette and IS the floor under that cue — an absolute\n * px value would be a quarter of one plate's half-width and the whole of a\n * smaller one's. 0.25 was judged in the playground on a 400 px-wide plate,\n * where it is the 50 px that shipped.\n */\nconst HEAD_TURN_TRAVEL_RATIO = 0.25;\n\n/** That travel in px, for a plate of this half-width: what a layer set ASKS\n * the turn for. A solve with a measured head cuts it down to what the held\n * silhouette has room for — see `shellTravelCap`. */\nfunction headTurnTravel(faceHalfWidth: number): number {\n return HEAD_TURN_TRAVEL_RATIO * faceHalfWidth;\n}\n\n/** The torso's share of that travel. Below ~0.2 the shoulders still read as\n * bolted down; at 1 the neck stops articulating. 0.3 keeps 70 % of the turn\n * in the neck while the shoulders visibly come along. Judged in the\n * playground, not derived. */\nconst BODY_TURN_FOLLOW = 0.3;\n/** Vertical travel of the head at full breath (px, +y up; negative = down). */\nconst HEAD_BREATH_BOB = -12;\n/** The torso's share of the head's breath bob, SAME direction. In phase with\n * the head (not the sample model's counter-phase +6/−12, which reads as a\n * shrug: chest up while the head settles) so the neck compresses by 6 px, not\n * 18. Moving DOWN also keeps the torso's flat canvas-bottom cut off-canvas —\n * a rise would lift that hard edge into view every breath. */\nconst BODY_BREATH_FOLLOW = 0.5;\n\n// ── turnSolveInputs ───────────────────────────────────────────────────────────\n\n/** What `solveTurnModel` is handed after the targets, in its own parameter\n * order, so it spreads straight in. */\ntype TurnSolveInputs = [\n landmarks: TurnLandmarkSet,\n lattice: IkiWarpGrid,\n faceCenterX: number,\n faceHalfWidth: number,\n faceCenterY: number,\n nodRadius: number,\n carriers: ReadonlyMap<string, TurnCarrier>,\n profile: FaceRowProfile | undefined,\n hairFront:\n | { x: number; centerY: number; cropW: number; cropH: number }\n | undefined,\n];\n\n/** The standard parameters every generated rig declares — ids and ranges\n * verbatim from sample-model.ts; the hair-sway pair joins them per rig\n * (`generateIkiFromLayerSet`). Also where a binding's default-pose value is\n * read from (`defaultPoseOf`). */\nconst STANDARD_PARAMETERS: readonly IkiParameter[] = [\n {\n id: StandardParameter.MouthOpen,\n name: \"Mouth Open\",\n min: 0,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.MouthForm,\n name: \"Mouth Form\",\n min: -1,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.EyeOpenLeft,\n name: \"Eye L\",\n min: 0,\n max: 1,\n default: 1,\n },\n {\n id: StandardParameter.EyeOpenRight,\n name: \"Eye R\",\n min: 0,\n max: 1,\n default: 1,\n },\n {\n id: StandardParameter.EyeballX,\n name: \"Gaze X\",\n min: -1,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.EyeballY,\n name: \"Gaze Y\",\n min: -1,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.AngleX,\n name: \"Head Angle\",\n min: -30,\n max: 30,\n default: 0,\n },\n {\n id: StandardParameter.AngleY,\n name: \"Head Angle Y\",\n min: -30,\n max: 30,\n default: 0,\n },\n {\n id: StandardParameter.AngleZ,\n name: \"Head Angle Z\",\n min: -30,\n max: 30,\n default: 0,\n },\n {\n id: StandardParameter.Breath,\n name: \"Breath\",\n min: 0,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.BrowLeftY,\n name: \"Brow L Y\",\n min: -1,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.BrowRightY,\n name: \"Brow R Y\",\n min: -1,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.BrowLeftAngle,\n name: \"Brow L Angle\",\n min: -1,\n max: 1,\n default: 0,\n },\n {\n id: StandardParameter.BrowRightAngle,\n name: \"Brow R Angle\",\n min: -1,\n max: 1,\n default: 0,\n },\n];\n\n/**\n * `evaluateTransform`'s additive rule, mirrored (the engine is not a\n * dependency of this package): scale starts at 1 and adds each binding's\n * value, rotation and translate sum, opacity moves nothing. `valueOf` picks\n * each binding's value — a `from`/`to` extreme for a bound, the default-pose\n * value for the pose the renderer rests in.\n */\nfunction transformUnder(\n bindings: readonly IkiBinding[],\n valueOf: (binding: IkiBinding, index: number) => number,\n): { x: number; y: number; rotation: number; scaleX: number; scaleY: number } {\n const trs = { x: 0, y: 0, rotation: 0, scaleX: 1, scaleY: 1 };\n bindings.forEach((binding, i) => {\n const value = valueOf(binding, i);\n switch (binding.channel) {\n case \"translateX\":\n trs.x += value;\n break;\n case \"translateY\":\n trs.y += value;\n break;\n case \"rotate\":\n trs.rotation += value;\n break;\n case \"scaleX\":\n trs.scaleX += value;\n break;\n case \"scaleY\":\n trs.scaleY += value;\n break;\n case \"opacity\":\n break;\n }\n });\n return trs;\n}\n\n/** The TRS a part's bindings give it with every parameter at its declared\n * default — what `evaluateTransform` returns on a freshly loaded rig, the\n * pose every `RenderedPart` is read in. A binding's value there is its\n * range at `ParameterStore.normalized`'s own `(default − min) / (max − min)`\n * (no standard parameter is zero-width). */\nfunction defaultPoseOf(bindings: readonly IkiBinding[]) {\n return transformUnder(bindings, (binding) => {\n const p = STANDARD_PARAMETERS.find((p) => p.id === binding.parameter);\n if (p === undefined) {\n throw new Error(\n `auto-rig: defaultPoseOf: \"${binding.parameter}\" is not a standard parameter`,\n );\n }\n const t = (p.default - p.min) / (p.max - p.min);\n return binding.from + (binding.to - binding.from) * t;\n });\n}\n\n/** A face-family layer as the generator rigs it: the crop's placement, the\n * part as it renders (`RenderedPart`, the default pose on that placement),\n * the mesh it renders with, the role's OWN bindings — `bindingsForRole`\n * without the parallax units, the expression it has whatever the turn does\n * — and its part warps (the eyelid fold on a white or a lash, else none).\n * Everything its group grid is sized from (`groupGridFor`) and its carrier\n * reads with, built once for the solve and the parts alike. Exported at\n * module level for the tests, not from the package. */\nexport interface TurnGroupMember {\n role: string;\n group: TurnGroupId;\n /** The crop's own placement (`bboxToTransform`), what the bindings add to. */\n transform: { x: number; y: number };\n part: RenderedPart;\n mesh: IkiMesh;\n bindings: readonly IkiBinding[];\n warps: readonly IkiWarp[];\n}\n\n/**\n * One turn group's grid: the members' PRE-BIND extent — where the engine\n * binds their vertices to it (`applyWarpToChild`: part warps, then the TRS\n * the bindings give) — grown by the family's turn and nod shifts and then by\n * GRID_MARGIN of its span per axis plus a pixel, over `cells` × `cells`\n * uniform cells.\n *\n * With a `cellPx`, every uniform cell wider than it that overlaps the\n * members' pre-bind x extent — before the shifts and the margin — also gets\n * columns across that overlap: at its ends, where an end falls inside the\n * cell, and at equal steps between them no wider than `cellPx`. Every uniform\n * column and row is kept bit for bit, so the grid's reach and any node a\n * reader sits on are unchanged; the new nodes only shorten the chords under\n * the members. Only the mouth takes it (MOUTH_GRID_CELL_PX): the widest\n * drawing on a grid sized by a depth cap several times its solved shift, on\n * the rows the jaw's taper bends hardest.\n *\n * The extent is the union, over every member, of its mesh under each part\n * warp's keyforms — the rest mesh, and the mesh with each keyform's offsets\n * added (the fold's closed shape; a blend of two keyforms lies inside the box\n * of their union, and a member carries one warp at most) — placed by EVERY\n * combination of its geometric bindings' `from`/`to` extremes through\n * `evaluateTransform`'s additive rule (`transformUnder`: scale = 1 + Σ\n * values, so a `mouth` without an open drawing reaches scaleY 4 and scaleX\n * 1.4; rotation = Σ, so a ±12° brow grows its y extent by its half-width's\n * sine; a translate sums; opacity moves nothing and is skipped). The extremes\n * are at the corners: each channel is affine in its bindings, and the\n * rotations are small enough that a corner's x and y are monotone in the\n * angle to within a fraction of a pixel (a brow flatter than tan 12° peaks a\n * little inside ±12° and overshoots its corner by under 0.1 px), far under\n * the margin. `turnShiftPx` is the furthest the family's solved depth can carry a\n * node — the depth solver's own cap in px — and `nodShiftPx` its nod\n * translate; both are added on both sides, every stop being ±.\n *\n * The margin is what keeps a vertex off the grid boundary, where\n * `bindPointToRestGrid` would clamp it. The face — no binding, no warp, no\n * shift — gets its crop plus the margin and nothing else, symmetric about its\n * centre, so the plate's axis is a node. Exported at module level for the\n * tests, not from the package.\n */\nexport function groupGridFor(\n members: readonly TurnGroupMember[],\n cells: number,\n turnShiftPx: number,\n nodShiftPx: number,\n cellPx?: number,\n): IkiWarpGrid {\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n for (const member of members) {\n const rest = member.mesh.vertices;\n const shapes = [rest];\n for (const warp of member.warps) {\n for (const keyform of warp.keyforms) {\n shapes.push(rest.map((v, i) => v + keyform.offsets[i]));\n }\n }\n const geometric = member.bindings.filter((b) => b.channel !== \"opacity\");\n for (let corner = 0; corner < 1 << geometric.length; corner++) {\n const trs = transformUnder(geometric, (b, i) =>\n (corner & (1 << i)) === 0 ? b.from : b.to,\n );\n const cos = Math.cos(trs.rotation * (Math.PI / 180));\n const sin = Math.sin(trs.rotation * (Math.PI / 180));\n for (const shape of shapes) {\n for (let i = 0; i < shape.length; i += 2) {\n // The engine's order: scale, rotate, translate — about the part's\n // own centre, where the crop places it.\n const sx = shape[i] * trs.scaleX;\n const sy = shape[i + 1] * trs.scaleY;\n const x = member.transform.x + trs.x + cos * sx - sin * sy;\n const y = member.transform.y + trs.y + sin * sx + cos * sy;\n minX = Math.min(minX, x);\n maxX = Math.max(maxX, x);\n minY = Math.min(minY, y);\n maxY = Math.max(maxY, y);\n }\n }\n }\n }\n const extentMinX = minX;\n const extentMaxX = maxX;\n minX -= turnShiftPx;\n maxX += turnShiftPx;\n minY -= nodShiftPx;\n maxY += nodShiftPx;\n const marginX = (maxX - minX) * GRID_MARGIN + 1;\n const marginY = (maxY - minY) * GRID_MARGIN + 1;\n const points = generateGridPoints(\n cells,\n cells,\n minX - marginX,\n maxX + marginX,\n minY - marginY,\n maxY + marginY,\n );\n if (cellPx === undefined) return { cols: cells, rows: cells, points };\n\n // The uniform grid's own columns and rows, read back off it so every one is\n // kept bit for bit, with the new columns merged in between.\n const stride = cells + 1;\n const uniformXs = Array.from({ length: stride }, (_, c) => points[c * 2]);\n const ys = Array.from(\n { length: stride },\n (_, r) => points[r * stride * 2 + 1],\n );\n const xs = [uniformXs[0]];\n for (let c = 0; c < cells; c++) {\n const left = uniformXs[c];\n const right = uniformXs[c + 1];\n const from = Math.max(left, extentMinX);\n const to = Math.min(right, extentMaxX);\n if (right - left > cellPx && from < to) {\n const steps = Math.ceil((to - from) / cellPx);\n if (from > left) xs.push(from);\n for (let k = 1; k < steps; k++) {\n xs.push(from + (k * (to - from)) / steps);\n }\n if (to < right) xs.push(to);\n }\n xs.push(right);\n }\n const merged: number[] = [];\n for (const y of ys) {\n for (const x of xs) merged.push(x, y);\n }\n return { cols: xs.length - 1, rows: cells, points: merged };\n}\n\n/** The eyelid fold of an eye white or a lash, or undefined for any other\n * role: both fold to the WHITE's crease on that side — EYELID_FOLD_CREASE\n * below the white's own centre — on that side's EyeOpen, so the lash lands\n * on top of the cut eyeball and covers the seam. The lash keeps LASH_FOLD_K\n * of its height; the white EYELID_FOLD_K, or nothing at all when a lash\n * supplies the closed line (a strip of iris must not show under it). Both\n * whites are REQUIRED_ROLES, so the crease always has one to read. `t` is\n * the part's placement and `mesh` the mesh it renders with. */\nfunction eyelidFoldFor(\n layers: LayerInput[],\n layer: LayerInput,\n mesh: IkiMesh,\n t: { x: number; y: number },\n): IkiWarp | undefined {\n const side = ROLE_TABLE[layer.role].eyeSide;\n const isLash = layer.role.startsWith(\"lash_\");\n if (side === undefined || !(layer.role.startsWith(\"eye_\") || isLash)) {\n return undefined;\n }\n const white = layers.find((l) => l.role === `eye_${side}`)!;\n const creaseWorldY =\n bboxToTransform(white.bbox, white.canvasW, white.canvasH, white.role).y -\n EYELID_FOLD_CREASE * white.cropH;\n const hasLash = layers.some((l) => l.role === `lash_${side}`);\n return bakeEyelidFoldWarp(\n mesh,\n side === \"L\"\n ? StandardParameter.EyeOpenLeft\n : StandardParameter.EyeOpenRight,\n creaseWorldY - t.y,\n isLash ? LASH_FOLD_K : hasLash ? 0 : EYELID_FOLD_K,\n );\n}\n\n/** Everything `turnSetup` derives from a layer set, by name: what the solve\n * is handed (`solveInputsOf` lines the first nine up in `solveTurnModel`'s\n * order), the plate guards derived once for the solve and the bake alike,\n * and what the generator ships and bakes from — the members' meshes and\n * warps, each group's grid, the nod's parallax unit off the same head\n * half-height as the nod radius — so the two read one set of values. */\ninterface TurnSetup {\n landmarks: TurnLandmarkSet;\n lattice: IkiWarpGrid;\n faceCenterX: number;\n faceHalfWidth: number;\n faceCenterY: number;\n nodRadius: number;\n carriers: ReadonlyMap<string, TurnCarrier>;\n /** The face layer's own row profile, when it measured one. */\n profile: FaceRowProfile | undefined;\n hairFront:\n | { x: number; centerY: number; cropW: number; cropH: number }\n | undefined;\n /** The eye pair's mean y — the row the cues are measured on and a row\n * profile is normalised at, as `solveTurnModel` reads it off the same\n * landmarks; the generator's own surface, when nothing is solved, is\n * normalised there too. */\n eyeRowY: number;\n /** The plate and its guards, `plateGuardsOf` — see that function. */\n plate: TurnCarrier;\n edgeAt: (y: number) => number;\n plateGuardRows: number[];\n members: readonly TurnGroupMember[];\n groupGrids: ReadonlyMap<TurnGroupId, IkiWarpGrid>;\n parallaxUnitY: number;\n hasNose: boolean;\n hasMouthOpen: boolean;\n}\n\n/**\n * The turn's geometry for a layer set, in the order nothing may depend on the\n * solve: every face-family layer's placement, role bindings and part warps\n * (pure — no unit, no depth) → each group's grid from its members\n * (`groupGridFor`) → the virtual lattice from those grids → the landmarks on\n * their carriers. The solve then runs on exactly these, and the bakes sample\n * exactly these, which is what lets the report equal the render.\n *\n * The head's vertical reach — the nod cylinder's radius and its parallax unit\n * — is the TURN FAMILY's union on y (every group member plus the bangs,\n * transform ± crop/2, the centred pixel-mesh convention) grown by GRID_MARGIN,\n * about the face centre: the rows the shared face grid had, kept as the one\n * head-level value however the groups are cut.\n *\n * A family's shift bound is the depth solver's own cap in px — its landmarks'\n * far edge to the plate's edge (`solveTurnDepthSigned`) — and 0 when nothing\n * is solved (no nose, FEATURE_NOD_DEPTH's gate); the brows and the blush ride\n * with the eyes (`turnFamily`), the plate has none. Each group's grid takes its\n * family's, and the lattice — one row of TURN_LATTICE_CELL_PX columns\n * anchored on the face centre — is widened so every grid's every node,\n * shifted by the LARGEST bound either way, sits strictly inside its outer\n * columns, snapped outward to the next anchored column: `mapX` pins anything\n * outside onto the edge column, which would deform a node even at rest. The\n * lattice's height is the union's; `turnColumnMap` reads row 0 only.\n *\n * The face's row profile (`faceRowProfile`), when its layer carries one, is\n * read here and handed to the solve and the generator alike, and the plate's\n * guards are derived from it once (`plateGuardsOf`).\n */\nfunction turnSetup(layers: LayerInput[]): TurnSetup {\n // validateLayerInputs guarantees \"face\" is present — safe to assert here.\n const faceLayer = layers.find((l) => l.role === \"face\")!;\n const faceTransform = bboxToTransform(\n faceLayer.bbox,\n faceLayer.canvasW,\n faceLayer.canvasH,\n \"face\",\n );\n // Source-placed face centre in model space (unshifted).\n const faceCenterX = faceTransform.x;\n const faceCenterY = faceTransform.y;\n const faceHalfWidth = faceLayer.cropW / 2;\n const plateEdgeX = faceCenterX - faceHalfWidth;\n // The feature parallax's gate — see FEATURE_NOD_DEPTH.\n const hasNose = layers.some((l) => l.role === \"nose\");\n const hasMouthOpen = layers.some((l) => l.role === \"mouth_open\");\n\n // ── The head's vertical reach ─────────────────────────────────────────────\n // The face is required and a group member, so the union is never empty.\n const turnLayers = layers.filter(\n (l) => isTurnGroup(ROLE_TABLE[l.role].deformer) || l.role === \"hair_front\",\n );\n const turnYs = turnLayers.map((l) => ({\n y: bboxToTransform(l.bbox, l.canvasW, l.canvasH, l.role).y,\n h: l.cropH,\n }));\n let unionMinY = Math.min(...turnYs.map(({ y, h }) => y - h / 2));\n let unionMaxY = Math.max(...turnYs.map(({ y, h }) => y + h / 2));\n const spanY = unionMaxY - unionMinY;\n unionMinY -= spanY * GRID_MARGIN;\n unionMaxY += spanY * GRID_MARGIN;\n // The larger of the two distances from the nod axis, so a union that is not\n // symmetric about it still keeps every row within the cylinder's bound.\n const halfH = Math.max(faceCenterY - unionMinY, unionMaxY - faceCenterY);\n const nodRadius = halfH * HEAD_CYLINDER_RADIUS_FACTOR;\n const parallaxUnitY = headNodParallaxUnit(halfH);\n\n // ── The members ───────────────────────────────────────────────────────────\n const members: TurnGroupMember[] = [];\n for (const layer of layers) {\n const spec = ROLE_TABLE[layer.role];\n if (!isTurnGroup(spec.deformer)) continue;\n const { role, cropW, cropH } = layer;\n const t = bboxToTransform(layer.bbox, layer.canvasW, layer.canvasH, role);\n const { cols, rows } = meshCellsFor(cropW, cropH);\n const mesh = createPixelGridMesh(cols, rows, cropW, cropH);\n const bindings = bindingsForRole(spec, role, cropW, cropH, {\n hasMouthOpen,\n });\n const fold = eyelidFoldFor(layers, layer, mesh, t);\n const pose = defaultPoseOf(bindings);\n members.push({\n role,\n group: spec.deformer,\n transform: t,\n part: {\n x: t.x + pose.x,\n y: t.y + pose.y,\n cropW,\n cropH,\n cols,\n rows,\n scaleX: pose.scaleX,\n scaleY: pose.scaleY,\n rotation: pose.rotation,\n },\n mesh,\n bindings,\n warps: fold === undefined ? [] : [fold],\n });\n }\n\n // ── Each family's shift bounds ────────────────────────────────────────────\n const landmarks = turnLandmarks(layers);\n const turnShiftOf = (marks: TurnLandmark[]) =>\n hasNose && marks.length > 0 ? familyReachPx(marks, plateEdgeX) : 0;\n const turnShift: Record<keyof TurnDepths, number> = {\n eye: turnShiftOf(landmarks.eye),\n nose: turnShiftOf(landmarks.nose),\n mouth: turnShiftOf(landmarks.mouth),\n };\n\n // ── The group grids and the carriers ──────────────────────────────────────\n const groupGrids = new Map<TurnGroupId, IkiWarpGrid>();\n for (const { group, role } of members) {\n if (groupGrids.has(group)) continue;\n const own = members.filter((m) => m.group === group);\n const isPlate = group === \"faceWarp\";\n // The nod translate the members will carry (featureParallaxBindings) —\n // one depth per group, taken as the members' largest all the same.\n const nodShiftPx = hasNose\n ? Math.max(\n ...own.map((m) => FEATURE_NOD_DEPTH[roleFamily(m.role)] ?? 0),\n ) * parallaxUnitY\n : 0;\n // The mouth's grid gains columns over its drawings (MOUTH_GRID_CELL_PX).\n // Every uniform column is kept, so the reach the lattice reads off the\n // end columns below is unchanged, and so is the mouth landmark wherever\n // the two mouth drawings share a centre x (its vertex then rests on the\n // kept centre column; otherwise it sits inside a cell and the mouth's\n // depth may re-settle by the chord that cell no longer carries).\n groupGrids.set(\n group,\n groupGridFor(\n own,\n isPlate ? FACE_PLATE_CELLS : FEATURE_GRID_CELLS,\n isPlate ? 0 : turnShift[turnFamily(role)],\n nodShiftPx,\n group === \"mouthWarp\" ? MOUTH_GRID_CELL_PX : undefined,\n ),\n );\n }\n // The mouth family turns about the closed mouth's rest centre, its source\n // placement — the one its landmark reads (`turnLandmarks`) — with or\n // without a nose, the tilt being no slide. validateLayerInputs guarantees\n // \"mouth\" is present.\n const mouthAt = members.find((m) => m.role === \"mouth\")!.transform;\n const mouthAnchor: TurnAnchor = {\n x: mouthAt.x,\n y: mouthAt.y,\n tiltDeg: MOUTH_TURN_TILT_DEG,\n };\n // The nose turns about its bridge top — the top edge of the box its\n // landmark reads (`noseDrawnBox`), at that box's centre x — so the bridge\n // rides the slide and the tip, the end furthest from the pivot, carries the\n // tilt.\n const noseLayer = layers.find((l) => l.role === \"nose\");\n let noseAnchor: TurnAnchor | undefined;\n if (noseLayer) {\n const drawn = noseDrawnBox(noseLayer);\n const at = bboxToTransform(\n drawn,\n noseLayer.canvasW,\n noseLayer.canvasH,\n \"nose\",\n );\n noseAnchor = {\n x: at.x,\n y: at.y + drawn.h / 2,\n tiltDeg: NOSE_TURN_TILT_DEG,\n };\n }\n const carriers = new Map<string, TurnCarrier>(\n members.map((m) => {\n const grid = groupGrids.get(m.group)!;\n const anchor =\n m.group === \"mouthWarp\"\n ? mouthAnchor\n : m.group === \"noseWarp\"\n ? noseAnchor\n : undefined;\n return [\n m.role,\n anchor === undefined\n ? { grid, part: m.part }\n : { grid, part: m.part, anchor },\n ];\n }),\n );\n\n // ── The lattice ───────────────────────────────────────────────────────────\n const maxShift = Math.max(turnShift.eye, turnShift.nose, turnShift.mouth);\n let reach = 0;\n for (const grid of groupGrids.values()) {\n reach = Math.max(\n reach,\n faceCenterX - grid.points[0] + maxShift,\n grid.points[grid.cols * 2] - faceCenterX + maxShift,\n );\n }\n const halfCells = Math.floor(reach / TURN_LATTICE_CELL_PX) + 1;\n // Columns at faceCenterX ± k·pitch outright, not a span divided up, so the\n // axis column IS faceCenterX to the last bit; row 0 is the top row.\n const points: number[] = [];\n for (const y of [unionMaxY, unionMinY]) {\n for (let k = -halfCells; k <= halfCells; k++) {\n points.push(faceCenterX + k * TURN_LATTICE_CELL_PX, y);\n }\n }\n const lattice: IkiWarpGrid = { cols: 2 * halfCells, rows: 1, points };\n\n const hairFrontLayer = layers.find((l) => l.role === \"hair_front\");\n const hairFront =\n hairFrontLayer &&\n (() => {\n const t = bboxToTransform(\n hairFrontLayer.bbox,\n hairFrontLayer.canvasW,\n hairFrontLayer.canvasH,\n \"hair_front\",\n );\n return {\n x: t.x,\n centerY: t.y,\n cropW: hairFrontLayer.cropW,\n cropH: hairFrontLayer.cropH,\n };\n })();\n\n const eyeRowY = eyeRowOf(landmarks, faceCenterY);\n const profile = faceRowProfile(faceLayer, faceCenterY);\n\n return {\n landmarks: turnLandmarks(layers, carriers),\n lattice,\n faceCenterX,\n faceHalfWidth,\n faceCenterY,\n nodRadius,\n carriers,\n profile,\n hairFront,\n eyeRowY,\n ...plateGuardsOf(carriers, faceHalfWidth, profile, hairFront),\n members,\n groupGrids,\n parallaxUnitY,\n hasNose,\n hasMouthOpen,\n };\n}\n\n/**\n * Everything `generateIkiFromLayerSet` hands `solveTurnModel` for a layer set,\n * the targets and the caller's `headEdges` excepted (they are the caller's\n * own) — built by `turnSetup` and nowhere else, so a test that solves a layer\n * set directly solves exactly the turn the generator solves, on the very\n * grids and carriers it ships. Exported at module level for those tests, not\n * from the package.\n */\nexport function turnSolveInputs(layers: LayerInput[]): TurnSolveInputs {\n return solveInputsOf(turnSetup(layers));\n}\n\n/** A setup's fields in `solveTurnModel`'s positional order — spelled here\n * once, since four adjacent numbers in it are not told apart by type. */\nfunction solveInputsOf(s: TurnSetup): TurnSolveInputs {\n return [\n s.landmarks,\n s.lattice,\n s.faceCenterX,\n s.faceHalfWidth,\n s.faceCenterY,\n s.nodRadius,\n s.carriers,\n s.profile,\n s.hairFront,\n ];\n}\n\n// ── generateIkiFromLayerSet ───────────────────────────────────────────────────\n\n/**\n * Auto-rig: given decoded layer inputs and the shared canvas size, produce a\n * valid IkiModel ready for parseIkiModel.\n *\n * - Validate all inputs before deriving anything.\n * - Place parts at source-derived positions (bboxToTransform, unshifted).\n * - Emit the standard parameters (STANDARD_PARAMETERS), plus a conditional\n * HairSwayX/Z pair + hair-sway physics rigs when a hair_front layer is\n * present.\n * - Size every turn group's grid to its own members and the virtual lattice\n * to those grids, then solve the turn on them (turnSetup, solveTurnModel):\n * one surface — radius, travel, the head-level nod radius — that every\n * bake below samples.\n * - Build headDeformer (matrix, neck pivot, nod/tilt/breath bindings — the\n * turn translates nothing rigidly; its travel is in the group grids), one\n * warp deformer per turn group present — `faceWarp` for the plate,\n * `eyeWarp_L/R`, `mouthWarp`, `noseWarp`, `browWarp_L/R`, `blushWarp_L/R`\n * — each a warp2d over its own grid baked from that surface with the\n * family's solved depth as keyform geometry (bakeTurnGroupWarp2D), and\n * bodyDeformer when a body layer is present (matrix, torso-base pivot,\n * AngleX follow + Breath follow).\n * - Mesh parts (spec.mesh===true) → width:1, height:1, pixel grid mesh sized by\n * meshCellsFor + role bindings, each face-family part on its group's warp.\n * - Static parts (spec.mesh===false) → width:cropW, height:cropH, no mesh.\n * - Part ids equal the role string (deterministic, no crypto.randomUUID).\n * - Return parseIkiModel(structuredClone(model)) — every caller gets a\n * validated model; bad assembly fails loudly.\n *\n * `options.turnTargets` is what the head turn is fitted to: the cues a 30°\n * reference measures, defaulting to DEFAULT_TURN_TARGETS. A target the CALLER\n * passed that this layer set cannot reach throws; one that came from the\n * defaults is clamped to what it can do, and so is a caller's `eyeShift` past\n * the room the art leaves the far eye — see TurnTargets for why, and pass\n * `options.onTurnSolved` to see which ones were clamped and what the turn\n * ended up reaching.\n *\n * `options.strandEdges` gives each side's iris and the bangs' run it would\n * slide under as measured pixel edges (`IrisStrand`), and with it the turn\n * keeps each far iris from sliding under that run any further than it is\n * painted, best effort: where no radius the fit can use holds that, the rig\n * is still built, with the eyes' depth at 0, and the report's\n * `strandOverlap` says how much of the iris the bangs cover.\n */\nexport function generateIkiFromLayerSet(\n layers: LayerInput[],\n canvas: { width: number; height: number },\n options: {\n turnTargets?: TurnTargets;\n /** Called once, after a turn is solved, with what it settled on. Not called\n * for a layer set without a nose, which solves no turn at all. */\n onTurnSolved?: (report: TurnSolveReport) => void;\n /** Every role with an opaque pixel in the eye-row band, per side, when\n * `turnTargets.headHalfWidth` was measured off that same union — a\n * companion to it, not a turn target itself (the caller-facing contract\n * stays the three cues). See `TurnSolveContext.headEdges`. */\n headEdges?: {\n left: { role: string; x: number }[];\n right: { role: string; x: number }[];\n };\n /** Per side (`left` the −x side, `right` the +x side, like `headEdges`),\n * that side's iris and the bangs' run it would slide under on the turn,\n * as real pixel edges on the row holding the iris's centre — see\n * `IrisStrand` for the contract and its three cases (a strand outward of\n * a clear iris, a run over the iris centre that clears on the face side,\n * a fringe spanning the face). A side's iris is whichever of `iris_L` /\n * `iris_R` sits on that side; the sides are independent, so either may\n * be absent. With it the turn keeps each far iris from sliding under its\n * run any further than it is painted, best effort: never a refusal. The\n * report's `strandOverlap` gives, per side whose far iris the run still\n * covers at some far stop, the covered width at the stop where it is\n * largest, next to the painted one, and whether the bound held — `true`\n * for an iris painted under its run that the turn took no deeper,\n * `false` where the fitted radius could not hold it and the eyes' turn\n * depth is 0. It is always\n * validated against the layers first (`validateStrandEdges`), but only\n * the turn solve reads it, so like `headEdges` it has no effect on the\n * rig without a nose; absent or `{}`, the rig is the one built without\n * it. */\n strandEdges?: { left?: IrisStrand; right?: IrisStrand };\n } = {},\n): IkiModel {\n // Validate first — never derive anything from unchecked input.\n validateLayerInputs(layers, canvas);\n validateStrandEdges(layers, options.strandEdges);\n\n // Hair-sway secondary motion is gated on a front-hair layer being present.\n const hasHair = layers.some((l) => l.role === \"hair_front\");\n const bodyLayer = layers.find((l) => l.role === \"body\");\n\n // ── Standard parameters ───────────────────────────────────────────────────\n const parameters: IkiParameter[] = [...STANDARD_PARAMETERS];\n\n // Hair-sway output params (physics-driven), declared only when there is front\n // hair to drive — keeps no-hair models free of unused parameters.\n if (hasHair) {\n parameters.push(\n {\n id: StandardParameter.HairSwayX,\n name: \"Hair Sway X\",\n min: -HAIR_SWAY_RANGE,\n max: HAIR_SWAY_RANGE,\n default: 0,\n },\n {\n id: StandardParameter.HairSwayZ,\n name: \"Hair Sway Z\",\n min: -HAIR_SWAY_RANGE,\n max: HAIR_SWAY_RANGE,\n default: 0,\n },\n );\n }\n\n // ── The face, its groups, the lattice and the turn ────────────────────────\n // Exactly what the solve is handed, and the members and group grids it was\n // built from — built once in turnSetup, so a test that solves this layer\n // set directly (turnSolveInputs) solves this very turn on these very grids.\n const setup = turnSetup(layers);\n const {\n lattice,\n faceCenterX,\n faceHalfWidth,\n faceCenterY,\n nodRadius,\n profile,\n eyeRowY,\n plate,\n edgeAt,\n plateGuardRows,\n members,\n groupGrids,\n carriers,\n parallaxUnitY,\n hasNose,\n hasMouthOpen,\n } = setup;\n const faceCropH = plate.part.cropH;\n\n // The head cylinder's turn radius, the face's sideways travel and how far in\n // front of the axis each feature sits: SOLVED from the turn cues, on the\n // very surface every group grid is baked from, so what the targets promise\n // is what the keyforms do. Without a nose there is no feature slide to fit\n // (see FEATURE_NOD_DEPTH) and nothing to solve the radius against.\n const turn = hasNose\n ? solveTurnModel(\n resolveTurnTargets(options.turnTargets),\n ...solveInputsOf(setup),\n options.headEdges,\n options.strandEdges,\n )\n : undefined;\n if (turn?.unreachable) {\n throw new TurnTargetError(\n turn.attainable === undefined\n ? // Only a silhouette can land here: nothing the field's own accepted\n // range allows renders on this layer set, so there is no interval to\n // offer instead — see `silhouetteMiss` in evaluateTurnCandidate.\n `auto-rig: turnTargets.${turn.field} ${turn.value} is unreachable for this layer set, and so is every ${turn.field} in [${SILHOUETTE_RATIO_MIN}, ${TURN_RATIO_MAX}]`\n : `auto-rig: turnTargets.${turn.field} ${turn.value} is unreachable for this layer set (attainable ${turn.attainable[0]}…${turn.attainable[1]})`,\n );\n }\n if (turn) {\n options.onTurnSolved?.({\n radius: turn.radius,\n holdBase: turn.holdBase,\n depths: turn.depths,\n achieved: turn.achieved,\n clamped: turn.clamped,\n ...(turn.strandOverlap === undefined\n ? {}\n : { strandOverlap: turn.strandOverlap }),\n });\n }\n // The analytic head every bake below samples — every group's grid, the\n // bangs' hold and the body's follow all read this ONE surface, so nothing\n // renders a turn the cues were not measured on. Solved when there was a\n // nose; without one, the lattice's own reach about the face centre with the\n // no-fold margin (on a row reading aMax the bound lands beyond every node\n // any grid reads, so those rows never leave the surface; a narrower profile\n // row's bound is nearer — ≈ 150 px on a 96 px band, against plate nodes at\n // ±250 — so its transparent-margin nodes ride the rigid branch: monotone,\n // so fold-free, and beyond the painted edge, so nothing reads them, as\n // Decision 5 accepts) and the plate's own uncut travel ask, there being no\n // held silhouette to size it against; the face's own row profile, when it\n // has one, bends that surface's rows the same way it would a solved one's.\n // Its nod radius is the head's, the one the solve was handed. The turn's\n // depth-parallax unit comes off the same cylinder the bakes bend.\n const latticeReach = Math.max(\n faceCenterX - lattice.points[0],\n lattice.points[lattice.cols * 2] - faceCenterX,\n );\n const surface =\n turn?.surface ??\n turnSurface({\n faceCenterX,\n faceCenterY,\n radius: latticeReach * HEAD_CYLINDER_RADIUS_FACTOR,\n travel: headTurnTravel(faceHalfWidth),\n nodRadius,\n lattice,\n profile,\n eyeRowY,\n });\n const parallaxUnit = headTurnParallaxUnit(surface.radius);\n // The plate as it RENDERS on that surface — its own mesh over its own grid,\n // the carrier. Its painted edge per row (`edgeAt`) and the rows it is\n // guarded at (`plateGuardRows`) are the setup's own, the very ones the solve\n // read (`plateGuardsOf`), so the hold the solve fitted is the hold the bake\n // below can build.\n const plateLandingAt = plateLandingOn(plate, rowMapsOf(surface));\n\n // ── headDeformer pivot (neck): slightly below the face bottom ─────────────\n // faceBottom is the model-space y of the bottom edge of the face crop.\n // The neck pivot sits 15% of the face crop height below the face bottom.\n const faceBottom = faceCenterY - faceCropH / 2;\n const neckPivot = {\n x: faceCenterX,\n y: faceBottom - faceCropH * 0.15, // 15% below face bottom = neck\n };\n\n // ── Deformers ─────────────────────────────────────────────────────────────\n const deformers: IkiDeformer[] = [\n // headDeformer: rigid matrix rotating/translating the whole head about the\n // neck pivot. Same bindings as sample-model.ts apart from the turn, which\n // moves the face inside the head rather than the head itself (see below).\n {\n id: \"headDeformer\",\n pivot: neckPivot,\n bindings: [\n // No AngleX binding: the turn moves NOTHING rigidly. Its sideways\n // travel is baked into every group grid (the surface's `travel`, see\n // bakeTurnGroupWarp2D) so the face slides inside a silhouette the\n // bangs hold; a translate here would take the hair shell with it. Nor\n // is the turn a roll — the sample model's ±6° \"lean into the turn\" was\n // tried here and dropped, since rotating about the neck pivot swings\n // the crown (~500px above it) far more than the chin, so the top of\n // the head appeared to lunge ahead of the face on every turn. Roll is\n // AngleZ's job, below.\n\n // Nod: a vertical translate only. No rotate — a pitch expressed as a\n // rigid rotation would sum with the AngleZ roll below at diagonal\n // poses, collapsing pitch into roll.\n {\n parameter: StandardParameter.AngleY,\n channel: \"translateY\" as const,\n from: -NOD_TRAVEL,\n to: NOD_TRAVEL,\n },\n // Tilt: the whole head rolls about the neck pivot, one degree per\n // degree. Positive AngleZ is clockwise on screen (engine rotate is\n // CCW-positive, hence the flipped from/to) — Live2D's convention: in\n // its own sample motions AngleZ carries the sign of AngleX 214 times\n // out of 222, i.e. a head that leans into its turn tilts clockwise on\n // a turn to the viewer's right. The only rotate binding on the head.\n {\n parameter: StandardParameter.AngleZ,\n channel: \"rotate\" as const,\n from: 30,\n to: -30,\n },\n {\n parameter: StandardParameter.Breath,\n channel: \"translateY\" as const,\n from: 0,\n to: HEAD_BREATH_BOB,\n },\n ],\n },\n ];\n\n // One cylinder-bend warp per turn group present, parented to headDeformer:\n // the group's own grid (`groupGridFor`, the very carrier the solve read its\n // parts through), every node sampling the surface at its own rest position\n // — shifted, at each turn stop, by the family's solved depth share of the\n // parallax unit, so the depth is the grid's keyform geometry rather than a\n // translate on the parts (see featureParallaxBindings); the mouth's and the\n // nose's nodes turn about their carrier's anchor, the one the solve read\n // them by. The plate has no depth: it IS the cylinder. Without a solve no\n // depth is known and every group reads the surface unshifted. One 2D warp\n // carries both the turn and the nod (a deformer holds either `warps` or\n // `warp2d`, never both).\n for (const [group, grid] of groupGrids) {\n const { role } = members.find((m) => m.group === group)!;\n const family = turnFamily(role);\n const depth = group === \"faceWarp\" ? 0 : (turn?.depths[family] ?? 0);\n deformers.push({\n kind: \"warp\" as const,\n id: group,\n parent: \"headDeformer\",\n grid,\n warp2d: bakeTurnGroupWarp2D(\n grid,\n StandardParameter.AngleX,\n StandardParameter.AngleY,\n surface,\n (deg) => (depth * parallaxUnit * deg) / HEAD_TURN_MAX_DEG,\n carriers.get(role)!.anchor,\n ),\n });\n }\n\n // bodyDeformer: the torso's own rigid deformer. A sibling of headDeformer, not\n // its parent, so the head's own bindings stay untouched; reparent the head\n // under it once the torso gains a rotation. Translate-only, so the pivot is\n // inert today; it sits at the torso base, where a future lean would rock from.\n if (bodyLayer) {\n const bt = bboxToTransform(\n bodyLayer.bbox,\n bodyLayer.canvasW,\n bodyLayer.canvasH,\n \"body\",\n );\n deformers.push({\n id: \"bodyDeformer\",\n pivot: { x: bt.x, y: bt.y - bodyLayer.cropH / 2 },\n bindings: [\n // Low-weight follow of the turn, same direction as the face's own\n // slide — the only rigid part of the travel left.\n {\n parameter: StandardParameter.AngleX,\n channel: \"translateX\" as const,\n from: -BODY_TURN_FOLLOW * surface.travel,\n to: BODY_TURN_FOLLOW * surface.travel,\n },\n // Breath: follow the head's bob, same direction, half amplitude.\n {\n parameter: StandardParameter.Breath,\n channel: \"translateY\" as const,\n from: 0,\n to: BODY_BREATH_FOLLOW * HEAD_BREATH_BOB,\n },\n ],\n });\n }\n\n // ── Parts ─────────────────────────────────────────────────────────────────\n const memberByRole = new Map(members.map((m) => [m.role, m]));\n const parts: IkiPart[] = layers.map((layer) => {\n const { role, bbox, cropW, cropH, canvasW, canvasH } = layer;\n const spec = ROLE_TABLE[role];\n const t = bboxToTransform(bbox, canvasW, canvasH, role);\n const roleBindings = bindingsForRole(spec, role, cropW, cropH, {\n hasMouthOpen,\n parallaxUnitY,\n hasNose,\n });\n // `IkiPart.deformer` is optional, so a \"none\" role states its detachment by\n // leaving the field off rather than naming a deformer that must exist.\n const deformerId = spec.deformer === \"none\" ? undefined : spec.deformer;\n\n if (spec.mesh) {\n // Mesh part: width:1, height:1 with a pixel grid mesh centered at the\n // crop center; the engine applies the part transform to position it. A\n // group member renders on the very mesh its carrier was read with.\n const member = memberByRole.get(role);\n const { cols, rows } = meshCellsFor(cropW, cropH);\n const mesh =\n member?.mesh ?? createPixelGridMesh(cols, rows, cropW, cropH);\n const part: IkiPart = {\n id: role,\n color: [1, 1, 1, 1] as [number, number, number, number],\n width: 1,\n height: 1,\n order: spec.order,\n transform: t,\n deformer: deformerId,\n mesh,\n };\n if (roleBindings.length > 0) {\n part.bindings = roleBindings;\n }\n // Hair sway and turn lead: the PhysicsMotion springs lag AngleX / AngleZ\n // onto HairSwayX / HairSwayZ (rigs and params exist only with front\n // hair), and both hair parts swing their ends on them with the roots\n // pinned. The back hair is longer, so the same fraction of its height is\n // a bigger swing.\n if ((role === \"hair_front\" || role === \"hair_back\") && hasHair) {\n // Both hair parts ride the rigid head, with no grid edge under them\n // to clamp a swing, so the full fraction of the height swings on\n // both. Warp order in the array doesn't matter; they sum.\n const tipShift = HAIR_SWAY_TIP_FRACTION * cropH;\n const ownWarps: IkiWarp[] = [];\n if (role === \"hair_front\") {\n // The bangs' turn lead: root-pinned like the sway, keyed to\n // HEAD_TURN_MAX_DEG instead of a sway spring's range — see\n // bindingsForRole's doc for why this is a warp, not a binding.\n ownWarps.push(\n bakeHairSwayWarp(\n mesh,\n StandardParameter.AngleX,\n HAIR_FRONT_DEPTH * parallaxUnit,\n HEAD_TURN_MAX_DEG,\n ),\n );\n // The bangs draw the head's outline, so they hold it through the\n // turn instead of squeezing in with the plate beneath them — their\n // inner strands on the RENDERED plate, their outer ones on the hold\n // edge. The solve already picked the hold's zone for the radius it\n // picked: the head's measured half-width when it had one, and a\n // destination that carries the silhouette ratio through the turn.\n // Without a solve nothing here has measured where the outline\n // actually is, so the hold edge is the outermost the plate ever\n // reaches, clear of it by a pixel, and it holds its rest position:\n // the silhouette stops narrowing without being asked to move. That\n // reach already covers the rest pose (its 0° stop is the identity),\n // and anything inside it would fold the ramp.\n const holdBase =\n turn?.holdBase ??\n plateReach(plateLandingAt, faceCenterX, edgeAt, plateGuardRows) +\n HOLD_CLEARANCE;\n ownWarps.push(\n bakeHairFrontSilhouetteWarp(\n mesh,\n t.x,\n t.y,\n faceCenterX,\n plateLandingAt,\n edgeAt,\n holdBase,\n turn?.holdEdgeAt ?? (() => holdBase),\n plateGuardRows,\n profile?.widestY,\n ),\n );\n // On the nod the bangs slide with the brows they hang over — only\n // when the brows slide, i.e. with a nose to lead them — and bend on\n // the same surface the plate does; see HAIR_FRONT_NOD_DEPTH.\n ownWarps.push(\n bakeNodWarp(\n mesh,\n t.y,\n surface,\n hasNose ? HAIR_FRONT_NOD_DEPTH * parallaxUnitY : 0,\n ),\n );\n }\n part.warps = [\n bakeHairSwayWarp(\n mesh,\n StandardParameter.HairSwayX,\n tipShift,\n HAIR_SWAY_RANGE,\n ),\n bakeHairSwayWarp(\n mesh,\n StandardParameter.HairSwayZ,\n tipShift,\n HAIR_SWAY_RANGE,\n ),\n ...ownWarps,\n ];\n }\n // Eye blink = fold: the white (eye_) and the lash (lash_) fold shut via a\n // warp toward the shared crease — the one their member was built with\n // (eyelidFoldFor), so the grid that bounds them saw the same fold;\n // iris/pupil/highlight clip to the white, so the closing white CUTS them\n // away (round, not squashed) and the lash lands on top to cover the\n // seam. The white is a required role (clip mask exists).\n if (spec.eyeSide !== undefined) {\n if (role.startsWith(\"eye_\") || role.startsWith(\"lash_\")) {\n part.warps = [...member!.warps];\n } else {\n part.clip = { masks: [`eye_${spec.eyeSide}`] };\n }\n }\n return part;\n } else {\n // Static quad: no mesh, sized to the crop. It still takes bindings: no\n // static role carries any today, but this branch once dropped them\n // silently, and the next static role with a binding must not hit that.\n const part: IkiPart = {\n id: role,\n color: [1, 1, 1, 1] as [number, number, number, number],\n width: cropW,\n height: cropH,\n order: spec.order,\n transform: t,\n deformer: deformerId,\n };\n if (roleBindings.length > 0) {\n part.bindings = roleBindings;\n }\n return part;\n }\n });\n\n const model = {\n version: IKI_FORMAT_VERSION,\n name: \"Auto-Rigged Model\",\n canvas: { width: canvas.width, height: canvas.height },\n textures: [],\n parameters,\n deformers,\n parts,\n // Secondary motion: one spring lags AngleX onto HairSwayX so front hair\n // sways behind the head turn (same constants as the hand-authored sample),\n // and a second lags AngleZ onto HairSwayZ so it swings behind a tilt. Two\n // rigs because a rig has one input and one output (validator-enforced).\n // Both hair parts read the outputs through root-pinned sway warps.\n // Omitted when there is no front hair to drive.\n physics: hasHair\n ? [\n {\n id: \"hairSway\",\n input: { parameter: StandardParameter.AngleX, weight: 1 },\n output: { parameter: StandardParameter.HairSwayX, scale: -10 },\n mass: 1,\n stiffness: 80,\n damping: 10,\n },\n {\n id: \"hairTilt\",\n input: { parameter: StandardParameter.AngleZ, weight: 1 },\n output: { parameter: StandardParameter.HairSwayZ, scale: -10 },\n mass: 1,\n stiffness: 80,\n damping: 10,\n },\n ]\n : undefined,\n };\n\n // Gate: run through parseIkiModel so bad assembly fails loudly at the source.\n // structuredClone prevents the validator's normalizing output from aliasing\n // the local object, and ensures the returned model is fully independent.\n return parseIkiModel(structuredClone(model));\n}\n"],"mappings":"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","names":["parseIkiModel","parseIkiModel","y","x","IKI_FORMAT_VERSION","parseIkiModel","depths","p","IKI_FORMAT_VERSION","parseIkiModel"]}