spine-html 1.12.1 → 1.14.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
@@ -377,6 +377,83 @@ function timelineIdentity(slot, shown) {
377
377
  return `${shown.skin}/${slot}/${shown.placeholder}`;
378
378
  }
379
379
  const nameOf = (a) => `${a.skin}/${a.slot}/${a.attachment}`;
380
+ const identitiesOfView = /* @__PURE__ */ new WeakMap();
381
+ function readIdentity(doc, resolve, slot, placeholder) {
382
+ const s = resolve(doc, { ...slot, setup: placeholder });
383
+ if (s === null || "conflict" in s || s.record.geometry === void 0) return null;
384
+ const g = s.record.geometry;
385
+ const source = g.kind === "linkedmesh" ? doc.skins.find((k) => k.name === g.skin)?.attachments[g.slot]?.[g.source] : s.record;
386
+ const sg = source?.geometry;
387
+ return Object.freeze({
388
+ entry: Object.freeze({ slot: slot.name, bone: slot.bone, name: shownRow(s).name, placeholder: s.placeholder, skin: s.skin, geometry: g }),
389
+ identity: timelineIdentity(slot.name, s),
390
+ vertices: sg !== void 0 && sg.kind !== "region" && sg.kind !== "linkedmesh" ? sg.vertices : null,
391
+ count: source?.sequenceCount,
392
+ fillers: Object.freeze(fillingSkins(doc, slot.name, s.placeholder).map((k) => `"${k}"`))
393
+ });
394
+ }
395
+ function identityOf(doc, resolve, slot, placeholder) {
396
+ if (placeholder === null) return null;
397
+ if (resolve !== shownAttachment) return readIdentity(doc, resolve, slot, placeholder);
398
+ let slots = identitiesOfView.get(doc);
399
+ if (slots === void 0) identitiesOfView.set(doc, slots = /* @__PURE__ */ new Map());
400
+ let placeholders = slots.get(slot.name);
401
+ if (placeholders === void 0) slots.set(slot.name, placeholders = /* @__PURE__ */ new Map());
402
+ if (placeholders.has(placeholder)) return placeholders.get(placeholder);
403
+ const identity = readIdentity(doc, resolve, slot, placeholder);
404
+ placeholders.set(placeholder, identity);
405
+ return identity;
406
+ }
407
+ const attachmentsByIdentity = /* @__PURE__ */ new WeakMap();
408
+ const switchesBySlot = /* @__PURE__ */ new WeakMap();
409
+ function matchingAttachments(timelines, identity) {
410
+ let index = attachmentsByIdentity.get(timelines);
411
+ if (index === void 0) {
412
+ const rows = /* @__PURE__ */ new Map();
413
+ for (const a of timelines) {
414
+ const name = nameOf(a);
415
+ const found = rows.get(name);
416
+ if (found === void 0) rows.set(name, [a]);
417
+ else found.push(a);
418
+ }
419
+ attachmentsByIdentity.set(timelines, index = rows);
420
+ }
421
+ return index.get(identity);
422
+ }
423
+ function slotSwitches(slots, slot) {
424
+ let index = switchesBySlot.get(slots);
425
+ if (index === void 0) {
426
+ const rows = /* @__PURE__ */ new Map();
427
+ for (const target of slots) {
428
+ for (const tl of target.timelines) {
429
+ if (tl.kind !== "attachment" || tl.keys === void 0) continue;
430
+ const found = rows.get(target.name);
431
+ if (found === void 0) rows.set(target.name, [tl.keys]);
432
+ else found.push(tl.keys);
433
+ }
434
+ }
435
+ switchesBySlot.set(slots, index = rows);
436
+ }
437
+ return index.get(slot);
438
+ }
439
+ function applyAttachment(now, values2, timelines, t, blend, evalDeform, blendOf2, evalFrame) {
440
+ if (now === null || now.identity === null) return values2;
441
+ const matching = matchingAttachments(timelines, now.identity);
442
+ if (matching === void 0) return values2;
443
+ values2 ??= { deform: null, frame: null, seen: [] };
444
+ if (!values2.seen.includes(now.identity)) values2.seen.push(now.identity);
445
+ for (const a of matching) {
446
+ if (a.deform !== null && now.vertices !== null) {
447
+ const target = evalDeform(now.vertices, a.deform, t);
448
+ if (target !== null) values2.deform = blend === null ? target : blendOf2(now.vertices, values2.deform, target, blend.alpha, blend.additive);
449
+ }
450
+ if (a.sequence !== null && now.count !== void 0) {
451
+ const f = evalFrame(a.sequence, now.count, t);
452
+ if (f !== null) values2.frame = f;
453
+ }
454
+ }
455
+ return values2;
456
+ }
380
457
  function attachmentStates(doc, resolve, placeholders, sample, sliders, plant = {}, mixed) {
381
458
  const evalDeform = plant.deform ?? deformAt;
382
459
  const blendOf2 = plant.deformBlend ?? blendDeform;
@@ -389,64 +466,37 @@ function attachmentStates(doc, resolve, placeholders, sample, sliders, plant = {
389
466
  const live = gate(doc, slot, active);
390
467
  let placeholder = placeholders.get(slot.name) ?? null;
391
468
  const base = live ? mixed?.get(slot.name) : void 0;
392
- let deform = base?.deform ?? null;
393
- let frame2 = null;
394
- const seen = base === void 0 ? [] : [...base.seen];
395
- const state = () => {
396
- if (placeholder === null) return null;
397
- const s = resolve(doc, { ...slot, setup: placeholder });
398
- if (s === null || "conflict" in s || s.record.geometry === void 0) return null;
399
- const g = s.record.geometry;
400
- const source = g.kind === "linkedmesh" ? doc.skins.find((k) => k.name === g.skin)?.attachments[g.slot]?.[g.source] : s.record;
401
- const sg = source?.geometry;
402
- const vertices = sg !== void 0 && sg.kind !== "region" && sg.kind !== "linkedmesh" ? sg.vertices : null;
403
- return { s, identity: timelineIdentity(slot.name, s), vertices, count: source?.sequenceCount };
404
- };
405
- const apply = (timelines, t, blend) => {
406
- const now2 = state();
407
- if (now2 === null || now2.identity === null) return;
408
- for (const a of timelines) {
409
- if (nameOf(a) !== now2.identity) continue;
410
- if (!seen.includes(now2.identity)) seen.push(now2.identity);
411
- if (a.deform !== null && now2.vertices !== null) {
412
- const target = evalDeform(now2.vertices, a.deform, t);
413
- if (target !== null) deform = blend === null ? target : blendOf2(now2.vertices, deform, target, blend.alpha, blend.additive);
414
- }
415
- if (a.sequence !== null && now2.count !== void 0) {
416
- const f = evalFrame(a.sequence, now2.count, t);
417
- if (f !== null) frame2 = f;
418
- }
419
- }
420
- };
421
- if (sample !== null && live) apply(sample.timelines.attachments, sample.t, null);
422
- for (const app of live ? sliders : []) {
423
- for (const target of app.timelines.slots) {
424
- if (target.name !== slot.name) continue;
425
- for (const tl of target.timelines) {
426
- if (tl.kind !== "attachment" || tl.keys === void 0) continue;
427
- const i = keyIndexAt(tl.keys, app.at);
428
- if (i < 0) continue;
429
- const next = tl.keys[i].name ?? null;
430
- if (next !== placeholder) {
431
- deform = null;
432
- frame2 = null;
469
+ let values2 = base === void 0 || base.deform === null && base.seen.length === 0 ? null : { deform: base.deform, frame: null, seen: [...base.seen] };
470
+ let now = identityOf(doc, resolve, slot, placeholder);
471
+ if (sample !== null && live) values2 = applyAttachment(now, values2, sample.timelines.attachments, sample.t, null, evalDeform, blendOf2, evalFrame);
472
+ if (live) for (const app of sliders) {
473
+ const switches = slotSwitches(app.timelines.slots, slot.name);
474
+ if (switches !== void 0) for (const keys of switches) {
475
+ const i = keyIndexAt(keys, app.at);
476
+ if (i < 0) continue;
477
+ const next = keys[i].name ?? null;
478
+ if (next !== placeholder) {
479
+ if (values2 !== null) {
480
+ values2.deform = null;
481
+ values2.frame = null;
433
482
  }
434
483
  placeholder = next;
484
+ now = identityOf(doc, resolve, slot, placeholder);
435
485
  }
436
486
  }
437
- apply(app.timelines.attachments, app.at, { alpha: app.alpha, additive: app.additive });
487
+ values2 = applyAttachment(now, values2, app.timelines.attachments, app.at, app, evalDeform, blendOf2, evalFrame);
438
488
  }
439
- const now = state();
440
489
  if (now === null) continue;
441
- const entry = { slot: slot.name, bone: slot.bone, name: shownRow(now.s).name, placeholder: now.s.placeholder, skin: now.s.skin, geometry: now.s.record.geometry };
490
+ const entry = values2 === null || values2.deform === null && values2.frame === null ? now.entry : { ...now.entry };
442
491
  shown.push(entry);
443
- const fillers = fillingSkins(doc, slot.name, now.s.placeholder).map((k) => `"${k}"`);
444
- if (seen.length > 0 && fillers.length > 1) {
445
- why.push(`slot "${slot.name}" shows placeholder "${now.s.placeholder}", which skins ${fillers.join(", ")} fill, and ${seen.map((x) => `"${x}"`).join(", ")} key(s) it — which record --skin all shows, and so whether the timeline moves it, is the Spine file's skin order, not the model's`);
492
+ if (values2 !== null && values2.seen.length > 0 && now.fillers.length > 1) {
493
+ why.push(`slot "${slot.name}" shows placeholder "${now.entry.placeholder}", which skins ${now.fillers.join(", ")} fill, and ${values2.seen.map((x) => `"${x}"`).join(", ")} key(s) it — which record --skin all shows, and so whether the timeline moves it, is the Spine file's skin order, not the model's`);
446
494
  continue;
447
495
  }
448
- if (deform !== null) entry.deform = deform;
449
- if (frame2 !== null) entry.frame = frame2;
496
+ if (values2 !== null) {
497
+ if (values2.deform !== null) entry.deform = values2.deform;
498
+ if (values2.frame !== null) entry.frame = values2.frame;
499
+ }
450
500
  }
451
501
  return { shown, why };
452
502
  }
@@ -712,8 +762,8 @@ function clipTriangles(polygon, vertices, triangles, uvs, reading = {}) {
712
762
  }
713
763
  return { clipped, vertices: outV, uvs: outUv, triangles: outT, sources };
714
764
  }
715
- const REGION_UVS = [0, 1, 0, 0, 1, 0, 1, 1];
716
- const REGION_TRIANGLES = [0, 1, 2, 2, 3, 0];
765
+ const REGION_UVS = Object.freeze([0, 1, 0, 0, 1, 0, 1, 1]);
766
+ const REGION_TRIANGLES = Object.freeze([0, 1, 2, 2, 3, 0]);
717
767
  function poseClipped(walk, round, clip = clipTriangles, through = (shape, v, t, uv) => clipThrough(shape, v, t, uv, clip)) {
718
768
  const rows = [];
719
769
  const refused = [];
@@ -968,6 +1018,15 @@ function clipThrough(shape, vertices, triangles, uvs, clip = clipTriangles, read
968
1018
  return { clipped, vertices: outV, uvs: outUv, triangles: outT, sources };
969
1019
  }
970
1020
  const RAD$2 = RUNTIME_PI / 180;
1021
+ const meshArraysByGeometry = /* @__PURE__ */ new WeakMap();
1022
+ function meshArraysOf(mesh) {
1023
+ let kept = meshArraysByGeometry.get(mesh);
1024
+ if (kept === void 0) {
1025
+ kept = Object.freeze({ uvs: Object.freeze([...mesh.uvs]), triangles: Object.freeze([...mesh.triangles]) });
1026
+ meshArraysByGeometry.set(mesh, kept);
1027
+ }
1028
+ return kept;
1029
+ }
971
1030
  const EXACT_COORDS = (v) => v;
972
1031
  function regionCorners(region, bone2) {
973
1032
  const { width: W, height: H } = region;
@@ -1019,6 +1078,34 @@ function worldVertices(vertices, bone2, world, coords = Math.fround) {
1019
1078
  }
1020
1079
  return out;
1021
1080
  }
1081
+ function deformedWorldVertices(vertices, deform, bone2, world) {
1082
+ 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
+ let k = 0;
1092
+ for (const influences of vertices.bindings) {
1093
+ let wx = 0;
1094
+ let wy = 0;
1095
+ 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];
1100
+ k += 2;
1101
+ const weight = Math.fround(binding.weight);
1102
+ wx += (x * t.a + y * t.b + t.worldX) * weight;
1103
+ wy += (x * t.c + y * t.d + t.worldY) * weight;
1104
+ }
1105
+ out.push(wx, wy);
1106
+ }
1107
+ return out;
1108
+ }
1022
1109
  const isRecord$7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1023
1110
  const finite$1 = (v) => typeof v === "number" && Number.isFinite(v);
1024
1111
  const RECT_FIELDS = ["width", "height", "offsetX", "offsetY", "originalWidth", "originalHeight"];
@@ -1149,42 +1236,55 @@ function readPathGeometry(raw, vertices, where, problems) {
1149
1236
  if (problems.length !== before) return void 0;
1150
1237
  return { kind: "path", vertices, closed: raw.closed === true, constantSpeed: raw.constantSpeed !== false, lengths };
1151
1238
  }
1239
+ function roundedRow(values2, round) {
1240
+ for (let i = 0; i < values2.length; i++) {
1241
+ const r = round(values2[i]);
1242
+ if (Object.is(r, values2[i])) continue;
1243
+ const out = values2.slice(0, i);
1244
+ out.push(r);
1245
+ for (let j = i + 1; j < values2.length; j++) out.push(round(values2[j]));
1246
+ return out;
1247
+ }
1248
+ return values2;
1249
+ }
1152
1250
  function poseGeometry(shown, world, sourceOf, round, plant = {}, draw) {
1153
1251
  const region = plant.region ?? regionCorners;
1154
1252
  const vertices = plant.vertices ?? worldVertices;
1155
1253
  const attachments = [];
1156
1254
  const clips = [];
1157
1255
  const nulls = [];
1256
+ const walked = draw !== void 0 && shown.some((s) => s.geometry.kind === "clipping");
1158
1257
  const steps = /* @__PURE__ */ new Map();
1159
1258
  for (const s of shown) {
1160
1259
  const bone2 = world.get(s.bone);
1161
1260
  if (bone2 === void 0) throw new Error(`slot "${s.slot}": bone "${s.bone}" has no world transform`);
1162
1261
  const g = s.geometry;
1163
- const drawn = (v) => s.deform === void 0 ? vertices(v, bone2, world) : vertices(deformedVertices(v, s.deform), bone2, world, EXACT_COORDS);
1262
+ 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);
1164
1264
  if (g.kind === "region") {
1165
1265
  const rect = s.frame === void 0 ? g.region.atlas : g.region.frames?.[s.frame] ?? g.region.atlas;
1166
1266
  if (rect === null) {
1167
1267
  nulls.push(`slot "${s.slot}" shows region "${s.name}" (skin "${s.skin}", placeholder "${s.placeholder}")`);
1168
1268
  continue;
1169
1269
  }
1170
- const corners = region({ ...g.region, atlas: rect }, bone2);
1171
- attachments.push([s.slot, s.name, "region", corners.map(round)]);
1172
- steps.set(s.slot, { slot: s.slot, kind: "draw", attachment: s.name, vertices: corners, triangles: [...REGION_TRIANGLES], uvs: [...REGION_UVS] });
1270
+ const corners = region(rect === g.region.atlas ? g.region : { ...g.region, atlas: rect }, bone2);
1271
+ attachments.push([s.slot, s.name, "region", roundedRow(corners, round)]);
1272
+ if (walked) steps.set(s.slot, { slot: s.slot, kind: "draw", attachment: s.name, vertices: corners, triangles: REGION_TRIANGLES, uvs: REGION_UVS });
1173
1273
  } else if (g.kind === "mesh") {
1174
1274
  const world2 = drawn(g.vertices);
1175
- attachments.push([s.slot, s.name, "mesh", world2.map(round)]);
1176
- steps.set(s.slot, { slot: s.slot, kind: "draw", attachment: s.name, vertices: world2, triangles: g.triangles, uvs: g.uvs });
1275
+ attachments.push([s.slot, s.name, "mesh", roundedRow(world2, round)]);
1276
+ if (walked) steps.set(s.slot, { slot: s.slot, kind: "draw", attachment: s.name, vertices: world2, triangles: g.triangles, uvs: g.uvs });
1177
1277
  } else if (g.kind === "linkedmesh") {
1178
1278
  const source = sourceOf(g.skin, g.slot, g.source);
1179
1279
  if (typeof source === "string") throw new Error(`slot "${s.slot}": ${source} (readModel refuses it first)`);
1180
1280
  const world2 = drawn(source);
1181
- attachments.push([s.slot, s.name, "mesh", world2.map(round)]);
1281
+ attachments.push([s.slot, s.name, "mesh", roundedRow(world2, round)]);
1182
1282
  const mesh = draw?.meshOf(g.skin, g.slot, g.source);
1183
1283
  if (draw !== void 0 && mesh === void 0) throw new Error(`slot "${s.slot}": the linked mesh's source "${g.source}" carries no triangles (readModel refuses it first)`);
1184
- if (mesh !== void 0) steps.set(s.slot, { slot: s.slot, kind: "draw", attachment: s.name, vertices: world2, triangles: mesh.triangles, uvs: mesh.uvs });
1284
+ if (walked && mesh !== void 0) steps.set(s.slot, { slot: s.slot, kind: "draw", attachment: s.name, vertices: world2, triangles: mesh.triangles, uvs: mesh.uvs });
1185
1285
  } else if (g.kind === "clipping") {
1186
1286
  const polygon = drawn(g.vertices);
1187
- clips.push([s.slot, s.name, g.end, polygon.map(round)]);
1287
+ clips.push([s.slot, s.name, g.end, roundedRow(polygon, round)]);
1188
1288
  steps.set(s.slot, { slot: s.slot, kind: "clip", attachment: s.name, active: draw?.active.has(s.bone) ?? false, end: g.end, polygon, inverse: g.inverse, convex: g.convex });
1189
1289
  }
1190
1290
  }
@@ -1193,7 +1293,12 @@ function poseGeometry(shown, world, sourceOf, round, plant = {}, draw) {
1193
1293
  let clippedWhy = draw === void 0 ? "the draw order was not given" : attachmentsWhy;
1194
1294
  let drawnClipped = null;
1195
1295
  let drawnClippedWhy = clippedWhy;
1196
- if (draw !== void 0 && attachmentsWhy === null) {
1296
+ if (draw !== void 0 && attachmentsWhy === null && !walked) {
1297
+ clipped = [];
1298
+ clippedWhy = null;
1299
+ drawnClipped = [];
1300
+ drawnClippedWhy = null;
1301
+ } else if (draw !== void 0 && attachmentsWhy === null) {
1197
1302
  const walk = draw.order.map((slot) => steps.get(slot) ?? { slot, kind: "none" });
1198
1303
  const posed = poseClipped(walk, round, draw.clip, draw.through);
1199
1304
  clipped = posed.rows;
@@ -2157,6 +2262,15 @@ function stepPhysics(r, w, length, ctx) {
2157
2262
  const k = 1 + (s.scaleOffset - s.scaleLag * z) * mix * r.scaleX;
2158
2263
  w.a *= k;
2159
2264
  w.c *= k;
2265
+ if (r.scaleYMode === "Uniform") {
2266
+ w.b *= k;
2267
+ w.d *= k;
2268
+ } else if (r.scaleYMode === "Volume") {
2269
+ const magnitude = Math.abs(k);
2270
+ const transverse = magnitude >= 0.7 ? 1 / magnitude : 4 - 3.67347 * magnitude;
2271
+ w.b *= transverse;
2272
+ w.d *= transverse;
2273
+ }
2160
2274
  }
2161
2275
  s.tx = l * w.a;
2162
2276
  s.ty = l * w.c;
@@ -3317,7 +3431,8 @@ function applyConstraints(bones, world, active, records, previous = null, applie
3317
3431
  if (c.kind === "physics") {
3318
3432
  if (physics !== void 0) {
3319
3433
  const w = { ...state.world.get(c.bone) };
3320
- const posed = physicsPose?.records.get(c.name) ?? c;
3434
+ const authored = physicsPose?.records.get(c.name) ?? c;
3435
+ const posed = physics.parameters?.apply(authored) ?? authored;
3321
3436
  if ((physics.step ?? stepPhysics)(posed, w, bone(state, c.bone).length ?? 0, physics)) {
3322
3437
  state.world.set(c.bone, w);
3323
3438
  changed = [c.bone];
@@ -3598,7 +3713,7 @@ function readBoneTimeline(kind, keysValue, where, problems) {
3598
3713
  }
3599
3714
  const channels = BONE_TIMELINE_CHANNELS[kind];
3600
3715
  unknownKeyFields(raw, ["time", ...channels, "curve"], at, problems);
3601
- const values = channels.map((c) => {
3716
+ const values2 = channels.map((c) => {
3602
3717
  const v = raw[c];
3603
3718
  if (typeof v !== "number" || !Number.isFinite(v)) {
3604
3719
  problems.push(`${at}: ${c} is ${JSON.stringify(v)}, not a finite number — the writer states every channel on every key`);
@@ -3606,7 +3721,7 @@ function readBoneTimeline(kind, keysValue, where, problems) {
3606
3721
  }
3607
3722
  return v;
3608
3723
  });
3609
- return { time: Math.fround(time), values: values.map(Math.fround), stated: { time, values }, curve: readCurve(raw, channels.length, i === listed.length - 1, at, problems) };
3724
+ return { time: Math.fround(time), values: values2.map(Math.fround), stated: { time, values: values2 }, curve: readCurve(raw, channels.length, i === listed.length - 1, at, problems) };
3610
3725
  });
3611
3726
  }
3612
3727
  function hexChannels(hex, count) {
@@ -3623,22 +3738,22 @@ function readSlotTimeline(kind, keysValue, where, problems) {
3623
3738
  return { time: Math.fround(time), values: [], stated: { time, values: [] }, curve: "stepped", name: typeof raw.name === "string" ? raw.name : null };
3624
3739
  }
3625
3740
  const shape = SLOT_COLOUR_TIMELINES[kind];
3626
- let values = [];
3741
+ let values2 = [];
3627
3742
  if (kind === "alpha") {
3628
3743
  unknownKeyFields(raw, ["time", "value", "curve"], at, problems);
3629
3744
  const v = raw.value;
3630
3745
  if (typeof v !== "number" || !Number.isFinite(v)) problems.push(`${at}: value is ${JSON.stringify(v)}, not a finite number`);
3631
- else values = [v];
3746
+ else values2 = [v];
3632
3747
  } else {
3633
3748
  unknownKeyFields(raw, ["time", ...shape.fields.map(([f]) => f), "curve"], at, problems);
3634
3749
  for (const [field, digits] of shape.fields) {
3635
3750
  const v = raw[field];
3636
3751
  if (!isHex(v, digits)) problems.push(`${at}: ${field} is ${JSON.stringify(v)}, not ${digits} hex digits — the one spelling the writer gives a ${kind} key's ${field}`);
3637
- else values.push(...hexChannels(v, field === "dark" || digits === 6 ? 3 : 4));
3752
+ else values2.push(...hexChannels(v, field === "dark" || digits === 6 ? 3 : 4));
3638
3753
  }
3639
3754
  }
3640
- if (values.length !== shape.channels.length) values = shape.channels.map(() => 0);
3641
- return { time: Math.fround(time), values: values.map(Math.fround), stated: { time, values }, curve: readCurve(raw, shape.channels.length, i === listed.length - 1, at, problems) };
3755
+ if (values2.length !== shape.channels.length) values2 = shape.channels.map(() => 0);
3756
+ return { time: Math.fround(time), values: values2.map(Math.fround), stated: { time, values: values2 }, curve: readCurve(raw, shape.channels.length, i === listed.length - 1, at, problems) };
3642
3757
  });
3643
3758
  }
3644
3759
  function constraintKeyTimes(value, where, problems) {
@@ -3934,7 +4049,7 @@ class StepLocals {
3934
4049
  this.write("shearY", v);
3935
4050
  }
3936
4051
  }
3937
- function adjustLocals(doc, bones, adjust) {
4052
+ function adjustLocals(doc, bones, adjust, context) {
3938
4053
  const views = /* @__PURE__ */ new Map();
3939
4054
  const names = doc.bones.map((b) => b.name);
3940
4055
  adjust({
@@ -3949,7 +4064,7 @@ function adjustLocals(doc, bones, adjust) {
3949
4064
  }
3950
4065
  return view;
3951
4066
  }
3952
- });
4067
+ }, context);
3953
4068
  }
3954
4069
  const clamp01$1 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
3955
4070
  function posedSlots(doc, timelines, t, plant = {}, sliders = [], placeholders) {
@@ -4036,7 +4151,7 @@ function posedBoneStep(doc, timelines, t, plant, constraints, sliders, step) {
4036
4151
  const bones = posedBones(doc, timelines, t, plant);
4037
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);
4038
4153
  const active = activeBones(doc);
4039
- if (step?.adjust !== void 0) adjustLocals(doc, bones, step.adjust);
4154
+ if (step?.adjustment !== void 0) adjustLocals(doc, bones, step.adjustment.callback, step.adjustment.context);
4040
4155
  let world = (plant.evaluate ?? worldTransforms)(bones, active);
4041
4156
  if (constraints !== void 0) {
4042
4157
  const setupRecords = constraintRecords(doc);
@@ -4049,7 +4164,7 @@ function posedBoneStep(doc, timelines, t, plant, constraints, sliders, step) {
4049
4164
  }
4050
4165
  function steppedLocals(doc, bones, plant, records, physicsKeyed, t, sliders, step) {
4051
4166
  const active = activeBones(doc);
4052
- if (step.adjust !== void 0) adjustLocals(doc, bones, step.adjust);
4167
+ if (step.adjustment !== void 0) adjustLocals(doc, bones, step.adjustment.callback, step.adjustment.context);
4053
4168
  let world = (plant.evaluate ?? worldTransforms)(bones, active);
4054
4169
  const stepped = stepPhysicsRecords(records(), physicsKeyed, t, active, step.ctx, step.before);
4055
4170
  world = applyConstraints(bones, world, active, plant.constraints ? plant.constraints(stepped) : stepped, null, sliders, step.ctx, void 0, solverRules(plant.solver));
@@ -4702,8 +4817,8 @@ function shownRow(shown) {
4702
4817
  return { name, path: CORE_REGION_KINDS.has(shown.record.kind) ? shown.record.path ?? name : null };
4703
4818
  }
4704
4819
  const shownOfView = /* @__PURE__ */ new WeakMap();
4705
- function shownAttachment(doc, slot) {
4706
- if (slot.setup === null) return null;
4820
+ function shownAttachment(doc, slot, placeholder = slot.setup) {
4821
+ if (placeholder === null) return null;
4707
4822
  let bySlot = shownOfView.get(doc);
4708
4823
  if (bySlot === void 0) {
4709
4824
  bySlot = /* @__PURE__ */ new Map();
@@ -4714,9 +4829,9 @@ function shownAttachment(doc, slot) {
4714
4829
  byPlaceholder = /* @__PURE__ */ new Map();
4715
4830
  bySlot.set(slot.name, byPlaceholder);
4716
4831
  }
4717
- if (byPlaceholder.has(slot.setup)) return byPlaceholder.get(slot.setup);
4718
- const resolved = resolveShown(doc, slot.name, slot.setup);
4719
- byPlaceholder.set(slot.setup, resolved);
4832
+ if (byPlaceholder.has(placeholder)) return byPlaceholder.get(placeholder);
4833
+ const resolved = resolveShown(doc, slot.name, placeholder);
4834
+ byPlaceholder.set(placeholder, resolved);
4720
4835
  return resolved;
4721
4836
  }
4722
4837
  function resolveShown(doc, slotName, placeholder) {
@@ -4907,6 +5022,13 @@ function drawnGeometry(doc, shown, world, order, plant, mode) {
4907
5022
  if (geometry.drawnClipped === null) throw new CoreInputError(`the raw pose leaves the clipped triangles out: ${geometry.drawnClippedWhy}`);
4908
5023
  return { rows: geometry.attachments, clips: geometry.clips, drawnClipped: geometry.drawnClipped };
4909
5024
  }
5025
+ function numbersOf(values2, what, mode) {
5026
+ return allNumbers(values2) ? values2 : values2.map((v) => numberOf(v, what, mode));
5027
+ }
5028
+ function allNumbers(values2) {
5029
+ for (const v of values2) if (v === null) return false;
5030
+ return true;
5031
+ }
4910
5032
  function rawDrawn(doc, shown, world, order, plant, mode = RAW_MODE) {
4911
5033
  const geometry = drawnGeometry(doc, shown, world, order, plant, mode);
4912
5034
  const rows = geometry.rows;
@@ -4923,25 +5045,56 @@ function rawDrawn(doc, shown, world, order, plant, mode = RAW_MODE) {
4923
5045
  let triangles;
4924
5046
  let hull = null;
4925
5047
  if (g.kind === "region") {
4926
- uvs = [...REGION_UVS];
4927
- triangles = [...REGION_TRIANGLES];
5048
+ uvs = REGION_UVS;
5049
+ triangles = REGION_TRIANGLES;
4928
5050
  } else {
4929
5051
  const source = g.kind === "mesh" ? g : doc.skins.find((x) => x.name === g.skin)?.attachments[g.slot]?.[g.source]?.geometry;
4930
5052
  if (source === void 0 || source.kind !== "mesh") throw new CoreInputError(`slot "${s.slot}": the linked mesh's source carries no mesh`);
4931
- uvs = [...source.uvs];
4932
- triangles = [...source.triangles];
5053
+ ({ uvs, triangles } = (plant.meshArrays ?? meshArraysOf)(source));
4933
5054
  hull = source.hull ?? null;
4934
5055
  }
4935
5056
  const sequenceIndex = s.frame ?? record?.sequenceSetup ?? 0;
4936
- drawn.push({ slot: s.slot, attachment: row[1], kind: row[2], vertices: row[3].map((v) => numberOf(v, `vertex of slot "${s.slot}"`, mode)), uvs, triangles, hull, sequenceIndex, colour });
5057
+ drawn.push({ slot: s.slot, attachment: row[1], kind: row[2], vertices: numbersOf(row[3], `vertex of slot "${s.slot}"`, mode), uvs, triangles, hull, sequenceIndex, colour });
4937
5058
  }
4938
- const clips = geometry.clips.map((c) => [c[0], c[1], c[2], c[3].map((v) => numberOf(v, `clip vertex of slot "${c[0]}"`, mode))]);
4939
- const clipped = geometry.drawnClipped.map((c) => [c[0], c[1], c[2], c[3].map((v) => numberOf(v, `clipped vertex of slot "${c[0]}"`, mode)), c[4].map((v) => numberOf(v, `clipped uv of slot "${c[0]}"`, mode)), c[5]]);
5059
+ const clips = geometry.clips.map((c) => [c[0], c[1], c[2], numbersOf(c[3], `clip vertex of slot "${c[0]}"`, mode)]);
5060
+ 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]]);
4940
5061
  return { drawn, clips, clipped };
4941
5062
  }
4942
5063
  function poseRawSetup(doc, plant = {}) {
4943
5064
  return setupPoseIn(doc, plant, RAW_MODE);
4944
5065
  }
5066
+ function sameSetupMeshDefinition(a, b) {
5067
+ if (Object.is(a, b)) return true;
5068
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
5069
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
5070
+ if (Array.isArray(a) && Array.isArray(b) && a.length !== b.length) return false;
5071
+ const left = a;
5072
+ const right = b;
5073
+ const keys = Object.keys(left);
5074
+ return keys.length === Object.keys(right).length && keys.every((key) => Object.prototype.hasOwnProperty.call(right, key) && sameSetupMeshDefinition(left[key], right[key]));
5075
+ }
5076
+ function readSetupMesh$1(doc, target) {
5077
+ const view = target.skin === void 0 ? doc : target.skin === null ? underNoSkin(doc) : underSkin(doc, target.skin);
5078
+ const where = `setup mesh slot ${JSON.stringify(target.slot)} attachment ${JSON.stringify(target.attachment)} under skin ${JSON.stringify(view.skin)}`;
5079
+ const slot = view.slots.find((s) => s.name === target.slot);
5080
+ if (slot === void 0) throw new CoreInputError(`${where}: no such slot`);
5081
+ const resolved = shownAttachment(view, slot, target.attachment);
5082
+ if (resolved === null) throw new CoreInputError(`${where}: no such attachment`);
5083
+ const fills = fillingSkins(view, slot.name, target.attachment);
5084
+ const definitions = fills.map((name) => view.skins.find((k) => k.name === name)?.attachments[slot.name]?.[target.attachment]);
5085
+ if ("conflict" in resolved || definitions.some((r) => !sameSetupMeshDefinition(r, definitions[0]))) {
5086
+ throw new CoreInputError(`${where}: skins [${fills.join(", ")}] fill this attachment differently; choose a named skin`);
5087
+ }
5088
+ const geometry = resolved.record.geometry;
5089
+ const mesh = geometry?.kind === "linkedmesh" ? view.skins.find((s) => s.name === geometry.skin)?.attachments[geometry.slot]?.[geometry.source]?.geometry : geometry;
5090
+ if (mesh?.kind !== "mesh") throw new CoreInputError(`${where}: ${resolved.record.kind} is not a mesh`);
5091
+ const { world, shown, drawOrder } = setupPosed(view, {}, RAW_MODE);
5092
+ const selected = shown.find((s) => s.slot === slot.name && s.placeholder === target.attachment && s.skin === resolved.skin);
5093
+ const bone2 = world.get(slot.bone);
5094
+ if (bone2 === void 0) throw new CoreInputError(`${where}: no setup transform for bone ${JSON.stringify(slot.bone)}`);
5095
+ const vertices = selected === void 0 ? numbersOf(worldVertices(mesh.vertices, bone2, world).map(rawNumber), `${where} vertex`, RAW_MODE) : rawDrawn(view, [selected], world, drawOrder, {}).drawn[0].vertices;
5096
+ return { slot: slot.name, attachment: target.attachment, skin: resolved.skin, vertices: [...vertices], triangles: [...mesh.triangles], hull: mesh.hull ?? null };
5097
+ }
4945
5098
  function setupPoseIn(doc, plant, mode) {
4946
5099
  const { world, shown, slots, drawOrder } = setupPosed(doc, plant, mode);
4947
5100
  const drawn = rawDrawn(doc, shown, world, drawOrder, plant, mode);
@@ -5015,10 +5168,11 @@ function animationOf(doc, animation) {
5015
5168
  if (anim === void 0) throw new CoreInputError(`animation "${animation}" is not one of this document's [${doc.animations.map((a) => a.name).join(", ")}]`);
5016
5169
  return anim;
5017
5170
  }
5018
- function openWalk(doc, anim, plant, reset, mode, shape, adjust) {
5171
+ function openWalk(doc, anim, plant, reset, mode, shape, adjust, before) {
5019
5172
  const why = constraintsAbsentWhy(doc) ?? pathAnimationsWhy(doc) ?? steppedPreviousPassWhy(doc);
5020
5173
  if (why !== null) throw new CoreInputError(`the raw walk leaves the bones out: ${why}`);
5021
5174
  const ctx = freshStepContext(plant.physicsStep);
5175
+ before?.(ctx);
5022
5176
  if (mode.loop && mode.wrapResets !== false) ctx.loop = true;
5023
5177
  const track = { doc, anim, duration: anim.timelines.duration, mode, plant, raw: { ...plant, ...roundPlant(mode) }, resolve: plant.shown ?? shownAttachment, shape, ctx, adjust, trackTime: 0, last: -1 };
5024
5178
  if (reset === "setup") {
@@ -5041,8 +5195,9 @@ function stepWalk(track, dt, first = false) {
5041
5195
  ctx.time += mode.clock === void 0 ? dt : mode.clock(dt, t - (first ? 0 : Math.max(last, 0)));
5042
5196
  const fire = plant.events ?? eventsFired;
5043
5197
  const keys = anim.timelines.events;
5198
+ const adjustment = adjust === void 0 ? void 0 : { callback: adjust, context: { trackTime, animation: anim.name, globalTime: trackTime } };
5044
5199
  const pose = poseStepFrom(doc, plant, resolve, shape, mode, ctx, first, trackTime, t, {
5045
- world: (sliders) => shape.rows ? posedBoneWorld(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjust }).world : posedBoneWorldAlone(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjust }),
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 }),
5046
5201
  slots: (sliders, placeholders) => posedSlots(doc, anim.timelines, t, raw, sliders, placeholders),
5047
5202
  drawOrder: anim.timelines.drawOrder,
5048
5203
  at: t,
@@ -5090,12 +5245,56 @@ function openRawTracksPath(doc, plant) {
5090
5245
  step: (first, trackTime, animationTime, posers) => poseStepFrom(doc, plant, resolve, RAW_SHAPE, RAW_MODE, ctx, first, trackTime, animationTime, posers)
5091
5246
  };
5092
5247
  }
5093
- function openRawTrack(doc, animation, plant, reset, mode, adjust) {
5094
- return openWalk(doc, animationOf(doc, animation), plant, reset, mode, RAW_SHAPE, adjust);
5248
+ function openRawTrack(doc, animation, plant, reset, mode, adjust, before) {
5249
+ return openWalk(doc, animationOf(doc, animation), plant, reset, mode, RAW_SHAPE, adjust, before);
5095
5250
  }
5096
5251
  function stepRawTrack(track, dt) {
5097
5252
  return stepWalk(track, dt);
5098
5253
  }
5254
+ const FIELDS = ["strength", "damping", "inertia", "wind", "gravity", "mix"];
5255
+ const values = (r) => ({ strength: r.strength, damping: r.damping, inertia: r.inertia, wind: r.wind, gravity: r.gravity, mix: r.mix });
5256
+ class LivePhysicsParameters {
5257
+ constructor(doc) {
5258
+ this.doc = doc;
5259
+ this.overrides = /* @__PURE__ */ new Map();
5260
+ this.evaluated = /* @__PURE__ */ new Map();
5261
+ }
5262
+ named(name) {
5263
+ const constraint = this.doc.constraints.find((c) => c.name === name);
5264
+ if (constraint === void 0) throw new CoreInputError(`physics parameters: no constraint "${String(name)}"; this document declares [${this.doc.constraints.map((c) => c.name).join(", ")}]`);
5265
+ if (constraint.kind !== "physics") throw new CoreInputError(`physics parameters: constraint "${name}" is ${constraint.kind}, not physics`);
5266
+ }
5267
+ set(name, patch) {
5268
+ this.named(name);
5269
+ if (typeof patch !== "object" || patch === null || Array.isArray(patch)) throw new CoreInputError(`physics parameters: constraint "${name}" patch is ${String(patch)}, not an object of finite fields [${FIELDS.join(", ")}]`);
5270
+ const checked = {};
5271
+ for (const key of Reflect.ownKeys(patch)) {
5272
+ if (typeof key !== "string" || !FIELDS.includes(key)) throw new CoreInputError(`physics parameters: constraint "${name}" field "${String(key)}" is unknown; expected [${FIELDS.join(", ")}]`);
5273
+ const field = key;
5274
+ const value = patch[field];
5275
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new CoreInputError(`physics parameters: constraint "${name}" field "${field}" is ${String(value)}, not a finite number`);
5276
+ checked[field] = value;
5277
+ }
5278
+ if (Reflect.ownKeys(checked).length > 0) this.overrides.set(name, { ...this.overrides.get(name), ...checked });
5279
+ }
5280
+ read(name) {
5281
+ this.named(name);
5282
+ const patch = this.overrides.get(name);
5283
+ const effective = this.evaluated.get(name);
5284
+ return { override: patch === void 0 ? null : { ...patch }, effective: effective === void 0 ? null : { ...effective } };
5285
+ }
5286
+ clear(name) {
5287
+ this.named(name);
5288
+ this.overrides.delete(name);
5289
+ }
5290
+ /** Called only for an active constraint, after the sliders and immediately before its step. */
5291
+ apply(record) {
5292
+ const patch = this.overrides.get(record.name);
5293
+ const posed = patch === void 0 ? record : { ...record, ...patch };
5294
+ this.evaluated.set(record.name, values(posed));
5295
+ return posed;
5296
+ }
5297
+ }
5099
5298
  const HELD = { loop: false, keep: false };
5100
5299
  const LOOPED = { loop: true, keep: false, live: true };
5101
5300
  function openTrack(doc, animation, options, plant = {}) {
@@ -5105,11 +5304,15 @@ function openTrack(doc, animation, options, plant = {}) {
5105
5304
  if (options.clock !== void 0 && typeof options.clock !== "function") throw new CoreInputError(`the live track's options.clock is ${typeof options.clock}, not a function`);
5106
5305
  const mode = options.loop ? LOOPED : HELD;
5107
5306
  const clock = options.clock === void 0 ? void 0 : checkedClock(options.clock, "the live track's");
5108
- const { track, first } = openRawTrack(doc, animation, plant, options.reset, clock === void 0 ? mode : { ...mode, clock: (dt) => clock(dt) }, options.adjust);
5109
- return new Track(track, first);
5307
+ const parameters = new LivePhysicsParameters(doc);
5308
+ const { track, first } = openRawTrack(doc, animation, plant, options.reset, clock === void 0 ? mode : { ...mode, clock: (dt) => clock(dt) }, options.adjust, (ctx) => {
5309
+ ctx.parameters = parameters;
5310
+ });
5311
+ return new Track(track, first, parameters);
5110
5312
  }
5111
5313
  class Track {
5112
- constructor(track, first) {
5314
+ constructor(track, first, parameters) {
5315
+ this.parameters = parameters;
5113
5316
  this.steps = 0;
5114
5317
  this.refused = null;
5115
5318
  this.track = track;
@@ -5132,6 +5335,17 @@ class Track {
5132
5335
  this.live("setEnvironment");
5133
5336
  setEnvironmentOn(this.track.ctx, environment, "the live track's");
5134
5337
  }
5338
+ setPhysicsParameters(name, patch) {
5339
+ this.live("setPhysicsParameters");
5340
+ this.parameters.set(name, patch);
5341
+ }
5342
+ readPhysicsParameters(name) {
5343
+ return this.parameters.read(name);
5344
+ }
5345
+ clearPhysicsParameters(name) {
5346
+ this.live("clearPhysicsParameters");
5347
+ this.parameters.clear(name);
5348
+ }
5135
5349
  physicsTranslate(x, y) {
5136
5350
  this.live("physicsTranslate");
5137
5351
  translateOn(this.track.ctx, x, y, "the live track's");
@@ -6063,6 +6277,8 @@ class Tracks {
6063
6277
  this.time = 0;
6064
6278
  this.refused = null;
6065
6279
  this.mixer = new Mixer(doc, plant);
6280
+ this.parameters = new LivePhysicsParameters(doc);
6281
+ this.path.ctx.parameters = this.parameters;
6066
6282
  }
6067
6283
  get trackTime() {
6068
6284
  return this.time;
@@ -6074,6 +6290,17 @@ class Tracks {
6074
6290
  this.live("setEnvironment");
6075
6291
  setEnvironmentOn(this.path.ctx, environment, "the live tracks'");
6076
6292
  }
6293
+ setPhysicsParameters(name, patch) {
6294
+ this.live("setPhysicsParameters");
6295
+ this.parameters.set(name, patch);
6296
+ }
6297
+ readPhysicsParameters(name) {
6298
+ return this.parameters.read(name);
6299
+ }
6300
+ clearPhysicsParameters(name) {
6301
+ this.live("clearPhysicsParameters");
6302
+ this.parameters.clear(name);
6303
+ }
6077
6304
  physicsTranslate(x, y) {
6078
6305
  this.live("physicsTranslate");
6079
6306
  translateOn(this.path.ctx, x, y, "the live tracks'");
@@ -6178,9 +6405,14 @@ class Tracks {
6178
6405
  ctx.time += this.clock === void 0 ? dt : this.clock(dt);
6179
6406
  const pose = new Pose(doc, this.plant);
6180
6407
  const animationTime = this.mixer.apply(pose);
6408
+ const entry = this.mixer.tracks[0] ?? null;
6409
+ const adjustment = this.adjust === void 0 ? void 0 : {
6410
+ callback: this.adjust,
6411
+ context: { trackTime: entry?.trackTime ?? null, animation: entry?.anim.name ?? null, globalTime: this.time }
6412
+ };
6181
6413
  const mixed = new Map(doc.slots.map((s, k) => [s.name, { deform: pose.slots[k].deform, seen: pose.slots[k].seen }]));
6182
6414
  return path.step(first, this.time, animationTime, {
6183
- world: (sliders) => steppedLocalsWorld(doc, pose.bones, path.raw, sliders, { ctx, before, adjust: this.adjust }),
6415
+ world: (sliders) => steppedLocalsWorld(doc, pose.bones, path.raw, sliders, { ctx, before, adjustment }),
6184
6416
  slots: (sliders, placeholders) => slotRowsFrom(doc, path.raw, sliders, placeholders, (_slot, _live) => {
6185
6417
  const s = pose.slots[doc.slots.indexOf(_slot)];
6186
6418
  return { placeholder: s.placeholder, light: s.light, dark: s.dark };
@@ -8343,6 +8575,9 @@ function affine(u0, v0, u1, v1, u2, v2, x0, y0, x1, y1, x2, y2) {
8343
8575
  const d = (eu1 * ey2 - eu2 * ey1) * inv;
8344
8576
  return [a, b, c, d, x0 - a * u0 - c * v0, y0 - b * u0 - d * v0];
8345
8577
  }
8578
+ function readSetupMesh(assets, target) {
8579
+ return readSetupMesh$1(underNoSkin(assets.document.doc), target);
8580
+ }
8346
8581
  const SETS_SPECS = ["spine-parts-sets/1", "rig-parts-sets/1"];
8347
8582
  function isRecord(value) {
8348
8583
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -8949,12 +9184,16 @@ class RigPlayer {
8949
9184
  setsByName;
8950
9185
  activeSet = null;
8951
9186
  adjustFn = null;
9187
+ /** Current preview patches, reapplied through the public core API on replay; no edit history. */
9188
+ physicsPatches = /* @__PURE__ */ new Map();
9189
+ /** Public read snapshots belonging to a restored seek pose, not the core's private evaluation cache. */
9190
+ heldParameters = null;
8952
9191
  slotFilter = null;
8953
9192
  /** `slotFilter` as a predicate; every slot when null. */
8954
9193
  draws = () => true;
8955
9194
  /** Handed to every track the player opens; reads the current `adjust` so setting it later needs no reopen. */
8956
- adjustHook = (locals) => {
8957
- if (this.adjustFn !== null) this.adjustFn(locals);
9195
+ adjustHook = (locals, ctx) => {
9196
+ if (this.adjustFn !== null) this.adjustFn(locals, ctx);
8958
9197
  };
8959
9198
  listeners = {
8960
9199
  event: /* @__PURE__ */ new Set(),
@@ -9051,7 +9290,7 @@ class RigPlayer {
9051
9290
  this.animation = name;
9052
9291
  this.loop = loop;
9053
9292
  this.track = openTrack(this.view, name, { loop, reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
9054
- this.setRawPose(this.track.first);
9293
+ this.setRawPose(this.initializeSingle(this.track));
9055
9294
  this.applyEnvironment(this.track);
9056
9295
  return;
9057
9296
  }
@@ -9157,6 +9396,7 @@ class RigPlayer {
9157
9396
  this.tracks = tracks;
9158
9397
  this.track = null;
9159
9398
  this.setRawPose(tracks.start());
9399
+ this.applyPhysicsParameters(tracks);
9160
9400
  this.applyEnvironment(tracks);
9161
9401
  if (time > 0) this.stepTo(time);
9162
9402
  return tracks;
@@ -9164,10 +9404,54 @@ class RigPlayer {
9164
9404
  /** Stores `raw` and applies `activeSet` when one is active. Zero work when `activeSet === null`. */
9165
9405
  setRawPose(raw) {
9166
9406
  this.rawPose = raw;
9407
+ this.heldParameters = null;
9167
9408
  this.pose = this.activeSet === null ? raw : applySetToPose(this.view, raw, this.activeSet);
9168
9409
  this.poseGeneration++;
9169
9410
  this.cachedIdentity = null;
9170
9411
  }
9412
+ /**
9413
+ * Sets a named physics constraint's pending patch, after keyed/slider values
9414
+ * and before the integrator on the next evaluation. No pose or clock change.
9415
+ * Unknown names/kinds/fields and non-finite values are refused atomically by
9416
+ * the core. Requires a live track (call play/queue first).
9417
+ */
9418
+ setPhysicsParameters(name, patch) {
9419
+ const live = this.requirePhysicsTrack("setPhysicsParameters", name);
9420
+ live.setPhysicsParameters(name, patch);
9421
+ const standing = live.readPhysicsParameters(name).override;
9422
+ if (standing !== null) this.physicsPatches.set(name, standing);
9423
+ this.clearSeekCache();
9424
+ }
9425
+ /** Pending override and the held pose's last active evaluation, detached. Does not evaluate a pose. */
9426
+ readPhysicsParameters(name) {
9427
+ const read = this.requirePhysicsTrack("readPhysicsParameters", name).readPhysicsParameters(name);
9428
+ if (this.heldParameters === null) return read;
9429
+ const effective = this.heldParameters.get(name)?.effective ?? null;
9430
+ return { override: read.override, effective: effective === null ? null : { ...effective } };
9431
+ }
9432
+ /** Removes only the patch; authored values resume next evaluation. Physics history is retained. */
9433
+ clearPhysicsParameters(name) {
9434
+ this.requirePhysicsTrack("clearPhysicsParameters", name).clearPhysicsParameters(name);
9435
+ this.physicsPatches.delete(name);
9436
+ this.clearSeekCache();
9437
+ }
9438
+ requirePhysicsTrack(operation, name) {
9439
+ const live = this.stepper();
9440
+ if (live === null) throw new Error(`${operation}(${JSON.stringify(name)}) before play: no live track`);
9441
+ return live;
9442
+ }
9443
+ /** No private parameter state is copied into the core's seek snapshots. */
9444
+ readParameters(live) {
9445
+ return new Map(this.view.constraints.filter((c) => c.kind === "physics").map((c) => [c.name, live.readPhysicsParameters(c.name)]));
9446
+ }
9447
+ applyPhysicsParameters(live) {
9448
+ for (const [name, patch] of this.physicsPatches) live.setPhysicsParameters(name, patch);
9449
+ }
9450
+ /** Like the environment, standing patches are pending after the ordinary first pose; no extra evaluation. */
9451
+ initializeSingle(live) {
9452
+ this.applyPhysicsParameters(live);
9453
+ return live.first;
9454
+ }
9171
9455
  /**
9172
9456
  * The world the rig's physics answers (#121): `wind` and `gravity`, as
9173
9457
  * spine-core's skeleton-level `windX/windY` and `gravityX/gravityY` — x to
@@ -9246,6 +9530,7 @@ class RigPlayer {
9246
9530
  restoreStepperState(stepper, exactHit.state);
9247
9531
  this.rawPose = exactHit.rawPose;
9248
9532
  this.pose = exactHit.pose;
9533
+ this.heldParameters = exactHit.parameters;
9249
9534
  this.poseGeneration++;
9250
9535
  this.cachedIdentity = null;
9251
9536
  return;
@@ -9257,24 +9542,32 @@ class RigPlayer {
9257
9542
  if (cache.frontierSteps <= SEEK_FINE_STEP_LIMIT || cache.frontierSteps % SEEK_COARSE_STRIDE === 0) {
9258
9543
  cache.grid.set(cache.frontierSteps, {
9259
9544
  state: captureStepperState(cache.frontier),
9260
- rawPose: raw
9545
+ rawPose: raw,
9546
+ parameters: this.readParameters(cache.frontier)
9261
9547
  });
9262
9548
  }
9263
9549
  }
9550
+ let parameters;
9264
9551
  const gridHit = cache.grid.get(steps);
9265
9552
  if (gridHit !== void 0) {
9266
9553
  restoreStepperState(stepper, gridHit.state);
9267
9554
  this.rawPose = gridHit.rawPose;
9555
+ parameters = gridHit.parameters;
9268
9556
  } else {
9269
9557
  const baseStep = steps - steps % SEEK_COARSE_STRIDE;
9270
9558
  const baseSnap = cache.grid.get(baseStep);
9271
9559
  restoreStepperState(stepper, baseSnap.state);
9272
9560
  this.rawPose = baseSnap.rawPose;
9273
9561
  for (let i = baseStep; i < steps; i++) this.rawPose = stepper.step(this.seekStep);
9562
+ parameters = this.readParameters(stepper);
9274
9563
  }
9275
9564
  const rest = time - stepper.trackTime;
9276
- if (rest > 0) this.rawPose = stepper.step(rest);
9565
+ if (rest > 0) {
9566
+ this.rawPose = stepper.step(rest);
9567
+ parameters = this.readParameters(stepper);
9568
+ }
9277
9569
  if (this.rawPose !== null) this.setRawPose(this.rawPose);
9570
+ this.heldParameters = parameters;
9278
9571
  if (this.rawPose !== null && this.pose !== null) {
9279
9572
  if (cache.exact.size >= SEEK_EXACT_LIMIT) {
9280
9573
  const oldest = cache.exact.keys().next().value;
@@ -9283,7 +9576,8 @@ class RigPlayer {
9283
9576
  cache.exact.set(time, {
9284
9577
  state: captureStepperState(stepper),
9285
9578
  rawPose: this.rawPose,
9286
- pose: this.pose
9579
+ pose: this.pose,
9580
+ parameters
9287
9581
  });
9288
9582
  }
9289
9583
  }
@@ -9304,16 +9598,17 @@ class RigPlayer {
9304
9598
  const tracks = openTracks(this.view, { reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
9305
9599
  tracks.setAnimation(0, this.animation, { loop: this.loop, mixDuration: 0 });
9306
9600
  firstPose = tracks.start();
9601
+ this.applyPhysicsParameters(tracks);
9307
9602
  this.applyEnvironment(tracks);
9308
9603
  frontier = tracks;
9309
9604
  } else {
9310
9605
  const track = openTrack(this.view, this.animation, { loop: this.loop, reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
9311
- firstPose = track.first;
9606
+ firstPose = this.initializeSingle(track);
9312
9607
  this.applyEnvironment(track);
9313
9608
  frontier = track;
9314
9609
  }
9315
9610
  const grid = /* @__PURE__ */ new Map();
9316
- grid.set(0, { state: captureStepperState(frontier), rawPose: firstPose });
9611
+ grid.set(0, { state: captureStepperState(frontier), rawPose: firstPose, parameters: this.readParameters(frontier) });
9317
9612
  this.seekCache = {
9318
9613
  mode,
9319
9614
  frontier,
@@ -9331,11 +9626,12 @@ class RigPlayer {
9331
9626
  this.trackCount = 1;
9332
9627
  this.tracks = tracks;
9333
9628
  this.setRawPose(tracks.start());
9629
+ this.applyPhysicsParameters(tracks);
9334
9630
  this.applyEnvironment(tracks);
9335
9631
  return;
9336
9632
  }
9337
9633
  this.track = openTrack(this.view, this.animation, { loop: this.loop, reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
9338
- this.setRawPose(this.track.first);
9634
+ this.setRawPose(this.initializeSingle(this.track));
9339
9635
  this.applyEnvironment(this.track);
9340
9636
  }
9341
9637
  /** What is open: the tracks, the single track, or nothing before `play`. */
@@ -9867,6 +10163,7 @@ export {
9867
10163
  DomRenderer,
9868
10164
  RigPlayer,
9869
10165
  loadModel,
10166
+ readSetupMesh,
9870
10167
  revokeRegions,
9871
10168
  unpackRegions
9872
10169
  };