@ikijs/editor 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../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":["export {\n EditorDocument,\n type AtlasAssignment,\n type ApplyAtlasInput,\n} from \"./document\";\nexport {\n AddDeformer,\n AddPart,\n AddPhysicsRig,\n CaptureGridKeyform,\n DeleteDeformer,\n DeletePart,\n DeletePhysicsRig,\n SetDeformerBindings,\n SetDeformerParent,\n SetDeformerPivot,\n SetDeformerPivotX,\n SetDeformerPivotY,\n SetDeformerTransform,\n SetPartBindings,\n SetPartColor,\n SetPartDeformer,\n SetPartMesh,\n SetPartHeight,\n SetPartOrder,\n SetPartTransform,\n SetPartWidth,\n SetPhysicsRig,\n type DeformerTransformChannel,\n type EditCommand,\n type EditTransformChannel,\n} from \"./commands\";\nexport {\n ALPHA_BBOX_THRESHOLD,\n detectAlphaBbox,\n type AlphaBbox,\n} from \"./alpha-bbox\";\nexport { captureBindingEndpoint } from \"./binding-capture\";\nexport {\n computeGridOffsets,\n interpolateGridOffsets,\n upsertGridKeyform,\n} from \"./grid-keyform\";\nexport {\n packAtlas,\n uvRectFor,\n ATLAS_PADDING,\n UV_INSET_PX,\n type AtlasSource,\n type AtlasPlacement,\n type AtlasLayout,\n} from \"./atlas\";\nexport {\n validateDeformerDelete,\n validateDeformerReparent,\n validatePartAttach,\n} from \"./reparent\";\nexport {\n createDefaultPart,\n createDefaultMatrixDeformer,\n createDefaultWarpDeformer,\n createGridMesh,\n} from \"./factories\";\nexport {\n generateIkiFromLayerSet,\n parseLayerRoles,\n type LayerInput,\n} from \"./auto-rig\";\n","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 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\nexport interface RoleSpec {\n /** Which deformer the part is attached to in the generated rig. */\n deformer: \"faceWarp\" | \"headDeformer\";\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\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 // hair_back stays rigid (silhouette behind the face); per-layer front/back\n // depth parallax is a later slice.\n hair_back: { deformer: \"headDeformer\", order: 0, mesh: false },\n face: { deformer: \"faceWarp\", order: 10, mesh: true },\n nose: { deformer: \"faceWarp\", order: 15, mesh: true },\n blush_L: { deformer: \"faceWarp\", order: 20, mesh: true },\n blush_R: { deformer: \"faceWarp\", order: 20, mesh: true },\n mouth: { deformer: \"faceWarp\", order: 25, mesh: true },\n eye_L: { deformer: \"faceWarp\", order: 30, mesh: true, eyeSide: \"L\" },\n eye_R: { deformer: \"faceWarp\", order: 30, mesh: true, eyeSide: \"R\" },\n iris_L: { deformer: \"faceWarp\", order: 31, mesh: true, eyeSide: \"L\" },\n iris_R: { deformer: \"faceWarp\", order: 31, mesh: true, eyeSide: \"R\" },\n pupil_L: { deformer: \"faceWarp\", order: 32, mesh: true, eyeSide: \"L\" },\n pupil_R: { deformer: \"faceWarp\", order: 32, mesh: true, eyeSide: \"R\" },\n highlight_L: { deformer: \"faceWarp\", order: 33, mesh: true, eyeSide: \"L\" },\n highlight_R: { deformer: \"faceWarp\", 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: \"faceWarp\", order: 34, mesh: true, eyeSide: \"L\" },\n lash_R: { deformer: \"faceWarp\", order: 34, mesh: true, eyeSide: \"R\" },\n brow_L: { deformer: \"faceWarp\", order: 40, mesh: true },\n brow_R: { deformer: \"faceWarp\", order: 40, mesh: true },\n // Front hair rides faceWarp (mesh) so it follows the head-turn curvature with\n // the face instead of detaching as a rigid blob; its bbox joins the faceWarp\n // grid union so the grid covers it.\n hair_front: { deformer: \"faceWarp\", 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 *\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 }\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// ── 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 + RADIUS * sin(asin(localX/RADIUS) + theta)\n * dx = xPrime - x, dy = 0\n *\n * At theta=0 the center keyform is all-zero (xPrime === x by identity).\n *\n * RADIUS is derived from the grid's own half-width (same 0.6/0.5 margin ratio\n * as bakeHeadTurnGridWarp in the example app) so asin stays clear of ±1.\n *\n * NOTE: Copy (not import) of the example's bakeHeadTurnGridWarp —\n * `@ikijs/editor` must not depend on the examples directory.\n */\nexport function bakeHeadTurnGridWarpCentered(\n grid: IkiWarpGrid,\n parameter: string,\n centerX: number,\n): IkiGridWarp {\n // Keyform stops (degrees) match ParamAngleX's −30..30 range.\n const ANGLES = [-30, 0, 30] as const;\n // Cylinder radius in MODEL units, derived from the grid's symmetric half-width.\n // Same 0.6/0.5 margin ratio as the local mesh bake — keeps asin clear of ±1.\n const halfWidth = (grid.points[grid.cols * 2] - grid.points[0]) / 2;\n const RADIUS = halfWidth * (0.6 / 0.5);\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\n for (let i = 0; i < pointCount; i++) {\n const x = grid.points[i * 2];\n const localX = x - centerX;\n // Clamp localX/RADIUS to [-1,1] to keep asin defined at boundary points.\n const alpha = Math.asin(Math.max(-1, Math.min(1, localX / RADIUS)));\n const xPrime = centerX + RADIUS * Math.sin(alpha + theta);\n const dx = xPrime - x;\n // dy is zero — cylinder bend only deforms horizontal position.\n offsets.push(dx, 0);\n }\n\n return { value: angleDeg, offsets };\n });\n\n // keyforms are sorted ascending by construction (ANGLES = [-30, 0, 30]).\n return { parameter, keyforms };\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/**\n * Derive the IkiBinding[] for a part from its role spec and crop dimensions.\n *\n * - face, hair_back, blush, nose → no bindings\n * - hair_front: HairSwayX rotate (-8..8) + translateX (-10..10) — secondary-motion\n * sway driven by the PhysicsMotion spring (rig emitted in generateIkiFromLayerSet)\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: MouthOpen scaleY (0 to 3) + MouthForm scaleX (-0.2 to 0.4)\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): 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 if (role === \"mouth\") {\n return [\n // Mouth open: scaleY from 0 (closed, param=0) to 3 (wide open, param=1).\n {\n parameter: StandardParameter.MouthOpen,\n channel: \"scaleY\",\n from: 0,\n to: 3,\n },\n // Mouth form: scaleX from -0.2 (pursed, param=-1) to 0.4 (wide, param=1).\n {\n parameter: StandardParameter.MouthForm,\n channel: \"scaleX\",\n from: -0.2,\n to: 0.4,\n },\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_front\") {\n // Hair sway: ParamHairSwayX (a PhysicsMotion spring output) lags/overshoots\n // the head turn. Fixed magnitudes match the hand-authored sample. hair_back\n // stays rigid (no sway). The PhysicsMotion rig that drives HairSwayX is\n // emitted in generateIkiFromLayerSet when a hair_front layer is present.\n return [\n {\n parameter: StandardParameter.HairSwayX,\n channel: \"rotate\",\n from: -8,\n to: 8,\n },\n {\n parameter: StandardParameter.HairSwayX,\n channel: \"translateX\",\n from: -10,\n to: 10,\n },\n ];\n }\n\n // face, blush_*, nose, hair_back → no bindings\n return [];\n}\n\n// ── bakeEyelidFoldWarp ─────────────────────────────────────────────────────\n\n/** Crease sits this fraction of the eye height BELOW the white's center, and the\n * white keeps this fraction of its height when fully closed (a thin band, not 0\n * so the lash texture in the white art doesn't crush to a single aliased row). */\nconst EYELID_FOLD_CREASE = 0.15;\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// ── 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 (same ids/ranges as sample-model.ts), plus a\n * conditional HairSwayX descriptor + hair-sway physics rig when a hair_front\n * layer is present.\n * - Build headDeformer (matrix, neck pivot, AngleX+Breath bindings) and faceWarp\n * (warp, 4×4, baked cylinder warp center-relative on faceCenterX).\n * - Mesh parts (spec.mesh===true) → width:1, height:1, pixel grid mesh 4×4 + role bindings.\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 */\nexport function generateIkiFromLayerSet(\n layers: LayerInput[],\n canvas: { width: number; height: number },\n): IkiModel {\n // Validate first — never derive anything from unchecked input.\n validateLayerInputs(layers, canvas);\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\n // ── Standard parameters — verbatim from sample-model.ts ──────────────────\n const parameters: 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.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 // Hair-sway output param (physics-driven), declared only when there is front\n // hair to drive — keeps no-hair models free of an unused parameter.\n if (hasHair) {\n parameters.push({\n id: StandardParameter.HairSwayX,\n name: \"Hair Sway X\",\n min: -20,\n max: 20,\n default: 0,\n });\n }\n\n // ── Face layer: derive center and crop for pivot + grid ───────────────────\n const faceLayers = layers.filter((l) => l.role === \"face\");\n // validateLayerInputs guarantees \"face\" is present — safe to assert here.\n const faceLayer = faceLayers[0]!;\n const faceTransform = bboxToTransform(\n faceLayer.bbox,\n faceLayer.canvasW,\n faceLayer.canvasH,\n \"face\",\n );\n // faceCenterX: source-placed face center in model space (unshifted).\n const faceCenterX = faceTransform.x;\n const faceCropH = faceLayer.cropH;\n\n // ── Union bbox of all faceWarp-child layers (model space) ─────────────────\n // All faceWarp-assigned roles have spec.mesh===true (validated by ROLE_TABLE).\n // Each child's model-space extent: transform.{x,y} ± cropW/2, cropH/2\n // (centered pixel mesh convention — part.transform is the crop center).\n const faceWarpLayers = layers.filter(\n (l) => ROLE_TABLE[l.role].deformer === \"faceWarp\",\n );\n\n // Fall back to a full-canvas box only when no faceWarp layers exist (shouldn't\n // happen given required roles, but guards against future role-table changes).\n let unionMinX = -canvas.width / 2;\n let unionMaxX = canvas.width / 2;\n let unionMinY = -canvas.height / 2;\n let unionMaxY = canvas.height / 2;\n\n if (faceWarpLayers.length > 0) {\n const transforms = faceWarpLayers.map((l) =>\n bboxToTransform(l.bbox, l.canvasW, l.canvasH, l.role),\n );\n\n unionMinX = Math.min(\n ...transforms.map((t, i) => t.x - faceWarpLayers[i].cropW / 2),\n );\n unionMaxX = Math.max(\n ...transforms.map((t, i) => t.x + faceWarpLayers[i].cropW / 2),\n );\n unionMinY = Math.min(\n ...transforms.map((t, i) => t.y - faceWarpLayers[i].cropH / 2),\n );\n unionMaxY = Math.max(\n ...transforms.map((t, i) => t.y + faceWarpLayers[i].cropH / 2),\n );\n\n // Expand by 12% margin on each side so no child vertex lands on the grid\n // boundary and gets clamped by bindPointToRestGrid.\n const spanX = unionMaxX - unionMinX;\n const spanY = unionMaxY - unionMinY;\n const MARGIN = 0.12;\n unionMinX -= spanX * MARGIN;\n unionMaxX += spanX * MARGIN;\n unionMinY -= spanY * MARGIN;\n unionMaxY += spanY * MARGIN;\n }\n\n // ── faceWarp grid: symmetric about faceCenterX, spanning the margined union ─\n // Symmetric x so the cylinder axis aligns exactly with the face center.\n // halfW is the larger of the two distances from faceCenterX to the union edges,\n // ensuring the symmetric range [faceCenterX-halfW, faceCenterX+halfW] encloses\n // every child. y-range uses the margined union directly (not symmetric).\n const halfW = Math.max(faceCenterX - unionMinX, unionMaxX - faceCenterX);\n const faceGridMinX = faceCenterX - halfW;\n const faceGridMaxX = faceCenterX + halfW;\n\n const faceGrid = {\n cols: 4,\n rows: 4,\n points: generateGridPoints(\n 4,\n 4,\n faceGridMinX,\n faceGridMaxX,\n unionMinY,\n unionMaxY,\n ),\n };\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 = faceTransform.y - faceCropH / 2;\n const neckPivot = {\n x: faceCenterX,\n y: faceBottom - faceCropH * 0.15, // 15% below face bottom = neck\n };\n\n // ── Bake center-relative head-turn cylinder warp ──────────────────────────\n const faceWarpBake = bakeHeadTurnGridWarpCentered(\n faceGrid,\n StandardParameter.AngleX,\n faceCenterX,\n );\n\n // ── Deformers ─────────────────────────────────────────────────────────────\n const deformers = [\n // headDeformer: rigid matrix rotating/translating the whole head about the\n // neck pivot; bindings mirror sample-model.ts exactly.\n {\n id: \"headDeformer\",\n pivot: neckPivot,\n bindings: [\n {\n parameter: StandardParameter.AngleX,\n channel: \"rotate\" as const,\n from: 6,\n to: -6,\n },\n {\n parameter: StandardParameter.AngleX,\n channel: \"translateX\" as const,\n from: -50,\n to: 50,\n },\n {\n parameter: StandardParameter.Breath,\n channel: \"translateY\" as const,\n from: 0,\n to: -12,\n },\n ],\n },\n // faceWarp: cylinder-bend warp parented to headDeformer; grid is symmetric\n // about faceCenterX so the bake's cylinder axis aligns with the face center.\n {\n kind: \"warp\" as const,\n id: \"faceWarp\",\n parent: \"headDeformer\",\n grid: faceGrid,\n warps: [faceWarpBake],\n },\n ];\n\n // ── Shared closed-eye crease per side ─────────────────────────────────────\n // The white AND the lash fold to the SAME seam (derived from the eye-white\n // center), so the lash lands on top of the cut eyeball and covers it.\n const eyeCreaseBySide: Partial<Record<\"L\" | \"R\", number>> = {};\n for (const layer of layers) {\n const side = ROLE_TABLE[layer.role].eyeSide;\n if ((layer.role === \"eye_L\" || layer.role === \"eye_R\") && side) {\n const ey = bboxToTransform(\n layer.bbox,\n layer.canvasW,\n layer.canvasH,\n layer.role,\n ).y;\n eyeCreaseBySide[side] = ey - EYELID_FOLD_CREASE * layer.cropH;\n }\n }\n\n // ── Parts ─────────────────────────────────────────────────────────────────\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\n if (spec.mesh) {\n // Warp-deformer child: width:1, height:1 with a pixel grid mesh centered\n // at the crop center. The engine applies the part transform to position it.\n const mesh = createPixelGridMesh(4, 4, 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: spec.deformer,\n mesh,\n };\n if (roleBindings.length > 0) {\n part.bindings = roleBindings;\n }\n // Eye blink = fold: the white (eye_) and the lash (lash_) fold shut via a\n // warp toward the shared crease; iris/pupil/highlight clip to the white, so\n // the closing white CUTS them away (round, not squashed) and the lash lands\n // on top to cover the seam. The white is a required role (clip mask exists).\n if (spec.eyeSide !== undefined) {\n const isLash = role.startsWith(\"lash_\");\n if (role.startsWith(\"eye_\") || isLash) {\n const openParam =\n spec.eyeSide === \"L\"\n ? StandardParameter.EyeOpenLeft\n : StandardParameter.EyeOpenRight;\n const creaseWorldY =\n eyeCreaseBySide[spec.eyeSide] ?? t.y - EYELID_FOLD_CREASE * cropH;\n part.warps = [\n bakeEyelidFoldWarp(\n mesh,\n openParam,\n creaseWorldY - t.y,\n isLash ? LASH_FOLD_K : EYELID_FOLD_K,\n ),\n ];\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. Placed on headDeformer.\n return {\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: spec.deformer,\n };\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: a spring lags AngleX onto HairSwayX so front hair sways\n // behind the head turn (same constants as the hand-authored sample). Omitted\n // 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 : 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":["import_format","y","x","import_format"]}
1
+ {"version":3,"sources":["../src/index.ts","../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":["export {\n EditorDocument,\n type AtlasAssignment,\n type ApplyAtlasInput,\n} from \"./document\";\nexport {\n AddDeformer,\n AddPart,\n AddPhysicsRig,\n CaptureGridKeyform,\n DeleteDeformer,\n DeletePart,\n DeletePhysicsRig,\n SetDeformerBindings,\n SetDeformerParent,\n SetDeformerPivot,\n SetDeformerPivotX,\n SetDeformerPivotY,\n SetDeformerTransform,\n SetPartBindings,\n SetPartColor,\n SetPartDeformer,\n SetPartMesh,\n SetPartHeight,\n SetPartOrder,\n SetPartTransform,\n SetPartWidth,\n SetPhysicsRig,\n type DeformerTransformChannel,\n type EditCommand,\n type EditTransformChannel,\n} from \"./commands\";\nexport {\n ALPHA_BBOX_THRESHOLD,\n detectAlphaBbox,\n type AlphaBbox,\n} from \"./alpha-bbox\";\nexport { captureBindingEndpoint } from \"./binding-capture\";\nexport {\n computeGridOffsets,\n interpolateGridOffsets,\n upsertGridKeyform,\n} from \"./grid-keyform\";\nexport {\n packAtlas,\n uvRectFor,\n ATLAS_PADDING,\n UV_INSET_PX,\n type AtlasSource,\n type AtlasPlacement,\n type AtlasLayout,\n} from \"./atlas\";\nexport {\n validateDeformerDelete,\n validateDeformerReparent,\n validatePartAttach,\n} from \"./reparent\";\nexport {\n createDefaultPart,\n createDefaultMatrixDeformer,\n createDefaultWarpDeformer,\n createGridMesh,\n} from \"./factories\";\nexport {\n generateIkiFromLayerSet,\n parseLayerRoles,\n type LayerInput,\n} from \"./auto-rig\";\n","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 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\nexport interface RoleSpec {\n /**\n * Which deformer the part is attached to in the generated rig. `\"none\"`\n * attaches it to nothing, so the part holds still in world space while\n * headDeformer turns the head — what a torso has to do.\n */\n deformer: \"faceWarp\" | \"headDeformer\" | \"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\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: it bends on\n // the turn through its own part warp (bakeHairBackTurnWarp) and swings its\n // ends on the hair-sway warps.\n hair_back: { deformer: \"headDeformer\", order: 0, mesh: true },\n // The torso. Alone among the roles it hangs from NO deformer: the head turns\n // about the neck pivot while the shoulders stay put, which is what keeps the\n // character from reading as a floating head. Drawn over the back hair so long\n // hair falls behind the shoulders.\n body: { deformer: \"none\", order: 5, mesh: false },\n face: { deformer: \"faceWarp\", order: 10, mesh: true },\n nose: { deformer: \"faceWarp\", order: 15, mesh: true },\n blush_L: { deformer: \"faceWarp\", order: 20, mesh: true },\n blush_R: { deformer: \"faceWarp\", order: 20, mesh: true },\n mouth: { deformer: \"faceWarp\", 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: \"faceWarp\", order: 26, mesh: true },\n eye_L: { deformer: \"faceWarp\", order: 30, mesh: true, eyeSide: \"L\" },\n eye_R: { deformer: \"faceWarp\", order: 30, mesh: true, eyeSide: \"R\" },\n iris_L: { deformer: \"faceWarp\", order: 31, mesh: true, eyeSide: \"L\" },\n iris_R: { deformer: \"faceWarp\", order: 31, mesh: true, eyeSide: \"R\" },\n pupil_L: { deformer: \"faceWarp\", order: 32, mesh: true, eyeSide: \"L\" },\n pupil_R: { deformer: \"faceWarp\", order: 32, mesh: true, eyeSide: \"R\" },\n highlight_L: { deformer: \"faceWarp\", order: 33, mesh: true, eyeSide: \"L\" },\n highlight_R: { deformer: \"faceWarp\", 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: \"faceWarp\", order: 34, mesh: true, eyeSide: \"L\" },\n lash_R: { deformer: \"faceWarp\", order: 34, mesh: true, eyeSide: \"R\" },\n brow_L: { deformer: \"faceWarp\", order: 40, mesh: true },\n brow_R: { deformer: \"faceWarp\", order: 40, mesh: true },\n // Front hair rides faceWarp (mesh) so it follows the head-turn curvature with\n // the face instead of detaching as a rigid blob; its bbox joins the faceWarp\n // grid union so the grid covers it.\n hair_front: { deformer: \"faceWarp\", 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 *\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 }\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// ── Head-turn cylinder constants ─────────────────────────────────────────────\n\n/** Cylinder radius as a multiple of the warp grid's half-width. The 0.6/0.5\n * margin ratio matches the local mesh bake and keeps asin clear of +/-1. */\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/**\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` that `bakeHeadTurnGridWarpCentered`\n * subtracts back out of the face warp. Pinning it there was right: applied to\n * the face it shoved the head off the shoulders. But it is also the whole depth\n * cue, so layers that do NOT sit on the face plane have to get their own share\n * of it back, scaled by how far in front of (or behind) the axis they sit.\n */\nexport function headTurnParallaxUnit(gridHalfWidth: number): number {\n return (\n gridHalfWidth *\n HEAD_CYLINDER_RADIUS_FACTOR *\n Math.sin(HEAD_TURN_MAX_DEG * (Math.PI / 180))\n );\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 + RADIUS * sin(asin(localX/RADIUS) + 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 * RADIUS is derived from the grid's own half-width (same 0.6/0.5 margin ratio\n * as bakeHeadTurnGridWarp in the example app) so asin stays clear of ±1.\n *\n * NOTE: Copy (not import) of the example's bakeHeadTurnGridWarp —\n * `@ikijs/editor` must not depend on the examples directory.\n */\nexport function bakeHeadTurnGridWarpCentered(\n grid: IkiWarpGrid,\n parameter: string,\n centerX: number,\n): IkiGridWarp {\n // Keyform stops (degrees) match ParamAngleX's −30..30 range.\n const ANGLES = [-HEAD_TURN_MAX_DEG, 0, HEAD_TURN_MAX_DEG] as const;\n // Cylinder radius in MODEL units, derived from the grid's symmetric half-width.\n const halfWidth = (grid.points[grid.cols * 2] - grid.points[0]) / 2;\n const RADIUS = halfWidth * HEAD_CYLINDER_RADIUS_FACTOR;\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 = pinnedCylinderBend(\n grid.points[i * 2] - centerX,\n RADIUS,\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 (ANGLES = [-30, 0, 30]).\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 stays on headDeformer's own translate\n * bindings, where it can be tuned independently, and the hair layers 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 * Bake the head turn AND nod as one 2D grid warp over AngleX × AngleY.\n *\n * The same pinned cylinder bend as `bakeHeadTurnGridWarpCentered`, applied per\n * axis: dx from the horizontal bend at valuesX[i], dy from a vertical bend at\n * valuesY[j] about `centerY`. The axes are independent (dx depends only on x\n * and the yaw, dy only on y and the pitch), which is the same separable\n * convention the playground's 2D bake ships; the row at AngleY=0 is exactly the\n * 1D bake.\n *\n * Y uses its own radius from the grid's vertical extent about `centerY`, with\n * the same margin factor, so the same no-fold guarantee holds: |local|/radius\n * stays ≤ 1/1.2 and asin(1/1.2) + 30° < 90°. The pitch itself is scaled by\n * NOD_BEND — see that constant for why a full nod is not a full 30° bend.\n *\n * Layout is the format's row-major `k(i, j) = j * valuesX.length + i`.\n */\nexport function bakeHeadTurnGridWarp2DCentered(\n grid: IkiWarpGrid,\n parameterX: string,\n parameterY: string,\n centerX: number,\n centerY: number,\n): IkiGrid2DWarp {\n const STOPS = [-HEAD_TURN_MAX_DEG, 0, HEAD_TURN_MAX_DEG];\n const halfWidth = (grid.points[grid.cols * 2] - grid.points[0]) / 2;\n const RADIUS_X = halfWidth * HEAD_CYLINDER_RADIUS_FACTOR;\n\n const pointCount = grid.points.length / 2;\n let halfHeight = 0;\n for (let i = 0; i < pointCount; i++) {\n halfHeight = Math.max(\n halfHeight,\n Math.abs(grid.points[i * 2 + 1] - centerY),\n );\n }\n const RADIUS_Y = halfHeight * HEAD_CYLINDER_RADIUS_FACTOR;\n\n const DEG_TO_RAD = Math.PI / 180;\n const keyforms2d: { offsets: number[] }[] = [];\n for (const angleY of STOPS) {\n const thetaY = angleY * NOD_BEND * DEG_TO_RAD;\n for (const angleX of STOPS) {\n const thetaX = angleX * DEG_TO_RAD;\n const offsets: number[] = [];\n for (let i = 0; i < pointCount; i++) {\n offsets.push(\n pinnedCylinderBend(grid.points[i * 2] - centerX, RADIUS_X, thetaX),\n pinnedCylinderBend(\n grid.points[i * 2 + 1] - centerY,\n RADIUS_Y,\n thetaY,\n ),\n );\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// ── 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 each hair layer from the head cylinder's axis, as a fraction\n * of the cylinder radius; positive is toward the viewer. Tuned by eye against\n * the rendered turn, not derived.\n *\n * The bangs lead the face by a little. 0.16 was right while the back hair\n * stood still and the lead was the only depth cue; once the back hair bent\n * and bulged on the turn the same lead read as the bangs running ahead of\n * the head, so it came down. At 0.06 the layering all but vanishes.\n *\n * hair_back follows the head at about 60% of its travel (its counter-shift\n * takes ~20px off headDeformer's +50px). A deeper value that held the back of\n * the head still in world space read as the face sliding over a backdrop; the\n * turn cue for the back hair comes from its bend (bakeHairBackTurnWarp), not\n * from lagging the head. */\nconst HAIR_FRONT_DEPTH = 0.1;\nconst HAIR_BACK_DEPTH = -0.08;\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 face-warp grid reaches up to the hair crown, which\n * sits near 45° on the vertical cylinder; 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 the vertical\n * parallax unit. The bangs already ride the vertical bend, so they get no\n * extra lead on the nod — with one they lifted clear off the brows. The back\n * hair, a rigid quad, only needs to follow the bent crown down so it stays\n * tucked beneath it. */\nconst HAIR_BACK_NOD_DEPTH = -0.07;\n\n/**\n * Derive the IkiBinding[] for a part from its role spec and crop dimensions.\n *\n * - face, blush, nose → no bindings\n * - hair_front: the AngleX depth-parallax translateX (needs `parallaxUnit`).\n * Its sway is NOT a binding — a rotate would pivot the bangs about their\n * centre — but a root-pinned warp, attached in generateIkiFromLayerSet.\n * - hair_back: the AngleX depth-parallax translateX, and an AngleY translateY\n * that tucks its crown under the bent front hair (needs `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: MouthOpen scaleY (0 to 3) + MouthForm scaleX (-0.2 to 0.4)\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 parallaxUnit?: number;\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_front\" || role === \"hair_back\") {\n const isFront = role === \"hair_front\";\n // Depth parallax on the head turn. Both hair layers ride the head but\n // neither sits on the face plane, so neither may track it exactly: the\n // bangs lead the face and the back hair swings against it. Without this the\n // hair is glued flat to the face and the turn reads as a cutout sliding.\n // Shifting the mesh moves it INSIDE the warp grid (applyWarpToChild\n // transforms before it binds), so the shift has to stay within the grid's\n // 12% margin. For a union centered on the face that margin is about twice\n // the shift; the generated-model test pins the real headroom.\n const depth = isFront ? HAIR_FRONT_DEPTH : HAIR_BACK_DEPTH;\n const parallax: IkiBinding[] = [];\n const shiftX = depth * (options.parallaxUnit ?? 0);\n if (shiftX !== 0) {\n parallax.push({\n parameter: StandardParameter.AngleX,\n channel: \"translateX\",\n from: -shiftX,\n to: shiftX,\n });\n }\n // On the nod only the rigid back hair moves; see HAIR_BACK_NOD_DEPTH.\n const shiftY =\n (isFront ? 0 : HAIR_BACK_NOD_DEPTH) * (options.parallaxUnitY ?? 0);\n if (shiftY !== 0) {\n parallax.push({\n parameter: StandardParameter.AngleY,\n channel: \"translateY\",\n from: -shiftY,\n to: shiftY,\n });\n }\n return parallax;\n }\n\n // face, blush_*, nose → no bindings\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// ── bakeHairBackTurnWarp ──────────────────────────────────────────────────────\n\n/** Cylinder radius for the back hair's turn bend, as a multiple of the part's\n * own half-width. Much flatter than the face's 1.2: the back hair spans the\n * whole head, and at the face's curvature its near edge would fold in by\n * ~180px. At 2.5 the near side tucks behind the face and the far side fills\n * out, which is the whole cue. */\nconst HAIR_BACK_BEND_RADIUS_FACTOR = 2.5;\n/** How far the back hair's far edge bulges OUT at full turn, as a fraction of\n * the part's half-width. A cylinder bend barely moves the far edge (it just\n * stops compressing), but on a real head the hair volume hidden behind the\n * far side swings into view and the silhouette fills out. Grows linearly\n * from the centre column to the far edge. Judged from renders: at ~0.32 the\n * far strands look pulled thin. */\nconst HAIR_BACK_FAR_BULGE = 0.22;\n\n/**\n * The back hair's share of the head turn, as a per-vertex warp on AngleX.\n *\n * hair_back hangs from the rigid headDeformer, not faceWarp, so without this it\n * turned as a flat sheet: the face foreshortened and slid while the silhouette\n * behind it kept its rest outline. This runs the same pinned cylinder bend the\n * face uses over the part's own columns, at a flatter radius, plus a far-side\n * bulge (HAIR_BACK_FAR_BULGE): on a turn the near side compresses behind the\n * face and the far side swings out and fills.\n */\nexport function bakeHairBackTurnWarp(\n mesh: IkiMesh,\n parameter: string,\n): IkiWarp {\n let left = Infinity;\n let right = -Infinity;\n for (let i = 0; i < mesh.vertices.length; i += 2) {\n left = Math.min(left, mesh.vertices[i]);\n right = Math.max(right, mesh.vertices[i]);\n }\n const halfWidth = (right - left) / 2;\n const radius = halfWidth * HAIR_BACK_BEND_RADIUS_FACTOR;\n const bulge = HAIR_BACK_FAR_BULGE * halfWidth;\n const DEG_TO_RAD = Math.PI / 180;\n const keyforms = [-HEAD_TURN_MAX_DEG, 0, HEAD_TURN_MAX_DEG].map((deg) => {\n const theta = deg * DEG_TO_RAD;\n // Turning right (s = +1) the far side is x < 0; the bulge pushes it further\n // left, i.e. outward. Full strength at the keyform stops, zero at rest.\n const s = Math.sign(deg);\n const offsets: number[] = [];\n for (let i = 0; i < mesh.vertices.length; i += 2) {\n const x = mesh.vertices[i];\n const far = Math.max(0, (-s * x) / halfWidth);\n offsets.push(pinnedCylinderBend(x, radius, theta) - s * bulge * far, 0);\n }\n return { value: deg, offsets };\n });\n return { parameter, 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 */\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// ── 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 (same ids/ranges as sample-model.ts), plus a\n * conditional HairSwayX descriptor + hair-sway physics rig when a hair_front\n * layer is present.\n * - Build headDeformer (matrix, neck pivot, AngleX+Breath bindings) and faceWarp\n * (warp, 4×4, baked cylinder warp center-relative on faceCenterX).\n * - Mesh parts (spec.mesh===true) → width:1, height:1, pixel grid mesh 4×4 + role bindings.\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 */\nexport function generateIkiFromLayerSet(\n layers: LayerInput[],\n canvas: { width: number; height: number },\n): IkiModel {\n // Validate first — never derive anything from unchecked input.\n validateLayerInputs(layers, canvas);\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 hasMouthOpen = layers.some((l) => l.role === \"mouth_open\");\n\n // ── Standard parameters — verbatim from sample-model.ts ──────────────────\n const parameters: 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 // 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 // ── Face layer: derive center and crop for pivot + grid ───────────────────\n const faceLayers = layers.filter((l) => l.role === \"face\");\n // validateLayerInputs guarantees \"face\" is present — safe to assert here.\n const faceLayer = faceLayers[0]!;\n const faceTransform = bboxToTransform(\n faceLayer.bbox,\n faceLayer.canvasW,\n faceLayer.canvasH,\n \"face\",\n );\n // faceCenterX: source-placed face center in model space (unshifted).\n const faceCenterX = faceTransform.x;\n const faceCropH = faceLayer.cropH;\n\n // ── Union bbox of all faceWarp-child layers (model space) ─────────────────\n // All faceWarp-assigned roles have spec.mesh===true (validated by ROLE_TABLE).\n // Each child's model-space extent: transform.{x,y} ± cropW/2, cropH/2\n // (centered pixel mesh convention — part.transform is the crop center).\n const faceWarpLayers = layers.filter(\n (l) => ROLE_TABLE[l.role].deformer === \"faceWarp\",\n );\n\n // Fall back to a full-canvas box only when no faceWarp layers exist (shouldn't\n // happen given required roles, but guards against future role-table changes).\n let unionMinX = -canvas.width / 2;\n let unionMaxX = canvas.width / 2;\n let unionMinY = -canvas.height / 2;\n let unionMaxY = canvas.height / 2;\n\n if (faceWarpLayers.length > 0) {\n const transforms = faceWarpLayers.map((l) =>\n bboxToTransform(l.bbox, l.canvasW, l.canvasH, l.role),\n );\n\n unionMinX = Math.min(\n ...transforms.map((t, i) => t.x - faceWarpLayers[i].cropW / 2),\n );\n unionMaxX = Math.max(\n ...transforms.map((t, i) => t.x + faceWarpLayers[i].cropW / 2),\n );\n unionMinY = Math.min(\n ...transforms.map((t, i) => t.y - faceWarpLayers[i].cropH / 2),\n );\n unionMaxY = Math.max(\n ...transforms.map((t, i) => t.y + faceWarpLayers[i].cropH / 2),\n );\n\n // Expand by 12% margin on each side so no child vertex lands on the grid\n // boundary and gets clamped by bindPointToRestGrid.\n const spanX = unionMaxX - unionMinX;\n const spanY = unionMaxY - unionMinY;\n const MARGIN = 0.12;\n unionMinX -= spanX * MARGIN;\n unionMaxX += spanX * MARGIN;\n unionMinY -= spanY * MARGIN;\n unionMaxY += spanY * MARGIN;\n }\n\n // ── faceWarp grid: symmetric about faceCenterX, spanning the margined union ─\n // Symmetric x so the cylinder axis aligns exactly with the face center.\n // halfW is the larger of the two distances from faceCenterX to the union edges,\n // ensuring the symmetric range [faceCenterX-halfW, faceCenterX+halfW] encloses\n // every child. y-range uses the margined union directly (not symmetric).\n const halfW = Math.max(faceCenterX - unionMinX, unionMaxX - faceCenterX);\n const faceGridMinX = faceCenterX - halfW;\n const faceGridMaxX = faceCenterX + halfW;\n\n const faceGrid = {\n cols: 4,\n rows: 4,\n points: generateGridPoints(\n 4,\n 4,\n faceGridMinX,\n faceGridMaxX,\n unionMinY,\n unionMaxY,\n ),\n };\n\n // Depth-parallax units for the hair layers, from the same extents the\n // cylinder bake derives its radii from: half-width about the face center for\n // the turn, the larger vertical reach about it for the nod.\n const faceCenterY = faceTransform.y;\n const halfH = Math.max(faceCenterY - unionMinY, unionMaxY - faceCenterY);\n const parallaxUnit = headTurnParallaxUnit(halfW);\n const parallaxUnitY = headTurnParallaxUnit(halfH);\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 = faceTransform.y - faceCropH / 2;\n const neckPivot = {\n x: faceCenterX,\n y: faceBottom - faceCropH * 0.15, // 15% below face bottom = neck\n };\n\n // ── Bake the center-relative turn × nod cylinder warp ─────────────────────\n const faceWarp2d = bakeHeadTurnGridWarp2DCentered(\n faceGrid,\n StandardParameter.AngleX,\n StandardParameter.AngleY,\n faceCenterX,\n faceCenterY,\n );\n\n // ── Deformers ─────────────────────────────────────────────────────────────\n const deformers = [\n // headDeformer: rigid matrix rotating/translating the whole head about the\n // neck pivot; bindings mirror sample-model.ts exactly.\n {\n id: \"headDeformer\",\n pivot: neckPivot,\n bindings: [\n {\n parameter: StandardParameter.AngleX,\n channel: \"rotate\" as const,\n from: 6,\n to: -6,\n },\n {\n parameter: StandardParameter.AngleX,\n channel: \"translateX\" as const,\n from: -50,\n to: 50,\n },\n // Nod: a vertical translate only. No rotate — a pitch expressed as a\n // rigid rotation would sum with the roll below at diagonal poses,\n // 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, and\n // the same sense as the lean above: in Live2D's own sample motions\n // AngleZ carries the sign of AngleX 214 times out of 222. The two\n // rotations sum, which is correct for two rolls about one pivot.\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: -12,\n },\n ],\n },\n // faceWarp: cylinder-bend warp parented to headDeformer; grid is symmetric\n // about faceCenterX so the bake's cylinder axis aligns with the face center.\n // One 2D warp carries both the turn and the nod (a deformer holds either\n // `warps` or `warp2d`, never both).\n {\n kind: \"warp\" as const,\n id: \"faceWarp\",\n parent: \"headDeformer\",\n grid: faceGrid,\n warp2d: faceWarp2d,\n },\n ];\n\n // ── Shared closed-eye crease per side ─────────────────────────────────────\n // The white AND the lash fold to the SAME seam (derived from the eye-white\n // center), so the lash lands on top of the cut eyeball and covers it.\n const eyeCreaseBySide: Partial<Record<\"L\" | \"R\", number>> = {};\n for (const layer of layers) {\n const side = ROLE_TABLE[layer.role].eyeSide;\n if ((layer.role === \"eye_L\" || layer.role === \"eye_R\") && side) {\n const ey = bboxToTransform(\n layer.bbox,\n layer.canvasW,\n layer.canvasH,\n layer.role,\n ).y;\n eyeCreaseBySide[side] = ey - EYELID_FOLD_CREASE * layer.cropH;\n }\n }\n\n // ── Parts ─────────────────────────────────────────────────────────────────\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 parallaxUnit,\n parallaxUnitY,\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 // Warp-deformer child: width:1, height:1 with a pixel grid mesh centered\n // at the crop center. The engine applies the part transform to position it.\n const mesh = createPixelGridMesh(4, 4, 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: the PhysicsMotion springs lag AngleX / AngleZ onto\n // HairSwayX / HairSwayZ (rigs and params exist only with front hair), and\n // both hair parts swing their ends on them with the roots pinned. The back\n // hair is longer, so the same fraction of its height is a bigger swing.\n if ((role === \"hair_front\" || role === \"hair_back\") && hasHair) {\n const tipShift = HAIR_SWAY_TIP_FRACTION * cropH;\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 ];\n }\n // The back hair's turn: it gets no cylinder bend from a deformer, so it\n // bends on its own. Part warps sum, so this sits beside the sway.\n if (role === \"hair_back\") {\n part.warps = [\n ...(part.warps ?? []),\n bakeHairBackTurnWarp(mesh, StandardParameter.AngleX),\n ];\n }\n // Eye blink = fold: the white (eye_) and the lash (lash_) fold shut via a\n // warp toward the shared crease; iris/pupil/highlight clip to the white, so\n // the closing white CUTS them away (round, not squashed) and the lash lands\n // on top to cover the seam. The white is a required role (clip mask exists).\n if (spec.eyeSide !== undefined) {\n const isLash = role.startsWith(\"lash_\");\n if (role.startsWith(\"eye_\") || isLash) {\n const openParam =\n spec.eyeSide === \"L\"\n ? StandardParameter.EyeOpenLeft\n : StandardParameter.EyeOpenRight;\n const creaseWorldY =\n eyeCreaseBySide[spec.eyeSide] ?? t.y - EYELID_FOLD_CREASE * cropH;\n // Separate lashes supply the closed-eye line. Collapse their sclera\n // clip completely so a strip of iris cannot show underneath.\n const hasLash = layers.some((l) => l.role === `lash_${spec.eyeSide}`);\n part.warps = [\n bakeEyelidFoldWarp(\n mesh,\n openParam,\n creaseWorldY - t.y,\n isLash ? LASH_FOLD_K : hasLash ? 0 : EYELID_FOLD_K,\n ),\n ];\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":["import_format","y","x","import_format"]}