@rig-c/play 1.17.1 → 1.19.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
  }
@@ -953,7 +964,8 @@ function clipHalf(input, p, q) {
953
964
  return { points: out, cut };
954
965
  }
955
966
  function clipThrough(shape, vertices, triangles, uvs, clip = clipTriangles, reading = {}) {
956
- const plan = clipShapeOf(shape);
967
+ let plan = clipShapeOf(shape);
968
+ if (plan.kind === "pieces") plan = clipShapeOf({ ...shape, polygon: shape.polygon.map(Math.fround) });
957
969
  if (plan.kind === "refused") return null;
958
970
  if (plan.kind === "convex") return clip(plan.polygon, vertices, triangles, uvs);
959
971
  const f = Math.fround;
@@ -1017,7 +1029,7 @@ function clipThrough(shape, vertices, triangles, uvs, clip = clipTriangles, read
1017
1029
  }
1018
1030
  return { clipped, vertices: outV, uvs: outUv, triangles: outT, sources };
1019
1031
  }
1020
- const RAD$2 = RUNTIME_PI / 180;
1032
+ const RAD$3 = RUNTIME_PI / 180;
1021
1033
  const meshArraysByGeometry = /* @__PURE__ */ new WeakMap();
1022
1034
  function meshArraysOf(mesh) {
1023
1035
  let kept = meshArraysByGeometry.get(mesh);
@@ -1039,7 +1051,7 @@ function regionCorners(region, bone2) {
1039
1051
  const y1 = -H / 2 * sy + r.offsetY * ky;
1040
1052
  const x2 = x1 + r.width * kx;
1041
1053
  const y2 = y1 + r.height * ky;
1042
- const angle = (region.rotation ?? 0) * RAD$2;
1054
+ const angle = (region.rotation ?? 0) * RAD$3;
1043
1055
  const cos = Math.cos(angle);
1044
1056
  const sin = Math.sin(angle);
1045
1057
  const x = region.x ?? 0;
@@ -1052,25 +1064,64 @@ function regionCorners(region, bone2) {
1052
1064
  }
1053
1065
  return out;
1054
1066
  }
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);
1067
+ const compiledWeightedByVertices = /* @__PURE__ */ new WeakMap();
1068
+ function compileWeightedVertices(vertices, mapIndex) {
1069
+ const bones = [];
1070
+ const indexByName = /* @__PURE__ */ new Map();
1071
+ for (const influences of vertices.bindings) {
1072
+ for (const binding of influences) {
1073
+ if (!indexByName.has(binding.bone)) {
1074
+ indexByName.set(binding.bone, bones.length);
1075
+ bones.push(binding.bone);
1076
+ }
1062
1077
  }
1063
- return out;
1064
1078
  }
1065
- for (const influences of vertices.bindings) {
1079
+ bones.length;
1080
+ const bindings = vertices.bindings.map(
1081
+ (influences) => influences.map((binding) => {
1082
+ const idx = indexByName.get(binding.bone);
1083
+ return {
1084
+ bone: idx,
1085
+ x: binding.x,
1086
+ y: binding.y,
1087
+ fx: Math.fround(binding.x),
1088
+ fy: Math.fround(binding.y),
1089
+ weight: Math.fround(binding.weight)
1090
+ };
1091
+ })
1092
+ );
1093
+ return { bones, bindings };
1094
+ }
1095
+ function compiledWeightedOf(vertices) {
1096
+ let kept = compiledWeightedByVertices.get(vertices);
1097
+ if (kept === void 0) {
1098
+ kept = compileWeightedVertices(vertices);
1099
+ compiledWeightedByVertices.set(vertices, kept);
1100
+ }
1101
+ return kept;
1102
+ }
1103
+ function resolveBindingBones(bones, world) {
1104
+ const transforms = new Array(bones.length);
1105
+ for (let i = 0; i < bones.length; i++) {
1106
+ const name = bones[i];
1107
+ const t = world.get(name);
1108
+ if (t === void 0) throw new Error(`a binding names bone "${name}", which has no world transform (readModel refuses it first)`);
1109
+ transforms[i] = t;
1110
+ }
1111
+ return transforms;
1112
+ }
1113
+ function compiledWorldVertices(compiled, world, coords = Math.fround) {
1114
+ const transforms = resolveBindingBones(compiled.bones, world);
1115
+ const out = [];
1116
+ const fround = coords === Math.fround;
1117
+ for (const influences of compiled.bindings) {
1066
1118
  let wx = 0;
1067
1119
  let wy = 0;
1068
1120
  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);
1121
+ const t = transforms[binding.bone];
1122
+ const x = fround ? binding.fx : coords(binding.x);
1123
+ const y = fround ? binding.fy : coords(binding.y);
1124
+ const weight = binding.weight;
1074
1125
  wx += (x * t.a + y * t.b + t.worldX) * weight;
1075
1126
  wy += (x * t.c + y * t.d + t.worldY) * weight;
1076
1127
  }
@@ -1078,27 +1129,19 @@ function worldVertices(vertices, bone2, world, coords = Math.fround) {
1078
1129
  }
1079
1130
  return out;
1080
1131
  }
1081
- function deformedWorldVertices(vertices, deform, bone2, world) {
1132
+ function compiledDeformedWorldVertices(compiled, deform, world) {
1133
+ const transforms = resolveBindingBones(compiled.bones, world);
1082
1134
  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
1135
  let k = 0;
1092
- for (const influences of vertices.bindings) {
1136
+ for (const influences of compiled.bindings) {
1093
1137
  let wx = 0;
1094
1138
  let wy = 0;
1095
1139
  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];
1140
+ const t = transforms[binding.bone];
1141
+ const x = binding.fx + deform[k];
1142
+ const y = binding.fy + deform[k + 1];
1100
1143
  k += 2;
1101
- const weight = Math.fround(binding.weight);
1144
+ const weight = binding.weight;
1102
1145
  wx += (x * t.a + y * t.b + t.worldX) * weight;
1103
1146
  wy += (x * t.c + y * t.d + t.worldY) * weight;
1104
1147
  }
@@ -1106,6 +1149,30 @@ function deformedWorldVertices(vertices, deform, bone2, world) {
1106
1149
  }
1107
1150
  return out;
1108
1151
  }
1152
+ function worldVertices(vertices, bone2, world, coords = Math.fround) {
1153
+ if (!vertices.weighted) {
1154
+ const out = [];
1155
+ for (let i = 0; i + 1 < vertices.xy.length; i += 2) {
1156
+ const x = coords(vertices.xy[i]);
1157
+ const y = coords(vertices.xy[i + 1]);
1158
+ out.push(x * bone2.a + y * bone2.b + bone2.worldX, x * bone2.c + y * bone2.d + bone2.worldY);
1159
+ }
1160
+ return out;
1161
+ }
1162
+ return compiledWorldVertices(compiledWeightedOf(vertices), world, coords);
1163
+ }
1164
+ function deformedWorldVertices(vertices, deform, bone2, world) {
1165
+ if (!vertices.weighted) {
1166
+ const out = [];
1167
+ for (let i = 0; i + 1 < deform.length; i += 2) {
1168
+ const x = deform[i];
1169
+ const y = deform[i + 1];
1170
+ out.push(x * bone2.a + y * bone2.b + bone2.worldX, x * bone2.c + y * bone2.d + bone2.worldY);
1171
+ }
1172
+ return out;
1173
+ }
1174
+ return compiledDeformedWorldVertices(compiledWeightedOf(vertices), deform, world);
1175
+ }
1109
1176
  const isRecord$7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1110
1177
  const finite$1 = (v) => typeof v === "number" && Number.isFinite(v);
1111
1178
  const RECT_FIELDS = ["width", "height", "offsetX", "offsetY", "originalWidth", "originalHeight"];
@@ -1150,7 +1217,10 @@ function readVertices(value, where, bones, problems) {
1150
1217
  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
1218
  });
1152
1219
  });
1153
- return problems.length === before ? { weighted: true, bindings } : void 0;
1220
+ if (problems.length !== before) return void 0;
1221
+ const weighted = { weighted: true, bindings };
1222
+ compiledWeightedByVertices.set(weighted, compileWeightedVertices(weighted));
1223
+ return weighted;
1154
1224
  }
1155
1225
  function readGeometry(raw, kind, where, bones, slots, problems) {
1156
1226
  const before = problems.length;
@@ -1250,6 +1320,7 @@ function roundedRow(values2, round) {
1250
1320
  function poseGeometry(shown, world, sourceOf, round, plant = {}, draw) {
1251
1321
  const region = plant.region ?? regionCorners;
1252
1322
  const vertices = plant.vertices ?? worldVertices;
1323
+ const deformPoser = plant.deformedVertices ?? deformedWorldVertices;
1253
1324
  const attachments = [];
1254
1325
  const clips = [];
1255
1326
  const nulls = [];
@@ -1260,7 +1331,7 @@ function poseGeometry(shown, world, sourceOf, round, plant = {}, draw) {
1260
1331
  if (bone2 === void 0) throw new Error(`slot "${s.slot}": bone "${s.bone}" has no world transform`);
1261
1332
  const g = s.geometry;
1262
1333
  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);
1334
+ 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
1335
  if (g.kind === "region") {
1265
1336
  const rect = s.frame === void 0 ? g.region.atlas : g.region.frames?.[s.frame] ?? g.region.atlas;
1266
1337
  if (rect === null) {
@@ -1308,7 +1379,7 @@ function poseGeometry(shown, world, sourceOf, round, plant = {}, draw) {
1308
1379
  }
1309
1380
  return { attachments: attachmentsWhy === null ? attachments : null, attachmentsWhy, clips, clipped, clippedWhy, drawnClipped, drawnClippedWhy };
1310
1381
  }
1311
- const RAD$1 = RUNTIME_PI / 180;
1382
+ const RAD$2 = RUNTIME_PI / 180;
1312
1383
  function curveLengthTable(chain, curveCount) {
1313
1384
  const curveEnds = [];
1314
1385
  let total = 0;
@@ -1747,7 +1818,7 @@ function solvePath(state, c, slotWorld) {
1747
1818
  else {
1748
1819
  fromTip = false;
1749
1820
  const p = slotWorld;
1750
- offsetRotation *= p.a * p.d - p.b * p.c > 0 ? RAD$1 : -RAD$1;
1821
+ offsetRotation *= p.a * p.d - p.b * p.c > 0 ? RAD$2 : -RAD$2;
1751
1822
  }
1752
1823
  const changed = [];
1753
1824
  for (let i = 0, p = 3; i < boneCount; i++, p += 3) {
@@ -2593,8 +2664,8 @@ function applySliderPhysics(r, at, alpha, target) {
2593
2664
  }
2594
2665
  }
2595
2666
  }
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] };
2667
+ const clamp01$3 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
2668
+ 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
2669
  function applySliderSlots(slot, pose, applications) {
2599
2670
  for (const app of applications) {
2600
2671
  for (const target of app.timelines.slots) {
@@ -2607,9 +2678,9 @@ function applySliderSlots(slot, pose, applications) {
2607
2678
  }
2608
2679
  const v = valuesAt$1(tl.keys, app.at);
2609
2680
  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);
2681
+ COLOUR_CHANNELS$1[tl.kind].forEach((at, i) => {
2682
+ 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);
2683
+ 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
2684
  });
2614
2685
  }
2615
2686
  }
@@ -2632,8 +2703,8 @@ function sliderBonesWhy(doc) {
2632
2703
  }
2633
2704
  return found.length === 0 ? null : found.join("; ");
2634
2705
  }
2635
- const DEG = 180 / RUNTIME_PI;
2636
- const RAD = RUNTIME_PI / 180;
2706
+ const DEG$1 = 180 / RUNTIME_PI;
2707
+ const RAD$1 = RUNTIME_PI / 180;
2637
2708
  const ADMITTED_CONSTRAINT_KINDS = ["ik", "transform", "path", "physics", "slider"];
2638
2709
  const TRANSFORM_PROPERTIES = ["rotate", "x", "y", "scaleX", "scaleY", "shearY"];
2639
2710
  const TRANSFORM_MIXES = { rotate: "mixRotate", x: "mixX", y: "mixY", scaleX: "mixScaleX", scaleY: "mixScaleY", shearY: "mixShearY" };
@@ -2900,7 +2971,7 @@ function posedRecords(records, timelines, t, plant = {}) {
2900
2971
  return { ...r, mixes };
2901
2972
  });
2902
2973
  }
2903
- function wrap180(r) {
2974
+ function wrap180$1(r) {
2904
2975
  return r > 180 ? r - 360 : r <= -180 ? r + 360 : r;
2905
2976
  }
2906
2977
  const IDENTITY = { a: 1, b: 0, c: 0, d: 1, worldX: 0, worldY: 0 };
@@ -2939,7 +3010,7 @@ const RUNTIME_SOLVER_RULES = {
2939
3010
  function solverRules(plant) {
2940
3011
  return plant === void 0 ? RUNTIME_SOLVER_RULES : { ...RUNTIME_SOLVER_RULES, ...plant };
2941
3012
  }
2942
- function volumeScaleY(scaleY, s) {
3013
+ function volumeScaleY$1(scaleY, s) {
2943
3014
  return scaleY / (s < 0.7 ? 0.25 + s * 0.642857 : s);
2944
3015
  }
2945
3016
  function bone(state, name) {
@@ -2978,11 +3049,11 @@ function localFromWorld(b, p, w, rules = RUNTIME_SOLVER_RULES) {
2978
3049
  const length = Math.sqrt(w.a * w.a + w.c * w.c);
2979
3050
  const [ux2, uy2] = [w.a / length, w.c / length];
2980
3051
  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;
3052
+ b.rotation = Math.atan2((uy2 * p.a - ux2 * p.c) / det, (ux2 * p.d - uy2 * p.b) / det) * DEG$1;
2982
3053
  return;
2983
3054
  }
2984
3055
  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;
3056
+ 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
3057
  const [ux, uy] = noScaleDirection([p.a, p.b, p.c, p.d], rotation);
2987
3058
  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
3059
  b.rotation = rotation;
@@ -3006,7 +3077,7 @@ function localFromWorld(b, p, w, rules = RUNTIME_SOLVER_RULES) {
3006
3077
  [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
3078
  }
3008
3079
  decompose(b, la, lc, lb, ld, rules);
3009
- b.rotation = (b.rotation ?? 0) + Math.atan2(p.c, p.a) * DEG;
3080
+ b.rotation = (b.rotation ?? 0) + Math.atan2(p.c, p.a) * DEG$1;
3010
3081
  return;
3011
3082
  } else {
3012
3083
  const [ia, ib, ic, id] = [p.d * pid, p.b * pid, p.c * pid, p.a * pid];
@@ -3021,12 +3092,12 @@ function decompose(b, la, lc, lb, ld, rules, xAngleIn = "rotation") {
3021
3092
  b.scaleX = 0;
3022
3093
  b.scaleY = yLength;
3023
3094
  b.shearY = 0;
3024
- b.rotation = !rules.readBackCollapsed || yLength > 1e-5 ? Math.atan2(ld, lb) * DEG - 90 : 0;
3095
+ b.rotation = !rules.readBackCollapsed || yLength > 1e-5 ? Math.atan2(ld, lb) * DEG$1 - 90 : 0;
3025
3096
  return;
3026
3097
  }
3027
- const xAngle = Math.atan2(lc, la) * DEG;
3098
+ const xAngle = Math.atan2(lc, la) * DEG$1;
3028
3099
  const det = la * ld - lb * lc;
3029
- let yAngle = Math.atan2(ld, lb) * DEG;
3100
+ let yAngle = Math.atan2(ld, lb) * DEG$1;
3030
3101
  if (xAngleIn === "rotation") b.rotation = xAngle;
3031
3102
  else b.shearX = xAngle;
3032
3103
  b.scaleX = sx;
@@ -3055,7 +3126,7 @@ function solveOne(state, c, frame2 = null) {
3055
3126
  const k = Math.abs(pa * pd - pb * pc) / Math.max(state.rules.ikXAxisFloor, pa * pa + pc * pc);
3056
3127
  pb = -pc * k;
3057
3128
  pd = pa * k;
3058
- turn = Math.atan2(pc, pa) * DEG;
3129
+ turn = Math.atan2(pc, pa) * DEG$1;
3059
3130
  const det = pa * pd - pb * pc;
3060
3131
  const x = t.worldX - p.worldX;
3061
3132
  const y = t.worldY - p.worldY;
@@ -3073,9 +3144,9 @@ function solveOne(state, c, frame2 = null) {
3073
3144
  }
3074
3145
  const rotation = b.rotation ?? 0;
3075
3146
  const scaleX = b.scaleX ?? 1;
3076
- let r = -(b.shearX ?? 0) - rotation + turn + Math.atan2(ly, lx) * DEG;
3147
+ let r = -(b.shearX ?? 0) - rotation + turn + Math.atan2(ly, lx) * DEG$1;
3077
3148
  if (scaleX < 0) r += 180;
3078
- b.rotation = rotation + wrap180(r) * c.mix;
3149
+ b.rotation = rotation + wrap180$1(r) * c.mix;
3079
3150
  const len = (b.length ?? 0) * scaleX;
3080
3151
  if ((c.compress || c.stretch) && len > state.rules.ikStretchMinLength) {
3081
3152
  const wx = t.worldX - bw.worldX;
@@ -3085,7 +3156,7 @@ function solveOne(state, c, frame2 = null) {
3085
3156
  const s = (d / len - 1) * c.mix + 1;
3086
3157
  b.scaleX = scaleX * s;
3087
3158
  if (c.scaleY === "uniform") b.scaleY = (b.scaleY ?? 1) * s;
3088
- else if (c.scaleY === "volume") b.scaleY = volumeScaleY(b.scaleY ?? 1, s);
3159
+ else if (c.scaleY === "volume") b.scaleY = volumeScaleY$1(b.scaleY ?? 1, s);
3089
3160
  }
3090
3161
  }
3091
3162
  return [b.name];
@@ -3158,7 +3229,7 @@ function solveTwo(state, c, frame2 = null) {
3158
3229
  const f = (Math.sqrt(d2) / (l1 + l2) - 1) * c.mix + 1;
3159
3230
  sx *= f;
3160
3231
  if (c.scaleY === "uniform") sy *= f;
3161
- else if (c.scaleY === "volume") sy = volumeScaleY(sy, f);
3232
+ else if (c.scaleY === "volume") sy = volumeScaleY$1(sy, f);
3162
3233
  }
3163
3234
  } else a2 = Math.acos(cos) * bend;
3164
3235
  const along = l1 + l2 * cos;
@@ -3211,14 +3282,14 @@ function solveTwo(state, c, frame2 = null) {
3211
3282
  const signs = state.rules.ikZeroScaleSignPositive ? (psx < 0 ? -1 : 1) * (psy < 0 ? -1 : 1) : Math.sign(psx) * Math.sign(psy);
3212
3283
  const offset = Math.atan2(cy, cx);
3213
3284
  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;
3285
+ const parentChange = (a1 - signs * offset) * DEG$1 + (psx < 0 ? 180 : 0) - parentRotation;
3286
+ parent.rotation = parentRotation + wrap180$1(parentChange) * c.mix;
3216
3287
  parent.scaleX = sx;
3217
3288
  parent.scaleY = sy;
3218
- const childTarget = ((a2 + signs * offset) * DEG - (child.shearX ?? 0)) * signs + (csx < 0 ? 180 : 0);
3289
+ const childTarget = ((a2 + signs * offset) * DEG$1 - (child.shearX ?? 0)) * signs + (csx < 0 ? 180 : 0);
3219
3290
  const childRotation = child.rotation ?? 0;
3220
3291
  child.y = cy;
3221
- child.rotation = childRotation + wrap180(childTarget - childRotation) * c.mix;
3292
+ child.rotation = childRotation + wrap180$1(childTarget - childRotation) * c.mix;
3222
3293
  return [parent.name];
3223
3294
  }
3224
3295
  function sourceValue(state, c, property) {
@@ -3244,7 +3315,7 @@ function sourceValue(state, c, property) {
3244
3315
  switch (property) {
3245
3316
  case "rotate": {
3246
3317
  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);
3318
+ let r = Math.atan2(w.c, w.a) * DEG$1 + ((state.rules.offsetNegatedAtZeroDet ? det > 0 : det >= 0) ? o.rotate : -o.rotate);
3248
3319
  if (r < 0) r += 360;
3249
3320
  return r;
3250
3321
  }
@@ -3257,13 +3328,13 @@ function sourceValue(state, c, property) {
3257
3328
  case "scaleY":
3258
3329
  return Math.sqrt(w.b * w.b + w.d * w.d) + o.scaleY;
3259
3330
  case "shearY":
3260
- return (Math.atan2(w.d, w.b) - Math.atan2(w.c, w.a)) * DEG - 90 + o.shearY;
3331
+ return (Math.atan2(w.d, w.b) - Math.atan2(w.c, w.a)) * DEG$1 - 90 + o.shearY;
3261
3332
  }
3262
3333
  }
3263
- function applyWorld(w, property, v, mix, additive, rules) {
3334
+ function applyWorld$1(w, property, v, mix, additive, rules) {
3264
3335
  switch (property) {
3265
3336
  case "rotate": {
3266
- let r = additive ? v * RAD : v * RAD - Math.atan2(w.c, w.a);
3337
+ let r = additive ? v * RAD$1 : v * RAD$1 - Math.atan2(w.c, w.a);
3267
3338
  if (r > RUNTIME_PI) r -= 2 * RUNTIME_PI;
3268
3339
  else if (r < -RUNTIME_PI) r += 2 * RUNTIME_PI;
3269
3340
  r *= mix;
@@ -3295,7 +3366,7 @@ function applyWorld(w, property, v, mix, additive, rules) {
3295
3366
  case "shearY": {
3296
3367
  const yAngle = Math.atan2(w.d, w.b);
3297
3368
  const xAngle = Math.atan2(w.c, w.a);
3298
- let r = additive && !rules.additiveShearRightAngle ? v * RAD : (v + 90) * RAD - (additive ? 90 * RAD : yAngle - xAngle);
3369
+ let r = additive && !rules.additiveShearRightAngle ? v * RAD$1 : (v + 90) * RAD$1 - (additive ? 90 * RAD$1 : yAngle - xAngle);
3299
3370
  if (!additive) r = r > RUNTIME_PI ? r - 2 * RUNTIME_PI : r < -RUNTIME_PI ? r + 2 * RUNTIME_PI : r;
3300
3371
  const angle = yAngle + r * mix;
3301
3372
  const s = Math.sqrt(w.b * w.b + w.d * w.d);
@@ -3333,7 +3404,7 @@ function solveTransform(state, c) {
3333
3404
  }
3334
3405
  applied = true;
3335
3406
  if (c.localTarget) applyLocal(b, to.property, v, mix, c.additive);
3336
- else applyWorld(w, to.property, v, mix, c.additive, state.rules);
3407
+ else applyWorld$1(w, to.property, v, mix, c.additive, state.rules);
3337
3408
  }
3338
3409
  }
3339
3410
  if (!applied) continue;
@@ -3917,7 +3988,7 @@ function valuesAt(keys, t, plant) {
3917
3988
  const channel2 = plant.channel ?? channelAt;
3918
3989
  return keys[index].values.map((_v, c) => channel2(keys, index, c, t));
3919
3990
  }
3920
- function keyAt(keys, t, plant) {
3991
+ function keyAt$1(keys, t, plant) {
3921
3992
  const index = (plant.search ?? keyIndexAt)(keys, t);
3922
3993
  return index < 0 ? null : keys[index];
3923
3994
  }
@@ -3938,7 +4009,7 @@ function posedBones(doc, timelines, t, plant = {}) {
3938
4009
  const bone2 = { ...setup };
3939
4010
  for (const tl of target.timelines) {
3940
4011
  if (tl.kind === "inherit") {
3941
- const key = keyAt(tl.keys, t, plant);
4012
+ const key = keyAt$1(tl.keys, t, plant);
3942
4013
  if (key === null) {
3943
4014
  if (setup.inheritMode === void 0) delete bone2.inheritMode;
3944
4015
  else bone2.inheritMode = setup.inheritMode;
@@ -4066,28 +4137,29 @@ function adjustLocals(doc, bones, adjust, context) {
4066
4137
  }
4067
4138
  }, context);
4068
4139
  }
4069
- const clamp01$1 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
4140
+ const clamp01$2 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
4070
4141
  function posedSlots(doc, timelines, t, plant = {}, sliders = [], placeholders) {
4071
- const colour = plant.colour ?? readColour;
4142
+ const stated = viewColours(doc);
4143
+ const colour = plant.colour ?? stated;
4072
4144
  const byName = new Map(timelines.slots.map((s) => [s.name, s]));
4073
4145
  return slotRowsFrom(doc, plant, sliders, placeholders, (slot, live) => {
4074
4146
  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);
4147
+ const setupLight = slot.color === void 0 ? WHITE : colour(slot.color);
4148
+ const light = [setupLight[0], setupLight[1], setupLight[2], setupLight[3]];
4149
+ const setupDark = slot.dark === void 0 ? null : stated(slot.dark);
4077
4150
  const dark = setupDark === null ? null : [setupDark[0], setupDark[1], setupDark[2]];
4078
4151
  for (const tl of live ? byName.get(slot.name)?.timelines ?? [] : []) {
4079
4152
  if (tl.kind === "attachment") {
4080
- const key = keyAt(tl.keys, t, plant);
4153
+ const key = keyAt$1(tl.keys, t, plant);
4081
4154
  placeholder = key === null ? slot.setup : key.name ?? null;
4082
4155
  continue;
4083
4156
  }
4084
4157
  const v = valuesAt(tl.keys, t, plant);
4085
- const setupLight = slot.color === void 0 ? [1, 1, 1, 1] : colour(slot.color);
4086
4158
  const channels = SLOT_COLOUR_TIMELINES[tl.kind].channels;
4087
4159
  channels.forEach((name, i) => {
4088
4160
  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]);
4161
+ if (at < 4) light[at] = v === null ? setupLight[at] : clamp01$2(v[i]);
4162
+ else if (dark !== null && setupDark !== null) dark[at - 4] = v === null ? setupDark[at - 4] : clamp01$2(v[i]);
4091
4163
  });
4092
4164
  }
4093
4165
  return { placeholder, light, dark };
@@ -4131,50 +4203,60 @@ function slotRowsFrom(doc, plant, sliders, placeholders, baseOf) {
4131
4203
  }
4132
4204
  return { rows, conflicts };
4133
4205
  }
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
4206
  function posedBoneWorldAlone(doc, timelines, t, plant, constraints, sliders, step) {
4139
4207
  return posedBoneStep(doc, timelines, t, plant, constraints, sliders, step).world;
4140
4208
  }
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
4209
  function posedBoneStep(doc, timelines, t, plant, constraints, sliders, step) {
4210
+ const stage = plant.stage;
4211
+ if (stage !== void 0) stage.begin("timeline");
4151
4212
  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);
4213
+ if (stage !== void 0) stage.end("timeline");
4214
+ if (constraints !== void 0 && step !== void 0) {
4215
+ return steppedLocals(doc, bones, plant, () => pathDeformed(doc, posedRecords(constraintRecords(doc), constraints, t), timelines, t, plant), constraints.physicsKeyed ?? [], t, sliders, step);
4216
+ }
4153
4217
  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);
4218
+ if (step?.adjustment !== void 0) {
4219
+ if (stage !== void 0) stage.begin("timeline");
4220
+ adjustLocals(doc, bones, step.adjustment.callback, step.adjustment.context);
4221
+ if (stage !== void 0) stage.end("timeline");
4222
+ }
4223
+ if (stage !== void 0) stage.begin("world");
4224
+ const initialWorld = (plant.evaluate ?? worldTransforms)(bones, active);
4225
+ if (stage !== void 0) stage.end("world");
4226
+ let world = initialWorld;
4156
4227
  if (constraints !== void 0) {
4228
+ if (stage !== void 0) stage.begin("constraints");
4157
4229
  const setupRecords = constraintRecords(doc);
4158
4230
  const records = pathDeformed(doc, posedRecords(setupRecords, constraints, t), timelines, t, plant);
4159
4231
  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));
4232
+ world = applyConstraints(bones, initialWorld, active, plant.constraints ? plant.constraints(records) : records, previous, sliders, void 0, void 0, solverRules(plant.solver));
4233
+ if (stage !== void 0) stage.end("constraints");
4161
4234
  }
4162
4235
  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
4236
  return { bones, world, active, round: plant.round ?? gridRound };
4164
4237
  }
4165
4238
  function steppedLocals(doc, bones, plant, records, physicsKeyed, t, sliders, step) {
4239
+ const stage = plant.stage;
4166
4240
  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);
4241
+ if (step.adjustment !== void 0) {
4242
+ if (stage !== void 0) stage.begin("timeline");
4243
+ adjustLocals(doc, bones, step.adjustment.callback, step.adjustment.context);
4244
+ if (stage !== void 0) stage.end("timeline");
4245
+ }
4246
+ if (stage !== void 0) stage.begin("world");
4247
+ const initialWorld = (plant.evaluate ?? worldTransforms)(bones, active);
4248
+ if (stage !== void 0) {
4249
+ stage.end("world");
4250
+ stage.begin("constraints");
4251
+ }
4169
4252
  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));
4253
+ const world = applyConstraints(bones, initialWorld, active, plant.constraints ? plant.constraints(stepped) : stepped, null, sliders, step.ctx, void 0, solverRules(plant.solver));
4254
+ if (stage !== void 0) stage.end("constraints");
4171
4255
  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
4256
  return { bones, world, active, round: plant.round ?? gridRound };
4173
4257
  }
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;
4258
+ function steppedLocalsWorld(doc, bones, plant, sliders, step) {
4259
+ return steppedLocals(doc, bones, plant, () => constraintRecords(doc), [], 0, sliders, step).world;
4178
4260
  }
4179
4261
  function pathDeformed(doc, records, timelines, t, plant) {
4180
4262
  if (timelines.attachments.every((a) => a.deform === null)) return records;
@@ -4806,6 +4888,24 @@ function readColour(hex) {
4806
4888
  const channel2 = (i) => Number.parseInt(hex.slice(2 * i, 2 * i + 2), 16) / 255;
4807
4889
  return [channel2(0), channel2(1), channel2(2), hex.length === 8 ? channel2(3) : 1];
4808
4890
  }
4891
+ const coloursOfView = /* @__PURE__ */ new WeakMap();
4892
+ function viewColours(doc) {
4893
+ let kept = coloursOfView.get(doc);
4894
+ if (kept === void 0) {
4895
+ kept = /* @__PURE__ */ new Map();
4896
+ coloursOfView.set(doc, kept);
4897
+ }
4898
+ const colours = kept;
4899
+ return (hex) => {
4900
+ let c = colours.get(hex);
4901
+ if (c === void 0) {
4902
+ c = readColour(hex);
4903
+ colours.set(hex, c);
4904
+ }
4905
+ return c;
4906
+ };
4907
+ }
4908
+ const WHITE = [1, 1, 1, 1];
4809
4909
  function readBlend(slot) {
4810
4910
  if (slot.blend === void 0) return "Normal";
4811
4911
  const mode = foldBlend(slot.blend);
@@ -5017,7 +5117,7 @@ function rawBones(doc, world) {
5017
5117
  }
5018
5118
  function drawnGeometry(doc, shown, world, order, plant, mode) {
5019
5119
  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);
5120
+ 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
5121
  if (geometry.attachments === null) throw new CoreInputError(`the raw pose leaves the attachments out: ${geometry.attachmentsWhy}`);
5022
5122
  if (geometry.drawnClipped === null) throw new CoreInputError(`the raw pose leaves the clipped triangles out: ${geometry.drawnClippedWhy}`);
5023
5123
  return { rows: geometry.attachments, clips: geometry.clips, drawnClipped: geometry.drawnClipped };
@@ -5030,9 +5130,16 @@ function allNumbers(values2) {
5030
5130
  return true;
5031
5131
  }
5032
5132
  function rawDrawn(doc, shown, world, order, plant, mode = RAW_MODE) {
5133
+ const stage = plant.stage;
5134
+ if (stage !== void 0) stage.begin("geometry");
5033
5135
  const geometry = drawnGeometry(doc, shown, world, order, plant, mode);
5136
+ if (stage !== void 0) {
5137
+ stage.end("geometry");
5138
+ stage.begin("readout");
5139
+ }
5034
5140
  const rows = geometry.rows;
5035
5141
  const drawn = [];
5142
+ const stated = viewColours(doc);
5036
5143
  let k = 0;
5037
5144
  for (const s of shown) {
5038
5145
  const g = s.geometry;
@@ -5040,7 +5147,8 @@ function rawDrawn(doc, shown, world, order, plant, mode = RAW_MODE) {
5040
5147
  const row = rows[k++];
5041
5148
  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
5149
  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);
5150
+ const read = record?.color === void 0 ? WHITE : stated(record.color);
5151
+ const colour = [read[0], read[1], read[2], read[3]];
5044
5152
  let uvs;
5045
5153
  let triangles;
5046
5154
  let hull = null;
@@ -5058,6 +5166,7 @@ function rawDrawn(doc, shown, world, order, plant, mode = RAW_MODE) {
5058
5166
  }
5059
5167
  const clips = geometry.clips.map((c) => [c[0], c[1], c[2], numbersOf(c[3], `clip vertex of slot "${c[0]}"`, mode)]);
5060
5168
  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]]);
5169
+ if (stage !== void 0) stage.end("readout");
5061
5170
  return { drawn, clips, clipped };
5062
5171
  }
5063
5172
  function poseRawSetup(doc, plant = {}) {
@@ -5155,12 +5264,15 @@ function loopedTime(trackTime, duration) {
5155
5264
  return duration === 0 ? 0 : trackTime % duration;
5156
5265
  }
5157
5266
  const RAW_SHAPE = {
5158
- rows: true,
5159
5267
  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
5268
  step: (doc, p, plant, mode) => {
5161
5269
  const drawn = rawDrawn(doc, p.shown, p.world, p.drawOrder, plant, mode);
5270
+ const stage = plant.stage;
5271
+ if (stage !== void 0) stage.begin("readout");
5162
5272
  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 };
5273
+ const pose = { trackTime: p.trackTime, animationTime: p.animationTime, bones: rawBones(doc, p.world), slots: p.slots, drawOrder: p.drawOrder, ...drawn, events, shown: p.shown };
5274
+ if (stage !== void 0) stage.end("readout");
5275
+ return pose;
5164
5276
  }
5165
5277
  };
5166
5278
  function animationOf(doc, animation) {
@@ -5197,7 +5309,7 @@ function stepWalk(track, dt, first = false) {
5197
5309
  const keys = anim.timelines.events;
5198
5310
  const adjustment = adjust === void 0 ? void 0 : { callback: adjust, context: { trackTime, animation: anim.name, globalTime: trackTime } };
5199
5311
  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 }),
5312
+ world: (sliders) => posedBoneWorldAlone(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjustment }),
5201
5313
  slots: (sliders, placeholders) => posedSlots(doc, anim.timelines, t, raw, sliders, placeholders),
5202
5314
  drawOrder: anim.timelines.drawOrder,
5203
5315
  at: t,
@@ -5208,20 +5320,30 @@ function stepWalk(track, dt, first = false) {
5208
5320
  return pose;
5209
5321
  }
5210
5322
  function poseStepFrom(doc, plant, resolve, shape, mode, ctx, first, trackTime, animationTime, posers) {
5323
+ const stage = plant.stage;
5211
5324
  const sliders = [];
5212
5325
  const world = posers.world(sliders);
5213
5326
  if (first) ctx.phase = "update";
5327
+ if (stage !== void 0) stage.begin("readout");
5214
5328
  const placeholders = /* @__PURE__ */ new Map();
5215
5329
  const slots = posers.slots(sliders, placeholders);
5216
5330
  if (slots.conflicts.length > 0) throw new CoreInputError(`the raw walk leaves the slots out: ${slots.conflicts.join("; ")}`);
5217
5331
  const evalOrder = plant.drawOrder ?? drawOrderAt;
5218
- let order = evalOrder(doc.slots.length, posers.drawOrder, posers.at) ?? doc.slots.map((_s, k) => k);
5332
+ const baseOrder = evalOrder(doc.slots.length, posers.drawOrder, posers.at) ?? doc.slots.map((_s, k) => k);
5333
+ let order = baseOrder;
5219
5334
  for (const app of sliders) order = evalOrder(doc.slots.length, app.timelines.drawOrder, app.at) ?? order;
5220
5335
  const drawOrder = order.map((k) => doc.slots[k].name);
5336
+ if (stage !== void 0) {
5337
+ stage.end("readout");
5338
+ stage.begin("timeline");
5339
+ }
5221
5340
  const states = attachmentStates(doc, resolve, placeholders, posers.sample, sliders, plant, posers.mixed);
5341
+ if (stage !== void 0) stage.end("timeline");
5222
5342
  if (states.why.length > 0) throw new CoreInputError(`the raw walk leaves the attachments out: ${states.why.join("; ")}`);
5343
+ if (stage !== void 0) stage.begin("readout");
5223
5344
  const rank = new Map(drawOrder.map((n, r) => [n, r]));
5224
5345
  const shown = [...states.shown].sort((a, b) => (rank.get(a.slot) ?? 0) - (rank.get(b.slot) ?? 0));
5346
+ if (stage !== void 0) stage.end("readout");
5225
5347
  const events = () => posers.events().map((e) => [e[0], finite(e[1], "event time"), e[2], finite(e[3], "event float"), e[4]]);
5226
5348
  return shape.step(doc, { trackTime, animationTime, world, slots: slots.rows, drawOrder, shown, events }, plant, mode);
5227
5349
  }
@@ -5474,7 +5596,8 @@ function trackComplete(e) {
5474
5596
  return e.trackTime;
5475
5597
  }
5476
5598
  const signum = (v) => v > 0 ? 1 : v < 0 ? -1 : 0;
5477
- const clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
5599
+ const clamp01$1 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
5600
+ const NO_DARK = [0, 0, 0];
5478
5601
  const ATTACH_NONE = 0;
5479
5602
  const ATTACH_SETUP = 1;
5480
5603
  const ATTACH_RETAIN = 2;
@@ -5486,12 +5609,15 @@ class Pose {
5486
5609
  this.resolved = /* @__PURE__ */ new Map();
5487
5610
  this.bones = [...doc.bones];
5488
5611
  this.active = activeBones(doc);
5489
- const colour = plant.colour ?? readColour;
5612
+ const stated = viewColours(doc);
5613
+ this.stated = stated;
5614
+ const colour = plant.colour ?? stated;
5490
5615
  const gate = plant.slotTimelines ?? slotTimelinesApply;
5491
5616
  this.live = doc.slots.map((s) => gate(doc, s, this.active));
5492
5617
  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 };
5618
+ const setupDark = slot.dark === void 0 ? null : stated(slot.dark);
5619
+ const setupLight = slot.color === void 0 ? WHITE : colour(slot.color);
5620
+ 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
5621
  });
5496
5622
  }
5497
5623
  /** Bone `i`'s locals to write: the setup bone copied on its first write, as `posedBones` copies a keyed one. */
@@ -6068,11 +6194,10 @@ class Mixer {
6068
6194
  const s = pose.slots[tl.slot];
6069
6195
  const l = s.light;
6070
6196
  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]];
6197
+ const sl = slot.color === void 0 ? WHITE : (this.plant.colour ?? pose.stated)(slot.color);
6198
+ const sd = slot.dark === void 0 ? NO_DARK : pose.stated(slot.dark);
6074
6199
  const clampLight = () => {
6075
- for (let k = 0; k < 4; k++) l[k] = clamp01(l[k]);
6200
+ for (let k = 0; k < 4; k++) l[k] = clamp01$1(l[k]);
6076
6201
  };
6077
6202
  const v = this.values(tl.keys, time);
6078
6203
  const channels = SLOT_COLOUR_TIMELINES[tl.kind].channels.length;
@@ -6100,7 +6225,7 @@ class Mixer {
6100
6225
  }
6101
6226
  const rgb = [v[0], v[1], v[2]];
6102
6227
  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]);
6228
+ for (let k = 0; k < 3; k++) l[k] = clamp01$1(rgb[k]);
6104
6229
  return;
6105
6230
  }
6106
6231
  case "alpha": {
@@ -6111,7 +6236,7 @@ class Mixer {
6111
6236
  }
6112
6237
  let a = v[0];
6113
6238
  if (alpha !== 1) a = from === SETUP ? sl[3] + (a - sl[3]) * alpha : l[3] + (a - l[3]) * alpha;
6114
- l[3] = clamp01(a);
6239
+ l[3] = clamp01$1(a);
6115
6240
  return;
6116
6241
  }
6117
6242
  case "rgba2":
@@ -6149,9 +6274,9 @@ class Mixer {
6149
6274
  dark[k] = from === SETUP ? sd[k] + (dark[k] - sd[k]) * alpha : d[k] + (dark[k] - d[k]) * alpha;
6150
6275
  }
6151
6276
  }
6152
- for (let k = 0; k < 3; k++) l[k] = clamp01(light[k]);
6277
+ for (let k = 0; k < 3; k++) l[k] = clamp01$1(light[k]);
6153
6278
  }
6154
- for (let k = 0; k < 3; k++) d[k] = clamp01(dark[k]);
6279
+ for (let k = 0; k < 3; k++) d[k] = clamp01$1(dark[k]);
6155
6280
  return;
6156
6281
  }
6157
6282
  }
@@ -6382,7 +6507,10 @@ class Tracks {
6382
6507
  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
6508
  return this.guard(`step ${n}`, () => {
6384
6509
  this.time += dt;
6510
+ const stage = this.plant.stage;
6511
+ if (stage !== void 0) stage.begin("timeline");
6385
6512
  this.mixer.update(dt);
6513
+ if (stage !== void 0) stage.end("timeline");
6386
6514
  const pose = this.pose(dt, false);
6387
6515
  this.steps = n;
6388
6516
  return pose;
@@ -6403,6 +6531,8 @@ class Tracks {
6403
6531
  const ctx = path.ctx;
6404
6532
  const before = ctx.time;
6405
6533
  ctx.time += this.clock === void 0 ? dt : this.clock(dt);
6534
+ const stage = this.plant.stage;
6535
+ if (stage !== void 0) stage.begin("timeline");
6406
6536
  const pose = new Pose(doc, this.plant);
6407
6537
  const animationTime = this.mixer.apply(pose);
6408
6538
  const entry = this.mixer.tracks[0] ?? null;
@@ -6411,6 +6541,7 @@ class Tracks {
6411
6541
  context: { trackTime: entry?.trackTime ?? null, animation: entry?.anim.name ?? null, globalTime: this.time }
6412
6542
  };
6413
6543
  const mixed = new Map(doc.slots.map((s, k) => [s.name, { deform: pose.slots[k].deform, seen: pose.slots[k].seen }]));
6544
+ if (stage !== void 0) stage.end("timeline");
6414
6545
  return path.step(first, this.time, animationTime, {
6415
6546
  world: (sliders) => steppedLocalsWorld(doc, pose.bones, path.raw, sliders, { ctx, before, adjustment }),
6416
6547
  slots: (sliders, placeholders) => slotRowsFrom(doc, path.raw, sliders, placeholders, (_slot, _live) => {
@@ -6737,6 +6868,15 @@ async function unpackRegions(pages, pageImages, options = {}) {
6737
6868
  function revokeRegions(images) {
6738
6869
  for (const image of images.values()) revokeOwned(image.url);
6739
6870
  }
6871
+ const triangleReadSources = /* @__PURE__ */ new WeakMap();
6872
+ function triangleReadSource(source) {
6873
+ let kept = triangleReadSources.get(source);
6874
+ if (kept === void 0) {
6875
+ kept = Object.isFrozen(source) ? Array.from(source) : source;
6876
+ triangleReadSources.set(source, kept);
6877
+ }
6878
+ return kept;
6879
+ }
6740
6880
  const BLEND_FACTORS = {
6741
6881
  Normal: [
6742
6882
  1,
@@ -6938,7 +7078,8 @@ class MeshGlBlitter {
6938
7078
  const data = this.vertexData;
6939
7079
  let f = 0;
6940
7080
  for (let i = 0; i < jobs.length; i++) {
6941
- const { vertices, uvs, triangles, ratio } = jobs[i];
7081
+ const { vertices, uvs, ratio } = jobs[i];
7082
+ const triangles = triangleReadSource(jobs[i].triangles);
6942
7083
  const ox = packX[i];
6943
7084
  const oy = packY[i];
6944
7085
  for (let t = 0; t < triangles.length; t++) {
@@ -7461,7 +7602,8 @@ class DomRenderer {
7461
7602
  * clipping attachment, an inactive bone, a kind the DOM tier does not draw —
7462
7603
  * are hidden, their elements kept for when they draw again.
7463
7604
  */
7464
- render(frame2) {
7605
+ render(input) {
7606
+ const frame2 = renderFrameOf(input);
7465
7607
  this.clipCount = 0;
7466
7608
  this.clipSkipCount = 0;
7467
7609
  this.clipWriteCount = 0;
@@ -7703,6 +7845,7 @@ class DomRenderer {
7703
7845
  ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
7704
7846
  const uw = page.width;
7705
7847
  const uh = page.height;
7848
+ triangles = triangleReadSource(triangles);
7706
7849
  for (let t = 0; t < triangles.length; t += 3) {
7707
7850
  const i0 = triangles[t] * 2;
7708
7851
  const i1 = triangles[t + 1] * 2;
@@ -8215,7 +8358,8 @@ class CanvasRenderer {
8215
8358
  this.growTo(box.x, -(box.y + box.height), box.x + box.width, -box.y);
8216
8359
  }
8217
8360
  /** Draws one frame. */
8218
- render(frame2) {
8361
+ render(input) {
8362
+ const frame2 = renderFrameOf(input);
8219
8363
  this.drawCalls = 0;
8220
8364
  this.blendFlushes = 0;
8221
8365
  this.trianglesDrawn = 0;
@@ -8444,7 +8588,8 @@ class CanvasRenderer {
8444
8588
  if (!page) continue;
8445
8589
  const piece = this.piecesOf(slot, clips);
8446
8590
  if (!piece) continue;
8447
- const { vertices, uvs, triangles } = piece;
8591
+ const { vertices, uvs } = piece;
8592
+ const triangles = triangleReadSource(piece.triangles);
8448
8593
  const count = triangles.length;
8449
8594
  if (count === 0) continue;
8450
8595
  if (!this.glPages.has(page) && blitter) {
@@ -8519,7 +8664,8 @@ class CanvasRenderer {
8519
8664
  }
8520
8665
  ctx.globalAlpha = Math.max(0, Math.min(1, slot.tint[3]));
8521
8666
  this.drawCalls++;
8522
- const { vertices, uvs, triangles } = piece;
8667
+ const { vertices, uvs } = piece;
8668
+ const triangles = triangleReadSource(piece.triangles);
8523
8669
  if (slot.kind === "region" && vertices === slot.corners) {
8524
8670
  const c = vertices;
8525
8671
  const sx0 = uvs[2] * uw, sy0 = uvs[3] * uh;
@@ -8832,6 +8978,1460 @@ async function loadModel(options) {
8832
8978
  }
8833
8979
  };
8834
8980
  }
8981
+ const CURVE_LINEAR = 0;
8982
+ const CURVE_STEPPED = 1;
8983
+ const CURVE_BEZIER = 2;
8984
+ const POLYLINE = 2 * (BEZIER_PIECES + 1);
8985
+ function compileCurve(keys, channels) {
8986
+ const n = keys.length;
8987
+ const times2 = new Float64Array(n);
8988
+ const values2 = new Float64Array(n * channels);
8989
+ const kinds = new Uint8Array(n);
8990
+ const polylines = new Float64Array(n * channels * POLYLINE);
8991
+ for (let i = 0; i < n; i++) {
8992
+ const a = keys[i];
8993
+ times2[i] = a.time;
8994
+ for (let c = 0; c < channels; c++) values2[i * channels + c] = a.values[c];
8995
+ const curve = a.curve;
8996
+ kinds[i] = curve === "linear" ? CURVE_LINEAR : curve === "stepped" ? CURVE_STEPPED : CURVE_BEZIER;
8997
+ if (typeof curve === "string" || i + 1 >= n) continue;
8998
+ const b = keys[i + 1];
8999
+ for (let c = 0; c < channels; c++) {
9000
+ 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]);
9001
+ const o = (i * channels + c) * POLYLINE;
9002
+ polylines[o] = a.time;
9003
+ polylines[o + 1] = a.values[c];
9004
+ for (let q = 0; q < inner.length; q++) polylines[o + 2 + q] = inner[q];
9005
+ polylines[o + POLYLINE - 2] = b.time;
9006
+ polylines[o + POLYLINE - 1] = b.values[c];
9007
+ }
9008
+ }
9009
+ return { keys: n, channels, times: times2, values: values2, kinds, polylines };
9010
+ }
9011
+ function keyAt(curve, cursors, slot, t) {
9012
+ const times2 = curve.times;
9013
+ let i = cursors[slot];
9014
+ while (i >= 0 && times2[i] > t) i--;
9015
+ while (i + 1 < curve.keys && times2[i + 1] <= t) i++;
9016
+ cursors[slot] = i;
9017
+ return i;
9018
+ }
9019
+ function channelValue(curve, i, c, t) {
9020
+ const ch = curve.channels;
9021
+ const values2 = curve.values;
9022
+ const v0 = values2[i * ch + c];
9023
+ if (i + 1 >= curve.keys) return v0;
9024
+ const kind = curve.kinds[i];
9025
+ if (kind === CURVE_STEPPED) return v0;
9026
+ if (kind === CURVE_LINEAR) {
9027
+ const t0 = curve.times[i];
9028
+ return v0 + (t - t0) / (curve.times[i + 1] - t0) * (values2[(i + 1) * ch + c] - v0);
9029
+ }
9030
+ const p = curve.polylines;
9031
+ const o = (i * ch + c) * POLYLINE;
9032
+ let q = 2;
9033
+ while (q < POLYLINE - 2 && p[o + q] < t) q += 2;
9034
+ const x0 = p[o + q - 2];
9035
+ const y0 = p[o + q - 1];
9036
+ return y0 + (t - x0) / (p[o + q] - x0) * (p[o + q + 1] - y0);
9037
+ }
9038
+ function compileDeformCurve(keys) {
9039
+ const curve = compileCurve(keys.map((k) => ({ time: k.time, values: [0], curve: "linear", stated: { time: k.stated, values: [0] } })), 1);
9040
+ for (let i = 0; i < keys.length; i++) {
9041
+ const a = keys[i];
9042
+ curve.kinds[i] = a.curve === "linear" ? CURVE_LINEAR : a.curve === "stepped" ? CURVE_STEPPED : CURVE_BEZIER;
9043
+ if (typeof a.curve === "string" || i + 1 === keys.length) continue;
9044
+ const b = keys[i + 1];
9045
+ const c = a.curve;
9046
+ const inner = bezierPolyline(a.stated, 0, c[0], c[1], c[2], c[3], b.stated, DEFORM_CURVE_END);
9047
+ const o = i * POLYLINE;
9048
+ curve.polylines[o] = a.time;
9049
+ curve.polylines[o + 1] = 0;
9050
+ curve.polylines.set(inner, o + 2);
9051
+ curve.polylines[o + POLYLINE - 2] = b.time;
9052
+ curve.polylines[o + POLYLINE - 1] = 1;
9053
+ }
9054
+ return curve;
9055
+ }
9056
+ function deformFraction(curve, i, t) {
9057
+ if (curve.kinds[i] === CURVE_STEPPED) return 0;
9058
+ if (curve.kinds[i] === CURVE_LINEAR) return (t - curve.times[i]) / (curve.times[i + 1] - curve.times[i]);
9059
+ const p = curve.polylines;
9060
+ const o = i * POLYLINE;
9061
+ let q = 2;
9062
+ while (q < POLYLINE - 2 && p[o + q] < t) q += 2;
9063
+ const x0 = p[o + q - 2], y0 = p[o + q - 1], x1 = p[o + q], y1 = p[o + q + 1];
9064
+ if (q === 2) return y1 * (t - x0) / (x1 - x0);
9065
+ if (q === POLYLINE - 2) return y0 + (1 - y0) * (t - x0) / (x1 - x0);
9066
+ return y0 + (t - x0) / (x1 - x0) * (y1 - y0);
9067
+ }
9068
+ const RUNTIME_STAGE = 2;
9069
+ function missingText(m) {
9070
+ return `${m.feature}: ${m.detail}${m.animation === null ? "" : ` (animation "${m.animation}")`}`;
9071
+ }
9072
+ class RuntimeRefusal extends Error {
9073
+ constructor(missing) {
9074
+ super(`not posed by the runtime poser (stage ${RUNTIME_STAGE}), ${missing.length} construct(s) missing: ${missing.map(missingText).join("; ")}`);
9075
+ this.missing = missing;
9076
+ }
9077
+ }
9078
+ class RuntimeInputError extends Error {
9079
+ }
9080
+ const L_X = 0;
9081
+ const L_Y = 1;
9082
+ const L_ROTATION = 2;
9083
+ const L_SCALE_X = 3;
9084
+ const L_SCALE_Y = 4;
9085
+ const L_SHEAR_X = 5;
9086
+ const L_SHEAR_Y = 6;
9087
+ const LOCALS = 7;
9088
+ const W_A = 0;
9089
+ const W_B = 1;
9090
+ const W_C = 2;
9091
+ const W_D = 3;
9092
+ const W_X = 4;
9093
+ const W_Y = 5;
9094
+ const WORLDS = 6;
9095
+ const MODE_NORMAL = 0;
9096
+ const MODE_ONLY_TRANSLATION = 1;
9097
+ const MODE_NO_ROTATION_OR_REFLECTION = 2;
9098
+ const MODE_NO_SCALE = 3;
9099
+ const MODE_NO_SCALE_OR_REFLECTION = 4;
9100
+ 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 };
9101
+ const DRAW_NONE = 0;
9102
+ const DRAW_REGION = 1;
9103
+ const DRAW_MESH = 2;
9104
+ const DRAW_WEIGHTED = 3;
9105
+ const SCALE_Y_NONE = 0;
9106
+ const SCALE_Y_UNIFORM = 1;
9107
+ const SCALE_Y_VOLUME = 2;
9108
+ const IK_BEND_POSITIVE = 1;
9109
+ const IK_COMPRESS = 2;
9110
+ const IK_STRETCH = 4;
9111
+ const RAD = RUNTIME_PI / 180;
9112
+ const BONE_KINDS = {
9113
+ rotate: [[L_ROTATION, 0]],
9114
+ translate: [[L_X, 0], [L_Y, 0]],
9115
+ translatex: [[L_X, 0]],
9116
+ translatey: [[L_Y, 0]],
9117
+ scale: [[L_SCALE_X, 1], [L_SCALE_Y, 1]],
9118
+ scalex: [[L_SCALE_X, 1]],
9119
+ scaley: [[L_SCALE_Y, 1]],
9120
+ shear: [[L_SHEAR_X, 0], [L_SHEAR_Y, 0]],
9121
+ shearx: [[L_SHEAR_X, 0]],
9122
+ sheary: [[L_SHEAR_Y, 0]]
9123
+ };
9124
+ const COLOUR_CHANNELS = { rgba: [0, 1, 2, 3], rgb: [0, 1, 2], alpha: [3] };
9125
+ class Refusals {
9126
+ constructor(admitted) {
9127
+ this.admitted = admitted;
9128
+ this.model = [];
9129
+ this.byAnimation = /* @__PURE__ */ new Map();
9130
+ }
9131
+ /** Records the construct unless a plant admits it. The caller poses the construct as absent either way. */
9132
+ note(feature, detail, animation = null) {
9133
+ if (this.admitted.has(feature)) return;
9134
+ const m = { feature, detail, animation };
9135
+ if (animation === null) this.model.push(m);
9136
+ else {
9137
+ const list = this.byAnimation.get(animation) ?? [];
9138
+ list.push(m);
9139
+ this.byAnimation.set(animation, list);
9140
+ }
9141
+ }
9142
+ }
9143
+ function compileRuntime(doc, options = {}, plant = {}) {
9144
+ const refusals = new Refusals(new Set(plant.admit ?? []));
9145
+ const ignoreEvents = (options.ignore ?? []).includes("events");
9146
+ const absentWhy = constraintsAbsentWhy(doc);
9147
+ if (absentWhy !== null) refusals.note("oracle-absent", `the oracle leaves the bones out: ${absentWhy}`);
9148
+ const nb = doc.bones.length;
9149
+ const boneIndex2 = new Map(doc.bones.map((b, i) => [b.name, i]));
9150
+ const indexOf = (name) => {
9151
+ const i = boneIndex2.get(name);
9152
+ if (i === void 0) throw new RuntimeInputError(`bone "${name}" is not a bone of this document (readModel refuses it first)`);
9153
+ return i;
9154
+ };
9155
+ const active = activeBones(doc);
9156
+ const parents = new Int32Array(nb);
9157
+ const modes = new Uint8Array(nb);
9158
+ const activeFlags = new Uint8Array(nb);
9159
+ const lengths = new Float64Array(nb);
9160
+ const setup = new Float64Array(nb * LOCALS);
9161
+ doc.bones.forEach((b, i) => {
9162
+ parents[i] = b.parent === void 0 ? -1 : indexOf(b.parent);
9163
+ if (parents[i] >= i) throw new RuntimeInputError(`bone "${b.name}" is listed before its parent (readModel refuses it first)`);
9164
+ const mode = b.inheritMode === void 0 ? "normal" : foldInheritMode(b.inheritMode);
9165
+ if (mode === null) throw new RuntimeInputError(`bone "${b.name}": inheritMode ${JSON.stringify(b.inheritMode)} is no mode (readModel refuses it first)`);
9166
+ modes[i] = MODE_CODES[mode];
9167
+ activeFlags[i] = active.has(b.name) ? 1 : 0;
9168
+ lengths[i] = b.length ?? 0;
9169
+ const o = i * LOCALS;
9170
+ setup[o + L_X] = b.x ?? 0;
9171
+ setup[o + L_Y] = b.y ?? 0;
9172
+ setup[o + L_ROTATION] = b.rotation ?? 0;
9173
+ setup[o + L_SCALE_X] = b.scaleX ?? 1;
9174
+ setup[o + L_SCALE_Y] = b.scaleY ?? 1;
9175
+ setup[o + L_SHEAR_X] = b.shearX ?? 0;
9176
+ setup[o + L_SHEAR_Y] = b.shearY ?? 0;
9177
+ });
9178
+ if (plant.setupNudge !== void 0) setup[indexOf(plant.setupNudge.bone) * LOCALS + plant.setupNudge.field] += plant.setupNudge.delta;
9179
+ const lists = doc.bones.map(() => []);
9180
+ for (let k = 0; k < nb; k++) for (let at = k; at >= 0; at = parents[at]) lists[at].push(k);
9181
+ const subtreeStart = new Int32Array(nb + 1);
9182
+ const flat = [];
9183
+ for (let k = 0; k < nb; k++) {
9184
+ subtreeStart[k] = flat.length;
9185
+ for (const x of lists[k]) flat.push(x);
9186
+ }
9187
+ subtreeStart[nb] = flat.length;
9188
+ const constraints = [];
9189
+ 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}"`);
9190
+ for (const r of constraintRecords(doc)) {
9191
+ if (r.kind !== "ik" && r.kind !== "transform") continue;
9192
+ const applied = constraintInactiveWhy(r, active) === null;
9193
+ const bones = Int32Array.from(r.bones.map(indexOf));
9194
+ 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`);
9195
+ const index = constraints.length;
9196
+ if (r.kind === "ik") constraints.push(compileIk(r, index, applied, bones, indexOf));
9197
+ else {
9198
+ 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 ")}`);
9199
+ if (r.additive) refusals.note("additive-transform", `transform constraint "${r.name}" is additive`);
9200
+ const t = compileTransform(r, index, applied, bones, indexOf);
9201
+ if (applied) {
9202
+ for (let k = 0; k < bones.length; k++) {
9203
+ const m = modes[bones[k]];
9204
+ 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`);
9205
+ }
9206
+ }
9207
+ constraints.push(t);
9208
+ }
9209
+ }
9210
+ const attachments = [];
9211
+ let vertexCount = 0;
9212
+ let deformCount = 0;
9213
+ const sourceOf = sourceOfDoc(doc);
9214
+ const attachmentView = plant.attachmentSkin === void 0 ? doc : underSkin(doc, plant.attachmentSkin);
9215
+ const resolvedBySlot = doc.slots.map(() => /* @__PURE__ */ new Map());
9216
+ const resolve = (si, placeholder, animation) => {
9217
+ const cache = resolvedBySlot[si];
9218
+ let kept = cache.get(placeholder);
9219
+ if (kept === void 0) {
9220
+ kept = resolveRecord(si, placeholder);
9221
+ cache.set(placeholder, kept);
9222
+ }
9223
+ if (kept.missing !== null) refusals.note(kept.missing[0], kept.missing[1], animation);
9224
+ return kept.index;
9225
+ };
9226
+ const resolveRecord = (si, placeholder) => {
9227
+ const slot = doc.slots[si];
9228
+ const shown = placeholder === null ? null : shownAttachment(attachmentView, slot, placeholder);
9229
+ if (shown === null) return { index: -1, missing: null };
9230
+ if ("conflict" in shown) return { index: -1, missing: ["skin-conflict", `slot "${slot.name}" placeholder "${placeholder}" is filled by skins that disagree`] };
9231
+ const row = shownRow(shown);
9232
+ const g = shown.record.geometry;
9233
+ const at = { timeline: timelineIdentity(slot.name, shown), deformOffset: deformCount, 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 };
9234
+ let made = null;
9235
+ let missing = null;
9236
+ if (g?.kind === "clipping") missing = ["clipping", `slot "${slot.name}" shows clipping attachment "${row.name}"`];
9237
+ else if (g?.kind === "region") {
9238
+ const r = g.region;
9239
+ if (r.atlas === null) missing = ["null-atlas-region", `slot "${slot.name}" shows region "${row.name}", whose atlas rectangle the model states as null`];
9240
+ else {
9241
+ const W = r.width;
9242
+ const H = r.height;
9243
+ const sx = r.scaleX ?? 1;
9244
+ const sy = r.scaleY ?? 1;
9245
+ const rect = r.atlas;
9246
+ const kx = W / rect.originalWidth * sx;
9247
+ const ky = H / rect.originalHeight * sy;
9248
+ const x1 = -W / 2 * sx + rect.offsetX * kx;
9249
+ const y1 = -H / 2 * sy + rect.offsetY * ky;
9250
+ const x2 = x1 + rect.width * kx;
9251
+ const y2 = y1 + rect.height * ky;
9252
+ const angle = (r.rotation ?? 0) * RAD;
9253
+ const cos = Math.cos(angle);
9254
+ const sin = Math.sin(angle);
9255
+ const x = r.x ?? 0;
9256
+ const y = r.y ?? 0;
9257
+ const local = new Float64Array(8);
9258
+ const corners = [x1, y1, x1, y2, x2, y2, x2, y1];
9259
+ for (let k = 0; k < 8; k += 2) {
9260
+ const px = corners[k];
9261
+ const py = corners[k + 1];
9262
+ local[k] = px * cos + x - py * sin;
9263
+ local[k + 1] = py * cos + y + px * sin;
9264
+ }
9265
+ made = vertexAttachment(at, DRAW_REGION, local, 8);
9266
+ }
9267
+ } else if (g?.kind === "mesh" || g?.kind === "linkedmesh") {
9268
+ const source = g.kind === "mesh" ? g.vertices : sourceOf(g.skin, g.slot, g.source);
9269
+ if (typeof source === "string") throw new RuntimeInputError(`slot "${slot.name}": ${source} (readModel refuses it first)`);
9270
+ made = meshAttachment(at, source, indexOf, plant.bindingIndex, nb);
9271
+ }
9272
+ if (made === null) made = vertexAttachment(at, DRAW_NONE, new Float64Array(0), 0);
9273
+ attachments.push(made);
9274
+ vertexCount += made.count;
9275
+ deformCount += made.deformCount;
9276
+ return { index: attachments.length - 1, missing };
9277
+ };
9278
+ const slots = doc.slots.map((slot, si) => {
9279
+ const bone2 = indexOf(slot.bone);
9280
+ const light = slot.color === void 0 ? [1, 1, 1, 1] : readColour(slot.color);
9281
+ return {
9282
+ name: slot.name,
9283
+ bone: bone2,
9284
+ // A slot on a bone the view leaves inactive is not animated (`slotTimelinesApply` in src/core/skins.ts).
9285
+ live: active.has(slot.bone),
9286
+ setupAttachment: resolve(si, slot.setup, null),
9287
+ light: Float64Array.from(light),
9288
+ dark: slot.dark === void 0 ? null : Float64Array.from(readColour(slot.dark).slice(0, 3)),
9289
+ blend: readBlend(slot)
9290
+ };
9291
+ });
9292
+ const slotIndex = new Map(doc.slots.map((s, i) => [s.name, i]));
9293
+ const animations = /* @__PURE__ */ new Map();
9294
+ const refused = [];
9295
+ let cursorMax = 0;
9296
+ for (const anim of doc.animations) {
9297
+ const compiled = compileAnimation(anim, refusals, ignoreEvents, boneIndex2, slotIndex, slots, constraints, doc, (si, ph) => resolve(si, ph, anim.name));
9298
+ const missing = refusals.byAnimation.get(anim.name);
9299
+ if (missing !== void 0 && missing.length > 0) refused.push({ animation: anim.name, missing });
9300
+ else {
9301
+ animations.set(anim.name, compiled);
9302
+ cursorMax = Math.max(cursorMax, compiled.cursors);
9303
+ }
9304
+ }
9305
+ if (refusals.model.length > 0) throw new RuntimeRefusal(refusals.model);
9306
+ const names = doc.animations.map((a) => a.name);
9307
+ return {
9308
+ omitDeform: plant.omitDeform === true,
9309
+ deformAfterWeights: plant.deformAfterWeights === true,
9310
+ deformCount,
9311
+ eventWrapOffByOne: plant.eventWrapOffByOne === true,
9312
+ restoreEventsFromStart: plant.restoreEventsFromStart === true,
9313
+ doc,
9314
+ boneNames: doc.bones.map((b) => b.name),
9315
+ parents,
9316
+ modes,
9317
+ active: activeFlags,
9318
+ lengths,
9319
+ setup,
9320
+ subtreeStart,
9321
+ subtrees: Int32Array.from(flat),
9322
+ slots,
9323
+ attachments,
9324
+ vertexCount,
9325
+ constraints,
9326
+ animations,
9327
+ refused,
9328
+ cursors: cursorMax,
9329
+ scratch: plant.scratchByBones === true ? Math.max(1, nb) : Math.max(1, nb, ...constraints.map((c) => c.bones.length)),
9330
+ animation(name) {
9331
+ const found = animations.get(name);
9332
+ if (found !== void 0) return found;
9333
+ const no = refused.find((r) => r.animation === name);
9334
+ if (no !== void 0) throw new RuntimeRefusal(no.missing);
9335
+ throw new RuntimeInputError(`animation ${JSON.stringify(name)} is not one of this document's [${names.join(", ")}]`);
9336
+ }
9337
+ };
9338
+ }
9339
+ const NO_INTS = new Int32Array(0);
9340
+ const NO_NUMBERS = new Float64Array(0);
9341
+ function vertexAttachment(at, draw, local, count) {
9342
+ return { ...at, draw, local, count, deformCount: draw === DRAW_MESH ? count : 0, bindingCounts: NO_INTS, bindingBones: NO_INTS, bindingX: NO_NUMBERS, bindingY: NO_NUMBERS, bindingWeights: NO_NUMBERS };
9343
+ }
9344
+ function meshAttachment(at, vertices, indexOf, rewrite, bones) {
9345
+ if (!vertices.weighted) {
9346
+ const n = vertices.xy.length - vertices.xy.length % 2;
9347
+ const local = new Float64Array(n);
9348
+ for (let i = 0; i < n; i++) local[i] = Math.fround(vertices.xy[i]);
9349
+ return vertexAttachment(at, DRAW_MESH, local, n);
9350
+ }
9351
+ const counts = [];
9352
+ const ids = [];
9353
+ const xs = [];
9354
+ const ys = [];
9355
+ const ws = [];
9356
+ for (const influences of vertices.bindings) {
9357
+ counts.push(influences.length);
9358
+ for (const b of influences) {
9359
+ const i = indexOf(b.bone);
9360
+ ids.push(rewrite === void 0 ? i : rewrite(i, bones));
9361
+ xs.push(Math.fround(b.x));
9362
+ ys.push(Math.fround(b.y));
9363
+ ws.push(Math.fround(b.weight));
9364
+ }
9365
+ }
9366
+ return { ...at, deformCount: ids.length * 2, 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) };
9367
+ }
9368
+ function compileIk(r, index, applied, bones, indexOf) {
9369
+ return {
9370
+ kind: "ik",
9371
+ name: r.name,
9372
+ index,
9373
+ applied,
9374
+ bones,
9375
+ target: indexOf(r.target),
9376
+ scaleY: r.scaleY === "uniform" ? SCALE_Y_UNIFORM : r.scaleY === "volume" ? SCALE_Y_VOLUME : SCALE_Y_NONE,
9377
+ mix: r.mix,
9378
+ softness: r.softness,
9379
+ flags: ikFlags(r.bendPositive, r.compress, r.stretch)
9380
+ };
9381
+ }
9382
+ function ikFlags(bendPositive, compress, stretch) {
9383
+ return (bendPositive ? IK_BEND_POSITIVE : 0) | (compress ? IK_COMPRESS : 0) | (stretch ? IK_STRETCH : 0);
9384
+ }
9385
+ function compileTransform(r, index, applied, bones, indexOf) {
9386
+ const fromProperty = [];
9387
+ const fromOffset = [];
9388
+ const toStart = [];
9389
+ const toEnd = [];
9390
+ const toProperty = [];
9391
+ const toOffset = [];
9392
+ const toScale = [];
9393
+ const toMax = [];
9394
+ for (const from of r.properties) {
9395
+ fromProperty.push(TRANSFORM_PROPERTIES.indexOf(from.property));
9396
+ fromOffset.push(from.offset);
9397
+ toStart.push(toProperty.length);
9398
+ for (const to of from.to) {
9399
+ toProperty.push(TRANSFORM_PROPERTIES.indexOf(to.property));
9400
+ toOffset.push(to.offset);
9401
+ toScale.push(to.scale);
9402
+ toMax.push(to.max);
9403
+ }
9404
+ toEnd.push(toProperty.length);
9405
+ }
9406
+ return {
9407
+ kind: "transform",
9408
+ name: r.name,
9409
+ index,
9410
+ applied,
9411
+ bones,
9412
+ source: indexOf(r.source),
9413
+ fromProperty: Int32Array.from(fromProperty),
9414
+ fromOffset: Float64Array.from(fromOffset),
9415
+ toStart: Int32Array.from(toStart),
9416
+ toEnd: Int32Array.from(toEnd),
9417
+ toProperty: Int32Array.from(toProperty),
9418
+ toOffset: Float64Array.from(toOffset),
9419
+ toScale: Float64Array.from(toScale),
9420
+ toMax: Float64Array.from(toMax),
9421
+ offsets: Float64Array.from(TRANSFORM_PROPERTIES.map((p) => r.offsets[p])),
9422
+ mixes: Float64Array.from(TRANSFORM_PROPERTIES.map((p) => r.mixes[p])),
9423
+ clamp: r.clamp
9424
+ };
9425
+ }
9426
+ function compileAnimation(anim, refusals, ignoreEvents, boneIndex2, slotIndex, slots, constraints, doc, resolve) {
9427
+ const tl = anim.timelines;
9428
+ const name = anim.name;
9429
+ let cursors = 0;
9430
+ const deform = tl.attachments.filter((a) => a.deform !== null);
9431
+ const sequence = tl.attachments.filter((a) => a.sequence !== null);
9432
+ 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);
9433
+ if (tl.drawOrder.length > 0) refusals.note("draw-order-timeline", `${tl.drawOrder.length} draw-order key(s)`, name);
9434
+ const lastBy = (targets) => new Map(targets.map((t) => [t.name, t]));
9435
+ const bones = [];
9436
+ for (const target of lastBy(tl.bones).values()) {
9437
+ const bone2 = boneIndex2.get(target.name);
9438
+ if (bone2 === void 0) continue;
9439
+ for (const t of target.timelines) {
9440
+ if (t.kind === "inherit") {
9441
+ refusals.note("inherit-timeline", `bone "${target.name}" keys its inherit mode`, name);
9442
+ continue;
9443
+ }
9444
+ const spec = BONE_KINDS[t.kind];
9445
+ 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])) });
9446
+ }
9447
+ }
9448
+ const slotTimelines = [];
9449
+ const keyedSlots = [...lastBy(tl.slots).values()].flatMap((target) => {
9450
+ const si = slotIndex.get(target.name);
9451
+ return si === void 0 ? [] : [{ si, target }];
9452
+ }).sort((a, b) => a.si - b.si);
9453
+ for (const { si, target } of keyedSlots) {
9454
+ if (!slots[si].live) continue;
9455
+ for (const t of target.timelines) {
9456
+ if (t.kind === "attachment") {
9457
+ const ids = Int32Array.from(t.keys.map((k) => resolve(si, k.name ?? null)));
9458
+ slotTimelines.push({ slot: si, curve: compileCurve(t.keys, 0), cursor: cursors++, attachments: ids, channels: null });
9459
+ } else if (t.kind === "rgba2" || t.kind === "rgb2") {
9460
+ refusals.note("dark-colour-timeline", `slot "${target.name}" keys an ${t.kind} timeline`, name);
9461
+ } else {
9462
+ const channels = COLOUR_CHANNELS[t.kind];
9463
+ slotTimelines.push({ slot: si, curve: compileCurve(t.keys, channels.length), cursor: cursors++, attachments: null, channels: Int32Array.from(channels) });
9464
+ }
9465
+ }
9466
+ }
9467
+ const constraintTimelines = constraints.map(() => null);
9468
+ for (const c of constraints) {
9469
+ if (c.kind === "ik") {
9470
+ const keyed = anim.constraints.ik.find((x) => x.name === c.name);
9471
+ if (keyed === void 0) continue;
9472
+ 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))) };
9473
+ } else {
9474
+ const keyed = anim.constraints.transform.find((x) => x.name === c.name);
9475
+ if (keyed === void 0) continue;
9476
+ constraintTimelines[c.index] = { constraint: c.index, curve: compileCurve(keyed.keys, TRANSFORM_PROPERTIES.length), cursor: cursors++, flags: null };
9477
+ }
9478
+ }
9479
+ const deforms = deform.map((a) => {
9480
+ const g = doc.skins.find((s) => s.name === a.skin)?.attachments[a.slot]?.[a.attachment]?.geometry;
9481
+ if (g === void 0 || g.kind === "region" || g.kind === "linkedmesh") throw new RuntimeInputError(`deform "${a.skin}/${a.slot}/${a.attachment}" has no vertex geometry (readModel refuses it first)`);
9482
+ const keys = a.deform;
9483
+ const arrays = keys.map((k) => heldArray(g.vertices, k));
9484
+ return { identity: `${a.skin}/${a.slot}/${a.attachment}`, cursor: cursors++, curve: compileDeformCurve(keys), count: arrays[0].length, held: Float64Array.from(arrays.flat()) };
9485
+ });
9486
+ return { name, duration: tl.duration, deforms, 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 }))) };
9487
+ }
9488
+ const DEG = RUNTIME_DEG;
9489
+ const TWO_PI = 2 * RUNTIME_PI;
9490
+ const P_ROTATE = 0;
9491
+ const P_X = 1;
9492
+ const P_Y = 2;
9493
+ const P_SCALE_X = 3;
9494
+ const P_SCALE_Y = 4;
9495
+ function wrap180(r) {
9496
+ return r > 180 ? r - 360 : r <= -180 ? r + 360 : r;
9497
+ }
9498
+ function volumeScaleY(scaleY, s) {
9499
+ return scaleY / (s < 0.7 ? 0.25 + s * 0.642857 : s);
9500
+ }
9501
+ const clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
9502
+ class RuntimePose {
9503
+ constructor(model) {
9504
+ this.trackTime = 0;
9505
+ this.time = 0;
9506
+ this.fired = [];
9507
+ this.eventTime = -1;
9508
+ this.current = null;
9509
+ this.w = new Float64Array(WORLDS);
9510
+ this.model = model;
9511
+ const nb = model.boneNames.length;
9512
+ const ns = model.slots.length;
9513
+ const nc = model.constraints.length;
9514
+ this.locals = new Float64Array(nb * LOCALS);
9515
+ this.world = new Float64Array(nb * WORLDS);
9516
+ this.light = new Float64Array(ns * 4);
9517
+ this.shown = new Int32Array(ns);
9518
+ this.vertices = new Float64Array(model.vertexCount);
9519
+ this.deform = new Float64Array(model.deformCount);
9520
+ this.deformed = new Uint8Array(model.attachments.length);
9521
+ this.cursors = new Int32Array(Math.max(1, model.cursors)).fill(-1);
9522
+ this.ikMix = new Float64Array(nc);
9523
+ this.ikSoftness = new Float64Array(nc);
9524
+ this.ikFlags = new Uint8Array(nc);
9525
+ this.mixes = new Float64Array(nc * 6);
9526
+ this.changed = new Int32Array(model.scratch);
9527
+ this.inWorld = new Int32Array(model.scratch);
9528
+ this.changedMark = new Uint8Array(nb);
9529
+ this.inWorldMark = new Uint8Array(nb);
9530
+ this.below = new Uint8Array(nb);
9531
+ this.visit = new Uint8Array(nb);
9532
+ }
9533
+ /** Borrowed immutable event records, in delivery order, until the next apply/step. */
9534
+ get events() {
9535
+ return this.fired;
9536
+ }
9537
+ /** The animation this pose plays (a covered one: `model.animation` throws a `RuntimeRefusal` for a refused one), from track time 0. */
9538
+ setAnimation(name) {
9539
+ this.current = this.model.animation(name);
9540
+ this.cursors.fill(-1);
9541
+ this.trackTime = 0;
9542
+ this.time = 0;
9543
+ this.eventTime = -1;
9544
+ this.fired.length = 0;
9545
+ }
9546
+ /**
9547
+ * Restore one animation's two clocks without delivering keys already passed.
9548
+ * A skin/view replacement uses the old pose's trackTime and wrapped time.
9549
+ * Unlike setAnimation + apply, restoration never replays the initial interval.
9550
+ */
9551
+ restore(name, trackTime, animationTime) {
9552
+ if (!Number.isFinite(trackTime)) throw new RuntimeInputError(`restore: track time ${trackTime} is not finite`);
9553
+ if (!Number.isFinite(animationTime)) throw new RuntimeInputError(`restore: animation time ${animationTime} is not finite`);
9554
+ this.setAnimation(name);
9555
+ this.trackTime = trackTime;
9556
+ if (!this.model.restoreEventsFromStart) this.eventTime = animationTime;
9557
+ this.apply(animationTime);
9558
+ }
9559
+ /** The setup pose: every constraint applied with its own values, every slot as it is set up. */
9560
+ setupPose() {
9561
+ this.current = null;
9562
+ this.time = 0;
9563
+ this.eventTime = -1;
9564
+ this.fired.length = 0;
9565
+ this.locals.set(this.model.setup);
9566
+ this.hierarchy();
9567
+ this.constraintValues(null, 0);
9568
+ this.solveConstraints();
9569
+ this.slotValues(null, 0);
9570
+ this.deformValues(null, 0);
9571
+ }
9572
+ /** The animation posed at its time `t`, from the setup pose (the header). */
9573
+ apply(t) {
9574
+ const anim = this.current;
9575
+ if (anim === null) throw new RuntimeInputError("apply: no animation is set (setAnimation first)");
9576
+ if (!Number.isFinite(t)) throw new RuntimeInputError(`apply: the time ${t} is not finite`);
9577
+ this.collectEvents(anim, t);
9578
+ this.time = t;
9579
+ this.boneTimelines(anim, t);
9580
+ this.hierarchy();
9581
+ this.constraintValues(anim, t);
9582
+ this.solveConstraints();
9583
+ this.slotValues(anim, t);
9584
+ this.deformValues(anim, t);
9585
+ }
9586
+ /** 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). */
9587
+ step(dt) {
9588
+ const anim = this.current;
9589
+ if (anim === null) throw new RuntimeInputError("step: no animation is set (setAnimation first)");
9590
+ this.trackTime += dt;
9591
+ this.apply(anim.duration === 0 ? 0 : this.trackTime % anim.duration);
9592
+ }
9593
+ /** The oracle interval (last,t], tail then head on a wrap; never copies a payload. */
9594
+ collectEvents(anim, t) {
9595
+ const last = this.eventTime;
9596
+ const wrapped = t < last;
9597
+ const keys = anim.eventKeys;
9598
+ let count = 0;
9599
+ if (wrapped) {
9600
+ for (let i = 0; i < keys.length; i++) if (keys[i].time > last) this.fired[count++] = keys[i];
9601
+ }
9602
+ for (let i = 0; i < keys.length; i++) {
9603
+ const key = keys[i];
9604
+ if (key.time <= t && (wrapped ? !this.model.eventWrapOffByOne || key.time > 0 : key.time > last)) this.fired[count++] = key;
9605
+ }
9606
+ this.fired.length = count;
9607
+ this.eventTime = t;
9608
+ }
9609
+ // ---------------------------------------------------------------------------
9610
+ // the bones
9611
+ // ---------------------------------------------------------------------------
9612
+ /** 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. */
9613
+ boneTimelines(anim, t) {
9614
+ const L = this.locals;
9615
+ const S = this.model.setup;
9616
+ L.set(S);
9617
+ const timelines = anim.bones;
9618
+ for (let k = 0; k < timelines.length; k++) {
9619
+ const tl = timelines[k];
9620
+ const i = keyAt(tl.curve, this.cursors, tl.cursor, t);
9621
+ const o = tl.bone * LOCALS;
9622
+ const fields = tl.fields;
9623
+ for (let c = 0; c < fields.length; c++) {
9624
+ const at = o + fields[c];
9625
+ if (tl.multiply[c] === 1) L[at] = S[at] * (i < 0 ? 1 : channelValue(tl.curve, i, c, t));
9626
+ else L[at] = S[at] + (i < 0 ? 0 : channelValue(tl.curve, i, c, t));
9627
+ }
9628
+ }
9629
+ }
9630
+ /** Every bone's world transform from its locals, parents first (`worldTransforms`): an inactive bone as zeros. */
9631
+ hierarchy() {
9632
+ const nb = this.model.boneNames.length;
9633
+ for (let i = 0; i < nb; i++) this.poseBone(i, true);
9634
+ }
9635
+ /**
9636
+ * One bone's world transform from its locals under its parent's current
9637
+ * world (`worldTransforms` and the update order's `poseBone`). An inactive
9638
+ * bone is zeroed by the hierarchy (`fresh`) and left as it is by a repose.
9639
+ */
9640
+ poseBone(i, fresh) {
9641
+ const m = this.model;
9642
+ const W = this.world;
9643
+ const o = i * WORLDS;
9644
+ if (m.active[i] === 0) {
9645
+ if (fresh) {
9646
+ W[o + W_A] = 0;
9647
+ W[o + W_B] = 0;
9648
+ W[o + W_C] = 0;
9649
+ W[o + W_D] = 0;
9650
+ W[o + W_X] = 0;
9651
+ W[o + W_Y] = 0;
9652
+ }
9653
+ return;
9654
+ }
9655
+ const L = this.locals;
9656
+ const l = i * LOCALS;
9657
+ const x = L[l + L_X];
9658
+ const y = L[l + L_Y];
9659
+ const rotation = L[l + L_ROTATION];
9660
+ const scaleX = L[l + L_SCALE_X];
9661
+ const scaleY = L[l + L_SCALE_Y];
9662
+ const shearX = L[l + L_SHEAR_X];
9663
+ const shearY = L[l + L_SHEAR_Y];
9664
+ const parent = m.parents[i];
9665
+ if (parent < 0) {
9666
+ const xAngle = (rotation + shearX) * RAD;
9667
+ const yAngle = (rotation + 90 + shearY) * RAD;
9668
+ W[o + W_A] = Math.cos(xAngle) * scaleX;
9669
+ W[o + W_B] = Math.cos(yAngle) * scaleY;
9670
+ W[o + W_C] = Math.sin(xAngle) * scaleX;
9671
+ W[o + W_D] = Math.sin(yAngle) * scaleY;
9672
+ W[o + W_X] = x;
9673
+ W[o + W_Y] = y;
9674
+ return;
9675
+ }
9676
+ const q = parent * WORLDS;
9677
+ const pa = W[q + W_A];
9678
+ const pb = W[q + W_B];
9679
+ const pc = W[q + W_C];
9680
+ const pd = W[q + W_D];
9681
+ const px = W[q + W_X];
9682
+ const py = W[q + W_Y];
9683
+ switch (m.modes[i]) {
9684
+ case MODE_NORMAL: {
9685
+ const xAngle = (rotation + shearX) * RAD;
9686
+ const yAngle = (rotation + 90 + shearY) * RAD;
9687
+ const la = Math.cos(xAngle) * scaleX;
9688
+ const lb = Math.cos(yAngle) * scaleY;
9689
+ const lc = Math.sin(xAngle) * scaleX;
9690
+ const ld = Math.sin(yAngle) * scaleY;
9691
+ W[o + W_A] = pa * la + pb * lc;
9692
+ W[o + W_B] = pa * lb + pb * ld;
9693
+ W[o + W_C] = pc * la + pd * lc;
9694
+ W[o + W_D] = pc * lb + pd * ld;
9695
+ break;
9696
+ }
9697
+ case MODE_ONLY_TRANSLATION: {
9698
+ const xAngle = (rotation + shearX) * RAD;
9699
+ const yAngle = (rotation + 90 + shearY) * RAD;
9700
+ W[o + W_A] = Math.cos(xAngle) * scaleX;
9701
+ W[o + W_B] = Math.cos(yAngle) * scaleY;
9702
+ W[o + W_C] = Math.sin(xAngle) * scaleX;
9703
+ W[o + W_D] = Math.sin(yAngle) * scaleY;
9704
+ break;
9705
+ }
9706
+ case MODE_NO_ROTATION_OR_REFLECTION: {
9707
+ const xLengthSq = pa * pa + pc * pc;
9708
+ let c0;
9709
+ let c1;
9710
+ let c2;
9711
+ let c3;
9712
+ let r;
9713
+ if (xLengthSq > COLLAPSED_X_AXIS_SQ) {
9714
+ const k = Math.abs(pa * pd - pb * pc) / xLengthSq;
9715
+ c0 = pa;
9716
+ c1 = -pc * k;
9717
+ c2 = pc;
9718
+ c3 = pa * k;
9719
+ r = rotation - Math.atan2(pc, pa) * DEG;
9720
+ } else {
9721
+ c0 = 0;
9722
+ c1 = -pb;
9723
+ c2 = 0;
9724
+ c3 = pd;
9725
+ r = rotation - 90 + Math.atan2(pd, pb) * DEG;
9726
+ }
9727
+ const xAngle = (r + shearX) * RAD;
9728
+ const yAngle = (r + shearY + 90) * RAD;
9729
+ const la = Math.cos(xAngle) * scaleX;
9730
+ const lb = Math.cos(yAngle) * scaleY;
9731
+ const lc = Math.sin(xAngle) * scaleX;
9732
+ const ld = Math.sin(yAngle) * scaleY;
9733
+ W[o + W_A] = c0 * la + c1 * lc;
9734
+ W[o + W_B] = c0 * lb + c1 * ld;
9735
+ W[o + W_C] = c2 * la + c3 * lc;
9736
+ W[o + W_D] = c2 * lb + c3 * ld;
9737
+ break;
9738
+ }
9739
+ default: {
9740
+ const rr = rotation * RAD;
9741
+ const cos = Math.cos(rr);
9742
+ const sin = Math.sin(rr);
9743
+ const ux0 = pa * cos + pb * sin;
9744
+ const uy0 = pc * cos + pd * sin;
9745
+ const inverse = 1 / Math.sqrt(ux0 * ux0 + uy0 * uy0);
9746
+ const ux = ux0 * inverse;
9747
+ const uy = uy0 * inverse;
9748
+ const flip = m.modes[i] === MODE_NO_SCALE && pa * pd - pb * pc < 0 ? -1 : 1;
9749
+ const t0 = ux;
9750
+ const t1 = -uy * flip;
9751
+ const t2 = uy;
9752
+ const t3 = ux * flip;
9753
+ const xAngle = (0 + shearX) * RAD;
9754
+ const yAngle = (0 + 90 + shearY) * RAD;
9755
+ const la = Math.cos(xAngle) * scaleX;
9756
+ const lb = Math.cos(yAngle) * scaleY;
9757
+ const lc = Math.sin(xAngle) * scaleX;
9758
+ const ld = Math.sin(yAngle) * scaleY;
9759
+ W[o + W_A] = t0 * la + t1 * lc;
9760
+ W[o + W_B] = t0 * lb + t1 * ld;
9761
+ W[o + W_C] = t2 * la + t3 * lc;
9762
+ W[o + W_D] = t2 * lb + t3 * ld;
9763
+ }
9764
+ }
9765
+ W[o + W_X] = pa * x + pb * y + px;
9766
+ W[o + W_Y] = pc * x + pd * y + py;
9767
+ }
9768
+ // ---------------------------------------------------------------------------
9769
+ // the constraints
9770
+ // ---------------------------------------------------------------------------
9771
+ /** 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. */
9772
+ constraintValues(anim, t) {
9773
+ const cs = this.model.constraints;
9774
+ for (let k = 0; k < cs.length; k++) {
9775
+ const c = cs[k];
9776
+ const tl = anim === null ? null : anim.constraints[k];
9777
+ const i = tl === null ? -1 : keyAt(tl.curve, this.cursors, tl.cursor, t);
9778
+ if (c.kind === "ik") {
9779
+ if (tl === null || i < 0) {
9780
+ this.ikMix[k] = c.mix;
9781
+ this.ikSoftness[k] = c.softness;
9782
+ this.ikFlags[k] = c.flags;
9783
+ } else {
9784
+ this.ikMix[k] = c.mix + (channelValue(tl.curve, i, 0, t) - c.mix) * 1;
9785
+ this.ikSoftness[k] = c.softness + (channelValue(tl.curve, i, 1, t) - c.softness) * 1;
9786
+ this.ikFlags[k] = tl.flags[i];
9787
+ }
9788
+ } else {
9789
+ const o = k * 6;
9790
+ 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;
9791
+ }
9792
+ }
9793
+ }
9794
+ /** Every applied constraint in the document's order, each followed by the update order's repose (`applyConstraints`). */
9795
+ solveConstraints() {
9796
+ const cs = this.model.constraints;
9797
+ for (let k = 0; k < cs.length; k++) {
9798
+ const c = cs[k];
9799
+ if (!c.applied) continue;
9800
+ if (c.kind === "ik") this.solveIk(c, k);
9801
+ else this.solveTransform(c, k);
9802
+ }
9803
+ }
9804
+ solveIk(c, k) {
9805
+ const mix = this.ikMix[k];
9806
+ if (mix === 0) return;
9807
+ const flags = this.ikFlags[k];
9808
+ if (c.bones.length === 1) {
9809
+ this.solveOne(c.bones[0], c.target, mix, (flags & IK_COMPRESS) !== 0, (flags & IK_STRETCH) !== 0, c.scaleY);
9810
+ this.changed[0] = c.bones[0];
9811
+ this.repose(1, 0);
9812
+ } else if (this.solveTwo(c, mix, this.ikSoftness[k], flags)) {
9813
+ this.changed[0] = c.bones[0];
9814
+ this.repose(1, 0);
9815
+ }
9816
+ }
9817
+ /** A one-bone ik (`solveOne`). */
9818
+ solveOne(bi, ti, mix, compress, stretch, scaleYMode) {
9819
+ const m = this.model;
9820
+ const L = this.locals;
9821
+ const W = this.world;
9822
+ const parent = m.parents[bi];
9823
+ const q = parent * WORLDS;
9824
+ let pa = 1;
9825
+ let pb = 0;
9826
+ let pc = 0;
9827
+ let pd = 1;
9828
+ let pwx = 0;
9829
+ let pwy = 0;
9830
+ if (parent >= 0) {
9831
+ pa = W[q + W_A];
9832
+ pb = W[q + W_B];
9833
+ pc = W[q + W_C];
9834
+ pd = W[q + W_D];
9835
+ pwx = W[q + W_X];
9836
+ pwy = W[q + W_Y];
9837
+ }
9838
+ const bw = bi * WORLDS;
9839
+ const tw = ti * WORLDS;
9840
+ const l = bi * LOCALS;
9841
+ const mode = m.modes[bi];
9842
+ let turn = 0;
9843
+ let lx;
9844
+ let ly;
9845
+ if (mode === MODE_NO_ROTATION_OR_REFLECTION) {
9846
+ const k = Math.abs(pa * pd - pb * pc) / Math.max(1e-5, pa * pa + pc * pc);
9847
+ pb = -pc * k;
9848
+ pd = pa * k;
9849
+ turn = Math.atan2(pc, pa) * DEG;
9850
+ const det = pa * pd - pb * pc;
9851
+ const x = W[tw + W_X] - pwx;
9852
+ const y = W[tw + W_Y] - pwy;
9853
+ if (Math.abs(det) <= 1e-5) {
9854
+ lx = 0;
9855
+ ly = 0;
9856
+ } else {
9857
+ lx = (x * pd - y * pb) / det - L[l + L_X];
9858
+ ly = (y * pa - x * pc) / det - L[l + L_Y];
9859
+ }
9860
+ } else if (mode === MODE_ONLY_TRANSLATION) {
9861
+ lx = W[tw + W_X] - W[bw + W_X];
9862
+ ly = W[tw + W_Y] - W[bw + W_Y];
9863
+ } else {
9864
+ const det = pa * pd - pb * pc;
9865
+ const x = W[tw + W_X] - pwx;
9866
+ const y = W[tw + W_Y] - pwy;
9867
+ if (Math.abs(det) <= 1e-5) {
9868
+ lx = 0;
9869
+ ly = 0;
9870
+ } else {
9871
+ lx = (x * pd - y * pb) / det - L[l + L_X];
9872
+ ly = (y * pa - x * pc) / det - L[l + L_Y];
9873
+ }
9874
+ }
9875
+ const rotation = L[l + L_ROTATION];
9876
+ const scaleX = L[l + L_SCALE_X];
9877
+ let r = -L[l + L_SHEAR_X] - rotation + turn + Math.atan2(ly, lx) * DEG;
9878
+ if (scaleX < 0) r += 180;
9879
+ L[l + L_ROTATION] = rotation + wrap180(r) * mix;
9880
+ const len = m.lengths[bi] * scaleX;
9881
+ if ((compress || stretch) && len > 1e-5) {
9882
+ const wx = W[tw + W_X] - W[bw + W_X];
9883
+ const wy = W[tw + W_Y] - W[bw + W_Y];
9884
+ const d = mode === MODE_NO_SCALE || mode === MODE_NO_SCALE_OR_REFLECTION ? Math.sqrt(wx * wx + wy * wy) : Math.sqrt(lx * lx + ly * ly);
9885
+ if (compress && d < len || stretch && d > len) {
9886
+ const s = (d / len - 1) * mix + 1;
9887
+ L[l + L_SCALE_X] = scaleX * s;
9888
+ if (scaleYMode === SCALE_Y_UNIFORM) L[l + L_SCALE_Y] = L[l + L_SCALE_Y] * s;
9889
+ else if (scaleYMode === SCALE_Y_VOLUME) L[l + L_SCALE_Y] = volumeScaleY(L[l + L_SCALE_Y], s);
9890
+ }
9891
+ }
9892
+ }
9893
+ /** A two-bone ik (`solveTwo`): whether it changed the parent. */
9894
+ solveTwo(c, mix, softness, flags) {
9895
+ const m = this.model;
9896
+ const L = this.locals;
9897
+ const W = this.world;
9898
+ const pi = c.bones[0];
9899
+ const ci = c.bones[1];
9900
+ if (m.modes[pi] !== MODE_NORMAL || m.modes[ci] !== MODE_NORMAL) return false;
9901
+ const stretch = (flags & IK_STRETCH) !== 0;
9902
+ const g = m.parents[pi] * WORLDS;
9903
+ const pw = pi * WORLDS;
9904
+ const tw = c.target * WORLDS;
9905
+ const pl = pi * LOCALS;
9906
+ const cl = ci * LOCALS;
9907
+ const ga = W[g + W_A];
9908
+ const gb = W[g + W_B];
9909
+ const gc = W[g + W_C];
9910
+ const gd = W[g + W_D];
9911
+ const gx = W[g + W_X];
9912
+ const gy = W[g + W_Y];
9913
+ const gdet = ga * gd - gb * gc;
9914
+ const gid = Math.abs(gdet) <= 1e-5 ? 0 : 1 / gdet;
9915
+ const px = L[pl + L_X];
9916
+ const py = L[pl + L_Y];
9917
+ const psx = L[pl + L_SCALE_X];
9918
+ const psy = L[pl + L_SCALE_Y];
9919
+ const csx = L[cl + L_SCALE_X];
9920
+ let sx = psx;
9921
+ let sy = psy;
9922
+ const cx = L[cl + L_X];
9923
+ let cy = L[cl + L_Y];
9924
+ const uniform = Math.abs(Math.abs(psx) - Math.abs(psy)) <= 1e-5;
9925
+ if (!uniform || stretch) cy = 0;
9926
+ let x = W[pw + W_A] * cx + W[pw + W_B] * cy + W[pw + W_X] - gx;
9927
+ let y = W[pw + W_C] * cx + W[pw + W_D] * cy + W[pw + W_Y] - gy;
9928
+ const ox = (x * gd - y * gb) * gid;
9929
+ const oy = (y * ga - x * gc) * gid;
9930
+ const dx = ox - px;
9931
+ const dy = oy - py;
9932
+ const l1 = Math.sqrt(dx * dx + dy * dy);
9933
+ let l2 = m.lengths[ci] * Math.abs(csx);
9934
+ if (l1 < 1e-5) {
9935
+ this.solveOne(pi, c.target, mix, false, stretch, 0);
9936
+ L[cl + L_ROTATION] = 0;
9937
+ L[cl + L_Y] = cy;
9938
+ return true;
9939
+ }
9940
+ x = W[tw + W_X] - gx;
9941
+ y = W[tw + W_Y] - gy;
9942
+ let tx = (x * gd - y * gb) * gid;
9943
+ let ty = (y * ga - x * gc) * gid;
9944
+ tx -= px;
9945
+ ty -= py;
9946
+ let d2 = tx * tx + ty * ty;
9947
+ const bend = (flags & IK_BEND_POSITIVE) !== 0 ? 1 : -1;
9948
+ if (softness !== 0) {
9949
+ const soft = softness * (Math.abs(psx) * (Math.abs(csx) + 1) * 0.5);
9950
+ const d = Math.sqrt(d2);
9951
+ const sd = d - l1 - l2 * Math.abs(psx) + soft;
9952
+ if (sd > 0) {
9953
+ const q = Math.min(1, sd / (soft * 2)) - 1;
9954
+ const pull = (sd - soft * (1 - q * q)) / d;
9955
+ tx -= pull * tx;
9956
+ ty -= pull * ty;
9957
+ d2 = tx * tx + ty * ty;
9958
+ }
9959
+ }
9960
+ let a1;
9961
+ let a2;
9962
+ if (uniform) {
9963
+ l2 *= Math.abs(psx);
9964
+ let cos = (d2 - l1 * l1 - l2 * l2) / (2 * l1 * l2);
9965
+ if (cos < -1) {
9966
+ cos = -1;
9967
+ a2 = Math.PI * bend;
9968
+ } else if (cos > 1) {
9969
+ cos = 1;
9970
+ a2 = 0;
9971
+ if (stretch) {
9972
+ const f = (Math.sqrt(d2) / (l1 + l2) - 1) * mix + 1;
9973
+ sx *= f;
9974
+ if (c.scaleY === SCALE_Y_UNIFORM) sy *= f;
9975
+ else if (c.scaleY === SCALE_Y_VOLUME) sy = volumeScaleY(sy, f);
9976
+ }
9977
+ } else a2 = Math.acos(cos) * bend;
9978
+ const along = l1 + l2 * cos;
9979
+ const across = l2 * Math.sin(a2);
9980
+ a1 = Math.atan2(ty * along - tx * across, tx * along + ty * across);
9981
+ } else {
9982
+ const ea = Math.abs(psx) * l2;
9983
+ const eb = Math.abs(psy) * l2;
9984
+ const aa = ea * ea;
9985
+ const bb = eb * eb;
9986
+ const qa = bb - aa;
9987
+ const qb = -2 * bb * l1;
9988
+ const qc = bb * l1 * l1 + aa * d2 - aa * bb;
9989
+ const disc = qb * qb - 4 * qa * qc;
9990
+ let solved = false;
9991
+ a1 = 0;
9992
+ a2 = 0;
9993
+ if (disc >= 0) {
9994
+ let q = Math.sqrt(disc);
9995
+ if (qb < 0) q = -q;
9996
+ q = -(qb + q) * 0.5;
9997
+ const r0 = q / qa;
9998
+ const r1 = qc / q;
9999
+ const X = Math.abs(r0) < Math.abs(r1) ? r0 : r1;
10000
+ const y2 = d2 - X * X;
10001
+ if (y2 >= 0) {
10002
+ const Y = Math.sqrt(y2) * bend;
10003
+ a1 = Math.atan2(ty, tx) - Math.atan2(Y, X);
10004
+ a2 = Math.atan2(Y / Math.abs(psy), (X - l1) / Math.abs(psx));
10005
+ solved = true;
10006
+ }
10007
+ }
10008
+ if (!solved) {
10009
+ let nearAngle = RUNTIME_PI;
10010
+ let nearX = l1 - ea;
10011
+ let nearY = 0;
10012
+ let nearDist = (l1 - ea) * (l1 - ea);
10013
+ let farAngle = 0;
10014
+ let farX = l1 + ea;
10015
+ let farY = 0;
10016
+ let farDist = (l1 + ea) * (l1 + ea);
10017
+ const vertex = -ea * l1 / (aa - bb);
10018
+ if (vertex >= -1 && vertex <= 1) {
10019
+ const angle = Math.acos(vertex);
10020
+ const vx = ea * Math.cos(angle) + l1;
10021
+ const vy = eb * Math.sin(angle);
10022
+ const dist = vx * vx + vy * vy;
10023
+ if (dist < nearDist) {
10024
+ nearAngle = angle;
10025
+ nearX = vx;
10026
+ nearY = vy;
10027
+ nearDist = dist;
10028
+ }
10029
+ if (dist > farDist) {
10030
+ farAngle = angle;
10031
+ farX = vx;
10032
+ farY = vy;
10033
+ farDist = dist;
10034
+ }
10035
+ }
10036
+ if (d2 <= (nearDist + farDist) * 0.5) {
10037
+ a1 = Math.atan2(ty, tx) - Math.atan2(nearY * bend, nearX);
10038
+ a2 = nearAngle * bend;
10039
+ } else {
10040
+ a1 = Math.atan2(ty, tx) - Math.atan2(farY * bend, farX);
10041
+ a2 = farAngle * bend;
10042
+ }
10043
+ }
10044
+ }
10045
+ const signs = (psx < 0 ? -1 : 1) * (psy < 0 ? -1 : 1);
10046
+ const offset = Math.atan2(cy, cx);
10047
+ const parentRotation = L[pl + L_ROTATION];
10048
+ const parentChange = (a1 - signs * offset) * DEG + (psx < 0 ? 180 : 0) - parentRotation;
10049
+ L[pl + L_ROTATION] = parentRotation + wrap180(parentChange) * mix;
10050
+ L[pl + L_SCALE_X] = sx;
10051
+ L[pl + L_SCALE_Y] = sy;
10052
+ const childTarget = ((a2 + signs * offset) * DEG - L[cl + L_SHEAR_X]) * signs + (csx < 0 ? 180 : 0);
10053
+ const childRotation = L[cl + L_ROTATION];
10054
+ L[cl + L_Y] = cy;
10055
+ L[cl + L_ROTATION] = childRotation + wrap180(childTarget - childRotation) * mix;
10056
+ return true;
10057
+ }
10058
+ /** A world-space, absolute transform (`solveTransform`): each bone's world edited in turn and written back, then the repose. */
10059
+ solveTransform(c, k) {
10060
+ const W = this.world;
10061
+ const w = this.w;
10062
+ const mixes = this.mixes;
10063
+ const o6 = k * 6;
10064
+ let n = 0;
10065
+ for (let bk = 0; bk < c.bones.length; bk++) {
10066
+ const bi = c.bones[bk];
10067
+ const o = bi * WORLDS;
10068
+ w[W_A] = W[o + W_A];
10069
+ w[W_B] = W[o + W_B];
10070
+ w[W_C] = W[o + W_C];
10071
+ w[W_D] = W[o + W_D];
10072
+ w[W_X] = W[o + W_X];
10073
+ w[W_Y] = W[o + W_Y];
10074
+ let applied = false;
10075
+ for (let f = 0; f < c.fromProperty.length; f++) {
10076
+ const value = this.sourceValue(c, c.fromProperty[f]) - c.fromOffset[f];
10077
+ for (let q = c.toStart[f]; q < c.toEnd[f]; q++) {
10078
+ const property = c.toProperty[q];
10079
+ const mix = mixes[o6 + property];
10080
+ if (mix === 0) continue;
10081
+ let v = value * c.toScale[q] + c.toOffset[q];
10082
+ if (c.clamp) {
10083
+ const lo = Math.min(c.toOffset[q], c.toMax[q]);
10084
+ const hi = Math.max(c.toOffset[q], c.toMax[q]);
10085
+ v = v < lo ? lo : v > hi ? hi : v;
10086
+ }
10087
+ applied = true;
10088
+ applyWorld(w, property, v, mix);
10089
+ }
10090
+ }
10091
+ if (!applied) continue;
10092
+ W[o + W_A] = w[W_A];
10093
+ W[o + W_B] = w[W_B];
10094
+ W[o + W_C] = w[W_C];
10095
+ W[o + W_D] = w[W_D];
10096
+ W[o + W_X] = w[W_X];
10097
+ W[o + W_Y] = w[W_Y];
10098
+ this.changed[n] = bi;
10099
+ this.inWorld[n] = bi;
10100
+ n++;
10101
+ }
10102
+ if (n > 0) this.repose(n, n);
10103
+ }
10104
+ /** The source's world value of a property (`sourceValue`, world source). */
10105
+ sourceValue(c, property) {
10106
+ const W = this.world;
10107
+ const s = c.source * WORLDS;
10108
+ const o = c.offsets;
10109
+ const a = W[s + W_A];
10110
+ const b = W[s + W_B];
10111
+ const cc = W[s + W_C];
10112
+ const d = W[s + W_D];
10113
+ switch (property) {
10114
+ case P_ROTATE: {
10115
+ const det = a * d - b * cc;
10116
+ let r = Math.atan2(cc, a) * DEG + (det > 0 ? o[P_ROTATE] : -o[P_ROTATE]);
10117
+ if (r < 0) r += 360;
10118
+ return r;
10119
+ }
10120
+ case P_X:
10121
+ return a * o[P_X] + b * o[P_Y] + W[s + W_X];
10122
+ case P_Y:
10123
+ return cc * o[P_X] + d * o[P_Y] + W[s + W_Y];
10124
+ case P_SCALE_X:
10125
+ return Math.sqrt(a * a + cc * cc) + o[P_SCALE_X];
10126
+ case P_SCALE_Y:
10127
+ return Math.sqrt(b * b + d * d) + o[P_SCALE_Y];
10128
+ default:
10129
+ return (Math.atan2(d, b) - Math.atan2(cc, a)) * DEG - 90 + o[5];
10130
+ }
10131
+ }
10132
+ /**
10133
+ * After a constraint (`repose`): the `nInWorld` bones it moved in world space
10134
+ * (the first of `inWorld`) read back into local values, then every bone below
10135
+ * one of the `nChanged` it changed (`changed`) posed again, in bone order —
10136
+ * but not through an inactive bone it did not move itself, and not a bone it
10137
+ * moved in world space.
10138
+ */
10139
+ repose(nChanged, nInWorld) {
10140
+ const m = this.model;
10141
+ const changed = this.changed;
10142
+ const changedMark = this.changedMark;
10143
+ const inWorldMark = this.inWorldMark;
10144
+ const below = this.below;
10145
+ for (let k = 0; k < nInWorld; k++) {
10146
+ this.localFromWorld(this.inWorld[k]);
10147
+ inWorldMark[this.inWorld[k]] = 1;
10148
+ }
10149
+ for (let k = 0; k < nChanged; k++) {
10150
+ changedMark[changed[k]] = 1;
10151
+ below[changed[k]] = 1;
10152
+ }
10153
+ const starts = m.subtreeStart;
10154
+ const list = m.subtrees;
10155
+ const parents = m.parents;
10156
+ const active = m.active;
10157
+ if (nChanged === 1) {
10158
+ const e = starts[changed[0] + 1];
10159
+ for (let q = starts[changed[0]]; q < e; q++) this.reposeOne(list[q], parents, active);
10160
+ for (let q = starts[changed[0]]; q < e; q++) below[list[q]] = 0;
10161
+ } else {
10162
+ const visit = this.visit;
10163
+ let first = parents.length;
10164
+ for (let k = 0; k < nChanged; k++) {
10165
+ const b = changed[k];
10166
+ if (b < first) first = b;
10167
+ const e = starts[b + 1];
10168
+ for (let q = starts[b]; q < e; q++) visit[list[q]] = 1;
10169
+ }
10170
+ for (let b = first; b < parents.length; b++) if (visit[b] === 1) this.reposeOne(b, parents, active);
10171
+ for (let b = first; b < parents.length; b++) {
10172
+ visit[b] = 0;
10173
+ below[b] = 0;
10174
+ }
10175
+ }
10176
+ for (let k = 0; k < nChanged; k++) changedMark[changed[k]] = 0;
10177
+ for (let k = 0; k < nInWorld; k++) inWorldMark[this.inWorld[k]] = 0;
10178
+ }
10179
+ reposeOne(b, parents, active) {
10180
+ const below = this.below;
10181
+ const p = parents[b];
10182
+ if (p >= 0 && below[p] === 1 && (active[p] === 1 || this.changedMark[p] === 1)) below[b] = 1;
10183
+ if (below[b] === 1 && this.inWorldMark[b] === 0) this.poseBone(b, false);
10184
+ }
10185
+ /** 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. */
10186
+ localFromWorld(bi) {
10187
+ const m = this.model;
10188
+ const W = this.world;
10189
+ const L = this.locals;
10190
+ const w = bi * WORLDS;
10191
+ const l = bi * LOCALS;
10192
+ const parent = m.parents[bi];
10193
+ const mode = parent < 0 ? MODE_NORMAL : m.modes[bi];
10194
+ let pa = 1;
10195
+ let pb = 0;
10196
+ let pc = 0;
10197
+ let pd = 1;
10198
+ let px = 0;
10199
+ let py = 0;
10200
+ if (parent >= 0) {
10201
+ const q = parent * WORLDS;
10202
+ pa = W[q + W_A];
10203
+ pb = W[q + W_B];
10204
+ pc = W[q + W_C];
10205
+ pd = W[q + W_D];
10206
+ px = W[q + W_X];
10207
+ py = W[q + W_Y];
10208
+ }
10209
+ const pid = 1 / (pa * pd - pb * pc);
10210
+ const dx = W[w + W_X] - px;
10211
+ const dy = W[w + W_Y] - py;
10212
+ L[l + L_X] = dx * (pd * pid) - dy * (pb * pid);
10213
+ L[l + L_Y] = dy * (pa * pid) - dx * (pc * pid);
10214
+ L[l + L_SHEAR_X] = 0;
10215
+ const wa = W[w + W_A];
10216
+ const wb = W[w + W_B];
10217
+ const wc = W[w + W_C];
10218
+ const wd = W[w + W_D];
10219
+ if (mode === MODE_ONLY_TRANSLATION) {
10220
+ this.decompose(l, wa, wc, wb, wd);
10221
+ return;
10222
+ }
10223
+ if (mode === MODE_NO_ROTATION_OR_REFLECTION) {
10224
+ const xLengthInverse = 1 / (pa * pa + pc * pc);
10225
+ const detInverse = 1 / Math.abs(pa * pd - pb * pc);
10226
+ this.decompose(l, (pa * wa + pc * wc) * xLengthInverse, (pa * wc - pc * wa) * detInverse, (pa * wb + pc * wd) * xLengthInverse, (pa * wd - pc * wb) * detInverse);
10227
+ L[l + L_ROTATION] = L[l + L_ROTATION] + Math.atan2(pc, pa) * DEG;
10228
+ return;
10229
+ }
10230
+ const ia = pd * pid;
10231
+ const ib = pb * pid;
10232
+ const ic = pc * pid;
10233
+ const id = pa * pid;
10234
+ this.decompose(l, ia * wa - ib * wc, id * wc - ic * wa, ia * wb - ib * wd, id * wd - ic * wb);
10235
+ }
10236
+ /** A local matrix with columns `(la, lc)` and `(lb, ld)` as rotation, scales and shear (`decompose`). */
10237
+ decompose(l, la, lc, lb, ld) {
10238
+ const L = this.locals;
10239
+ const sx = Math.sqrt(la * la + lc * lc);
10240
+ const yLength = Math.sqrt(lb * lb + ld * ld);
10241
+ if (!(sx > 1e-4)) {
10242
+ L[l + L_SCALE_X] = 0;
10243
+ L[l + L_SCALE_Y] = yLength;
10244
+ L[l + L_SHEAR_Y] = 0;
10245
+ L[l + L_ROTATION] = yLength > 1e-5 ? Math.atan2(ld, lb) * DEG - 90 : 0;
10246
+ return;
10247
+ }
10248
+ const xAngle = Math.atan2(lc, la) * DEG;
10249
+ const det = la * ld - lb * lc;
10250
+ const yAngle = Math.atan2(ld, lb) * DEG;
10251
+ L[l + L_ROTATION] = xAngle;
10252
+ L[l + L_SCALE_X] = sx;
10253
+ L[l + L_SCALE_Y] = det < 0 ? -yLength : yLength;
10254
+ const right = det < 0 ? -90 : 90;
10255
+ let shear = yAngle - (xAngle + right);
10256
+ shear = shear > 180 ? shear - 360 : shear < -180 ? shear + 360 : shear;
10257
+ L[l + L_SHEAR_Y] = yLength > 1e-5 ? shear : 0;
10258
+ }
10259
+ // ---------------------------------------------------------------------------
10260
+ // the slots and the vertices
10261
+ // ---------------------------------------------------------------------------
10262
+ /** Every slot's colour and what it shows (`posedSlots`): the setup's, then each of its timelines at `t` in order. */
10263
+ slotValues(anim, t) {
10264
+ const slots = this.model.slots;
10265
+ const light = this.light;
10266
+ for (let s = 0; s < slots.length; s++) {
10267
+ const slot = slots[s];
10268
+ this.shown[s] = slot.setupAttachment;
10269
+ const o = s * 4;
10270
+ light[o] = slot.light[0];
10271
+ light[o + 1] = slot.light[1];
10272
+ light[o + 2] = slot.light[2];
10273
+ light[o + 3] = slot.light[3];
10274
+ }
10275
+ if (anim === null) return;
10276
+ const timelines = anim.slots;
10277
+ for (let k = 0; k < timelines.length; k++) {
10278
+ const tl = timelines[k];
10279
+ const i = keyAt(tl.curve, this.cursors, tl.cursor, t);
10280
+ const attachments = tl.attachments;
10281
+ if (attachments !== null) {
10282
+ this.shown[tl.slot] = i < 0 ? slots[tl.slot].setupAttachment : attachments[i];
10283
+ continue;
10284
+ }
10285
+ const channels = tl.channels;
10286
+ const setup = slots[tl.slot].light;
10287
+ const o = tl.slot * 4;
10288
+ for (let c = 0; c < channels.length; c++) {
10289
+ const at = channels[c];
10290
+ light[o + at] = i < 0 ? setup[at] : clamp01(channelValue(tl.curve, i, c, t));
10291
+ }
10292
+ }
10293
+ }
10294
+ /** Reset on every pose, then match source identity after switches; no old frame's offsets survive. */
10295
+ deformValues(anim, t) {
10296
+ this.deformed.fill(0);
10297
+ this.deform.fill(0);
10298
+ if (anim === null || this.model.omitDeform) return;
10299
+ for (let k = 0; k < anim.deforms.length; k++) {
10300
+ const tl = anim.deforms[k];
10301
+ const i = keyAt(tl.curve, this.cursors, tl.cursor, t);
10302
+ if (i < 0) continue;
10303
+ const held = i + 1 === tl.curve.keys || tl.curve.kinds[i] === CURVE_STEPPED;
10304
+ const p = held ? 0 : deformFraction(tl.curve, i, t);
10305
+ const start = i * tl.count;
10306
+ for (let s = 0; s < this.shown.length; s++) {
10307
+ const index = this.shown[s];
10308
+ if (index < 0 || !this.model.slots[s].live) continue;
10309
+ const at = this.model.attachments[index];
10310
+ if (at.timeline !== tl.identity || at.deformCount === 0) continue;
10311
+ this.deformed[index] = 1;
10312
+ for (let q = 0; q < tl.count; q++) {
10313
+ const v = tl.held[start + q];
10314
+ this.deform[at.deformOffset + q] = held ? v : v + (tl.held[start + tl.count + q] - v) * p;
10315
+ }
10316
+ }
10317
+ }
10318
+ }
10319
+ /** Every shown attachment's world vertices into `vertices` at its offset (`regionCorners`, `worldVertices`). */
10320
+ computeVertices() {
10321
+ const m = this.model;
10322
+ const W = this.world;
10323
+ const out = this.vertices;
10324
+ const shown = this.shown;
10325
+ const attachments = m.attachments;
10326
+ for (let s = 0; s < shown.length; s++) {
10327
+ const index = shown[s];
10328
+ if (index < 0) continue;
10329
+ const at = attachments[index];
10330
+ if (at.draw === DRAW_NONE) continue;
10331
+ let o = at.offset;
10332
+ if (at.draw === DRAW_REGION || at.draw === DRAW_MESH) {
10333
+ const b = at.bone * WORLDS;
10334
+ const a = W[b + W_A];
10335
+ const bb = W[b + W_B];
10336
+ const c = W[b + W_C];
10337
+ const d = W[b + W_D];
10338
+ const wx = W[b + W_X];
10339
+ const wy = W[b + W_Y];
10340
+ const local = this.deformed[index] && at.draw === DRAW_MESH ? this.deform : at.local;
10341
+ const lo = this.deformed[index] && at.draw === DRAW_MESH ? at.deformOffset : 0;
10342
+ if (at.draw === DRAW_REGION) {
10343
+ for (let q = 0; q < 8; q += 2) {
10344
+ const lx = local[q];
10345
+ const ly = local[q + 1];
10346
+ out[o++] = a * lx + bb * ly + wx;
10347
+ out[o++] = c * lx + d * ly + wy;
10348
+ }
10349
+ } else {
10350
+ for (let q = 0; q < at.count; q += 2) {
10351
+ const x = local[lo + q];
10352
+ const y = local[lo + q + 1];
10353
+ out[o++] = x * a + y * bb + wx;
10354
+ out[o++] = x * c + y * d + wy;
10355
+ }
10356
+ }
10357
+ continue;
10358
+ }
10359
+ const counts = at.bindingCounts;
10360
+ const bones = at.bindingBones;
10361
+ const xs = at.bindingX;
10362
+ const ys = at.bindingY;
10363
+ const weights = at.bindingWeights;
10364
+ let n = 0;
10365
+ for (let v = 0; v < counts.length; v++) {
10366
+ let wx = 0;
10367
+ let wy = 0;
10368
+ const end = n + counts[v];
10369
+ for (; n < end; n++) {
10370
+ const b = bones[n] * WORLDS;
10371
+ const weight = weights[n];
10372
+ const deformed = this.deformed[index] !== 0;
10373
+ const x = deformed ? xs[n] + this.deform[at.deformOffset + n * 2] : xs[n];
10374
+ const y = deformed ? ys[n] + this.deform[at.deformOffset + n * 2 + 1] : ys[n];
10375
+ if (deformed && m.deformAfterWeights) {
10376
+ wx += (xs[n] * W[b + W_A] + ys[n] * W[b + W_B] + W[b + W_X]) * weight + this.deform[at.deformOffset + n * 2];
10377
+ wy += (xs[n] * W[b + W_C] + ys[n] * W[b + W_D] + W[b + W_Y]) * weight + this.deform[at.deformOffset + n * 2 + 1];
10378
+ continue;
10379
+ }
10380
+ wx += (x * W[b + W_A] + y * W[b + W_B] + W[b + W_X]) * weight;
10381
+ wy += (x * W[b + W_C] + y * W[b + W_D] + W[b + W_Y]) * weight;
10382
+ }
10383
+ out[o++] = wx;
10384
+ out[o++] = wy;
10385
+ }
10386
+ }
10387
+ }
10388
+ }
10389
+ function applyWorld(w, property, v, mix) {
10390
+ switch (property) {
10391
+ case P_ROTATE: {
10392
+ let r = v * RAD - Math.atan2(w[W_C], w[W_A]);
10393
+ if (r > RUNTIME_PI) r -= TWO_PI;
10394
+ else if (r < -RUNTIME_PI) r += TWO_PI;
10395
+ r *= mix;
10396
+ const cos = Math.cos(r);
10397
+ const sin = Math.sin(r);
10398
+ const a = w[W_A];
10399
+ const b = w[W_B];
10400
+ w[W_A] = cos * a - sin * w[W_C];
10401
+ w[W_B] = cos * b - sin * w[W_D];
10402
+ w[W_C] = sin * a + cos * w[W_C];
10403
+ w[W_D] = sin * b + cos * w[W_D];
10404
+ return;
10405
+ }
10406
+ case P_X:
10407
+ w[W_X] = w[W_X] + (v - w[W_X]) * mix;
10408
+ return;
10409
+ case P_Y:
10410
+ w[W_Y] = w[W_Y] + (v - w[W_Y]) * mix;
10411
+ return;
10412
+ case P_SCALE_X:
10413
+ case P_SCALE_Y: {
10414
+ const p = property === P_SCALE_X ? W_A : W_B;
10415
+ const q = property === P_SCALE_X ? W_C : W_D;
10416
+ const s = Math.sqrt(w[p] * w[p] + w[q] * w[q]);
10417
+ if (s === 0) return;
10418
+ const k = 1 + (v - s) * mix / s;
10419
+ w[p] *= k;
10420
+ w[q] *= k;
10421
+ return;
10422
+ }
10423
+ default: {
10424
+ const yAngle = Math.atan2(w[W_D], w[W_B]);
10425
+ const xAngle = Math.atan2(w[W_C], w[W_A]);
10426
+ let r = (v + 90) * RAD - (yAngle - xAngle);
10427
+ r = r > RUNTIME_PI ? r - TWO_PI : r < -RUNTIME_PI ? r + TWO_PI : r;
10428
+ const angle = yAngle + r * mix;
10429
+ const s = Math.sqrt(w[W_B] * w[W_B] + w[W_D] * w[W_D]);
10430
+ w[W_B] = Math.cos(angle) * s;
10431
+ w[W_D] = Math.sin(angle) * s;
10432
+ }
10433
+ }
10434
+ }
8835
10435
  function frameSource(doc, parsed) {
8836
10436
  if (doc.pages === null) {
8837
10437
  throw new CoreInputError(
@@ -8839,12 +10439,20 @@ function frameSource(doc, parsed) {
8839
10439
  );
8840
10440
  }
8841
10441
  const pages = new Map(doc.pages.map((page) => [page.name, page]));
10442
+ const boneOf = /* @__PURE__ */ new Map();
10443
+ const slotIndex = /* @__PURE__ */ new Map();
10444
+ for (let i = 0; i < doc.slots.length; i++) {
10445
+ const slot = doc.slots[i];
10446
+ boneOf.set(slot.name, slot.bone);
10447
+ slotIndex.set(slot.name, i);
10448
+ }
8842
10449
  return {
8843
10450
  doc,
8844
10451
  uv: { lookup: documentPageLookup(doc.pages), sequences: readUvSequences(parsed) },
8845
10452
  pages,
8846
10453
  active: activeBones(doc),
8847
- boneOf: new Map(doc.slots.map((slot) => [slot.name, slot.bone])),
10454
+ boneOf,
10455
+ slotIndex,
8848
10456
  uvs: /* @__PURE__ */ new WeakMap(),
8849
10457
  regionUvs: /* @__PURE__ */ new WeakMap(),
8850
10458
  triangles: /* @__PURE__ */ new Map()
@@ -8877,24 +10485,57 @@ function regionUvs(source, found) {
8877
10485
  return kept;
8878
10486
  }
8879
10487
  function trianglesOf(source, drawn) {
8880
- const key = `${drawn.slot}\0${drawn.attachment}`;
8881
- let lists = source.triangles.get(key);
8882
- if (lists === void 0) {
8883
- lists = [];
8884
- source.triangles.set(key, lists);
10488
+ let byAttachment = source.triangles.get(drawn.slot);
10489
+ if (byAttachment === void 0) {
10490
+ byAttachment = /* @__PURE__ */ new Map();
10491
+ source.triangles.set(drawn.slot, byAttachment);
10492
+ }
10493
+ let cache = byAttachment.get(drawn.attachment);
10494
+ if (cache === void 0) {
10495
+ cache = { byRef: /* @__PURE__ */ new WeakMap(), lists: [] };
10496
+ byAttachment.set(drawn.attachment, cache);
8885
10497
  }
8886
10498
  const wanted = drawn.triangles;
8887
- let kept = lists.find((list) => list.length === wanted.length && list.every((v, i) => v === wanted[i]));
10499
+ const fast = cache.byRef.get(wanted);
10500
+ if (fast !== void 0) return fast;
10501
+ const frozen = Object.isFrozen(wanted);
10502
+ let kept = cache.lists.find((list) => list.length === wanted.length && list.every((v, i) => v === wanted[i]));
8888
10503
  if (kept === void 0) {
8889
- kept = [...wanted];
8890
- lists.push(kept);
10504
+ kept = frozen ? wanted : Object.freeze([...wanted]);
10505
+ cache.lists.push(kept);
10506
+ cache.byRef.set(kept, kept);
8891
10507
  }
10508
+ if (frozen) cache.byRef.set(wanted, kept);
8892
10509
  return kept;
8893
10510
  }
10511
+ function regionMap(regions) {
10512
+ const map = /* @__PURE__ */ new Map();
10513
+ for (let i = 0; i < regions.length; i++) map.set(regions[i].slot, regions[i]);
10514
+ return map;
10515
+ }
10516
+ function slotRowMap(slots) {
10517
+ const map = /* @__PURE__ */ new Map();
10518
+ for (let i = 0; i < slots.length; i++) map.set(slots[i][0], slots[i]);
10519
+ return map;
10520
+ }
10521
+ function drawOrderMap(drawOrder) {
10522
+ const map = /* @__PURE__ */ new Map();
10523
+ for (let i = 0; i < drawOrder.length; i++) map.set(drawOrder[i], i);
10524
+ return map;
10525
+ }
8894
10526
  function clipCover(pose, active, draws = () => true) {
8895
- const polygons = new Map(pose.clips.map((c) => [c[0], { end: c[2], polygon: c[3] }]));
8896
- const shownOf = new Map(pose.shown.map((s) => [s.slot, s]));
8897
10527
  const cover = /* @__PURE__ */ new Map();
10528
+ if (pose.clips.length === 0) return cover;
10529
+ const polygons = /* @__PURE__ */ new Map();
10530
+ for (let i = 0; i < pose.clips.length; i++) {
10531
+ const c = pose.clips[i];
10532
+ polygons.set(c[0], { end: c[2], polygon: c[3] });
10533
+ }
10534
+ const shownOf = /* @__PURE__ */ new Map();
10535
+ for (let i = 0; i < pose.shown.length; i++) {
10536
+ const s = pose.shown[i];
10537
+ shownOf.set(s.slot, s);
10538
+ }
8898
10539
  let current = null;
8899
10540
  for (const slot of pose.drawOrder) {
8900
10541
  const shown = shownOf.get(slot);
@@ -8915,20 +10556,25 @@ function clipCover(pose, active, draws = () => true) {
8915
10556
  function frameOf(source, pose, color = [1, 1, 1, 1], draws = () => true) {
8916
10557
  const { drawn: regions, why } = drawnRegions(source.doc, pose.shown, pose.drawOrder, source.uv);
8917
10558
  if (regions === null) throw new CoreInputError(`the page UVs are not posed: ${why}`);
8918
- const regionOf = new Map(regions.map((r) => [r.slot, r]));
8919
- const rows = new Map(pose.slots.map((row) => [row[0], row]));
8920
- const z = new Map(pose.drawOrder.map((slot, index) => [slot, index]));
8921
- const cover = clipCover(pose, source.active, draws);
10559
+ let regionOf = null;
10560
+ let rows = null;
10561
+ let z = null;
10562
+ const cover = pose.clips.length === 0 ? null : clipCover(pose, source.active, draws);
8922
10563
  const [sr, sg, sb, sa] = color;
8923
10564
  const order = [];
8924
- for (const d of pose.drawn) {
10565
+ for (let i = 0; i < pose.drawn.length; i++) {
10566
+ const d = pose.drawn[i];
8925
10567
  if (!draws(d.slot)) continue;
8926
10568
  const bone2 = source.boneOf.get(d.slot);
8927
10569
  if (bone2 === void 0 || !source.active.has(bone2)) continue;
8928
- const found = regionOf.get(d.slot);
10570
+ const directRegion = regions[i];
10571
+ const found = directRegion !== void 0 && directRegion.slot === d.slot ? directRegion : (regionOf ??= regionMap(regions)).get(d.slot);
8929
10572
  if (found === void 0) throw new CoreInputError(`slot "${d.slot}" drew "${d.attachment}", and no atlas region was resolved for it`);
8930
- const row = rows.get(d.slot);
10573
+ const slotIdx = source.slotIndex.get(d.slot);
10574
+ const directRow = slotIdx !== void 0 ? pose.slots[slotIdx] : void 0;
10575
+ const row = directRow !== void 0 && directRow[0] === d.slot ? directRow : (rows ??= slotRowMap(pose.slots)).get(d.slot);
8931
10576
  if (row === void 0) throw new CoreInputError(`slot "${d.slot}" drew and has no slot row`);
10577
+ const slotZ = slotIdx !== void 0 && pose.drawOrder[slotIdx] === d.slot ? slotIdx : (z ??= drawOrderMap(pose.drawOrder)).get(d.slot);
8932
10578
  const tint = [
8933
10579
  channel(row[2], d.slot) * d.colour[0] * sr,
8934
10580
  channel(row[3], d.slot) * d.colour[1] * sg,
@@ -8936,13 +10582,14 @@ function frameOf(source, pose, color = [1, 1, 1, 1], draws = () => true) {
8936
10582
  channel(row[5], d.slot) * d.colour[3] * sa
8937
10583
  ];
8938
10584
  const darkRow = row[6];
10585
+ const clip = cover === null ? void 0 : cover.get(d.slot);
8939
10586
  const common = {
8940
10587
  slot: d.slot,
8941
- z: z.get(d.slot),
10588
+ z: slotZ,
8942
10589
  blend: row[8] ?? "Normal",
8943
10590
  tint,
8944
10591
  ...darkRow === null ? {} : { dark: [channel(darkRow[0], d.slot), channel(darkRow[1], d.slot), channel(darkRow[2], d.slot)] },
8945
- ...cover.has(d.slot) ? { clip: cover.get(d.slot) } : {}
10592
+ ...clip === void 0 ? {} : { clip }
8946
10593
  };
8947
10594
  const page = source.pages.get(found.found.page.name);
8948
10595
  if (d.kind === "region") {
@@ -8970,6 +10617,238 @@ function frameOf(source, pose, color = [1, 1, 1, 1], draws = () => true) {
8970
10617
  }
8971
10618
  return { order };
8972
10619
  }
10620
+ function numbers(buffer, offset, count) {
10621
+ const result = new Array(count);
10622
+ for (let i = 0; i < count; i++) Object.defineProperty(result, i, {
10623
+ enumerable: true,
10624
+ get: () => buffer[offset + i]
10625
+ });
10626
+ return Object.freeze(result);
10627
+ }
10628
+ function checkedLiveFrame(frame2, current) {
10629
+ const generation = current();
10630
+ const frameGeneration = frame2.generation;
10631
+ const proxies = /* @__PURE__ */ new WeakMap();
10632
+ const check = () => {
10633
+ if (current() !== generation) throw new Error(`LIVE_FRAME_STALE: borrowed generation ${frameGeneration} was invalidated; call liveFrame() again`);
10634
+ };
10635
+ const wrap = (value) => {
10636
+ const found = proxies.get(value);
10637
+ if (found) return found;
10638
+ const proxy = new Proxy(Array.isArray(value) ? [] : {}, {
10639
+ get(_target, property) {
10640
+ check();
10641
+ if (property === "buffer" || property === "set" || property === "fill" || property === "copyWithin" || property === "reverse" || property === "sort") {
10642
+ throw new Error(`LIVE_FRAME_READONLY: ${String(property)} does not expose the live backing store`);
10643
+ }
10644
+ const item = Reflect.get(value, property, value);
10645
+ if (typeof item === "function") {
10646
+ if (ArrayBuffer.isView(value) && property !== Symbol.iterator) throw new Error(`LIVE_FRAME_READONLY: ${String(property)} is not part of the indexed live buffer interface`);
10647
+ return (...args) => {
10648
+ check();
10649
+ const returned = Reflect.apply(item, Array.isArray(value) ? proxy : value, args);
10650
+ return returned !== null && typeof returned === "object" ? wrap(returned) : returned;
10651
+ };
10652
+ }
10653
+ return item !== null && typeof item === "object" ? wrap(item) : item;
10654
+ },
10655
+ set() {
10656
+ check();
10657
+ throw new Error("LIVE_FRAME_READONLY: live frame values cannot be assigned");
10658
+ },
10659
+ defineProperty() {
10660
+ check();
10661
+ throw new Error("LIVE_FRAME_READONLY: live frame values cannot be redefined");
10662
+ },
10663
+ deleteProperty() {
10664
+ check();
10665
+ throw new Error("LIVE_FRAME_READONLY: live frame values cannot be deleted");
10666
+ },
10667
+ preventExtensions() {
10668
+ check();
10669
+ throw new Error("LIVE_FRAME_READONLY: live frame objects cannot be reconfigured");
10670
+ },
10671
+ setPrototypeOf() {
10672
+ check();
10673
+ throw new Error("LIVE_FRAME_READONLY: live frame prototypes cannot be changed");
10674
+ },
10675
+ has(_target, property) {
10676
+ check();
10677
+ return Reflect.has(value, property);
10678
+ },
10679
+ ownKeys() {
10680
+ check();
10681
+ return Reflect.ownKeys(value);
10682
+ },
10683
+ getOwnPropertyDescriptor(_target, property) {
10684
+ check();
10685
+ if (property === "length" && Array.isArray(value)) return { value: value.length, writable: true, configurable: false, enumerable: false };
10686
+ const descriptor = Reflect.getOwnPropertyDescriptor(value, property);
10687
+ return descriptor ? { configurable: true, enumerable: descriptor.enumerable, get: () => Reflect.get(proxy, property) } : void 0;
10688
+ }
10689
+ });
10690
+ proxies.set(value, proxy);
10691
+ return proxy;
10692
+ };
10693
+ const lease = wrap(frame2);
10694
+ const drawing = liveDrawings.get(frame2);
10695
+ if (drawing) liveDrawings.set(lease, drawing);
10696
+ return lease;
10697
+ }
10698
+ class RuntimeFrames {
10699
+ model;
10700
+ pose;
10701
+ renderFrame = { order: [] };
10702
+ live;
10703
+ order;
10704
+ tint;
10705
+ templates;
10706
+ colours;
10707
+ /** Incremented when a draw refresh changes colour without a pose step. */
10708
+ revision = 0;
10709
+ previousColor = new Float64Array(4).fill(Number.NaN);
10710
+ drawn = 0;
10711
+ epoch = 0;
10712
+ constructor(source, model) {
10713
+ this.model = model ?? compileRuntime(source.doc);
10714
+ this.pose = new RuntimePose(this.model);
10715
+ this.order = new Int32Array(this.model.slots.length);
10716
+ this.tint = new Float64Array(this.model.slots.length * 4);
10717
+ const colours = [];
10718
+ const metadata = [];
10719
+ this.templates = this.model.attachments.map((attachment, index) => {
10720
+ const identity = attachment;
10721
+ if (identity.skin === void 0 || identity.placeholder === void 0) {
10722
+ 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`);
10723
+ }
10724
+ const slot = this.model.slots[attachment.slot];
10725
+ const doc = { ...source.doc, slots: source.doc.slots.map((value, si) => ({ ...value, setup: si === attachment.slot ? identity.placeholder : null })) };
10726
+ const setup = poseRawSetup(doc);
10727
+ const raw = setup.drawn.find((value) => value.slot === slot.name);
10728
+ const template = frameOf(source, setup).order.find((value) => value.slot === slot.name) ?? null;
10729
+ const colour = raw?.colour ?? [1, 1, 1, 1];
10730
+ colours.push(colour);
10731
+ if (template) {
10732
+ const vertices = numbers(this.pose.vertices, attachment.offset, attachment.count);
10733
+ if (template.kind === "region") template.corners = vertices;
10734
+ else template.vertices = vertices;
10735
+ template.tint = numbers(this.tint, attachment.slot * 4, 4);
10736
+ }
10737
+ metadata.push(Object.freeze({
10738
+ slot: slot.name,
10739
+ slotIndex: attachment.slot,
10740
+ attachment: attachment.name,
10741
+ skin: identity.skin,
10742
+ placeholder: identity.placeholder,
10743
+ kind: template?.kind ?? "none",
10744
+ region: template?.kind === "region" ? template.region : null,
10745
+ page: template?.page ?? null,
10746
+ pma: template?.pma ?? false,
10747
+ blend: slot.blend,
10748
+ offset: attachment.offset,
10749
+ count: attachment.count,
10750
+ uvs: Object.freeze(template?.uvs ?? []),
10751
+ triangles: Object.freeze(template?.kind === "mesh" ? template.triangles : []),
10752
+ dark: template?.dark ? Object.freeze(template.dark) : null,
10753
+ clip: null
10754
+ }));
10755
+ return template;
10756
+ });
10757
+ this.colours = colours;
10758
+ const owner = this;
10759
+ this.live = Object.freeze({
10760
+ get generation() {
10761
+ return owner.epoch;
10762
+ },
10763
+ get count() {
10764
+ return owner.drawn;
10765
+ },
10766
+ drawOrder: this.order,
10767
+ vertices: this.pose.vertices,
10768
+ tint: this.tint,
10769
+ attachments: Object.freeze(metadata)
10770
+ });
10771
+ liveDrawings.set(this.live, this.renderFrame);
10772
+ }
10773
+ update(generation, color, draws, poseChanged = true) {
10774
+ if (poseChanged) this.pose.computeVertices();
10775
+ let colorChanged = false;
10776
+ for (let c = 0; c < 4; c++) {
10777
+ if (!Object.is(this.previousColor[c], color[c])) colorChanged = true;
10778
+ this.previousColor[c] = color[c];
10779
+ }
10780
+ if (colorChanged) this.revision++;
10781
+ let count = 0;
10782
+ for (let si = 0; si < this.pose.shown.length; si++) {
10783
+ const ai = this.pose.shown[si];
10784
+ const template = ai < 0 ? null : this.templates[ai];
10785
+ if (!template || !this.model.active[this.model.slots[si].bone]) continue;
10786
+ const offset = si * 4;
10787
+ const colour = this.colours[ai];
10788
+ for (let c = 0; c < 4; c++) this.tint[offset + c] = this.pose.light[offset + c] * colour[c] * color[c];
10789
+ if (!draws(template.slot)) continue;
10790
+ this.order[count] = ai;
10791
+ this.renderFrame.order[count++] = template;
10792
+ }
10793
+ this.renderFrame.order.length = count;
10794
+ this.drawn = count;
10795
+ this.epoch = generation;
10796
+ }
10797
+ snapshot(draws) {
10798
+ const selected = [];
10799
+ for (let si = 0; si < this.pose.shown.length; si++) {
10800
+ const ai = this.pose.shown[si];
10801
+ const slot = ai < 0 ? null : this.templates[ai];
10802
+ if (slot && this.model.active[this.model.slots[si].bone] && (draws === void 0 || draws(slot.slot))) selected.push(slot);
10803
+ }
10804
+ const order = selected.map((slot) => {
10805
+ const common = { ...slot, tint: slot.tint.slice() };
10806
+ 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 };
10807
+ });
10808
+ return { order };
10809
+ }
10810
+ }
10811
+ function oracleLiveFrame(frame2, identities, indices) {
10812
+ let size = 0;
10813
+ for (const slot of frame2.order) size += (slot.kind === "region" ? slot.corners : slot.vertices).length;
10814
+ const vertices = new Float64Array(size);
10815
+ const tint = new Float64Array(indices.size * 4);
10816
+ const drawOrder = new Int32Array(frame2.order.length);
10817
+ let offset = 0;
10818
+ const attachments = frame2.order.map((slot, index) => {
10819
+ const points = slot.kind === "region" ? slot.corners : slot.vertices;
10820
+ const slotIndex = indices.get(slot.slot);
10821
+ if (slotIndex === void 0) throw new Error(`LIVE_FRAME_SLOT: drawn slot "${slot.slot}" has no setup index`);
10822
+ vertices.set(points, offset);
10823
+ tint.set(slot.tint, slotIndex * 4);
10824
+ drawOrder[index] = index;
10825
+ const identity = identities.get(slot.slot);
10826
+ if (!identity) throw new Error(`LIVE_FRAME_IDENTITY: drawn slot "${slot.slot}" has no coherent identity`);
10827
+ const metadata = {
10828
+ slot: slot.slot,
10829
+ slotIndex,
10830
+ attachment: identity.attachment,
10831
+ skin: identity.skin,
10832
+ placeholder: null,
10833
+ kind: slot.kind,
10834
+ region: slot.kind === "region" ? slot.region : null,
10835
+ page: slot.page,
10836
+ pma: slot.pma,
10837
+ blend: slot.blend,
10838
+ offset,
10839
+ count: points.length,
10840
+ uvs: slot.uvs,
10841
+ triangles: slot.kind === "mesh" ? slot.triangles : [],
10842
+ dark: slot.dark ?? null,
10843
+ clip: slot.clip ?? null
10844
+ };
10845
+ offset += points.length;
10846
+ return metadata;
10847
+ });
10848
+ const live = { generation: frame2.generation ?? 0, count: attachments.length, drawOrder, vertices, tint, attachments };
10849
+ liveDrawings.set(live, frame2);
10850
+ return live;
10851
+ }
8973
10852
  const DEFAULT_ENVIRONMENT = Object.freeze({ wind: Object.freeze([1, 0]), gravity: Object.freeze([0, 1]) });
8974
10853
  function setEaseOption(call, options) {
8975
10854
  const ease = options?.ease;
@@ -9296,6 +11175,14 @@ class RigPlayer {
9296
11175
  setup = null;
9297
11176
  seekCache = null;
9298
11177
  poseGeneration = 0;
11178
+ runtimeModel = null;
11179
+ runtimeFrames = null;
11180
+ runtimeReadback = null;
11181
+ runtimeCompletionClock = { trackTime: 0 };
11182
+ poserLabel = "oracle: not selected";
11183
+ oracleOnly;
11184
+ liveChecks;
11185
+ liveDisposed = false;
9299
11186
  cachedIdentity = null;
9300
11187
  cachedIdentityGen = -1;
9301
11188
  setList;
@@ -9335,6 +11222,9 @@ class RigPlayer {
9335
11222
  constructor(root, assets, options = {}) {
9336
11223
  const read = assets.document;
9337
11224
  this.read = read;
11225
+ if (options.poser !== void 0 && options.poser !== "auto" && options.poser !== "oracle") throw new Error(`poser must be auto or oracle, got ${String(options.poser)}`);
11226
+ this.oracleOnly = options.poser === "oracle";
11227
+ this.liveChecks = options.liveFrameChecks ?? false;
9338
11228
  this.skinName = options.skin;
9339
11229
  this.view = options.skin === void 0 ? underNoSkin(read.doc) : underSkin(read.doc, options.skin);
9340
11230
  this.source = frameSource(this.view, read.parsed);
@@ -9351,6 +11241,7 @@ class RigPlayer {
9351
11241
  this.poseGeneration = 0;
9352
11242
  this.cachedIdentity = null;
9353
11243
  this.active = this.create(options.mode ?? "dom");
11244
+ this.selectRuntime();
9354
11245
  }
9355
11246
  /**
9356
11247
  * How the player draws. Setting it swaps the renderer without reloading
@@ -9395,7 +11286,7 @@ class RigPlayer {
9395
11286
  }
9396
11287
  /** The player's time — the running sum of the steps since `play` (or the last `seek`). */
9397
11288
  get time() {
9398
- return this.tracks?.trackTime ?? this.track?.trackTime ?? 0;
11289
+ return this.runtimeFrames?.pose.trackTime ?? this.tracks?.trackTime ?? this.track?.trackTime ?? 0;
9399
11290
  }
9400
11291
  /**
9401
11292
  * Plays `name` on a track — track 0 by default, which is what `play(name)`
@@ -9420,6 +11311,8 @@ class RigPlayer {
9420
11311
  const plain = track === 0 && mix === 0 && alpha === 1 && !additive;
9421
11312
  if (!plain) this.beyondBase = true;
9422
11313
  if (this.tracks === null && plain && !this.trackMode) {
11314
+ if (this.tryRuntimePlay(name, loop)) return;
11315
+ this.oracle("animation not supported by the runtime");
9423
11316
  this.animation = name;
9424
11317
  this.loop = loop;
9425
11318
  this.track = openTrack(this.view, name, { loop, reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
@@ -9494,6 +11387,11 @@ class RigPlayer {
9494
11387
  }
9495
11388
  /** `track`'s current entry, or null where it plays nothing (and before `play`). */
9496
11389
  entry(track = 0) {
11390
+ if (this.runtimeFrames !== null) {
11391
+ if (track !== 0 || this.animation === null) return null;
11392
+ const pose = this.runtimeFrames.pose;
11393
+ 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 };
11394
+ }
9497
11395
  if (this.tracks !== null) {
9498
11396
  const view = this.tracks.current(track);
9499
11397
  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 };
@@ -9517,6 +11415,7 @@ class RigPlayer {
9517
11415
  * `seekStep` of time played — which `tracks: true` avoids entirely.
9518
11416
  */
9519
11417
  compose() {
11418
+ this.oracle("track composition");
9520
11419
  if (this.tracks !== null) return this.tracks;
9521
11420
  this.clearSeekCache();
9522
11421
  const tracks = openTracks(this.view, { reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
@@ -9629,6 +11528,7 @@ class RigPlayer {
9629
11528
  * there is nothing to move.
9630
11529
  */
9631
11530
  moved(motion) {
11531
+ if (this.runtimeFrames !== null && Number.isFinite(motion.x ?? 0) && Number.isFinite(motion.y ?? 0) && Number.isFinite(motion.rotation ?? 0)) return;
9632
11532
  const live = this.stepper();
9633
11533
  if (!live) return;
9634
11534
  const x = motion.x ?? 0;
@@ -9657,6 +11557,16 @@ class RigPlayer {
9657
11557
  if (this.animation === null) throw new Error("seek before play: no animation is open");
9658
11558
  if (!(time >= 0) || !Number.isFinite(time)) throw new Error(`seek time must be a finite time at or above 0, got ${String(time)}`);
9659
11559
  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");
11560
+ if (this.runtimeFrames !== null) {
11561
+ const steps2 = Math.floor(time / this.seekStep);
11562
+ let replayTime = 0;
11563
+ for (let i = 0; i < steps2; i++) replayTime += this.seekStep;
11564
+ const rest2 = time - replayTime;
11565
+ if (rest2 > 0) replayTime += rest2;
11566
+ this.runtimeFrames.pose.trackTime = replayTime;
11567
+ this.applyRuntimeTime();
11568
+ return;
11569
+ }
9660
11570
  this.easing = null;
9661
11571
  const cache = this.ensureSeekCache();
9662
11572
  const stepper = this.tracks ?? this.track;
@@ -9771,6 +11681,7 @@ class RigPlayer {
9771
11681
  }
9772
11682
  /** What is open: the tracks, the single track, or nothing before `play`. */
9773
11683
  stepper() {
11684
+ this.oracle("oracle track operation");
9774
11685
  return this.tracks ?? this.track;
9775
11686
  }
9776
11687
  /** A newly opened track or tracks answer the environment the player stands in; one never set leaves it untouched. */
@@ -9810,6 +11721,33 @@ class RigPlayer {
9810
11721
  * the events and `time` stay on the steps themselves (#103).
9811
11722
  */
9812
11723
  advance(dt) {
11724
+ if (this.runtimeFrames !== null) {
11725
+ const step2 = dt * this.timeScale;
11726
+ if (this.paused || !(step2 > 0)) return;
11727
+ if (!Number.isFinite(step2)) throw new Error(`advance step must be finite, got ${String(step2)}`);
11728
+ const before2 = this.runtimeFrames.pose.trackTime;
11729
+ const duration = this.runtimeFrames.model.animations.get(this.animation).duration;
11730
+ this.runtimeFrames.pose.trackTime += step2;
11731
+ this.lastStep = step2;
11732
+ this.applyRuntimeTime();
11733
+ const completionClock = this.runtimeCompletionClock;
11734
+ if (this.listeners.event.size > 0) {
11735
+ const events = this.runtimeFrames.pose.events.slice();
11736
+ for (const key of events) {
11737
+ const payload2 = { name: key.name, time: key.time, int: key.int, float: key.float, string: key.string, track: 0 };
11738
+ for (const listener of this.listeners.event) listener(payload2);
11739
+ }
11740
+ }
11741
+ if (this.listeners.complete.size > 0) {
11742
+ const after = completionClock.trackTime;
11743
+ const complete = this.loop ? duration === 0 || Math.floor(after / duration) > Math.floor(before2 / duration) : before2 < duration && after >= duration;
11744
+ if (complete) {
11745
+ const payload2 = { animation: this.animation, track: 0 };
11746
+ for (const listener of this.listeners.complete) listener(payload2);
11747
+ }
11748
+ }
11749
+ return;
11750
+ }
9813
11751
  if (this.pose === null) return;
9814
11752
  const step = dt * this.timeScale;
9815
11753
  if (this.paused || !(step > 0)) return;
@@ -9833,6 +11771,11 @@ class RigPlayer {
9833
11771
  }
9834
11772
  /** Draws the pose the player holds. */
9835
11773
  draw() {
11774
+ if (this.runtimeFrames !== null) {
11775
+ this.runtimeFrames.update(this.poseGeneration, this.color, this.draws, false);
11776
+ this.renderer.render(this.runtimeFrames.live);
11777
+ return;
11778
+ }
9836
11779
  if (this.pose === null) return;
9837
11780
  this.renderer.render(this.posedFrame());
9838
11781
  }
@@ -9853,14 +11796,27 @@ class RigPlayer {
9853
11796
  if (name === this.skinName) return;
9854
11797
  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");
9855
11798
  const view = name === void 0 ? underNoSkin(this.read.doc) : underSkin(this.read.doc, name);
11799
+ this.oracle("skin change");
9856
11800
  this.view = view;
9857
11801
  this.source = frameSource(view, this.read.parsed);
11802
+ this.selectRuntime();
9858
11803
  this.skinName = name;
9859
11804
  this.setup = null;
9860
11805
  this.easing = null;
9861
11806
  this.clearSeekCache();
9862
11807
  if (this.animation !== null && (this.track !== null || this.tracks !== null)) {
9863
11808
  const time = this.time;
11809
+ const animation = this.animation;
11810
+ const animationTime = this.entry(0).animationTime;
11811
+ if (this.tryRuntimePlay(animation, this.loop)) {
11812
+ const frames = this.runtimeFrames;
11813
+ frames.pose.restore(animation, time, animationTime);
11814
+ this.poseGeneration++;
11815
+ this.cachedIdentity = null;
11816
+ this.runtimeReadback = null;
11817
+ frames.update(this.poseGeneration, this.color, this.draws);
11818
+ return;
11819
+ }
9864
11820
  this.reopen();
9865
11821
  this.stepTo(time);
9866
11822
  } else if (this.rawPose !== null) {
@@ -10246,6 +12202,7 @@ class RigPlayer {
10246
12202
  * before changing anything, then applies their composition to the held pose.
10247
12203
  */
10248
12204
  activate(sets, physics, ease = 0) {
12205
+ this.oracle("attachment sets");
10249
12206
  for (let i = 0; i < sets.length; i++) {
10250
12207
  for (let j = 0; j < i; j++) {
10251
12208
  const other = this.touched(sets[j]);
@@ -10347,6 +12304,7 @@ class RigPlayer {
10347
12304
  }
10348
12305
  set adjust(fn) {
10349
12306
  if (fn !== null && typeof fn !== "function") throw new Error(`adjust must be a function or null, got ${typeof fn}`);
12307
+ this.oracle("local adjustment");
10350
12308
  this.adjustFn = fn;
10351
12309
  this.clearSeekCache();
10352
12310
  this.repose();
@@ -10359,6 +12317,10 @@ class RigPlayer {
10359
12317
  * changed, then `draw`. Nothing before `play`.
10360
12318
  */
10361
12319
  repose() {
12320
+ if (this.runtimeFrames !== null) {
12321
+ this.applyRuntimeTime();
12322
+ return;
12323
+ }
10362
12324
  this.clearSeekCache();
10363
12325
  const stepper = this.tracks ?? this.track;
10364
12326
  if (stepper === null || this.pose === null) return;
@@ -10401,7 +12363,7 @@ class RigPlayer {
10401
12363
  * mode; nothing is computed until called.
10402
12364
  */
10403
12365
  bounds() {
10404
- const frame2 = this.pose === null ? frameOf(this.source, this.posed(), this.color, this.draws) : this.posedFrame();
12366
+ const frame2 = this.runtimeFrames !== null ? this.posedFrame() : this.pose === null ? frameOf(this.source, this.posed(), this.color, this.draws) : this.posedFrame();
10405
12367
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
10406
12368
  for (const slot of frame2.order) {
10407
12369
  const points = slot.kind === "region" ? slot.corners : slot.vertices;
@@ -10433,10 +12395,110 @@ class RigPlayer {
10433
12395
  }
10434
12396
  /** The pose the reads above see: the track's, or the setup pose before `play` (posed once). */
10435
12397
  posed() {
12398
+ if (this.runtimeFrames !== null && this.animation !== null) {
12399
+ if (this.runtimeReadback === null) {
12400
+ const track = openTrack(this.view, this.animation, { loop: this.loop, reset: "animation", clock: physicsClockStep });
12401
+ this.runtimeReadback = this.time > 0 ? track.step(this.time) : track.first;
12402
+ }
12403
+ return this.runtimeReadback;
12404
+ }
10436
12405
  if (this.pose !== null) return this.pose;
10437
12406
  if (this.setup === null) this.setup = poseRawSetup(this.view);
10438
12407
  return this.activeSet === null ? this.setup : applySetToPose(this.view, this.setup, this.activeSet);
10439
12408
  }
12409
+ /** Selected poser, including the named reason when the oracle is required. */
12410
+ get poser() {
12411
+ return this.runtimeFrames !== null ? "runtime" : this.poserLabel === "runtime" ? "oracle: no runtime animation is active" : this.poserLabel;
12412
+ }
12413
+ selectRuntime() {
12414
+ this.runtimeModel = null;
12415
+ if (this.oracleOnly) {
12416
+ this.poserLabel = "oracle: explicitly selected";
12417
+ return;
12418
+ }
12419
+ try {
12420
+ this.runtimeModel = compileRuntime(this.view);
12421
+ this.poserLabel = this.trackMode ? "oracle: track composition" : this.adjustFn !== null ? "oracle: local adjustment" : "runtime";
12422
+ } catch (error) {
12423
+ if (!(error instanceof RuntimeRefusal)) throw error;
12424
+ this.poserLabel = `oracle: ${error.message}`;
12425
+ }
12426
+ }
12427
+ tryRuntimePlay(name, loop) {
12428
+ if (this.runtimeModel === null) return false;
12429
+ 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;
12430
+ if (blocked !== null) {
12431
+ this.poserLabel = `oracle: ${blocked}`;
12432
+ return false;
12433
+ }
12434
+ if (!this.runtimeModel.animations.has(name)) {
12435
+ const refused = this.runtimeModel.refused.find((value) => value.animation === name);
12436
+ if (refused) this.poserLabel = `oracle: ${new RuntimeRefusal(refused.missing).message}`;
12437
+ return false;
12438
+ }
12439
+ const frames = this.runtimeFrames ?? new RuntimeFrames(this.source, this.runtimeModel);
12440
+ frames.pose.setAnimation(name);
12441
+ frames.pose.apply(0);
12442
+ this.runtimeFrames = frames;
12443
+ this.runtimeCompletionClock = { trackTime: 0 };
12444
+ this.animation = name;
12445
+ this.loop = loop;
12446
+ this.track = null;
12447
+ this.tracks = null;
12448
+ this.pose = null;
12449
+ this.rawPose = null;
12450
+ this.poserLabel = "runtime";
12451
+ this.poseGeneration++;
12452
+ this.cachedIdentity = null;
12453
+ this.runtimeReadback = null;
12454
+ frames.update(this.poseGeneration, this.color, this.draws);
12455
+ return true;
12456
+ }
12457
+ applyRuntimeTime() {
12458
+ const frames = this.runtimeFrames;
12459
+ const duration = frames.model.animations.get(this.animation).duration;
12460
+ const time = frames.pose.trackTime;
12461
+ this.runtimeCompletionClock.trackTime = time;
12462
+ frames.pose.apply(duration === 0 ? 0 : this.loop ? time % duration : Math.min(time, duration));
12463
+ this.poseGeneration++;
12464
+ this.cachedIdentity = null;
12465
+ this.runtimeReadback = null;
12466
+ frames.update(this.poseGeneration, this.color, this.draws);
12467
+ }
12468
+ /** Materialize the old track only at an operation stage 0 cannot execute. */
12469
+ oracle(reason) {
12470
+ if (this.runtimeFrames === null || this.animation === null) return;
12471
+ const time = this.runtimeFrames.pose.trackTime;
12472
+ const generation = this.poseGeneration;
12473
+ this.runtimeFrames = null;
12474
+ this.runtimeReadback = null;
12475
+ if (this.poserLabel === "runtime") this.poserLabel = `oracle: ${reason}`;
12476
+ this.track = openTrack(this.view, this.animation, { loop: this.loop, reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
12477
+ this.setRawPose(this.track.first);
12478
+ if (time > 0) this.setRawPose(this.track.step(time));
12479
+ this.poseGeneration = generation;
12480
+ this.applyEnvironment(this.track);
12481
+ }
12482
+ /** Borrow buffers until the next pose mutation. Request posedFrame() to retain a snapshot. */
12483
+ liveFrame() {
12484
+ if (this.liveDisposed) throw new Error("LIVE_FRAME_DISPOSED: cannot borrow a frame after dispose");
12485
+ let frame2;
12486
+ if (this.runtimeFrames !== null) {
12487
+ this.runtimeFrames.update(this.poseGeneration, this.color, this.draws, false);
12488
+ frame2 = this.runtimeFrames.live;
12489
+ } else {
12490
+ const identities = new Map(this.slotIdentity().slots.map((slot) => [slot.slot, slot]));
12491
+ frame2 = oracleLiveFrame(this.posedFrame(), identities, this.source.slotIndex);
12492
+ }
12493
+ return this.liveChecks ? this.checkedFrame(frame2) : frame2;
12494
+ }
12495
+ /** Keep lease closures out of the production borrow's call environment. */
12496
+ checkedFrame(frame2) {
12497
+ const generation = this.poseGeneration;
12498
+ const state = this.runtimeFrames;
12499
+ const revision = state?.revision;
12500
+ return checkedLiveFrame(frame2, () => this.poseGeneration === generation && (state === null || state.revision === revision) ? generation : -1);
12501
+ }
10440
12502
  /** Monotonic pose generation counter matching `posedFrame().generation` and `slotIdentity().generation`. */
10441
12503
  get generation() {
10442
12504
  return this.poseGeneration;
@@ -10450,7 +12512,8 @@ class RigPlayer {
10450
12512
  */
10451
12513
  posedFrame(slots) {
10452
12514
  const draws = slots === void 0 ? this.draws : slots === null ? () => true : typeof slots === "function" ? slots : predicateOf(slots);
10453
- const frame2 = this.pose === null ? { order: [] } : frameOf(this.source, this.pose, this.color, draws);
12515
+ if (this.runtimeFrames !== null) this.runtimeFrames.update(this.poseGeneration, this.color, this.draws, false);
12516
+ const frame2 = this.runtimeFrames !== null ? this.runtimeFrames.snapshot(draws) : this.pose === null ? { order: [] } : frameOf(this.source, this.pose, this.color, draws);
10454
12517
  Object.defineProperty(frame2, "generation", {
10455
12518
  value: this.poseGeneration,
10456
12519
  enumerable: false,
@@ -10474,7 +12537,7 @@ class RigPlayer {
10474
12537
  }
10475
12538
  const setupPose = this.setup ?? (this.setup = poseRawSetup(this.view));
10476
12539
  const pose = this.posed();
10477
- const raw = this.rawPose ?? setupPose;
12540
+ const raw = this.runtimeFrames !== null ? pose : this.rawPose ?? setupPose;
10478
12541
  const shownBySlot = new Map(pose.shown.map((s) => [s.slot, s]));
10479
12542
  const setupShownSlots = new Set(setupPose.shown.map((s) => s.slot));
10480
12543
  const rawRowBySlot = new Map(raw.slots.map((r) => [r[0], r]));
@@ -10572,13 +12635,17 @@ class RigPlayer {
10572
12635
  }
10573
12636
  return result;
10574
12637
  }
10575
- if (this.track !== null && this.animation !== null) {
12638
+ if ((this.track !== null || this.runtimeFrames !== null) && this.animation !== null) {
10576
12639
  return checkAnimation(this.animation, animationTime) === true;
10577
12640
  }
10578
12641
  return false;
10579
12642
  }
10580
12643
  /** Removes the renderer's elements and hands its GPU pages back. The assets stay the caller's. */
10581
12644
  dispose() {
12645
+ this.runtimeFrames = null;
12646
+ this.runtimeReadback = null;
12647
+ this.liveDisposed = true;
12648
+ this.poseGeneration++;
10582
12649
  this.renderer.dispose();
10583
12650
  this.clearSeekCache();
10584
12651
  this.cachedIdentity = null;