@forgeax/engine-animation 0.1.2
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/LICENSE +202 -0
- package/README.md +141 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/animation-diagnostic.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-diagnostic.unit.test.d.ts.map +1 -0
- package/dist/__tests__/animation-recovery.integration.test.d.ts +2 -0
- package/dist/__tests__/animation-recovery.integration.test.d.ts.map +1 -0
- package/dist/__tests__/animation-target-id.browser.test.d.ts +2 -0
- package/dist/__tests__/animation-target-id.browser.test.d.ts.map +1 -0
- package/dist/__tests__/animation-target-id.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-target-id.unit.test.d.ts.map +1 -0
- package/dist/__tests__/animation-target-isolation.integration.test.d.ts +2 -0
- package/dist/__tests__/animation-target-isolation.integration.test.d.ts.map +1 -0
- package/dist/__tests__/animation-target-relationship.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-target-relationship.unit.test.d.ts.map +1 -0
- package/dist/__tests__/animation-target-source-boundary.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-target-source-boundary.unit.test.d.ts.map +1 -0
- package/dist/__tests__/bind-animation-targets.unit.test.d.ts +2 -0
- package/dist/__tests__/bind-animation-targets.unit.test.d.ts.map +1 -0
- package/dist/__tests__/documentation-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/documentation-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ecs-consumer.integration.test.d.ts +2 -0
- package/dist/__tests__/ecs-consumer.integration.test.d.ts.map +1 -0
- package/dist/__tests__/errors.unit.test.d.ts +2 -0
- package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/graph-player.integration.test.d.ts +2 -0
- package/dist/__tests__/graph-player.integration.test.d.ts.map +1 -0
- package/dist/__tests__/graph-player.unit.test.d.ts +2 -0
- package/dist/__tests__/graph-player.unit.test.d.ts.map +1 -0
- package/dist/__tests__/handle-errors.unit.test.d.ts +2 -0
- package/dist/__tests__/handle-errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/loader-boundary.unit.test.d.ts +2 -0
- package/dist/__tests__/loader-boundary.unit.test.d.ts.map +1 -0
- package/dist/__tests__/morph-weights-playback.integration.test.d.ts +2 -0
- package/dist/__tests__/morph-weights-playback.integration.test.d.ts.map +1 -0
- package/dist/__tests__/morph-weights.unit.test.d.ts +2 -0
- package/dist/__tests__/morph-weights.unit.test.d.ts.map +1 -0
- package/dist/__tests__/public-animation-target-api.test-d.d.ts +2 -0
- package/dist/__tests__/public-animation-target-api.test-d.d.ts.map +1 -0
- package/dist/__tests__/resolver-ownership.integration.test.d.ts +2 -0
- package/dist/__tests__/resolver-ownership.integration.test.d.ts.map +1 -0
- package/dist/__tests__/root-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/root-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-instance-target-collection.integration.test.d.ts +2 -0
- package/dist/__tests__/scene-instance-target-collection.integration.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-consumer.test-d.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-consumer.test-d.d.ts.map +1 -0
- package/dist/__tests__/time-delta-regression.integration.test.d.ts +2 -0
- package/dist/__tests__/time-delta-regression.integration.test.d.ts.map +1 -0
- package/dist/__tests__/transform-target-playback.integration.test.d.ts +2 -0
- package/dist/__tests__/transform-target-playback.integration.test.d.ts.map +1 -0
- package/dist/animation-diagnostic.d.ts +27 -0
- package/dist/animation-diagnostic.d.ts.map +1 -0
- package/dist/animation-player.d.ts +13 -0
- package/dist/animation-player.d.ts.map +1 -0
- package/dist/animation-target.d.ts +21 -0
- package/dist/animation-target.d.ts.map +1 -0
- package/dist/assets/animation-decoder.d.ts +4 -0
- package/dist/assets/animation-decoder.d.ts.map +1 -0
- package/dist/chunk-DMXAQUPI.mjs +52 -0
- package/dist/chunk-DMXAQUPI.mjs.map +1 -0
- package/dist/errors.d.ts +120 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/graph/define-animation-graph.d.ts +42 -0
- package/dist/graph/define-animation-graph.d.ts.map +1 -0
- package/dist/graph/describe-animation-graph.d.ts +37 -0
- package/dist/graph/describe-animation-graph.d.ts.map +1 -0
- package/dist/graph/serialize-animation-graph.d.ts +27 -0
- package/dist/graph/serialize-animation-graph.d.ts.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +1457 -0
- package/dist/index.mjs.map +1 -0
- package/dist/player-errors.d.ts +43 -0
- package/dist/player-errors.d.ts.map +1 -0
- package/dist/plugin.d.ts +13 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/resolve-animation-asset.d.ts +42 -0
- package/dist/resolve-animation-asset.d.ts.map +1 -0
- package/dist/systems/advance-animation-player.d.ts +40 -0
- package/dist/systems/advance-animation-player.d.ts.map +1 -0
- package/dist/systems/evaluate-animation-graph.d.ts +32 -0
- package/dist/systems/evaluate-animation-graph.d.ts.map +1 -0
- package/dist/target-id.d.ts +4 -0
- package/dist/target-id.d.ts.map +1 -0
- package/dist/target-id.mjs +9 -0
- package/dist/target-id.mjs.map +1 -0
- package/package.json +65 -0
- package/src/__tests__/animation-diagnostic.unit.test.ts +237 -0
- package/src/__tests__/animation-recovery.integration.test.ts +125 -0
- package/src/__tests__/animation-target-id.browser.test.ts +22 -0
- package/src/__tests__/animation-target-id.unit.test.ts +41 -0
- package/src/__tests__/animation-target-isolation.integration.test.ts +179 -0
- package/src/__tests__/animation-target-relationship.unit.test.ts +65 -0
- package/src/__tests__/animation-target-source-boundary.unit.test.ts +49 -0
- package/src/__tests__/bind-animation-targets.unit.test.ts +167 -0
- package/src/__tests__/documentation-contract.unit.test.ts +94 -0
- package/src/__tests__/ecs-consumer.integration.test.ts +10 -0
- package/src/__tests__/errors.unit.test.ts +19 -0
- package/src/__tests__/graph-player.integration.test.ts +22 -0
- package/src/__tests__/graph-player.unit.test.ts +12 -0
- package/src/__tests__/handle-errors.unit.test.ts +17 -0
- package/src/__tests__/loader-boundary.unit.test.ts +66 -0
- package/src/__tests__/morph-weights-playback.integration.test.ts +84 -0
- package/src/__tests__/morph-weights.unit.test.ts +26 -0
- package/src/__tests__/public-animation-target-api.test-d.ts +47 -0
- package/src/__tests__/resolver-ownership.integration.test.ts +99 -0
- package/src/__tests__/root-surface.unit.test.ts +10 -0
- package/src/__tests__/scene-instance-target-collection.integration.test.ts +26 -0
- package/src/__tests__/scriptable-pack-consumer.test-d.ts +8 -0
- package/src/__tests__/time-delta-regression.integration.test.ts +79 -0
- package/src/__tests__/transform-target-playback.integration.test.ts +198 -0
- package/src/animation-diagnostic.ts +88 -0
- package/src/animation-player.ts +84 -0
- package/src/animation-target.ts +257 -0
- package/src/assets/animation-decoder.ts +107 -0
- package/src/errors.ts +201 -0
- package/src/graph/define-animation-graph.ts +193 -0
- package/src/graph/describe-animation-graph.ts +74 -0
- package/src/graph/serialize-animation-graph.ts +90 -0
- package/src/index.ts +57 -0
- package/src/player-errors.ts +72 -0
- package/src/plugin.ts +73 -0
- package/src/resolve-animation-asset.ts +128 -0
- package/src/systems/advance-animation-player.ts +971 -0
- package/src/systems/evaluate-animation-graph.ts +261 -0
- package/src/target-id.ts +51 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/animation-diagnostic.ts","../src/animation-player.ts","../src/animation-target.ts","../src/assets/animation-decoder.ts","../src/graph/define-animation-graph.ts","../src/errors.ts","../src/graph/describe-animation-graph.ts","../src/graph/serialize-animation-graph.ts","../src/systems/advance-animation-player.ts","../src/player-errors.ts","../src/systems/evaluate-animation-graph.ts","../src/resolve-animation-asset.ts","../src/plugin.ts"],"sourcesContent":["import type { World } from '@forgeax/engine-ecs';\n\nexport type AnimationDiagnosticCode =\n | 'animation-target-missing'\n | 'animation-target-transform-missing'\n | 'animation-target-id-duplicate'\n | 'animation-target-owner-stale'\n | 'animation-channel-missing'\n | 'animation-target-morph-weights-missing'\n | 'animation-morph-weight-count-mismatch';\n\nexport interface AnimationDiagnosticDetail {\n readonly player: number;\n readonly clip: number;\n readonly channel: number;\n readonly targetId: string;\n readonly reason:\n | 'target-missing'\n | 'transform-missing'\n | 'target-id-duplicate'\n | 'target-stale'\n | 'channel-missing'\n | 'morph-weights-missing'\n | 'morph-weight-count-mismatch';\n readonly target?: number;\n readonly property?: 'translation' | 'rotation' | 'scale' | 'weights';\n readonly expectedWeightCount?: number;\n readonly actualWeightCount?: number;\n}\n\nexport interface AnimationDiagnostic {\n readonly code: AnimationDiagnosticCode;\n readonly hint: string;\n readonly detail: Readonly<AnimationDiagnosticDetail>;\n}\n\nexport type AnimationDiagnosticListener = (\n world: World,\n diagnostic: Readonly<AnimationDiagnostic>,\n) => void;\n\nconst emittedKeysByWorld = new WeakMap<World, Set<string>>();\nconst listeners = new Set<AnimationDiagnosticListener>();\n\n/** Observe unique channel-level diagnostic facts for editor/runtime projection. */\nexport function subscribeAnimationDiagnostics(listener: AnimationDiagnosticListener): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\n/** @internal */\nexport function isAnimationDevMode(): boolean {\n const proc = (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process;\n if (proc?.env?.NODE_ENV === 'production') return false;\n return Boolean((import.meta as { env?: { DEV?: unknown } }).env?.DEV) || proc !== undefined;\n}\n\nexport function emitAnimationDiagnostic(world: World, diagnostic: AnimationDiagnostic): void {\n if (!isAnimationDevMode()) return;\n let emitted = emittedKeysByWorld.get(world);\n if (emitted === undefined) {\n emitted = new Set();\n emittedKeysByWorld.set(world, emitted);\n }\n const { player, clip, channel, targetId, reason } = diagnostic.detail;\n const emittedKey = `${player}|${clip}|${channel}|${targetId}|${reason}`;\n if (emitted.has(emittedKey)) return;\n emitted.add(emittedKey);\n\n const frozen = Object.freeze({\n ...diagnostic,\n detail: Object.freeze({ ...diagnostic.detail }),\n });\n for (const listener of listeners) {\n try {\n listener(world, frozen);\n } catch {\n // Diagnostics must never break animation evaluation.\n }\n }\n\n console.warn(frozen);\n}\n\n/** @internal */\nexport function _resetAnimationWarnsForTests(world: World): void {\n emittedKeysByWorld.delete(world);\n}\n","// @forgeax/engine-animation - AnimationPlayer component (variable N-way SoA slots).\n//\n// Schema (10 fields, SoA variable arrays):\n// clips: 'array<shared<AnimationClip>>' (simulation-transient asset binding)\n// times: 'array<f32>' (layer-3 zero — empty Float32Array)\n// weights: 'array<f32>' (layer-3 zero — empty Float32Array)\n// speeds: 'array<f32>' (layer-3 zero — empty Float32Array)\n// graph: 'shared<AnimationGraph>' (layer-3 zero — 0, no graph)\n// nodeWeights: 'array<f32>' (layer-3 zero — empty Float32Array)\n// nodeTimes: 'array<f32>' (layer-3 zero — empty Float32Array)\n// nodeSpeeds: 'array<f32>' (layer-3 zero — empty Float32Array)\n// paused: 'bool' (layer-2 false)\n// looping: 'bool' (layer-2 true)\n//\n// The four parallel columns support up to N concurrent clips for crossfade\n// blending — the fixed 4-slot cap is retired (feat-20260713 M1 / w4). Slot i is\n// active when clips[i] != 0 (invalid handle = id=0). advanceAnimationPlayer\n// skips slot i when clips[i]=0.\n//\n// Graph mode (feat-20260713 M3 / w24, plan D-3): a SINGLE component surface hosts\n// both direct-write and graph-driven playback — no second peer component (the\n// requirements forbid a parallel component that splits the evaluation path). The\n// `graph` scalar carries a `shared<AnimationGraph>` handle; the three node-*\n// columns carry per-node runtime knobs (weight / seek-time / speed), indexed by\n// graph node index:\n// - graph == 0 (no handle): clips[] / times[] / weights[] / speeds[] are the\n// DIRECT-WRITE SSOT (the pre-M3 behaviour; consumers own the slots).\n// - graph != 0: evaluateAnimationGraph (a before-advance system) is the SOLE\n// writer of clips[] / times[] / weights[] / speeds[] — they become DERIVED\n// output of the post-order graph evaluation, and any caller-written slot\n// values are overwritten each frame. The effective weight of a node is its\n// runtime weight (nodeWeights, default 1 per node) x its graph static weight\n// (AC-07 orthogonal product); eval owns the seek-time via nodeTimes/nodeSpeeds\n// and parks speeds[]=0 so advance does not re-advance it (D-7).\n//\n// Parallel-length contract (D-5): clips / times / weights / speeds are variable\n// `array<T>` columns set field-by-field (release-then-alloc per field), so the\n// ECS layer cannot cross-check their lengths. Consumers MUST write all four\n// columns at the SAME length every time; advanceAnimationPlayer's evaluation\n// entry validates this once per row and rejects a mismatch with the structured\n// `animation-player-slot-length-mismatch` error (player-errors.ts).\n//\n// Default-fallback contract (engine-ecs three-layer SSOT):\n// layer 1 (caller) — explicit raw, e.g. `data: { clips: [h], weights: [1] }`\n// layer 2 (this file) — `default:` on the field descriptor (paused / looping)\n// layer 3 (engine-ecs) — typeDefault(fieldType): bool->false, array<T>->[]\n//\n// Unlike the retired fixed schema, a variable column does not tail-pad a short\n// write: `clips: [h]` stores length 1, NOT [h, 0, 0, 0]. The minimal call to\n// play one clip on slot 0 writes all four columns length-synced:\n// data: { clips: [h], times: [0], weights: [1], speeds: [1] }\n//\n// Naming: single-semantic component drops the 'Component' suffix\n// (AGENTS.md §Component naming rule #1). Field names mirror common game\n// engine convention (Unity Animator / Godot AnimationPlayer speed/paused\n// /looping idioms).\n//\n// Decision anchors:\n// - requirements IS-1 (SoA arrays) + AC-01 (variable N-slot, 6 field set)\n// - requirements OOS-6 (no ECS-layer change; reuse the existing variable\n// array<T> / array<shared<T>> vocab, mirroring MeshRenderer.materials)\n// - feat-20260713 M1 plan D-1 (variable columns) / D-6 (speeds default [])\n// - feat-20260713 M3 plan D-3 (graph handle + per-node runtime knobs extend\n// the existing component; no second peer component) / D-7 (eval owns time)\n// - charter P4 (consistent abstraction: single component surface)\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\nexport const AnimationPlayer = defineComponent('AnimationPlayer', {\n // The render/animation owner re-resolves clip assets in the target World;\n // playback clocks and weights remain portable simulation state.\n clips: { type: 'array<shared<AnimationClip>>' },\n times: { type: 'array<f32>' },\n weights: { type: 'array<f32>' },\n speeds: { type: 'array<f32>' },\n // The graph evaluator owns this compiled runtime binding; portable playback\n // controls and derived slots remain available to the simulation record.\n graph: { type: 'shared<AnimationGraph>' },\n nodeWeights: { type: 'array<f32>' },\n nodeTimes: { type: 'array<f32>' },\n nodeSpeeds: { type: 'array<f32>' },\n paused: { type: 'bool', default: false },\n looping: { type: 'bool', default: true },\n});\n","import {\n defineComponent,\n defineRelationship,\n Entity,\n type EntityHandle,\n type World,\n} from '@forgeax/engine-ecs';\nimport { ChildOf, Name, Transform } from '@forgeax/engine-scene';\nimport { type AnimationTargetIdValue, err, ok, type Result } from '@forgeax/engine-types';\nimport { AnimationPlayer } from './animation-player';\nimport { deriveAnimationTargetId, isAnimationTargetId } from './target-id';\n\nexport const AnimationTargetId = defineComponent('AnimationTargetId', {\n value: { type: 'string' },\n});\n\nexport const { target: AnimationTargets, source: AnimatedBy } = defineRelationship({\n sourceName: 'AnimatedBy',\n sourceField: 'player',\n targetName: 'AnimationTargets',\n targetField: 'targets',\n exclusive: true,\n linkedSpawn: false,\n allowSelf: true,\n});\n\nexport type BindAnimationTargetsErrorCode =\n | 'animation-target-player-invalid'\n | 'animation-target-invalid'\n | 'animation-target-outside-player-root'\n | 'animation-target-name-missing'\n | 'animation-target-id-invalid'\n | 'animation-target-id-duplicate'\n | 'animation-target-player-conflict'\n | 'animation-target-capacity-reserve-failed'\n | 'animation-target-bind-failed';\n\nexport class BindAnimationTargetsError extends Error {\n override readonly name = 'BindAnimationTargetsError';\n\n constructor(\n readonly code: BindAnimationTargetsErrorCode,\n readonly expected: string,\n readonly hint: string,\n readonly detail: Readonly<Record<string, unknown>>,\n ) {\n super(`[BindAnimationTargetsError ${code}] expected: ${expected}; hint: ${hint}`);\n }\n}\n\ninterface Candidate {\n readonly entity: EntityHandle;\n readonly id: AnimationTargetIdValue;\n readonly needsId: boolean;\n readonly owner: EntityHandle | null | undefined;\n}\n\nfunction bindError(\n code: BindAnimationTargetsErrorCode,\n expected: string,\n hint: string,\n detail: Readonly<Record<string, unknown>>,\n): Result<never, BindAnimationTargetsError> {\n return err(new BindAnimationTargetsError(code, expected, hint, detail));\n}\n\nfunction targetLineage(\n world: World,\n player: EntityHandle,\n target: EntityHandle,\n): Result<readonly EntityHandle[], BindAnimationTargetsError> {\n const reversed: EntityHandle[] = [];\n const visited = new Set<number>();\n let current = target;\n\n while (!visited.has(current as number)) {\n visited.add(current as number);\n reversed.push(current);\n if (current === player) return ok(reversed.reverse());\n\n const parent = world.get(current, ChildOf);\n if (!parent.ok || parent.value.parent === null) break;\n current = parent.value.parent;\n }\n\n return bindError(\n 'animation-target-outside-player-root',\n 'the target to be the player itself or one of its descendants',\n 'parent the target below the animation player or bind it to the correct player',\n { player: player as number, target: target as number },\n );\n}\n\nfunction inspectTarget(\n world: World,\n player: EntityHandle,\n target: EntityHandle,\n): Result<Candidate, BindAnimationTargetsError> {\n if (!world.get(target, Entity).ok || !world.get(target, Transform).ok) {\n return bindError(\n 'animation-target-invalid',\n 'a live target entity carrying Transform',\n 'spawn or retain the Transform target before binding it',\n { player: player as number, target: target as number },\n );\n }\n\n const lineage = targetLineage(world, player, target);\n if (!lineage.ok) return lineage;\n\n const storedId = world.get(target, AnimationTargetId);\n let id: AnimationTargetIdValue;\n if (storedId.ok) {\n if (!isAnimationTargetId(storedId.value.value)) {\n return bindError(\n 'animation-target-id-invalid',\n 'a 32-character lowercase hexadecimal AnimationTargetId wire',\n 'replace the stored value with deriveAnimationTargetId(path)',\n { target: target as number, value: storedId.value.value },\n );\n }\n id = storedId.value.value;\n } else {\n const path: string[] = [];\n for (const entity of lineage.value) {\n const name = world.get(entity, Name);\n if (!name.ok) {\n return bindError(\n 'animation-target-name-missing',\n 'a Name on every entity from the animation root through the target',\n 'attach Name before deriving the target ID',\n { player: player as number, target: target as number, entity: entity as number },\n );\n }\n path.push(name.value.value);\n }\n id = deriveAnimationTargetId(path);\n }\n\n const owner = world.get(target, AnimatedBy);\n if (\n owner.ok &&\n owner.value.player !== null &&\n owner.value.player !== player &&\n world.get(owner.value.player, Entity).ok\n ) {\n return bindError(\n 'animation-target-player-conflict',\n 'an unowned target, a stale owner, or the same animation player',\n 'remove AnimatedBy or bind the target through its current live owner',\n {\n player: player as number,\n target: target as number,\n owner: owner.value.player as number,\n },\n );\n }\n\n return ok({\n entity: target,\n id,\n needsId: !storedId.ok,\n owner: owner.ok ? owner.value.player : undefined,\n });\n}\n\nexport function bindAnimationTargets(\n world: World,\n player: EntityHandle,\n targets: readonly EntityHandle[],\n): Result<void, BindAnimationTargetsError> {\n if (!world.get(player, Entity).ok || !world.get(player, AnimationPlayer).ok) {\n return bindError(\n 'animation-target-player-invalid',\n 'a live entity carrying AnimationPlayer',\n 'spawn or retain the AnimationPlayer before binding targets',\n { player: player as number },\n );\n }\n\n const uniqueTargets = [...new Map(targets.map((target) => [target as number, target])).values()];\n const mirror = world.get(player, AnimationTargets);\n const candidates: Candidate[] = [];\n const ids = new Map<AnimationTargetIdValue, EntityHandle[]>();\n if (mirror.ok) {\n for (const targetRaw of mirror.value.targets) {\n const target = targetRaw as EntityHandle;\n if (!world.get(target, Entity).ok) continue;\n const storedId = world.get(target, AnimationTargetId);\n if (!storedId.ok || !isAnimationTargetId(storedId.value.value)) continue;\n const matching = ids.get(storedId.value.value);\n if (matching) {\n if (!matching.includes(target)) matching.push(target);\n } else {\n ids.set(storedId.value.value, [target]);\n }\n }\n }\n for (const target of uniqueTargets) {\n const candidate = inspectTarget(world, player, target);\n if (!candidate.ok) return candidate;\n const matching = ids.get(candidate.value.id);\n const previous = matching?.find((entity) => entity !== target);\n if (previous !== undefined) {\n return bindError(\n 'animation-target-id-duplicate',\n 'one target entity per AnimationTargetId within a batch',\n 'rename one target path or preserve distinct authored target IDs',\n {\n id: candidate.value.id,\n first: previous as number,\n second: target as number,\n },\n );\n }\n if (matching) {\n if (!matching.includes(target)) matching.push(target);\n } else {\n ids.set(candidate.value.id, [target]);\n }\n candidates.push(candidate.value);\n }\n\n const existing = new Set(mirror.ok ? [...mirror.value.targets] : []);\n\n for (const candidate of candidates) {\n if (candidate.needsId) {\n const added = world.addComponent(candidate.entity, {\n component: AnimationTargetId,\n data: { value: candidate.id },\n });\n if (!added.ok) {\n return bindError(\n 'animation-target-bind-failed',\n 'the preflighted AnimationTargetId write to succeed',\n 'inspect the ECS error before retrying the batch',\n { target: candidate.entity as number, cause: added.error.code },\n );\n }\n }\n if (candidate.owner !== player || !existing.has(candidate.entity)) {\n const added = world.addComponent(candidate.entity, {\n component: AnimatedBy,\n data: { player },\n });\n if (!added.ok) {\n return bindError(\n 'animation-target-bind-failed',\n 'the preflighted AnimatedBy write to succeed',\n 'inspect the ECS error before retrying the batch',\n { target: candidate.entity as number, cause: added.error.code },\n );\n }\n }\n }\n return ok(undefined);\n}\n","import {\n type AnimationClip,\n type AnimationGraph,\n type AnimationGraphNode,\n type AssetDecoderContribution,\n type AssetKind,\n err,\n ok,\n} from '@forgeax/engine-types';\n\nconst invalid = (guid: string, expected: string) =>\n err({\n code: 'asset-package-invalid' as const,\n expected,\n hint: 'recook the animation asset and publish its complete payload',\n detail: { guid, reason: 'animation owner validation failed' },\n });\n\nfunction refGuid(value: unknown, refs: readonly string[]): string | undefined {\n if (typeof value === 'string' && value.length > 0) return value;\n if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) {\n return refs[value];\n }\n return undefined;\n}\n\nfunction graphNode(value: unknown, refs: readonly string[]): AnimationGraphNode | undefined {\n if (value === null || typeof value !== 'object') return undefined;\n const source = value as Record<string, unknown>;\n if (typeof source.weight !== 'number' || !Number.isFinite(source.weight)) return undefined;\n if (source.type === 'clip') {\n const clip = refGuid(source.clip, refs);\n return clip === undefined ? undefined : { type: 'clip', clip, weight: source.weight };\n }\n if (source.type === 'blend') {\n return Array.isArray(source.children) &&\n source.children.every((child) => Number.isSafeInteger(child) && (child as number) >= 0)\n ? { type: 'blend', children: source.children as number[], weight: source.weight }\n : undefined;\n }\n if (source.type === 'add') {\n return typeof source.base === 'number' &&\n Number.isSafeInteger(source.base) &&\n source.base >= 0 &&\n Array.isArray(source.additive) &&\n source.additive.every((child) => Number.isSafeInteger(child) && (child as number) >= 0)\n ? {\n type: 'add',\n base: source.base,\n additive: source.additive as number[],\n weight: source.weight,\n }\n : undefined;\n }\n return undefined;\n}\n\nexport const animationClipContribution: AssetDecoderContribution<AnimationClip, 'animation-clip'> =\n {\n kind: { kind: 'animation-clip' } as AssetKind<AnimationClip, 'animation-clip'>,\n consumer: 'AnimationPlayer',\n decoder: {\n async decode({ envelope }) {\n return envelope.payload.kind === 'animation-clip' &&\n Array.isArray(envelope.payload.channels) &&\n Number.isFinite(envelope.payload.duration) &&\n envelope.payload.duration >= 0\n ? ok(envelope.payload)\n : invalid(envelope.guid, 'an animation clip with channels and non-negative duration');\n },\n },\n };\n\nexport const animationGraphContribution: AssetDecoderContribution<\n AnimationGraph,\n 'animation-graph'\n> = {\n kind: { kind: 'animation-graph' } as AssetKind<AnimationGraph, 'animation-graph'>,\n consumer: 'evaluateAnimationGraph',\n decoder: {\n async decode({ envelope }) {\n const payload = envelope.payload as unknown;\n if (payload === null || typeof payload !== 'object') {\n return invalid(envelope.guid, 'an animation graph with at least one node');\n }\n const source = payload as Record<string, unknown>;\n if (\n source.kind !== 'animation-graph' ||\n !Array.isArray(source.nodes) ||\n source.nodes.length === 0 ||\n !Number.isSafeInteger(source.root) ||\n (source.root as number) < 0\n ) {\n return invalid(envelope.guid, 'an animation graph with at least one node');\n }\n const nodes: AnimationGraphNode[] = [];\n for (const value of source.nodes) {\n const node = graphNode(value, envelope.refs);\n if (node === undefined) {\n return invalid(envelope.guid, 'an animation graph with resolvable node references');\n }\n nodes.push(node);\n }\n return ok({ kind: 'animation-graph', nodes, root: source.root as number });\n },\n },\n};\n","// @forgeax/engine-animation -- defineAnimationGraph builder + construction-time\n// topology validation.\n//\n// feat-20260713-animation-state-machine-plugin M2 / w14 (plan D-4 landing (2),\n// D-5 construct-time validation):\n//\n// const g = defineAnimationGraph((b) => {\n// const walk = b.clip(walkGuid);\n// const run = b.clip(runGuid);\n// const loco = b.blend([walk, run]); // normalizing\n// const overlay = b.clip(overlayGuid, 0.3);\n// return b.add(loco, [overlay]); // non-normalizing; returns root\n// }); // -> Result<AnimationGraph, AnimationGraphError>\n// if (!g.ok) return; // g.error.code / g.error.hint (AC-11)\n//\n// This is a pure library: NO evaluation system, NO AnimationPlayer change, NO\n// loader/serialization (those are M3/M4). The builder assembles a flat node\n// array and validates topology BEFORE returning ok(graph) -- an illegal graph\n// (empty / out-of-range ref / cycle / invalid weight) fails fast with a\n// structured error (charter P3, requirements AC-11) so it can never be minted\n// into a GUID handle or serialized into a pack (AC-14).\n\nimport type { AnimationGraph, AnimationGraphNode } from '@forgeax/engine-types';\nimport { err, ok, type Result } from '@forgeax/engine-types';\nimport {\n AnimationGraphCycleError,\n AnimationGraphEmptyError,\n type AnimationGraphError,\n AnimationGraphNodeOutOfRangeError,\n AnimationGraphNodeWeightInvalidError,\n} from '../errors';\n\n/**\n * Opaque reference to a node inside the graph under construction. Branded so a\n * raw number cannot be passed where a node ref is expected without an explicit\n * cast (the illegal-graph tests forge refs deliberately). At runtime it is the\n * node's index in {@link AnimationGraph.nodes}.\n */\nexport type AnimationGraphNodeRef = number & {\n readonly __animationGraphNodeRef: unique symbol;\n};\n\n/**\n * Declarative builder handed to the `defineAnimationGraph` callback. Exposes\n * exactly the three node kinds (clip / blend / add) + nesting -- no FSM /\n * transition / mask surface is reserved (OOS-1..4, charter F4). Each method\n * appends a node and returns its {@link AnimationGraphNodeRef}.\n */\nexport interface AnimationGraphBuilder {\n /** Add a Clip leaf sampling `clip` with the given static `weight` (default 1). */\n clip(clip: string, weight?: number): AnimationGraphNodeRef;\n /** Add a Blend node (normalizing lerp) over `children` with static `weight` (default 1). */\n blend(children: readonly AnimationGraphNodeRef[], weight?: number): AnimationGraphNodeRef;\n /** Add an Add node (non-normalizing) stacking `additive` layers onto `base`; static `weight` default 1. */\n add(\n base: AnimationGraphNodeRef,\n additive: readonly AnimationGraphNodeRef[],\n weight?: number,\n ): AnimationGraphNodeRef;\n}\n\n/**\n * Enumerate the child node indices a node references: clip -> none; blend ->\n * its children; add -> `[base, ...additive]`. Exhaustive over the closed node\n * union (charter P3). Shared by the validator here and describeAnimationGraph.\n */\nexport function animationGraphNodeChildren(node: AnimationGraphNode): readonly number[] {\n switch (node.type) {\n case 'clip':\n return [];\n case 'blend':\n return node.children;\n case 'add':\n return [node.base, ...node.additive];\n }\n}\n\nfunction makeBuilder(nodes: AnimationGraphNode[]): AnimationGraphBuilder {\n return {\n clip(clip, weight = 1) {\n const index = nodes.length;\n nodes.push({ type: 'clip', clip, weight });\n return index as AnimationGraphNodeRef;\n },\n blend(children, weight = 1) {\n const index = nodes.length;\n nodes.push({ type: 'blend', children: children.slice(), weight });\n return index as AnimationGraphNodeRef;\n },\n add(base, additive, weight = 1) {\n const index = nodes.length;\n nodes.push({ type: 'add', base, additive: additive.slice(), weight });\n return index as AnimationGraphNodeRef;\n },\n };\n}\n\nfunction findInvalidWeight(\n nodes: readonly AnimationGraphNode[],\n): AnimationGraphNodeWeightInvalidError | null {\n for (let i = 0; i < nodes.length; i++) {\n const weight = nodes[i]?.weight ?? Number.NaN;\n if (!Number.isFinite(weight) || weight < 0) {\n return new AnimationGraphNodeWeightInvalidError({ node: i, weight });\n }\n }\n return null;\n}\n\nfunction isRef(ref: number, nodeCount: number): boolean {\n return Number.isInteger(ref) && ref >= 0 && ref < nodeCount;\n}\n\nfunction findOutOfRangeRef(\n nodes: readonly AnimationGraphNode[],\n root: number,\n): AnimationGraphNodeOutOfRangeError | null {\n const nodeCount = nodes.length;\n if (!isRef(root, nodeCount)) {\n return new AnimationGraphNodeOutOfRangeError({ node: root, ref: root, nodeCount });\n }\n for (let i = 0; i < nodeCount; i++) {\n const node = nodes[i];\n if (node === undefined) continue;\n for (const ref of animationGraphNodeChildren(node)) {\n if (!isRef(ref, nodeCount)) {\n return new AnimationGraphNodeOutOfRangeError({ node: i, ref, nodeCount });\n }\n }\n }\n return null;\n}\n\nfunction findCycle(nodes: readonly AnimationGraphNode[]): AnimationGraphCycleError | null {\n const WHITE = 0;\n const GRAY = 1;\n const BLACK = 2;\n const color = new Uint8Array(nodes.length);\n let cycleNode = -1;\n\n const visit = (i: number): boolean => {\n color[i] = GRAY;\n const node = nodes[i];\n const children = node === undefined ? [] : animationGraphNodeChildren(node);\n for (const child of children) {\n if (color[child] === GRAY) {\n cycleNode = child;\n return true;\n }\n if (color[child] === WHITE && visit(child)) return true;\n }\n color[i] = BLACK;\n return false;\n };\n\n for (let i = 0; i < nodes.length; i++) {\n if (color[i] === WHITE && visit(i)) {\n return new AnimationGraphCycleError({ node: cycleNode });\n }\n }\n return null;\n}\n\n/**\n * Validate a graph's topology (order: empty -> invalid weight -> out-of-range\n * ref -> cycle). Returns the first structured error found, or null when the\n * graph is a well-formed DAG. Runs before a handle is minted (plan D-5).\n */\nfunction validateAnimationGraph(\n nodes: readonly AnimationGraphNode[],\n root: number,\n): AnimationGraphError | null {\n if (nodes.length === 0) return new AnimationGraphEmptyError();\n return findInvalidWeight(nodes) ?? findOutOfRangeRef(nodes, root) ?? findCycle(nodes);\n}\n\n/**\n * Construct an {@link AnimationGraph} declaratively. The callback receives a\n * builder, adds clip/blend/add nodes, and returns the ref of the root node.\n * The graph's topology is validated at construction time; on success the POD is\n * returned in `ok(graph)` ready to be minted into a `shared<AnimationGraph>`\n * handle (AC-02) and serialized (AC-14). On an illegal topology a structured\n * {@link AnimationGraphError} is returned in `err(...)` (AC-11).\n */\nexport function defineAnimationGraph(\n build: (builder: AnimationGraphBuilder) => AnimationGraphNodeRef,\n): Result<AnimationGraph, AnimationGraphError> {\n const nodes: AnimationGraphNode[] = [];\n const root = build(makeBuilder(nodes)) as number;\n const error = validateAnimationGraph(nodes, root);\n if (error) return err(error);\n return ok({ kind: 'animation-graph', nodes, root });\n}\n","// @forgeax/engine-animation -- AnimationGraph cluster structured errors.\n//\n// feat-20260713-animation-state-machine-plugin M2 / w14: defineAnimationGraph\n// validates graph topology at CONSTRUCTION time (plan D-5) -- before a GUID\n// handle is minted, so an illegal graph can never be registered or serialized\n// into a pack (AC-14). Each illegal shape returns a structured error carrying a\n// machine-readable `.code` / `.hint` / `.detail` so AI users self-repair by\n// property access without parsing prose (requirements AC-11, charter P3). This\n// mirrors the player-errors.ts convention (code/expected/hint/detail\n// four-field surface + closed code union + closed error union).\n\n// -- AnimationGraphEmptyError ----------------------------------------------------\n\n/**\n * Structured error for an empty graph (zero nodes).\n *\n * - `.code = 'animation-graph-empty'`\n * - `.expected` -- a graph declares at least one node\n * - `.hint` -- add at least one clip/blend/add node before returning the root\n */\nexport class AnimationGraphEmptyError extends Error {\n readonly code = 'animation-graph-empty' as const;\n readonly expected: string;\n readonly hint: string;\n\n constructor() {\n super('AnimationGraph is empty: the builder produced zero nodes');\n this.name = 'AnimationGraphEmptyError';\n this.expected = 'an AnimationGraph declares at least one node';\n this.hint =\n 'declare at least one clip/blend/add node inside defineAnimationGraph(...) and return its ref as the root';\n }\n}\n\n// -- AnimationGraphNodeOutOfRangeError -------------------------------------------\n\n/** Detail for `'animation-graph-node-out-of-range'`. */\nexport interface AnimationGraphNodeOutOfRangeDetail {\n /** Index of the node holding the offending reference, or the graph root. */\n readonly node: number;\n /** The out-of-range reference value that was encountered. */\n readonly ref: number;\n /** Total node count -- valid refs live in `[0, nodeCount)`. */\n readonly nodeCount: number;\n}\n\n/**\n * Structured error for a node reference that points outside `[0, nodeCount)`.\n *\n * - `.code = 'animation-graph-node-out-of-range'`\n * - `.expected` -- every child/base/additive/root ref is in `[0, nodeCount)`\n * - `.hint` -- reference only node refs returned by the builder in this graph\n * - `.detail = { node, ref, nodeCount }`\n */\nexport class AnimationGraphNodeOutOfRangeError extends Error {\n readonly code = 'animation-graph-node-out-of-range' as const;\n readonly expected: string;\n readonly hint: string;\n readonly detail: AnimationGraphNodeOutOfRangeDetail;\n\n constructor(detail: AnimationGraphNodeOutOfRangeDetail) {\n const { node, ref, nodeCount } = detail;\n super(\n `AnimationGraph node ${node} references out-of-range node ${ref} (graph has ${nodeCount} nodes)`,\n );\n this.name = 'AnimationGraphNodeOutOfRangeError';\n this.expected = 'every node reference is a valid index in [0, nodeCount)';\n this.hint = `node ${node} references index ${ref}, but valid indices are 0..${nodeCount - 1}; reference only node refs returned by the builder in this graph`;\n this.detail = detail;\n }\n}\n\n// -- AnimationGraphNodeWeightInvalidError ----------------------------------------\n\n/** Detail for `'animation-graph-node-weight-invalid'`. */\nexport interface AnimationGraphNodeWeightInvalidDetail {\n /** Index of the node whose static weight is invalid. */\n readonly node: number;\n /** The offending weight value (negative or non-finite / NaN). */\n readonly weight: number;\n}\n\n/**\n * Structured error for a negative or non-finite (NaN / Infinity) static weight.\n *\n * - `.code = 'animation-graph-node-weight-invalid'`\n * - `.expected` -- every node static weight is a finite value `>= 0`\n * - `.hint` -- pass a finite non-negative weight (Add layers may exceed 1)\n * - `.detail = { node, weight }`\n */\nexport class AnimationGraphNodeWeightInvalidError extends Error {\n readonly code = 'animation-graph-node-weight-invalid' as const;\n readonly expected: string;\n readonly hint: string;\n readonly detail: AnimationGraphNodeWeightInvalidDetail;\n\n constructor(detail: AnimationGraphNodeWeightInvalidDetail) {\n const { node, weight } = detail;\n super(`AnimationGraph node ${node} has an invalid static weight ${weight}`);\n this.name = 'AnimationGraphNodeWeightInvalidError';\n this.expected = 'every node static weight is a finite number >= 0';\n this.hint = `node ${node} has weight ${weight}; pass a finite non-negative static weight (Add layers may sum above 1, but a single node weight must be >= 0)`;\n this.detail = detail;\n }\n}\n\n// -- AnimationGraphCycleError ----------------------------------------------------\n\n/** Detail for `'animation-graph-cycle'`. */\nexport interface AnimationGraphCycleDetail {\n /** A node index that participates in the detected cycle (the back edge). */\n readonly node: number;\n}\n\n/**\n * Structured error for a cycle in the (must-be-acyclic) graph.\n *\n * - `.code = 'animation-graph-cycle'`\n * - `.expected` -- the graph is a DAG (no node reaches itself)\n * - `.hint` -- remove the self/back reference so the graph is acyclic\n * - `.detail = { node }`\n */\nexport class AnimationGraphCycleError extends Error {\n readonly code = 'animation-graph-cycle' as const;\n readonly expected: string;\n readonly hint: string;\n readonly detail: AnimationGraphCycleDetail;\n\n constructor(detail: AnimationGraphCycleDetail) {\n const { node } = detail;\n super(`AnimationGraph contains a cycle reachable from node ${node}`);\n this.name = 'AnimationGraphCycleError';\n this.expected = 'the AnimationGraph is a DAG (no node reaches itself)';\n this.hint = `node ${node} participates in a cycle; a graph must be acyclic -- remove the self/back reference so no node transitively references itself`;\n this.detail = detail;\n }\n}\n\n// -- AnimationGraphClipMissingError ----------------------------------------------\n\n/** Detail for `'animation-graph-clip-missing'`. */\nexport interface AnimationGraphClipMissingDetail {\n /** Index of the clip node whose `shared<AnimationClip>` handle failed to resolve. */\n readonly node: number;\n /** The raw clip handle that did not resolve (never registered / rc released). */\n readonly clip: number;\n}\n\n/**\n * Structured error for a graph clip leaf whose `shared<AnimationClip>` handle\n * cannot be resolved at evaluation time (never registered, or its refcount was\n * released). Raised by `evaluateAnimationGraph` (M3 / w25) BEFORE any derived\n * slot is written, so a dangling clip never leaves a dirty pose (requirements\n * AC-11 clip-missing branch, plan §8). Unlike the construction-time\n * {@link AnimationGraphError} shapes, this is an EVALUATION-time error: it is not\n * part of `defineAnimationGraph`'s `Result` (topology is validated at build, clip\n * liveness only at eval), so it stays out of the construction closed union.\n *\n * - `.code = 'animation-graph-clip-missing'`\n * - `.expected` -- every clip node's handle resolves to a live AnimationClip\n * - `.hint` -- keep the clip asset registered (rc retained) while the graph\n * references it\n * - `.detail = { node, clip }`\n */\nexport class AnimationGraphClipMissingError extends Error {\n readonly code = 'animation-graph-clip-missing' as const;\n readonly expected: string;\n readonly hint: string;\n readonly detail: AnimationGraphClipMissingDetail;\n\n constructor(detail: AnimationGraphClipMissingDetail) {\n const { node, clip } = detail;\n super(`AnimationGraph clip node ${node} references unresolved AnimationClip handle ${clip}`);\n this.name = 'AnimationGraphClipMissingError';\n this.expected = 'every clip node handle resolves to a live AnimationClip';\n this.hint = `clip node ${node} references AnimationClip handle ${clip}, which did not resolve; keep the clip asset registered (refcount retained) for as long as the graph references it`;\n this.detail = detail;\n }\n}\n\n// -- AnimationGraphErrorCode / AnimationGraphError closed unions ------------------\n\n/**\n * Closed union of AnimationGraph-cluster error codes. AI users perform\n * exhaustive `switch (err.code)` without default; TS guards completeness.\n */\nexport type AnimationGraphErrorCode =\n | 'animation-graph-empty'\n | 'animation-graph-node-out-of-range'\n | 'animation-graph-node-weight-invalid'\n | 'animation-graph-cycle';\n\n/**\n * Closed union of the AnimationGraph-cluster structured error classes, each\n * carrying an `AnimationGraphErrorCode` discriminant on `.code`.\n */\nexport type AnimationGraphError =\n | AnimationGraphEmptyError\n | AnimationGraphNodeOutOfRangeError\n | AnimationGraphNodeWeightInvalidError\n | AnimationGraphCycleError;\n","// @forgeax/engine-animation -- describeAnimationGraph machine-readable introspection.\n//\n// feat-20260713-animation-state-machine-plugin M2 / w15 (plan §8.4, AC-12):\n//\n// const desc = describeAnimationGraph(graph);\n// desc.root; // index of the output node (construction order)\n// desc.nodes; // per-node { index, type, weight, children }\n// desc.staticWeights; // per-node static weight, construction order\n//\n// AI users discover a graph's structure (nodes / topology / root / per-node\n// static weights) WITHOUT reading implementation source -- mirroring the\n// World-local component catalog reflection (research Finding H, charter F1/P1).\n//\n// Presentation order matches the graph POD construction order so that\n// `desc.nodes[i].index === i` aligns with runtime `nodeWeights[i]` /\n// `nodeTimes[i]` / `nodeSpeeds[i]` (verify V-1: introspect-then-drive path for\n// shared/deserialized graphs must not silently remap indices). The builder must\n// create a child ref before referencing it, so construction order is already a\n// valid topological order. This is a pure, read-only projection of the validated\n// POD -- no evaluation, no mutation.\n\nimport type { AnimationGraph, AnimationGraphNode } from '@forgeax/engine-types';\nimport { animationGraphNodeChildren } from './define-animation-graph';\n\n/**\n * Machine-readable description of a single graph node: its index in the\n * construction order, its `type` discriminant, its static `weight`, and the\n * child node indices it references (construction order, canonical child ordering).\n */\nexport interface AnimationGraphNodeDescription {\n /** Index of this node in {@link AnimationGraphDescription.nodes} (construction order). */\n readonly index: number;\n /** Node kind discriminant (clip / blend / add). */\n readonly type: AnimationGraphNode['type'];\n /** The node's static weight (effective weight = runtime weight x static). */\n readonly weight: number;\n /** Child node indices referenced by this node (clip -> []), construction order. */\n readonly children: readonly number[];\n}\n\n/**\n * Machine-readable description of an entire {@link AnimationGraph}: the ordered\n * per-node descriptions (construction order), the root node index, and the\n * per-node static weights (a flat projection of each node's `weight`).\n */\nexport interface AnimationGraphDescription {\n /** Per-node descriptions, in graph POD construction order. */\n readonly nodes: readonly AnimationGraphNodeDescription[];\n /** Index of the graph's root (output) node, in construction order. */\n readonly root: number;\n /** Per-node static weights, in construction order. */\n readonly staticWeights: readonly number[];\n}\n\n/**\n * Enumerate a validated {@link AnimationGraph}'s nodes, topology, root, and\n * per-node static weights for machine-readable introspection (AC-12). Pure and\n * read-only -- returns a fresh description derived from the POD in construction\n * order so described indices match runtime `nodeWeights` indices.\n */\nexport function describeAnimationGraph(graph: AnimationGraph): AnimationGraphDescription {\n const nodes = graph.nodes.map((node, index) => ({\n index,\n type: node.type,\n weight: node.weight,\n children: animationGraphNodeChildren(node),\n }));\n\n return {\n nodes,\n root: graph.root,\n staticWeights: nodes.map((node) => node.weight),\n };\n}\n","// @forgeax/engine-animation -- AnimationGraph POD -> pack payload serialization.\n//\n// feat-20260713-animation-state-machine-plugin M4 / w29 (plan D-4 landing (2),\n// §6.3 serialize seam):\n//\n// const out = serializeAnimationGraph(graph, (clipGuid) => clipGuid);\n// if (out === undefined) return; // a durable clip GUID was unavailable\n// // out.payload -> the pack `payload` object; out.refs -> the pack `refs[]`.\n//\n// The inverse of the animation owner's graph contribution:\n// serialize rewrites each Clip leaf's durable GUID into a deduped `refs[]`\n// array (the payload stores the refs INDEX, not a runtime handle) -- the exact\n// \"GUID in refs, index in payload\" contract\n// the scene/material serialize paths use (plan D-4, asset-registry D-19). Blend\n// and Add nodes carry only intra-graph node indices, which are position-stable\n// under round-trip, so they pass through unchanged. `root` is a node index and\n// passes through too.\n//\n// OOS-7: this serializes only the topology of an engine-authored\n// `defineAnimationGraph` graph (nodes / static weights / clip refs); there is no\n// DCC-import metadata surface.\n\nimport type { AnimationGraph } from '@forgeax/engine-types';\n\n/**\n * Resolves a Clip leaf's durable GUID string to the form stored in Pack\n * `refs[]`. Returns `undefined` when the GUID is unavailable, which aborts\n * serialization.\n * The caller supplies this so serialize stays free of AssetRegistry / World\n * coupling; the graph already carries durable GUID strings at this boundary.\n */\nexport type ClipGuidResolver = (clip: string) => string | undefined;\n\n/**\n * Output of {@link serializeAnimationGraph}: the pack `payload` object (a flat\n * `{ nodes, root }` shape where Clip leaves reference `refs[]` by index) plus the\n * deduped `refs[]` array of clip GUID strings.\n */\nexport interface SerializedAnimationGraph {\n readonly payload: Record<string, unknown>;\n readonly refs: readonly string[];\n}\n\n/**\n * Serialize an {@link AnimationGraph} POD into a pack payload + refs pair. Clip\n * leaves are rewritten from durable GUIDs to deduped `refs[]` GUID indices via\n * `resolveClipGuid`; Blend/Add node references and `root` (all intra-graph node\n * indices) pass through. Returns `undefined` if any durable clip GUID cannot be\n * resolved -- an unresolvable clip means the graph cannot be persisted losslessly.\n */\nexport function serializeAnimationGraph(\n graph: AnimationGraph,\n resolveClipGuid: ClipGuidResolver = (clip) => clip,\n): SerializedAnimationGraph | undefined {\n const refs: string[] = [];\n const guidToIndex = new Map<string, number>();\n const internRef = (guid: string): number => {\n const existing = guidToIndex.get(guid);\n if (existing !== undefined) return existing;\n const index = refs.length;\n refs.push(guid);\n guidToIndex.set(guid, index);\n return index;\n };\n\n const nodes: Array<Record<string, unknown>> = [];\n for (const node of graph.nodes) {\n switch (node.type) {\n case 'clip': {\n const guid = resolveClipGuid(node.clip);\n if (guid === undefined || guid.length === 0) return undefined;\n nodes.push({ type: 'clip', clip: internRef(guid), weight: node.weight });\n break;\n }\n case 'blend':\n nodes.push({ type: 'blend', children: [...node.children], weight: node.weight });\n break;\n case 'add':\n nodes.push({\n type: 'add',\n base: node.base,\n additive: [...node.additive],\n weight: node.weight,\n });\n break;\n }\n }\n\n return { payload: { nodes, root: graph.root }, refs };\n}\n","import { Time, Update } from '@forgeax/engine-ecs';\n// @forgeax/engine-animation — advanceAnimationPlayer system (variable N-way blend).\n//\n// Per-tick: scans the variable-length SoA columns on each AnimationPlayer; for\n// each active slot (clips[i] != 0), advances times[i] += dt * speeds[i] (paused\n// gates the whole entity), samples the AnimationClip channels, and accumulates a\n// weighted pose into per-joint TRS accumulators. Once all slots are\n// folded in, each joint receives a single `world.set(joint, Transform,\n// fullPose)` (research F-2 / F-7 / F-8 — single write per joint per tick).\n//\n// Variable N-slot (feat-20260713 M1 / w5): the fixed 4-slot cap is retired. The\n// four parallel columns (clips / times / weights / speeds) are variable\n// `array<T>` columns, so row queries expose them through each entity's resolved\n// component value; the flat-SoA row window used by the retired fixed schema no\n// longer applies. Each entity's columns are read back as\n// resolved TypedArrays via `world.get(entity, AnimationPlayer)` and the advanced\n// `times` column is written back via `world.set` (D-7: weights are never written\n// back; clamping is read-time only). The blend math (translation linear / scale\n// linear / rotation nlerp, per-channel sumW) is byte-for-byte unchanged from the\n// fixed schema — only the loop bound moved from a hard 4 to the column length.\n//\n// Blend math (plan-strategy D-1):\n// - translation / scale: linear average — accumulator += w_i * v_i, then\n// accumulator /= Σw_i (per-channel sumW).\n// - rotation: nlerp — first valid quat fixes the sign reference; later\n// quats negated when dot < 0 to take the short arc; accumulator += w * q;\n// finalize = normalize(accumulator). nlerp (not slerp) per D-1 (research\n// F-7: Three.js Normal-mode mathematical form).\n// - per-channel sumW: a joint receiving translation from 2 slots and rotation\n// from 1 slot normalizes each channel by its own sum. Slot weights need not\n// be partitioned-by-1 (research F-2).\n//\n// Best-effort failure modes (AC-05, plan-strategy D-7 / D-9):\n// - clips[i] == 0 : skip slot, no resolver call (AC-04)\n// - resolver miss : skip slot\n// - weights[i] < 0 : clamped via max(0, w); not written back (D-7)\n// - duration mismatch : per-slot modulo on its own duration\n// - channel target missing : skip channel\n// - channel missing on slot: per-channel normalize covers it\n//\n// w5 layers a dev-mode warn pass on top of these silent skips\n// (channel-target-missing / channel-missing-on-some-slot, once per\n// (entity, channelKey, reason)).\n//\n// Decision anchors:\n// - requirements IS-2 / AC-03 / AC-04 / AC-05 (best-effort N-way blend)\n// - plan-strategy D-1 (TRS accumulators + nlerp), D-3 (public Query, no\n// query-backed lookup), D-7 (clamp without write-back), D-9 (negative speed\n// natural reverse)\n// - charter P4 (single Transform write per joint per tick)\n\nimport type { EntityHandle, SystemHandle, World } from '@forgeax/engine-ecs';\nimport { defineSystem, defineSystemSet, ENTITY_NULL_RAW } from '@forgeax/engine-ecs';\nimport { createWorldProjection, type WorldProjection } from '@forgeax/engine-ecs/projection';\nimport { MorphWeights, Transform } from '@forgeax/engine-scene';\nimport type { AnimationChannel, AnimationClip, AnimationSampler } from '@forgeax/engine-types';\nimport { toShared } from '@forgeax/engine-types';\nimport {\n emitAnimationDiagnostic,\n isAnimationDevMode,\n _resetAnimationWarnsForTests as resetAnimationDiagnosticsForTests,\n} from '../animation-diagnostic';\nimport { AnimationPlayer } from '../animation-player';\nimport { AnimationTargetId, AnimationTargets } from '../animation-target';\nimport { AnimationPlayerSlotLengthMismatchError } from '../player-errors';\n\n/**\n * System name used when `registerAdvanceAnimationPlayer` installs the system\n * into the ECS schedule. External consumers can reference this constant to\n * declare `after: [ADVANCE_ANIMATION_PLAYER_SYSTEM]` on dependent systems.\n */\nexport const ADVANCE_ANIMATION_PLAYER_SYSTEM = 'advanceAnimationPlayer' as const;\nexport const AnimationSet = defineSystemSet({ name: 'animation' });\n\nexport function _resetAnimationWarnsForTests(world: World): void {\n resetAnimationDiagnosticsForTests(world);\n targetMapCacheByWorld.delete(world);\n}\n\n/**\n * Advance all AnimationPlayer components by dt, blend the N active clips per\n * entity, and write one Transform per joint per tick. Returns void.\n *\n * Iteration walks every matching row through the injected Query (D-3). Variable\n * SoA columns are read through the row facade, so entity handles are collected first, then each\n * entity's columns are resolved to TypedArrays via `world.get` and the advanced\n * `times` written back via `world.set` (mutating the query bundle mid-walk would\n * be a compile error against the reader shape).\n */\nexport function advanceAnimationPlayer(world: World, dt: number): void {\n const query = world.query({ with: [AnimationPlayer] }).unwrap();\n\n // Collect entity handles inside the walk (the Entity.self view is transient),\n // then resolve + mutate each player outside it.\n const entities: EntityHandle[] = [];\n for (const row of query) entities.push(row.entity);\n\n for (const entityRaw of entities) {\n advanceOnePlayer(world, entityRaw, dt);\n }\n}\n\n/**\n * Resolved per-entity AnimationPlayer columns (a `world.get` snapshot). Variable\n * columns alias the BufferPool slot bytes; the advanced `times` is written back\n * via `world.set`, never in place, so the snapshot is treated read-only here.\n */\ninterface PlayerColumns {\n readonly clips: Uint32Array;\n readonly times: Float32Array;\n readonly weights: Float32Array;\n readonly speeds: Float32Array;\n readonly paused: boolean;\n readonly looping: boolean;\n}\n\n/**\n * Advance one entity's AnimationPlayer: validate the four parallel column\n * lengths at the evaluation entry (the single length chokepoint, D-5), walk the\n * N slots to collect the active set + advanced times, write the advanced `times`\n * column back, and fold the active slots into the joint pose.\n *\n * Length guard (AC-11 / D-5): variable columns are set field-by-field, so a\n * consumer that desyncs their lengths is rejected here with the structured\n * `animation-player-slot-length-mismatch` error rather than silently padded or\n * truncated.\n */\nfunction advanceOnePlayer(world: World, entityRaw: number, dt: number): void {\n const entity = entityRaw as EntityHandle;\n const apRes = world.get(entity, AnimationPlayer);\n if (!apRes.ok) return;\n const ap = apRes.value as unknown as PlayerColumns;\n\n const count = ap.clips.length;\n if (ap.times.length !== count || ap.weights.length !== count || ap.speeds.length !== count) {\n throw new AnimationPlayerSlotLengthMismatchError({\n entity: entityRaw,\n clips: count,\n times: ap.times.length,\n weights: ap.weights.length,\n speeds: ap.speeds.length,\n });\n }\n if (count === 0) return;\n\n const newTimes = new Float32Array(ap.times);\n const activeSlots = collectActiveSlotsAndAdvanceTimes(world, ap, newTimes, dt);\n\n // Persist the advanced times column (D-7: weights are never written back —\n // negative-weight clamping is read-time only). Only `times` is set, so the\n // clips / weights / speeds slots are left untouched.\n world.set(entity, AnimationPlayer, { times: newTimes });\n\n if (activeSlots.length === 0) return;\n tickEntityTargets(world, entity, entityRaw, activeSlots);\n}\n\n/**\n * Walk the N SoA slots for one entity: skip `clips[i]==0` (AC-04, no resolver\n * call), skip resolver miss, advance `times[i] += speeds[i]*dt` (paused gates\n * the entity per AC-05) into `newTimes`, wrap / clamp by clip duration based on\n * `looping`, and keep the slot if `max(0, weights[i]) > 0` (D-7 clamp without\n * write-back).\n *\n * The negative-speed reverse case (D-9) falls out naturally — `newTime`\n * goes negative, the looping branch's `+= duration` re-anchors it; the\n * looping=false branch clamps to 0.\n */\nfunction collectActiveSlotsAndAdvanceTimes(\n world: World,\n ap: PlayerColumns,\n newTimes: Float32Array,\n dt: number,\n): ActiveSlot[] {\n const paused = ap.paused;\n const looping = ap.looping;\n const count = ap.clips.length;\n const activeSlots: ActiveSlot[] = [];\n\n for (let i = 0; i < count; i++) {\n const clipHandleRaw = ap.clips[i] ?? 0;\n if (clipHandleRaw === 0) continue;\n const clipLookup = world.sharedRefs.resolve<'AnimationClip', AnimationClip>(\n toShared<'AnimationClip'>(clipHandleRaw),\n );\n if (!clipLookup.ok) throw clipLookup.error;\n const clip = clipLookup.value;\n\n const speed = ap.speeds[i] ?? 0;\n let newTime = paused ? (ap.times[i] ?? 0) : (ap.times[i] ?? 0) + speed * dt;\n const duration = clip.duration;\n if (duration > 0) {\n if (looping) {\n newTime = newTime % duration;\n if (newTime < 0) newTime += duration;\n } else if (newTime > duration) {\n newTime = duration;\n } else if (newTime < 0) {\n newTime = 0;\n }\n }\n newTimes[i] = newTime;\n\n const wRaw = ap.weights[i] ?? 0;\n const w = wRaw > 0 ? wRaw : 0;\n if (w === 0) continue;\n\n activeSlots.push({ clip, clipHandleRaw, weight: w, time: newTime, slotIdx: i });\n }\n\n return activeSlots;\n}\n\n/**\n * Target-write pass for one player: resolves its explicit target mirror and\n * folds every active-slot channel into a per-target TRS accumulator (linear\n * for translation/scale, nlerp with sign-fixed reference for rotation),\n * then writes one `world.set(joint, Transform, ...)` per touched joint.\n *\n * Dev-mode warns (D-2):\n * - channel-target-missing: a clip channel ID does not resolve in this\n * player's explicit target set. Once per (entityId, clip, chIdx).\n * - channel-missing-on-some-slot: a (joint, kind) tuple is covered by\n * some slot but missing on another. The warn is emitted once per\n * (entityId, the-covering-slot's-clip, that-slot's-chIdx) so users\n * find the authoring point that has the channel; per-channel sumW\n * normalize covers the runtime gap regardless.\n */\ninterface TargetMap {\n readonly entities: ReadonlyMap<string, EntityHandle>;\n readonly missingTransforms: ReadonlyMap<string, EntityHandle>;\n readonly duplicateIds: ReadonlySet<string>;\n readonly hasStaleTarget: boolean;\n readonly resolvedClips: WeakMap<AnimationClip, readonly (EntityHandle | undefined)[]>;\n}\n\ninterface WorldTargetMapCache {\n readonly projection: WorldProjection;\n readonly players: Map<number, TargetMap>;\n}\n\nconst targetMapCacheByWorld = new WeakMap<World, WorldTargetMapCache>();\n\nfunction targetMapForPlayer(world: World, player: EntityHandle): TargetMap {\n let cache = targetMapCacheByWorld.get(world);\n if (cache === undefined) {\n cache = {\n projection: createWorldProjection(world, {\n components: [AnimationTargets, AnimationTargetId],\n }),\n players: new Map(),\n };\n targetMapCacheByWorld.set(world, cache);\n } else {\n const changes = cache.projection.poll();\n if (changes.status === 'rebuild' || changes.changes.length > 0) {\n cache = {\n projection: createWorldProjection(world, {\n components: [AnimationTargets, AnimationTargetId],\n }),\n players: new Map(),\n };\n targetMapCacheByWorld.set(world, cache);\n }\n }\n\n const cached = cache.players.get(player as number);\n if (cached !== undefined) return cached;\n const built = buildTargetMap(world, player);\n cache.players.set(player as number, built);\n return built;\n}\n\nfunction buildTargetMap(world: World, player: EntityHandle): TargetMap {\n const targets = world.get(player, AnimationTargets);\n if (!targets.ok) {\n return {\n entities: new Map(),\n missingTransforms: new Map(),\n duplicateIds: new Set(),\n hasStaleTarget: false,\n resolvedClips: new WeakMap(),\n };\n }\n const result = new Map<string, EntityHandle>();\n const missingTransforms = new Map<string, EntityHandle>();\n const seen = new Set<string>();\n const ambiguous = new Set<string>();\n let hasStaleTarget = false;\n for (const raw of targets.value.targets) {\n if (raw === ENTITY_NULL_RAW) continue;\n const target = raw as EntityHandle;\n const id = world.get(target, AnimationTargetId);\n if (!id.ok) {\n hasStaleTarget = true;\n continue;\n }\n const targetId = id.value.value;\n if (ambiguous.has(targetId)) continue;\n if (seen.has(targetId)) {\n result.delete(targetId);\n missingTransforms.delete(targetId);\n ambiguous.add(targetId);\n continue;\n }\n seen.add(targetId);\n if (world.get(target, Transform).ok) result.set(targetId, target);\n else missingTransforms.set(targetId, target);\n }\n return {\n entities: result,\n missingTransforms,\n duplicateIds: ambiguous,\n hasStaleTarget,\n resolvedClips: new WeakMap(),\n };\n}\n\nfunction tickEntityTargets(\n world: World,\n entity: EntityHandle,\n entityRaw: number,\n activeSlots: ActiveSlot[],\n): void {\n const targetMap = targetMapForPlayer(world, entity);\n\n // Per-joint accumulator: lazily allocated when first channel writes.\n // A Map keyed by jointIndex keeps the typical case (a few animated\n // joints out of 20+) sparse rather than allocating for every joint.\n const accumulators: Map<number, JointAccumulator> = new Map();\n const morphAccumulators: Map<number, MorphWeightAccumulator> = new Map();\n // Per-slot signature of (joint, channel-kind) coverage — used to detect\n // channel-missing-on-some-slot once at the end of the channel walk. Lazy\n // build only when there are 2+ active slots and dev-mode is on (warn pass\n // is skipped in production by the shared diagnostics mode gate).\n const slotCoverage: SlotCoverage[] = [];\n const wantsCoverage = activeSlots.length >= 2 && isAnimationDevMode();\n if (wantsCoverage) {\n for (let i = 0; i < activeSlots.length; i++) slotCoverage.push(new Map());\n }\n\n for (let slotIdx = 0; slotIdx < activeSlots.length; slotIdx++) {\n // biome-ignore lint/style/noNonNullAssertion: bounded by activeSlots.length\n const slot = activeSlots[slotIdx]!;\n const resolvedTargets = resolveClipTargets(world, entityRaw, slot, targetMap);\n for (let chIdx = 0; chIdx < slot.clip.channels.length; chIdx++) {\n // biome-ignore lint/style/noNonNullAssertion: bounded by channels.length\n const channel = slot.clip.channels[chIdx]!;\n const sampled = sampleChannel(channel.sampler, slot.time, channel.property);\n if (sampled === undefined) continue;\n const target =\n channel.property === 'weights'\n ? resolveChannelTarget(\n world,\n entityRaw,\n slot.clipHandleRaw,\n chIdx,\n channel.targetId,\n channel.property,\n sampled.length,\n targetMap,\n )\n : resolvedTargets[chIdx];\n if (target === undefined) continue;\n const targetRaw = target as number;\n\n if (channel.property === 'weights') {\n let weights = morphAccumulators.get(targetRaw);\n if (weights === undefined) {\n weights = { values: new Float32Array(sampled.length), sumW: 0 };\n morphAccumulators.set(targetRaw, weights);\n }\n if (weights.values.length !== sampled.length) continue;\n for (let i = 0; i < sampled.length; i++) {\n weights.values[i] = (weights.values[i] ?? 0) + slot.weight * (sampled[i] ?? 0);\n }\n weights.sumW += slot.weight;\n if (wantsCoverage) {\n const coverage = slotCoverage[slotIdx];\n if (coverage !== undefined) {\n recordSlotCoverage(coverage, channel.targetId, channel.property, chIdx);\n }\n }\n continue;\n }\n\n let acc = accumulators.get(targetRaw);\n if (acc === undefined) {\n acc = createAccumulator();\n accumulators.set(targetRaw, acc);\n }\n\n foldChannelIntoAccumulator(acc, channel.property, sampled, slot.weight);\n\n if (wantsCoverage) {\n // biome-ignore lint/style/noNonNullAssertion: parallel to activeSlots\n recordSlotCoverage(slotCoverage[slotIdx]!, channel.targetId, channel.property, chIdx);\n }\n }\n }\n\n if (wantsCoverage) {\n emitMissingOnSomeSlotWarns(world, entityRaw, activeSlots, slotCoverage);\n }\n\n for (const [targetRaw, acc] of accumulators) {\n const target = targetRaw as EntityHandle;\n const partial = finalizeAccumulator(acc);\n if (Object.keys(partial).length > 0) {\n world.set(target, Transform as never, partial as never);\n }\n }\n for (const [targetRaw, acc] of morphAccumulators) {\n if (acc.sumW <= 0) continue;\n const target = targetRaw as EntityHandle;\n const weights = world.get(target, MorphWeights);\n if (weights.ok && weights.value.weights.length === acc.values.length) {\n const next = new Float32Array(acc.values.length);\n for (let i = 0; i < next.length; i++) next[i] = (acc.values[i] ?? 0) / acc.sumW;\n world.set(target, MorphWeights, { weights: next });\n }\n }\n}\n\nfunction resolveClipTargets(\n world: World,\n player: number,\n slot: ActiveSlot,\n targetMap: TargetMap,\n): readonly (EntityHandle | undefined)[] {\n const cached = targetMap.resolvedClips.get(slot.clip);\n if (cached !== undefined) return cached;\n\n const targets = slot.clip.channels.map((channel, channelIndex) =>\n channel.property === 'weights'\n ? undefined\n : resolveChannelTarget(\n world,\n player,\n slot.clipHandleRaw,\n channelIndex,\n channel.targetId,\n channel.property,\n undefined,\n targetMap,\n ),\n );\n targetMap.resolvedClips.set(slot.clip, targets);\n return targets;\n}\n\nfunction resolveChannelTarget(\n world: World,\n player: number,\n clip: number,\n channel: number,\n targetId: string,\n property: ChannelKind,\n expectedWeightCount: number | undefined,\n targetMap: TargetMap,\n): EntityHandle | undefined {\n if (targetMap.duplicateIds.has(targetId)) {\n emitTargetDiagnostic(\n world,\n player,\n clip,\n channel,\n targetId,\n 'animation-target-id-duplicate',\n 'target-id-duplicate',\n 'assign a unique AnimationTargetId to each target owned by this player',\n );\n return undefined;\n }\n const target = targetMap.entities.get(targetId);\n if (target === undefined) {\n const transformMissingTarget = targetMap.missingTransforms.get(targetId);\n if (transformMissingTarget !== undefined) {\n emitTargetDiagnostic(\n world,\n player,\n clip,\n channel,\n targetId,\n 'animation-target-transform-missing',\n 'transform-missing',\n 'attach Transform to the bound animation target',\n transformMissingTarget as number,\n );\n return undefined;\n }\n emitTargetDiagnostic(\n world,\n player,\n clip,\n channel,\n targetId,\n targetMap.hasStaleTarget ? 'animation-target-owner-stale' : 'animation-target-missing',\n targetMap.hasStaleTarget ? 'target-stale' : 'target-missing',\n targetMap.hasStaleTarget\n ? 'remove the stale target relation or bind a live replacement'\n : 'bind the matching AnimationTargetId to this player',\n );\n return undefined;\n }\n if (property === 'weights') {\n const weights = world.get(target, MorphWeights);\n if (!weights.ok) {\n emitAnimationDiagnostic(world, {\n code: 'animation-target-morph-weights-missing',\n hint: 'attach MorphWeights to the morph target entity before playing a weights channel',\n detail: {\n player,\n clip,\n channel,\n targetId,\n reason: 'morph-weights-missing',\n target: target as number,\n property,\n ...(expectedWeightCount === undefined ? {} : { expectedWeightCount }),\n },\n });\n return undefined;\n }\n if (expectedWeightCount !== undefined && weights.value.weights.length !== expectedWeightCount) {\n emitAnimationDiagnostic(world, {\n code: 'animation-morph-weight-count-mismatch',\n hint: 'make MorphWeights.length equal the animation channel output width',\n detail: {\n player,\n clip,\n channel,\n targetId,\n reason: 'morph-weight-count-mismatch',\n target: target as number,\n property,\n expectedWeightCount,\n actualWeightCount: weights.value.weights.length,\n },\n });\n return undefined;\n }\n return target;\n }\n if (!world.get(target, Transform).ok) {\n emitTargetDiagnostic(\n world,\n player,\n clip,\n channel,\n targetId,\n 'animation-target-transform-missing',\n 'transform-missing',\n 'attach Transform to the bound animation target',\n target as number,\n );\n return undefined;\n }\n return target;\n}\n\n/**\n * Per-slot (joint -> covered kinds) signature. The `chIdxByKind` field\n * remembers which channel index of the slot's clip first covered the\n * (joint, kind) pair — used as the channelKey when emitting a\n * channel-missing-on-some-slot warn so the user can locate the\n * authoring channel that exposed the asymmetry.\n */\ntype ChannelKind = AnimationChannel['property'];\ntype SlotCoverage = Map<string, Map<ChannelKind, number>>;\n\nfunction recordSlotCoverage(\n cov: SlotCoverage,\n targetId: string,\n kind: ChannelKind,\n chIdx: number,\n): void {\n let perTarget = cov.get(targetId);\n if (perTarget === undefined) {\n perTarget = new Map();\n cov.set(targetId, perTarget);\n }\n if (!perTarget.has(kind)) perTarget.set(kind, chIdx);\n}\n\nfunction emitTargetDiagnostic(\n world: World,\n entityRaw: number,\n clipHandleRaw: number,\n chIdx: number,\n targetId: string,\n code:\n | 'animation-target-missing'\n | 'animation-target-transform-missing'\n | 'animation-target-id-duplicate'\n | 'animation-target-owner-stale',\n reason: 'target-missing' | 'transform-missing' | 'target-id-duplicate' | 'target-stale',\n hint: string,\n target?: number,\n): void {\n emitAnimationDiagnostic(world, {\n code,\n hint,\n detail: {\n player: entityRaw,\n clip: clipHandleRaw,\n channel: chIdx,\n targetId,\n reason,\n ...(target === undefined ? {} : { target }),\n },\n });\n}\n\n/**\n * Reconcile per-slot coverage against the union: for any (joint, kind)\n * tuple covered by ≥ 1 slot but missing on another, emit the warn once per\n * (entityId, the-covering-slot's-clip, that-slot's-chIdx, reason). Each\n * covering slot may emit its own warn pointing at its own channel index —\n * authoring tools can land on any of them.\n */\nfunction emitMissingOnSomeSlotWarns(\n world: World,\n entityRaw: number,\n activeSlots: ActiveSlot[],\n slotCoverage: SlotCoverage[],\n): void {\n // Union over all slots: jointIndex -> Set<ChannelKind>.\n const union: Map<string, Set<ChannelKind>> = new Map();\n for (const cov of slotCoverage) {\n for (const [targetId, kindMap] of cov) {\n let set = union.get(targetId);\n if (set === undefined) {\n set = new Set();\n union.set(targetId, set);\n }\n for (const kind of kindMap.keys()) set.add(kind);\n }\n }\n\n for (const [targetId, unionKinds] of union) {\n for (let slotIdx = 0; slotIdx < activeSlots.length; slotIdx++) {\n // biome-ignore lint/style/noNonNullAssertion: parallel arrays\n const cov = slotCoverage[slotIdx]!;\n const slotKinds = cov.get(targetId);\n for (const kind of unionKinds) {\n if (slotKinds?.has(kind)) continue;\n // This slot is missing `kind` on jointIndex. Find the slot that\n // does cover (joint, kind) and use ITS chIdx as the warn anchor.\n for (let coveringIdx = 0; coveringIdx < activeSlots.length; coveringIdx++) {\n if (coveringIdx === slotIdx) continue;\n // biome-ignore lint/style/noNonNullAssertion: parallel arrays\n const coveringCov = slotCoverage[coveringIdx]!;\n const coveringKinds = coveringCov.get(targetId);\n if (coveringKinds === undefined) continue;\n const chIdx = coveringKinds.get(kind);\n if (chIdx === undefined) continue;\n // biome-ignore lint/style/noNonNullAssertion: parallel arrays\n const coveringSlot = activeSlots[coveringIdx]!;\n emitAnimationDiagnostic(world, {\n code: 'animation-channel-missing',\n hint: `author the missing ${kind} channel on the slot whose clip lacks it`,\n detail: {\n player: entityRaw,\n clip: coveringSlot.clipHandleRaw,\n channel: chIdx,\n targetId,\n reason: 'channel-missing',\n property: kind,\n },\n });\n break;\n }\n }\n }\n }\n}\n\n/**\n * Add a sampled (translation / rotation / scale) channel to the per-joint\n * accumulator, weighted by the slot's weight. Quat handling sign-fixes\n * against the first quat seen so the nlerp picks the short arc (research\n * F-7); per-channel sumW lets translation / rotation / scale normalize\n * independently when slot coverage differs (research F-2 / AC-05(b)).\n */\nfunction foldChannelIntoAccumulator(\n acc: JointAccumulator,\n property: ChannelKind,\n sampled: number[],\n weight: number,\n): void {\n if (property === 'weights') return;\n if (property === 'translation' && sampled.length >= 3) {\n acc.posX += weight * (sampled[0] ?? 0);\n acc.posY += weight * (sampled[1] ?? 0);\n acc.posZ += weight * (sampled[2] ?? 0);\n acc.sumWPos += weight;\n acc.hasPos = true;\n return;\n }\n if (property === 'rotation' && sampled.length >= 4) {\n const qx = sampled[0] ?? 0;\n const qy = sampled[1] ?? 0;\n const qz = sampled[2] ?? 0;\n const qw = sampled[3] ?? 1;\n if (!acc.hasQuat) {\n acc.refQX = qx;\n acc.refQY = qy;\n acc.refQZ = qz;\n acc.refQW = qw;\n acc.quatX = weight * qx;\n acc.quatY = weight * qy;\n acc.quatZ = weight * qz;\n acc.quatW = weight * qw;\n acc.hasQuat = true;\n } else {\n const dot = acc.refQX * qx + acc.refQY * qy + acc.refQZ * qz + acc.refQW * qw;\n const sign = dot < 0 ? -1 : 1;\n acc.quatX += weight * sign * qx;\n acc.quatY += weight * sign * qy;\n acc.quatZ += weight * sign * qz;\n acc.quatW += weight * sign * qw;\n }\n acc.sumWQuat += weight;\n return;\n }\n if (property === 'scale' && sampled.length >= 3) {\n acc.scaleX += weight * (sampled[0] ?? 1);\n acc.scaleY += weight * (sampled[1] ?? 1);\n acc.scaleZ += weight * (sampled[2] ?? 1);\n acc.sumWScale += weight;\n acc.hasScale = true;\n }\n}\n\n/**\n * Per-channel normalize: divide by per-channel sumW. Quat finalize\n * normalizes the resulting vec4 (nlerp). A channel with sumW=0 is silently\n * absent from the partial — `world.set` with a partial leaves untouched\n * fields at their existing values (AC-05(b) per-channel fallback). Channel\n * granularity maps 1:1 onto the Transform array columns (feat-20260709 M2):\n * an animated channel always covers its whole pos/quat/scale vector, so the\n * per-field partial write semantics are unchanged by the column migration.\n */\nfunction finalizeAccumulator(acc: JointAccumulator): Record<string, number[]> {\n const partial: Record<string, number[]> = {};\n if (acc.hasPos && acc.sumWPos > 0) {\n partial.pos = [acc.posX / acc.sumWPos, acc.posY / acc.sumWPos, acc.posZ / acc.sumWPos];\n }\n if (acc.hasQuat && acc.sumWQuat > 0) {\n const qx = acc.quatX / acc.sumWQuat;\n const qy = acc.quatY / acc.sumWQuat;\n const qz = acc.quatZ / acc.sumWQuat;\n const qw = acc.quatW / acc.sumWQuat;\n const len = Math.sqrt(qx * qx + qy * qy + qz * qz + qw * qw);\n if (len > 0) {\n // Component order [x, y, z, w] (E6).\n partial.quat = [qx / len, qy / len, qz / len, qw / len];\n }\n }\n if (acc.hasScale && acc.sumWScale > 0) {\n partial.scale = [\n acc.scaleX / acc.sumWScale,\n acc.scaleY / acc.sumWScale,\n acc.scaleZ / acc.sumWScale,\n ];\n }\n return partial;\n}\n\n/**\n * Per-active-slot snapshot folded into the per-entity accumulator pass:\n * the resolved clip + the (advanced) sample time + the clamped weight, so\n * the channel loop never has to re-read the SoA columns.\n */\ninterface ActiveSlot {\n readonly clip: AnimationClip;\n readonly clipHandleRaw: number;\n readonly weight: number;\n readonly time: number;\n readonly slotIdx: number;\n}\n\ninterface JointAccumulator {\n posX: number;\n posY: number;\n posZ: number;\n sumWPos: number;\n hasPos: boolean;\n // Quat reference + accumulator. refQ* is the first sampled quat (sign-fixed)\n // so subsequent quats with dot<0 are negated for short-arc nlerp.\n refQX: number;\n refQY: number;\n refQZ: number;\n refQW: number;\n quatX: number;\n quatY: number;\n quatZ: number;\n quatW: number;\n sumWQuat: number;\n hasQuat: boolean;\n scaleX: number;\n scaleY: number;\n scaleZ: number;\n sumWScale: number;\n hasScale: boolean;\n}\n\ninterface MorphWeightAccumulator {\n readonly values: Float32Array;\n sumW: number;\n}\n\nfunction createAccumulator(): JointAccumulator {\n return {\n posX: 0,\n posY: 0,\n posZ: 0,\n sumWPos: 0,\n hasPos: false,\n refQX: 0,\n refQY: 0,\n refQZ: 0,\n refQW: 1,\n quatX: 0,\n quatY: 0,\n quatZ: 0,\n quatW: 0,\n sumWQuat: 0,\n hasQuat: false,\n scaleX: 0,\n scaleY: 0,\n scaleZ: 0,\n sumWScale: 0,\n hasScale: false,\n };\n}\n\n/**\n * Sample an animation sampler at the given time.\n *\n * Returns an array of floats whose length matches the property element count:\n * - translation / scale: 3 floats (vec3)\n * - rotation: 4 floats (quat)\n */\nfunction sampleChannel(\n sampler: AnimationSampler,\n time: number,\n property: ChannelKind,\n): number[] | undefined {\n const { input, output, interpolation } = sampler;\n if (input.length === 0) return undefined;\n\n const elementCount = output.length / input.length;\n\n // Clamp if before first key.\n if (time <= (input[0] as number)) {\n return sliceOutput(output, 0, elementCount);\n }\n\n // Clamp if after last key.\n const lastIdx = input.length - 1;\n if (time >= (input[lastIdx] as number)) {\n return sliceOutput(output, lastIdx, elementCount);\n }\n\n // Binary search for the bracket.\n let lo = 0;\n let hi = input.length - 1;\n while (hi - lo > 1) {\n const mid = (lo + hi) >> 1;\n if ((input[mid] as number) <= time) {\n lo = mid;\n } else {\n hi = mid;\n }\n }\n const prev = lo;\n const next = hi;\n\n if (interpolation === 'STEP') {\n return sliceOutput(output, prev, elementCount);\n }\n\n // LINEAR interpolation.\n const t0 = input[prev] as number;\n const t1 = input[next] as number;\n const alpha = (time - t0) / (t1 - t0);\n\n const prevValues = sliceOutput(output, prev, elementCount);\n const nextValues = sliceOutput(output, next, elementCount);\n\n if (property === 'rotation') {\n // Per-sampler quat slerp at the bracket level — multi-slot blending is\n // a separate stage (nlerp at the entity level, in advanceAnimationPlayer).\n const px = prevValues[0] ?? 0;\n const py = prevValues[1] ?? 0;\n const pz = prevValues[2] ?? 0;\n const pw = prevValues[3] ?? 1;\n let nx = nextValues[0] ?? 0;\n let ny = nextValues[1] ?? 0;\n let nz = nextValues[2] ?? 0;\n let nw = nextValues[3] ?? 1;\n let dot = px * nx + py * ny + pz * nz + pw * nw;\n if (dot < 0) {\n nx = -nx;\n ny = -ny;\n nz = -nz;\n nw = -nw;\n dot = -dot;\n }\n if (dot > 0.9995) {\n // Near-parallel — fall back to nlerp to avoid sin(theta) -> 0 blowup.\n const lx = px + alpha * (nx - px);\n const ly = py + alpha * (ny - py);\n const lz = pz + alpha * (nz - pz);\n const lw = pw + alpha * (nw - pw);\n const len = Math.sqrt(lx * lx + ly * ly + lz * lz + lw * lw);\n return len > 0 ? [lx / len, ly / len, lz / len, lw / len] : [0, 0, 0, 1];\n }\n const theta = Math.acos(dot);\n const sinTheta = Math.sin(theta);\n const sa = Math.sin((1 - alpha) * theta) / sinTheta;\n const sb = Math.sin(alpha * theta) / sinTheta;\n return [px * sa + nx * sb, py * sa + ny * sb, pz * sa + nz * sb, pw * sa + nw * sb];\n }\n\n return prevValues.map((value, index) => value + alpha * ((nextValues[index] ?? value) - value));\n}\n\nfunction sliceOutput(output: Float32Array, index: number, elementCount: number): number[] {\n const result: number[] = [];\n const base = index * elementCount;\n for (let i = 0; i < elementCount; i++) {\n result.push(output[base + i] as number);\n }\n return result;\n}\n\n/**\n * The `advanceAnimationPlayer` system token.\n *\n * Module-level `defineSystem` with the real fn body — no closure and no\n * feature-specific resolver resource. Handles resolve from the World passed\n * to the system, so invalid clips become structured animation failures.\n */\nexport const AdvanceAnimationPlayer: SystemHandle<readonly []> = defineSystem({\n name: ADVANCE_ANIMATION_PLAYER_SYSTEM,\n queries: [],\n before: ['propagateTransforms'],\n fn: (world) => {\n advanceAnimationPlayer(world, world.getResource(Time).delta);\n },\n});\n\n/**\n * Register `advanceAnimationPlayer` into the ECS schedule before\n * `propagateTransforms`. Animation clip handles are resolved from the running\n * World; callers only need to register the system once per World.\n *\n * @example Driver registers once per World:\n * const world = new World();\n * registerAdvanceAnimationPlayer(world);\n * // ...system will run each world.update() before propagateTransforms...\n */\nexport function registerAdvanceAnimationPlayer(world: World): () => void {\n world.addSystems(Update, AnimationSet, [AdvanceAnimationPlayer]).unwrap();\n return () => {\n world.removeSystem(Update, ADVANCE_ANIMATION_PLAYER_SYSTEM);\n };\n}\n","// @forgeax/engine-animation -- AnimationPlayer cluster error classes.\n//\n// feat-20260713-animation-state-machine-plugin M1 / w5: the AnimationPlayer SoA\n// columns (clips / times / weights / speeds) became variable `array<T>` when the\n// fixed 4-slot cap was retired (w4). Variable columns are set field-by-field\n// (release-then-alloc per field, plan D-5), so the ECS layer cannot cross-check\n// that the four parallel columns stay length-synced. advanceAnimationPlayer\n// validates the four lengths once per row at its evaluation entry (the single\n// chokepoint, D-5) and rejects a mismatch with the structured error below rather\n// than silently zero-padding or truncating (requirements AC-11).\n\n// -- AnimationPlayerSlotLengthMismatchError --------------------------------------\n\n/**\n * Detail for `'animation-player-slot-length-mismatch'`.\n *\n * Carries the observed length of each of the four parallel SoA columns so an\n * AI consumer can see which column desynced without parsing prose.\n */\nexport interface AnimationPlayerSlotLengthMismatchDetail {\n readonly entity: number;\n readonly clips: number;\n readonly times: number;\n readonly weights: number;\n readonly speeds: number;\n}\n\n/**\n * Structured error for AnimationPlayer parallel-column length disagreement.\n *\n * Emitted at advanceAnimationPlayer's evaluation entry when an entity's\n * `clips` / `times` / `weights` / `speeds` columns do not all share the same\n * length. Four-field surface (mirrors the errors/skin.ts convention):\n * - `.code = 'animation-player-slot-length-mismatch'`\n * - `.expected` -- clips/times/weights/speeds share one length\n * - `.hint` -- write all four columns length-synced on every AnimationPlayer\n * write (variable columns do not tail-pad a short write)\n * - `.detail = { entity, clips, times, weights, speeds }`\n */\nexport class AnimationPlayerSlotLengthMismatchError extends Error {\n readonly code = 'animation-player-slot-length-mismatch' as const;\n readonly expected: string;\n readonly hint: string;\n readonly detail: AnimationPlayerSlotLengthMismatchDetail;\n\n constructor(detail: AnimationPlayerSlotLengthMismatchDetail) {\n const { entity, clips, times, weights, speeds } = detail;\n const expected = 'AnimationPlayer clips/times/weights/speeds columns share one length';\n const hint = `entity ${entity} AnimationPlayer columns are length-desynced (clips=${clips}, times=${times}, weights=${weights}, speeds=${speeds}); write all four parallel columns at the same length on every AnimationPlayer write -- a variable array<T> column does not tail-pad a short write`;\n super(\n `AnimationPlayer slot length mismatch on entity ${entity}: clips=${clips}, times=${times}, weights=${weights}, speeds=${speeds}`,\n );\n this.name = 'AnimationPlayerSlotLengthMismatchError';\n this.expected = expected;\n this.hint = hint;\n this.detail = detail;\n }\n}\n\n// -- AnimationPlayerErrorCode / AnimationPlayerError closed unions ----------------\n\n/**\n * Closed union of AnimationPlayer-cluster error codes. AI users perform\n * exhaustive `switch (err.code)` without default; TS guards completeness.\n */\nexport type AnimationPlayerErrorCode = 'animation-player-slot-length-mismatch';\n\n/**\n * Closed union of the AnimationPlayer-cluster structured error classes, each\n * carrying an `AnimationPlayerErrorCode` discriminant on `.code`.\n */\nexport type AnimationPlayerError = AnimationPlayerSlotLengthMismatchError;\n","// @forgeax/engine-animation -- evaluateAnimationGraph system (post-order DAG eval).\n//\n// feat-20260713-animation-state-machine-plugin M3 / w25 (plan D-2 independent\n// before-advance system, D-7 eval owns time):\n//\n// This is the \"graph -> N-slot\" seam. It runs BEFORE advanceAnimationPlayer (the\n// \"N-slot -> pose\" seam) and is the SOLE writer of the derived slot columns for\n// entities carrying an AnimationGraph handle (AnimationPlayer.graph != 0):\n//\n// 1. Resolve the shared<AnimationGraph> handle to its POD.\n// 2. Resolve every clip leaf's shared<AnimationClip> BEFORE writing anything --\n// a dangling handle throws the structured animation asset error so no\n// dirty pose is left (AC-04 / AC-11).\n// 3. Post-order evaluate effective weights: effective = runtime weight\n// (nodeWeights, default 1) x graph static weight (AC-07 orthogonal product);\n// Blend normalizes its children (AC-04), Add stacks additive layers without\n// normalization (AC-05), nesting propagates a subtree's normalized weight\n// down before its parent combines it (AC-06).\n// 4. Advance each clip node's seek-time by nodeSpeeds*dt (wrap/clamp reusing\n// advance's looping logic) and persist it back to nodeTimes (D-7: eval owns\n// the time SSOT).\n// 5. Spread one derived slot per clip leaf into the variable clips[] / times[]\n// / weights[] columns and park speeds[]=0 so advance does not re-advance the\n// time (D-7). advance's blend math is left completely unchanged (D-2).\n//\n// Entities without a graph (graph == 0) are left untouched -- their slots stay\n// the direct-write SSOT (single evaluation path, plan D-3). advance's mixing\n// math never branches on graph mode; it just consumes whatever fills the slots.\n\nimport type { EntityHandle, SystemHandle, World } from '@forgeax/engine-ecs';\nimport { defineSystem, Time, Update } from '@forgeax/engine-ecs';\nimport type { AnimationClip, AnimationGraph, Handle } from '@forgeax/engine-types';\nimport { AnimationPlayer } from '../animation-player';\nimport { resolveAnimationAsset } from '../resolve-animation-asset';\nimport { ADVANCE_ANIMATION_PLAYER_SYSTEM } from './advance-animation-player';\n\n/**\n * System name used when `registerEvaluateAnimationGraph` installs the system.\n * External consumers can reference this constant to declare ordering against the\n * graph-evaluation seam.\n */\nexport const EVALUATE_ANIMATION_GRAPH_SYSTEM = 'evaluateAnimationGraph' as const;\n\nexport type AnimationPayloadLookup = (\n guid: string,\n) => AnimationClip | { readonly code: 'stale' } | undefined;\n\n/**\n * Resolved per-entity graph-mode columns (a `world.get` snapshot). `graph` is the\n * raw shared handle (0 = no graph); the three node-* columns are per-node runtime\n * knobs indexed by graph node index.\n */\ninterface PlayerGraphColumns {\n readonly graph: number;\n readonly nodeWeights: Float32Array;\n readonly nodeTimes: Float32Array;\n readonly nodeSpeeds: Float32Array;\n readonly paused: boolean;\n readonly looping: boolean;\n}\n\n/** Read `arr[i]` when in range, else the default (missing per-node knobs default). */\nfunction readAt(arr: Float32Array, i: number, dflt: number): number {\n return i >= 0 && i < arr.length ? (arr[i] ?? dflt) : dflt;\n}\n\n/**\n * Wrap / clamp an advanced time against a clip duration -- byte-for-byte the same\n * rule advanceAnimationPlayer applies (looping = modulo into [0, duration);\n * non-looping = clamp to [0, duration]). Shared logic so eval and advance agree\n * on the seek-time domain (D-7).\n */\nfunction wrapTime(time: number, duration: number, looping: boolean): number {\n if (duration <= 0) return time;\n if (looping) {\n let wrapped = time % duration;\n if (wrapped < 0) wrapped += duration;\n return wrapped;\n }\n if (time > duration) return duration;\n if (time < 0) return 0;\n return time;\n}\n\n/**\n * Evaluate every graph-carrying AnimationPlayer for one frame, filling the\n * derived N-slot columns. Iterates rows through the system Query (like advance),\n * collecting entity handles first, then resolving + writing each player outside\n * the transient row facade.\n */\nexport function evaluateAnimationGraph(\n world: World,\n dt: number,\n lookup: AnimationPayloadLookup = () => undefined,\n): void {\n const query = world.query({ with: [AnimationPlayer] }).unwrap();\n\n const entities: EntityHandle[] = [];\n for (const row of query) entities.push(row.entity);\n\n for (const entityRaw of entities) {\n evaluateOneEntity(world, entityRaw, dt, lookup);\n }\n}\n\n/**\n * Evaluate one entity's AnimationGraph into its derived slots. No-op when the\n * entity has no graph (graph == 0). Every non-zero graph or clip handle is\n * resolved through the World-local animation lookup and invalid handles throw\n * a structured animation error before any derived write (AC-04 / AC-11).\n */\nfunction evaluateOneEntity(\n world: World,\n entityRaw: number,\n dt: number,\n lookup: AnimationPayloadLookup,\n): void {\n const entity = entityRaw as EntityHandle;\n const apRes = world.get(entity, AnimationPlayer);\n if (!apRes.ok) return;\n const ap = apRes.value as unknown as PlayerGraphColumns;\n\n const graphRaw = ap.graph;\n if (graphRaw === 0) return; // no graph -> direct-write path, untouched.\n\n const graphLookup = world.sharedRefs.resolve<string, AnimationGraph>(\n graphRaw as Handle<string, 'shared'>,\n );\n if (!graphLookup.ok) throw graphLookup.error;\n const graph = graphLookup.value;\n\n const nodes = graph.nodes;\n if (nodes.length === 0) return; // construction rejects empty graphs; guard anyway.\n\n // Collect clip leaves (ascending node index) and resolve every clip BEFORE any\n // write (AC-11: a dangling handle must not leave a dirty pose).\n const clipNodeIndices: number[] = [];\n const clipHandles: Handle<'AnimationClip', 'shared'>[] = [];\n const clipDurations: number[] = [];\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n if (node === undefined || node.type !== 'clip') continue;\n const clipLookup = resolveAnimationAsset<AnimationClip>(\n world,\n node.clip,\n 'animation-clip',\n (guid) => {\n return lookup(guid);\n },\n );\n if (!clipLookup.ok) throw clipLookup.error;\n const handle = clipLookup.value.handle;\n clipNodeIndices.push(i);\n clipHandles.push(handle);\n clipDurations.push(clipLookup.value.asset.duration);\n }\n\n // Post-order effective-weight evaluation from the root (incoming influence 1).\n const effByNode = new Map<number, number>();\n const runtimeWeight = (n: number): number => readAt(ap.nodeWeights, n, 1);\n const evalNode = (nodeIndex: number, incoming: number): void => {\n const node = nodes[nodeIndex];\n if (node === undefined) return;\n const eff = incoming * runtimeWeight(nodeIndex) * node.weight;\n switch (node.type) {\n case 'clip':\n effByNode.set(nodeIndex, (effByNode.get(nodeIndex) ?? 0) + eff);\n return;\n case 'blend': {\n let total = 0;\n for (const child of node.children)\n total += runtimeWeight(child) * (nodes[child]?.weight ?? 0);\n if (total > 0) {\n for (const child of node.children) evalNode(child, eff / total);\n }\n return;\n }\n case 'add':\n evalNode(node.base, eff);\n for (const layer of node.additive) evalNode(layer, eff);\n return;\n }\n };\n evalNode(graph.root, 1);\n\n // Advance each clip node's seek-time (D-7: eval owns the time). Persist the full\n // per-node time column so it stays bounded frame-to-frame.\n const newNodeTimes = new Float32Array(nodes.length);\n for (let i = 0; i < nodes.length; i++) newNodeTimes[i] = readAt(ap.nodeTimes, i, 0);\n for (let k = 0; k < clipNodeIndices.length; k++) {\n // biome-ignore lint/style/noNonNullAssertion: parallel to clipNodeIndices\n const nodeIndex = clipNodeIndices[k]!;\n // biome-ignore lint/style/noNonNullAssertion: parallel to clipNodeIndices\n const duration = clipDurations[k]!;\n const current = readAt(ap.nodeTimes, nodeIndex, 0);\n const speed = readAt(ap.nodeSpeeds, nodeIndex, 0);\n const advanced = ap.paused ? current : current + speed * dt;\n newNodeTimes[nodeIndex] = wrapTime(advanced, duration, ap.looping);\n }\n\n // Spread one derived slot per clip leaf into the variable columns; speeds[]=0\n // parks the time so advance does not re-advance it (D-7).\n const slotCount = clipNodeIndices.length;\n const times = new Float32Array(slotCount);\n const weights = new Float32Array(slotCount);\n const speeds = new Float32Array(slotCount);\n for (let k = 0; k < slotCount; k++) {\n // biome-ignore lint/style/noNonNullAssertion: parallel to clipNodeIndices\n const nodeIndex = clipNodeIndices[k]!;\n weights[k] = effByNode.get(nodeIndex) ?? 0;\n times[k] = newNodeTimes[nodeIndex] ?? 0;\n speeds[k] = 0;\n }\n\n world.set(entity, AnimationPlayer, {\n clips: clipHandles,\n times,\n weights,\n speeds,\n nodeTimes: newNodeTimes,\n });\n}\n\n/**\n * The `evaluateAnimationGraph` system token. Runs `before` advanceAnimationPlayer\n * (plan D-2 / R-5) so the derived slots are ready when advance blends them, and\n * is labelled `'animation'`. Registered by the default `animationPlugin` (w26),\n * so it is on the default path (AC-09), not opt-in.\n */\nexport const EvaluateAnimationGraph: SystemHandle<readonly []> = defineSystem({\n name: EVALUATE_ANIMATION_GRAPH_SYSTEM,\n queries: [],\n before: [ADVANCE_ANIMATION_PLAYER_SYSTEM],\n fn: (world) => {\n evaluateAnimationGraph(world, world.getResource(Time).delta);\n },\n});\n\n/**\n * Register `evaluateAnimationGraph` into the ECS schedule (before\n * advanceAnimationPlayer). Called by `animationPlugin` so both createApp forms\n * get graph evaluation on the default path for free.\n */\nexport function registerEvaluateAnimationGraph(\n world: World,\n lookup: AnimationPayloadLookup = () => undefined,\n): () => void {\n world\n .addSystem(Update, {\n name: EVALUATE_ANIMATION_GRAPH_SYSTEM,\n queries: [],\n before: [ADVANCE_ANIMATION_PLAYER_SYSTEM],\n fn: (world) => {\n evaluateAnimationGraph(world, world.getResource(Time).delta, lookup);\n },\n })\n .unwrap();\n return () => {\n world.removeSystem(Update, EVALUATE_ANIMATION_GRAPH_SYSTEM);\n };\n}\n","import type { World } from '@forgeax/engine-ecs';\nimport type { Asset, Handle, Result } from '@forgeax/engine-types';\nimport { err, ok } from '@forgeax/engine-types';\n\n/** Closed animation-domain failure codes for durable GUID projection. */\nexport type AnimationAssetErrorCode =\n | 'animation-asset-not-found'\n | 'animation-asset-stale'\n | 'animation-asset-kind-mismatch';\n\nexport interface AnimationAssetErrorDetail {\n readonly guid: string;\n readonly expectedKind: string;\n readonly actualKind?: string;\n readonly lookupCode: string;\n}\n\ntype AnimationAssetTarget<T extends Asset> = T extends { readonly kind: 'animation-clip' }\n ? 'AnimationClip'\n : 'AnimationGraph';\n\n/** Structured failure at the animation consumer boundary. */\nexport class AnimationAssetError extends Error {\n readonly code: AnimationAssetErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail: AnimationAssetErrorDetail;\n\n constructor(args: {\n code: AnimationAssetErrorCode;\n expected: string;\n hint: string;\n detail: AnimationAssetErrorDetail;\n }) {\n super(`[AnimationAssetError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'AnimationAssetError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n this.detail = args.detail;\n }\n}\n\nexport interface ResolvedAnimationAsset<T extends Asset> {\n readonly guid: string;\n readonly asset: T;\n readonly handle: Handle<AnimationAssetTarget<T>, 'shared'>;\n}\n\nexport type AnimationAssetLookup<T extends Asset> = T | { readonly code: 'stale' } | undefined;\n\n/**\n * Resolve a durable GUID through the caller's existing payload owner, then\n * project that payload into the current World. The callback is deliberately a\n * lookup seam, not a registry: AssetRegistry remains GUID -> payload only.\n */\nexport function resolveAnimationAsset<T extends Asset>(\n world: World,\n guid: string,\n expectedKind: T['kind'],\n lookup: (guid: string) => AnimationAssetLookup<T>,\n): Result<ResolvedAnimationAsset<T>, AnimationAssetError> {\n if (guid.length === 0) {\n return err(\n new AnimationAssetError({\n code: 'animation-asset-not-found',\n expected: `a durable ${expectedKind} GUID`,\n hint: 'load the animation asset by GUID before evaluating the graph',\n detail: { guid, expectedKind, lookupCode: 'guid-empty' },\n }),\n );\n }\n let asset: AnimationAssetLookup<T>;\n try {\n asset = lookup(guid);\n } catch {\n return err(\n new AnimationAssetError({\n code: 'animation-asset-not-found',\n expected: `a loaded ${expectedKind} payload for GUID ${guid}`,\n hint: 'repair the animation asset lookup provider and retry resolution',\n detail: { guid, expectedKind, lookupCode: 'lookup-threw' },\n }),\n );\n }\n if (asset !== undefined && 'code' in asset && asset.code === 'stale') {\n return err(\n new AnimationAssetError({\n code: 'animation-asset-stale',\n expected: `a current ${expectedKind} payload for GUID ${guid}`,\n hint: 'rebuild the stale asset projection before evaluating the graph',\n detail: { guid, expectedKind, lookupCode: 'asset-stale' },\n }),\n );\n }\n if (asset === undefined) {\n return err(\n new AnimationAssetError({\n code: 'animation-asset-not-found',\n expected: `a loaded ${expectedKind} payload for GUID ${guid}`,\n hint: 'load or retain the referenced animation asset before evaluating the graph',\n detail: { guid, expectedKind, lookupCode: 'asset-not-found' },\n }),\n );\n }\n const payload = asset as T;\n if (payload.kind !== expectedKind) {\n return err(\n new AnimationAssetError({\n code: 'animation-asset-kind-mismatch',\n expected: `asset kind '${expectedKind}'`,\n hint: `replace GUID ${guid} with a loaded ${expectedKind} asset`,\n detail: {\n guid,\n expectedKind,\n actualKind: payload.kind,\n lookupCode: 'asset-kind-mismatch',\n },\n }),\n );\n }\n const target = expectedKind === 'animation-clip' ? 'AnimationClip' : 'AnimationGraph';\n const handle = world.internSharedRef(target, payload) as Handle<\n AnimationAssetTarget<T>,\n 'shared'\n >;\n return ok({ guid, asset: payload, handle });\n}\n","import type { Component, World } from '@forgeax/engine-ecs';\nimport type { Plugin } from '@forgeax/engine-plugin';\nimport { AnimationPlayer } from './animation-player';\nimport { AnimatedBy, AnimationTargetId, AnimationTargets } from './animation-target';\nimport { registerAdvanceAnimationPlayer } from './systems/advance-animation-player';\nimport {\n type AnimationPayloadLookup,\n registerEvaluateAnimationGraph,\n} from './systems/evaluate-animation-graph';\n\nconst ANIMATION_COMPONENTS: readonly Component[] = [\n AnimationPlayer,\n AnimatedBy,\n AnimationTargetId,\n AnimationTargets,\n];\n\nfunction registerAnimationComponents(world: World): () => void {\n const leases = ANIMATION_COMPONENTS.map((component) =>\n world.components.register(component).unwrap(),\n );\n return () => {\n for (let index = leases.length - 1; index >= 0; index -= 1) leases[index]?.dispose();\n };\n}\n\ndeclare module '@forgeax/engine-plugin' {\n interface EngineContextServices {\n animationPayloads?: AnimationPayloadLookup;\n }\n}\n\n/** Provide the realm's asset-backed animation payload resolver. */\nexport function animationPayloadsPlugin(lookup: AnimationPayloadLookup): Plugin {\n return {\n name: 'animation-payloads',\n provide: 'animationPayloads',\n apply(ctx) {\n ctx.provide('animationPayloads', lookup);\n },\n };\n}\n\n/** Install animation behavior from declared World and payload services. */\nexport function animationRuntimePlugin(): Plugin {\n return {\n name: 'animation',\n inject: ['world', 'animationPayloads'],\n apply(ctx) {\n ctx.effect(() => registerAnimationComponents(ctx.world), 'animation/components');\n ctx.effect(\n () => registerEvaluateAnimationGraph(ctx.world, ctx.animationPayloads),\n 'animation/evaluate-graph',\n );\n ctx.effect(() => registerAdvanceAnimationPlayer(ctx.world), 'animation/advance-player');\n },\n };\n}\n\nexport function animationPlugin(lookup?: AnimationPayloadLookup): Plugin {\n return {\n name: 'animation',\n inject: ['world'],\n apply(ctx) {\n ctx.effect(() => registerAnimationComponents(ctx.world), 'animation/components');\n ctx.effect(\n () => registerEvaluateAnimationGraph(ctx.world, lookup),\n 'animation/evaluate-graph',\n );\n ctx.effect(() => registerAdvanceAnimationPlayer(ctx.world), 'animation/advance-player');\n },\n };\n}\n"],"mappings":"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","names":["defineComponent","defineComponent","err","ok","err","ok","err","ok","Transform","_resetAnimationWarnsForTests","Transform","defineSystem","Time","Update","err","ok","defineSystem","Time","Update","world"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detail for `'animation-player-slot-length-mismatch'`.
|
|
3
|
+
*
|
|
4
|
+
* Carries the observed length of each of the four parallel SoA columns so an
|
|
5
|
+
* AI consumer can see which column desynced without parsing prose.
|
|
6
|
+
*/
|
|
7
|
+
export interface AnimationPlayerSlotLengthMismatchDetail {
|
|
8
|
+
readonly entity: number;
|
|
9
|
+
readonly clips: number;
|
|
10
|
+
readonly times: number;
|
|
11
|
+
readonly weights: number;
|
|
12
|
+
readonly speeds: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Structured error for AnimationPlayer parallel-column length disagreement.
|
|
16
|
+
*
|
|
17
|
+
* Emitted at advanceAnimationPlayer's evaluation entry when an entity's
|
|
18
|
+
* `clips` / `times` / `weights` / `speeds` columns do not all share the same
|
|
19
|
+
* length. Four-field surface (mirrors the errors/skin.ts convention):
|
|
20
|
+
* - `.code = 'animation-player-slot-length-mismatch'`
|
|
21
|
+
* - `.expected` -- clips/times/weights/speeds share one length
|
|
22
|
+
* - `.hint` -- write all four columns length-synced on every AnimationPlayer
|
|
23
|
+
* write (variable columns do not tail-pad a short write)
|
|
24
|
+
* - `.detail = { entity, clips, times, weights, speeds }`
|
|
25
|
+
*/
|
|
26
|
+
export declare class AnimationPlayerSlotLengthMismatchError extends Error {
|
|
27
|
+
readonly code: "animation-player-slot-length-mismatch";
|
|
28
|
+
readonly expected: string;
|
|
29
|
+
readonly hint: string;
|
|
30
|
+
readonly detail: AnimationPlayerSlotLengthMismatchDetail;
|
|
31
|
+
constructor(detail: AnimationPlayerSlotLengthMismatchDetail);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Closed union of AnimationPlayer-cluster error codes. AI users perform
|
|
35
|
+
* exhaustive `switch (err.code)` without default; TS guards completeness.
|
|
36
|
+
*/
|
|
37
|
+
export type AnimationPlayerErrorCode = 'animation-player-slot-length-mismatch';
|
|
38
|
+
/**
|
|
39
|
+
* Closed union of the AnimationPlayer-cluster structured error classes, each
|
|
40
|
+
* carrying an `AnimationPlayerErrorCode` discriminant on `.code`.
|
|
41
|
+
*/
|
|
42
|
+
export type AnimationPlayerError = AnimationPlayerSlotLengthMismatchError;
|
|
43
|
+
//# sourceMappingURL=player-errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player-errors.d.ts","sourceRoot":"","sources":["../src/player-errors.ts"],"names":[],"mappings":"AAaA;;;;;GAKG;AACH,MAAM,WAAW,uCAAuC;IACtD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,sCAAuC,SAAQ,KAAK;IAC/D,QAAQ,CAAC,IAAI,EAAG,uCAAuC,CAAU;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,uCAAuC,CAAC;gBAE7C,MAAM,EAAE,uCAAuC;CAY5D;AAID;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG,uCAAuC,CAAC;AAE/E;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,sCAAsC,CAAC"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Plugin } from '@forgeax/engine-plugin';
|
|
2
|
+
import { type AnimationPayloadLookup } from './systems/evaluate-animation-graph';
|
|
3
|
+
declare module '@forgeax/engine-plugin' {
|
|
4
|
+
interface EngineContextServices {
|
|
5
|
+
animationPayloads?: AnimationPayloadLookup;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/** Provide the realm's asset-backed animation payload resolver. */
|
|
9
|
+
export declare function animationPayloadsPlugin(lookup: AnimationPayloadLookup): Plugin;
|
|
10
|
+
/** Install animation behavior from declared World and payload services. */
|
|
11
|
+
export declare function animationRuntimePlugin(): Plugin;
|
|
12
|
+
export declare function animationPlugin(lookup?: AnimationPayloadLookup): Plugin;
|
|
13
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAIrD,OAAO,EACL,KAAK,sBAAsB,EAE5B,MAAM,oCAAoC,CAAC;AAkB5C,OAAO,QAAQ,wBAAwB,CAAC;IACtC,UAAU,qBAAqB;QAC7B,iBAAiB,CAAC,EAAE,sBAAsB,CAAC;KAC5C;CACF;AAED,mEAAmE;AACnE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAQ9E;AAED,2EAA2E;AAC3E,wBAAgB,sBAAsB,IAAI,MAAM,CAa/C;AAED,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAavE"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { World } from '@forgeax/engine-ecs';
|
|
2
|
+
import type { Asset, Handle, Result } from '@forgeax/engine-types';
|
|
3
|
+
/** Closed animation-domain failure codes for durable GUID projection. */
|
|
4
|
+
export type AnimationAssetErrorCode = 'animation-asset-not-found' | 'animation-asset-stale' | 'animation-asset-kind-mismatch';
|
|
5
|
+
export interface AnimationAssetErrorDetail {
|
|
6
|
+
readonly guid: string;
|
|
7
|
+
readonly expectedKind: string;
|
|
8
|
+
readonly actualKind?: string;
|
|
9
|
+
readonly lookupCode: string;
|
|
10
|
+
}
|
|
11
|
+
type AnimationAssetTarget<T extends Asset> = T extends {
|
|
12
|
+
readonly kind: 'animation-clip';
|
|
13
|
+
} ? 'AnimationClip' : 'AnimationGraph';
|
|
14
|
+
/** Structured failure at the animation consumer boundary. */
|
|
15
|
+
export declare class AnimationAssetError extends Error {
|
|
16
|
+
readonly code: AnimationAssetErrorCode;
|
|
17
|
+
readonly expected: string;
|
|
18
|
+
readonly hint: string;
|
|
19
|
+
readonly detail: AnimationAssetErrorDetail;
|
|
20
|
+
constructor(args: {
|
|
21
|
+
code: AnimationAssetErrorCode;
|
|
22
|
+
expected: string;
|
|
23
|
+
hint: string;
|
|
24
|
+
detail: AnimationAssetErrorDetail;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export interface ResolvedAnimationAsset<T extends Asset> {
|
|
28
|
+
readonly guid: string;
|
|
29
|
+
readonly asset: T;
|
|
30
|
+
readonly handle: Handle<AnimationAssetTarget<T>, 'shared'>;
|
|
31
|
+
}
|
|
32
|
+
export type AnimationAssetLookup<T extends Asset> = T | {
|
|
33
|
+
readonly code: 'stale';
|
|
34
|
+
} | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Resolve a durable GUID through the caller's existing payload owner, then
|
|
37
|
+
* project that payload into the current World. The callback is deliberately a
|
|
38
|
+
* lookup seam, not a registry: AssetRegistry remains GUID -> payload only.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveAnimationAsset<T extends Asset>(world: World, guid: string, expectedKind: T['kind'], lookup: (guid: string) => AnimationAssetLookup<T>): Result<ResolvedAnimationAsset<T>, AnimationAssetError>;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=resolve-animation-asset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-animation-asset.d.ts","sourceRoot":"","sources":["../src/resolve-animation-asset.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAGnE,yEAAyE;AACzE,MAAM,MAAM,uBAAuB,GAC/B,2BAA2B,GAC3B,uBAAuB,GACvB,+BAA+B,CAAC;AAEpC,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,KAAK,oBAAoB,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,GACtF,eAAe,GACf,gBAAgB,CAAC;AAErB,6DAA6D;AAC7D,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,yBAAyB,CAAC;gBAE/B,IAAI,EAAE;QAChB,IAAI,EAAE,uBAAuB,CAAC;QAC9B,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,yBAAyB,CAAC;KACnC;CAQF;AAED,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,KAAK;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;CAC5D;AAED,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAAC;AAE/F;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,KAAK,EACnD,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,oBAAoB,CAAC,CAAC,CAAC,GAChD,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAkExD"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { SystemHandle, World } from '@forgeax/engine-ecs';
|
|
2
|
+
/**
|
|
3
|
+
* System name used when `registerAdvanceAnimationPlayer` installs the system
|
|
4
|
+
* into the ECS schedule. External consumers can reference this constant to
|
|
5
|
+
* declare `after: [ADVANCE_ANIMATION_PLAYER_SYSTEM]` on dependent systems.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ADVANCE_ANIMATION_PLAYER_SYSTEM: "advanceAnimationPlayer";
|
|
8
|
+
export declare const AnimationSet: import("@forgeax/engine-ecs").SystemSet;
|
|
9
|
+
export declare function _resetAnimationWarnsForTests(world: World): void;
|
|
10
|
+
/**
|
|
11
|
+
* Advance all AnimationPlayer components by dt, blend the N active clips per
|
|
12
|
+
* entity, and write one Transform per joint per tick. Returns void.
|
|
13
|
+
*
|
|
14
|
+
* Iteration walks every matching row through the injected Query (D-3). Variable
|
|
15
|
+
* SoA columns are read through the row facade, so entity handles are collected first, then each
|
|
16
|
+
* entity's columns are resolved to TypedArrays via `world.get` and the advanced
|
|
17
|
+
* `times` written back via `world.set` (mutating the query bundle mid-walk would
|
|
18
|
+
* be a compile error against the reader shape).
|
|
19
|
+
*/
|
|
20
|
+
export declare function advanceAnimationPlayer(world: World, dt: number): void;
|
|
21
|
+
/**
|
|
22
|
+
* The `advanceAnimationPlayer` system token.
|
|
23
|
+
*
|
|
24
|
+
* Module-level `defineSystem` with the real fn body — no closure and no
|
|
25
|
+
* feature-specific resolver resource. Handles resolve from the World passed
|
|
26
|
+
* to the system, so invalid clips become structured animation failures.
|
|
27
|
+
*/
|
|
28
|
+
export declare const AdvanceAnimationPlayer: SystemHandle<readonly []>;
|
|
29
|
+
/**
|
|
30
|
+
* Register `advanceAnimationPlayer` into the ECS schedule before
|
|
31
|
+
* `propagateTransforms`. Animation clip handles are resolved from the running
|
|
32
|
+
* World; callers only need to register the system once per World.
|
|
33
|
+
*
|
|
34
|
+
* @example Driver registers once per World:
|
|
35
|
+
* const world = new World();
|
|
36
|
+
* registerAdvanceAnimationPlayer(world);
|
|
37
|
+
* // ...system will run each world.update() before propagateTransforms...
|
|
38
|
+
*/
|
|
39
|
+
export declare function registerAdvanceAnimationPlayer(world: World): () => void;
|
|
40
|
+
//# sourceMappingURL=advance-animation-player.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advance-animation-player.d.ts","sourceRoot":"","sources":["../../src/systems/advance-animation-player.ts"],"names":[],"mappings":"AAmDA,OAAO,KAAK,EAAgB,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAe7E;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,EAAG,wBAAiC,CAAC;AACjF,eAAO,MAAM,YAAY,yCAAyC,CAAC;AAEnE,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAG/D;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAWrE;AAu0BD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,YAAY,CAAC,SAAS,EAAE,CAO3D,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,IAAI,CAKvE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { SystemHandle, World } from '@forgeax/engine-ecs';
|
|
2
|
+
import type { AnimationClip } from '@forgeax/engine-types';
|
|
3
|
+
/**
|
|
4
|
+
* System name used when `registerEvaluateAnimationGraph` installs the system.
|
|
5
|
+
* External consumers can reference this constant to declare ordering against the
|
|
6
|
+
* graph-evaluation seam.
|
|
7
|
+
*/
|
|
8
|
+
export declare const EVALUATE_ANIMATION_GRAPH_SYSTEM: "evaluateAnimationGraph";
|
|
9
|
+
export type AnimationPayloadLookup = (guid: string) => AnimationClip | {
|
|
10
|
+
readonly code: 'stale';
|
|
11
|
+
} | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Evaluate every graph-carrying AnimationPlayer for one frame, filling the
|
|
14
|
+
* derived N-slot columns. Iterates rows through the system Query (like advance),
|
|
15
|
+
* collecting entity handles first, then resolving + writing each player outside
|
|
16
|
+
* the transient row facade.
|
|
17
|
+
*/
|
|
18
|
+
export declare function evaluateAnimationGraph(world: World, dt: number, lookup?: AnimationPayloadLookup): void;
|
|
19
|
+
/**
|
|
20
|
+
* The `evaluateAnimationGraph` system token. Runs `before` advanceAnimationPlayer
|
|
21
|
+
* (plan D-2 / R-5) so the derived slots are ready when advance blends them, and
|
|
22
|
+
* is labelled `'animation'`. Registered by the default `animationPlugin` (w26),
|
|
23
|
+
* so it is on the default path (AC-09), not opt-in.
|
|
24
|
+
*/
|
|
25
|
+
export declare const EvaluateAnimationGraph: SystemHandle<readonly []>;
|
|
26
|
+
/**
|
|
27
|
+
* Register `evaluateAnimationGraph` into the ECS schedule (before
|
|
28
|
+
* advanceAnimationPlayer). Called by `animationPlugin` so both createApp forms
|
|
29
|
+
* get graph evaluation on the default path for free.
|
|
30
|
+
*/
|
|
31
|
+
export declare function registerEvaluateAnimationGraph(world: World, lookup?: AnimationPayloadLookup): () => void;
|
|
32
|
+
//# sourceMappingURL=evaluate-animation-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate-animation-graph.d.ts","sourceRoot":"","sources":["../../src/systems/evaluate-animation-graph.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAgB,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAE7E,OAAO,KAAK,EAAE,aAAa,EAA0B,MAAM,uBAAuB,CAAC;AAKnF;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,EAAG,wBAAiC,CAAC;AAEjF,MAAM,MAAM,sBAAsB,GAAG,CACnC,IAAI,EAAE,MAAM,KACT,aAAa,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAAC;AAuC5D;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,EAAE,EAAE,MAAM,EACV,MAAM,GAAE,sBAAwC,GAC/C,IAAI,CASN;AAwHD;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,EAAE,YAAY,CAAC,SAAS,EAAE,CAO3D,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,KAAK,EACZ,MAAM,GAAE,sBAAwC,GAC/C,MAAM,IAAI,CAcZ"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AnimationTargetIdValue } from '@forgeax/engine-types';
|
|
2
|
+
export declare function deriveAnimationTargetId(path: readonly string[]): AnimationTargetIdValue;
|
|
3
|
+
export declare function isAnimationTargetId(value: unknown): value is AnimationTargetIdValue;
|
|
4
|
+
//# sourceMappingURL=target-id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"target-id.d.ts","sourceRoot":"","sources":["../src/target-id.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAwBpE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,sBAAsB,CAsBvF;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,sBAAsB,CAEnF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forgeax/engine-animation",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"description": "Animation graph and playback domain.",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"./target-id": {
|
|
15
|
+
"types": "./dist/target-id.d.ts",
|
|
16
|
+
"import": "./dist/target-id.mjs"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/index.mjs",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"src",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@forgeax/engine-ecs": "0.1.2",
|
|
30
|
+
"@forgeax/engine-plugin": "0.1.2",
|
|
31
|
+
"@forgeax/engine-scene": "0.1.2",
|
|
32
|
+
"@forgeax/engine-skinning": "0.1.2",
|
|
33
|
+
"@forgeax/engine-types": "0.1.2",
|
|
34
|
+
"@noble/hashes": "^2.2.0"
|
|
35
|
+
},
|
|
36
|
+
"forgeax": {
|
|
37
|
+
"metrics": {
|
|
38
|
+
"bundle-size": {
|
|
39
|
+
"enabled": true,
|
|
40
|
+
"path": "dist/index.mjs",
|
|
41
|
+
"compression": "gzip"
|
|
42
|
+
},
|
|
43
|
+
"fps": {
|
|
44
|
+
"enabled": false,
|
|
45
|
+
"reason": "headless animation package"
|
|
46
|
+
},
|
|
47
|
+
"bench": {
|
|
48
|
+
"enabled": false,
|
|
49
|
+
"reason": "animation is validated by unit tests"
|
|
50
|
+
},
|
|
51
|
+
"gate": {
|
|
52
|
+
"enabled": false,
|
|
53
|
+
"reason": "no package binary gate"
|
|
54
|
+
},
|
|
55
|
+
"spike-report": {
|
|
56
|
+
"enabled": false,
|
|
57
|
+
"reason": "not a spike package"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "tsup",
|
|
63
|
+
"test": "vitest run"
|
|
64
|
+
}
|
|
65
|
+
}
|