@energy8platform/golem 0.2.1 → 0.5.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.
Files changed (38) hide show
  1. package/README.md +28 -2
  2. package/dist/editor.css +50 -0
  3. package/dist/editor.js +1772 -1037
  4. package/dist/lib/cli.js +1575 -366
  5. package/dist/lib/cli.js.map +4 -4
  6. package/dist/lib/editor/controls-panel.d.ts +2 -0
  7. package/dist/lib/editor/stage.d.ts +1 -0
  8. package/dist/lib/editor/store.d.ts +212 -33
  9. package/dist/lib/editor-entry.js +1575 -366
  10. package/dist/lib/editor-entry.js.map +4 -4
  11. package/dist/lib/harness.js +328 -31
  12. package/dist/lib/interactive-bones.d.ts +37 -0
  13. package/dist/lib/interactive-editor.d.ts +80 -0
  14. package/dist/lib/interactive-editor.js +744 -0
  15. package/dist/lib/interactive-editor.js.map +7 -0
  16. package/dist/lib/preview/export-video.d.ts +12 -0
  17. package/dist/lib/preview/harness.d.ts +2 -0
  18. package/dist/lib/preview/render-preview.d.ts +6 -0
  19. package/dist/lib/rig-anim.d.ts +6 -3
  20. package/dist/lib/rig-check.d.ts +2 -1
  21. package/dist/lib/rig-constraints.d.ts +63 -0
  22. package/dist/lib/rig-control-qa.d.ts +319 -0
  23. package/dist/lib/rig-controls.d.ts +13 -0
  24. package/dist/lib/rig-format.d.ts +1469 -331
  25. package/dist/lib/rig-gates.d.ts +209 -0
  26. package/dist/lib/rig-mesh.d.ts +1 -0
  27. package/dist/lib/rig-motion.d.ts +22 -0
  28. package/dist/lib/rig-qa.d.ts +35 -0
  29. package/dist/lib/rig-quality.d.ts +12 -0
  30. package/dist/lib/rig-runtime.d.ts +43 -3
  31. package/dist/lib/rig-state.d.ts +3 -0
  32. package/dist/lib/rig-tools.d.ts +5 -1
  33. package/dist/lib/runtime.d.ts +3 -1
  34. package/dist/lib/runtime.js +306 -28
  35. package/dist/lib/runtime.js.map +4 -4
  36. package/dist/lib/tools.js +1557 -348
  37. package/dist/lib/tools.js.map +4 -4
  38. package/package.json +8 -2
@@ -49413,7 +49413,7 @@ ${parts.join("\n")}
49413
49413
  var ID_RE = /^[a-z][a-z0-9_]*$/;
49414
49414
  var Id = external_exports.string().regex(ID_RE, "snake_case id");
49415
49415
  var Ease = external_exports.union([
49416
- external_exports.enum(["linear", "step", "ease_in", "ease_out", "ease_in_out", "back_out", "elastic_out"]),
49416
+ external_exports.enum(["linear", "step", "ease_in", "ease_out", "ease_in_out", "minimum_jerk", "back_out", "elastic_out"]),
49417
49417
  // cubic-bezier control points, same semantics as CSS
49418
49418
  external_exports.object({ bezier: external_exports.tuple([external_exports.number(), external_exports.number(), external_exports.number(), external_exports.number()]) })
49419
49419
  ]);
@@ -49445,6 +49445,7 @@ ${parts.join("\n")}
49445
49445
  /** where this layer came from — provenance for agents */
49446
49446
  origin: external_exports.object({
49447
49447
  sourceImage: external_exports.string().optional(),
49448
+ bboxSpace: external_exports.enum(["source", "document"]).optional(),
49448
49449
  /** bbox in source image: [x, y, w, h] */
49449
49450
  bbox: external_exports.tuple([external_exports.number(), external_exports.number(), external_exports.number(), external_exports.number()]).optional(),
49450
49451
  inpainted: external_exports.boolean().default(false),
@@ -49467,6 +49468,8 @@ ${parts.join("\n")}
49467
49468
  var Slot = external_exports.object({
49468
49469
  id: Id,
49469
49470
  bone: Id,
49471
+ /** Closed path attachment used as a moving clipping mask. */
49472
+ clip: Id.optional(),
49470
49473
  /** active attachment id; undefined = hidden */
49471
49474
  attachment: Id.optional(),
49472
49475
  alpha: external_exports.number().min(0).max(1).default(1),
@@ -49529,6 +49532,8 @@ ${parts.join("\n")}
49529
49532
  order: external_exports.number().int().default(0),
49530
49533
  bones: external_exports.array(Id).min(1),
49531
49534
  target: Id,
49535
+ iterations: external_exports.number().int().min(1).max(1e3).optional(),
49536
+ tolerance: external_exports.number().positive().optional(),
49532
49537
  mix: external_exports.number().default(1),
49533
49538
  softness: external_exports.number().default(0),
49534
49539
  bendPositive: external_exports.boolean().default(true),
@@ -49576,7 +49581,7 @@ ${parts.join("\n")}
49576
49581
  var SlotProp = external_exports.enum(["alpha", "attachment", "z", "color"]);
49577
49582
  var DeformProp = external_exports.string().regex(/^(bulge|bend|sway|squash|p[0-2][0-2][xy]|vertices)$/, "deform prop must be bulge|bend|sway|squash|p<row><col>x|y|vertices");
49578
49583
  var IkProp = external_exports.enum(["mix", "softness", "bendPositive", "compress", "stretch"]);
49579
- var TransformProp = external_exports.enum(["mixRotate", "mixX", "mixY", "mixScaleX", "mixScaleY", "mixShearY"]);
49584
+ var TransformProp = external_exports.enum(["mixRotate", "mixX", "mixY", "mixScaleX", "mixScaleY", "mixShearY", "x", "y", "rotation", "scaleX", "scaleY", "shearY"]);
49580
49585
  var PathProp = external_exports.enum(["position", "spacing", "mixRotate", "mixX", "mixY"]);
49581
49586
  var KeyValue = external_exports.union([external_exports.number(), external_exports.string(), external_exports.null(), external_exports.array(external_exports.number()), external_exports.array(external_exports.string())]);
49582
49587
  var Key = external_exports.object({
@@ -49585,14 +49590,16 @@ ${parts.join("\n")}
49585
49590
  /** easing INTO the next key */
49586
49591
  ease: Ease.default("ease_in_out")
49587
49592
  });
49593
+ var Interpolation = { interpolation: external_exports.enum(["segment", "pchip"]).optional() };
49588
49594
  var Track = external_exports.discriminatedUnion("target", [
49589
- external_exports.object({ target: external_exports.literal("bone"), id: Id, prop: BoneProp, keys: external_exports.array(Key).min(1) }),
49590
- external_exports.object({ target: external_exports.literal("slot"), id: Id, prop: SlotProp, keys: external_exports.array(Key).min(1) }),
49591
- external_exports.object({ target: external_exports.literal("deform"), id: Id, prop: DeformProp, keys: external_exports.array(Key).min(1) }),
49592
- external_exports.object({ target: external_exports.literal("ik"), id: Id, prop: IkProp, keys: external_exports.array(Key).min(1) }),
49593
- external_exports.object({ target: external_exports.literal("transform"), id: Id, prop: TransformProp, keys: external_exports.array(Key).min(1) }),
49594
- external_exports.object({ target: external_exports.literal("path"), id: Id, prop: PathProp, keys: external_exports.array(Key).min(1) }),
49595
- external_exports.object({ target: external_exports.literal("drawOrder"), id: Id.default("order"), prop: external_exports.literal("order").default("order"), keys: external_exports.array(Key).min(1) })
49595
+ external_exports.object({ target: external_exports.literal("control"), id: Id, prop: external_exports.literal("value"), ...Interpolation, keys: external_exports.array(Key).min(1) }),
49596
+ external_exports.object({ target: external_exports.literal("bone"), id: Id, prop: BoneProp, ...Interpolation, keys: external_exports.array(Key).min(1) }),
49597
+ external_exports.object({ target: external_exports.literal("slot"), id: Id, prop: SlotProp, ...Interpolation, keys: external_exports.array(Key).min(1) }),
49598
+ external_exports.object({ target: external_exports.literal("deform"), id: Id, prop: DeformProp, ...Interpolation, keys: external_exports.array(Key).min(1) }),
49599
+ external_exports.object({ target: external_exports.literal("ik"), id: Id, prop: IkProp, ...Interpolation, keys: external_exports.array(Key).min(1) }),
49600
+ external_exports.object({ target: external_exports.literal("transform"), id: Id, prop: TransformProp, ...Interpolation, keys: external_exports.array(Key).min(1) }),
49601
+ external_exports.object({ target: external_exports.literal("path"), id: Id, prop: PathProp, ...Interpolation, keys: external_exports.array(Key).min(1) }),
49602
+ external_exports.object({ target: external_exports.literal("drawOrder"), id: Id.default("order"), prop: external_exports.literal("order").default("order"), ...Interpolation, keys: external_exports.array(Key).min(1) })
49596
49603
  ]);
49597
49604
  var Event2 = external_exports.object({
49598
49605
  t: external_exports.number().nonnegative(),
@@ -49608,6 +49615,25 @@ ${parts.join("\n")}
49608
49615
  /** if generated from a preset — keep params so the agent can re-tune instead of re-keying */
49609
49616
  preset: external_exports.object({ name: external_exports.string(), params: external_exports.record(external_exports.unknown()) }).optional()
49610
49617
  });
49618
+ var RigControl = external_exports.object({
49619
+ id: Id,
49620
+ label: external_exports.string().optional(),
49621
+ min: external_exports.number().finite(),
49622
+ max: external_exports.number().finite(),
49623
+ default: external_exports.number().finite()
49624
+ }).strict();
49625
+ var ControlValues = external_exports.array(external_exports.union([external_exports.number().finite(), external_exports.array(external_exports.number().finite()).min(1)])).min(2);
49626
+ var ControlChannel = external_exports.discriminatedUnion("target", [
49627
+ external_exports.object({ target: external_exports.literal("bone"), id: Id, prop: BoneProp, values: ControlValues }).strict(),
49628
+ external_exports.object({ target: external_exports.literal("slot"), id: Id, prop: external_exports.literal("alpha"), values: ControlValues }).strict(),
49629
+ external_exports.object({ target: external_exports.literal("deform"), id: Id, prop: external_exports.literal("vertices"), values: ControlValues }).strict()
49630
+ ]);
49631
+ var ControlBinding = external_exports.object({
49632
+ id: Id,
49633
+ axes: external_exports.array(external_exports.object({ control: Id, keys: external_exports.array(external_exports.number().finite()).min(2).max(32) }).strict()).min(1).max(2),
49634
+ /** Flattened grid, first axis varies fastest. Linear / bilinear interpolation. */
49635
+ channels: external_exports.array(ControlChannel).min(1)
49636
+ }).strict();
49611
49637
  var Reference = external_exports.object({
49612
49638
  src: external_exports.string(),
49613
49639
  opacity: external_exports.number().min(0).max(1).default(0.5),
@@ -49633,7 +49659,9 @@ ${parts.join("\n")}
49633
49659
  ik: external_exports.array(IkConstraint).default([]),
49634
49660
  transform: external_exports.array(TransformConstraint).default([]),
49635
49661
  path: external_exports.array(PathConstraint).default([]),
49636
- animations: external_exports.array(Animation)
49662
+ animations: external_exports.array(Animation),
49663
+ controls: external_exports.array(RigControl).optional(),
49664
+ controlBindings: external_exports.array(ControlBinding).optional()
49637
49665
  });
49638
49666
  var RigDocument = external_exports.preprocess((input) => {
49639
49667
  const v2 = input?.version;
@@ -49708,6 +49736,8 @@ ${parts.join("\n")}
49708
49736
  dup(doc.ik, "ik");
49709
49737
  dup(doc.transform, "transform");
49710
49738
  dup(doc.path, "path");
49739
+ dup(doc.controls ?? [], "control");
49740
+ dup(doc.controlBindings ?? [], "control binding");
49711
49741
  const bones = new Set(doc.bones.map((b2) => b2.id));
49712
49742
  const assets = new Map(doc.assets.map((a2) => [a2.id, a2]));
49713
49743
  const slots = new Set(doc.slots.map((s2) => s2.id));
@@ -49734,6 +49764,10 @@ ${parts.join("\n")}
49734
49764
  if (a2.frame && !a2.trim && !a2.rotate && (a2.frame[2] !== a2.width || a2.frame[3] !== a2.height)) errs.push(`asset "${a2.id}": frame size must equal width\xD7height (or declare trim/rotate)`);
49735
49765
  }
49736
49766
  for (const s2 of doc.slots) {
49767
+ if (s2.clip) {
49768
+ const mask = attachments.get(s2.clip);
49769
+ if (mask?.type !== "path" || !mask.closed) errs.push(`slot "${s2.id}": clip must name a closed path attachment`);
49770
+ }
49737
49771
  if (!bones.has(s2.bone)) errs.push(`slot "${s2.id}": unknown bone "${s2.bone}"`);
49738
49772
  if (s2.attachment) {
49739
49773
  const a2 = attachments.get(s2.attachment);
@@ -49765,7 +49799,8 @@ ${parts.join("\n")}
49765
49799
  }
49766
49800
  }
49767
49801
  for (const c2 of doc.ik) {
49768
- if (c2.bones.length > 2) errs.push(`ik "${c2.id}": must have 1 or 2 bones`);
49802
+ for (let i2 = 1; i2 < c2.bones.length; i2++) if (doc.bones.find((b2) => b2.id === c2.bones[i2])?.parent !== c2.bones[i2 - 1])
49803
+ errs.push(`ik "${c2.id}": bones must form a contiguous parent-to-child chain`);
49769
49804
  for (const b2 of c2.bones) if (!bones.has(b2)) errs.push(`ik "${c2.id}": unknown bone "${b2}"`);
49770
49805
  if (!bones.has(c2.target)) errs.push(`ik "${c2.id}": unknown target bone "${c2.target}"`);
49771
49806
  }
@@ -49777,13 +49812,47 @@ ${parts.join("\n")}
49777
49812
  for (const b2 of c2.bones) if (!bones.has(b2)) errs.push(`path "${c2.id}": unknown bone "${b2}"`);
49778
49813
  if (!slots.has(c2.target)) errs.push(`path "${c2.id}": unknown target slot "${c2.target}"`);
49779
49814
  }
49815
+ const controls = new Map((doc.controls ?? []).map((c2) => [c2.id, c2]));
49816
+ const driven = /* @__PURE__ */ new Set();
49817
+ for (const c2 of controls.values()) if (!(c2.min < c2.max && c2.default >= c2.min && c2.default <= c2.max))
49818
+ errs.push(`control "${c2.id}": require min < max and default within range`);
49819
+ for (const b2 of doc.controlBindings ?? []) {
49820
+ const prefix = `control binding "${b2.id}"`;
49821
+ if (new Set(b2.axes.map((a2) => a2.control)).size !== b2.axes.length) errs.push(`${prefix}: axes must use different controls`);
49822
+ for (const axis of b2.axes) {
49823
+ const c2 = controls.get(axis.control);
49824
+ if (!c2) errs.push(`${prefix}: unknown control "${axis.control}"`);
49825
+ else if (axis.keys[0] !== c2.min || axis.keys.at(-1) !== c2.max) errs.push(`${prefix}: keys must span the control's full range`);
49826
+ if (axis.keys.some((v2, i2) => i2 > 0 && v2 <= axis.keys[i2 - 1])) errs.push(`${prefix}: keys must be strictly increasing`);
49827
+ }
49828
+ const count2 = b2.axes.reduce((n2, a2) => n2 * a2.keys.length, 1);
49829
+ for (const ch of b2.channels) {
49830
+ const key = `${ch.target}.${ch.id}.${ch.prop}`;
49831
+ if (driven.has(key)) errs.push(`${prefix}: channel ${key} already owned by a binding`);
49832
+ driven.add(key);
49833
+ if (ch.values.length !== count2) errs.push(`${prefix}: ${key} needs ${count2} grid values`);
49834
+ if (ch.target === "deform") {
49835
+ const att = attachments.get(ch.id);
49836
+ if (!att || att.type === "region") errs.push(`${prefix}: vertices need a mesh or path attachment`);
49837
+ else {
49838
+ const n2 = att.weights ? att.weights.reduce((s2, a2) => s2 + a2.length * 2, 0) : att.vertices.length;
49839
+ if (ch.values.some((v2) => !Array.isArray(v2) || v2.length !== n2)) errs.push(`${prefix}: ${key} needs arrays of ${n2} offsets`);
49840
+ }
49841
+ } else {
49842
+ if (!(ch.target === "bone" ? bones : slots).has(ch.id)) errs.push(`${prefix}: unknown ${ch.target} "${ch.id}"`);
49843
+ if (ch.values.some((v2) => typeof v2 !== "number")) errs.push(`${prefix}: ${key} needs numeric values`);
49844
+ if (ch.target === "slot" && ch.values.some((v2) => typeof v2 === "number" && (v2 < 0 || v2 > 1))) errs.push(`${prefix}: alpha must be in 0..1`);
49845
+ }
49846
+ }
49847
+ }
49780
49848
  for (const a2 of doc.animations) {
49781
49849
  const tracks = /* @__PURE__ */ new Set();
49782
49850
  for (const t2 of a2.tracks) {
49783
49851
  const tid = trackId(t2);
49784
49852
  if (tracks.has(tid)) errs.push(`animation "${a2.id}": duplicate track ${tid}`);
49785
49853
  tracks.add(tid);
49786
- const known = t2.target === "bone" ? bones.has(t2.id) : t2.target === "slot" ? slots.has(t2.id) : t2.target === "deform" ? attachments.has(t2.id) : t2.target === "ik" ? iks.has(t2.id) : t2.target === "transform" ? transforms.has(t2.id) : t2.target === "path" ? paths.has(t2.id) : true;
49854
+ if (driven.has(tid)) errs.push(`animation "${a2.id}": ${tid} is controlled by a binding; animate its control instead`);
49855
+ const known = t2.target === "bone" ? bones.has(t2.id) : t2.target === "slot" ? slots.has(t2.id) : t2.target === "deform" ? attachments.has(t2.id) : t2.target === "ik" ? iks.has(t2.id) : t2.target === "transform" ? transforms.has(t2.id) : t2.target === "path" ? paths.has(t2.id) : t2.target === "control" ? controls.has(t2.id) : true;
49787
49856
  if (!known) errs.push(`animation "${a2.id}": unknown ${t2.target} "${t2.id}"`);
49788
49857
  const bound = t2.target === "deform" ? DEFORM_BOUNDS[t2.prop] : void 0;
49789
49858
  const att = t2.target === "deform" ? attachments.get(t2.id) : void 0;
@@ -49791,6 +49860,10 @@ ${parts.join("\n")}
49791
49860
  for (const k2 of t2.keys) {
49792
49861
  if (k2.t <= prev && prev >= 0) errs.push(`animation "${a2.id}" ${tid}: keys not strictly increasing`);
49793
49862
  if (k2.t > a2.duration) errs.push(`animation "${a2.id}" ${tid}: key at ${k2.t}s exceeds duration`);
49863
+ if (t2.target === "control") {
49864
+ const c2 = controls.get(t2.id);
49865
+ if (typeof k2.v !== "number" || !Number.isFinite(k2.v) || c2 && (k2.v < c2.min || k2.v > c2.max)) errs.push(`animation "${a2.id}" ${tid}: control key outside its finite range`);
49866
+ }
49794
49867
  if (t2.target === "slot" && t2.prop === "attachment") {
49795
49868
  if (k2.v !== null && typeof k2.v !== "string") errs.push(`animation "${a2.id}" ${tid}: attachment key must be an attachment id or null`);
49796
49869
  else if (typeof k2.v === "string") {
@@ -49829,6 +49902,52 @@ ${parts.join("\n")}
49829
49902
  return doc;
49830
49903
  }
49831
49904
 
49905
+ // src/rig-controls.ts
49906
+ function controlPose(doc, values) {
49907
+ const out2 = /* @__PURE__ */ new Map();
49908
+ for (const [id, value] of Object.entries(values)) {
49909
+ const c2 = doc.controls?.find((c3) => c3.id === id);
49910
+ if (!c2) throw new Error(`unknown control "${id}"`);
49911
+ if (!Number.isFinite(value)) throw new Error(`control "${id}" must be finite`);
49912
+ out2.set(`control:${id}:value`, Math.max(c2.min, Math.min(c2.max, value)));
49913
+ }
49914
+ return out2;
49915
+ }
49916
+ function bracket(keys, value) {
49917
+ let i2 = 0;
49918
+ while (i2 < keys.length - 2 && value > keys[i2 + 1]) i2++;
49919
+ return [i2, Math.max(0, Math.min(1, (value - keys[i2]) / (keys[i2 + 1] - keys[i2])))];
49920
+ }
49921
+ function sampleBinding(binding, values) {
49922
+ const [x2, u2] = bracket(binding.axes[0].keys, values[0]);
49923
+ const [y2, v2] = binding.axes[1] ? bracket(binding.axes[1].keys, values[1]) : [0, 0];
49924
+ const width = binding.axes[0].keys.length, a2 = y2 * width + x2;
49925
+ const mix = (a3, b2, u3) => a3 + (b2 - a3) * u3;
49926
+ return binding.channels.map((ch) => {
49927
+ const read = (i2, j2) => j2 === void 0 ? ch.values[i2] : ch.values[i2][j2];
49928
+ const at = (j2) => {
49929
+ const top = mix(read(a2, j2), read(a2 + 1, j2), u2);
49930
+ return binding.axes[1] ? mix(top, mix(read(a2 + width, j2), read(a2 + width + 1, j2), u2), v2) : top;
49931
+ };
49932
+ return Array.isArray(ch.values[0]) ? ch.values[0].map((_, j2) => at(j2)) : at();
49933
+ });
49934
+ }
49935
+ function resolveControls(doc, input) {
49936
+ if (!doc.controlBindings?.length) return input;
49937
+ const out2 = new Map(input), controls = new Map(doc.controls?.map((c2) => [c2.id, c2]));
49938
+ for (const b2 of doc.controlBindings) {
49939
+ const values = b2.axes.map((axis) => {
49940
+ const c2 = controls.get(axis.control);
49941
+ const value = input.get(`control:${c2.id}:value`) ?? c2.default;
49942
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`control "${c2.id}" must be finite`);
49943
+ return Math.max(c2.min, Math.min(c2.max, value));
49944
+ });
49945
+ const sampled = sampleBinding(b2, values);
49946
+ b2.channels.forEach((ch, i2) => out2.set(`${ch.target}:${ch.id}:${ch.prop}`, sampled[i2]));
49947
+ }
49948
+ return out2;
49949
+ }
49950
+
49832
49951
  // src/rig-anim.ts
49833
49952
  var DEG2 = Math.PI / 180;
49834
49953
  var named = {
@@ -49837,6 +49956,7 @@ ${parts.join("\n")}
49837
49956
  ease_in: (t2) => t2 * t2,
49838
49957
  ease_out: (t2) => 1 - (1 - t2) * (1 - t2),
49839
49958
  ease_in_out: (t2) => t2 < 0.5 ? 2 * t2 * t2 : 1 - (-2 * t2 + 2) ** 2 / 2,
49959
+ minimum_jerk: (t2) => t2 * t2 * t2 * (10 + t2 * (-15 + 6 * t2)),
49840
49960
  back_out: (t2) => 1 + 2.70158 * (t2 - 1) ** 3 + 1.70158 * (t2 - 1) ** 2,
49841
49961
  elastic_out: (t2) => t2 === 0 ? 0 : t2 === 1 ? 1 : 2 ** (-10 * t2) * Math.sin((t2 * 10 - 0.75) * (2 * Math.PI / 3)) + 1
49842
49962
  };
@@ -49868,7 +49988,25 @@ ${parts.join("\n")}
49868
49988
  return f2;
49869
49989
  }
49870
49990
  var pk = (target, id, prop) => `${target}:${id}:${prop}`;
49871
- function sample(track, t2) {
49991
+ function smoothValue(track, i2, t2, component, loop) {
49992
+ const k2 = track.keys, n2 = k2.length;
49993
+ const v2 = (j2) => component === void 0 ? k2[j2].v : k2[j2].v[component];
49994
+ const h2 = (j2) => k2[j2 + 1].t - k2[j2].t;
49995
+ const d2 = (j2) => (v2(j2 + 1) - v2(j2)) / h2(j2);
49996
+ const harmonic = (a2, b2, h0, h1) => a2 * b2 <= 0 ? 0 : 3 * (h0 + h1) / ((2 * h1 + h0) / a2 + (h1 + 2 * h0) / b2);
49997
+ const tangent = (j2) => {
49998
+ if (j2 === 0 || j2 === n2 - 1) {
49999
+ if (loop && n2 > 2 && v2(0) === v2(n2 - 1) && k2[0].ease !== "step" && k2[n2 - 2].ease !== "step")
50000
+ return harmonic(d2(n2 - 2), d2(0), h2(n2 - 2), h2(0));
50001
+ return 0;
50002
+ }
50003
+ if (k2[j2 - 1].ease === "step" || k2[j2].ease === "step") return 0;
50004
+ return harmonic(d2(j2 - 1), d2(j2), h2(j2 - 1), h2(j2));
50005
+ };
50006
+ const u2 = (t2 - k2[i2].t) / h2(i2), u22 = u2 * u2, u3 = u22 * u2;
50007
+ return (2 * u3 - 3 * u22 + 1) * v2(i2) + (u3 - 2 * u22 + u2) * h2(i2) * tangent(i2) + (-2 * u3 + 3 * u22) * v2(i2 + 1) + (u3 - u22) * h2(i2) * tangent(i2 + 1);
50008
+ }
50009
+ function sample(track, t2, loop = false) {
49872
50010
  const keys = track.keys;
49873
50011
  if (t2 <= keys[0].t) return keys[0].v;
49874
50012
  const last = keys[keys.length - 1];
@@ -49876,36 +50014,44 @@ ${parts.join("\n")}
49876
50014
  let i2 = 0;
49877
50015
  while (keys[i2 + 1].t <= t2) i2++;
49878
50016
  const a2 = keys[i2], b2 = keys[i2 + 1];
50017
+ if (a2.ease === "step") return a2.v;
49879
50018
  if (typeof a2.v === "number" && typeof b2.v === "number") {
50019
+ if (track.interpolation === "pchip") return smoothValue(track, i2, t2, void 0, loop);
49880
50020
  const u2 = ease(a2.ease)((t2 - a2.t) / (b2.t - a2.t));
49881
50021
  return a2.v + (b2.v - a2.v) * u2;
49882
50022
  }
49883
50023
  if (Array.isArray(a2.v) && Array.isArray(b2.v) && typeof a2.v[0] === "number") {
50024
+ if (track.interpolation === "pchip") return a2.v.map((_, j2) => smoothValue(track, i2, t2, j2, loop));
49884
50025
  const u2 = ease(a2.ease)((t2 - a2.t) / (b2.t - a2.t)), av = a2.v, bv = b2.v;
49885
50026
  return av.map((v2, k2) => v2 + ((bv[k2] ?? v2) - v2) * u2);
49886
50027
  }
49887
50028
  return a2.v;
49888
50029
  }
49889
50030
  function poseOf(anim, t2, out2 = /* @__PURE__ */ new Map()) {
49890
- for (const tr of anim.tracks) out2.set(pk(tr.target, tr.id, tr.prop), sample(tr, t2));
50031
+ for (const tr of anim.tracks) out2.set(pk(tr.target, tr.id, tr.prop), sample(tr, t2, anim.loop && tr.keys[0].t === 0 && tr.keys.at(-1).t === anim.duration));
49891
50032
  return out2;
49892
50033
  }
49893
50034
  function blendPoses(from, to, w2, restOf) {
49894
- const out2 = new Map(to);
49895
- for (const [k2, f2] of from) {
49896
- const cur = to.get(k2);
49897
- if (typeof f2 === "number") {
50035
+ if (w2 >= 1) return new Map(to);
50036
+ const out2 = /* @__PURE__ */ new Map();
50037
+ for (const k2 of /* @__PURE__ */ new Set([...from.keys(), ...to.keys()])) {
50038
+ const f2 = from.has(k2) ? from.get(k2) : restOf(k2);
50039
+ const cur = to.has(k2) ? to.get(k2) : restOf(k2);
50040
+ if (typeof f2 === "number" && !Array.isArray(cur)) {
49898
50041
  const target = typeof cur === "number" ? cur : restOf(k2);
49899
50042
  out2.set(k2, f2 + (target - f2) * w2);
49900
50043
  } else if (Array.isArray(f2) && typeof f2[0] === "number") {
49901
50044
  const fv = f2, tv = Array.isArray(cur) && typeof cur[0] === "number" ? cur : fv.map(() => 0);
49902
50045
  out2.set(k2, fv.map((v2, i2) => v2 + ((tv[i2] ?? 0) - v2) * w2));
49903
- } else if (cur === void 0 && w2 < 1) out2.set(k2, f2);
50046
+ } else if (Array.isArray(cur) && typeof cur[0] === "number" && typeof f2 !== "string") {
50047
+ out2.set(k2, cur.map((v2) => v2 * w2));
50048
+ } else out2.set(k2, f2 ?? null);
49904
50049
  }
49905
50050
  return out2;
49906
50051
  }
49907
50052
  function restValue(doc, key) {
49908
50053
  const [target, id, prop] = key.split(":");
50054
+ if (target === "control") return doc.controls?.find((c2) => c2.id === id)?.default ?? 0;
49909
50055
  if (target === "bone") return doc.bones.find((b2) => b2.id === id).transform[prop];
49910
50056
  if (target === "slot") {
49911
50057
  const s2 = doc.slots.find((s3) => s3.id === id);
@@ -50008,7 +50154,9 @@ ${parts.join("\n")}
50008
50154
  for (const k2 of kids.get(id) ?? []) stack.push(k2.id);
50009
50155
  }
50010
50156
  }
50011
- function evaluate(doc, pose) {
50157
+ function evaluate(doc, pose, modifiers) {
50158
+ pose = resolveControls(doc, pose);
50159
+ if (modifiers) for (const modify of modifiers) pose = modify(new Map(pose));
50012
50160
  const skel = { doc, pose, bones: /* @__PURE__ */ new Map(), attachment: /* @__PURE__ */ new Map() };
50013
50161
  for (const b2 of boneOrder(doc)) {
50014
50162
  const s2 = localOf(doc, pose, b2);
@@ -50069,10 +50217,43 @@ ${parts.join("\n")}
50069
50217
  updateSubtree(skel, boneId);
50070
50218
  }
50071
50219
  var shortest = (deg) => ((deg + 180) % 360 + 360) % 360 - 180;
50220
+ function applyLongIk(skel, c2, tx, ty, mix) {
50221
+ const initial = c2.bones.map((id) => ({ ...skel.bones.get(id) }));
50222
+ const last = c2.bones.at(-1), len = boneOf(skel.doc, last).length;
50223
+ const end = () => apply(skel.bones.get(last).world, len, 0);
50224
+ const tolerance = c2.tolerance ?? 0.05;
50225
+ const root = skel.bones.get(c2.bones[0]).world, tip = end();
50226
+ if (Math.abs((tip[0] - root.tx) * (ty - root.ty) - (tip[1] - root.ty) * (tx - root.tx)) < 1e-7 && Math.hypot(tx - root.tx, ty - root.ty) < Math.hypot(tip[0] - root.tx, tip[1] - root.ty) - tolerance) {
50227
+ skel.bones.get(last).rotation += cnum(skel, "ik", c2.id, "bendPositive", c2.bendPositive) > 0.5 ? 5 : -5;
50228
+ updateSubtree(skel, last);
50229
+ }
50230
+ for (let iter = 0; iter < (c2.iterations ?? 100); iter++) {
50231
+ const e2 = end();
50232
+ if (Math.hypot(e2[0] - tx, e2[1] - ty) <= tolerance) break;
50233
+ for (let i2 = c2.bones.length - 1; i2 >= 0; i2--) {
50234
+ const id = c2.bones[i2], b2 = boneOf(skel.doc, id), st = skel.bones.get(id), w2 = st.world, e3 = end();
50235
+ const angle = wrapRad(Math.atan2(ty - w2.ty, tx - w2.tx) - Math.atan2(e3[1] - w2.ty, e3[0] - w2.tx));
50236
+ const cos = Math.cos(angle), sin = Math.sin(angle);
50237
+ setEffectiveWorld(skel, b2, st, { ...w2, a: cos * w2.a - sin * w2.b, b: sin * w2.a + cos * w2.b, c: cos * w2.c - sin * w2.d, d: sin * w2.c + cos * w2.d });
50238
+ }
50239
+ }
50240
+ if (mix < 1) {
50241
+ c2.bones.forEach((id, i2) => {
50242
+ const st = skel.bones.get(id), from = initial[i2];
50243
+ for (const p2 of ["rotation", "shearX", "shearY"]) st[p2] = from[p2] + shortest(st[p2] - from[p2]) * mix;
50244
+ for (const p2 of ["x", "y", "scaleX", "scaleY"]) st[p2] = from[p2] + (st[p2] - from[p2]) * mix;
50245
+ });
50246
+ updateSubtree(skel, c2.bones[0]);
50247
+ }
50248
+ }
50072
50249
  function applyIk(skel, c2) {
50073
50250
  const mix = cnum(skel, "ik", c2.id, "mix", c2.mix);
50074
50251
  if (mix <= 0) return;
50075
50252
  const target = skel.bones.get(c2.target).world;
50253
+ if (c2.bones.length > 2) {
50254
+ applyLongIk(skel, c2, target.tx, target.ty, mix);
50255
+ return;
50256
+ }
50076
50257
  const bendDir = cnum(skel, "ik", c2.id, "bendPositive", c2.bendPositive) > 0.5 ? 1 : -1;
50077
50258
  const compress = cnum(skel, "ik", c2.id, "compress", c2.compress) > 0.5, stretch = cnum(skel, "ik", c2.id, "stretch", c2.stretch) > 0.5;
50078
50259
  let softness = cnum(skel, "ik", c2.id, "softness", c2.softness);
@@ -50207,6 +50388,7 @@ ${parts.join("\n")}
50207
50388
  const mixRotate = cnum(skel, "transform", c2.id, "mixRotate", c2.mixRotate), mixX = cnum(skel, "transform", c2.id, "mixX", c2.mixX), mixY = cnum(skel, "transform", c2.id, "mixY", c2.mixY);
50208
50389
  const mixScaleX = cnum(skel, "transform", c2.id, "mixScaleX", c2.mixScaleX), mixScaleY = cnum(skel, "transform", c2.id, "mixScaleY", c2.mixScaleY), mixShearY = cnum(skel, "transform", c2.id, "mixShearY", c2.mixShearY);
50209
50390
  if (!mixRotate && !mixX && !mixY && !mixScaleX && !mixScaleY && !mixShearY) return;
50391
+ c2 = { ...c2, ...Object.fromEntries(["x", "y", "rotation", "scaleX", "scaleY", "shearY"].map((p2) => [p2, cnum(skel, "transform", c2.id, p2, c2[p2])])) };
50210
50392
  const target = skel.bones.get(c2.target), tw = target.world;
50211
50393
  const offsetRotation = c2.rotation * DEG2, offsetShearY = c2.shearY * DEG2;
50212
50394
  for (const id of c2.bones) {
@@ -50534,6 +50716,7 @@ ${parts.join("\n")}
50534
50716
  var AnimationState = class {
50535
50717
  constructor(sink, opts = {}) {
50536
50718
  this.sink = sink;
50719
+ this.opts = opts;
50537
50720
  this.defaultMix = opts.mix ?? 0;
50538
50721
  }
50539
50722
  onEvent;
@@ -50558,11 +50741,18 @@ ${parts.join("\n")}
50558
50741
  }
50559
50742
  channel(id, loop, priority = 0) {
50560
50743
  const anim = this.sink.doc.animations.find((a2) => a2.id === id);
50561
- if (!anim) throw new Error(`unknown animation "${id}"`);
50744
+ if (!anim) {
50745
+ const message = `unknown animation "${id}"; available: ${this.sink.doc.animations.map((a2) => a2.id).join(", ")}`;
50746
+ if (this.opts.missingAnimation === "throw") throw new Error(message);
50747
+ (this.opts.onWarning ?? console.warn)(message);
50748
+ return void 0;
50749
+ }
50562
50750
  return { anim, time: 0, loop: loop ?? anim.loop, done: false, priority, fresh: true };
50563
50751
  }
50564
50752
  setBase(id, opts = {}) {
50565
- this.base = id === void 0 ? void 0 : this.channel(id);
50753
+ const next = id === void 0 ? void 0 : this.channel(id);
50754
+ if (id !== void 0 && !next) return;
50755
+ this.base = next;
50566
50756
  if (!this.main || this.main.done) {
50567
50757
  this.main = void 0;
50568
50758
  this.beginMix(opts.mix);
@@ -50571,6 +50761,16 @@ ${parts.join("\n")}
50571
50761
  }
50572
50762
  play(id, opts = {}) {
50573
50763
  const ch = this.channel(id, opts.loop, opts.priority ?? 0);
50764
+ if (!ch) {
50765
+ this.main?.settle?.("interrupted");
50766
+ for (const queued of this.queue) queued.settle?.("dropped");
50767
+ this.main = void 0;
50768
+ this.queue = [];
50769
+ this.beginMix(opts.mix);
50770
+ this.update(0);
50771
+ opts.onComplete?.("dropped");
50772
+ return Promise.resolve("dropped");
50773
+ }
50574
50774
  const promise2 = new Promise((resolve) => {
50575
50775
  ch.settle = (r2) => {
50576
50776
  ch.settle = void 0;
@@ -50591,7 +50791,10 @@ ${parts.join("\n")}
50591
50791
  /** Independent loop on top of the shown pose; `undefined` removes it. */
50592
50792
  setOverlay(name, id) {
50593
50793
  if (id === void 0) this.overlays.delete(name);
50594
- else this.overlays.set(name, this.channel(id));
50794
+ else {
50795
+ const ch = this.channel(id);
50796
+ if (ch) this.overlays.set(name, ch);
50797
+ }
50595
50798
  }
50596
50799
  /** Clear everything and show the setup pose. Pending plays resolve as "stopped". */
50597
50800
  stop() {
@@ -50676,6 +50879,7 @@ ${parts.join("\n")}
50676
50879
  // src/rig-runtime.ts
50677
50880
  var DEG3 = Math.PI / 180;
50678
50881
  var MESH_GRID = 9;
50882
+ var GHOST_ALPHA = 0.5;
50679
50883
  async function createTextures(doc, loadSource) {
50680
50884
  const bySrc = /* @__PURE__ */ new Map(), out2 = /* @__PURE__ */ new Map();
50681
50885
  for (const a2 of doc.assets) {
@@ -50715,7 +50919,7 @@ ${parts.join("\n")}
50715
50919
  var rgbOf = (c2) => Math.round(c2[0] * 255) << 16 | Math.round(c2[1] * 255) << 8 | Math.round(c2[2] * 255);
50716
50920
  var sameNumbers = (a2, b2) => a2 === b2 || a2.length === b2.length && a2.every((v2, i2) => v2 === b2[i2]);
50717
50921
  var RigPlayer = class {
50718
- /** `mix`: default crossfade seconds for state transitions (setBase, completion → base); play() passes its own */
50922
+ /** Default crossfade is 0.18 seconds; explicit mix: 0 requests a hard cut. */
50719
50923
  constructor(doc, textures, opts = {}) {
50720
50924
  this.doc = doc;
50721
50925
  parse2(doc);
@@ -50754,17 +50958,63 @@ ${parts.join("\n")}
50754
50958
  this.items.push(item);
50755
50959
  this.view.addChild(item.node);
50756
50960
  }
50757
- this.state = new AnimationState(this, { mix: opts.mix ?? 0 });
50961
+ this.state = new AnimationState(this, { ...opts, mix: opts.mix ?? 0.18 });
50758
50962
  this.setPose(/* @__PURE__ */ new Map());
50759
50963
  }
50964
+ inputPose = /* @__PURE__ */ new Map();
50965
+ controlOverrides = /* @__PURE__ */ new Map();
50966
+ /** Manual inputs override animated controls until resetControls; values are clamped to declared ranges. */
50967
+ setControls(values) {
50968
+ this.controlOverrides = controlPose(this.doc, values);
50969
+ this.setPose(this.inputPose);
50970
+ }
50971
+ resetControls() {
50972
+ this.controlOverrides.clear();
50973
+ this.setPose(this.inputPose);
50974
+ }
50760
50975
  view = new Container();
50761
50976
  state;
50762
50977
  items = [];
50978
+ beforePose = /* @__PURE__ */ new Set();
50979
+ afterPose = /* @__PURE__ */ new Set();
50980
+ clipPoints = /* @__PURE__ */ new Map();
50763
50981
  skel;
50764
50982
  textures;
50765
50983
  pages = /* @__PURE__ */ new Map();
50766
50984
  /** slots the editor hides; honoured by setPose */
50767
50985
  hidden = /* @__PURE__ */ new Set();
50986
+ /** slots the editor has selected; setPose draws them at GHOST_ALPHA at least */
50987
+ ghosts = /* @__PURE__ */ new Set();
50988
+ clipGraphics = /* @__PURE__ */ new Map();
50989
+ /** Read-only render handles for in-page tools. Nodes remain owned by the player. */
50990
+ get renderItems() {
50991
+ return this.items;
50992
+ }
50993
+ /** Current document-space clipping polygons, keyed by path attachment id. */
50994
+ get clipPolygons() {
50995
+ return this.clipPoints;
50996
+ }
50997
+ /**
50998
+ * Modify resolved control outputs before skeleton evaluation, constraints and skinning.
50999
+ * Callbacks run in registration order, each with its own map. Change control inputs via setControls.
51000
+ */
51001
+ onBeforePose(callback) {
51002
+ this.beforePose.add(callback);
51003
+ return () => {
51004
+ this.beforePose.delete(callback);
51005
+ };
51006
+ }
51007
+ /** Subscribe after the base pose has been rebuilt. Return value removes the callback. */
51008
+ onAfterPose(callback) {
51009
+ this.afterPose.add(callback);
51010
+ return () => {
51011
+ this.afterPose.delete(callback);
51012
+ };
51013
+ }
51014
+ /** Rebuild the current pose without advancing playback; useful for reversible overlays. */
51015
+ refreshPose() {
51016
+ this.setPose(this.inputPose);
51017
+ }
50768
51018
  /** whole atlas page as a plain texture (identity uv mapping) for skinned meshes */
50769
51019
  pageTexture(tex) {
50770
51020
  let page = this.pages.get(tex.source);
@@ -50776,7 +51026,7 @@ ${parts.join("\n")}
50776
51026
  this.state.onEvent = opts.onEvent;
50777
51027
  return this.state.play(animId, {
50778
51028
  loop: opts.loop,
50779
- mix: opts.mix ?? 0,
51029
+ mix: opts.mix,
50780
51030
  ifBusy: "interrupt",
50781
51031
  onComplete: (r2) => {
50782
51032
  if (r2 === "completed") opts.onComplete?.();
@@ -50805,6 +51055,10 @@ ${parts.join("\n")}
50805
51055
  if (visible) this.hidden.delete(slotId);
50806
51056
  else this.hidden.add(slotId);
50807
51057
  }
51058
+ /** Editor-only: draw these slots at GHOST_ALPHA at least, whatever the pose says (an empty list clears it). */
51059
+ setGhostSlots(slotIds) {
51060
+ this.ghosts = new Set(slotIds);
51061
+ }
50808
51062
  /** Stop playback and immediately restore the complete setup pose. */
50809
51063
  stop() {
50810
51064
  this.state.stop();
@@ -50815,13 +51069,39 @@ ${parts.join("\n")}
50815
51069
  }
50816
51070
  /** Apply a pose (rest where silent) to the display objects. */
50817
51071
  setPose(pose) {
50818
- const skel = this.skel = evaluate(this.doc, pose);
51072
+ this.inputPose = new Map(pose);
51073
+ if (this.controlOverrides.size) pose = new Map([...pose, ...this.controlOverrides]);
51074
+ this.clipPoints.clear();
51075
+ const skel = this.skel = evaluate(this.doc, pose, this.beforePose);
51076
+ pose = skel.pose;
51077
+ const clips = new Set(this.doc.slots.flatMap((s2) => s2.clip ? [s2.clip] : []));
51078
+ for (const [id, g2] of this.clipGraphics) if (!clips.has(id)) {
51079
+ g2.destroy();
51080
+ this.clipGraphics.delete(id);
51081
+ }
51082
+ for (const id of clips) {
51083
+ const att = this.doc.attachments.find((a2) => a2.id === id);
51084
+ if (att?.type !== "path" || !att.closed) continue;
51085
+ let g2 = this.clipGraphics.get(id);
51086
+ if (!g2) {
51087
+ g2 = new Graphics();
51088
+ this.clipGraphics.set(id, g2);
51089
+ this.view.addChild(g2);
51090
+ }
51091
+ const vertices = attachmentWorldVertices(skel, att.slot, att);
51092
+ const n2 = Math.max(32, vertices.length * 4);
51093
+ const points = samplePath(vertices, true, att.lengths, true, Array.from({ length: n2 }, (_, i2) => i2 / n2), true);
51094
+ const polygon = points.flatMap((p2) => [p2.x, p2.y]);
51095
+ this.clipPoints.set(id, polygon);
51096
+ g2.clear().poly(polygon, true).fill(16777215);
51097
+ }
50819
51098
  const m2 = new Matrix();
50820
51099
  const orderV = pose.get(pk("drawOrder", "order", "order"));
50821
51100
  const order = /* @__PURE__ */ new Map();
50822
51101
  if (Array.isArray(orderV)) orderV.forEach((id, i2) => order.set(id, i2));
50823
51102
  for (const it of this.items) {
50824
51103
  const s2 = it.slot, shown = skel.attachment.get(s2.id);
51104
+ it.node.mask = s2.clip ? this.clipGraphics.get(s2.clip) ?? null : null;
50825
51105
  if (shown !== it.att || this.hidden.has(s2.id)) {
50826
51106
  it.node.visible = false;
50827
51107
  continue;
@@ -50829,10 +51109,12 @@ ${parts.join("\n")}
50829
51109
  it.node.visible = true;
50830
51110
  const colorV = pose.get(pk("slot", s2.id, "color"));
50831
51111
  const color = Array.isArray(colorV) && typeof colorV[0] === "number" ? colorV : colorToArray(s2.color);
50832
- it.node.alpha = numOf(pose, pk("slot", s2.id, "alpha"), s2.alpha) * color[3];
51112
+ const alpha = numOf(pose, pk("slot", s2.id, "alpha"), s2.alpha) * color[3];
51113
+ it.node.alpha = this.ghosts.has(s2.id) ? Math.max(alpha, GHOST_ALPHA) : alpha;
50833
51114
  it.node.tint = rgbOf(color);
50834
51115
  it.node.zIndex = order.size ? order.get(s2.id) ?? 0 : numOf(pose, pk("slot", s2.id, "z"), s2.z);
50835
51116
  if (it.skinned) {
51117
+ it.node.setFromMatrix(m2.identity());
50836
51118
  const v2 = attachmentWorldVertices(skel, s2.id, it.att);
50837
51119
  const buf = it.skinned.mesh.geometry.getBuffer("aPosition");
50838
51120
  buf.data.set(v2);
@@ -50865,6 +51147,7 @@ ${parts.join("\n")}
50865
51147
  it.node.scale.y *= h2 / (this.textures.get(att.asset)?.height ?? h2);
50866
51148
  }
50867
51149
  }
51150
+ for (const callback of this.afterPose) callback(this);
50868
51151
  }
50869
51152
  /** World-space bone positions — used by editor gizmos and renderPreview overlays */
50870
51153
  bonePositions() {
@@ -50912,7 +51195,7 @@ ${parts.join("\n")}
50912
51195
  if (!assets[src]) throw new Error(`missing asset data for "${src}"`);
50913
51196
  return textureFromDataUrl(assets[src]);
50914
51197
  });
50915
- player = new RigPlayer(doc, textures);
51198
+ player = new RigPlayer(doc, textures, { mix: 0 });
50916
51199
  player.view.scale.set(scale);
50917
51200
  overlay = new Container();
50918
51201
  overlay.scale.set(scale);
@@ -50957,7 +51240,7 @@ ${parts.join("\n")}
50957
51240
  };
50958
51241
  for (const t2 of opts.times) {
50959
51242
  if (opts.animation) {
50960
- player.play(opts.animation);
51243
+ player.play(opts.animation, { mix: 0 });
50961
51244
  player.tick(t2);
50962
51245
  } else {
50963
51246
  player.stop();
@@ -50965,7 +51248,21 @@ ${parts.join("\n")}
50965
51248
  }
50966
51249
  if (opts.overlayBones) drawBones();
50967
51250
  else clearOverlay();
50968
- const frame = grab(1);
51251
+ if (opts.weightBone) {
51252
+ if (!player.doc.bones.some((b2) => b2.id === opts.weightBone)) throw new Error(`unknown weight bone "${opts.weightBone}"`);
51253
+ const anim = player.doc.animations.find((a2) => a2.id === opts.animation), pose = anim ? poseOf(anim, anim.loop ? t2 % anim.duration : t2) : /* @__PURE__ */ new Map();
51254
+ const skel = evaluate(player.doc, pose), g2 = new Graphics();
51255
+ for (const att of skel.attachment.values()) if (att?.type === "mesh" && att.weights) {
51256
+ const v2 = attachmentWorldVertices(skel, att.slot, att);
51257
+ for (let j2 = 0; j2 < att.triangles.length; j2 += 3) {
51258
+ const ids = att.triangles.slice(j2, j2 + 3);
51259
+ const weight = ids.reduce((sum, i2) => sum + att.weights[i2].filter((w3) => w3.bone === opts.weightBone).reduce((s2, w3) => s2 + w3.w, 0), 0) / 3;
51260
+ g2.poly(ids.flatMap((i2) => [v2[i2 * 2], v2[i2 * 2 + 1]]), true).fill({ color: Math.round(255 * weight) << 16 | Math.round(255 * (1 - weight)), alpha: 0.75 });
51261
+ }
51262
+ }
51263
+ overlay.addChild(g2);
51264
+ }
51265
+ const frame = grab(opts.transparent ? 0 : 1);
50969
51266
  const ctx = frame.getContext("2d");
50970
51267
  if (opts.onionSkin && prev) {
50971
51268
  ctx.globalAlpha = 0.35;