rig-play 1.10.0 → 1.11.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
@@ -151,7 +151,7 @@ function listedByAppliedSkin(doc, kind, name) {
151
151
  const DEFORM_CURVE_END = 0.99999999;
152
152
  const SEQUENCE_MODES = ["hold", "once", "loop", "pingpong", "onceReverse", "loopReverse", "pingpongReverse"];
153
153
  const SEQUENCE_EPSILON = 1e-5;
154
- const isRecord$9 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
154
+ const isRecord$a = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
155
155
  const finite$2 = (v) => typeof v === "number" && Number.isFinite(v);
156
156
  function deformLength(vertices) {
157
157
  return vertices.weighted ? 2 * vertices.bindings.reduce((n, b) => n + b.length, 0) : vertices.xy.length;
@@ -159,7 +159,7 @@ function deformLength(vertices) {
159
159
  function keyTimes(list, at, problems) {
160
160
  let last = -Infinity;
161
161
  list.forEach((k, i) => {
162
- if (!isRecord$9(k)) return;
162
+ if (!isRecord$a(k)) return;
163
163
  const t = k.time;
164
164
  if (!finite$2(t) || t < 0) problems.push(`${at}[${i}]: time is ${JSON.stringify(t)}, not a finite time at or after 0`);
165
165
  else if (t <= last) problems.push(`${at}[${i}]: time ${t} is not after the key before it — the writer refuses key times that do not strictly increase`);
@@ -176,7 +176,7 @@ function readDeformKeys(value, at, vertices, problems) {
176
176
  const out = [];
177
177
  value.forEach((k, i) => {
178
178
  const kat = `${at}[${i}]`;
179
- if (!isRecord$9(k)) {
179
+ if (!isRecord$a(k)) {
180
180
  problems.push(`${kat} is not an object`);
181
181
  return;
182
182
  }
@@ -215,7 +215,7 @@ function readSequenceKeys(value, at, count, problems) {
215
215
  const out = [];
216
216
  value.forEach((k, i) => {
217
217
  const kat = `${at}[${i}]`;
218
- if (!isRecord$9(k)) {
218
+ if (!isRecord$a(k)) {
219
219
  problems.push(`${kat} is not an object`);
220
220
  return;
221
221
  }
@@ -249,7 +249,7 @@ function readAttachmentTimelines(value, label, skins, problems) {
249
249
  }
250
250
  value.forEach((skinEntry, i) => {
251
251
  const sat = `${where}[${i}]`;
252
- if (!isRecord$9(skinEntry) || typeof skinEntry.name !== "string") {
252
+ if (!isRecord$a(skinEntry) || typeof skinEntry.name !== "string") {
253
253
  problems.push(`${sat} names no skin`);
254
254
  return;
255
255
  }
@@ -261,7 +261,7 @@ function readAttachmentTimelines(value, label, skins, problems) {
261
261
  }
262
262
  skinEntry.slots.forEach((slotEntry, j) => {
263
263
  const lat = `${sat}.slots[${j}]`;
264
- if (!isRecord$9(slotEntry) || typeof slotEntry.name !== "string") {
264
+ if (!isRecord$a(slotEntry) || typeof slotEntry.name !== "string") {
265
265
  problems.push(`${lat} names no slot`);
266
266
  return;
267
267
  }
@@ -272,7 +272,7 @@ function readAttachmentTimelines(value, label, skins, problems) {
272
272
  const slot = slotEntry.name;
273
273
  slotEntry.attachments.forEach((attEntry, m) => {
274
274
  const aat = `${lat}.attachments[${m}]`;
275
- if (!isRecord$9(attEntry) || typeof attEntry.name !== "string") {
275
+ if (!isRecord$a(attEntry) || typeof attEntry.name !== "string") {
276
276
  problems.push(`${aat} names no attachment`);
277
277
  return;
278
278
  }
@@ -450,7 +450,7 @@ function attachmentStates(doc, resolve, placeholders, sample, sliders, plant = {
450
450
  }
451
451
  return { shown, why };
452
452
  }
453
- const isRecord$8 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
453
+ const isRecord$9 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
454
454
  function readDrawOrderKeys(value, label, slots, problems) {
455
455
  const where = `${label}.drawOrder`;
456
456
  if (!Array.isArray(value)) {
@@ -462,7 +462,7 @@ function readDrawOrderKeys(value, label, slots, problems) {
462
462
  let last = -Infinity;
463
463
  value.forEach((k, i) => {
464
464
  const at = `${where}[${i}]`;
465
- if (!isRecord$8(k)) {
465
+ if (!isRecord$9(k)) {
466
466
  problems.push(`${at} is not an object`);
467
467
  return;
468
468
  }
@@ -478,7 +478,7 @@ function readDrawOrderKeys(value, label, slots, problems) {
478
478
  const landed = /* @__PURE__ */ new Map();
479
479
  k.offsets.forEach((o, j) => {
480
480
  const oat = `${at}.offsets[${j}]`;
481
- if (!isRecord$8(o) || typeof o.slot !== "string") {
481
+ if (!isRecord$9(o) || typeof o.slot !== "string") {
482
482
  problems.push(`${oat} names no slot`);
483
483
  return;
484
484
  }
@@ -517,7 +517,7 @@ function drawOrderAt(count, keys, t) {
517
517
  const i = keyIndexAt(keys, t);
518
518
  return i < 0 ? null : orderOf(count, keys[i].moves);
519
519
  }
520
- const isRecord$7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
520
+ const isRecord$8 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
521
521
  const EVENT_DEF_FIELDS = ["name", "int", "float", "string", "audio", "volume", "balance"];
522
522
  const EVENT_KEY_FIELDS = ["time", "name", "int", "float", "string", "volume", "balance"];
523
523
  function payload(raw, at, problems) {
@@ -545,7 +545,7 @@ function readEventDefs(value, problems) {
545
545
  }
546
546
  value.forEach((raw, i) => {
547
547
  const at = `events[${i}]`;
548
- if (!isRecord$7(raw) || typeof raw.name !== "string" || raw.name === "") {
548
+ if (!isRecord$8(raw) || typeof raw.name !== "string" || raw.name === "") {
549
549
  problems.push(`${at} names no event`);
550
550
  return;
551
551
  }
@@ -567,7 +567,7 @@ function readEventKeys(value, label, defs, problems) {
567
567
  let last = -Infinity;
568
568
  value.forEach((raw, i) => {
569
569
  const at = `${where}[${i}]`;
570
- if (!isRecord$7(raw)) {
570
+ if (!isRecord$8(raw)) {
571
571
  problems.push(`${at} is not an object`);
572
572
  return;
573
573
  }
@@ -1019,11 +1019,11 @@ function worldVertices(vertices, bone2, world, coords = Math.fround) {
1019
1019
  }
1020
1020
  return out;
1021
1021
  }
1022
- const isRecord$6 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1022
+ const isRecord$7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1023
1023
  const finite$1 = (v) => typeof v === "number" && Number.isFinite(v);
1024
1024
  const RECT_FIELDS = ["width", "height", "offsetX", "offsetY", "originalWidth", "originalHeight"];
1025
1025
  function readRect(value, where, problems) {
1026
- if (!isRecord$6(value)) {
1026
+ if (!isRecord$7(value)) {
1027
1027
  problems.push(`${where} is ${JSON.stringify(value)}, not an atlas rectangle`);
1028
1028
  return void 0;
1029
1029
  }
@@ -1033,7 +1033,7 @@ function readRect(value, where, problems) {
1033
1033
  return problems.length === before ? value : void 0;
1034
1034
  }
1035
1035
  function readVertices(value, where, bones, problems) {
1036
- if (!isRecord$6(value) || typeof value.weighted !== "boolean") {
1036
+ if (!isRecord$7(value) || typeof value.weighted !== "boolean") {
1037
1037
  problems.push(`${where} is not { weighted: false, xy } or { weighted: true, bindings }`);
1038
1038
  return void 0;
1039
1039
  }
@@ -1055,7 +1055,7 @@ function readVertices(value, where, bones, problems) {
1055
1055
  }
1056
1056
  influences.forEach((b, j) => {
1057
1057
  const at = `${where}.bindings[${i}][${j}]`;
1058
- if (!isRecord$6(b)) {
1058
+ if (!isRecord$7(b)) {
1059
1059
  problems.push(`${at} is not an object`);
1060
1060
  return;
1061
1061
  }
@@ -1078,10 +1078,10 @@ function readGeometry(raw, kind, where, bones, slots, problems) {
1078
1078
  if (hasRect === hasSequence) {
1079
1079
  problems.push(`${where}: the region carries ${hasRect ? "both an atlas rectangle and a sequence" : "neither an atlas rectangle nor a sequence"}; the writer states exactly one of the two, and a sequence holds a rectangle per frame (issue #935)`);
1080
1080
  } else if (hasRect) {
1081
- atlas = raw.atlas === null ? null : isRecord$6(raw.atlas) ? raw.atlas : void 0;
1081
+ atlas = raw.atlas === null ? null : isRecord$7(raw.atlas) ? raw.atlas : void 0;
1082
1082
  } else {
1083
1083
  const seq = raw.sequence;
1084
- if (!isRecord$6(seq)) problems.push(`${where}.sequence is not an object`);
1084
+ if (!isRecord$7(seq)) problems.push(`${where}.sequence is not an object`);
1085
1085
  else {
1086
1086
  const count = seq.count;
1087
1087
  const setup = seq.setup ?? 0;
@@ -1247,7 +1247,7 @@ const PATH_POSITION_MODES = ["fixed", "percent"];
1247
1247
  const PATH_SPACING_MODES = ["length", "fixed", "percent", "proportional"];
1248
1248
  const PATH_ROTATE_MODES = ["tangent", "chain", "chainScale"];
1249
1249
  const PATH_FIELDS = ["bones", "slot", "positionMode", "spacingMode", "rotateMode", "rotation", "position", "spacing", "mixRotate", "mixX", "mixY", "skin"];
1250
- const isRecord$5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1250
+ const isRecord$6 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1251
1251
  function fold(raw, key, modes, dflt, where, problems) {
1252
1252
  const v = raw[key];
1253
1253
  if (v === void 0) return dflt;
@@ -1325,7 +1325,7 @@ function readPathTimelines(value, label, declared, problems) {
1325
1325
  if (!Array.isArray(value)) return out;
1326
1326
  value.forEach((entry, i) => {
1327
1327
  const at = `${label}.constraints.path[${i}]`;
1328
- if (!isRecord$5(entry) || typeof entry.name !== "string") {
1328
+ if (!isRecord$6(entry) || typeof entry.name !== "string") {
1329
1329
  problems.push(`${at} names no constraint`);
1330
1330
  return;
1331
1331
  }
@@ -1337,8 +1337,8 @@ function readPathTimelines(value, label, declared, problems) {
1337
1337
  }
1338
1338
  entry.timelines.forEach((tl, j) => {
1339
1339
  const tat = `${at}.timelines[${j}]`;
1340
- if (!isRecord$5(tl) || typeof tl.name !== "string" || !(tl.name in PATH_TIMELINES)) {
1341
- problems.push(`${tat}: ${isRecord$5(tl) ? JSON.stringify(tl.name) : "it"} is not a path timeline; they are ${Object.keys(PATH_TIMELINES).join(", ")}`);
1340
+ if (!isRecord$6(tl) || typeof tl.name !== "string" || !(tl.name in PATH_TIMELINES)) {
1341
+ problems.push(`${tat}: ${isRecord$6(tl) ? JSON.stringify(tl.name) : "it"} is not a path timeline; they are ${Object.keys(PATH_TIMELINES).join(", ")}`);
1342
1342
  return;
1343
1343
  }
1344
1344
  const kind = tl.name;
@@ -1353,7 +1353,7 @@ function readPathTimelines(value, label, declared, problems) {
1353
1353
  const keys = [];
1354
1354
  rawKeys.forEach((k, n) => {
1355
1355
  const kat = `${tat}.keys[${n}]`;
1356
- if (!isRecord$5(k)) {
1356
+ if (!isRecord$6(k)) {
1357
1357
  problems.push(`${kat} is not an object`);
1358
1358
  return;
1359
1359
  }
@@ -2179,7 +2179,7 @@ function steppedPreviousPassWhy(doc) {
2179
2179
  return reads.length === 0 ? null : `${reads.map((r) => `path constraint "${r.constraint}" reads slot bone "${r.bone}" from the previous pose`).join("; ")} — under the step the previous pose is the previous step's, which the stepped walk does not carry`;
2180
2180
  }
2181
2181
  const SLIDER_FIELDS = ["animation", "additive", "loop", "mix", "bone", "property", "from", "to", "scale", "max", "local", "time", "skin"];
2182
- const isRecord$4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
2182
+ const isRecord$5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
2183
2183
  function readSliderRecord(raw, name, where, bones, animations, problems) {
2184
2184
  const before = problems.length;
2185
2185
  for (const key of Object.keys(raw)) {
@@ -2252,7 +2252,7 @@ function readSliderTimelines(value, label, declared, problems) {
2252
2252
  if (!Array.isArray(value)) return out;
2253
2253
  value.forEach((entry, i) => {
2254
2254
  const at = `${label}.constraints.slider[${i}]`;
2255
- if (!isRecord$4(entry) || typeof entry.name !== "string") {
2255
+ if (!isRecord$5(entry) || typeof entry.name !== "string") {
2256
2256
  problems.push(`${at} names no slider`);
2257
2257
  return;
2258
2258
  }
@@ -2264,8 +2264,8 @@ function readSliderTimelines(value, label, declared, problems) {
2264
2264
  }
2265
2265
  entry.timelines.forEach((raw, j) => {
2266
2266
  const tat = `${at}.timelines[${j}]`;
2267
- if (!isRecord$4(raw) || raw.name !== "time" && raw.name !== "mix") {
2268
- problems.push(`${tat}: ${JSON.stringify(isRecord$4(raw) ? raw.name : raw)} is not a slider timeline; a slider keys time and mix`);
2267
+ if (!isRecord$5(raw) || raw.name !== "time" && raw.name !== "mix") {
2268
+ problems.push(`${tat}: ${JSON.stringify(isRecord$5(raw) ? raw.name : raw)} is not a slider timeline; a slider keys time and mix`);
2269
2269
  return;
2270
2270
  }
2271
2271
  const which = raw.name;
@@ -2285,7 +2285,7 @@ function readKeys(value, at, problems) {
2285
2285
  let last = -Infinity;
2286
2286
  value.forEach((k, j) => {
2287
2287
  const kat = `${at}.keys[${j}]`;
2288
- if (!isRecord$4(k)) {
2288
+ if (!isRecord$5(k)) {
2289
2289
  problems.push(`${kat} is not an object`);
2290
2290
  return;
2291
2291
  }
@@ -2529,7 +2529,7 @@ const CONSTRAINT_FIELDS = {
2529
2529
  ik: ["bones", "target", "scaleY", "mix", "softness", "bendPositive", "compress", "stretch", "skin"],
2530
2530
  transform: ["bones", "source", "properties", "localSource", "localTarget", "additive", "clamp", "rotation", "x", "y", "scaleX", "scaleY", "shearY", "mixRotate", "mixX", "mixY", "mixScaleX", "mixScaleY", "mixShearY", "skin"]
2531
2531
  };
2532
- const isRecord$3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
2532
+ const isRecord$4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
2533
2533
  function unknownFields$1(record, known, where, problems) {
2534
2534
  for (const key of Object.keys(record)) if (!known.includes(key)) problems.push(`${where}: field "${key}" is not one this reader knows; it reads [${known.join(", ")}]`);
2535
2535
  }
@@ -2618,8 +2618,8 @@ function readConstraintRecord(raw, kind, name, where, bones, parents, problems)
2618
2618
  const source = boneRef(raw, "source", bones, where, problems);
2619
2619
  const properties = [];
2620
2620
  const driven = /* @__PURE__ */ new Set();
2621
- if (raw.properties !== void 0 && !isRecord$3(raw.properties)) problems.push(`${where}: properties is not an object`);
2622
- else if (isRecord$3(raw.properties)) {
2621
+ if (raw.properties !== void 0 && !isRecord$4(raw.properties)) problems.push(`${where}: properties is not an object`);
2622
+ else if (isRecord$4(raw.properties)) {
2623
2623
  for (const [fromName, fromRaw] of Object.entries(raw.properties)) {
2624
2624
  const at = `${where}.properties.${fromName}`;
2625
2625
  const from = TRANSFORM_PROPERTIES.find((p) => p === fromName);
@@ -2627,13 +2627,13 @@ function readConstraintRecord(raw, kind, name, where, bones, parents, problems)
2627
2627
  problems.push(`${at}: "${fromName}" is none of ${TRANSFORM_PROPERTIES.join(", ")}`);
2628
2628
  continue;
2629
2629
  }
2630
- if (!isRecord$3(fromRaw)) {
2630
+ if (!isRecord$4(fromRaw)) {
2631
2631
  problems.push(`${at} is not an object`);
2632
2632
  continue;
2633
2633
  }
2634
2634
  unknownFields$1(fromRaw, ["offset", "to"], at, problems);
2635
2635
  const to = [];
2636
- if (!isRecord$3(fromRaw.to)) problems.push(`${at}: to is not an object`);
2636
+ if (!isRecord$4(fromRaw.to)) problems.push(`${at}: to is not an object`);
2637
2637
  else {
2638
2638
  for (const [toName, toRaw] of Object.entries(fromRaw.to)) {
2639
2639
  const at2 = `${at}.to.${toName}`;
@@ -2642,7 +2642,7 @@ function readConstraintRecord(raw, kind, name, where, bones, parents, problems)
2642
2642
  problems.push(`${at2}: "${toName}" is none of ${TRANSFORM_PROPERTIES.join(", ")}`);
2643
2643
  continue;
2644
2644
  }
2645
- if (!isRecord$3(toRaw)) {
2645
+ if (!isRecord$4(toRaw)) {
2646
2646
  problems.push(`${at2} is not an object`);
2647
2647
  continue;
2648
2648
  }
@@ -2695,7 +2695,7 @@ function keyCurve(raw, channels, last, at, problems) {
2695
2695
  }
2696
2696
  function readConstraintTimelines(value, label, declared, problems) {
2697
2697
  const out = { ik: [], transform: [], path: [], physics: 0, slider: [] };
2698
- if (!isRecord$3(value)) return out;
2698
+ if (!isRecord$4(value)) return out;
2699
2699
  out.path = readPathTimelines(value.path, label, new Set(declared.filter((c) => c.kind === "path").map((c) => c.name)), problems);
2700
2700
  out.physics = physicsTimelineCount(value.physics);
2701
2701
  out.physicsKeyed = readPhysicsTimelines(value.physics, label, new Set(declared.filter((c) => c.kind === "physics").map((c) => c.name)), problems);
@@ -2705,7 +2705,7 @@ function readConstraintTimelines(value, label, declared, problems) {
2705
2705
  if (!Array.isArray(list)) continue;
2706
2706
  list.forEach((entry, i) => {
2707
2707
  const at = `${label}.constraints.${kind}[${i}]`;
2708
- if (!isRecord$3(entry) || typeof entry.name !== "string") {
2708
+ if (!isRecord$4(entry) || typeof entry.name !== "string") {
2709
2709
  problems.push(`${at} names no constraint`);
2710
2710
  return;
2711
2711
  }
@@ -2721,7 +2721,7 @@ function readConstraintTimelines(value, label, declared, problems) {
2721
2721
  const keys = [];
2722
2722
  rawKeys.forEach((k, j) => {
2723
2723
  const kat = `${at}.keys[${j}]`;
2724
- if (!isRecord$3(k)) {
2724
+ if (!isRecord$4(k)) {
2725
2725
  problems.push(`${kat} is not an object`);
2726
2726
  return;
2727
2727
  }
@@ -3539,7 +3539,7 @@ const SLOT_COLOUR_TIMELINES = {
3539
3539
  rgb2: { fields: [["light", 6], ["dark", 6]], channels: ["r", "g", "b", "r2", "g2", "b2"] }
3540
3540
  };
3541
3541
  const SLOT_TIMELINE_KINDS = ["attachment", ...Object.keys(SLOT_COLOUR_TIMELINES)];
3542
- const isRecord$2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3542
+ const isRecord$3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3543
3543
  const isHex = (v, digits) => typeof v === "string" && v.length === digits && /^[0-9a-fA-F]+$/.test(v);
3544
3544
  function keyTime(raw, where, problems) {
3545
3545
  const time = raw.time;
@@ -3558,7 +3558,7 @@ function keyList(value, where, problems) {
3558
3558
  let last = -Infinity;
3559
3559
  value.forEach((raw, i) => {
3560
3560
  const at = `${where}.keys[${i}]`;
3561
- if (!isRecord$2(raw)) {
3561
+ if (!isRecord$3(raw)) {
3562
3562
  problems.push(`${at} is not an object`);
3563
3563
  return;
3564
3564
  }
@@ -3651,7 +3651,7 @@ function constraintKeyTimes(value, where, problems) {
3651
3651
  return;
3652
3652
  }
3653
3653
  list.forEach((k, i) => {
3654
- if (!isRecord$2(k)) problems.push(`${at}[${i}] is not an object`);
3654
+ if (!isRecord$3(k)) problems.push(`${at}[${i}] is not an object`);
3655
3655
  else {
3656
3656
  const t = keyTime(k, `${at}[${i}]`, problems);
3657
3657
  if (t !== null) times2.push(Math.fround(t));
@@ -3663,9 +3663,9 @@ function constraintKeyTimes(value, where, problems) {
3663
3663
  problems.push(`${at} is not a list`);
3664
3664
  return;
3665
3665
  }
3666
- list.forEach((entry, i) => isRecord$2(entry) ? each(entry, `${at}[${i}]`) : problems.push(`${at}[${i}] is not an object`));
3666
+ list.forEach((entry, i) => isRecord$3(entry) ? each(entry, `${at}[${i}]`) : problems.push(`${at}[${i}] is not an object`));
3667
3667
  };
3668
- if (!isRecord$2(value)) problems.push(`${where} is not an object`);
3668
+ if (!isRecord$3(value)) problems.push(`${where} is not an object`);
3669
3669
  else {
3670
3670
  for (const kind of ["ik", "transform"]) named(value[kind], `${where}.${kind}`, (e, at) => keys(e.keys, `${at}.keys`));
3671
3671
  for (const kind of ["path", "physics", "slider"]) named(value[kind], `${where}.${kind}`, (e, at) => named(e.timelines, `${at}.timelines`, (tl, at2) => keys(tl.keys, `${at2}.keys`)));
@@ -3686,7 +3686,7 @@ function readAnimationTimelines(raw, label, bones, slots, problems, context) {
3686
3686
  }
3687
3687
  value.forEach((entry, i) => {
3688
3688
  const at = `${label}.${group}[${i}]`;
3689
- if (!isRecord$2(entry) || typeof entry.name !== "string") {
3689
+ if (!isRecord$3(entry) || typeof entry.name !== "string") {
3690
3690
  problems.push(`${at} names no target`);
3691
3691
  return;
3692
3692
  }
@@ -3701,7 +3701,7 @@ function readAnimationTimelines(raw, label, bones, slots, problems, context) {
3701
3701
  const target = { name, timelines: [] };
3702
3702
  entry.timelines.forEach((tl, j) => {
3703
3703
  const tat = `${where}.timelines[${j}]`;
3704
- if (!isRecord$2(tl) || typeof tl.name !== "string") {
3704
+ if (!isRecord$3(tl) || typeof tl.name !== "string") {
3705
3705
  problems.push(`${tat} names no timeline`);
3706
3706
  return;
3707
3707
  }
@@ -4144,7 +4144,7 @@ function readPages(value, problems, flags) {
4144
4144
  const out = [];
4145
4145
  value.forEach((raw, i) => {
4146
4146
  const where = `pages[${i}]`;
4147
- if (!isRecord$1(raw)) {
4147
+ if (!isRecord$2(raw)) {
4148
4148
  problems.push(`${where} is not an object`);
4149
4149
  return;
4150
4150
  }
@@ -4166,7 +4166,7 @@ function readPages(value, problems, flags) {
4166
4166
  else {
4167
4167
  raw.regions.forEach((region, j) => {
4168
4168
  const at = `${label}.regions[${j}]`;
4169
- if (!isRecord$1(region)) {
4169
+ if (!isRecord$2(region)) {
4170
4170
  problems.push(`${at} is not an object`);
4171
4171
  return;
4172
4172
  }
@@ -4191,7 +4191,7 @@ function readPages(value, problems, flags) {
4191
4191
  }
4192
4192
  function readSpineDigest(value, problems) {
4193
4193
  if (value === void 0) return "";
4194
- if (!isRecord$1(value)) {
4194
+ if (!isRecord$2(value)) {
4195
4195
  problems.push(`spine is ${JSON.stringify(value)}, not { "sha256": "<64 lowercase hex digits>" }`);
4196
4196
  return "";
4197
4197
  }
@@ -4205,7 +4205,7 @@ function readSpineDigest(value, problems) {
4205
4205
  }
4206
4206
  function readStage(value, problems) {
4207
4207
  if (value === null) return null;
4208
- if (!isRecord$1(value)) {
4208
+ if (!isRecord$2(value)) {
4209
4209
  problems.push(`stage is ${JSON.stringify(value) ?? "absent"}, not null or { x, y, width, height } — the setup-pose box the Spine header declares`);
4210
4210
  return null;
4211
4211
  }
@@ -4217,7 +4217,7 @@ function readStage(value, problems) {
4217
4217
  }
4218
4218
  const box = value.box;
4219
4219
  if (box !== void 0) {
4220
- if (!isRecord$1(box)) problems.push(`stage.box is ${JSON.stringify(box)}, not { slot, attachment }`);
4220
+ if (!isRecord$2(box)) problems.push(`stage.box is ${JSON.stringify(box)}, not { slot, attachment }`);
4221
4221
  else {
4222
4222
  unknownFields(box, CORE_STAGE_BOX_FIELDS, "stage.box", problems);
4223
4223
  for (const key of CORE_STAGE_BOX_FIELDS) if (typeof box[key] !== "string" || box[key] === "") problems.push(`stage.box: ${key} is ${JSON.stringify(box[key]) ?? "absent"}, not a non-empty string`);
@@ -4225,12 +4225,12 @@ function readStage(value, problems) {
4225
4225
  }
4226
4226
  if (problems.length !== before) return null;
4227
4227
  const stage = { x: value.x, y: value.y, width: value.width, height: value.height };
4228
- if (isRecord$1(box)) stage.box = { slot: box.slot, attachment: box.attachment };
4228
+ if (isRecord$2(box)) stage.box = { slot: box.slot, attachment: box.attachment };
4229
4229
  return stage;
4230
4230
  }
4231
4231
  function readEditorOrder(value, skins, animations, problems) {
4232
4232
  const out = { skins: [], animations: [] };
4233
- if (!isRecord$1(value)) {
4233
+ if (!isRecord$2(value)) {
4234
4234
  problems.push(`editorOrder is ${JSON.stringify(value) ?? "absent"}, not { skins, animations }`);
4235
4235
  return out;
4236
4236
  }
@@ -4256,7 +4256,7 @@ function readEditorOrder(value, skins, animations, problems) {
4256
4256
  else {
4257
4257
  value.skins.forEach((entry, i) => {
4258
4258
  const at = `editorOrder.skins[${i}]`;
4259
- if (!isRecord$1(entry) || typeof entry.name !== "string") return void problems.push(`${at} is not { name, slots }`);
4259
+ if (!isRecord$2(entry) || typeof entry.name !== "string") return void problems.push(`${at} is not { name, slots }`);
4260
4260
  unknownFields(entry, ["name", "slots"], at, problems);
4261
4261
  const slots = names(entry.slots, `${at}.slots`);
4262
4262
  const skin = skins.find((k) => k.name === entry.name);
@@ -4272,7 +4272,7 @@ function readEditorOrder(value, skins, animations, problems) {
4272
4272
  }
4273
4273
  return out;
4274
4274
  }
4275
- const isRecord$1 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
4275
+ const isRecord$2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
4276
4276
  function foldInheritMode(value) {
4277
4277
  const folded = value.length === 0 ? value : value[0].toLowerCase() + value.slice(1);
4278
4278
  return CORE_INHERIT_MODES.find((m) => m === folded) ?? null;
@@ -4291,7 +4291,7 @@ function readBones(value, problems) {
4291
4291
  const seen = /* @__PURE__ */ new Set();
4292
4292
  value.forEach((raw, i) => {
4293
4293
  const where = `bones[${i}]`;
4294
- if (!isRecord$1(raw)) {
4294
+ if (!isRecord$2(raw)) {
4295
4295
  problems.push(`${where} is not an object`);
4296
4296
  return;
4297
4297
  }
@@ -4314,7 +4314,7 @@ function readBones(value, problems) {
4314
4314
  }
4315
4315
  if (raw.skinRequired !== void 0 && typeof raw.skinRequired !== "boolean") problems.push(`${label}: skinRequired is ${JSON.stringify(raw.skinRequired)}, not a boolean`);
4316
4316
  if (raw.editor !== void 0) {
4317
- if (!isRecord$1(raw.editor)) problems.push(`${label}: editor is not an object`);
4317
+ if (!isRecord$2(raw.editor)) problems.push(`${label}: editor is not an object`);
4318
4318
  else {
4319
4319
  unknownFields(raw.editor, ["color", "icon"], `${label}.editor`, problems);
4320
4320
  for (const key of ["color", "icon"]) {
@@ -4335,7 +4335,7 @@ function readSlots(value, bones, problems) {
4335
4335
  const out = [];
4336
4336
  value.forEach((raw, i) => {
4337
4337
  const where = `slots[${i}]`;
4338
- if (!isRecord$1(raw)) {
4338
+ if (!isRecord$2(raw)) {
4339
4339
  problems.push(`${where} is not an object`);
4340
4340
  return;
4341
4341
  }
@@ -4364,14 +4364,14 @@ function readAttachments(value, bones, slots, label, problems) {
4364
4364
  for (const [slot, table] of Object.entries(value)) {
4365
4365
  const at = `${label}.attachments["${slot}"]`;
4366
4366
  if (!slots.has(slot)) problems.push(`${at}: "${slot}" is not a slot of this document`);
4367
- if (!isRecord$1(table)) {
4367
+ if (!isRecord$2(table)) {
4368
4368
  problems.push(`${at} is not an object`);
4369
4369
  continue;
4370
4370
  }
4371
4371
  const entries = {};
4372
4372
  for (const [placeholder, raw] of Object.entries(table)) {
4373
4373
  const where = `${at}["${placeholder}"]`;
4374
- if (!isRecord$1(raw)) {
4374
+ if (!isRecord$2(raw)) {
4375
4375
  problems.push(`${where} is not an object`);
4376
4376
  continue;
4377
4377
  }
@@ -4395,7 +4395,7 @@ function readAttachments(value, bones, slots, label, problems) {
4395
4395
  }
4396
4396
  const geometry = readGeometry(raw, kind, where, bones, slots, problems);
4397
4397
  if (geometry !== void 0) record.geometry = geometry;
4398
- if (isRecord$1(raw.sequence)) {
4398
+ if (isRecord$2(raw.sequence)) {
4399
4399
  const count = raw.sequence.count;
4400
4400
  if (typeof count === "number" && Number.isInteger(count) && count >= 1) record.sequenceCount = count;
4401
4401
  else if (kind !== "region") problems.push(`${where}.sequence: count is ${JSON.stringify(count)}, not a whole number of at least 1`);
@@ -4419,7 +4419,7 @@ function readAttachments(value, bones, slots, label, problems) {
4419
4419
  }
4420
4420
  function readAtlasRect(value, where, problems) {
4421
4421
  if (value === null) return null;
4422
- if (!isRecord$1(value)) {
4422
+ if (!isRecord$2(value)) {
4423
4423
  problems.push(`${where} is ${JSON.stringify(value)}, neither null nor an object`);
4424
4424
  return void 0;
4425
4425
  }
@@ -4439,7 +4439,7 @@ function readSkins(value, bones, slots, problems) {
4439
4439
  const out = [];
4440
4440
  value.forEach((raw, i) => {
4441
4441
  const where = `skins[${i}]`;
4442
- if (!isRecord$1(raw)) {
4442
+ if (!isRecord$2(raw)) {
4443
4443
  problems.push(`${where} is not an object`);
4444
4444
  return;
4445
4445
  }
@@ -4456,7 +4456,7 @@ function readSkins(value, bones, slots, problems) {
4456
4456
  });
4457
4457
  }
4458
4458
  const lists = Object.fromEntries(CORE_CONSTRAINT_KINDS.map((k) => [k, []]));
4459
- if (!isRecord$1(raw.constraints)) problems.push(`${label}: constraints is not an object`);
4459
+ if (!isRecord$2(raw.constraints)) problems.push(`${label}: constraints is not an object`);
4460
4460
  else {
4461
4461
  for (const [kind, list] of Object.entries(raw.constraints)) {
4462
4462
  const known = CORE_CONSTRAINT_KINDS.find((k) => k === kind);
@@ -4466,7 +4466,7 @@ function readSkins(value, bones, slots, problems) {
4466
4466
  }
4467
4467
  }
4468
4468
  let attachments = {};
4469
- if (!isRecord$1(raw.attachments)) problems.push(`${label}: attachments is not an object`);
4469
+ if (!isRecord$2(raw.attachments)) problems.push(`${label}: attachments is not an object`);
4470
4470
  else attachments = readAttachments(raw.attachments, bones, slots, label, problems);
4471
4471
  out.push({ name: typeof raw.name === "string" ? raw.name : "", bones: members, constraints: lists, attachments });
4472
4472
  });
@@ -4481,7 +4481,7 @@ function readAnimations(value, bones, slotRecords, skins, events, problems) {
4481
4481
  const out = [];
4482
4482
  value.forEach((raw, i) => {
4483
4483
  const where = `animations[${i}]`;
4484
- if (!isRecord$1(raw)) {
4484
+ if (!isRecord$2(raw)) {
4485
4485
  problems.push(`${where} is not an object`);
4486
4486
  return;
4487
4487
  }
@@ -4491,13 +4491,13 @@ function readAnimations(value, bones, slotRecords, skins, events, problems) {
4491
4491
  if (!Array.isArray(raw.slots)) problems.push(`${label}: slots is not a list`);
4492
4492
  else {
4493
4493
  raw.slots.forEach((entry, j) => {
4494
- if (!isRecord$1(entry) || typeof entry.name !== "string") problems.push(`${label}: slots[${j}] names no slot`);
4494
+ if (!isRecord$2(entry) || typeof entry.name !== "string") problems.push(`${label}: slots[${j}] names no slot`);
4495
4495
  else if (!slots.has(entry.name)) problems.push(`${label}: slots[${j}] "${entry.name}" is not a slot of this document`);
4496
4496
  else keyed.push(entry.name);
4497
4497
  });
4498
4498
  }
4499
4499
  const deforms = [];
4500
- const list = (v) => Array.isArray(v) ? v.filter(isRecord$1) : [];
4500
+ const list = (v) => Array.isArray(v) ? v.filter(isRecord$2) : [];
4501
4501
  for (const skin of list(raw.attachments)) for (const slot of list(skin.slots)) for (const att of list(slot.attachments)) if (att.deform !== void 0) deforms.push(`${String(skin.name)}/${String(slot.name)}/${String(att.name)}`);
4502
4502
  out.push({ name: typeof raw.name === "string" ? raw.name : "", slots: keyed, timelines: readAnimationTimelines(raw, label, bones, slotRecords, problems, { skins, events }), constraints: { ik: [], transform: [], path: [], physics: 0, slider: [] }, deforms });
4503
4503
  });
@@ -4514,7 +4514,7 @@ function readConstraints(value, animations, bones, slots, referenceScale, proble
4514
4514
  const out = [];
4515
4515
  value.forEach((raw, i) => {
4516
4516
  const where = `constraints[${i}]`;
4517
- if (!isRecord$1(raw)) {
4517
+ if (!isRecord$2(raw)) {
4518
4518
  problems.push(`${where} is not an object`);
4519
4519
  return;
4520
4520
  }
@@ -4541,7 +4541,7 @@ function readModel(text, where = "the model document") {
4541
4541
  } catch (err) {
4542
4542
  throw new CoreInputError(`${where}: not JSON — ${err.message}`);
4543
4543
  }
4544
- if (!isRecord$1(value)) throw new CoreInputError(`${where}: not a JSON object`);
4544
+ if (!isRecord$2(value)) throw new CoreInputError(`${where}: not a JSON object`);
4545
4545
  if (typeof value.spec !== "string" || !CORE_DOCUMENT_SPECS.includes(value.spec)) {
4546
4546
  throw new CoreInputError(
4547
4547
  `${where}: spec is ${JSON.stringify(value.spec)}, not "${CORE_DOCUMENT_SPEC}" (or "${CORE_DOCUMENT_SPEC_2}", read without its stage, editor order and page flags, or "${CORE_DOCUMENT_SPEC_1}", read without its pages too)`
@@ -4577,7 +4577,7 @@ function readModel(text, where = "the model document") {
4577
4577
  }
4578
4578
  if (Array.isArray(value.animations)) {
4579
4579
  value.animations.forEach((raw, i) => {
4580
- if (isRecord$1(raw) && animations[i] !== void 0) animations[i].constraints = readConstraintTimelines(raw.constraints, `animations[${i}] "${animations[i].name}"`, constraints, problems);
4580
+ if (isRecord$2(raw) && animations[i] !== void 0) animations[i].constraints = readConstraintTimelines(raw.constraints, `animations[${i}] "${animations[i].name}"`, constraints, problems);
4581
4581
  });
4582
4582
  }
4583
4583
  for (const c of constraints) {
@@ -6266,21 +6266,21 @@ function meshPageUvs(region, page, art, reading = {}) {
6266
6266
  function frameRegionName(path, sequence, frame2) {
6267
6267
  return path + String(sequence.start + frame2).padStart(sequence.digits, "0");
6268
6268
  }
6269
- const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
6269
+ const isRecord$1 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
6270
6270
  const whole = (v, min) => typeof v === "number" && Number.isInteger(v) && v >= min;
6271
6271
  function readUvSequences(document2) {
6272
6272
  const out = /* @__PURE__ */ new Map();
6273
6273
  const problems = [];
6274
- const skins = isRecord(document2) && Array.isArray(document2.skins) ? document2.skins : [];
6274
+ const skins = isRecord$1(document2) && Array.isArray(document2.skins) ? document2.skins : [];
6275
6275
  skins.forEach((skin, k) => {
6276
- if (!isRecord(skin) || typeof skin.name !== "string" || !isRecord(skin.attachments)) return;
6276
+ if (!isRecord$1(skin) || typeof skin.name !== "string" || !isRecord$1(skin.attachments)) return;
6277
6277
  for (const [slot, table] of Object.entries(skin.attachments)) {
6278
- if (!isRecord(table)) continue;
6278
+ if (!isRecord$1(table)) continue;
6279
6279
  for (const [placeholder, record] of Object.entries(table)) {
6280
- if (!isRecord(record) || record.sequence === void 0) continue;
6280
+ if (!isRecord$1(record) || record.sequence === void 0) continue;
6281
6281
  const where = `skins[${k}] "${skin.name}".attachments["${slot}"]["${placeholder}"].sequence`;
6282
6282
  const seq = record.sequence;
6283
- if (!isRecord(seq)) {
6283
+ if (!isRecord$1(seq)) {
6284
6284
  problems.push(`${where} is not an object`);
6285
6285
  continue;
6286
6286
  }
@@ -7003,6 +7003,17 @@ function clipNum(v) {
7003
7003
  return Math.round(v * 1e3) / 1e3;
7004
7004
  }
7005
7005
  const CLIP_COORD_LIMIT = 1e9;
7006
+ function float32HalfUlp(value) {
7007
+ if (!Number.isFinite(value)) return 0;
7008
+ return 2 ** (Math.max(-149, Math.floor(Math.log2(Math.abs(value))) - 23) - 1);
7009
+ }
7010
+ function sameMeshCoordinate(relative, cached, world, offset) {
7011
+ if (relative === cached) return true;
7012
+ if (offset === 0) return false;
7013
+ const tolerance = float32HalfUlp(world);
7014
+ const residual = Math.abs(relative - cached);
7015
+ return residual < 0.5 && residual <= tolerance;
7016
+ }
7006
7017
  const BLEND_CSS = {
7007
7018
  Normal: "",
7008
7019
  Additive: "plus-lighter",
@@ -7382,18 +7393,24 @@ class DomRenderer {
7382
7393
  dirty = true;
7383
7394
  }
7384
7395
  const rel = view.meshVertices;
7385
- for (let v = 0; v < count; v += 2) {
7386
- const x = vertices[v] - minX;
7387
- const y = vertices[v + 1] - minY;
7388
- if (rel[v] !== x || rel[v + 1] !== y) dirty = true;
7389
- rel[v] = x;
7390
- rel[v + 1] = y;
7396
+ if (!dirty) {
7397
+ const offsetX = minX - view.meshOriginX;
7398
+ const offsetY = minY - view.meshOriginY;
7399
+ for (let v = 0; v < count && !dirty; v += 2) {
7400
+ dirty = !sameMeshCoordinate(vertices[v] - minX, rel[v], vertices[v], offsetX) || !sameMeshCoordinate(vertices[v + 1] - minY, rel[v + 1], vertices[v + 1], offsetY);
7401
+ }
7391
7402
  }
7392
7403
  this.setTransform(view, `translate(${minX}px,${minY}px)`);
7393
7404
  this.applyClip(view, 1, 0, 0, 1, minX, minY, view.canvasW / ratio, view.canvasH / ratio);
7394
7405
  if (!dirty) {
7395
7406
  this.meshReuseCount++;
7396
7407
  } else {
7408
+ for (let v = 0; v < count; v += 2) {
7409
+ rel[v] = vertices[v] - minX;
7410
+ rel[v + 1] = vertices[v + 1] - minY;
7411
+ }
7412
+ view.meshOriginX = minX;
7413
+ view.meshOriginY = minY;
7397
7414
  view.meshUvs = uvs;
7398
7415
  view.meshTriangles = triangles;
7399
7416
  view.meshPage = page;
@@ -7762,6 +7779,8 @@ class DomRenderer {
7762
7779
  meshVertexCount: -1,
7763
7780
  meshBackendDrawn: "",
7764
7781
  meshVertices: new Float64Array(0),
7782
+ meshOriginX: 0,
7783
+ meshOriginY: 0,
7765
7784
  drawnIn: 0
7766
7785
  };
7767
7786
  this.views.set(slot, view);
@@ -8323,6 +8342,136 @@ function affine(u0, v0, u1, v1, u2, v2, x0, y0, x1, y1, x2, y2) {
8323
8342
  const d = (eu1 * ey2 - eu2 * ey1) * inv;
8324
8343
  return [a, b, c, d, x0 - a * u0 - c * v0, y0 - b * u0 - d * v0];
8325
8344
  }
8345
+ const SETS_SPECS = ["spine-parts-sets/1", "rig-parts-sets/1"];
8346
+ function isRecord(value) {
8347
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8348
+ }
8349
+ function readSetsText(text, where, doc) {
8350
+ let raw;
8351
+ try {
8352
+ raw = JSON.parse(text);
8353
+ } catch (error) {
8354
+ throw new Error(`${where}: not JSON — ${error.message}`);
8355
+ }
8356
+ if (!isRecord(raw)) {
8357
+ throw new Error(`${where}: sets document must be a JSON object`);
8358
+ }
8359
+ if (raw.spec !== SETS_SPECS[0] && raw.spec !== SETS_SPECS[1]) {
8360
+ throw new Error(
8361
+ `${where}: spec is ${JSON.stringify(raw.spec)}, not "${SETS_SPECS[0]}" or "${SETS_SPECS[1]}"`
8362
+ );
8363
+ }
8364
+ if (!isRecord(raw.sets) && !Array.isArray(raw.sets)) {
8365
+ throw new Error(`${where}: "sets" must be an object or an array`);
8366
+ }
8367
+ const knownSlots = new Set(doc.slots.map((slot) => slot.name));
8368
+ const attachmentsBySlot = /* @__PURE__ */ new Map();
8369
+ for (const skin of doc.skins) {
8370
+ for (const [slotName, table] of Object.entries(skin.attachments)) {
8371
+ let set = attachmentsBySlot.get(slotName);
8372
+ if (!set) {
8373
+ set = /* @__PURE__ */ new Set();
8374
+ attachmentsBySlot.set(slotName, set);
8375
+ }
8376
+ for (const attName of Object.keys(table)) set.add(attName);
8377
+ }
8378
+ }
8379
+ const entries = [];
8380
+ if (Array.isArray(raw.sets)) {
8381
+ for (let i = 0; i < raw.sets.length; i++) {
8382
+ const item = raw.sets[i];
8383
+ if (!isRecord(item) || typeof item.name !== "string" || item.name.length === 0) {
8384
+ throw new Error(`${where}: sets[${i}] must be an object with a non-empty "name"`);
8385
+ }
8386
+ entries.push([item.name, item]);
8387
+ }
8388
+ } else {
8389
+ for (const [name, item] of Object.entries(raw.sets)) {
8390
+ if (name.length === 0 || !isRecord(item)) {
8391
+ throw new Error(`${where}: set "${name}" must be an object`);
8392
+ }
8393
+ entries.push([name, item]);
8394
+ }
8395
+ }
8396
+ const out = [];
8397
+ const seenNames = /* @__PURE__ */ new Set();
8398
+ for (const [name, item] of entries) {
8399
+ if (seenNames.has(name)) {
8400
+ throw new Error(`${where}: duplicate set "${name}"`);
8401
+ }
8402
+ seenNames.add(name);
8403
+ if (!isRecord(item.slots)) {
8404
+ throw new Error(`${where}: set "${name}" must have a "slots" object`);
8405
+ }
8406
+ const slots = {};
8407
+ for (const [slotName, attName] of Object.entries(item.slots)) {
8408
+ if (!knownSlots.has(slotName)) {
8409
+ throw new Error(`${where}: set "${name}" names unknown slot "${slotName}"`);
8410
+ }
8411
+ if (typeof attName !== "string" || attName.length === 0) {
8412
+ throw new Error(`${where}: set "${name}" slot "${slotName}" attachment must be a non-empty string`);
8413
+ }
8414
+ if (!attachmentsBySlot.get(slotName)?.has(attName)) {
8415
+ throw new Error(`${where}: set "${name}" maps slot "${slotName}" to unknown attachment "${attName}"`);
8416
+ }
8417
+ slots[slotName] = attName;
8418
+ }
8419
+ const hides = [];
8420
+ if (item.hides !== void 0) {
8421
+ if (!Array.isArray(item.hides)) {
8422
+ throw new Error(`${where}: set "${name}" "hides" must be an array of slot names`);
8423
+ }
8424
+ for (const hidden of item.hides) {
8425
+ if (typeof hidden !== "string" || hidden.length === 0) {
8426
+ throw new Error(`${where}: set "${name}" "hides" entries must be non-empty strings`);
8427
+ }
8428
+ if (!knownSlots.has(hidden)) {
8429
+ throw new Error(`${where}: set "${name}" hides unknown slot "${hidden}"`);
8430
+ }
8431
+ if (Object.prototype.hasOwnProperty.call(slots, hidden)) {
8432
+ throw new Error(`${where}: set "${name}" both replaces and hides slot "${hidden}"`);
8433
+ }
8434
+ if (!hides.includes(hidden)) hides.push(hidden);
8435
+ }
8436
+ }
8437
+ out.push(
8438
+ Object.freeze({
8439
+ name,
8440
+ slots: Object.freeze(slots),
8441
+ hides: Object.freeze(hides)
8442
+ })
8443
+ );
8444
+ }
8445
+ return Object.freeze(out);
8446
+ }
8447
+ async function loadSets(fetchImpl, modelUrl, setsUrlOption, doc) {
8448
+ if (setsUrlOption === null) return Object.freeze([]);
8449
+ if (typeof setsUrlOption === "string") {
8450
+ const text2 = await fetchText(fetchImpl, setsUrlOption);
8451
+ return readSetsText(text2, setsUrlOption, doc);
8452
+ }
8453
+ const url = resolveAgainstModel("sets.json", modelUrl);
8454
+ let response;
8455
+ try {
8456
+ response = await fetchImpl(url);
8457
+ } catch {
8458
+ return Object.freeze([]);
8459
+ }
8460
+ if (!response.ok) return Object.freeze([]);
8461
+ const contentType = response.headers.get("content-type") ?? "";
8462
+ if (contentType.includes("text/html")) return Object.freeze([]);
8463
+ const text = await response.text();
8464
+ const trimmed = text.trimStart();
8465
+ if (trimmed.length === 0 || trimmed.startsWith("<")) return Object.freeze([]);
8466
+ try {
8467
+ const parsed = JSON.parse(trimmed);
8468
+ if (isRecord(parsed) && typeof parsed.spec === "string" && parsed.spec.startsWith("rigc-compiled/")) {
8469
+ return Object.freeze([]);
8470
+ }
8471
+ } catch {
8472
+ }
8473
+ return readSetsText(text, url, doc);
8474
+ }
8326
8475
  function stageOf(doc) {
8327
8476
  const stage = doc.stated?.stage;
8328
8477
  return stage ? { x: stage.x, y: stage.y, width: stage.width, height: stage.height } : null;
@@ -8386,12 +8535,14 @@ async function loadModel(options) {
8386
8535
  pageImages.set(page.name, await loadImage(resolvePage(page.name, modelUrl), crossOrigin));
8387
8536
  })
8388
8537
  );
8538
+ const sets = await loadSets(fetchImpl, modelUrl, options.setsUrl, read.doc);
8389
8539
  const regionImages = regions === "none" ? /* @__PURE__ */ new Map() : await unpackRegions(pages, pageImages, { encoding: regions });
8390
8540
  let disposed = false;
8391
8541
  return {
8392
8542
  spec: read.spec,
8393
8543
  animations: read.doc.animations.map((a) => ({ name: a.name, duration: a.timelines.duration })),
8394
8544
  skins: read.doc.skins.map((s) => s.name),
8545
+ sets,
8395
8546
  stage: stageOf(read.doc),
8396
8547
  regions,
8397
8548
  regionImages,
@@ -8547,6 +8698,84 @@ function predicateOf(names) {
8547
8698
  const allowed = new Set(names);
8548
8699
  return (slot) => allowed.has(slot);
8549
8700
  }
8701
+ function applySetToPose(view, raw, set) {
8702
+ const coveredNames = new Set(Object.keys(set.slots));
8703
+ const hiddenNames = new Set(set.hides);
8704
+ const overrideSlots = view.slots.flatMap((slot) => {
8705
+ const setup = set.slots[slot.name];
8706
+ return setup !== void 0 ? [{ ...slot, setup }] : [];
8707
+ });
8708
+ const world = new Map(
8709
+ raw.bones.map((bone2) => [
8710
+ bone2.name,
8711
+ { a: bone2.a, b: bone2.b, c: bone2.c, d: bone2.d, worldX: bone2.worldX, worldY: bone2.worldY }
8712
+ ])
8713
+ );
8714
+ const overridePose = overrideSlots.length > 0 ? poseRawSetup(
8715
+ { ...view, slots: overrideSlots, constraints: [] },
8716
+ { evaluate: () => world, constraints: () => [] }
8717
+ ) : null;
8718
+ const overRowBySlot = /* @__PURE__ */ new Map();
8719
+ const overShownBySlot = /* @__PURE__ */ new Map();
8720
+ const overDrawnBySlot = /* @__PURE__ */ new Map();
8721
+ const overClipBySlot = /* @__PURE__ */ new Map();
8722
+ if (overridePose !== null) {
8723
+ for (const row of overridePose.slots) overRowBySlot.set(row[0], row);
8724
+ for (const s of overridePose.shown) overShownBySlot.set(s.slot, s);
8725
+ for (const d of overridePose.drawn) overDrawnBySlot.set(d.slot, d);
8726
+ for (const c of overridePose.clips) overClipBySlot.set(c[0], c);
8727
+ }
8728
+ const rawShownBySlot = new Map(raw.shown.map((s) => [s.slot, s]));
8729
+ const rawDrawnBySlot = new Map(raw.drawn.map((d) => [d.slot, d]));
8730
+ const rawClipBySlot = new Map(raw.clips.map((c) => [c[0], c]));
8731
+ const hiddenSlot = (slotName) => {
8732
+ if (hiddenNames.has(slotName)) return true;
8733
+ if (slotName.endsWith("_still") && !coveredNames.has(slotName)) {
8734
+ const base = slotName.slice(0, -"_still".length);
8735
+ if (coveredNames.has(base) || hiddenNames.has(base)) return true;
8736
+ }
8737
+ return false;
8738
+ };
8739
+ const slots = raw.slots.map((rawRow) => {
8740
+ const slotName = rawRow[0];
8741
+ if (hiddenSlot(slotName)) {
8742
+ return [slotName, null, rawRow[2], rawRow[3], rawRow[4], rawRow[5], rawRow[6], null, rawRow[8]];
8743
+ }
8744
+ const overRow = overRowBySlot.get(slotName);
8745
+ if (overRow !== void 0) {
8746
+ return [slotName, overRow[1], rawRow[2], rawRow[3], rawRow[4], rawRow[5], rawRow[6], overRow[7], rawRow[8]];
8747
+ }
8748
+ return rawRow;
8749
+ });
8750
+ const shown = [];
8751
+ const drawn = [];
8752
+ const clips = [];
8753
+ for (const slotName of raw.drawOrder) {
8754
+ if (hiddenSlot(slotName)) continue;
8755
+ if (coveredNames.has(slotName)) {
8756
+ const s = overShownBySlot.get(slotName);
8757
+ if (s !== void 0) shown.push(s);
8758
+ const d = overDrawnBySlot.get(slotName);
8759
+ if (d !== void 0) drawn.push(d);
8760
+ const c = overClipBySlot.get(slotName);
8761
+ if (c !== void 0) clips.push(c);
8762
+ } else {
8763
+ const s = rawShownBySlot.get(slotName);
8764
+ if (s !== void 0) shown.push(s);
8765
+ const d = rawDrawnBySlot.get(slotName);
8766
+ if (d !== void 0) drawn.push(d);
8767
+ const c = rawClipBySlot.get(slotName);
8768
+ if (c !== void 0) clips.push(c);
8769
+ }
8770
+ }
8771
+ return {
8772
+ ...raw,
8773
+ slots,
8774
+ shown,
8775
+ drawn,
8776
+ clips
8777
+ };
8778
+ }
8550
8779
  class RigPlayer {
8551
8780
  /**
8552
8781
  * The renderer this player draws through — its knobs and counters are the
@@ -8598,7 +8827,12 @@ class RigPlayer {
8598
8827
  beyondBase = false;
8599
8828
  paused = false;
8600
8829
  pose = null;
8830
+ /** The un-overridden pose from the track/tracks (or setup pose when `setSet` is called before `play`). */
8831
+ rawPose = null;
8601
8832
  setup = null;
8833
+ setList;
8834
+ setsByName;
8835
+ activeSet = null;
8602
8836
  adjustFn = null;
8603
8837
  slotFilter = null;
8604
8838
  /** `slotFilter` as a predicate; every slot when null. */
@@ -8622,6 +8856,8 @@ class RigPlayer {
8622
8856
  if (!(this.seekStep > 0)) throw new Error(`seekStep must be a positive number of seconds, got ${String(options.seekStep)}`);
8623
8857
  this.root = root;
8624
8858
  this.assets = assets;
8859
+ this.setList = assets.sets ?? [];
8860
+ this.setsByName = new Map(this.setList.map((s) => [s.name, s]));
8625
8861
  this.meshBackend = options.meshBackend;
8626
8862
  this.adjustFn = options.adjust ?? null;
8627
8863
  if (options.slots !== void 0 && options.slots !== null) this.slots = options.slots;
@@ -8697,7 +8933,7 @@ class RigPlayer {
8697
8933
  this.animation = name;
8698
8934
  this.loop = loop;
8699
8935
  this.track = openTrack(this.view, name, { loop, reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
8700
- this.pose = this.track.first;
8936
+ this.setRawPose(this.track.first);
8701
8937
  this.applyEnvironment(this.track);
8702
8938
  return;
8703
8939
  }
@@ -8708,7 +8944,7 @@ class RigPlayer {
8708
8944
  this.animation = name;
8709
8945
  this.loop = loop;
8710
8946
  }
8711
- this.pose = tracks.step(0);
8947
+ this.setRawPose(tracks.step(0));
8712
8948
  }
8713
8949
  /**
8714
8950
  * Queues `name` after the last entry on `track` (`QueueOptions`): with
@@ -8722,7 +8958,7 @@ class RigPlayer {
8722
8958
  const tracks = this.compose();
8723
8959
  tracks.addAnimation(track, name, { loop: options.loop ?? true, mixDuration: options.mix ?? 0, delay: options.delay ?? 0, alpha: options.alpha ?? 1, additive: options.additive ?? false });
8724
8960
  this.trackCount = Math.max(this.trackCount, track + 1);
8725
- this.pose = tracks.step(0);
8961
+ this.setRawPose(tracks.step(0));
8726
8962
  }
8727
8963
  /**
8728
8964
  * Mixes `track` out to nothing over `mix` seconds — the layer or one-shot
@@ -8734,7 +8970,7 @@ class RigPlayer {
8734
8970
  const tracks = this.compose();
8735
8971
  tracks.setEmptyAnimation(track, options.mix ?? 0);
8736
8972
  this.trackCount = Math.max(this.trackCount, track + 1);
8737
- this.pose = tracks.step(0);
8973
+ this.setRawPose(tracks.step(0));
8738
8974
  }
8739
8975
  /**
8740
8976
  * Queues a mix out to nothing after the last entry on `track`, over `mix`
@@ -8747,7 +8983,7 @@ class RigPlayer {
8747
8983
  const tracks = this.compose();
8748
8984
  tracks.addEmptyAnimation(track, options.mix ?? 0, options.delay ?? 0);
8749
8985
  this.trackCount = Math.max(this.trackCount, track + 1);
8750
- this.pose = tracks.step(0);
8986
+ this.setRawPose(tracks.step(0));
8751
8987
  }
8752
8988
  /**
8753
8989
  * Sets the weight of `track`'s current entry, from 0 to 1, from the next pose
@@ -8760,7 +8996,7 @@ class RigPlayer {
8760
8996
  this.beyondBase = true;
8761
8997
  const tracks = this.compose();
8762
8998
  tracks.setAlpha(track, alpha);
8763
- this.pose = tracks.step(0);
8999
+ this.setRawPose(tracks.step(0));
8764
9000
  }
8765
9001
  /** `track`'s current entry, or null where it plays nothing (and before `play`). */
8766
9002
  entry(track = 0) {
@@ -8797,11 +9033,16 @@ class RigPlayer {
8797
9033
  }
8798
9034
  this.tracks = tracks;
8799
9035
  this.track = null;
8800
- this.pose = tracks.start();
9036
+ this.setRawPose(tracks.start());
8801
9037
  this.applyEnvironment(tracks);
8802
9038
  if (time > 0) this.stepTo(time);
8803
9039
  return tracks;
8804
9040
  }
9041
+ /** Stores `raw` and applies `activeSet` when one is active. Zero work when `activeSet === null`. */
9042
+ setRawPose(raw) {
9043
+ this.rawPose = raw;
9044
+ this.pose = this.activeSet === null ? raw : applySetToPose(this.view, raw, this.activeSet);
9045
+ }
8805
9046
  /**
8806
9047
  * The world the rig's physics answers (#121): `wind` and `gravity`, as
8807
9048
  * spine-core's skeleton-level `windX/windY` and `gravityX/gravityY` — x to
@@ -8881,12 +9122,12 @@ class RigPlayer {
8881
9122
  tracks.setAnimation(0, this.animation, { loop: this.loop, mixDuration: 0 });
8882
9123
  this.trackCount = 1;
8883
9124
  this.tracks = tracks;
8884
- this.pose = tracks.start();
9125
+ this.setRawPose(tracks.start());
8885
9126
  this.applyEnvironment(tracks);
8886
9127
  return;
8887
9128
  }
8888
9129
  this.track = openTrack(this.view, this.animation, { loop: this.loop, reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
8889
- this.pose = this.track.first;
9130
+ this.setRawPose(this.track.first);
8890
9131
  this.applyEnvironment(this.track);
8891
9132
  }
8892
9133
  /** What is open: the tracks, the single track, or nothing before `play`. */
@@ -8901,9 +9142,10 @@ class RigPlayer {
8901
9142
  stepTo(time) {
8902
9143
  const stepper = this.tracks ?? this.track;
8903
9144
  const steps = Math.floor(time / this.seekStep);
8904
- for (let i = 0; i < steps; i++) this.pose = stepper.step(this.seekStep);
9145
+ for (let i = 0; i < steps; i++) this.rawPose = stepper.step(this.seekStep);
8905
9146
  const rest = time - stepper.trackTime;
8906
- if (rest > 0) this.pose = stepper.step(rest);
9147
+ if (rest > 0) this.rawPose = stepper.step(rest);
9148
+ if (this.rawPose !== null) this.setRawPose(this.rawPose);
8907
9149
  }
8908
9150
  on(type, listener) {
8909
9151
  const set = this.listeners[type];
@@ -8936,15 +9178,17 @@ class RigPlayer {
8936
9178
  if (tracks !== null) {
8937
9179
  const before2 = [];
8938
9180
  for (let t = 0; t < this.trackCount; t++) before2.push(tracks.current(t));
8939
- this.pose = tracks.step(step);
8940
- this.dispatch(this.pose, before2, tracks);
9181
+ const raw2 = tracks.step(step);
9182
+ this.setRawPose(raw2);
9183
+ this.dispatch(raw2, before2, tracks);
8941
9184
  return;
8942
9185
  }
8943
9186
  const track = this.track;
8944
9187
  if (track === null) return;
8945
9188
  const before = track.trackTime;
8946
- this.pose = track.step(step);
8947
- this.dispatchSingle(this.pose, before, track);
9189
+ const raw = track.step(step);
9190
+ this.setRawPose(raw);
9191
+ this.dispatchSingle(raw, before, track);
8948
9192
  }
8949
9193
  /** Draws the pose the player holds. */
8950
9194
  draw() {
@@ -8976,7 +9220,59 @@ class RigPlayer {
8976
9220
  const time = this.time;
8977
9221
  this.reopen();
8978
9222
  this.stepTo(time);
9223
+ } else if (this.rawPose !== null) {
9224
+ this.setup = poseRawSetup(this.view);
9225
+ this.setRawPose(this.setup);
9226
+ }
9227
+ }
9228
+ /**
9229
+ * The named attachment sets declared in `sets.json` (`ModelAssets.sets`),
9230
+ * with the slots each covers (`slots`) and hides (`hides`). Empty (`[]`)
9231
+ * when the build has no `sets.json`.
9232
+ */
9233
+ sets() {
9234
+ return this.setList;
9235
+ }
9236
+ /** The currently active attachment set's name, or `null` when the base attachments are active. */
9237
+ get set() {
9238
+ return this.activeSet?.name ?? null;
9239
+ }
9240
+ /**
9241
+ * Switches the active attachment set (#134): every slot the set covers takes
9242
+ * the set's attachment (`null` restores the base / track attachments), and
9243
+ * every slot the set `hides` (plus any `<base>_still` slot of a covered or
9244
+ * hidden base slot) is hidden. Applied immediately to the pose the player
9245
+ * holds (including the setup pose before `play`) and after track evaluation
9246
+ * on every subsequent `advance`, so a track key on a covered slot is
9247
+ * overridden while the set is active and restored when cleared (`setSet(null)`).
9248
+ * Uncovered slots keep their track attachments, mesh deforms and sequence
9249
+ * frames untouched; when no set is active (`null`), track evaluation runs
9250
+ * with zero overhead and produces byte-identical poses.
9251
+ *
9252
+ * A name not in `sets.json` throws by name; a build without `sets.json` has
9253
+ * `sets()` empty and `setSet` throws.
9254
+ */
9255
+ setSet(name) {
9256
+ if (this.setList.length === 0) {
9257
+ throw new Error(`cannot setSet(${JSON.stringify(name)}): this model has no sets.json`);
9258
+ }
9259
+ if (name === null) {
9260
+ this.activeSet = null;
9261
+ const base2 = this.rawPose ?? (this.setup ?? (this.setup = poseRawSetup(this.view)));
9262
+ this.rawPose = base2;
9263
+ this.pose = base2;
9264
+ return;
9265
+ }
9266
+ const found = this.setsByName.get(name);
9267
+ if (found === void 0) {
9268
+ throw new Error(
9269
+ `no set named ${JSON.stringify(name)} in sets.json (available: ${this.setList.map((s) => s.name).join(", ")})`
9270
+ );
8979
9271
  }
9272
+ this.activeSet = found;
9273
+ const base = this.rawPose ?? (this.setup ?? (this.setup = poseRawSetup(this.view)));
9274
+ this.rawPose = base;
9275
+ this.pose = applySetToPose(this.view, base, found);
8980
9276
  }
8981
9277
  /**
8982
9278
  * Which slots this player draws (`SlotFilter`), or null for every slot.
@@ -9035,7 +9331,7 @@ class RigPlayer {
9035
9331
  repose() {
9036
9332
  const stepper = this.tracks ?? this.track;
9037
9333
  if (stepper === null || this.pose === null) return;
9038
- this.pose = stepper.step(0);
9334
+ this.setRawPose(stepper.step(0));
9039
9335
  }
9040
9336
  /**
9041
9337
  * Where `name` is in the pose the player holds — the pose of the last
@@ -9108,7 +9404,7 @@ class RigPlayer {
9108
9404
  posed() {
9109
9405
  if (this.pose !== null) return this.pose;
9110
9406
  if (this.setup === null) this.setup = poseRawSetup(this.view);
9111
- return this.setup;
9407
+ return this.activeSet === null ? this.setup : applySetToPose(this.view, this.setup, this.activeSet);
9112
9408
  }
9113
9409
  /**
9114
9410
  * The pose the player holds, as the frame record the renderer draws — under
@@ -9128,6 +9424,7 @@ class RigPlayer {
9128
9424
  this.tracks = null;
9129
9425
  this.track = null;
9130
9426
  this.pose = null;
9427
+ this.rawPose = null;
9131
9428
  }
9132
9429
  /** The single track's step: its events (track 0) and its complete, as every 1.x player fired them. */
9133
9430
  dispatchSingle(pose, before, track) {