@rig-c/play 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,3 +1,11 @@
1
+ const liveDrawings = /* @__PURE__ */ new WeakMap();
2
+ function renderFrameOf(frame2) {
3
+ if ("order" in frame2) return frame2;
4
+ void frame2.generation;
5
+ const drawing = liveDrawings.get(frame2);
6
+ if (!drawing) throw new Error("LIVE_FRAME_SOURCE: renderer needs a liveFrame() borrowed from a player");
7
+ return drawing;
8
+ }
1
9
  const SCALE_PROBE_PX = 100;
2
10
  const PIXEL_RATIO_DEADBAND = 1e-3;
3
11
  function syncedPixelRatio(root, current) {
@@ -27,15 +35,15 @@ function syncedPixelRatio(root, current) {
27
35
  return next;
28
36
  }
29
37
  const RUNTIME_PI = 3.1415927;
30
- const RAD$3 = RUNTIME_PI / 180;
38
+ const RAD$4 = RUNTIME_PI / 180;
31
39
  const RUNTIME_DEG = 180 / RUNTIME_PI;
32
40
  const COLLAPSED_X_AXIS_SQ = 1e-5 * 1e-5;
33
41
  const RUNTIME_ARITHMETIC = {
34
- radiansPerDegree: RAD$3,
42
+ radiansPerDegree: RAD$4,
35
43
  degreesOf: (radians) => radians * RUNTIME_DEG,
36
44
  rotationOnlyFrame: false
37
45
  };
38
- function frame(rotation, shearX, shearY, scaleX, scaleY, rad = RAD$3) {
46
+ function frame(rotation, shearX, shearY, scaleX, scaleY, rad = RAD$4) {
39
47
  const xAngle = (rotation + shearX) * rad;
40
48
  const yAngle = (rotation + 90 + shearY) * rad;
41
49
  return [Math.cos(xAngle) * scaleX, Math.cos(yAngle) * scaleY, Math.sin(xAngle) * scaleX, Math.sin(yAngle) * scaleY];
@@ -52,7 +60,7 @@ function localFrame(arithmetic, rotation, shearX, shearY, scaleX, scaleY) {
52
60
  function times(p, q) {
53
61
  return [p[0] * q[0] + p[1] * q[2], p[0] * q[1] + p[1] * q[3], p[2] * q[0] + p[3] * q[2], p[2] * q[1] + p[3] * q[3]];
54
62
  }
55
- function noScaleDirection(p, rotation, rad = RAD$3) {
63
+ function noScaleDirection(p, rotation, rad = RAD$4) {
56
64
  const r = rotation * rad;
57
65
  const cos = Math.cos(r);
58
66
  const sin = Math.sin(r);
@@ -584,7 +592,10 @@ function payload(raw, at, problems) {
584
592
  if (typeof raw.string !== "string") problems.push(`${at}: string is ${JSON.stringify(raw.string)}, not a string`);
585
593
  else out.string = raw.string;
586
594
  }
587
- for (const key of ["volume", "balance"]) if (raw[key] !== void 0 && (typeof raw[key] !== "number" || !Number.isFinite(raw[key]))) problems.push(`${at}: ${key} is ${JSON.stringify(raw[key])}, not a finite number`);
595
+ for (const key of ["volume", "balance"]) if (raw[key] !== void 0) {
596
+ if (typeof raw[key] !== "number" || !Number.isFinite(raw[key])) problems.push(`${at}: ${key} is ${JSON.stringify(raw[key])}, not a finite number`);
597
+ else out[key] = raw[key];
598
+ }
588
599
  return out;
589
600
  }
590
601
  function readEventDefs(value, problems) {
@@ -603,7 +614,7 @@ function readEventDefs(value, problems) {
603
614
  if (raw.audio !== void 0 && typeof raw.audio !== "string") problems.push(`${at} "${raw.name}": audio is ${JSON.stringify(raw.audio)}, not a string`);
604
615
  if (out.has(raw.name)) problems.push(`${at}: event "${raw.name}" is declared twice`);
605
616
  const p = payload(raw, `${at} "${raw.name}"`, problems);
606
- out.set(raw.name, { name: raw.name, int: p.int ?? 0, float: p.float ?? 0, string: p.string ?? "" });
617
+ out.set(raw.name, { audio: typeof raw.audio === "string" ? raw.audio : "", volume: p.volume ?? 0, balance: p.balance ?? 0, name: raw.name, int: p.int ?? 0, float: p.float ?? 0, string: p.string ?? "" });
607
618
  });
608
619
  return out;
609
620
  }
@@ -632,7 +643,7 @@ function readEventKeys(value, label, defs, problems) {
632
643
  return;
633
644
  }
634
645
  const p = payload(raw, at, problems);
635
- out.push({ time: Math.fround(typeof t === "number" && Number.isFinite(t) ? t : 0), name: def.name, int: p.int ?? def.int, float: p.float ?? def.float, string: p.string ?? def.string });
646
+ out.push({ volume: def.audio ? p.volume ?? def.volume ?? 0 : 0, balance: def.audio ? p.balance ?? def.volume ?? 0 : 0, time: Math.fround(typeof t === "number" && Number.isFinite(t) ? t : 0), name: def.name, int: p.int ?? def.int, float: p.float ?? def.float, string: p.string ?? def.string });
636
647
  });
637
648
  return out;
638
649
  }
@@ -1017,7 +1028,7 @@ function clipThrough(shape, vertices, triangles, uvs, clip = clipTriangles, read
1017
1028
  }
1018
1029
  return { clipped, vertices: outV, uvs: outUv, triangles: outT, sources };
1019
1030
  }
1020
- const RAD$2 = RUNTIME_PI / 180;
1031
+ const RAD$3 = RUNTIME_PI / 180;
1021
1032
  const meshArraysByGeometry = /* @__PURE__ */ new WeakMap();
1022
1033
  function meshArraysOf(mesh) {
1023
1034
  let kept = meshArraysByGeometry.get(mesh);
@@ -1039,7 +1050,7 @@ function regionCorners(region, bone2) {
1039
1050
  const y1 = -H / 2 * sy + r.offsetY * ky;
1040
1051
  const x2 = x1 + r.width * kx;
1041
1052
  const y2 = y1 + r.height * ky;
1042
- const angle = (region.rotation ?? 0) * RAD$2;
1053
+ const angle = (region.rotation ?? 0) * RAD$3;
1043
1054
  const cos = Math.cos(angle);
1044
1055
  const sin = Math.sin(angle);
1045
1056
  const x = region.x ?? 0;
@@ -1052,25 +1063,64 @@ function regionCorners(region, bone2) {
1052
1063
  }
1053
1064
  return out;
1054
1065
  }
1055
- function worldVertices(vertices, bone2, world, coords = Math.fround) {
1056
- const out = [];
1057
- if (!vertices.weighted) {
1058
- for (let i = 0; i + 1 < vertices.xy.length; i += 2) {
1059
- const x = coords(vertices.xy[i]);
1060
- const y = coords(vertices.xy[i + 1]);
1061
- out.push(x * bone2.a + y * bone2.b + bone2.worldX, x * bone2.c + y * bone2.d + bone2.worldY);
1066
+ const compiledWeightedByVertices = /* @__PURE__ */ new WeakMap();
1067
+ function compileWeightedVertices(vertices, mapIndex) {
1068
+ const bones = [];
1069
+ const indexByName = /* @__PURE__ */ new Map();
1070
+ for (const influences of vertices.bindings) {
1071
+ for (const binding of influences) {
1072
+ if (!indexByName.has(binding.bone)) {
1073
+ indexByName.set(binding.bone, bones.length);
1074
+ bones.push(binding.bone);
1075
+ }
1062
1076
  }
1063
- return out;
1064
1077
  }
1065
- for (const influences of vertices.bindings) {
1078
+ bones.length;
1079
+ const bindings = vertices.bindings.map(
1080
+ (influences) => influences.map((binding) => {
1081
+ const idx = indexByName.get(binding.bone);
1082
+ return {
1083
+ bone: idx,
1084
+ x: binding.x,
1085
+ y: binding.y,
1086
+ fx: Math.fround(binding.x),
1087
+ fy: Math.fround(binding.y),
1088
+ weight: Math.fround(binding.weight)
1089
+ };
1090
+ })
1091
+ );
1092
+ return { bones, bindings };
1093
+ }
1094
+ function compiledWeightedOf(vertices) {
1095
+ let kept = compiledWeightedByVertices.get(vertices);
1096
+ if (kept === void 0) {
1097
+ kept = compileWeightedVertices(vertices);
1098
+ compiledWeightedByVertices.set(vertices, kept);
1099
+ }
1100
+ return kept;
1101
+ }
1102
+ function resolveBindingBones(bones, world) {
1103
+ const transforms = new Array(bones.length);
1104
+ for (let i = 0; i < bones.length; i++) {
1105
+ const name = bones[i];
1106
+ const t = world.get(name);
1107
+ if (t === void 0) throw new Error(`a binding names bone "${name}", which has no world transform (readModel refuses it first)`);
1108
+ transforms[i] = t;
1109
+ }
1110
+ return transforms;
1111
+ }
1112
+ function compiledWorldVertices(compiled, world, coords = Math.fround) {
1113
+ const transforms = resolveBindingBones(compiled.bones, world);
1114
+ const out = [];
1115
+ const fround = coords === Math.fround;
1116
+ for (const influences of compiled.bindings) {
1066
1117
  let wx = 0;
1067
1118
  let wy = 0;
1068
1119
  for (const binding of influences) {
1069
- const t = world.get(binding.bone);
1070
- if (t === void 0) throw new Error(`a binding names bone "${binding.bone}", which has no world transform (readModel refuses it first)`);
1071
- const x = coords(binding.x);
1072
- const y = coords(binding.y);
1073
- const weight = Math.fround(binding.weight);
1120
+ const t = transforms[binding.bone];
1121
+ const x = fround ? binding.fx : coords(binding.x);
1122
+ const y = fround ? binding.fy : coords(binding.y);
1123
+ const weight = binding.weight;
1074
1124
  wx += (x * t.a + y * t.b + t.worldX) * weight;
1075
1125
  wy += (x * t.c + y * t.d + t.worldY) * weight;
1076
1126
  }
@@ -1078,27 +1128,19 @@ function worldVertices(vertices, bone2, world, coords = Math.fround) {
1078
1128
  }
1079
1129
  return out;
1080
1130
  }
1081
- function deformedWorldVertices(vertices, deform, bone2, world) {
1131
+ function compiledDeformedWorldVertices(compiled, deform, world) {
1132
+ const transforms = resolveBindingBones(compiled.bones, world);
1082
1133
  const out = [];
1083
- if (!vertices.weighted) {
1084
- for (let i = 0; i + 1 < deform.length; i += 2) {
1085
- const x = deform[i];
1086
- const y = deform[i + 1];
1087
- out.push(x * bone2.a + y * bone2.b + bone2.worldX, x * bone2.c + y * bone2.d + bone2.worldY);
1088
- }
1089
- return out;
1090
- }
1091
1134
  let k = 0;
1092
- for (const influences of vertices.bindings) {
1135
+ for (const influences of compiled.bindings) {
1093
1136
  let wx = 0;
1094
1137
  let wy = 0;
1095
1138
  for (const binding of influences) {
1096
- const t = world.get(binding.bone);
1097
- if (t === void 0) throw new Error(`a binding names bone "${binding.bone}", which has no world transform (readModel refuses it first)`);
1098
- const x = Math.fround(binding.x) + deform[k];
1099
- const y = Math.fround(binding.y) + deform[k + 1];
1139
+ const t = transforms[binding.bone];
1140
+ const x = binding.fx + deform[k];
1141
+ const y = binding.fy + deform[k + 1];
1100
1142
  k += 2;
1101
- const weight = Math.fround(binding.weight);
1143
+ const weight = binding.weight;
1102
1144
  wx += (x * t.a + y * t.b + t.worldX) * weight;
1103
1145
  wy += (x * t.c + y * t.d + t.worldY) * weight;
1104
1146
  }
@@ -1106,6 +1148,30 @@ function deformedWorldVertices(vertices, deform, bone2, world) {
1106
1148
  }
1107
1149
  return out;
1108
1150
  }
1151
+ function worldVertices(vertices, bone2, world, coords = Math.fround) {
1152
+ if (!vertices.weighted) {
1153
+ const out = [];
1154
+ for (let i = 0; i + 1 < vertices.xy.length; i += 2) {
1155
+ const x = coords(vertices.xy[i]);
1156
+ const y = coords(vertices.xy[i + 1]);
1157
+ out.push(x * bone2.a + y * bone2.b + bone2.worldX, x * bone2.c + y * bone2.d + bone2.worldY);
1158
+ }
1159
+ return out;
1160
+ }
1161
+ return compiledWorldVertices(compiledWeightedOf(vertices), world, coords);
1162
+ }
1163
+ function deformedWorldVertices(vertices, deform, bone2, world) {
1164
+ if (!vertices.weighted) {
1165
+ const out = [];
1166
+ for (let i = 0; i + 1 < deform.length; i += 2) {
1167
+ const x = deform[i];
1168
+ const y = deform[i + 1];
1169
+ out.push(x * bone2.a + y * bone2.b + bone2.worldX, x * bone2.c + y * bone2.d + bone2.worldY);
1170
+ }
1171
+ return out;
1172
+ }
1173
+ return compiledDeformedWorldVertices(compiledWeightedOf(vertices), deform, world);
1174
+ }
1109
1175
  const isRecord$7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1110
1176
  const finite$1 = (v) => typeof v === "number" && Number.isFinite(v);
1111
1177
  const RECT_FIELDS = ["width", "height", "offsetX", "offsetY", "originalWidth", "originalHeight"];
@@ -1150,7 +1216,10 @@ function readVertices(value, where, bones, problems) {
1150
1216
  for (const key of ["x", "y", "weight"]) if (!finite$1(b[key])) problems.push(`${at}: ${key} is ${JSON.stringify(b[key]) ?? "absent"}, not a finite number`);
1151
1217
  });
1152
1218
  });
1153
- return problems.length === before ? { weighted: true, bindings } : void 0;
1219
+ if (problems.length !== before) return void 0;
1220
+ const weighted = { weighted: true, bindings };
1221
+ compiledWeightedByVertices.set(weighted, compileWeightedVertices(weighted));
1222
+ return weighted;
1154
1223
  }
1155
1224
  function readGeometry(raw, kind, where, bones, slots, problems) {
1156
1225
  const before = problems.length;
@@ -1250,6 +1319,7 @@ function roundedRow(values2, round) {
1250
1319
  function poseGeometry(shown, world, sourceOf, round, plant = {}, draw) {
1251
1320
  const region = plant.region ?? regionCorners;
1252
1321
  const vertices = plant.vertices ?? worldVertices;
1322
+ const deformPoser = plant.deformedVertices ?? deformedWorldVertices;
1253
1323
  const attachments = [];
1254
1324
  const clips = [];
1255
1325
  const nulls = [];
@@ -1260,7 +1330,7 @@ function poseGeometry(shown, world, sourceOf, round, plant = {}, draw) {
1260
1330
  if (bone2 === void 0) throw new Error(`slot "${s.slot}": bone "${s.bone}" has no world transform`);
1261
1331
  const g = s.geometry;
1262
1332
  const deform = s.deform;
1263
- const drawn = (v) => deform === void 0 ? vertices(v, bone2, world) : plant.vertices === void 0 ? deformedWorldVertices(v, deform, bone2, world) : vertices(deformedVertices(v, deform), bone2, world, EXACT_COORDS);
1333
+ const drawn = (v) => deform === void 0 ? vertices(v, bone2, world) : plant.vertices === void 0 || plant.deformedVertices !== void 0 ? deformPoser(v, deform, bone2, world) : vertices(deformedVertices(v, deform), bone2, world, EXACT_COORDS);
1264
1334
  if (g.kind === "region") {
1265
1335
  const rect = s.frame === void 0 ? g.region.atlas : g.region.frames?.[s.frame] ?? g.region.atlas;
1266
1336
  if (rect === null) {
@@ -1308,7 +1378,7 @@ function poseGeometry(shown, world, sourceOf, round, plant = {}, draw) {
1308
1378
  }
1309
1379
  return { attachments: attachmentsWhy === null ? attachments : null, attachmentsWhy, clips, clipped, clippedWhy, drawnClipped, drawnClippedWhy };
1310
1380
  }
1311
- const RAD$1 = RUNTIME_PI / 180;
1381
+ const RAD$2 = RUNTIME_PI / 180;
1312
1382
  function curveLengthTable(chain, curveCount) {
1313
1383
  const curveEnds = [];
1314
1384
  let total = 0;
@@ -1747,7 +1817,7 @@ function solvePath(state, c, slotWorld) {
1747
1817
  else {
1748
1818
  fromTip = false;
1749
1819
  const p = slotWorld;
1750
- offsetRotation *= p.a * p.d - p.b * p.c > 0 ? RAD$1 : -RAD$1;
1820
+ offsetRotation *= p.a * p.d - p.b * p.c > 0 ? RAD$2 : -RAD$2;
1751
1821
  }
1752
1822
  const changed = [];
1753
1823
  for (let i = 0, p = 3; i < boneCount; i++, p += 3) {
@@ -2593,8 +2663,8 @@ function applySliderPhysics(r, at, alpha, target) {
2593
2663
  }
2594
2664
  }
2595
2665
  }
2596
- const clamp01$2 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
2597
- const COLOUR_CHANNELS = { rgba: [0, 1, 2, 3], rgb: [0, 1, 2], alpha: [3], rgba2: [0, 1, 2, 3, 4, 5, 6], rgb2: [0, 1, 2, 4, 5, 6] };
2666
+ const clamp01$3 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
2667
+ const COLOUR_CHANNELS$1 = { rgba: [0, 1, 2, 3], rgb: [0, 1, 2], alpha: [3], rgba2: [0, 1, 2, 3, 4, 5, 6], rgb2: [0, 1, 2, 4, 5, 6] };
2598
2668
  function applySliderSlots(slot, pose, applications) {
2599
2669
  for (const app of applications) {
2600
2670
  for (const target of app.timelines.slots) {
@@ -2607,9 +2677,9 @@ function applySliderSlots(slot, pose, applications) {
2607
2677
  }
2608
2678
  const v = valuesAt$1(tl.keys, app.at);
2609
2679
  if (v === null) continue;
2610
- COLOUR_CHANNELS[tl.kind].forEach((at, i) => {
2611
- if (at < 4) pose.light[at] = app.alpha === 1 ? clamp01$2(v[i]) : clamp01$2(pose.light[at] + (v[i] - pose.light[at]) * app.alpha);
2612
- else if (pose.dark !== null) pose.dark[at - 4] = app.alpha === 1 ? clamp01$2(v[i]) : clamp01$2(pose.dark[at - 4] + (v[i] - pose.dark[at - 4]) * app.alpha);
2680
+ COLOUR_CHANNELS$1[tl.kind].forEach((at, i) => {
2681
+ if (at < 4) pose.light[at] = app.alpha === 1 ? clamp01$3(v[i]) : clamp01$3(pose.light[at] + (v[i] - pose.light[at]) * app.alpha);
2682
+ else if (pose.dark !== null) pose.dark[at - 4] = app.alpha === 1 ? clamp01$3(v[i]) : clamp01$3(pose.dark[at - 4] + (v[i] - pose.dark[at - 4]) * app.alpha);
2613
2683
  });
2614
2684
  }
2615
2685
  }
@@ -2632,8 +2702,8 @@ function sliderBonesWhy(doc) {
2632
2702
  }
2633
2703
  return found.length === 0 ? null : found.join("; ");
2634
2704
  }
2635
- const DEG = 180 / RUNTIME_PI;
2636
- const RAD = RUNTIME_PI / 180;
2705
+ const DEG$1 = 180 / RUNTIME_PI;
2706
+ const RAD$1 = RUNTIME_PI / 180;
2637
2707
  const ADMITTED_CONSTRAINT_KINDS = ["ik", "transform", "path", "physics", "slider"];
2638
2708
  const TRANSFORM_PROPERTIES = ["rotate", "x", "y", "scaleX", "scaleY", "shearY"];
2639
2709
  const TRANSFORM_MIXES = { rotate: "mixRotate", x: "mixX", y: "mixY", scaleX: "mixScaleX", scaleY: "mixScaleY", shearY: "mixShearY" };
@@ -2900,7 +2970,7 @@ function posedRecords(records, timelines, t, plant = {}) {
2900
2970
  return { ...r, mixes };
2901
2971
  });
2902
2972
  }
2903
- function wrap180(r) {
2973
+ function wrap180$1(r) {
2904
2974
  return r > 180 ? r - 360 : r <= -180 ? r + 360 : r;
2905
2975
  }
2906
2976
  const IDENTITY = { a: 1, b: 0, c: 0, d: 1, worldX: 0, worldY: 0 };
@@ -2939,7 +3009,7 @@ const RUNTIME_SOLVER_RULES = {
2939
3009
  function solverRules(plant) {
2940
3010
  return plant === void 0 ? RUNTIME_SOLVER_RULES : { ...RUNTIME_SOLVER_RULES, ...plant };
2941
3011
  }
2942
- function volumeScaleY(scaleY, s) {
3012
+ function volumeScaleY$1(scaleY, s) {
2943
3013
  return scaleY / (s < 0.7 ? 0.25 + s * 0.642857 : s);
2944
3014
  }
2945
3015
  function bone(state, name) {
@@ -2978,11 +3048,11 @@ function localFromWorld(b, p, w, rules = RUNTIME_SOLVER_RULES) {
2978
3048
  const length = Math.sqrt(w.a * w.a + w.c * w.c);
2979
3049
  const [ux2, uy2] = [w.a / length, w.c / length];
2980
3050
  decompose(b, length, 0, ux2 * w.b + uy2 * w.d, (ux2 * w.d - uy2 * w.b) * flip, rules);
2981
- b.rotation = Math.atan2((uy2 * p.a - ux2 * p.c) / det, (ux2 * p.d - uy2 * p.b) / det) * DEG;
3051
+ b.rotation = Math.atan2((uy2 * p.a - ux2 * p.c) / det, (ux2 * p.d - uy2 * p.b) / det) * DEG$1;
2982
3052
  return;
2983
3053
  }
2984
3054
  const sign = det < 0 ? -1 : 1;
2985
- const rotation = Math.atan2((p.a * w.c - p.c * w.a) * sign, (p.d * w.a - p.b * w.c) * sign) * DEG;
3055
+ const rotation = Math.atan2((p.a * w.c - p.c * w.a) * sign, (p.d * w.a - p.b * w.c) * sign) * DEG$1;
2986
3056
  const [ux, uy] = noScaleDirection([p.a, p.b, p.c, p.d], rotation);
2987
3057
  decompose(b, ux * w.a + uy * w.c, (ux * w.c - uy * w.a) * flip, ux * w.b + uy * w.d, (ux * w.d - uy * w.b) * flip, rules, "shearX");
2988
3058
  b.rotation = rotation;
@@ -3006,7 +3076,7 @@ function localFromWorld(b, p, w, rules = RUNTIME_SOLVER_RULES) {
3006
3076
  [la, lb, lc, ld] = [(cd * w.a - cb * w.c) / cdet, (cd * w.b - cb * w.d) / cdet, (ca * w.c - cc * w.a) / cdet, (ca * w.d - cc * w.b) / cdet];
3007
3077
  }
3008
3078
  decompose(b, la, lc, lb, ld, rules);
3009
- b.rotation = (b.rotation ?? 0) + Math.atan2(p.c, p.a) * DEG;
3079
+ b.rotation = (b.rotation ?? 0) + Math.atan2(p.c, p.a) * DEG$1;
3010
3080
  return;
3011
3081
  } else {
3012
3082
  const [ia, ib, ic, id] = [p.d * pid, p.b * pid, p.c * pid, p.a * pid];
@@ -3021,12 +3091,12 @@ function decompose(b, la, lc, lb, ld, rules, xAngleIn = "rotation") {
3021
3091
  b.scaleX = 0;
3022
3092
  b.scaleY = yLength;
3023
3093
  b.shearY = 0;
3024
- b.rotation = !rules.readBackCollapsed || yLength > 1e-5 ? Math.atan2(ld, lb) * DEG - 90 : 0;
3094
+ b.rotation = !rules.readBackCollapsed || yLength > 1e-5 ? Math.atan2(ld, lb) * DEG$1 - 90 : 0;
3025
3095
  return;
3026
3096
  }
3027
- const xAngle = Math.atan2(lc, la) * DEG;
3097
+ const xAngle = Math.atan2(lc, la) * DEG$1;
3028
3098
  const det = la * ld - lb * lc;
3029
- let yAngle = Math.atan2(ld, lb) * DEG;
3099
+ let yAngle = Math.atan2(ld, lb) * DEG$1;
3030
3100
  if (xAngleIn === "rotation") b.rotation = xAngle;
3031
3101
  else b.shearX = xAngle;
3032
3102
  b.scaleX = sx;
@@ -3055,7 +3125,7 @@ function solveOne(state, c, frame2 = null) {
3055
3125
  const k = Math.abs(pa * pd - pb * pc) / Math.max(state.rules.ikXAxisFloor, pa * pa + pc * pc);
3056
3126
  pb = -pc * k;
3057
3127
  pd = pa * k;
3058
- turn = Math.atan2(pc, pa) * DEG;
3128
+ turn = Math.atan2(pc, pa) * DEG$1;
3059
3129
  const det = pa * pd - pb * pc;
3060
3130
  const x = t.worldX - p.worldX;
3061
3131
  const y = t.worldY - p.worldY;
@@ -3073,9 +3143,9 @@ function solveOne(state, c, frame2 = null) {
3073
3143
  }
3074
3144
  const rotation = b.rotation ?? 0;
3075
3145
  const scaleX = b.scaleX ?? 1;
3076
- let r = -(b.shearX ?? 0) - rotation + turn + Math.atan2(ly, lx) * DEG;
3146
+ let r = -(b.shearX ?? 0) - rotation + turn + Math.atan2(ly, lx) * DEG$1;
3077
3147
  if (scaleX < 0) r += 180;
3078
- b.rotation = rotation + wrap180(r) * c.mix;
3148
+ b.rotation = rotation + wrap180$1(r) * c.mix;
3079
3149
  const len = (b.length ?? 0) * scaleX;
3080
3150
  if ((c.compress || c.stretch) && len > state.rules.ikStretchMinLength) {
3081
3151
  const wx = t.worldX - bw.worldX;
@@ -3085,7 +3155,7 @@ function solveOne(state, c, frame2 = null) {
3085
3155
  const s = (d / len - 1) * c.mix + 1;
3086
3156
  b.scaleX = scaleX * s;
3087
3157
  if (c.scaleY === "uniform") b.scaleY = (b.scaleY ?? 1) * s;
3088
- else if (c.scaleY === "volume") b.scaleY = volumeScaleY(b.scaleY ?? 1, s);
3158
+ else if (c.scaleY === "volume") b.scaleY = volumeScaleY$1(b.scaleY ?? 1, s);
3089
3159
  }
3090
3160
  }
3091
3161
  return [b.name];
@@ -3158,7 +3228,7 @@ function solveTwo(state, c, frame2 = null) {
3158
3228
  const f = (Math.sqrt(d2) / (l1 + l2) - 1) * c.mix + 1;
3159
3229
  sx *= f;
3160
3230
  if (c.scaleY === "uniform") sy *= f;
3161
- else if (c.scaleY === "volume") sy = volumeScaleY(sy, f);
3231
+ else if (c.scaleY === "volume") sy = volumeScaleY$1(sy, f);
3162
3232
  }
3163
3233
  } else a2 = Math.acos(cos) * bend;
3164
3234
  const along = l1 + l2 * cos;
@@ -3211,14 +3281,14 @@ function solveTwo(state, c, frame2 = null) {
3211
3281
  const signs = state.rules.ikZeroScaleSignPositive ? (psx < 0 ? -1 : 1) * (psy < 0 ? -1 : 1) : Math.sign(psx) * Math.sign(psy);
3212
3282
  const offset = Math.atan2(cy, cx);
3213
3283
  const parentRotation = parent.rotation ?? 0;
3214
- const parentChange = (a1 - signs * offset) * DEG + (psx < 0 ? 180 : 0) - parentRotation;
3215
- parent.rotation = parentRotation + wrap180(parentChange) * c.mix;
3284
+ const parentChange = (a1 - signs * offset) * DEG$1 + (psx < 0 ? 180 : 0) - parentRotation;
3285
+ parent.rotation = parentRotation + wrap180$1(parentChange) * c.mix;
3216
3286
  parent.scaleX = sx;
3217
3287
  parent.scaleY = sy;
3218
- const childTarget = ((a2 + signs * offset) * DEG - (child.shearX ?? 0)) * signs + (csx < 0 ? 180 : 0);
3288
+ const childTarget = ((a2 + signs * offset) * DEG$1 - (child.shearX ?? 0)) * signs + (csx < 0 ? 180 : 0);
3219
3289
  const childRotation = child.rotation ?? 0;
3220
3290
  child.y = cy;
3221
- child.rotation = childRotation + wrap180(childTarget - childRotation) * c.mix;
3291
+ child.rotation = childRotation + wrap180$1(childTarget - childRotation) * c.mix;
3222
3292
  return [parent.name];
3223
3293
  }
3224
3294
  function sourceValue(state, c, property) {
@@ -3244,7 +3314,7 @@ function sourceValue(state, c, property) {
3244
3314
  switch (property) {
3245
3315
  case "rotate": {
3246
3316
  const det = w.a * w.d - w.b * w.c;
3247
- let r = Math.atan2(w.c, w.a) * DEG + ((state.rules.offsetNegatedAtZeroDet ? det > 0 : det >= 0) ? o.rotate : -o.rotate);
3317
+ let r = Math.atan2(w.c, w.a) * DEG$1 + ((state.rules.offsetNegatedAtZeroDet ? det > 0 : det >= 0) ? o.rotate : -o.rotate);
3248
3318
  if (r < 0) r += 360;
3249
3319
  return r;
3250
3320
  }
@@ -3257,13 +3327,13 @@ function sourceValue(state, c, property) {
3257
3327
  case "scaleY":
3258
3328
  return Math.sqrt(w.b * w.b + w.d * w.d) + o.scaleY;
3259
3329
  case "shearY":
3260
- return (Math.atan2(w.d, w.b) - Math.atan2(w.c, w.a)) * DEG - 90 + o.shearY;
3330
+ return (Math.atan2(w.d, w.b) - Math.atan2(w.c, w.a)) * DEG$1 - 90 + o.shearY;
3261
3331
  }
3262
3332
  }
3263
- function applyWorld(w, property, v, mix, additive, rules) {
3333
+ function applyWorld$1(w, property, v, mix, additive, rules) {
3264
3334
  switch (property) {
3265
3335
  case "rotate": {
3266
- let r = additive ? v * RAD : v * RAD - Math.atan2(w.c, w.a);
3336
+ let r = additive ? v * RAD$1 : v * RAD$1 - Math.atan2(w.c, w.a);
3267
3337
  if (r > RUNTIME_PI) r -= 2 * RUNTIME_PI;
3268
3338
  else if (r < -RUNTIME_PI) r += 2 * RUNTIME_PI;
3269
3339
  r *= mix;
@@ -3295,7 +3365,7 @@ function applyWorld(w, property, v, mix, additive, rules) {
3295
3365
  case "shearY": {
3296
3366
  const yAngle = Math.atan2(w.d, w.b);
3297
3367
  const xAngle = Math.atan2(w.c, w.a);
3298
- let r = additive && !rules.additiveShearRightAngle ? v * RAD : (v + 90) * RAD - (additive ? 90 * RAD : yAngle - xAngle);
3368
+ let r = additive && !rules.additiveShearRightAngle ? v * RAD$1 : (v + 90) * RAD$1 - (additive ? 90 * RAD$1 : yAngle - xAngle);
3299
3369
  if (!additive) r = r > RUNTIME_PI ? r - 2 * RUNTIME_PI : r < -RUNTIME_PI ? r + 2 * RUNTIME_PI : r;
3300
3370
  const angle = yAngle + r * mix;
3301
3371
  const s = Math.sqrt(w.b * w.b + w.d * w.d);
@@ -3333,7 +3403,7 @@ function solveTransform(state, c) {
3333
3403
  }
3334
3404
  applied = true;
3335
3405
  if (c.localTarget) applyLocal(b, to.property, v, mix, c.additive);
3336
- else applyWorld(w, to.property, v, mix, c.additive, state.rules);
3406
+ else applyWorld$1(w, to.property, v, mix, c.additive, state.rules);
3337
3407
  }
3338
3408
  }
3339
3409
  if (!applied) continue;
@@ -3917,7 +3987,7 @@ function valuesAt(keys, t, plant) {
3917
3987
  const channel2 = plant.channel ?? channelAt;
3918
3988
  return keys[index].values.map((_v, c) => channel2(keys, index, c, t));
3919
3989
  }
3920
- function keyAt(keys, t, plant) {
3990
+ function keyAt$1(keys, t, plant) {
3921
3991
  const index = (plant.search ?? keyIndexAt)(keys, t);
3922
3992
  return index < 0 ? null : keys[index];
3923
3993
  }
@@ -3938,7 +4008,7 @@ function posedBones(doc, timelines, t, plant = {}) {
3938
4008
  const bone2 = { ...setup };
3939
4009
  for (const tl of target.timelines) {
3940
4010
  if (tl.kind === "inherit") {
3941
- const key = keyAt(tl.keys, t, plant);
4011
+ const key = keyAt$1(tl.keys, t, plant);
3942
4012
  if (key === null) {
3943
4013
  if (setup.inheritMode === void 0) delete bone2.inheritMode;
3944
4014
  else bone2.inheritMode = setup.inheritMode;
@@ -4066,28 +4136,29 @@ function adjustLocals(doc, bones, adjust, context) {
4066
4136
  }
4067
4137
  }, context);
4068
4138
  }
4069
- const clamp01$1 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
4139
+ const clamp01$2 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
4070
4140
  function posedSlots(doc, timelines, t, plant = {}, sliders = [], placeholders) {
4071
- const colour = plant.colour ?? readColour;
4141
+ const stated = viewColours(doc);
4142
+ const colour = plant.colour ?? stated;
4072
4143
  const byName = new Map(timelines.slots.map((s) => [s.name, s]));
4073
4144
  return slotRowsFrom(doc, plant, sliders, placeholders, (slot, live) => {
4074
4145
  let placeholder = slot.setup;
4075
- const light = slot.color === void 0 ? [1, 1, 1, 1] : colour(slot.color);
4076
- const setupDark = slot.dark === void 0 ? null : readColour(slot.dark);
4146
+ const setupLight = slot.color === void 0 ? WHITE : colour(slot.color);
4147
+ const light = [setupLight[0], setupLight[1], setupLight[2], setupLight[3]];
4148
+ const setupDark = slot.dark === void 0 ? null : stated(slot.dark);
4077
4149
  const dark = setupDark === null ? null : [setupDark[0], setupDark[1], setupDark[2]];
4078
4150
  for (const tl of live ? byName.get(slot.name)?.timelines ?? [] : []) {
4079
4151
  if (tl.kind === "attachment") {
4080
- const key = keyAt(tl.keys, t, plant);
4152
+ const key = keyAt$1(tl.keys, t, plant);
4081
4153
  placeholder = key === null ? slot.setup : key.name ?? null;
4082
4154
  continue;
4083
4155
  }
4084
4156
  const v = valuesAt(tl.keys, t, plant);
4085
- const setupLight = slot.color === void 0 ? [1, 1, 1, 1] : colour(slot.color);
4086
4157
  const channels = SLOT_COLOUR_TIMELINES[tl.kind].channels;
4087
4158
  channels.forEach((name, i) => {
4088
4159
  const at = { r: 0, g: 1, b: 2, a: 3, r2: 4, g2: 5, b2: 6 }[name];
4089
- if (at < 4) light[at] = v === null ? setupLight[at] : clamp01$1(v[i]);
4090
- else if (dark !== null && setupDark !== null) dark[at - 4] = v === null ? setupDark[at - 4] : clamp01$1(v[i]);
4160
+ if (at < 4) light[at] = v === null ? setupLight[at] : clamp01$2(v[i]);
4161
+ else if (dark !== null && setupDark !== null) dark[at - 4] = v === null ? setupDark[at - 4] : clamp01$2(v[i]);
4091
4162
  });
4092
4163
  }
4093
4164
  return { placeholder, light, dark };
@@ -4131,50 +4202,60 @@ function slotRowsFrom(doc, plant, sliders, placeholders, baseOf) {
4131
4202
  }
4132
4203
  return { rows, conflicts };
4133
4204
  }
4134
- function posedBoneWorld(doc, timelines, t, plant = {}, constraints, sliders, step) {
4135
- const posed = posedBoneStep(doc, timelines, t, plant, constraints, sliders, step);
4136
- return { rows: stepRows(posed), world: posed.world };
4137
- }
4138
4205
  function posedBoneWorldAlone(doc, timelines, t, plant, constraints, sliders, step) {
4139
4206
  return posedBoneStep(doc, timelines, t, plant, constraints, sliders, step).world;
4140
4207
  }
4141
- function stepRows(posed) {
4142
- if (posed.rows !== void 0) return posed.rows;
4143
- const { world, active, round } = posed;
4144
- posed.rows = posed.bones.map((b) => {
4145
- const w = world.get(b.name);
4146
- return [b.name, round(w.worldX), round(w.worldY), round(w.a), round(w.b), round(w.c), round(w.d), active.has(b.name) ? 1 : 0, b.parent ?? null];
4147
- });
4148
- return posed.rows;
4149
- }
4150
4208
  function posedBoneStep(doc, timelines, t, plant, constraints, sliders, step) {
4209
+ const stage = plant.stage;
4210
+ if (stage !== void 0) stage.begin("timeline");
4151
4211
  const bones = posedBones(doc, timelines, t, plant);
4152
- if (constraints !== void 0 && step !== void 0) return steppedLocals(doc, bones, plant, () => pathDeformed(doc, posedRecords(constraintRecords(doc), constraints, t), timelines, t, plant), constraints.physicsKeyed ?? [], t, sliders, step);
4212
+ if (stage !== void 0) stage.end("timeline");
4213
+ if (constraints !== void 0 && step !== void 0) {
4214
+ return steppedLocals(doc, bones, plant, () => pathDeformed(doc, posedRecords(constraintRecords(doc), constraints, t), timelines, t, plant), constraints.physicsKeyed ?? [], t, sliders, step);
4215
+ }
4153
4216
  const active = activeBones(doc);
4154
- if (step?.adjustment !== void 0) adjustLocals(doc, bones, step.adjustment.callback, step.adjustment.context);
4155
- let world = (plant.evaluate ?? worldTransforms)(bones, active);
4217
+ if (step?.adjustment !== void 0) {
4218
+ if (stage !== void 0) stage.begin("timeline");
4219
+ adjustLocals(doc, bones, step.adjustment.callback, step.adjustment.context);
4220
+ if (stage !== void 0) stage.end("timeline");
4221
+ }
4222
+ if (stage !== void 0) stage.begin("world");
4223
+ const initialWorld = (plant.evaluate ?? worldTransforms)(bones, active);
4224
+ if (stage !== void 0) stage.end("world");
4225
+ let world = initialWorld;
4156
4226
  if (constraints !== void 0) {
4227
+ if (stage !== void 0) stage.begin("constraints");
4157
4228
  const setupRecords = constraintRecords(doc);
4158
4229
  const records = pathDeformed(doc, posedRecords(setupRecords, constraints, t), timelines, t, plant);
4159
4230
  const previous = setupRecords.some((r) => r.kind === "path") ? applyConstraints(doc.bones, (plant.evaluate ?? worldTransforms)(doc.bones, active), active, plant.constraints ? plant.constraints(setupRecords) : setupRecords, null, void 0, void 0, void 0, solverRules(plant.solver)) : null;
4160
- world = applyConstraints(bones, world, active, plant.constraints ? plant.constraints(records) : records, previous, sliders, void 0, void 0, solverRules(plant.solver));
4231
+ world = applyConstraints(bones, initialWorld, active, plant.constraints ? plant.constraints(records) : records, previous, sliders, void 0, void 0, solverRules(plant.solver));
4232
+ if (stage !== void 0) stage.end("constraints");
4161
4233
  }
4162
4234
  for (const b of bones) if (world.get(b.name) === void 0) throw new CoreInputError(`the evaluator returned no transform for bone "${b.name}"`);
4163
4235
  return { bones, world, active, round: plant.round ?? gridRound };
4164
4236
  }
4165
4237
  function steppedLocals(doc, bones, plant, records, physicsKeyed, t, sliders, step) {
4238
+ const stage = plant.stage;
4166
4239
  const active = activeBones(doc);
4167
- if (step.adjustment !== void 0) adjustLocals(doc, bones, step.adjustment.callback, step.adjustment.context);
4168
- let world = (plant.evaluate ?? worldTransforms)(bones, active);
4240
+ if (step.adjustment !== void 0) {
4241
+ if (stage !== void 0) stage.begin("timeline");
4242
+ adjustLocals(doc, bones, step.adjustment.callback, step.adjustment.context);
4243
+ if (stage !== void 0) stage.end("timeline");
4244
+ }
4245
+ if (stage !== void 0) stage.begin("world");
4246
+ const initialWorld = (plant.evaluate ?? worldTransforms)(bones, active);
4247
+ if (stage !== void 0) {
4248
+ stage.end("world");
4249
+ stage.begin("constraints");
4250
+ }
4169
4251
  const stepped = stepPhysicsRecords(records(), physicsKeyed, t, active, step.ctx, step.before);
4170
- world = applyConstraints(bones, world, active, plant.constraints ? plant.constraints(stepped) : stepped, null, sliders, step.ctx, void 0, solverRules(plant.solver));
4252
+ const world = applyConstraints(bones, initialWorld, active, plant.constraints ? plant.constraints(stepped) : stepped, null, sliders, step.ctx, void 0, solverRules(plant.solver));
4253
+ if (stage !== void 0) stage.end("constraints");
4171
4254
  for (const b of bones) if (world.get(b.name) === void 0) throw new CoreInputError(`the evaluator returned no transform for bone "${b.name}"`);
4172
4255
  return { bones, world, active, round: plant.round ?? gridRound };
4173
4256
  }
4174
- function steppedLocalsWorld(doc, bones, plant, sliders, step, rows) {
4175
- const posed = steppedLocals(doc, bones, plant, () => constraintRecords(doc), [], 0, sliders, step);
4176
- stepRows(posed);
4177
- return posed.world;
4257
+ function steppedLocalsWorld(doc, bones, plant, sliders, step) {
4258
+ return steppedLocals(doc, bones, plant, () => constraintRecords(doc), [], 0, sliders, step).world;
4178
4259
  }
4179
4260
  function pathDeformed(doc, records, timelines, t, plant) {
4180
4261
  if (timelines.attachments.every((a) => a.deform === null)) return records;
@@ -4806,6 +4887,24 @@ function readColour(hex) {
4806
4887
  const channel2 = (i) => Number.parseInt(hex.slice(2 * i, 2 * i + 2), 16) / 255;
4807
4888
  return [channel2(0), channel2(1), channel2(2), hex.length === 8 ? channel2(3) : 1];
4808
4889
  }
4890
+ const coloursOfView = /* @__PURE__ */ new WeakMap();
4891
+ function viewColours(doc) {
4892
+ let kept = coloursOfView.get(doc);
4893
+ if (kept === void 0) {
4894
+ kept = /* @__PURE__ */ new Map();
4895
+ coloursOfView.set(doc, kept);
4896
+ }
4897
+ const colours = kept;
4898
+ return (hex) => {
4899
+ let c = colours.get(hex);
4900
+ if (c === void 0) {
4901
+ c = readColour(hex);
4902
+ colours.set(hex, c);
4903
+ }
4904
+ return c;
4905
+ };
4906
+ }
4907
+ const WHITE = [1, 1, 1, 1];
4809
4908
  function readBlend(slot) {
4810
4909
  if (slot.blend === void 0) return "Normal";
4811
4910
  const mode = foldBlend(slot.blend);
@@ -5017,7 +5116,7 @@ function rawBones(doc, world) {
5017
5116
  }
5018
5117
  function drawnGeometry(doc, shown, world, order, plant, mode) {
5019
5118
  const draw = drawWalkOf(doc, order, plant);
5020
- const geometry = poseGeometry(shown, world, sourceOfDoc(doc), mode.keep ? keptNumber : rawNumber, { region: plant.region, vertices: plant.vertices }, mode.loop && mode.live !== true && plant.through === void 0 ? { ...draw, through: NO_CLIPPED_ROWS } : draw);
5119
+ const geometry = poseGeometry(shown, world, sourceOfDoc(doc), mode.keep ? keptNumber : rawNumber, { region: plant.region, vertices: plant.vertices, deformedVertices: plant.deformedVertices }, mode.loop && mode.live !== true && plant.through === void 0 ? { ...draw, through: NO_CLIPPED_ROWS } : draw);
5021
5120
  if (geometry.attachments === null) throw new CoreInputError(`the raw pose leaves the attachments out: ${geometry.attachmentsWhy}`);
5022
5121
  if (geometry.drawnClipped === null) throw new CoreInputError(`the raw pose leaves the clipped triangles out: ${geometry.drawnClippedWhy}`);
5023
5122
  return { rows: geometry.attachments, clips: geometry.clips, drawnClipped: geometry.drawnClipped };
@@ -5030,9 +5129,16 @@ function allNumbers(values2) {
5030
5129
  return true;
5031
5130
  }
5032
5131
  function rawDrawn(doc, shown, world, order, plant, mode = RAW_MODE) {
5132
+ const stage = plant.stage;
5133
+ if (stage !== void 0) stage.begin("geometry");
5033
5134
  const geometry = drawnGeometry(doc, shown, world, order, plant, mode);
5135
+ if (stage !== void 0) {
5136
+ stage.end("geometry");
5137
+ stage.begin("readout");
5138
+ }
5034
5139
  const rows = geometry.rows;
5035
5140
  const drawn = [];
5141
+ const stated = viewColours(doc);
5036
5142
  let k = 0;
5037
5143
  for (const s of shown) {
5038
5144
  const g = s.geometry;
@@ -5040,7 +5146,8 @@ function rawDrawn(doc, shown, world, order, plant, mode = RAW_MODE) {
5040
5146
  const row = rows[k++];
5041
5147
  if (row === void 0 || row[0] !== s.slot) throw new CoreInputError(`slot "${s.slot}": the attachment rows are not in the shown records' order`);
5042
5148
  const record = doc.skins.find((x) => x.name === s.skin)?.attachments[s.slot]?.[s.placeholder];
5043
- const colour = record?.color === void 0 ? [1, 1, 1, 1] : readColour(record.color);
5149
+ const read = record?.color === void 0 ? WHITE : stated(record.color);
5150
+ const colour = [read[0], read[1], read[2], read[3]];
5044
5151
  let uvs;
5045
5152
  let triangles;
5046
5153
  let hull = null;
@@ -5058,6 +5165,7 @@ function rawDrawn(doc, shown, world, order, plant, mode = RAW_MODE) {
5058
5165
  }
5059
5166
  const clips = geometry.clips.map((c) => [c[0], c[1], c[2], numbersOf(c[3], `clip vertex of slot "${c[0]}"`, mode)]);
5060
5167
  const clipped = geometry.drawnClipped.map((c) => [c[0], c[1], c[2], numbersOf(c[3], `clipped vertex of slot "${c[0]}"`, mode), numbersOf(c[4], `clipped uv of slot "${c[0]}"`, mode), c[5]]);
5168
+ if (stage !== void 0) stage.end("readout");
5061
5169
  return { drawn, clips, clipped };
5062
5170
  }
5063
5171
  function poseRawSetup(doc, plant = {}) {
@@ -5155,12 +5263,15 @@ function loopedTime(trackTime, duration) {
5155
5263
  return duration === 0 ? 0 : trackTime % duration;
5156
5264
  }
5157
5265
  const RAW_SHAPE = {
5158
- rows: true,
5159
5266
  setup: (doc, world, slots, drawOrder, shown, plant, mode) => ({ trackTime: 0, animationTime: 0, bones: rawBones(doc, world), slots, drawOrder, ...rawDrawn(doc, shown, world, drawOrder, plant, mode), events: [], shown: drawOrderOf(shown, drawOrder) }),
5160
5267
  step: (doc, p, plant, mode) => {
5161
5268
  const drawn = rawDrawn(doc, p.shown, p.world, p.drawOrder, plant, mode);
5269
+ const stage = plant.stage;
5270
+ if (stage !== void 0) stage.begin("readout");
5162
5271
  const events = p.events();
5163
- return { trackTime: p.trackTime, animationTime: p.animationTime, bones: rawBones(doc, p.world), slots: p.slots, drawOrder: p.drawOrder, ...drawn, events, shown: p.shown };
5272
+ const pose = { trackTime: p.trackTime, animationTime: p.animationTime, bones: rawBones(doc, p.world), slots: p.slots, drawOrder: p.drawOrder, ...drawn, events, shown: p.shown };
5273
+ if (stage !== void 0) stage.end("readout");
5274
+ return pose;
5164
5275
  }
5165
5276
  };
5166
5277
  function animationOf(doc, animation) {
@@ -5197,7 +5308,7 @@ function stepWalk(track, dt, first = false) {
5197
5308
  const keys = anim.timelines.events;
5198
5309
  const adjustment = adjust === void 0 ? void 0 : { callback: adjust, context: { trackTime, animation: anim.name, globalTime: trackTime } };
5199
5310
  const pose = poseStepFrom(doc, plant, resolve, shape, mode, ctx, first, trackTime, t, {
5200
- world: (sliders) => shape.rows ? posedBoneWorld(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjustment }).world : posedBoneWorldAlone(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjustment }),
5311
+ world: (sliders) => posedBoneWorldAlone(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjustment }),
5201
5312
  slots: (sliders, placeholders) => posedSlots(doc, anim.timelines, t, raw, sliders, placeholders),
5202
5313
  drawOrder: anim.timelines.drawOrder,
5203
5314
  at: t,
@@ -5208,20 +5319,30 @@ function stepWalk(track, dt, first = false) {
5208
5319
  return pose;
5209
5320
  }
5210
5321
  function poseStepFrom(doc, plant, resolve, shape, mode, ctx, first, trackTime, animationTime, posers) {
5322
+ const stage = plant.stage;
5211
5323
  const sliders = [];
5212
5324
  const world = posers.world(sliders);
5213
5325
  if (first) ctx.phase = "update";
5326
+ if (stage !== void 0) stage.begin("readout");
5214
5327
  const placeholders = /* @__PURE__ */ new Map();
5215
5328
  const slots = posers.slots(sliders, placeholders);
5216
5329
  if (slots.conflicts.length > 0) throw new CoreInputError(`the raw walk leaves the slots out: ${slots.conflicts.join("; ")}`);
5217
5330
  const evalOrder = plant.drawOrder ?? drawOrderAt;
5218
- let order = evalOrder(doc.slots.length, posers.drawOrder, posers.at) ?? doc.slots.map((_s, k) => k);
5331
+ const baseOrder = evalOrder(doc.slots.length, posers.drawOrder, posers.at) ?? doc.slots.map((_s, k) => k);
5332
+ let order = baseOrder;
5219
5333
  for (const app of sliders) order = evalOrder(doc.slots.length, app.timelines.drawOrder, app.at) ?? order;
5220
5334
  const drawOrder = order.map((k) => doc.slots[k].name);
5335
+ if (stage !== void 0) {
5336
+ stage.end("readout");
5337
+ stage.begin("timeline");
5338
+ }
5221
5339
  const states = attachmentStates(doc, resolve, placeholders, posers.sample, sliders, plant, posers.mixed);
5340
+ if (stage !== void 0) stage.end("timeline");
5222
5341
  if (states.why.length > 0) throw new CoreInputError(`the raw walk leaves the attachments out: ${states.why.join("; ")}`);
5342
+ if (stage !== void 0) stage.begin("readout");
5223
5343
  const rank = new Map(drawOrder.map((n, r) => [n, r]));
5224
5344
  const shown = [...states.shown].sort((a, b) => (rank.get(a.slot) ?? 0) - (rank.get(b.slot) ?? 0));
5345
+ if (stage !== void 0) stage.end("readout");
5225
5346
  const events = () => posers.events().map((e) => [e[0], finite(e[1], "event time"), e[2], finite(e[3], "event float"), e[4]]);
5226
5347
  return shape.step(doc, { trackTime, animationTime, world, slots: slots.rows, drawOrder, shown, events }, plant, mode);
5227
5348
  }
@@ -5474,7 +5595,8 @@ function trackComplete(e) {
5474
5595
  return e.trackTime;
5475
5596
  }
5476
5597
  const signum = (v) => v > 0 ? 1 : v < 0 ? -1 : 0;
5477
- const clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
5598
+ const clamp01$1 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
5599
+ const NO_DARK = [0, 0, 0];
5478
5600
  const ATTACH_NONE = 0;
5479
5601
  const ATTACH_SETUP = 1;
5480
5602
  const ATTACH_RETAIN = 2;
@@ -5486,12 +5608,15 @@ class Pose {
5486
5608
  this.resolved = /* @__PURE__ */ new Map();
5487
5609
  this.bones = [...doc.bones];
5488
5610
  this.active = activeBones(doc);
5489
- const colour = plant.colour ?? readColour;
5611
+ const stated = viewColours(doc);
5612
+ this.stated = stated;
5613
+ const colour = plant.colour ?? stated;
5490
5614
  const gate = plant.slotTimelines ?? slotTimelinesApply;
5491
5615
  this.live = doc.slots.map((s) => gate(doc, s, this.active));
5492
5616
  this.slots = doc.slots.map((slot) => {
5493
- const setupDark = slot.dark === void 0 ? null : readColour(slot.dark);
5494
- return { placeholder: slot.setup, light: slot.color === void 0 ? [1, 1, 1, 1] : colour(slot.color), dark: setupDark === null ? null : [setupDark[0], setupDark[1], setupDark[2]], deform: null, seen: [], attachmentState: ATTACH_NONE };
5617
+ const setupDark = slot.dark === void 0 ? null : stated(slot.dark);
5618
+ const setupLight = slot.color === void 0 ? WHITE : colour(slot.color);
5619
+ return { placeholder: slot.setup, light: [setupLight[0], setupLight[1], setupLight[2], setupLight[3]], dark: setupDark === null ? null : [setupDark[0], setupDark[1], setupDark[2]], deform: null, seen: [], attachmentState: ATTACH_NONE };
5495
5620
  });
5496
5621
  }
5497
5622
  /** Bone `i`'s locals to write: the setup bone copied on its first write, as `posedBones` copies a keyed one. */
@@ -6068,11 +6193,10 @@ class Mixer {
6068
6193
  const s = pose.slots[tl.slot];
6069
6194
  const l = s.light;
6070
6195
  const d = s.dark;
6071
- const sl = slot.color === void 0 ? [1, 1, 1, 1] : (this.plant.colour ?? readColour)(slot.color);
6072
- const sdFull = slot.dark === void 0 ? null : readColour(slot.dark);
6073
- const sd = sdFull === null ? [0, 0, 0] : [sdFull[0], sdFull[1], sdFull[2]];
6196
+ const sl = slot.color === void 0 ? WHITE : (this.plant.colour ?? pose.stated)(slot.color);
6197
+ const sd = slot.dark === void 0 ? NO_DARK : pose.stated(slot.dark);
6074
6198
  const clampLight = () => {
6075
- for (let k = 0; k < 4; k++) l[k] = clamp01(l[k]);
6199
+ for (let k = 0; k < 4; k++) l[k] = clamp01$1(l[k]);
6076
6200
  };
6077
6201
  const v = this.values(tl.keys, time);
6078
6202
  const channels = SLOT_COLOUR_TIMELINES[tl.kind].channels.length;
@@ -6100,7 +6224,7 @@ class Mixer {
6100
6224
  }
6101
6225
  const rgb = [v[0], v[1], v[2]];
6102
6226
  if (alpha !== 1) for (let k = 0; k < 3; k++) rgb[k] = from === SETUP ? sl[k] + (rgb[k] - sl[k]) * alpha : l[k] + (rgb[k] - l[k]) * alpha;
6103
- for (let k = 0; k < 3; k++) l[k] = clamp01(rgb[k]);
6227
+ for (let k = 0; k < 3; k++) l[k] = clamp01$1(rgb[k]);
6104
6228
  return;
6105
6229
  }
6106
6230
  case "alpha": {
@@ -6111,7 +6235,7 @@ class Mixer {
6111
6235
  }
6112
6236
  let a = v[0];
6113
6237
  if (alpha !== 1) a = from === SETUP ? sl[3] + (a - sl[3]) * alpha : l[3] + (a - l[3]) * alpha;
6114
- l[3] = clamp01(a);
6238
+ l[3] = clamp01$1(a);
6115
6239
  return;
6116
6240
  }
6117
6241
  case "rgba2":
@@ -6149,9 +6273,9 @@ class Mixer {
6149
6273
  dark[k] = from === SETUP ? sd[k] + (dark[k] - sd[k]) * alpha : d[k] + (dark[k] - d[k]) * alpha;
6150
6274
  }
6151
6275
  }
6152
- for (let k = 0; k < 3; k++) l[k] = clamp01(light[k]);
6276
+ for (let k = 0; k < 3; k++) l[k] = clamp01$1(light[k]);
6153
6277
  }
6154
- for (let k = 0; k < 3; k++) d[k] = clamp01(dark[k]);
6278
+ for (let k = 0; k < 3; k++) d[k] = clamp01$1(dark[k]);
6155
6279
  return;
6156
6280
  }
6157
6281
  }
@@ -6382,7 +6506,10 @@ class Tracks {
6382
6506
  if (typeof dt !== "number" || !Number.isFinite(dt) || dt < 0) throw new CoreInputError(`the live tracks' step ${n} is ${String(dt)}, not a finite time at or above 0`);
6383
6507
  return this.guard(`step ${n}`, () => {
6384
6508
  this.time += dt;
6509
+ const stage = this.plant.stage;
6510
+ if (stage !== void 0) stage.begin("timeline");
6385
6511
  this.mixer.update(dt);
6512
+ if (stage !== void 0) stage.end("timeline");
6386
6513
  const pose = this.pose(dt, false);
6387
6514
  this.steps = n;
6388
6515
  return pose;
@@ -6403,6 +6530,8 @@ class Tracks {
6403
6530
  const ctx = path.ctx;
6404
6531
  const before = ctx.time;
6405
6532
  ctx.time += this.clock === void 0 ? dt : this.clock(dt);
6533
+ const stage = this.plant.stage;
6534
+ if (stage !== void 0) stage.begin("timeline");
6406
6535
  const pose = new Pose(doc, this.plant);
6407
6536
  const animationTime = this.mixer.apply(pose);
6408
6537
  const entry = this.mixer.tracks[0] ?? null;
@@ -6411,6 +6540,7 @@ class Tracks {
6411
6540
  context: { trackTime: entry?.trackTime ?? null, animation: entry?.anim.name ?? null, globalTime: this.time }
6412
6541
  };
6413
6542
  const mixed = new Map(doc.slots.map((s, k) => [s.name, { deform: pose.slots[k].deform, seen: pose.slots[k].seen }]));
6543
+ if (stage !== void 0) stage.end("timeline");
6414
6544
  return path.step(first, this.time, animationTime, {
6415
6545
  world: (sliders) => steppedLocalsWorld(doc, pose.bones, path.raw, sliders, { ctx, before, adjustment }),
6416
6546
  slots: (sliders, placeholders) => slotRowsFrom(doc, path.raw, sliders, placeholders, (_slot, _live) => {
@@ -6737,6 +6867,15 @@ async function unpackRegions(pages, pageImages, options = {}) {
6737
6867
  function revokeRegions(images) {
6738
6868
  for (const image of images.values()) revokeOwned(image.url);
6739
6869
  }
6870
+ const triangleReadSources = /* @__PURE__ */ new WeakMap();
6871
+ function triangleReadSource(source) {
6872
+ let kept = triangleReadSources.get(source);
6873
+ if (kept === void 0) {
6874
+ kept = Object.isFrozen(source) ? Array.from(source) : source;
6875
+ triangleReadSources.set(source, kept);
6876
+ }
6877
+ return kept;
6878
+ }
6740
6879
  const BLEND_FACTORS = {
6741
6880
  Normal: [
6742
6881
  1,
@@ -6790,11 +6929,12 @@ const VERTEX_SHADER = `
6790
6929
  attribute vec2 aPos;
6791
6930
  attribute vec2 aUV;
6792
6931
  uniform vec2 uResolution;
6932
+ uniform vec2 uOrigin;
6793
6933
  varying vec2 vUV;
6794
6934
  void main() {
6795
6935
  // aPos is in offscreen-canvas device px, top-left origin (matching the
6796
6936
  // top-left-origin drawImage read of the same rect).
6797
- vec2 clip = aPos / uResolution * 2.0 - 1.0;
6937
+ vec2 clip = (aPos - uOrigin) / uResolution * 2.0 - 1.0;
6798
6938
  gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
6799
6939
  vUV = aUV;
6800
6940
  }`;
@@ -6818,6 +6958,7 @@ class MeshGlBlitter {
6818
6958
  canvas = document.createElement("canvas");
6819
6959
  gl;
6820
6960
  uResolution;
6961
+ uOrigin;
6821
6962
  meshProgram;
6822
6963
  meshPos;
6823
6964
  meshUV;
@@ -6860,8 +7001,10 @@ class MeshGlBlitter {
6860
7001
  this.meshProgram = program;
6861
7002
  gl.useProgram(program);
6862
7003
  const uResolution = gl.getUniformLocation(program, "uResolution");
6863
- if (!uResolution) throw new Error("uResolution not found");
7004
+ const uOrigin = gl.getUniformLocation(program, "uOrigin");
7005
+ if (!uResolution || !uOrigin) throw new Error("mesh program uniforms not found");
6864
7006
  this.uResolution = uResolution;
7007
+ this.uOrigin = uOrigin;
6865
7008
  gl.uniform1i(gl.getUniformLocation(program, "uTex"), 0);
6866
7009
  this.meshPos = gl.getAttribLocation(program, "aPos");
6867
7010
  this.meshUV = gl.getAttribLocation(program, "aUV");
@@ -6924,11 +7067,8 @@ class MeshGlBlitter {
6924
7067
  Math.ceil(Math.max(height, this.canvas.height) / GROW_STEP) * GROW_STEP
6925
7068
  );
6926
7069
  }
6927
- const bufW = this.canvas.width;
6928
7070
  const bufH = this.canvas.height;
6929
- gl.viewport(0, 0, bufW, bufH);
6930
7071
  this.bindMesh();
6931
- gl.uniform2f(this.uResolution, bufW, bufH);
6932
7072
  let floats = 0;
6933
7073
  for (const job of jobs) floats += job.triangles.length * 4;
6934
7074
  if (this.vertexData.length < floats) {
@@ -6937,7 +7077,8 @@ class MeshGlBlitter {
6937
7077
  const data = this.vertexData;
6938
7078
  let f = 0;
6939
7079
  for (let i = 0; i < jobs.length; i++) {
6940
- const { vertices, uvs, triangles, ratio } = jobs[i];
7080
+ const { vertices, uvs, ratio } = jobs[i];
7081
+ const triangles = triangleReadSource(jobs[i].triangles);
6941
7082
  const ox = packX[i];
6942
7083
  const oy = packY[i];
6943
7084
  for (let t = 0; t < triangles.length; t++) {
@@ -6957,6 +7098,9 @@ class MeshGlBlitter {
6957
7098
  gl.bindTexture(gl.TEXTURE_2D, this.textureFor(job.page, job.pma));
6958
7099
  boundPage = job.page;
6959
7100
  }
7101
+ gl.viewport(packX[i], bufH - packY[i] - job.height, job.width, job.height);
7102
+ gl.uniform2f(this.uResolution, job.width, job.height);
7103
+ gl.uniform2f(this.uOrigin, packX[i], packY[i]);
6960
7104
  gl.scissor(packX[i], bufH - packY[i] - job.height, job.width, job.height);
6961
7105
  gl.clear(gl.COLOR_BUFFER_BIT);
6962
7106
  gl.drawArrays(gl.TRIANGLES, first, job.triangles.length);
@@ -7457,7 +7601,8 @@ class DomRenderer {
7457
7601
  * clipping attachment, an inactive bone, a kind the DOM tier does not draw —
7458
7602
  * are hidden, their elements kept for when they draw again.
7459
7603
  */
7460
- render(frame2) {
7604
+ render(input) {
7605
+ const frame2 = renderFrameOf(input);
7461
7606
  this.clipCount = 0;
7462
7607
  this.clipSkipCount = 0;
7463
7608
  this.clipWriteCount = 0;
@@ -7699,6 +7844,7 @@ class DomRenderer {
7699
7844
  ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
7700
7845
  const uw = page.width;
7701
7846
  const uh = page.height;
7847
+ triangles = triangleReadSource(triangles);
7702
7848
  for (let t = 0; t < triangles.length; t += 3) {
7703
7849
  const i0 = triangles[t] * 2;
7704
7850
  const i1 = triangles[t + 1] * 2;
@@ -8211,7 +8357,8 @@ class CanvasRenderer {
8211
8357
  this.growTo(box.x, -(box.y + box.height), box.x + box.width, -box.y);
8212
8358
  }
8213
8359
  /** Draws one frame. */
8214
- render(frame2) {
8360
+ render(input) {
8361
+ const frame2 = renderFrameOf(input);
8215
8362
  this.drawCalls = 0;
8216
8363
  this.blendFlushes = 0;
8217
8364
  this.trianglesDrawn = 0;
@@ -8440,7 +8587,8 @@ class CanvasRenderer {
8440
8587
  if (!page) continue;
8441
8588
  const piece = this.piecesOf(slot, clips);
8442
8589
  if (!piece) continue;
8443
- const { vertices, uvs, triangles } = piece;
8590
+ const { vertices, uvs } = piece;
8591
+ const triangles = triangleReadSource(piece.triangles);
8444
8592
  const count = triangles.length;
8445
8593
  if (count === 0) continue;
8446
8594
  if (!this.glPages.has(page) && blitter) {
@@ -8515,7 +8663,8 @@ class CanvasRenderer {
8515
8663
  }
8516
8664
  ctx.globalAlpha = Math.max(0, Math.min(1, slot.tint[3]));
8517
8665
  this.drawCalls++;
8518
- const { vertices, uvs, triangles } = piece;
8666
+ const { vertices, uvs } = piece;
8667
+ const triangles = triangleReadSource(piece.triangles);
8519
8668
  if (slot.kind === "region" && vertices === slot.corners) {
8520
8669
  const c = vertices;
8521
8670
  const sx0 = uvs[2] * uw, sy0 = uvs[3] * uh;
@@ -8579,7 +8728,7 @@ function readSetupMesh(assets, target) {
8579
8728
  return readSetupMesh$1(underNoSkin(assets.document.doc), target);
8580
8729
  }
8581
8730
  const SETS_SPECS = ["spine-parts-sets/1", "rig-parts-sets/1"];
8582
- const SET_KEYS_NOT_POSED = ["drawnLength", "registration", "threshold"];
8731
+ const SET_KEYS_NOT_POSED = ["drawnLength", "registration", "threshold", "focus"];
8583
8732
  const SET_KEYS = ["slots", "hides", "drawn", ...SET_KEYS_NOT_POSED];
8584
8733
  const SETS_DOCUMENT_KEYS = ["spec", "sets", "threshold"];
8585
8734
  function isRecord(value) {
@@ -8701,6 +8850,11 @@ function readSetsText(text, where, doc) {
8701
8850
  drawn[boneName] = degrees;
8702
8851
  }
8703
8852
  }
8853
+ if (item.focus !== void 0) {
8854
+ if (!Array.isArray(item.focus) || item.focus.length !== 2 || typeof item.focus[0] !== "number" || !Number.isFinite(item.focus[0]) || typeof item.focus[1] !== "number" || !Number.isFinite(item.focus[1])) {
8855
+ throw new Error(`${where}: set "${name}" "focus" must be [x, y] of two finite numbers`);
8856
+ }
8857
+ }
8704
8858
  out.push(
8705
8859
  Object.freeze({
8706
8860
  name,
@@ -8823,6 +8977,1383 @@ async function loadModel(options) {
8823
8977
  }
8824
8978
  };
8825
8979
  }
8980
+ const CURVE_LINEAR = 0;
8981
+ const CURVE_STEPPED = 1;
8982
+ const CURVE_BEZIER = 2;
8983
+ const POLYLINE = 2 * (BEZIER_PIECES + 1);
8984
+ function compileCurve(keys, channels) {
8985
+ const n = keys.length;
8986
+ const times2 = new Float64Array(n);
8987
+ const values2 = new Float64Array(n * channels);
8988
+ const kinds = new Uint8Array(n);
8989
+ const polylines = new Float64Array(n * channels * POLYLINE);
8990
+ for (let i = 0; i < n; i++) {
8991
+ const a = keys[i];
8992
+ times2[i] = a.time;
8993
+ for (let c = 0; c < channels; c++) values2[i * channels + c] = a.values[c];
8994
+ const curve = a.curve;
8995
+ kinds[i] = curve === "linear" ? CURVE_LINEAR : curve === "stepped" ? CURVE_STEPPED : CURVE_BEZIER;
8996
+ if (typeof curve === "string" || i + 1 >= n) continue;
8997
+ const b = keys[i + 1];
8998
+ for (let c = 0; c < channels; c++) {
8999
+ const inner = bezierPolyline(a.stated.time, a.stated.values[c], curve[c * 4], curve[c * 4 + 1], curve[c * 4 + 2], curve[c * 4 + 3], b.stated.time, b.stated.values[c]);
9000
+ const o = (i * channels + c) * POLYLINE;
9001
+ polylines[o] = a.time;
9002
+ polylines[o + 1] = a.values[c];
9003
+ for (let q = 0; q < inner.length; q++) polylines[o + 2 + q] = inner[q];
9004
+ polylines[o + POLYLINE - 2] = b.time;
9005
+ polylines[o + POLYLINE - 1] = b.values[c];
9006
+ }
9007
+ }
9008
+ return { keys: n, channels, times: times2, values: values2, kinds, polylines };
9009
+ }
9010
+ function keyAt(curve, cursors, slot, t) {
9011
+ const times2 = curve.times;
9012
+ let i = cursors[slot];
9013
+ while (i >= 0 && times2[i] > t) i--;
9014
+ while (i + 1 < curve.keys && times2[i + 1] <= t) i++;
9015
+ cursors[slot] = i;
9016
+ return i;
9017
+ }
9018
+ function channelValue(curve, i, c, t) {
9019
+ const ch = curve.channels;
9020
+ const values2 = curve.values;
9021
+ const v0 = values2[i * ch + c];
9022
+ if (i + 1 >= curve.keys) return v0;
9023
+ const kind = curve.kinds[i];
9024
+ if (kind === CURVE_STEPPED) return v0;
9025
+ if (kind === CURVE_LINEAR) {
9026
+ const t0 = curve.times[i];
9027
+ return v0 + (t - t0) / (curve.times[i + 1] - t0) * (values2[(i + 1) * ch + c] - v0);
9028
+ }
9029
+ const p = curve.polylines;
9030
+ const o = (i * ch + c) * POLYLINE;
9031
+ let q = 2;
9032
+ while (q < POLYLINE - 2 && p[o + q] < t) q += 2;
9033
+ const x0 = p[o + q - 2];
9034
+ const y0 = p[o + q - 1];
9035
+ return y0 + (t - x0) / (p[o + q] - x0) * (p[o + q + 1] - y0);
9036
+ }
9037
+ const RUNTIME_STAGE = 1;
9038
+ function missingText(m) {
9039
+ return `${m.feature}: ${m.detail}${m.animation === null ? "" : ` (animation "${m.animation}")`}`;
9040
+ }
9041
+ class RuntimeRefusal extends Error {
9042
+ constructor(missing) {
9043
+ super(`not posed by the runtime poser (stage ${RUNTIME_STAGE}), ${missing.length} construct(s) missing: ${missing.map(missingText).join("; ")}`);
9044
+ this.missing = missing;
9045
+ }
9046
+ }
9047
+ class RuntimeInputError extends Error {
9048
+ }
9049
+ const L_X = 0;
9050
+ const L_Y = 1;
9051
+ const L_ROTATION = 2;
9052
+ const L_SCALE_X = 3;
9053
+ const L_SCALE_Y = 4;
9054
+ const L_SHEAR_X = 5;
9055
+ const L_SHEAR_Y = 6;
9056
+ const LOCALS = 7;
9057
+ const W_A = 0;
9058
+ const W_B = 1;
9059
+ const W_C = 2;
9060
+ const W_D = 3;
9061
+ const W_X = 4;
9062
+ const W_Y = 5;
9063
+ const WORLDS = 6;
9064
+ const MODE_NORMAL = 0;
9065
+ const MODE_ONLY_TRANSLATION = 1;
9066
+ const MODE_NO_ROTATION_OR_REFLECTION = 2;
9067
+ const MODE_NO_SCALE = 3;
9068
+ const MODE_NO_SCALE_OR_REFLECTION = 4;
9069
+ const MODE_CODES = { normal: MODE_NORMAL, onlyTranslation: MODE_ONLY_TRANSLATION, noRotationOrReflection: MODE_NO_ROTATION_OR_REFLECTION, noScale: MODE_NO_SCALE, noScaleOrReflection: MODE_NO_SCALE_OR_REFLECTION };
9070
+ const DRAW_NONE = 0;
9071
+ const DRAW_REGION = 1;
9072
+ const DRAW_MESH = 2;
9073
+ const DRAW_WEIGHTED = 3;
9074
+ const SCALE_Y_NONE = 0;
9075
+ const SCALE_Y_UNIFORM = 1;
9076
+ const SCALE_Y_VOLUME = 2;
9077
+ const IK_BEND_POSITIVE = 1;
9078
+ const IK_COMPRESS = 2;
9079
+ const IK_STRETCH = 4;
9080
+ const RAD = RUNTIME_PI / 180;
9081
+ const BONE_KINDS = {
9082
+ rotate: [[L_ROTATION, 0]],
9083
+ translate: [[L_X, 0], [L_Y, 0]],
9084
+ translatex: [[L_X, 0]],
9085
+ translatey: [[L_Y, 0]],
9086
+ scale: [[L_SCALE_X, 1], [L_SCALE_Y, 1]],
9087
+ scalex: [[L_SCALE_X, 1]],
9088
+ scaley: [[L_SCALE_Y, 1]],
9089
+ shear: [[L_SHEAR_X, 0], [L_SHEAR_Y, 0]],
9090
+ shearx: [[L_SHEAR_X, 0]],
9091
+ sheary: [[L_SHEAR_Y, 0]]
9092
+ };
9093
+ const COLOUR_CHANNELS = { rgba: [0, 1, 2, 3], rgb: [0, 1, 2], alpha: [3] };
9094
+ class Refusals {
9095
+ constructor(admitted) {
9096
+ this.admitted = admitted;
9097
+ this.model = [];
9098
+ this.byAnimation = /* @__PURE__ */ new Map();
9099
+ }
9100
+ /** Records the construct unless a plant admits it. The caller poses the construct as absent either way. */
9101
+ note(feature, detail, animation = null) {
9102
+ if (this.admitted.has(feature)) return;
9103
+ const m = { feature, detail, animation };
9104
+ if (animation === null) this.model.push(m);
9105
+ else {
9106
+ const list = this.byAnimation.get(animation) ?? [];
9107
+ list.push(m);
9108
+ this.byAnimation.set(animation, list);
9109
+ }
9110
+ }
9111
+ }
9112
+ function compileRuntime(doc, options = {}, plant = {}) {
9113
+ const refusals = new Refusals(new Set(plant.admit ?? []));
9114
+ const ignoreEvents = (options.ignore ?? []).includes("events");
9115
+ const absentWhy = constraintsAbsentWhy(doc);
9116
+ if (absentWhy !== null) refusals.note("oracle-absent", `the oracle leaves the bones out: ${absentWhy}`);
9117
+ const nb = doc.bones.length;
9118
+ const boneIndex2 = new Map(doc.bones.map((b, i) => [b.name, i]));
9119
+ const indexOf = (name) => {
9120
+ const i = boneIndex2.get(name);
9121
+ if (i === void 0) throw new RuntimeInputError(`bone "${name}" is not a bone of this document (readModel refuses it first)`);
9122
+ return i;
9123
+ };
9124
+ const active = activeBones(doc);
9125
+ const parents = new Int32Array(nb);
9126
+ const modes = new Uint8Array(nb);
9127
+ const activeFlags = new Uint8Array(nb);
9128
+ const lengths = new Float64Array(nb);
9129
+ const setup = new Float64Array(nb * LOCALS);
9130
+ doc.bones.forEach((b, i) => {
9131
+ parents[i] = b.parent === void 0 ? -1 : indexOf(b.parent);
9132
+ if (parents[i] >= i) throw new RuntimeInputError(`bone "${b.name}" is listed before its parent (readModel refuses it first)`);
9133
+ const mode = b.inheritMode === void 0 ? "normal" : foldInheritMode(b.inheritMode);
9134
+ if (mode === null) throw new RuntimeInputError(`bone "${b.name}": inheritMode ${JSON.stringify(b.inheritMode)} is no mode (readModel refuses it first)`);
9135
+ modes[i] = MODE_CODES[mode];
9136
+ activeFlags[i] = active.has(b.name) ? 1 : 0;
9137
+ lengths[i] = b.length ?? 0;
9138
+ const o = i * LOCALS;
9139
+ setup[o + L_X] = b.x ?? 0;
9140
+ setup[o + L_Y] = b.y ?? 0;
9141
+ setup[o + L_ROTATION] = b.rotation ?? 0;
9142
+ setup[o + L_SCALE_X] = b.scaleX ?? 1;
9143
+ setup[o + L_SCALE_Y] = b.scaleY ?? 1;
9144
+ setup[o + L_SHEAR_X] = b.shearX ?? 0;
9145
+ setup[o + L_SHEAR_Y] = b.shearY ?? 0;
9146
+ });
9147
+ if (plant.setupNudge !== void 0) setup[indexOf(plant.setupNudge.bone) * LOCALS + plant.setupNudge.field] += plant.setupNudge.delta;
9148
+ const lists = doc.bones.map(() => []);
9149
+ for (let k = 0; k < nb; k++) for (let at = k; at >= 0; at = parents[at]) lists[at].push(k);
9150
+ const subtreeStart = new Int32Array(nb + 1);
9151
+ const flat = [];
9152
+ for (let k = 0; k < nb; k++) {
9153
+ subtreeStart[k] = flat.length;
9154
+ for (const x of lists[k]) flat.push(x);
9155
+ }
9156
+ subtreeStart[nb] = flat.length;
9157
+ const constraints = [];
9158
+ for (const c of doc.constraints) if (c.kind === "path" || c.kind === "physics" || c.kind === "slider") refusals.note(`${c.kind}-constraint`, `${c.kind} constraint "${c.name}"`);
9159
+ for (const r of constraintRecords(doc)) {
9160
+ if (r.kind !== "ik" && r.kind !== "transform") continue;
9161
+ const applied = constraintInactiveWhy(r, active) === null;
9162
+ const bones = Int32Array.from(r.bones.map(indexOf));
9163
+ if (applied && r.bones.some((b) => !active.has(b))) refusals.note("inactive-constrained-bone", `${r.kind} constraint "${r.name}" names bone(s) ${r.bones.filter((b) => !active.has(b)).map((b) => `"${b}"`).join(", ")}, which the view leaves inactive — what the oracle writes there depends on the pass before`);
9164
+ const index = constraints.length;
9165
+ if (r.kind === "ik") constraints.push(compileIk(r, index, applied, bones, indexOf));
9166
+ else {
9167
+ if (r.localSource || r.localTarget) refusals.note("local-transform", `transform constraint "${r.name}" ${[r.localSource ? "reads local values (localSource)" : "", r.localTarget ? "writes local values (localTarget)" : ""].filter((x) => x !== "").join(" and ")}`);
9168
+ if (r.additive) refusals.note("additive-transform", `transform constraint "${r.name}" is additive`);
9169
+ const t = compileTransform(r, index, applied, bones, indexOf);
9170
+ if (applied) {
9171
+ for (let k = 0; k < bones.length; k++) {
9172
+ const m = modes[bones[k]];
9173
+ if (parents[bones[k]] >= 0 && (m === MODE_NO_SCALE || m === MODE_NO_SCALE_OR_REFLECTION)) refusals.note("noscale-world-target", `transform constraint "${r.name}" moves bone "${r.bones[k]}" in world space, and its local values are read back in a noScale mode`);
9174
+ }
9175
+ }
9176
+ constraints.push(t);
9177
+ }
9178
+ }
9179
+ const attachments = [];
9180
+ let vertexCount = 0;
9181
+ const sourceOf = sourceOfDoc(doc);
9182
+ const attachmentView = plant.attachmentSkin === void 0 ? doc : underSkin(doc, plant.attachmentSkin);
9183
+ const resolvedBySlot = doc.slots.map(() => /* @__PURE__ */ new Map());
9184
+ const resolve = (si, placeholder, animation) => {
9185
+ const cache = resolvedBySlot[si];
9186
+ let kept = cache.get(placeholder);
9187
+ if (kept === void 0) {
9188
+ kept = resolveRecord(si, placeholder);
9189
+ cache.set(placeholder, kept);
9190
+ }
9191
+ if (kept.missing !== null) refusals.note(kept.missing[0], kept.missing[1], animation);
9192
+ return kept.index;
9193
+ };
9194
+ const resolveRecord = (si, placeholder) => {
9195
+ const slot = doc.slots[si];
9196
+ const shown = placeholder === null ? null : shownAttachment(attachmentView, slot, placeholder);
9197
+ if (shown === null) return { index: -1, missing: null };
9198
+ if ("conflict" in shown) return { index: -1, missing: ["skin-conflict", `slot "${slot.name}" placeholder "${placeholder}" is filled by skins that disagree`] };
9199
+ const row = shownRow(shown);
9200
+ const g = shown.record.geometry;
9201
+ const at = { slot: si, name: row.name, skin: shown.skin, placeholder: shown.placeholder, path: CORE_REGION_KINDS.has(shown.record.kind) ? row.path : null, bone: indexOf(slot.bone), offset: vertexCount };
9202
+ let made = null;
9203
+ let missing = null;
9204
+ if (g?.kind === "clipping") missing = ["clipping", `slot "${slot.name}" shows clipping attachment "${row.name}"`];
9205
+ else if (g?.kind === "region") {
9206
+ const r = g.region;
9207
+ if (r.atlas === null) missing = ["null-atlas-region", `slot "${slot.name}" shows region "${row.name}", whose atlas rectangle the model states as null`];
9208
+ else {
9209
+ const W = r.width;
9210
+ const H = r.height;
9211
+ const sx = r.scaleX ?? 1;
9212
+ const sy = r.scaleY ?? 1;
9213
+ const rect = r.atlas;
9214
+ const kx = W / rect.originalWidth * sx;
9215
+ const ky = H / rect.originalHeight * sy;
9216
+ const x1 = -W / 2 * sx + rect.offsetX * kx;
9217
+ const y1 = -H / 2 * sy + rect.offsetY * ky;
9218
+ const x2 = x1 + rect.width * kx;
9219
+ const y2 = y1 + rect.height * ky;
9220
+ const angle = (r.rotation ?? 0) * RAD;
9221
+ const cos = Math.cos(angle);
9222
+ const sin = Math.sin(angle);
9223
+ const x = r.x ?? 0;
9224
+ const y = r.y ?? 0;
9225
+ const local = new Float64Array(8);
9226
+ const corners = [x1, y1, x1, y2, x2, y2, x2, y1];
9227
+ for (let k = 0; k < 8; k += 2) {
9228
+ const px = corners[k];
9229
+ const py = corners[k + 1];
9230
+ local[k] = px * cos + x - py * sin;
9231
+ local[k + 1] = py * cos + y + px * sin;
9232
+ }
9233
+ made = vertexAttachment(at, DRAW_REGION, local, 8);
9234
+ }
9235
+ } else if (g?.kind === "mesh" || g?.kind === "linkedmesh") {
9236
+ const source = g.kind === "mesh" ? g.vertices : sourceOf(g.skin, g.slot, g.source);
9237
+ if (typeof source === "string") throw new RuntimeInputError(`slot "${slot.name}": ${source} (readModel refuses it first)`);
9238
+ made = meshAttachment(at, source, indexOf, plant.bindingIndex, nb);
9239
+ }
9240
+ if (made === null) made = vertexAttachment(at, DRAW_NONE, new Float64Array(0), 0);
9241
+ attachments.push(made);
9242
+ vertexCount += made.count;
9243
+ return { index: attachments.length - 1, missing };
9244
+ };
9245
+ const slots = doc.slots.map((slot, si) => {
9246
+ const bone2 = indexOf(slot.bone);
9247
+ const light = slot.color === void 0 ? [1, 1, 1, 1] : readColour(slot.color);
9248
+ return {
9249
+ name: slot.name,
9250
+ bone: bone2,
9251
+ // A slot on a bone the view leaves inactive is not animated (`slotTimelinesApply` in src/core/skins.ts).
9252
+ live: active.has(slot.bone),
9253
+ setupAttachment: resolve(si, slot.setup, null),
9254
+ light: Float64Array.from(light),
9255
+ dark: slot.dark === void 0 ? null : Float64Array.from(readColour(slot.dark).slice(0, 3)),
9256
+ blend: readBlend(slot)
9257
+ };
9258
+ });
9259
+ const slotIndex = new Map(doc.slots.map((s, i) => [s.name, i]));
9260
+ const animations = /* @__PURE__ */ new Map();
9261
+ const refused = [];
9262
+ let cursorMax = 0;
9263
+ for (const anim of doc.animations) {
9264
+ const compiled = compileAnimation(anim, refusals, ignoreEvents, boneIndex2, slotIndex, slots, constraints, (si, ph) => resolve(si, ph, anim.name));
9265
+ const missing = refusals.byAnimation.get(anim.name);
9266
+ if (missing !== void 0 && missing.length > 0) refused.push({ animation: anim.name, missing });
9267
+ else {
9268
+ animations.set(anim.name, compiled);
9269
+ cursorMax = Math.max(cursorMax, compiled.cursors);
9270
+ }
9271
+ }
9272
+ if (refusals.model.length > 0) throw new RuntimeRefusal(refusals.model);
9273
+ const names = doc.animations.map((a) => a.name);
9274
+ return {
9275
+ eventWrapOffByOne: plant.eventWrapOffByOne === true,
9276
+ restoreEventsFromStart: plant.restoreEventsFromStart === true,
9277
+ doc,
9278
+ boneNames: doc.bones.map((b) => b.name),
9279
+ parents,
9280
+ modes,
9281
+ active: activeFlags,
9282
+ lengths,
9283
+ setup,
9284
+ subtreeStart,
9285
+ subtrees: Int32Array.from(flat),
9286
+ slots,
9287
+ attachments,
9288
+ vertexCount,
9289
+ constraints,
9290
+ animations,
9291
+ refused,
9292
+ cursors: cursorMax,
9293
+ scratch: plant.scratchByBones === true ? Math.max(1, nb) : Math.max(1, nb, ...constraints.map((c) => c.bones.length)),
9294
+ animation(name) {
9295
+ const found = animations.get(name);
9296
+ if (found !== void 0) return found;
9297
+ const no = refused.find((r) => r.animation === name);
9298
+ if (no !== void 0) throw new RuntimeRefusal(no.missing);
9299
+ throw new RuntimeInputError(`animation ${JSON.stringify(name)} is not one of this document's [${names.join(", ")}]`);
9300
+ }
9301
+ };
9302
+ }
9303
+ const NO_INTS = new Int32Array(0);
9304
+ const NO_NUMBERS = new Float64Array(0);
9305
+ function vertexAttachment(at, draw, local, count) {
9306
+ return { ...at, draw, local, count, bindingCounts: NO_INTS, bindingBones: NO_INTS, bindingX: NO_NUMBERS, bindingY: NO_NUMBERS, bindingWeights: NO_NUMBERS };
9307
+ }
9308
+ function meshAttachment(at, vertices, indexOf, rewrite, bones) {
9309
+ if (!vertices.weighted) {
9310
+ const n = vertices.xy.length - vertices.xy.length % 2;
9311
+ const local = new Float64Array(n);
9312
+ for (let i = 0; i < n; i++) local[i] = Math.fround(vertices.xy[i]);
9313
+ return vertexAttachment(at, DRAW_MESH, local, n);
9314
+ }
9315
+ const counts = [];
9316
+ const ids = [];
9317
+ const xs = [];
9318
+ const ys = [];
9319
+ const ws = [];
9320
+ for (const influences of vertices.bindings) {
9321
+ counts.push(influences.length);
9322
+ for (const b of influences) {
9323
+ const i = indexOf(b.bone);
9324
+ ids.push(rewrite === void 0 ? i : rewrite(i, bones));
9325
+ xs.push(Math.fround(b.x));
9326
+ ys.push(Math.fround(b.y));
9327
+ ws.push(Math.fround(b.weight));
9328
+ }
9329
+ }
9330
+ return { ...at, draw: DRAW_WEIGHTED, local: NO_NUMBERS, count: counts.length * 2, bindingCounts: Int32Array.from(counts), bindingBones: Int32Array.from(ids), bindingX: Float64Array.from(xs), bindingY: Float64Array.from(ys), bindingWeights: Float64Array.from(ws) };
9331
+ }
9332
+ function compileIk(r, index, applied, bones, indexOf) {
9333
+ return {
9334
+ kind: "ik",
9335
+ name: r.name,
9336
+ index,
9337
+ applied,
9338
+ bones,
9339
+ target: indexOf(r.target),
9340
+ scaleY: r.scaleY === "uniform" ? SCALE_Y_UNIFORM : r.scaleY === "volume" ? SCALE_Y_VOLUME : SCALE_Y_NONE,
9341
+ mix: r.mix,
9342
+ softness: r.softness,
9343
+ flags: ikFlags(r.bendPositive, r.compress, r.stretch)
9344
+ };
9345
+ }
9346
+ function ikFlags(bendPositive, compress, stretch) {
9347
+ return (bendPositive ? IK_BEND_POSITIVE : 0) | (compress ? IK_COMPRESS : 0) | (stretch ? IK_STRETCH : 0);
9348
+ }
9349
+ function compileTransform(r, index, applied, bones, indexOf) {
9350
+ const fromProperty = [];
9351
+ const fromOffset = [];
9352
+ const toStart = [];
9353
+ const toEnd = [];
9354
+ const toProperty = [];
9355
+ const toOffset = [];
9356
+ const toScale = [];
9357
+ const toMax = [];
9358
+ for (const from of r.properties) {
9359
+ fromProperty.push(TRANSFORM_PROPERTIES.indexOf(from.property));
9360
+ fromOffset.push(from.offset);
9361
+ toStart.push(toProperty.length);
9362
+ for (const to of from.to) {
9363
+ toProperty.push(TRANSFORM_PROPERTIES.indexOf(to.property));
9364
+ toOffset.push(to.offset);
9365
+ toScale.push(to.scale);
9366
+ toMax.push(to.max);
9367
+ }
9368
+ toEnd.push(toProperty.length);
9369
+ }
9370
+ return {
9371
+ kind: "transform",
9372
+ name: r.name,
9373
+ index,
9374
+ applied,
9375
+ bones,
9376
+ source: indexOf(r.source),
9377
+ fromProperty: Int32Array.from(fromProperty),
9378
+ fromOffset: Float64Array.from(fromOffset),
9379
+ toStart: Int32Array.from(toStart),
9380
+ toEnd: Int32Array.from(toEnd),
9381
+ toProperty: Int32Array.from(toProperty),
9382
+ toOffset: Float64Array.from(toOffset),
9383
+ toScale: Float64Array.from(toScale),
9384
+ toMax: Float64Array.from(toMax),
9385
+ offsets: Float64Array.from(TRANSFORM_PROPERTIES.map((p) => r.offsets[p])),
9386
+ mixes: Float64Array.from(TRANSFORM_PROPERTIES.map((p) => r.mixes[p])),
9387
+ clamp: r.clamp
9388
+ };
9389
+ }
9390
+ function compileAnimation(anim, refusals, ignoreEvents, boneIndex2, slotIndex, slots, constraints, resolve) {
9391
+ const tl = anim.timelines;
9392
+ const name = anim.name;
9393
+ let cursors = 0;
9394
+ const deform = tl.attachments.filter((a) => a.deform !== null);
9395
+ const sequence = tl.attachments.filter((a) => a.sequence !== null);
9396
+ if (deform.length > 0) refusals.note("deform-timeline", `${deform.length} deform timeline(s): ${deform.slice(0, 3).map((a) => `${a.skin}/${a.slot}/${a.attachment}`).join(", ")}${deform.length > 3 ? ", …" : ""}`, name);
9397
+ if (sequence.length > 0) refusals.note("sequence-timeline", `${sequence.length} sequence timeline(s): ${sequence.slice(0, 3).map((a) => `${a.skin}/${a.slot}/${a.attachment}`).join(", ")}${sequence.length > 3 ? ", …" : ""}`, name);
9398
+ if (tl.drawOrder.length > 0) refusals.note("draw-order-timeline", `${tl.drawOrder.length} draw-order key(s)`, name);
9399
+ const lastBy = (targets) => new Map(targets.map((t) => [t.name, t]));
9400
+ const bones = [];
9401
+ for (const target of lastBy(tl.bones).values()) {
9402
+ const bone2 = boneIndex2.get(target.name);
9403
+ if (bone2 === void 0) continue;
9404
+ for (const t of target.timelines) {
9405
+ if (t.kind === "inherit") {
9406
+ refusals.note("inherit-timeline", `bone "${target.name}" keys its inherit mode`, name);
9407
+ continue;
9408
+ }
9409
+ const spec = BONE_KINDS[t.kind];
9410
+ bones.push({ bone: bone2, curve: compileCurve(t.keys, spec.length), cursor: cursors++, fields: Int32Array.from(spec.map((s) => s[0])), multiply: Uint8Array.from(spec.map((s) => s[1])) });
9411
+ }
9412
+ }
9413
+ const slotTimelines = [];
9414
+ const keyedSlots = [...lastBy(tl.slots).values()].flatMap((target) => {
9415
+ const si = slotIndex.get(target.name);
9416
+ return si === void 0 ? [] : [{ si, target }];
9417
+ }).sort((a, b) => a.si - b.si);
9418
+ for (const { si, target } of keyedSlots) {
9419
+ if (!slots[si].live) continue;
9420
+ for (const t of target.timelines) {
9421
+ if (t.kind === "attachment") {
9422
+ const ids = Int32Array.from(t.keys.map((k) => resolve(si, k.name ?? null)));
9423
+ slotTimelines.push({ slot: si, curve: compileCurve(t.keys, 0), cursor: cursors++, attachments: ids, channels: null });
9424
+ } else if (t.kind === "rgba2" || t.kind === "rgb2") {
9425
+ refusals.note("dark-colour-timeline", `slot "${target.name}" keys an ${t.kind} timeline`, name);
9426
+ } else {
9427
+ const channels = COLOUR_CHANNELS[t.kind];
9428
+ slotTimelines.push({ slot: si, curve: compileCurve(t.keys, channels.length), cursor: cursors++, attachments: null, channels: Int32Array.from(channels) });
9429
+ }
9430
+ }
9431
+ }
9432
+ const constraintTimelines = constraints.map(() => null);
9433
+ for (const c of constraints) {
9434
+ if (c.kind === "ik") {
9435
+ const keyed = anim.constraints.ik.find((x) => x.name === c.name);
9436
+ if (keyed === void 0) continue;
9437
+ constraintTimelines[c.index] = { constraint: c.index, curve: compileCurve(keyed.keys, 2), cursor: cursors++, flags: Uint8Array.from(keyed.keys.map((k) => ikFlags(k.flags.bendPositive, k.flags.compress, k.flags.stretch))) };
9438
+ } else {
9439
+ const keyed = anim.constraints.transform.find((x) => x.name === c.name);
9440
+ if (keyed === void 0) continue;
9441
+ constraintTimelines[c.index] = { constraint: c.index, curve: compileCurve(keyed.keys, TRANSFORM_PROPERTIES.length), cursor: cursors++, flags: null };
9442
+ }
9443
+ }
9444
+ return { name, duration: tl.duration, bones, slots: slotTimelines, constraints: constraintTimelines, cursors, events: tl.events.length, eventKeys: Object.freeze(ignoreEvents ? [] : tl.events.map((key) => Object.freeze({ name: key.name, time: key.time, int: key.int, float: key.float, string: key.string, volume: key.volume ?? 0, balance: key.balance ?? 0 }))) };
9445
+ }
9446
+ const DEG = RUNTIME_DEG;
9447
+ const TWO_PI = 2 * RUNTIME_PI;
9448
+ const P_ROTATE = 0;
9449
+ const P_X = 1;
9450
+ const P_Y = 2;
9451
+ const P_SCALE_X = 3;
9452
+ const P_SCALE_Y = 4;
9453
+ function wrap180(r) {
9454
+ return r > 180 ? r - 360 : r <= -180 ? r + 360 : r;
9455
+ }
9456
+ function volumeScaleY(scaleY, s) {
9457
+ return scaleY / (s < 0.7 ? 0.25 + s * 0.642857 : s);
9458
+ }
9459
+ const clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
9460
+ class RuntimePose {
9461
+ constructor(model) {
9462
+ this.trackTime = 0;
9463
+ this.time = 0;
9464
+ this.fired = [];
9465
+ this.eventTime = -1;
9466
+ this.current = null;
9467
+ this.w = new Float64Array(WORLDS);
9468
+ this.model = model;
9469
+ const nb = model.boneNames.length;
9470
+ const ns = model.slots.length;
9471
+ const nc = model.constraints.length;
9472
+ this.locals = new Float64Array(nb * LOCALS);
9473
+ this.world = new Float64Array(nb * WORLDS);
9474
+ this.light = new Float64Array(ns * 4);
9475
+ this.shown = new Int32Array(ns);
9476
+ this.vertices = new Float64Array(model.vertexCount);
9477
+ this.cursors = new Int32Array(Math.max(1, model.cursors)).fill(-1);
9478
+ this.ikMix = new Float64Array(nc);
9479
+ this.ikSoftness = new Float64Array(nc);
9480
+ this.ikFlags = new Uint8Array(nc);
9481
+ this.mixes = new Float64Array(nc * 6);
9482
+ this.changed = new Int32Array(model.scratch);
9483
+ this.inWorld = new Int32Array(model.scratch);
9484
+ this.changedMark = new Uint8Array(nb);
9485
+ this.inWorldMark = new Uint8Array(nb);
9486
+ this.below = new Uint8Array(nb);
9487
+ this.visit = new Uint8Array(nb);
9488
+ }
9489
+ /** Borrowed immutable event records, in delivery order, until the next apply/step. */
9490
+ get events() {
9491
+ return this.fired;
9492
+ }
9493
+ /** The animation this pose plays (a covered one: `model.animation` throws a `RuntimeRefusal` for a refused one), from track time 0. */
9494
+ setAnimation(name) {
9495
+ this.current = this.model.animation(name);
9496
+ this.cursors.fill(-1);
9497
+ this.trackTime = 0;
9498
+ this.time = 0;
9499
+ this.eventTime = -1;
9500
+ this.fired.length = 0;
9501
+ }
9502
+ /**
9503
+ * Restore one animation's two clocks without delivering keys already passed.
9504
+ * A skin/view replacement uses the old pose's trackTime and wrapped time.
9505
+ * Unlike setAnimation + apply, restoration never replays the initial interval.
9506
+ */
9507
+ restore(name, trackTime, animationTime) {
9508
+ if (!Number.isFinite(trackTime)) throw new RuntimeInputError(`restore: track time ${trackTime} is not finite`);
9509
+ if (!Number.isFinite(animationTime)) throw new RuntimeInputError(`restore: animation time ${animationTime} is not finite`);
9510
+ this.setAnimation(name);
9511
+ this.trackTime = trackTime;
9512
+ if (!this.model.restoreEventsFromStart) this.eventTime = animationTime;
9513
+ this.apply(animationTime);
9514
+ }
9515
+ /** The setup pose: every constraint applied with its own values, every slot as it is set up. */
9516
+ setupPose() {
9517
+ this.current = null;
9518
+ this.time = 0;
9519
+ this.eventTime = -1;
9520
+ this.fired.length = 0;
9521
+ this.locals.set(this.model.setup);
9522
+ this.hierarchy();
9523
+ this.constraintValues(null, 0);
9524
+ this.solveConstraints();
9525
+ this.slotValues(null, 0);
9526
+ }
9527
+ /** The animation posed at its time `t`, from the setup pose (the header). */
9528
+ apply(t) {
9529
+ const anim = this.current;
9530
+ if (anim === null) throw new RuntimeInputError("apply: no animation is set (setAnimation first)");
9531
+ if (!Number.isFinite(t)) throw new RuntimeInputError(`apply: the time ${t} is not finite`);
9532
+ this.collectEvents(anim, t);
9533
+ this.time = t;
9534
+ this.boneTimelines(anim, t);
9535
+ this.hierarchy();
9536
+ this.constraintValues(anim, t);
9537
+ this.solveConstraints();
9538
+ this.slotValues(anim, t);
9539
+ }
9540
+ /** One step of a looping track: the track time moved by `dt`, the animation posed at it wrapped by the duration (`loopedTime` in src/core/raw.ts). */
9541
+ step(dt) {
9542
+ const anim = this.current;
9543
+ if (anim === null) throw new RuntimeInputError("step: no animation is set (setAnimation first)");
9544
+ this.trackTime += dt;
9545
+ this.apply(anim.duration === 0 ? 0 : this.trackTime % anim.duration);
9546
+ }
9547
+ /** The oracle interval (last,t], tail then head on a wrap; never copies a payload. */
9548
+ collectEvents(anim, t) {
9549
+ const last = this.eventTime;
9550
+ const wrapped = t < last;
9551
+ const keys = anim.eventKeys;
9552
+ let count = 0;
9553
+ if (wrapped) {
9554
+ for (let i = 0; i < keys.length; i++) if (keys[i].time > last) this.fired[count++] = keys[i];
9555
+ }
9556
+ for (let i = 0; i < keys.length; i++) {
9557
+ const key = keys[i];
9558
+ if (key.time <= t && (wrapped ? !this.model.eventWrapOffByOne || key.time > 0 : key.time > last)) this.fired[count++] = key;
9559
+ }
9560
+ this.fired.length = count;
9561
+ this.eventTime = t;
9562
+ }
9563
+ // ---------------------------------------------------------------------------
9564
+ // the bones
9565
+ // ---------------------------------------------------------------------------
9566
+ /** The setup locals with the bone timelines' values at `t` (`posedBones`): added, or multiplied for a scale; a channel before its timeline's first key at setup plus 0, or times 1. */
9567
+ boneTimelines(anim, t) {
9568
+ const L = this.locals;
9569
+ const S = this.model.setup;
9570
+ L.set(S);
9571
+ const timelines = anim.bones;
9572
+ for (let k = 0; k < timelines.length; k++) {
9573
+ const tl = timelines[k];
9574
+ const i = keyAt(tl.curve, this.cursors, tl.cursor, t);
9575
+ const o = tl.bone * LOCALS;
9576
+ const fields = tl.fields;
9577
+ for (let c = 0; c < fields.length; c++) {
9578
+ const at = o + fields[c];
9579
+ if (tl.multiply[c] === 1) L[at] = S[at] * (i < 0 ? 1 : channelValue(tl.curve, i, c, t));
9580
+ else L[at] = S[at] + (i < 0 ? 0 : channelValue(tl.curve, i, c, t));
9581
+ }
9582
+ }
9583
+ }
9584
+ /** Every bone's world transform from its locals, parents first (`worldTransforms`): an inactive bone as zeros. */
9585
+ hierarchy() {
9586
+ const nb = this.model.boneNames.length;
9587
+ for (let i = 0; i < nb; i++) this.poseBone(i, true);
9588
+ }
9589
+ /**
9590
+ * One bone's world transform from its locals under its parent's current
9591
+ * world (`worldTransforms` and the update order's `poseBone`). An inactive
9592
+ * bone is zeroed by the hierarchy (`fresh`) and left as it is by a repose.
9593
+ */
9594
+ poseBone(i, fresh) {
9595
+ const m = this.model;
9596
+ const W = this.world;
9597
+ const o = i * WORLDS;
9598
+ if (m.active[i] === 0) {
9599
+ if (fresh) {
9600
+ W[o + W_A] = 0;
9601
+ W[o + W_B] = 0;
9602
+ W[o + W_C] = 0;
9603
+ W[o + W_D] = 0;
9604
+ W[o + W_X] = 0;
9605
+ W[o + W_Y] = 0;
9606
+ }
9607
+ return;
9608
+ }
9609
+ const L = this.locals;
9610
+ const l = i * LOCALS;
9611
+ const x = L[l + L_X];
9612
+ const y = L[l + L_Y];
9613
+ const rotation = L[l + L_ROTATION];
9614
+ const scaleX = L[l + L_SCALE_X];
9615
+ const scaleY = L[l + L_SCALE_Y];
9616
+ const shearX = L[l + L_SHEAR_X];
9617
+ const shearY = L[l + L_SHEAR_Y];
9618
+ const parent = m.parents[i];
9619
+ if (parent < 0) {
9620
+ const xAngle = (rotation + shearX) * RAD;
9621
+ const yAngle = (rotation + 90 + shearY) * RAD;
9622
+ W[o + W_A] = Math.cos(xAngle) * scaleX;
9623
+ W[o + W_B] = Math.cos(yAngle) * scaleY;
9624
+ W[o + W_C] = Math.sin(xAngle) * scaleX;
9625
+ W[o + W_D] = Math.sin(yAngle) * scaleY;
9626
+ W[o + W_X] = x;
9627
+ W[o + W_Y] = y;
9628
+ return;
9629
+ }
9630
+ const q = parent * WORLDS;
9631
+ const pa = W[q + W_A];
9632
+ const pb = W[q + W_B];
9633
+ const pc = W[q + W_C];
9634
+ const pd = W[q + W_D];
9635
+ const px = W[q + W_X];
9636
+ const py = W[q + W_Y];
9637
+ switch (m.modes[i]) {
9638
+ case MODE_NORMAL: {
9639
+ const xAngle = (rotation + shearX) * RAD;
9640
+ const yAngle = (rotation + 90 + shearY) * RAD;
9641
+ const la = Math.cos(xAngle) * scaleX;
9642
+ const lb = Math.cos(yAngle) * scaleY;
9643
+ const lc = Math.sin(xAngle) * scaleX;
9644
+ const ld = Math.sin(yAngle) * scaleY;
9645
+ W[o + W_A] = pa * la + pb * lc;
9646
+ W[o + W_B] = pa * lb + pb * ld;
9647
+ W[o + W_C] = pc * la + pd * lc;
9648
+ W[o + W_D] = pc * lb + pd * ld;
9649
+ break;
9650
+ }
9651
+ case MODE_ONLY_TRANSLATION: {
9652
+ const xAngle = (rotation + shearX) * RAD;
9653
+ const yAngle = (rotation + 90 + shearY) * RAD;
9654
+ W[o + W_A] = Math.cos(xAngle) * scaleX;
9655
+ W[o + W_B] = Math.cos(yAngle) * scaleY;
9656
+ W[o + W_C] = Math.sin(xAngle) * scaleX;
9657
+ W[o + W_D] = Math.sin(yAngle) * scaleY;
9658
+ break;
9659
+ }
9660
+ case MODE_NO_ROTATION_OR_REFLECTION: {
9661
+ const xLengthSq = pa * pa + pc * pc;
9662
+ let c0;
9663
+ let c1;
9664
+ let c2;
9665
+ let c3;
9666
+ let r;
9667
+ if (xLengthSq > COLLAPSED_X_AXIS_SQ) {
9668
+ const k = Math.abs(pa * pd - pb * pc) / xLengthSq;
9669
+ c0 = pa;
9670
+ c1 = -pc * k;
9671
+ c2 = pc;
9672
+ c3 = pa * k;
9673
+ r = rotation - Math.atan2(pc, pa) * DEG;
9674
+ } else {
9675
+ c0 = 0;
9676
+ c1 = -pb;
9677
+ c2 = 0;
9678
+ c3 = pd;
9679
+ r = rotation - 90 + Math.atan2(pd, pb) * DEG;
9680
+ }
9681
+ const xAngle = (r + shearX) * RAD;
9682
+ const yAngle = (r + shearY + 90) * RAD;
9683
+ const la = Math.cos(xAngle) * scaleX;
9684
+ const lb = Math.cos(yAngle) * scaleY;
9685
+ const lc = Math.sin(xAngle) * scaleX;
9686
+ const ld = Math.sin(yAngle) * scaleY;
9687
+ W[o + W_A] = c0 * la + c1 * lc;
9688
+ W[o + W_B] = c0 * lb + c1 * ld;
9689
+ W[o + W_C] = c2 * la + c3 * lc;
9690
+ W[o + W_D] = c2 * lb + c3 * ld;
9691
+ break;
9692
+ }
9693
+ default: {
9694
+ const rr = rotation * RAD;
9695
+ const cos = Math.cos(rr);
9696
+ const sin = Math.sin(rr);
9697
+ const ux0 = pa * cos + pb * sin;
9698
+ const uy0 = pc * cos + pd * sin;
9699
+ const inverse = 1 / Math.sqrt(ux0 * ux0 + uy0 * uy0);
9700
+ const ux = ux0 * inverse;
9701
+ const uy = uy0 * inverse;
9702
+ const flip = m.modes[i] === MODE_NO_SCALE && pa * pd - pb * pc < 0 ? -1 : 1;
9703
+ const t0 = ux;
9704
+ const t1 = -uy * flip;
9705
+ const t2 = uy;
9706
+ const t3 = ux * flip;
9707
+ const xAngle = (0 + shearX) * RAD;
9708
+ const yAngle = (0 + 90 + shearY) * RAD;
9709
+ const la = Math.cos(xAngle) * scaleX;
9710
+ const lb = Math.cos(yAngle) * scaleY;
9711
+ const lc = Math.sin(xAngle) * scaleX;
9712
+ const ld = Math.sin(yAngle) * scaleY;
9713
+ W[o + W_A] = t0 * la + t1 * lc;
9714
+ W[o + W_B] = t0 * lb + t1 * ld;
9715
+ W[o + W_C] = t2 * la + t3 * lc;
9716
+ W[o + W_D] = t2 * lb + t3 * ld;
9717
+ }
9718
+ }
9719
+ W[o + W_X] = pa * x + pb * y + px;
9720
+ W[o + W_Y] = pc * x + pd * y + py;
9721
+ }
9722
+ // ---------------------------------------------------------------------------
9723
+ // the constraints
9724
+ // ---------------------------------------------------------------------------
9725
+ /** Each constraint's values at `t` (`posedRecords`): its own before its timeline's first key, else the keyed values through the setup blend and the key's flags. */
9726
+ constraintValues(anim, t) {
9727
+ const cs = this.model.constraints;
9728
+ for (let k = 0; k < cs.length; k++) {
9729
+ const c = cs[k];
9730
+ const tl = anim === null ? null : anim.constraints[k];
9731
+ const i = tl === null ? -1 : keyAt(tl.curve, this.cursors, tl.cursor, t);
9732
+ if (c.kind === "ik") {
9733
+ if (tl === null || i < 0) {
9734
+ this.ikMix[k] = c.mix;
9735
+ this.ikSoftness[k] = c.softness;
9736
+ this.ikFlags[k] = c.flags;
9737
+ } else {
9738
+ this.ikMix[k] = c.mix + (channelValue(tl.curve, i, 0, t) - c.mix) * 1;
9739
+ this.ikSoftness[k] = c.softness + (channelValue(tl.curve, i, 1, t) - c.softness) * 1;
9740
+ this.ikFlags[k] = tl.flags[i];
9741
+ }
9742
+ } else {
9743
+ const o = k * 6;
9744
+ for (let p = 0; p < 6; p++) this.mixes[o + p] = tl === null || i < 0 ? c.mixes[p] : c.mixes[p] + (channelValue(tl.curve, i, p, t) - c.mixes[p]) * 1;
9745
+ }
9746
+ }
9747
+ }
9748
+ /** Every applied constraint in the document's order, each followed by the update order's repose (`applyConstraints`). */
9749
+ solveConstraints() {
9750
+ const cs = this.model.constraints;
9751
+ for (let k = 0; k < cs.length; k++) {
9752
+ const c = cs[k];
9753
+ if (!c.applied) continue;
9754
+ if (c.kind === "ik") this.solveIk(c, k);
9755
+ else this.solveTransform(c, k);
9756
+ }
9757
+ }
9758
+ solveIk(c, k) {
9759
+ const mix = this.ikMix[k];
9760
+ if (mix === 0) return;
9761
+ const flags = this.ikFlags[k];
9762
+ if (c.bones.length === 1) {
9763
+ this.solveOne(c.bones[0], c.target, mix, (flags & IK_COMPRESS) !== 0, (flags & IK_STRETCH) !== 0, c.scaleY);
9764
+ this.changed[0] = c.bones[0];
9765
+ this.repose(1, 0);
9766
+ } else if (this.solveTwo(c, mix, this.ikSoftness[k], flags)) {
9767
+ this.changed[0] = c.bones[0];
9768
+ this.repose(1, 0);
9769
+ }
9770
+ }
9771
+ /** A one-bone ik (`solveOne`). */
9772
+ solveOne(bi, ti, mix, compress, stretch, scaleYMode) {
9773
+ const m = this.model;
9774
+ const L = this.locals;
9775
+ const W = this.world;
9776
+ const parent = m.parents[bi];
9777
+ const q = parent * WORLDS;
9778
+ let pa = 1;
9779
+ let pb = 0;
9780
+ let pc = 0;
9781
+ let pd = 1;
9782
+ let pwx = 0;
9783
+ let pwy = 0;
9784
+ if (parent >= 0) {
9785
+ pa = W[q + W_A];
9786
+ pb = W[q + W_B];
9787
+ pc = W[q + W_C];
9788
+ pd = W[q + W_D];
9789
+ pwx = W[q + W_X];
9790
+ pwy = W[q + W_Y];
9791
+ }
9792
+ const bw = bi * WORLDS;
9793
+ const tw = ti * WORLDS;
9794
+ const l = bi * LOCALS;
9795
+ const mode = m.modes[bi];
9796
+ let turn = 0;
9797
+ let lx;
9798
+ let ly;
9799
+ if (mode === MODE_NO_ROTATION_OR_REFLECTION) {
9800
+ const k = Math.abs(pa * pd - pb * pc) / Math.max(1e-5, pa * pa + pc * pc);
9801
+ pb = -pc * k;
9802
+ pd = pa * k;
9803
+ turn = Math.atan2(pc, pa) * DEG;
9804
+ const det = pa * pd - pb * pc;
9805
+ const x = W[tw + W_X] - pwx;
9806
+ const y = W[tw + W_Y] - pwy;
9807
+ if (Math.abs(det) <= 1e-5) {
9808
+ lx = 0;
9809
+ ly = 0;
9810
+ } else {
9811
+ lx = (x * pd - y * pb) / det - L[l + L_X];
9812
+ ly = (y * pa - x * pc) / det - L[l + L_Y];
9813
+ }
9814
+ } else if (mode === MODE_ONLY_TRANSLATION) {
9815
+ lx = W[tw + W_X] - W[bw + W_X];
9816
+ ly = W[tw + W_Y] - W[bw + W_Y];
9817
+ } else {
9818
+ const det = pa * pd - pb * pc;
9819
+ const x = W[tw + W_X] - pwx;
9820
+ const y = W[tw + W_Y] - pwy;
9821
+ if (Math.abs(det) <= 1e-5) {
9822
+ lx = 0;
9823
+ ly = 0;
9824
+ } else {
9825
+ lx = (x * pd - y * pb) / det - L[l + L_X];
9826
+ ly = (y * pa - x * pc) / det - L[l + L_Y];
9827
+ }
9828
+ }
9829
+ const rotation = L[l + L_ROTATION];
9830
+ const scaleX = L[l + L_SCALE_X];
9831
+ let r = -L[l + L_SHEAR_X] - rotation + turn + Math.atan2(ly, lx) * DEG;
9832
+ if (scaleX < 0) r += 180;
9833
+ L[l + L_ROTATION] = rotation + wrap180(r) * mix;
9834
+ const len = m.lengths[bi] * scaleX;
9835
+ if ((compress || stretch) && len > 1e-5) {
9836
+ const wx = W[tw + W_X] - W[bw + W_X];
9837
+ const wy = W[tw + W_Y] - W[bw + W_Y];
9838
+ const d = mode === MODE_NO_SCALE || mode === MODE_NO_SCALE_OR_REFLECTION ? Math.sqrt(wx * wx + wy * wy) : Math.sqrt(lx * lx + ly * ly);
9839
+ if (compress && d < len || stretch && d > len) {
9840
+ const s = (d / len - 1) * mix + 1;
9841
+ L[l + L_SCALE_X] = scaleX * s;
9842
+ if (scaleYMode === SCALE_Y_UNIFORM) L[l + L_SCALE_Y] = L[l + L_SCALE_Y] * s;
9843
+ else if (scaleYMode === SCALE_Y_VOLUME) L[l + L_SCALE_Y] = volumeScaleY(L[l + L_SCALE_Y], s);
9844
+ }
9845
+ }
9846
+ }
9847
+ /** A two-bone ik (`solveTwo`): whether it changed the parent. */
9848
+ solveTwo(c, mix, softness, flags) {
9849
+ const m = this.model;
9850
+ const L = this.locals;
9851
+ const W = this.world;
9852
+ const pi = c.bones[0];
9853
+ const ci = c.bones[1];
9854
+ if (m.modes[pi] !== MODE_NORMAL || m.modes[ci] !== MODE_NORMAL) return false;
9855
+ const stretch = (flags & IK_STRETCH) !== 0;
9856
+ const g = m.parents[pi] * WORLDS;
9857
+ const pw = pi * WORLDS;
9858
+ const tw = c.target * WORLDS;
9859
+ const pl = pi * LOCALS;
9860
+ const cl = ci * LOCALS;
9861
+ const ga = W[g + W_A];
9862
+ const gb = W[g + W_B];
9863
+ const gc = W[g + W_C];
9864
+ const gd = W[g + W_D];
9865
+ const gx = W[g + W_X];
9866
+ const gy = W[g + W_Y];
9867
+ const gdet = ga * gd - gb * gc;
9868
+ const gid = Math.abs(gdet) <= 1e-5 ? 0 : 1 / gdet;
9869
+ const px = L[pl + L_X];
9870
+ const py = L[pl + L_Y];
9871
+ const psx = L[pl + L_SCALE_X];
9872
+ const psy = L[pl + L_SCALE_Y];
9873
+ const csx = L[cl + L_SCALE_X];
9874
+ let sx = psx;
9875
+ let sy = psy;
9876
+ const cx = L[cl + L_X];
9877
+ let cy = L[cl + L_Y];
9878
+ const uniform = Math.abs(Math.abs(psx) - Math.abs(psy)) <= 1e-5;
9879
+ if (!uniform || stretch) cy = 0;
9880
+ let x = W[pw + W_A] * cx + W[pw + W_B] * cy + W[pw + W_X] - gx;
9881
+ let y = W[pw + W_C] * cx + W[pw + W_D] * cy + W[pw + W_Y] - gy;
9882
+ const ox = (x * gd - y * gb) * gid;
9883
+ const oy = (y * ga - x * gc) * gid;
9884
+ const dx = ox - px;
9885
+ const dy = oy - py;
9886
+ const l1 = Math.sqrt(dx * dx + dy * dy);
9887
+ let l2 = m.lengths[ci] * Math.abs(csx);
9888
+ if (l1 < 1e-5) {
9889
+ this.solveOne(pi, c.target, mix, false, stretch, 0);
9890
+ L[cl + L_ROTATION] = 0;
9891
+ L[cl + L_Y] = cy;
9892
+ return true;
9893
+ }
9894
+ x = W[tw + W_X] - gx;
9895
+ y = W[tw + W_Y] - gy;
9896
+ let tx = (x * gd - y * gb) * gid;
9897
+ let ty = (y * ga - x * gc) * gid;
9898
+ tx -= px;
9899
+ ty -= py;
9900
+ let d2 = tx * tx + ty * ty;
9901
+ const bend = (flags & IK_BEND_POSITIVE) !== 0 ? 1 : -1;
9902
+ if (softness !== 0) {
9903
+ const soft = softness * (Math.abs(psx) * (Math.abs(csx) + 1) * 0.5);
9904
+ const d = Math.sqrt(d2);
9905
+ const sd = d - l1 - l2 * Math.abs(psx) + soft;
9906
+ if (sd > 0) {
9907
+ const q = Math.min(1, sd / (soft * 2)) - 1;
9908
+ const pull = (sd - soft * (1 - q * q)) / d;
9909
+ tx -= pull * tx;
9910
+ ty -= pull * ty;
9911
+ d2 = tx * tx + ty * ty;
9912
+ }
9913
+ }
9914
+ let a1;
9915
+ let a2;
9916
+ if (uniform) {
9917
+ l2 *= Math.abs(psx);
9918
+ let cos = (d2 - l1 * l1 - l2 * l2) / (2 * l1 * l2);
9919
+ if (cos < -1) {
9920
+ cos = -1;
9921
+ a2 = Math.PI * bend;
9922
+ } else if (cos > 1) {
9923
+ cos = 1;
9924
+ a2 = 0;
9925
+ if (stretch) {
9926
+ const f = (Math.sqrt(d2) / (l1 + l2) - 1) * mix + 1;
9927
+ sx *= f;
9928
+ if (c.scaleY === SCALE_Y_UNIFORM) sy *= f;
9929
+ else if (c.scaleY === SCALE_Y_VOLUME) sy = volumeScaleY(sy, f);
9930
+ }
9931
+ } else a2 = Math.acos(cos) * bend;
9932
+ const along = l1 + l2 * cos;
9933
+ const across = l2 * Math.sin(a2);
9934
+ a1 = Math.atan2(ty * along - tx * across, tx * along + ty * across);
9935
+ } else {
9936
+ const ea = Math.abs(psx) * l2;
9937
+ const eb = Math.abs(psy) * l2;
9938
+ const aa = ea * ea;
9939
+ const bb = eb * eb;
9940
+ const qa = bb - aa;
9941
+ const qb = -2 * bb * l1;
9942
+ const qc = bb * l1 * l1 + aa * d2 - aa * bb;
9943
+ const disc = qb * qb - 4 * qa * qc;
9944
+ let solved = false;
9945
+ a1 = 0;
9946
+ a2 = 0;
9947
+ if (disc >= 0) {
9948
+ let q = Math.sqrt(disc);
9949
+ if (qb < 0) q = -q;
9950
+ q = -(qb + q) * 0.5;
9951
+ const r0 = q / qa;
9952
+ const r1 = qc / q;
9953
+ const X = Math.abs(r0) < Math.abs(r1) ? r0 : r1;
9954
+ const y2 = d2 - X * X;
9955
+ if (y2 >= 0) {
9956
+ const Y = Math.sqrt(y2) * bend;
9957
+ a1 = Math.atan2(ty, tx) - Math.atan2(Y, X);
9958
+ a2 = Math.atan2(Y / Math.abs(psy), (X - l1) / Math.abs(psx));
9959
+ solved = true;
9960
+ }
9961
+ }
9962
+ if (!solved) {
9963
+ let nearAngle = RUNTIME_PI;
9964
+ let nearX = l1 - ea;
9965
+ let nearY = 0;
9966
+ let nearDist = (l1 - ea) * (l1 - ea);
9967
+ let farAngle = 0;
9968
+ let farX = l1 + ea;
9969
+ let farY = 0;
9970
+ let farDist = (l1 + ea) * (l1 + ea);
9971
+ const vertex = -ea * l1 / (aa - bb);
9972
+ if (vertex >= -1 && vertex <= 1) {
9973
+ const angle = Math.acos(vertex);
9974
+ const vx = ea * Math.cos(angle) + l1;
9975
+ const vy = eb * Math.sin(angle);
9976
+ const dist = vx * vx + vy * vy;
9977
+ if (dist < nearDist) {
9978
+ nearAngle = angle;
9979
+ nearX = vx;
9980
+ nearY = vy;
9981
+ nearDist = dist;
9982
+ }
9983
+ if (dist > farDist) {
9984
+ farAngle = angle;
9985
+ farX = vx;
9986
+ farY = vy;
9987
+ farDist = dist;
9988
+ }
9989
+ }
9990
+ if (d2 <= (nearDist + farDist) * 0.5) {
9991
+ a1 = Math.atan2(ty, tx) - Math.atan2(nearY * bend, nearX);
9992
+ a2 = nearAngle * bend;
9993
+ } else {
9994
+ a1 = Math.atan2(ty, tx) - Math.atan2(farY * bend, farX);
9995
+ a2 = farAngle * bend;
9996
+ }
9997
+ }
9998
+ }
9999
+ const signs = (psx < 0 ? -1 : 1) * (psy < 0 ? -1 : 1);
10000
+ const offset = Math.atan2(cy, cx);
10001
+ const parentRotation = L[pl + L_ROTATION];
10002
+ const parentChange = (a1 - signs * offset) * DEG + (psx < 0 ? 180 : 0) - parentRotation;
10003
+ L[pl + L_ROTATION] = parentRotation + wrap180(parentChange) * mix;
10004
+ L[pl + L_SCALE_X] = sx;
10005
+ L[pl + L_SCALE_Y] = sy;
10006
+ const childTarget = ((a2 + signs * offset) * DEG - L[cl + L_SHEAR_X]) * signs + (csx < 0 ? 180 : 0);
10007
+ const childRotation = L[cl + L_ROTATION];
10008
+ L[cl + L_Y] = cy;
10009
+ L[cl + L_ROTATION] = childRotation + wrap180(childTarget - childRotation) * mix;
10010
+ return true;
10011
+ }
10012
+ /** A world-space, absolute transform (`solveTransform`): each bone's world edited in turn and written back, then the repose. */
10013
+ solveTransform(c, k) {
10014
+ const W = this.world;
10015
+ const w = this.w;
10016
+ const mixes = this.mixes;
10017
+ const o6 = k * 6;
10018
+ let n = 0;
10019
+ for (let bk = 0; bk < c.bones.length; bk++) {
10020
+ const bi = c.bones[bk];
10021
+ const o = bi * WORLDS;
10022
+ w[W_A] = W[o + W_A];
10023
+ w[W_B] = W[o + W_B];
10024
+ w[W_C] = W[o + W_C];
10025
+ w[W_D] = W[o + W_D];
10026
+ w[W_X] = W[o + W_X];
10027
+ w[W_Y] = W[o + W_Y];
10028
+ let applied = false;
10029
+ for (let f = 0; f < c.fromProperty.length; f++) {
10030
+ const value = this.sourceValue(c, c.fromProperty[f]) - c.fromOffset[f];
10031
+ for (let q = c.toStart[f]; q < c.toEnd[f]; q++) {
10032
+ const property = c.toProperty[q];
10033
+ const mix = mixes[o6 + property];
10034
+ if (mix === 0) continue;
10035
+ let v = value * c.toScale[q] + c.toOffset[q];
10036
+ if (c.clamp) {
10037
+ const lo = Math.min(c.toOffset[q], c.toMax[q]);
10038
+ const hi = Math.max(c.toOffset[q], c.toMax[q]);
10039
+ v = v < lo ? lo : v > hi ? hi : v;
10040
+ }
10041
+ applied = true;
10042
+ applyWorld(w, property, v, mix);
10043
+ }
10044
+ }
10045
+ if (!applied) continue;
10046
+ W[o + W_A] = w[W_A];
10047
+ W[o + W_B] = w[W_B];
10048
+ W[o + W_C] = w[W_C];
10049
+ W[o + W_D] = w[W_D];
10050
+ W[o + W_X] = w[W_X];
10051
+ W[o + W_Y] = w[W_Y];
10052
+ this.changed[n] = bi;
10053
+ this.inWorld[n] = bi;
10054
+ n++;
10055
+ }
10056
+ if (n > 0) this.repose(n, n);
10057
+ }
10058
+ /** The source's world value of a property (`sourceValue`, world source). */
10059
+ sourceValue(c, property) {
10060
+ const W = this.world;
10061
+ const s = c.source * WORLDS;
10062
+ const o = c.offsets;
10063
+ const a = W[s + W_A];
10064
+ const b = W[s + W_B];
10065
+ const cc = W[s + W_C];
10066
+ const d = W[s + W_D];
10067
+ switch (property) {
10068
+ case P_ROTATE: {
10069
+ const det = a * d - b * cc;
10070
+ let r = Math.atan2(cc, a) * DEG + (det > 0 ? o[P_ROTATE] : -o[P_ROTATE]);
10071
+ if (r < 0) r += 360;
10072
+ return r;
10073
+ }
10074
+ case P_X:
10075
+ return a * o[P_X] + b * o[P_Y] + W[s + W_X];
10076
+ case P_Y:
10077
+ return cc * o[P_X] + d * o[P_Y] + W[s + W_Y];
10078
+ case P_SCALE_X:
10079
+ return Math.sqrt(a * a + cc * cc) + o[P_SCALE_X];
10080
+ case P_SCALE_Y:
10081
+ return Math.sqrt(b * b + d * d) + o[P_SCALE_Y];
10082
+ default:
10083
+ return (Math.atan2(d, b) - Math.atan2(cc, a)) * DEG - 90 + o[5];
10084
+ }
10085
+ }
10086
+ /**
10087
+ * After a constraint (`repose`): the `nInWorld` bones it moved in world space
10088
+ * (the first of `inWorld`) read back into local values, then every bone below
10089
+ * one of the `nChanged` it changed (`changed`) posed again, in bone order —
10090
+ * but not through an inactive bone it did not move itself, and not a bone it
10091
+ * moved in world space.
10092
+ */
10093
+ repose(nChanged, nInWorld) {
10094
+ const m = this.model;
10095
+ const changed = this.changed;
10096
+ const changedMark = this.changedMark;
10097
+ const inWorldMark = this.inWorldMark;
10098
+ const below = this.below;
10099
+ for (let k = 0; k < nInWorld; k++) {
10100
+ this.localFromWorld(this.inWorld[k]);
10101
+ inWorldMark[this.inWorld[k]] = 1;
10102
+ }
10103
+ for (let k = 0; k < nChanged; k++) {
10104
+ changedMark[changed[k]] = 1;
10105
+ below[changed[k]] = 1;
10106
+ }
10107
+ const starts = m.subtreeStart;
10108
+ const list = m.subtrees;
10109
+ const parents = m.parents;
10110
+ const active = m.active;
10111
+ if (nChanged === 1) {
10112
+ const e = starts[changed[0] + 1];
10113
+ for (let q = starts[changed[0]]; q < e; q++) this.reposeOne(list[q], parents, active);
10114
+ for (let q = starts[changed[0]]; q < e; q++) below[list[q]] = 0;
10115
+ } else {
10116
+ const visit = this.visit;
10117
+ let first = parents.length;
10118
+ for (let k = 0; k < nChanged; k++) {
10119
+ const b = changed[k];
10120
+ if (b < first) first = b;
10121
+ const e = starts[b + 1];
10122
+ for (let q = starts[b]; q < e; q++) visit[list[q]] = 1;
10123
+ }
10124
+ for (let b = first; b < parents.length; b++) if (visit[b] === 1) this.reposeOne(b, parents, active);
10125
+ for (let b = first; b < parents.length; b++) {
10126
+ visit[b] = 0;
10127
+ below[b] = 0;
10128
+ }
10129
+ }
10130
+ for (let k = 0; k < nChanged; k++) changedMark[changed[k]] = 0;
10131
+ for (let k = 0; k < nInWorld; k++) inWorldMark[this.inWorld[k]] = 0;
10132
+ }
10133
+ reposeOne(b, parents, active) {
10134
+ const below = this.below;
10135
+ const p = parents[b];
10136
+ if (p >= 0 && below[p] === 1 && (active[p] === 1 || this.changedMark[p] === 1)) below[b] = 1;
10137
+ if (below[b] === 1 && this.inWorldMark[b] === 0) this.poseBone(b, false);
10138
+ }
10139
+ /** A bone's local values read back from its world transform (`localFromWorld`) — `normal`, `onlyTranslation` and `noRotationOrReflection`; a world target in a `noScale` mode is refused at load. */
10140
+ localFromWorld(bi) {
10141
+ const m = this.model;
10142
+ const W = this.world;
10143
+ const L = this.locals;
10144
+ const w = bi * WORLDS;
10145
+ const l = bi * LOCALS;
10146
+ const parent = m.parents[bi];
10147
+ const mode = parent < 0 ? MODE_NORMAL : m.modes[bi];
10148
+ let pa = 1;
10149
+ let pb = 0;
10150
+ let pc = 0;
10151
+ let pd = 1;
10152
+ let px = 0;
10153
+ let py = 0;
10154
+ if (parent >= 0) {
10155
+ const q = parent * WORLDS;
10156
+ pa = W[q + W_A];
10157
+ pb = W[q + W_B];
10158
+ pc = W[q + W_C];
10159
+ pd = W[q + W_D];
10160
+ px = W[q + W_X];
10161
+ py = W[q + W_Y];
10162
+ }
10163
+ const pid = 1 / (pa * pd - pb * pc);
10164
+ const dx = W[w + W_X] - px;
10165
+ const dy = W[w + W_Y] - py;
10166
+ L[l + L_X] = dx * (pd * pid) - dy * (pb * pid);
10167
+ L[l + L_Y] = dy * (pa * pid) - dx * (pc * pid);
10168
+ L[l + L_SHEAR_X] = 0;
10169
+ const wa = W[w + W_A];
10170
+ const wb = W[w + W_B];
10171
+ const wc = W[w + W_C];
10172
+ const wd = W[w + W_D];
10173
+ if (mode === MODE_ONLY_TRANSLATION) {
10174
+ this.decompose(l, wa, wc, wb, wd);
10175
+ return;
10176
+ }
10177
+ if (mode === MODE_NO_ROTATION_OR_REFLECTION) {
10178
+ const xLengthInverse = 1 / (pa * pa + pc * pc);
10179
+ const detInverse = 1 / Math.abs(pa * pd - pb * pc);
10180
+ this.decompose(l, (pa * wa + pc * wc) * xLengthInverse, (pa * wc - pc * wa) * detInverse, (pa * wb + pc * wd) * xLengthInverse, (pa * wd - pc * wb) * detInverse);
10181
+ L[l + L_ROTATION] = L[l + L_ROTATION] + Math.atan2(pc, pa) * DEG;
10182
+ return;
10183
+ }
10184
+ const ia = pd * pid;
10185
+ const ib = pb * pid;
10186
+ const ic = pc * pid;
10187
+ const id = pa * pid;
10188
+ this.decompose(l, ia * wa - ib * wc, id * wc - ic * wa, ia * wb - ib * wd, id * wd - ic * wb);
10189
+ }
10190
+ /** A local matrix with columns `(la, lc)` and `(lb, ld)` as rotation, scales and shear (`decompose`). */
10191
+ decompose(l, la, lc, lb, ld) {
10192
+ const L = this.locals;
10193
+ const sx = Math.sqrt(la * la + lc * lc);
10194
+ const yLength = Math.sqrt(lb * lb + ld * ld);
10195
+ if (!(sx > 1e-4)) {
10196
+ L[l + L_SCALE_X] = 0;
10197
+ L[l + L_SCALE_Y] = yLength;
10198
+ L[l + L_SHEAR_Y] = 0;
10199
+ L[l + L_ROTATION] = yLength > 1e-5 ? Math.atan2(ld, lb) * DEG - 90 : 0;
10200
+ return;
10201
+ }
10202
+ const xAngle = Math.atan2(lc, la) * DEG;
10203
+ const det = la * ld - lb * lc;
10204
+ const yAngle = Math.atan2(ld, lb) * DEG;
10205
+ L[l + L_ROTATION] = xAngle;
10206
+ L[l + L_SCALE_X] = sx;
10207
+ L[l + L_SCALE_Y] = det < 0 ? -yLength : yLength;
10208
+ const right = det < 0 ? -90 : 90;
10209
+ let shear = yAngle - (xAngle + right);
10210
+ shear = shear > 180 ? shear - 360 : shear < -180 ? shear + 360 : shear;
10211
+ L[l + L_SHEAR_Y] = yLength > 1e-5 ? shear : 0;
10212
+ }
10213
+ // ---------------------------------------------------------------------------
10214
+ // the slots and the vertices
10215
+ // ---------------------------------------------------------------------------
10216
+ /** Every slot's colour and what it shows (`posedSlots`): the setup's, then each of its timelines at `t` in order. */
10217
+ slotValues(anim, t) {
10218
+ const slots = this.model.slots;
10219
+ const light = this.light;
10220
+ for (let s = 0; s < slots.length; s++) {
10221
+ const slot = slots[s];
10222
+ this.shown[s] = slot.setupAttachment;
10223
+ const o = s * 4;
10224
+ light[o] = slot.light[0];
10225
+ light[o + 1] = slot.light[1];
10226
+ light[o + 2] = slot.light[2];
10227
+ light[o + 3] = slot.light[3];
10228
+ }
10229
+ if (anim === null) return;
10230
+ const timelines = anim.slots;
10231
+ for (let k = 0; k < timelines.length; k++) {
10232
+ const tl = timelines[k];
10233
+ const i = keyAt(tl.curve, this.cursors, tl.cursor, t);
10234
+ const attachments = tl.attachments;
10235
+ if (attachments !== null) {
10236
+ this.shown[tl.slot] = i < 0 ? slots[tl.slot].setupAttachment : attachments[i];
10237
+ continue;
10238
+ }
10239
+ const channels = tl.channels;
10240
+ const setup = slots[tl.slot].light;
10241
+ const o = tl.slot * 4;
10242
+ for (let c = 0; c < channels.length; c++) {
10243
+ const at = channels[c];
10244
+ light[o + at] = i < 0 ? setup[at] : clamp01(channelValue(tl.curve, i, c, t));
10245
+ }
10246
+ }
10247
+ }
10248
+ /** Every shown attachment's world vertices into `vertices` at its offset (`regionCorners`, `worldVertices`). */
10249
+ computeVertices() {
10250
+ const m = this.model;
10251
+ const W = this.world;
10252
+ const out = this.vertices;
10253
+ const shown = this.shown;
10254
+ const attachments = m.attachments;
10255
+ for (let s = 0; s < shown.length; s++) {
10256
+ const index = shown[s];
10257
+ if (index < 0) continue;
10258
+ const at = attachments[index];
10259
+ if (at.draw === DRAW_NONE) continue;
10260
+ let o = at.offset;
10261
+ if (at.draw === DRAW_REGION || at.draw === DRAW_MESH) {
10262
+ const b = at.bone * WORLDS;
10263
+ const a = W[b + W_A];
10264
+ const bb = W[b + W_B];
10265
+ const c = W[b + W_C];
10266
+ const d = W[b + W_D];
10267
+ const wx = W[b + W_X];
10268
+ const wy = W[b + W_Y];
10269
+ const local = at.local;
10270
+ if (at.draw === DRAW_REGION) {
10271
+ for (let q = 0; q < 8; q += 2) {
10272
+ const lx = local[q];
10273
+ const ly = local[q + 1];
10274
+ out[o++] = a * lx + bb * ly + wx;
10275
+ out[o++] = c * lx + d * ly + wy;
10276
+ }
10277
+ } else {
10278
+ for (let q = 0; q < at.count; q += 2) {
10279
+ const x = local[q];
10280
+ const y = local[q + 1];
10281
+ out[o++] = x * a + y * bb + wx;
10282
+ out[o++] = x * c + y * d + wy;
10283
+ }
10284
+ }
10285
+ continue;
10286
+ }
10287
+ const counts = at.bindingCounts;
10288
+ const bones = at.bindingBones;
10289
+ const xs = at.bindingX;
10290
+ const ys = at.bindingY;
10291
+ const weights = at.bindingWeights;
10292
+ let n = 0;
10293
+ for (let v = 0; v < counts.length; v++) {
10294
+ let wx = 0;
10295
+ let wy = 0;
10296
+ const end = n + counts[v];
10297
+ for (; n < end; n++) {
10298
+ const b = bones[n] * WORLDS;
10299
+ const x = xs[n];
10300
+ const y = ys[n];
10301
+ const weight = weights[n];
10302
+ wx += (x * W[b + W_A] + y * W[b + W_B] + W[b + W_X]) * weight;
10303
+ wy += (x * W[b + W_C] + y * W[b + W_D] + W[b + W_Y]) * weight;
10304
+ }
10305
+ out[o++] = wx;
10306
+ out[o++] = wy;
10307
+ }
10308
+ }
10309
+ }
10310
+ }
10311
+ function applyWorld(w, property, v, mix) {
10312
+ switch (property) {
10313
+ case P_ROTATE: {
10314
+ let r = v * RAD - Math.atan2(w[W_C], w[W_A]);
10315
+ if (r > RUNTIME_PI) r -= TWO_PI;
10316
+ else if (r < -RUNTIME_PI) r += TWO_PI;
10317
+ r *= mix;
10318
+ const cos = Math.cos(r);
10319
+ const sin = Math.sin(r);
10320
+ const a = w[W_A];
10321
+ const b = w[W_B];
10322
+ w[W_A] = cos * a - sin * w[W_C];
10323
+ w[W_B] = cos * b - sin * w[W_D];
10324
+ w[W_C] = sin * a + cos * w[W_C];
10325
+ w[W_D] = sin * b + cos * w[W_D];
10326
+ return;
10327
+ }
10328
+ case P_X:
10329
+ w[W_X] = w[W_X] + (v - w[W_X]) * mix;
10330
+ return;
10331
+ case P_Y:
10332
+ w[W_Y] = w[W_Y] + (v - w[W_Y]) * mix;
10333
+ return;
10334
+ case P_SCALE_X:
10335
+ case P_SCALE_Y: {
10336
+ const p = property === P_SCALE_X ? W_A : W_B;
10337
+ const q = property === P_SCALE_X ? W_C : W_D;
10338
+ const s = Math.sqrt(w[p] * w[p] + w[q] * w[q]);
10339
+ if (s === 0) return;
10340
+ const k = 1 + (v - s) * mix / s;
10341
+ w[p] *= k;
10342
+ w[q] *= k;
10343
+ return;
10344
+ }
10345
+ default: {
10346
+ const yAngle = Math.atan2(w[W_D], w[W_B]);
10347
+ const xAngle = Math.atan2(w[W_C], w[W_A]);
10348
+ let r = (v + 90) * RAD - (yAngle - xAngle);
10349
+ r = r > RUNTIME_PI ? r - TWO_PI : r < -RUNTIME_PI ? r + TWO_PI : r;
10350
+ const angle = yAngle + r * mix;
10351
+ const s = Math.sqrt(w[W_B] * w[W_B] + w[W_D] * w[W_D]);
10352
+ w[W_B] = Math.cos(angle) * s;
10353
+ w[W_D] = Math.sin(angle) * s;
10354
+ }
10355
+ }
10356
+ }
8826
10357
  function frameSource(doc, parsed) {
8827
10358
  if (doc.pages === null) {
8828
10359
  throw new CoreInputError(
@@ -8830,12 +10361,20 @@ function frameSource(doc, parsed) {
8830
10361
  );
8831
10362
  }
8832
10363
  const pages = new Map(doc.pages.map((page) => [page.name, page]));
10364
+ const boneOf = /* @__PURE__ */ new Map();
10365
+ const slotIndex = /* @__PURE__ */ new Map();
10366
+ for (let i = 0; i < doc.slots.length; i++) {
10367
+ const slot = doc.slots[i];
10368
+ boneOf.set(slot.name, slot.bone);
10369
+ slotIndex.set(slot.name, i);
10370
+ }
8833
10371
  return {
8834
10372
  doc,
8835
10373
  uv: { lookup: documentPageLookup(doc.pages), sequences: readUvSequences(parsed) },
8836
10374
  pages,
8837
10375
  active: activeBones(doc),
8838
- boneOf: new Map(doc.slots.map((slot) => [slot.name, slot.bone])),
10376
+ boneOf,
10377
+ slotIndex,
8839
10378
  uvs: /* @__PURE__ */ new WeakMap(),
8840
10379
  regionUvs: /* @__PURE__ */ new WeakMap(),
8841
10380
  triangles: /* @__PURE__ */ new Map()
@@ -8868,24 +10407,57 @@ function regionUvs(source, found) {
8868
10407
  return kept;
8869
10408
  }
8870
10409
  function trianglesOf(source, drawn) {
8871
- const key = `${drawn.slot}\0${drawn.attachment}`;
8872
- let lists = source.triangles.get(key);
8873
- if (lists === void 0) {
8874
- lists = [];
8875
- source.triangles.set(key, lists);
10410
+ let byAttachment = source.triangles.get(drawn.slot);
10411
+ if (byAttachment === void 0) {
10412
+ byAttachment = /* @__PURE__ */ new Map();
10413
+ source.triangles.set(drawn.slot, byAttachment);
10414
+ }
10415
+ let cache = byAttachment.get(drawn.attachment);
10416
+ if (cache === void 0) {
10417
+ cache = { byRef: /* @__PURE__ */ new WeakMap(), lists: [] };
10418
+ byAttachment.set(drawn.attachment, cache);
8876
10419
  }
8877
10420
  const wanted = drawn.triangles;
8878
- let kept = lists.find((list) => list.length === wanted.length && list.every((v, i) => v === wanted[i]));
10421
+ const fast = cache.byRef.get(wanted);
10422
+ if (fast !== void 0) return fast;
10423
+ const frozen = Object.isFrozen(wanted);
10424
+ let kept = cache.lists.find((list) => list.length === wanted.length && list.every((v, i) => v === wanted[i]));
8879
10425
  if (kept === void 0) {
8880
- kept = [...wanted];
8881
- lists.push(kept);
10426
+ kept = frozen ? wanted : Object.freeze([...wanted]);
10427
+ cache.lists.push(kept);
10428
+ cache.byRef.set(kept, kept);
8882
10429
  }
10430
+ if (frozen) cache.byRef.set(wanted, kept);
8883
10431
  return kept;
8884
10432
  }
10433
+ function regionMap(regions) {
10434
+ const map = /* @__PURE__ */ new Map();
10435
+ for (let i = 0; i < regions.length; i++) map.set(regions[i].slot, regions[i]);
10436
+ return map;
10437
+ }
10438
+ function slotRowMap(slots) {
10439
+ const map = /* @__PURE__ */ new Map();
10440
+ for (let i = 0; i < slots.length; i++) map.set(slots[i][0], slots[i]);
10441
+ return map;
10442
+ }
10443
+ function drawOrderMap(drawOrder) {
10444
+ const map = /* @__PURE__ */ new Map();
10445
+ for (let i = 0; i < drawOrder.length; i++) map.set(drawOrder[i], i);
10446
+ return map;
10447
+ }
8885
10448
  function clipCover(pose, active, draws = () => true) {
8886
- const polygons = new Map(pose.clips.map((c) => [c[0], { end: c[2], polygon: c[3] }]));
8887
- const shownOf = new Map(pose.shown.map((s) => [s.slot, s]));
8888
10449
  const cover = /* @__PURE__ */ new Map();
10450
+ if (pose.clips.length === 0) return cover;
10451
+ const polygons = /* @__PURE__ */ new Map();
10452
+ for (let i = 0; i < pose.clips.length; i++) {
10453
+ const c = pose.clips[i];
10454
+ polygons.set(c[0], { end: c[2], polygon: c[3] });
10455
+ }
10456
+ const shownOf = /* @__PURE__ */ new Map();
10457
+ for (let i = 0; i < pose.shown.length; i++) {
10458
+ const s = pose.shown[i];
10459
+ shownOf.set(s.slot, s);
10460
+ }
8889
10461
  let current = null;
8890
10462
  for (const slot of pose.drawOrder) {
8891
10463
  const shown = shownOf.get(slot);
@@ -8906,20 +10478,25 @@ function clipCover(pose, active, draws = () => true) {
8906
10478
  function frameOf(source, pose, color = [1, 1, 1, 1], draws = () => true) {
8907
10479
  const { drawn: regions, why } = drawnRegions(source.doc, pose.shown, pose.drawOrder, source.uv);
8908
10480
  if (regions === null) throw new CoreInputError(`the page UVs are not posed: ${why}`);
8909
- const regionOf = new Map(regions.map((r) => [r.slot, r]));
8910
- const rows = new Map(pose.slots.map((row) => [row[0], row]));
8911
- const z = new Map(pose.drawOrder.map((slot, index) => [slot, index]));
8912
- const cover = clipCover(pose, source.active, draws);
10481
+ let regionOf = null;
10482
+ let rows = null;
10483
+ let z = null;
10484
+ const cover = pose.clips.length === 0 ? null : clipCover(pose, source.active, draws);
8913
10485
  const [sr, sg, sb, sa] = color;
8914
10486
  const order = [];
8915
- for (const d of pose.drawn) {
10487
+ for (let i = 0; i < pose.drawn.length; i++) {
10488
+ const d = pose.drawn[i];
8916
10489
  if (!draws(d.slot)) continue;
8917
10490
  const bone2 = source.boneOf.get(d.slot);
8918
10491
  if (bone2 === void 0 || !source.active.has(bone2)) continue;
8919
- const found = regionOf.get(d.slot);
10492
+ const directRegion = regions[i];
10493
+ const found = directRegion !== void 0 && directRegion.slot === d.slot ? directRegion : (regionOf ??= regionMap(regions)).get(d.slot);
8920
10494
  if (found === void 0) throw new CoreInputError(`slot "${d.slot}" drew "${d.attachment}", and no atlas region was resolved for it`);
8921
- const row = rows.get(d.slot);
10495
+ const slotIdx = source.slotIndex.get(d.slot);
10496
+ const directRow = slotIdx !== void 0 ? pose.slots[slotIdx] : void 0;
10497
+ const row = directRow !== void 0 && directRow[0] === d.slot ? directRow : (rows ??= slotRowMap(pose.slots)).get(d.slot);
8922
10498
  if (row === void 0) throw new CoreInputError(`slot "${d.slot}" drew and has no slot row`);
10499
+ const slotZ = slotIdx !== void 0 && pose.drawOrder[slotIdx] === d.slot ? slotIdx : (z ??= drawOrderMap(pose.drawOrder)).get(d.slot);
8923
10500
  const tint = [
8924
10501
  channel(row[2], d.slot) * d.colour[0] * sr,
8925
10502
  channel(row[3], d.slot) * d.colour[1] * sg,
@@ -8927,13 +10504,14 @@ function frameOf(source, pose, color = [1, 1, 1, 1], draws = () => true) {
8927
10504
  channel(row[5], d.slot) * d.colour[3] * sa
8928
10505
  ];
8929
10506
  const darkRow = row[6];
10507
+ const clip = cover === null ? void 0 : cover.get(d.slot);
8930
10508
  const common = {
8931
10509
  slot: d.slot,
8932
- z: z.get(d.slot),
10510
+ z: slotZ,
8933
10511
  blend: row[8] ?? "Normal",
8934
10512
  tint,
8935
10513
  ...darkRow === null ? {} : { dark: [channel(darkRow[0], d.slot), channel(darkRow[1], d.slot), channel(darkRow[2], d.slot)] },
8936
- ...cover.has(d.slot) ? { clip: cover.get(d.slot) } : {}
10514
+ ...clip === void 0 ? {} : { clip }
8937
10515
  };
8938
10516
  const page = source.pages.get(found.found.page.name);
8939
10517
  if (d.kind === "region") {
@@ -8961,6 +10539,238 @@ function frameOf(source, pose, color = [1, 1, 1, 1], draws = () => true) {
8961
10539
  }
8962
10540
  return { order };
8963
10541
  }
10542
+ function numbers(buffer, offset, count) {
10543
+ const result = new Array(count);
10544
+ for (let i = 0; i < count; i++) Object.defineProperty(result, i, {
10545
+ enumerable: true,
10546
+ get: () => buffer[offset + i]
10547
+ });
10548
+ return Object.freeze(result);
10549
+ }
10550
+ function checkedLiveFrame(frame2, current) {
10551
+ const generation = current();
10552
+ const frameGeneration = frame2.generation;
10553
+ const proxies = /* @__PURE__ */ new WeakMap();
10554
+ const check = () => {
10555
+ if (current() !== generation) throw new Error(`LIVE_FRAME_STALE: borrowed generation ${frameGeneration} was invalidated; call liveFrame() again`);
10556
+ };
10557
+ const wrap = (value) => {
10558
+ const found = proxies.get(value);
10559
+ if (found) return found;
10560
+ const proxy = new Proxy(Array.isArray(value) ? [] : {}, {
10561
+ get(_target, property) {
10562
+ check();
10563
+ if (property === "buffer" || property === "set" || property === "fill" || property === "copyWithin" || property === "reverse" || property === "sort") {
10564
+ throw new Error(`LIVE_FRAME_READONLY: ${String(property)} does not expose the live backing store`);
10565
+ }
10566
+ const item = Reflect.get(value, property, value);
10567
+ if (typeof item === "function") {
10568
+ if (ArrayBuffer.isView(value) && property !== Symbol.iterator) throw new Error(`LIVE_FRAME_READONLY: ${String(property)} is not part of the indexed live buffer interface`);
10569
+ return (...args) => {
10570
+ check();
10571
+ const returned = Reflect.apply(item, Array.isArray(value) ? proxy : value, args);
10572
+ return returned !== null && typeof returned === "object" ? wrap(returned) : returned;
10573
+ };
10574
+ }
10575
+ return item !== null && typeof item === "object" ? wrap(item) : item;
10576
+ },
10577
+ set() {
10578
+ check();
10579
+ throw new Error("LIVE_FRAME_READONLY: live frame values cannot be assigned");
10580
+ },
10581
+ defineProperty() {
10582
+ check();
10583
+ throw new Error("LIVE_FRAME_READONLY: live frame values cannot be redefined");
10584
+ },
10585
+ deleteProperty() {
10586
+ check();
10587
+ throw new Error("LIVE_FRAME_READONLY: live frame values cannot be deleted");
10588
+ },
10589
+ preventExtensions() {
10590
+ check();
10591
+ throw new Error("LIVE_FRAME_READONLY: live frame objects cannot be reconfigured");
10592
+ },
10593
+ setPrototypeOf() {
10594
+ check();
10595
+ throw new Error("LIVE_FRAME_READONLY: live frame prototypes cannot be changed");
10596
+ },
10597
+ has(_target, property) {
10598
+ check();
10599
+ return Reflect.has(value, property);
10600
+ },
10601
+ ownKeys() {
10602
+ check();
10603
+ return Reflect.ownKeys(value);
10604
+ },
10605
+ getOwnPropertyDescriptor(_target, property) {
10606
+ check();
10607
+ if (property === "length" && Array.isArray(value)) return { value: value.length, writable: true, configurable: false, enumerable: false };
10608
+ const descriptor = Reflect.getOwnPropertyDescriptor(value, property);
10609
+ return descriptor ? { configurable: true, enumerable: descriptor.enumerable, get: () => Reflect.get(proxy, property) } : void 0;
10610
+ }
10611
+ });
10612
+ proxies.set(value, proxy);
10613
+ return proxy;
10614
+ };
10615
+ const lease = wrap(frame2);
10616
+ const drawing = liveDrawings.get(frame2);
10617
+ if (drawing) liveDrawings.set(lease, drawing);
10618
+ return lease;
10619
+ }
10620
+ class RuntimeFrames {
10621
+ model;
10622
+ pose;
10623
+ renderFrame = { order: [] };
10624
+ live;
10625
+ order;
10626
+ tint;
10627
+ templates;
10628
+ colours;
10629
+ /** Incremented when a draw refresh changes colour without a pose step. */
10630
+ revision = 0;
10631
+ previousColor = new Float64Array(4).fill(Number.NaN);
10632
+ drawn = 0;
10633
+ epoch = 0;
10634
+ constructor(source, model) {
10635
+ this.model = model ?? compileRuntime(source.doc);
10636
+ this.pose = new RuntimePose(this.model);
10637
+ this.order = new Int32Array(this.model.slots.length);
10638
+ this.tint = new Float64Array(this.model.slots.length * 4);
10639
+ const colours = [];
10640
+ const metadata = [];
10641
+ this.templates = this.model.attachments.map((attachment, index) => {
10642
+ const identity = attachment;
10643
+ if (identity.skin === void 0 || identity.placeholder === void 0) {
10644
+ throw new Error(`RUNTIME_DRAW_IDENTITY: attachment ${index} on slot "${this.model.slots[attachment.slot].name}" needs resolved skin and placeholder from rig-c/runtime`);
10645
+ }
10646
+ const slot = this.model.slots[attachment.slot];
10647
+ const doc = { ...source.doc, slots: source.doc.slots.map((value, si) => ({ ...value, setup: si === attachment.slot ? identity.placeholder : null })) };
10648
+ const setup = poseRawSetup(doc);
10649
+ const raw = setup.drawn.find((value) => value.slot === slot.name);
10650
+ const template = frameOf(source, setup).order.find((value) => value.slot === slot.name) ?? null;
10651
+ const colour = raw?.colour ?? [1, 1, 1, 1];
10652
+ colours.push(colour);
10653
+ if (template) {
10654
+ const vertices = numbers(this.pose.vertices, attachment.offset, attachment.count);
10655
+ if (template.kind === "region") template.corners = vertices;
10656
+ else template.vertices = vertices;
10657
+ template.tint = numbers(this.tint, attachment.slot * 4, 4);
10658
+ }
10659
+ metadata.push(Object.freeze({
10660
+ slot: slot.name,
10661
+ slotIndex: attachment.slot,
10662
+ attachment: attachment.name,
10663
+ skin: identity.skin,
10664
+ placeholder: identity.placeholder,
10665
+ kind: template?.kind ?? "none",
10666
+ region: template?.kind === "region" ? template.region : null,
10667
+ page: template?.page ?? null,
10668
+ pma: template?.pma ?? false,
10669
+ blend: slot.blend,
10670
+ offset: attachment.offset,
10671
+ count: attachment.count,
10672
+ uvs: Object.freeze(template?.uvs ?? []),
10673
+ triangles: Object.freeze(template?.kind === "mesh" ? template.triangles : []),
10674
+ dark: template?.dark ? Object.freeze(template.dark) : null,
10675
+ clip: null
10676
+ }));
10677
+ return template;
10678
+ });
10679
+ this.colours = colours;
10680
+ const owner = this;
10681
+ this.live = Object.freeze({
10682
+ get generation() {
10683
+ return owner.epoch;
10684
+ },
10685
+ get count() {
10686
+ return owner.drawn;
10687
+ },
10688
+ drawOrder: this.order,
10689
+ vertices: this.pose.vertices,
10690
+ tint: this.tint,
10691
+ attachments: Object.freeze(metadata)
10692
+ });
10693
+ liveDrawings.set(this.live, this.renderFrame);
10694
+ }
10695
+ update(generation, color, draws, poseChanged = true) {
10696
+ if (poseChanged) this.pose.computeVertices();
10697
+ let colorChanged = false;
10698
+ for (let c = 0; c < 4; c++) {
10699
+ if (!Object.is(this.previousColor[c], color[c])) colorChanged = true;
10700
+ this.previousColor[c] = color[c];
10701
+ }
10702
+ if (colorChanged) this.revision++;
10703
+ let count = 0;
10704
+ for (let si = 0; si < this.pose.shown.length; si++) {
10705
+ const ai = this.pose.shown[si];
10706
+ const template = ai < 0 ? null : this.templates[ai];
10707
+ if (!template || !this.model.active[this.model.slots[si].bone]) continue;
10708
+ const offset = si * 4;
10709
+ const colour = this.colours[ai];
10710
+ for (let c = 0; c < 4; c++) this.tint[offset + c] = this.pose.light[offset + c] * colour[c] * color[c];
10711
+ if (!draws(template.slot)) continue;
10712
+ this.order[count] = ai;
10713
+ this.renderFrame.order[count++] = template;
10714
+ }
10715
+ this.renderFrame.order.length = count;
10716
+ this.drawn = count;
10717
+ this.epoch = generation;
10718
+ }
10719
+ snapshot(draws) {
10720
+ const selected = [];
10721
+ for (let si = 0; si < this.pose.shown.length; si++) {
10722
+ const ai = this.pose.shown[si];
10723
+ const slot = ai < 0 ? null : this.templates[ai];
10724
+ if (slot && this.model.active[this.model.slots[si].bone] && (draws === void 0 || draws(slot.slot))) selected.push(slot);
10725
+ }
10726
+ const order = selected.map((slot) => {
10727
+ const common = { ...slot, tint: slot.tint.slice() };
10728
+ return slot.kind === "region" ? { ...common, kind: "region", region: slot.region, corners: slot.corners.slice(), page: slot.page, pma: slot.pma, uvs: slot.uvs } : { ...common, kind: "mesh", vertices: slot.vertices.slice(), page: slot.page, pma: slot.pma, uvs: slot.uvs, triangles: slot.triangles, sequence: slot.sequence };
10729
+ });
10730
+ return { order };
10731
+ }
10732
+ }
10733
+ function oracleLiveFrame(frame2, identities, indices) {
10734
+ let size = 0;
10735
+ for (const slot of frame2.order) size += (slot.kind === "region" ? slot.corners : slot.vertices).length;
10736
+ const vertices = new Float64Array(size);
10737
+ const tint = new Float64Array(indices.size * 4);
10738
+ const drawOrder = new Int32Array(frame2.order.length);
10739
+ let offset = 0;
10740
+ const attachments = frame2.order.map((slot, index) => {
10741
+ const points = slot.kind === "region" ? slot.corners : slot.vertices;
10742
+ const slotIndex = indices.get(slot.slot);
10743
+ if (slotIndex === void 0) throw new Error(`LIVE_FRAME_SLOT: drawn slot "${slot.slot}" has no setup index`);
10744
+ vertices.set(points, offset);
10745
+ tint.set(slot.tint, slotIndex * 4);
10746
+ drawOrder[index] = index;
10747
+ const identity = identities.get(slot.slot);
10748
+ if (!identity) throw new Error(`LIVE_FRAME_IDENTITY: drawn slot "${slot.slot}" has no coherent identity`);
10749
+ const metadata = {
10750
+ slot: slot.slot,
10751
+ slotIndex,
10752
+ attachment: identity.attachment,
10753
+ skin: identity.skin,
10754
+ placeholder: null,
10755
+ kind: slot.kind,
10756
+ region: slot.kind === "region" ? slot.region : null,
10757
+ page: slot.page,
10758
+ pma: slot.pma,
10759
+ blend: slot.blend,
10760
+ offset,
10761
+ count: points.length,
10762
+ uvs: slot.uvs,
10763
+ triangles: slot.kind === "mesh" ? slot.triangles : [],
10764
+ dark: slot.dark ?? null,
10765
+ clip: slot.clip ?? null
10766
+ };
10767
+ offset += points.length;
10768
+ return metadata;
10769
+ });
10770
+ const live = { generation: frame2.generation ?? 0, count: attachments.length, drawOrder, vertices, tint, attachments };
10771
+ liveDrawings.set(live, frame2);
10772
+ return live;
10773
+ }
8964
10774
  const DEFAULT_ENVIRONMENT = Object.freeze({ wind: Object.freeze([1, 0]), gravity: Object.freeze([0, 1]) });
8965
10775
  function setEaseOption(call, options) {
8966
10776
  const ease = options?.ease;
@@ -9287,6 +11097,14 @@ class RigPlayer {
9287
11097
  setup = null;
9288
11098
  seekCache = null;
9289
11099
  poseGeneration = 0;
11100
+ runtimeModel = null;
11101
+ runtimeFrames = null;
11102
+ runtimeReadback = null;
11103
+ runtimeCompletionClock = { trackTime: 0 };
11104
+ poserLabel = "oracle: not selected";
11105
+ oracleOnly;
11106
+ liveChecks;
11107
+ liveDisposed = false;
9290
11108
  cachedIdentity = null;
9291
11109
  cachedIdentityGen = -1;
9292
11110
  setList;
@@ -9326,6 +11144,9 @@ class RigPlayer {
9326
11144
  constructor(root, assets, options = {}) {
9327
11145
  const read = assets.document;
9328
11146
  this.read = read;
11147
+ if (options.poser !== void 0 && options.poser !== "auto" && options.poser !== "oracle") throw new Error(`poser must be auto or oracle, got ${String(options.poser)}`);
11148
+ this.oracleOnly = options.poser === "oracle";
11149
+ this.liveChecks = options.liveFrameChecks ?? false;
9329
11150
  this.skinName = options.skin;
9330
11151
  this.view = options.skin === void 0 ? underNoSkin(read.doc) : underSkin(read.doc, options.skin);
9331
11152
  this.source = frameSource(this.view, read.parsed);
@@ -9342,6 +11163,7 @@ class RigPlayer {
9342
11163
  this.poseGeneration = 0;
9343
11164
  this.cachedIdentity = null;
9344
11165
  this.active = this.create(options.mode ?? "dom");
11166
+ this.selectRuntime();
9345
11167
  }
9346
11168
  /**
9347
11169
  * How the player draws. Setting it swaps the renderer without reloading
@@ -9386,7 +11208,7 @@ class RigPlayer {
9386
11208
  }
9387
11209
  /** The player's time — the running sum of the steps since `play` (or the last `seek`). */
9388
11210
  get time() {
9389
- return this.tracks?.trackTime ?? this.track?.trackTime ?? 0;
11211
+ return this.runtimeFrames?.pose.trackTime ?? this.tracks?.trackTime ?? this.track?.trackTime ?? 0;
9390
11212
  }
9391
11213
  /**
9392
11214
  * Plays `name` on a track — track 0 by default, which is what `play(name)`
@@ -9411,6 +11233,8 @@ class RigPlayer {
9411
11233
  const plain = track === 0 && mix === 0 && alpha === 1 && !additive;
9412
11234
  if (!plain) this.beyondBase = true;
9413
11235
  if (this.tracks === null && plain && !this.trackMode) {
11236
+ if (this.tryRuntimePlay(name, loop)) return;
11237
+ this.oracle("animation not supported by the runtime");
9414
11238
  this.animation = name;
9415
11239
  this.loop = loop;
9416
11240
  this.track = openTrack(this.view, name, { loop, reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
@@ -9485,6 +11309,11 @@ class RigPlayer {
9485
11309
  }
9486
11310
  /** `track`'s current entry, or null where it plays nothing (and before `play`). */
9487
11311
  entry(track = 0) {
11312
+ if (this.runtimeFrames !== null) {
11313
+ if (track !== 0 || this.animation === null) return null;
11314
+ const pose = this.runtimeFrames.pose;
11315
+ return { animation: this.animation, loop: this.loop, trackTime: pose.trackTime, animationTime: pose.time, duration: this.runtimeFrames.model.animation(this.animation).duration, alpha: 1, additive: false };
11316
+ }
9488
11317
  if (this.tracks !== null) {
9489
11318
  const view = this.tracks.current(track);
9490
11319
  return view === null ? null : { animation: view.animation, loop: view.loop, trackTime: view.trackTime, animationTime: view.animationTime, duration: view.duration, alpha: view.alpha, additive: view.additive };
@@ -9508,6 +11337,7 @@ class RigPlayer {
9508
11337
  * `seekStep` of time played — which `tracks: true` avoids entirely.
9509
11338
  */
9510
11339
  compose() {
11340
+ this.oracle("track composition");
9511
11341
  if (this.tracks !== null) return this.tracks;
9512
11342
  this.clearSeekCache();
9513
11343
  const tracks = openTracks(this.view, { reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
@@ -9620,6 +11450,7 @@ class RigPlayer {
9620
11450
  * there is nothing to move.
9621
11451
  */
9622
11452
  moved(motion) {
11453
+ if (this.runtimeFrames !== null && Number.isFinite(motion.x ?? 0) && Number.isFinite(motion.y ?? 0) && Number.isFinite(motion.rotation ?? 0)) return;
9623
11454
  const live = this.stepper();
9624
11455
  if (!live) return;
9625
11456
  const x = motion.x ?? 0;
@@ -9648,6 +11479,16 @@ class RigPlayer {
9648
11479
  if (this.animation === null) throw new Error("seek before play: no animation is open");
9649
11480
  if (!(time >= 0) || !Number.isFinite(time)) throw new Error(`seek time must be a finite time at or above 0, got ${String(time)}`);
9650
11481
  if (this.beyondBase) throw new Error("seek is defined for a single animation on track 0; after a mix, another track, a queue or a clear the player cannot replay to a time — step it forward with frame/advance instead");
11482
+ if (this.runtimeFrames !== null) {
11483
+ const steps2 = Math.floor(time / this.seekStep);
11484
+ let replayTime = 0;
11485
+ for (let i = 0; i < steps2; i++) replayTime += this.seekStep;
11486
+ const rest2 = time - replayTime;
11487
+ if (rest2 > 0) replayTime += rest2;
11488
+ this.runtimeFrames.pose.trackTime = replayTime;
11489
+ this.applyRuntimeTime();
11490
+ return;
11491
+ }
9651
11492
  this.easing = null;
9652
11493
  const cache = this.ensureSeekCache();
9653
11494
  const stepper = this.tracks ?? this.track;
@@ -9762,6 +11603,7 @@ class RigPlayer {
9762
11603
  }
9763
11604
  /** What is open: the tracks, the single track, or nothing before `play`. */
9764
11605
  stepper() {
11606
+ this.oracle("oracle track operation");
9765
11607
  return this.tracks ?? this.track;
9766
11608
  }
9767
11609
  /** A newly opened track or tracks answer the environment the player stands in; one never set leaves it untouched. */
@@ -9801,6 +11643,33 @@ class RigPlayer {
9801
11643
  * the events and `time` stay on the steps themselves (#103).
9802
11644
  */
9803
11645
  advance(dt) {
11646
+ if (this.runtimeFrames !== null) {
11647
+ const step2 = dt * this.timeScale;
11648
+ if (this.paused || !(step2 > 0)) return;
11649
+ if (!Number.isFinite(step2)) throw new Error(`advance step must be finite, got ${String(step2)}`);
11650
+ const before2 = this.runtimeFrames.pose.trackTime;
11651
+ const duration = this.runtimeFrames.model.animations.get(this.animation).duration;
11652
+ this.runtimeFrames.pose.trackTime += step2;
11653
+ this.lastStep = step2;
11654
+ this.applyRuntimeTime();
11655
+ const completionClock = this.runtimeCompletionClock;
11656
+ if (this.listeners.event.size > 0) {
11657
+ const events = this.runtimeFrames.pose.events.slice();
11658
+ for (const key of events) {
11659
+ const payload2 = { name: key.name, time: key.time, int: key.int, float: key.float, string: key.string, track: 0 };
11660
+ for (const listener of this.listeners.event) listener(payload2);
11661
+ }
11662
+ }
11663
+ if (this.listeners.complete.size > 0) {
11664
+ const after = completionClock.trackTime;
11665
+ const complete = this.loop ? duration === 0 || Math.floor(after / duration) > Math.floor(before2 / duration) : before2 < duration && after >= duration;
11666
+ if (complete) {
11667
+ const payload2 = { animation: this.animation, track: 0 };
11668
+ for (const listener of this.listeners.complete) listener(payload2);
11669
+ }
11670
+ }
11671
+ return;
11672
+ }
9804
11673
  if (this.pose === null) return;
9805
11674
  const step = dt * this.timeScale;
9806
11675
  if (this.paused || !(step > 0)) return;
@@ -9824,6 +11693,11 @@ class RigPlayer {
9824
11693
  }
9825
11694
  /** Draws the pose the player holds. */
9826
11695
  draw() {
11696
+ if (this.runtimeFrames !== null) {
11697
+ this.runtimeFrames.update(this.poseGeneration, this.color, this.draws, false);
11698
+ this.renderer.render(this.runtimeFrames.live);
11699
+ return;
11700
+ }
9827
11701
  if (this.pose === null) return;
9828
11702
  this.renderer.render(this.posedFrame());
9829
11703
  }
@@ -9844,14 +11718,27 @@ class RigPlayer {
9844
11718
  if (name === this.skinName) return;
9845
11719
  if (this.beyondBase) throw new Error("skin cannot change after a mix, another track, a queue or a clear: the player cannot replay those to the current time — set it before composing, or play again");
9846
11720
  const view = name === void 0 ? underNoSkin(this.read.doc) : underSkin(this.read.doc, name);
11721
+ this.oracle("skin change");
9847
11722
  this.view = view;
9848
11723
  this.source = frameSource(view, this.read.parsed);
11724
+ this.selectRuntime();
9849
11725
  this.skinName = name;
9850
11726
  this.setup = null;
9851
11727
  this.easing = null;
9852
11728
  this.clearSeekCache();
9853
11729
  if (this.animation !== null && (this.track !== null || this.tracks !== null)) {
9854
11730
  const time = this.time;
11731
+ const animation = this.animation;
11732
+ const animationTime = this.entry(0).animationTime;
11733
+ if (this.tryRuntimePlay(animation, this.loop)) {
11734
+ const frames = this.runtimeFrames;
11735
+ frames.pose.restore(animation, time, animationTime);
11736
+ this.poseGeneration++;
11737
+ this.cachedIdentity = null;
11738
+ this.runtimeReadback = null;
11739
+ frames.update(this.poseGeneration, this.color, this.draws);
11740
+ return;
11741
+ }
9855
11742
  this.reopen();
9856
11743
  this.stepTo(time);
9857
11744
  } else if (this.rawPose !== null) {
@@ -10237,6 +12124,7 @@ class RigPlayer {
10237
12124
  * before changing anything, then applies their composition to the held pose.
10238
12125
  */
10239
12126
  activate(sets, physics, ease = 0) {
12127
+ this.oracle("attachment sets");
10240
12128
  for (let i = 0; i < sets.length; i++) {
10241
12129
  for (let j = 0; j < i; j++) {
10242
12130
  const other = this.touched(sets[j]);
@@ -10338,6 +12226,7 @@ class RigPlayer {
10338
12226
  }
10339
12227
  set adjust(fn) {
10340
12228
  if (fn !== null && typeof fn !== "function") throw new Error(`adjust must be a function or null, got ${typeof fn}`);
12229
+ this.oracle("local adjustment");
10341
12230
  this.adjustFn = fn;
10342
12231
  this.clearSeekCache();
10343
12232
  this.repose();
@@ -10350,6 +12239,10 @@ class RigPlayer {
10350
12239
  * changed, then `draw`. Nothing before `play`.
10351
12240
  */
10352
12241
  repose() {
12242
+ if (this.runtimeFrames !== null) {
12243
+ this.applyRuntimeTime();
12244
+ return;
12245
+ }
10353
12246
  this.clearSeekCache();
10354
12247
  const stepper = this.tracks ?? this.track;
10355
12248
  if (stepper === null || this.pose === null) return;
@@ -10392,7 +12285,7 @@ class RigPlayer {
10392
12285
  * mode; nothing is computed until called.
10393
12286
  */
10394
12287
  bounds() {
10395
- const frame2 = this.pose === null ? frameOf(this.source, this.posed(), this.color, this.draws) : this.posedFrame();
12288
+ const frame2 = this.runtimeFrames !== null ? this.posedFrame() : this.pose === null ? frameOf(this.source, this.posed(), this.color, this.draws) : this.posedFrame();
10396
12289
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
10397
12290
  for (const slot of frame2.order) {
10398
12291
  const points = slot.kind === "region" ? slot.corners : slot.vertices;
@@ -10424,10 +12317,110 @@ class RigPlayer {
10424
12317
  }
10425
12318
  /** The pose the reads above see: the track's, or the setup pose before `play` (posed once). */
10426
12319
  posed() {
12320
+ if (this.runtimeFrames !== null && this.animation !== null) {
12321
+ if (this.runtimeReadback === null) {
12322
+ const track = openTrack(this.view, this.animation, { loop: this.loop, reset: "animation", clock: physicsClockStep });
12323
+ this.runtimeReadback = this.time > 0 ? track.step(this.time) : track.first;
12324
+ }
12325
+ return this.runtimeReadback;
12326
+ }
10427
12327
  if (this.pose !== null) return this.pose;
10428
12328
  if (this.setup === null) this.setup = poseRawSetup(this.view);
10429
12329
  return this.activeSet === null ? this.setup : applySetToPose(this.view, this.setup, this.activeSet);
10430
12330
  }
12331
+ /** Selected poser, including the named reason when the oracle is required. */
12332
+ get poser() {
12333
+ return this.runtimeFrames !== null ? "runtime" : this.poserLabel === "runtime" ? "oracle: no runtime animation is active" : this.poserLabel;
12334
+ }
12335
+ selectRuntime() {
12336
+ this.runtimeModel = null;
12337
+ if (this.oracleOnly) {
12338
+ this.poserLabel = "oracle: explicitly selected";
12339
+ return;
12340
+ }
12341
+ try {
12342
+ this.runtimeModel = compileRuntime(this.view);
12343
+ this.poserLabel = this.trackMode ? "oracle: track composition" : this.adjustFn !== null ? "oracle: local adjustment" : "runtime";
12344
+ } catch (error) {
12345
+ if (!(error instanceof RuntimeRefusal)) throw error;
12346
+ this.poserLabel = `oracle: ${error.message}`;
12347
+ }
12348
+ }
12349
+ tryRuntimePlay(name, loop) {
12350
+ if (this.runtimeModel === null) return false;
12351
+ const blocked = this.trackMode ? "track composition" : this.adjustFn !== null ? "local adjustment" : this.activeSet !== null ? "attachment sets" : this.easing !== null ? "set easing" : this.env !== null ? "environment configuration" : null;
12352
+ if (blocked !== null) {
12353
+ this.poserLabel = `oracle: ${blocked}`;
12354
+ return false;
12355
+ }
12356
+ if (!this.runtimeModel.animations.has(name)) {
12357
+ const refused = this.runtimeModel.refused.find((value) => value.animation === name);
12358
+ if (refused) this.poserLabel = `oracle: ${new RuntimeRefusal(refused.missing).message}`;
12359
+ return false;
12360
+ }
12361
+ const frames = this.runtimeFrames ?? new RuntimeFrames(this.source, this.runtimeModel);
12362
+ frames.pose.setAnimation(name);
12363
+ frames.pose.apply(0);
12364
+ this.runtimeFrames = frames;
12365
+ this.runtimeCompletionClock = { trackTime: 0 };
12366
+ this.animation = name;
12367
+ this.loop = loop;
12368
+ this.track = null;
12369
+ this.tracks = null;
12370
+ this.pose = null;
12371
+ this.rawPose = null;
12372
+ this.poserLabel = "runtime";
12373
+ this.poseGeneration++;
12374
+ this.cachedIdentity = null;
12375
+ this.runtimeReadback = null;
12376
+ frames.update(this.poseGeneration, this.color, this.draws);
12377
+ return true;
12378
+ }
12379
+ applyRuntimeTime() {
12380
+ const frames = this.runtimeFrames;
12381
+ const duration = frames.model.animations.get(this.animation).duration;
12382
+ const time = frames.pose.trackTime;
12383
+ this.runtimeCompletionClock.trackTime = time;
12384
+ frames.pose.apply(duration === 0 ? 0 : this.loop ? time % duration : Math.min(time, duration));
12385
+ this.poseGeneration++;
12386
+ this.cachedIdentity = null;
12387
+ this.runtimeReadback = null;
12388
+ frames.update(this.poseGeneration, this.color, this.draws);
12389
+ }
12390
+ /** Materialize the old track only at an operation stage 0 cannot execute. */
12391
+ oracle(reason) {
12392
+ if (this.runtimeFrames === null || this.animation === null) return;
12393
+ const time = this.runtimeFrames.pose.trackTime;
12394
+ const generation = this.poseGeneration;
12395
+ this.runtimeFrames = null;
12396
+ this.runtimeReadback = null;
12397
+ if (this.poserLabel === "runtime") this.poserLabel = `oracle: ${reason}`;
12398
+ this.track = openTrack(this.view, this.animation, { loop: this.loop, reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
12399
+ this.setRawPose(this.track.first);
12400
+ if (time > 0) this.setRawPose(this.track.step(time));
12401
+ this.poseGeneration = generation;
12402
+ this.applyEnvironment(this.track);
12403
+ }
12404
+ /** Borrow buffers until the next pose mutation. Request posedFrame() to retain a snapshot. */
12405
+ liveFrame() {
12406
+ if (this.liveDisposed) throw new Error("LIVE_FRAME_DISPOSED: cannot borrow a frame after dispose");
12407
+ let frame2;
12408
+ if (this.runtimeFrames !== null) {
12409
+ this.runtimeFrames.update(this.poseGeneration, this.color, this.draws, false);
12410
+ frame2 = this.runtimeFrames.live;
12411
+ } else {
12412
+ const identities = new Map(this.slotIdentity().slots.map((slot) => [slot.slot, slot]));
12413
+ frame2 = oracleLiveFrame(this.posedFrame(), identities, this.source.slotIndex);
12414
+ }
12415
+ return this.liveChecks ? this.checkedFrame(frame2) : frame2;
12416
+ }
12417
+ /** Keep lease closures out of the production borrow's call environment. */
12418
+ checkedFrame(frame2) {
12419
+ const generation = this.poseGeneration;
12420
+ const state = this.runtimeFrames;
12421
+ const revision = state?.revision;
12422
+ return checkedLiveFrame(frame2, () => this.poseGeneration === generation && (state === null || state.revision === revision) ? generation : -1);
12423
+ }
10431
12424
  /** Monotonic pose generation counter matching `posedFrame().generation` and `slotIdentity().generation`. */
10432
12425
  get generation() {
10433
12426
  return this.poseGeneration;
@@ -10441,7 +12434,8 @@ class RigPlayer {
10441
12434
  */
10442
12435
  posedFrame(slots) {
10443
12436
  const draws = slots === void 0 ? this.draws : slots === null ? () => true : typeof slots === "function" ? slots : predicateOf(slots);
10444
- const frame2 = this.pose === null ? { order: [] } : frameOf(this.source, this.pose, this.color, draws);
12437
+ if (this.runtimeFrames !== null) this.runtimeFrames.update(this.poseGeneration, this.color, this.draws, false);
12438
+ const frame2 = this.runtimeFrames !== null ? this.runtimeFrames.snapshot(draws) : this.pose === null ? { order: [] } : frameOf(this.source, this.pose, this.color, draws);
10445
12439
  Object.defineProperty(frame2, "generation", {
10446
12440
  value: this.poseGeneration,
10447
12441
  enumerable: false,
@@ -10465,7 +12459,7 @@ class RigPlayer {
10465
12459
  }
10466
12460
  const setupPose = this.setup ?? (this.setup = poseRawSetup(this.view));
10467
12461
  const pose = this.posed();
10468
- const raw = this.rawPose ?? setupPose;
12462
+ const raw = this.runtimeFrames !== null ? pose : this.rawPose ?? setupPose;
10469
12463
  const shownBySlot = new Map(pose.shown.map((s) => [s.slot, s]));
10470
12464
  const setupShownSlots = new Set(setupPose.shown.map((s) => s.slot));
10471
12465
  const rawRowBySlot = new Map(raw.slots.map((r) => [r[0], r]));
@@ -10563,13 +12557,17 @@ class RigPlayer {
10563
12557
  }
10564
12558
  return result;
10565
12559
  }
10566
- if (this.track !== null && this.animation !== null) {
12560
+ if ((this.track !== null || this.runtimeFrames !== null) && this.animation !== null) {
10567
12561
  return checkAnimation(this.animation, animationTime) === true;
10568
12562
  }
10569
12563
  return false;
10570
12564
  }
10571
12565
  /** Removes the renderer's elements and hands its GPU pages back. The assets stay the caller's. */
10572
12566
  dispose() {
12567
+ this.runtimeFrames = null;
12568
+ this.runtimeReadback = null;
12569
+ this.liveDisposed = true;
12570
+ this.poseGeneration++;
10573
12571
  this.renderer.dispose();
10574
12572
  this.clearSeekCache();
10575
12573
  this.cachedIdentity = null;