@energy8platform/golem 0.7.0 → 0.8.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.
@@ -6,7 +6,7 @@ export declare const TEXT_ARGS: readonly ["animation", "asset", "attachment", "b
6
6
  /** Nested arguments whose string values golem reads from disk at call time (resolved from the rig's directory); each value is checked and kept as written. */
7
7
  export declare const NESTED_PATH_ARGS: readonly ["debugFillMasks"];
8
8
  /** Strings nested inside other arguments that are not paths (or only land in rig.json as data); the test enumerates them. */
9
- export declare const NESTED_TEXT_ARGS: readonly ["a", "animation", "b", "bone", "control", "from", "id", "label", "name", "prompt", "prop", "requiredAnimations", "sourceImage", "src", "to", "v", "whenSlotVisible"];
9
+ export declare const NESTED_TEXT_ARGS: readonly ["a", "animation", "b", "bone", "code", "control", "from", "id", "label", "name", "prompt", "prop", "requiredAnimations", "sourceImage", "src", "subject", "to", "v", "whenSlotVisible"];
10
10
  /**
11
11
  * `args` with every path made absolute and checked to stay under `root`; throws naming the argument otherwise. Top-level PATH_ARGS are resolved from
12
12
  * the rig's directory and checked (an empty one is left as given, STORED ones stay relative); an empty `path` is refused; NESTED_PATH_ARGS (debugFillMasks values) are checked against it and kept as written.
@@ -297,7 +297,7 @@ var Key = z.object({
297
297
  /** Optional independent easing for numeric-array components (for example Spine RGBA curves). */
298
298
  componentEase: z.array(Ease).min(1).optional()
299
299
  });
300
- var Interpolation = { interpolation: z.enum(["segment", "pchip"]).optional() };
300
+ var Interpolation = { interpolation: z.enum(["segment", "pchip", "spline"]).optional() };
301
301
  var Track = z.discriminatedUnion("target", [
302
302
  z.object({ target: z.literal("control"), id: Id, prop: z.literal("value"), ...Interpolation, keys: z.array(Key).min(1) }),
303
303
  z.object({ target: z.literal("bone"), id: Id, prop: BoneProp, ...Interpolation, keys: z.array(Key).min(1) }),
@@ -731,6 +731,57 @@ function smoothValue(track, i, t, component, loop) {
731
731
  const u = (t - k[i].t) / h(i), u2 = u * u, u3 = u2 * u;
732
732
  return (2 * u3 - 3 * u2 + 1) * v(i) + (u3 - 2 * u2 + u) * h(i) * tangent(i) + (-2 * u3 + 3 * u2) * v(i + 1) + (u3 - u2) * h(i) * tangent(i + 1);
733
733
  }
734
+ function tridiagonal(a, b, c, r) {
735
+ const n = b.length, cp = [], x = [];
736
+ let beta = b[0];
737
+ x[0] = r[0] / beta;
738
+ for (let i = 1; i < n; i++) {
739
+ cp[i] = c[i - 1] / beta;
740
+ beta = b[i] - a[i] * cp[i];
741
+ x[i] = (r[i] - a[i] * x[i - 1]) / beta;
742
+ }
743
+ for (let i = n - 2; i >= 0; i--) x[i] -= cp[i + 1] * x[i + 1];
744
+ return x;
745
+ }
746
+ function splineMoments(t, v, periodic) {
747
+ const n = t.length, h = (j2) => t[j2 + 1] - t[j2], d = (j2) => (v[j2 + 1] - v[j2]) / h(j2);
748
+ if (!periodic) {
749
+ const a2 = [], b2 = [], c2 = [], r2 = [];
750
+ for (let i = 0; i < n; i++) {
751
+ const hp = i > 0 ? h(i - 1) : 0, hn = i < n - 1 ? h(i) : 0;
752
+ a2.push(hp);
753
+ b2.push(2 * (hp + hn));
754
+ c2.push(hn);
755
+ r2.push(6 * ((i < n - 1 ? d(i) : 0) - (i > 0 ? d(i - 1) : 0)));
756
+ }
757
+ return tridiagonal(a2, b2, c2, r2);
758
+ }
759
+ const m = n - 1, a = [], b = [], c = [], r = [];
760
+ for (let i = 0; i < m; i++) {
761
+ const p = (i + m - 1) % m;
762
+ a.push(h(p));
763
+ b.push(2 * (h(p) + h(i)));
764
+ c.push(h(i));
765
+ r.push(6 * (d(i) - d(p)));
766
+ }
767
+ const top = a[0], bottom = c[m - 1], gamma = -b[0];
768
+ const bb = [...b];
769
+ bb[0] -= gamma;
770
+ bb[m - 1] -= bottom * top / gamma;
771
+ const x = tridiagonal(a, bb, c, r), u = new Array(m).fill(0);
772
+ u[0] = gamma;
773
+ u[m - 1] = bottom;
774
+ const z4 = tridiagonal(a, bb, c, u);
775
+ const f = (x[0] + top * x[m - 1] / gamma) / (1 + z4[0] + top * z4[m - 1] / gamma);
776
+ const M = x.map((xi, i) => xi - f * z4[i]);
777
+ return [...M, M[0]];
778
+ }
779
+ function splineValue(track, i, t, component, loop) {
780
+ const k = track.keys, ts = k.map((x) => x.t), vs = k.map((x) => component === void 0 ? x.v : x.v[component]);
781
+ const M = splineMoments(ts, vs, loop && k.length > 3 && vs[0] === vs[k.length - 1]);
782
+ const H = ts[i + 1] - ts[i], A = (ts[i + 1] - t) / H, B = (t - ts[i]) / H;
783
+ return A * vs[i] + B * vs[i + 1] + ((A ** 3 - A) * M[i] + (B ** 3 - B) * M[i + 1]) * H * H / 6;
784
+ }
734
785
  function sample(track, t, loop = false) {
735
786
  const keys = track.keys;
736
787
  if (t <= keys[0].t) return keys[0].v;
@@ -742,11 +793,13 @@ function sample(track, t, loop = false) {
742
793
  if (a.ease === "step") return a.v;
743
794
  if (typeof a.v === "number" && typeof b.v === "number") {
744
795
  if (track.interpolation === "pchip") return smoothValue(track, i, t, void 0, loop);
796
+ if (track.interpolation === "spline") return splineValue(track, i, t, void 0, loop);
745
797
  const u = ease(a.ease)((t - a.t) / (b.t - a.t));
746
798
  return a.v + (b.v - a.v) * u;
747
799
  }
748
800
  if (Array.isArray(a.v) && Array.isArray(b.v) && typeof a.v[0] === "number") {
749
801
  if (track.interpolation === "pchip") return a.v.map((_, j2) => smoothValue(track, i, t, j2, loop));
802
+ if (track.interpolation === "spline") return a.v.map((_, j2) => splineValue(track, i, t, j2, loop));
750
803
  const phase = (t - a.t) / (b.t - a.t), av = a.v, bv = b.v;
751
804
  return av.map((v, k) => v + ((bv[k] ?? v) - v) * ease(a.componentEase?.[k] ?? a.ease)(phase));
752
805
  }
@@ -2478,7 +2531,8 @@ function adjustTrack(doc, animId, track, adj) {
2478
2531
  return { ...k, t: r42(adj.timeScale ? k.t * adj.timeScale : k.t), v: r42(v) };
2479
2532
  });
2480
2533
  const duration = Math.max(anim.duration, keys[keys.length - 1].t);
2481
- const next = { ...anim, duration, tracks: anim.tracks.map((t) => same(t) ? { ...t, keys } : t), preset: void 0 };
2534
+ const interpolation = track.interpolation ?? tr.interpolation;
2535
+ const next = { ...anim, duration, tracks: anim.tracks.map((t) => same(t) ? { ...t, keys, ...interpolation && { interpolation } } : t), preset: void 0 };
2482
2536
  return { ...doc, animations: doc.animations.map((a) => a.id === animId ? next : a) };
2483
2537
  }
2484
2538
  function removeAnimation(doc, id) {
@@ -2895,19 +2949,20 @@ var requireSlot = (doc, id) => {
2895
2949
  if (!s) throw new Error(`unknown slot "${id}"`);
2896
2950
  return s;
2897
2951
  };
2898
- function sampled(duration, n, f, ease2 = "linear") {
2952
+ function sampled(duration, n, f) {
2899
2953
  const keys = [];
2900
2954
  for (let i = 0; i <= n; i++) {
2901
2955
  const t = +(duration * i / n).toFixed(4);
2902
- keys.push({ t, v: +f(t).toFixed(4) + 0, ease: ease2 });
2956
+ keys.push({ t, v: +f(t).toFixed(4) + 0, ease: "linear" });
2903
2957
  }
2904
2958
  return keys;
2905
2959
  }
2906
- function boneTrack(bone, prop, keys) {
2907
- return { target: "bone", id: bone.id, prop, keys };
2960
+ var smooth = "spline";
2961
+ function boneTrack(bone, prop, keys, interpolation) {
2962
+ return { target: "bone", id: bone.id, prop, keys, ...interpolation && { interpolation } };
2908
2963
  }
2909
- function deformTrack(slot, prop, keys) {
2910
- return slot.attachment ? { target: "deform", id: slot.attachment, prop, keys } : void 0;
2964
+ function deformTrack(slot, prop, keys, interpolation) {
2965
+ return slot.attachment ? { target: "deform", id: slot.attachment, prop, keys, ...interpolation && { interpolation } } : void 0;
2911
2966
  }
2912
2967
  var pushTrack = (tracks, t) => {
2913
2968
  if (t) tracks.push(t);
@@ -2931,18 +2986,18 @@ var idleBreathing = {
2931
2986
  const armsR = byRole(doc, "arm_r", "arm_upper_r");
2932
2987
  const chest = slotOfBone(doc, torso);
2933
2988
  if (chest?.attachment) {
2934
- pushTrack(tracks, deformTrack(chest, "bulge", sampled(T2, 8, (t) => p.amplitude * Math.sin(w * t), "ease_in_out")));
2989
+ pushTrack(tracks, deformTrack(chest, "bulge", sampled(T2, 8, (t) => p.amplitude * Math.sin(w * t)), smooth));
2935
2990
  } else {
2936
2991
  const r = torso.transform;
2937
- tracks.push(boneTrack(torso, "scaleY", sampled(T2, 8, (t) => r.scaleY + p.amplitude * Math.sin(w * t), "ease_in_out")));
2938
- tracks.push(boneTrack(torso, "scaleX", sampled(T2, 8, (t) => r.scaleX - p.amplitude * 0.4 * Math.sin(w * t), "ease_in_out")));
2992
+ tracks.push(boneTrack(torso, "scaleY", sampled(T2, 8, (t) => r.scaleY + p.amplitude * Math.sin(w * t)), smooth));
2993
+ tracks.push(boneTrack(torso, "scaleX", sampled(T2, 8, (t) => r.scaleX - p.amplitude * 0.4 * Math.sin(w * t)), smooth));
2939
2994
  }
2940
2995
  for (const h of heads)
2941
- tracks.push(boneTrack(h, "y", sampled(T2, 8, (t) => h.transform.y - p.headBob * Math.sin(w * t + 0.4), "ease_in_out")));
2996
+ tracks.push(boneTrack(h, "y", sampled(T2, 8, (t) => h.transform.y - p.headBob * Math.sin(w * t + 0.4)), smooth));
2942
2997
  for (const a of armsL)
2943
- tracks.push(boneTrack(a, "rotation", sampled(T2, 8, (t) => a.transform.rotation + p.armSway * Math.sin(w * t + 0.8), "ease_in_out")));
2998
+ tracks.push(boneTrack(a, "rotation", sampled(T2, 8, (t) => a.transform.rotation + p.armSway * Math.sin(w * t + 0.8)), smooth));
2944
2999
  for (const a of armsR)
2945
- tracks.push(boneTrack(a, "rotation", sampled(T2, 8, (t) => a.transform.rotation - p.armSway * Math.sin(w * t + 0.8), "ease_in_out")));
3000
+ tracks.push(boneTrack(a, "rotation", sampled(T2, 8, (t) => a.transform.rotation - p.armSway * Math.sin(w * t + 0.8)), smooth));
2946
3001
  return finish(this.name, p.id, T2, true, tracks, p);
2947
3002
  }
2948
3003
  };
@@ -2951,22 +3006,20 @@ var winBounce = {
2951
3006
  defaults: { id: "win", duration: 1.2, height: 40, bounces: 2, squash: 0.15 },
2952
3007
  build(doc, p) {
2953
3008
  const b = root(doc), r = b.transform, tracks = [];
2954
- const yK = [[0, r.y, "ease_out"]];
2955
- const sxK = [[0, r.scaleX, "ease_out"]];
2956
- const syK = [[0, r.scaleY, "ease_out"]];
3009
+ const yK = [[0, r.y]], sxK = [[0, r.scaleX]], syK = [[0, r.scaleY]];
2957
3010
  const per = p.duration / p.bounces;
2958
3011
  for (let i = 0; i < p.bounces; i++) {
2959
3012
  const t0 = i * per, h = p.height * 0.55 ** i, s = p.squash * 0.7 ** i;
2960
- sxK.push([t0 + per * 0.08, r.scaleX * (1 + s), "ease_out"], [t0 + per * 0.5, r.scaleX * (1 - s * 0.6), "ease_in"], [t0 + per * 0.95, r.scaleX * (1 + s), "ease_out"]);
2961
- syK.push([t0 + per * 0.08, r.scaleY * (1 - s), "ease_out"], [t0 + per * 0.5, r.scaleY * (1 + s * 0.6), "ease_in"], [t0 + per * 0.95, r.scaleY * (1 - s), "ease_out"]);
2962
- yK.push([t0 + per * 0.08, r.y, "ease_out"], [t0 + per * 0.5, r.y - h, "ease_in"], [t0 + per * 0.95, r.y, "ease_out"]);
3013
+ sxK.push([t0 + per * 0.08, r.scaleX * (1 + s)], [t0 + per * 0.5, r.scaleX * (1 - s * 0.6)], [t0 + per * 0.95, r.scaleX * (1 + s)]);
3014
+ syK.push([t0 + per * 0.08, r.scaleY * (1 - s)], [t0 + per * 0.5, r.scaleY * (1 + s * 0.6)], [t0 + per * 0.95, r.scaleY * (1 - s)]);
3015
+ yK.push([t0 + per * 0.08, r.y], [t0 + per * 0.5, r.y - h], [t0 + per * 0.95, r.y]);
2963
3016
  }
2964
3017
  yK.push([p.duration, r.y]);
2965
3018
  sxK.push([p.duration, r.scaleX]);
2966
3019
  syK.push([p.duration, r.scaleY]);
2967
- tracks.push(boneTrack(b, "y", K(...yK)), boneTrack(b, "scaleX", K(...sxK)), boneTrack(b, "scaleY", K(...syK)));
3020
+ tracks.push(boneTrack(b, "y", K(...yK), "pchip"), boneTrack(b, "scaleX", K(...sxK), smooth), boneTrack(b, "scaleY", K(...syK), smooth));
2968
3021
  for (const h of byRole(doc, "head"))
2969
- tracks.push(boneTrack(h, "rotation", K([0, h.transform.rotation], [p.duration * 0.25, h.transform.rotation - 6, "back_out"], [p.duration * 0.6, h.transform.rotation + 4], [p.duration, h.transform.rotation])));
3022
+ tracks.push(boneTrack(h, "rotation", K([0, h.transform.rotation], [p.duration * 0.25, h.transform.rotation - 6], [p.duration * 0.6, h.transform.rotation + 4], [p.duration, h.transform.rotation]), smooth));
2970
3023
  const anim = finish(this.name, p.id, p.duration, false, tracks, p);
2971
3024
  anim.events.push({ t: 0, name: "win_start" }, { t: per * 0.95, name: "land" });
2972
3025
  return anim;
@@ -2979,8 +3032,8 @@ var anticipationShake = {
2979
3032
  const b = root(doc), r = b.transform, tracks = [];
2980
3033
  const n = Math.ceil(p.duration * p.frequency * 4);
2981
3034
  const env = (t) => p.grow ? t / p.duration : 1;
2982
- tracks.push(boneTrack(b, "x", sampled(p.duration, n, (t) => r.x + p.amplitude * env(t) * Math.sin(2 * Math.PI * p.frequency * t), "linear")));
2983
- tracks.push(boneTrack(b, "rotation", sampled(p.duration, n, (t) => r.rotation + 0.6 * p.amplitude * env(t) * Math.sin(2 * Math.PI * p.frequency * t + 1.2), "linear")));
3035
+ tracks.push(boneTrack(b, "x", sampled(p.duration, n, (t) => r.x + p.amplitude * env(t) * Math.sin(2 * Math.PI * p.frequency * t)), smooth));
3036
+ tracks.push(boneTrack(b, "rotation", sampled(p.duration, n, (t) => r.rotation + 0.6 * p.amplitude * env(t) * Math.sin(2 * Math.PI * p.frequency * t + 1.2)), smooth));
2984
3037
  tracks.push(boneTrack(b, "scaleX", K([0, r.scaleX, "ease_in"], [p.duration, r.scaleX * p.scaleUp])));
2985
3038
  tracks.push(boneTrack(b, "scaleY", K([0, r.scaleY, "ease_in"], [p.duration, r.scaleY * p.scaleUp])));
2986
3039
  return finish(this.name, p.id, p.duration, false, tracks, p);
@@ -3005,6 +3058,7 @@ var mouthTalk = {
3005
3058
  if (!p.frames.length) throw new Error("mouth_talk: frames must not be empty");
3006
3059
  const n = Math.max(1, Math.round(p.duration * p.rate));
3007
3060
  const keys = S(...Array.from({ length: n }, (_, i) => [+(i / p.rate).toFixed(4), p.frames[i % p.frames.length]]));
3061
+ if (keys.at(-1).t < p.duration && keys.at(-1).v !== keys[0].v) keys.push(...S([p.duration, keys[0].v]));
3008
3062
  return finish(this.name, p.id, p.duration, true, [slotTrack(slot, "attachment", keys)], p);
3009
3063
  }
3010
3064
  };
@@ -3022,10 +3076,10 @@ var symbolIdle = {
3022
3076
  build(doc, p) {
3023
3077
  const b = root(doc), r = b.transform, T2 = p.period, w = 2 * Math.PI / T2, tracks = [];
3024
3078
  const rise = (t) => 0.5 - 0.5 * Math.cos(w * t);
3025
- tracks.push(boneTrack(b, "y", sampled(T2, 8, (t) => r.y - p.float * rise(t), "ease_in_out")));
3026
- if (p.tilt) tracks.push(boneTrack(b, "rotation", sampled(T2, 8, (t) => r.rotation + p.tilt * Math.sin(w * t), "ease_in_out")));
3079
+ tracks.push(boneTrack(b, "y", sampled(T2, 8, (t) => r.y - p.float * rise(t)), smooth));
3080
+ if (p.tilt) tracks.push(boneTrack(b, "rotation", sampled(T2, 8, (t) => r.rotation + p.tilt * Math.sin(w * t)), smooth));
3027
3081
  if (p.pulse) for (const s of doc.slots.filter((s2) => s2.bone === b.id && s2.attachment))
3028
- pushTrack(tracks, deformTrack(s, "bulge", sampled(T2, 8, (t) => p.pulse * rise(t), "ease_in_out")));
3082
+ pushTrack(tracks, deformTrack(s, "bulge", sampled(T2, 8, (t) => p.pulse * rise(t)), smooth));
3029
3083
  return finish(this.name, p.id, T2, true, tracks, p);
3030
3084
  }
3031
3085
  };
@@ -3035,12 +3089,12 @@ var symbolWin = {
3035
3089
  build(doc, p) {
3036
3090
  const b = root(doc), r = b.transform, d = p.duration, tracks = [];
3037
3091
  for (const [prop, rest] of [["scaleX", r.scaleX], ["scaleY", r.scaleY]])
3038
- tracks.push(boneTrack(b, prop, K([0, rest, "ease_out"], [d * 0.2, rest * (1 + p.pop), "ease_in_out"], [d * 0.5, rest * (1 - p.pop * 0.3), "ease_out"], [d, rest])));
3092
+ tracks.push(boneTrack(b, prop, K([0, rest], [d * 0.2, rest * (1 + p.pop)], [d * 0.5, rest * (1 - p.pop * 0.3)], [d, rest]), smooth));
3039
3093
  if (p.wobble) {
3040
3094
  const n = Math.max(8, p.wobbles * 4);
3041
- const keys = sampled(d, n, (t) => p.wobble * Math.exp(-3 * t / d) * Math.sin(2 * Math.PI * p.wobbles * t / d), "ease_in_out");
3095
+ const keys = sampled(d, n, (t) => p.wobble * Math.exp(-3 * t / d) * Math.sin(2 * Math.PI * p.wobbles * t / d));
3042
3096
  keys[keys.length - 1].v = 0;
3043
- for (const s of doc.slots.filter((s2) => s2.bone === b.id && s2.attachment)) pushTrack(tracks, deformTrack(s, "sway", keys));
3097
+ for (const s of doc.slots.filter((s2) => s2.bone === b.id && s2.attachment)) pushTrack(tracks, deformTrack(s, "sway", keys, smooth));
3044
3098
  }
3045
3099
  const anim = finish(this.name, p.id, d, false, tracks, p);
3046
3100
  anim.events.push({ t: 0, name: "win_start" });
@@ -3054,11 +3108,11 @@ var weightShift = {
3054
3108
  const T2 = p.period, w = 2 * Math.PI / T2, N = 16, tracks = [];
3055
3109
  const hips = byRole(doc, "hips")[0] ?? root(doc), hr = hips.transform;
3056
3110
  const s = (t) => Math.sin(w * t);
3057
- tracks.push(boneTrack(hips, "x", sampled(T2, N, (t) => hr.x + p.shift * s(t))));
3058
- if (p.dip) tracks.push(boneTrack(hips, "y", sampled(T2, N, (t) => hr.y + p.dip * s(t) ** 2)));
3111
+ tracks.push(boneTrack(hips, "x", sampled(T2, N, (t) => hr.x + p.shift * s(t)), smooth));
3112
+ if (p.dip) tracks.push(boneTrack(hips, "y", sampled(T2, N, (t) => hr.y + p.dip * s(t) ** 2), smooth));
3059
3113
  const torso = byRole(doc, "torso", "body")[0];
3060
- if (torso && torso !== hips) tracks.push(boneTrack(torso, "rotation", sampled(T2, N, (t) => torso.transform.rotation - p.lean * s(t))));
3061
- for (const h of byRole(doc, "head")) tracks.push(boneTrack(h, "rotation", sampled(T2, N, (t) => h.transform.rotation + p.lean / 2 * s(t))));
3114
+ if (torso && torso !== hips) tracks.push(boneTrack(torso, "rotation", sampled(T2, N, (t) => torso.transform.rotation - p.lean * s(t)), smooth));
3115
+ for (const h of byRole(doc, "head")) tracks.push(boneTrack(h, "rotation", sampled(T2, N, (t) => h.transform.rotation + p.lean / 2 * s(t)), smooth));
3062
3116
  for (const leg of byRole(doc, "leg_l", "leg_r")) {
3063
3117
  const slot = slotOfBone(doc, leg), att = slot && doc.attachments.find((a) => a.id === slot.attachment && a.type === "region");
3064
3118
  const asset = att && att.type === "region" ? doc.assets.find((a) => a.id === att.asset) : void 0;
@@ -3068,8 +3122,8 @@ var weightShift = {
3068
3122
  length = Math.max(...[[0, -h / 2], [w2 / 2, 0], [0, h / 2], [-w2 / 2, 0]].map(([x, y]) => Math.hypot(...apply(m, x, y))));
3069
3123
  }
3070
3124
  if (!(length > p.shift)) continue;
3071
- tracks.push(boneTrack(leg, "rotation", sampled(T2, N, (t) => leg.transform.rotation + Math.asin(p.shift * s(t) / length) * 180 / Math.PI)));
3072
- if (p.dip) tracks.push(boneTrack(leg, "y", sampled(T2, N, (t) => leg.transform.y - p.dip * s(t) ** 2)));
3125
+ tracks.push(boneTrack(leg, "rotation", sampled(T2, N, (t) => leg.transform.rotation + Math.asin(p.shift * s(t) / length) * 180 / Math.PI), smooth));
3126
+ if (p.dip) tracks.push(boneTrack(leg, "y", sampled(T2, N, (t) => leg.transform.y - p.dip * s(t) ** 2), smooth));
3073
3127
  }
3074
3128
  return finish(this.name, p.id, T2, true, tracks, p);
3075
3129
  }
@@ -3312,13 +3366,13 @@ function motionQuality(doc, anim, opts = {}) {
3312
3366
  const before = delta(values(k.v), values(p.v)), after = delta(values(n.v), values(k.v));
3313
3367
  const pass = before.some((v, j2) => v * after[j2] > 1e-6);
3314
3368
  const speed = Math.max(magnitude(before) / (k.t - p.t), magnitude(after) / (n.t - k.t));
3315
- if (pass && Math.max(magnitude(left), magnitude(right)) < speed * 0.02) report("pass_key_stop", id, `motion stops at a pass-through key; use track interpolation pchip or matched tangents`, k.t);
3369
+ if (pass && Math.max(magnitude(left), magnitude(right)) < speed * 0.02) report("pass_key_stop", id, `motion stops at a pass-through key; use track interpolation pchip or spline, or matched tangents`, k.t);
3316
3370
  const jump = magnitude(delta(left, right));
3317
3371
  if (jump > Math.max(0.1, speed * 0.1)) report("velocity_jump", id, `velocity jumps ${jump.toFixed(3)} units/s across this key`, k.t);
3318
3372
  const acceleration = (a, b, c) => values(at(b)).map((v, j2) => (values(at(c))[j2] - 2 * v + values(at(a))[j2]) / (h * h));
3319
3373
  const al = acceleration(k.t - 2 * h, k.t - h, k.t), ar = acceleration(k.t, k.t + h, k.t + 2 * h);
3320
3374
  const aj = magnitude(delta(al, ar));
3321
- if (aj > Math.max(1, speed / Math.min(k.t - p.t, n.t - k.t) * 2)) report("acceleration_jump", id, `acceleration changes by ${aj.toFixed(2)} units/s\xB2 at this key; PCHIP guarantees velocity, not acceleration continuity`, k.t, "info");
3375
+ if (aj > Math.max(1, speed / Math.min(k.t - p.t, n.t - k.t) * 2)) report("acceleration_jump", id, `acceleration changes by ${aj.toFixed(2)} units/s\xB2 at this key; PCHIP guarantees velocity, not acceleration continuity \u2014 spline does both`, k.t, "info");
3322
3376
  }
3323
3377
  }
3324
3378
  const steps = Math.max(2, Math.ceil(D * 60));
@@ -3800,6 +3854,17 @@ function checkRig(doc, opts = {}) {
3800
3854
  out.push({ level: "warn", code: "never_visible", subject: s.id, message: `slot "${s.id}" has no attachment and no animation ever shows one` });
3801
3855
  return out;
3802
3856
  }
3857
+ function summarizeIgnoring(findings, rules, max) {
3858
+ const hits = (r, f) => r.code === f.code && (r.subject ?? f.subject) === f.subject && (r.animation ?? f.animation) === f.animation;
3859
+ const kept = findings.filter((f) => !rules.some((r) => hits(r, f)));
3860
+ const idle = rules.filter((r) => !findings.some((f) => hits(r, f)));
3861
+ const notes = [
3862
+ ...kept.length < findings.length ? [` (${findings.length - kept.length} ignored)`] : [],
3863
+ ...idle.map((r) => ` ignore matched nothing: ${JSON.stringify(r)}`)
3864
+ ];
3865
+ return [summarize(kept, max), ...notes].join("\n");
3866
+ }
3867
+ var where = (f) => `${f.subject}${f.animation ? ` in ${f.animation}` : ""}${f.time !== void 0 ? ` @${+f.time.toFixed(4)}s` : ""}`;
3803
3868
  function summarize(findings, max = 12) {
3804
3869
  if (!findings.length) return "check: OK";
3805
3870
  const rank = { error: 0, warn: 1, info: 2 };
@@ -3814,7 +3879,7 @@ function summarize(findings, max = 12) {
3814
3879
  const lines = [];
3815
3880
  for (const f of sorted) {
3816
3881
  const shown = shownPerCode.get(f.code) ?? 0;
3817
- if (shown < PER_CODE) lines.push(` [${f.level}] ${f.code}: ${f.message}`);
3882
+ if (shown < PER_CODE) lines.push(` [${f.level}] ${f.code} ${where(f)}: ${f.message}`);
3818
3883
  else if (shown === PER_CODE) lines.push(` \u2026 ${codeTotal.get(f.code) - PER_CODE} more ${f.code}`);
3819
3884
  shownPerCode.set(f.code, shown + 1);
3820
3885
  }
@@ -4005,7 +4070,7 @@ function packRects(rects, opts = {}) {
4005
4070
  shelf = Math.max(shelf, r.height);
4006
4071
  }
4007
4072
  const height = y + shelf + pad;
4008
- if (height <= max) return { width, height, frames };
4073
+ if (height <= max) return { width: Math.max(pad, ...[...frames.values()].map(([fx, , fw]) => fx + fw)) + pad, height, frames };
4009
4074
  if (width >= max) throw new Error(`atlas does not fit in ${max}\xD7${max} (needs ${width}\xD7${height})`);
4010
4075
  width *= 2;
4011
4076
  }
@@ -5612,7 +5677,8 @@ function previewCheck(doc, animation) {
5612
5677
  const a = animation ? doc.animations.find((x) => x.id === animation) : void 0;
5613
5678
  return a ? `check: OK for "${a.id}" (${a.tracks.length} tracks, ${a.duration}s${a.loop ? " loop" : ""})` : "check: OK (rest pose)";
5614
5679
  }
5615
- var TrackRef = { interpolation: z3.enum(["segment", "pchip"]).optional().describe("pchip: smooth through numeric keys, preserving extrema; segment: per-key easing"), target: z3.enum(["bone", "slot", "deform", "ik", "transform", "path", "drawOrder", "control"]), id: z3.string(), prop: z3.string() };
5680
+ var TrackId = { target: z3.enum(["bone", "slot", "deform", "ik", "transform", "path", "drawOrder", "control"]), id: z3.string(), prop: z3.string() };
5681
+ var TrackRef = { interpolation: z3.enum(["segment", "pchip", "spline"]).optional().describe("segment: per-key easing; pchip: smooth through numeric keys, never past them (continuous velocity); spline: C2 cubic through them, continuous acceleration too, may overshoot between keys \u2014 for sampled curves"), ...TrackId };
5616
5682
  tool(
5617
5683
  "rig_set_control",
5618
5684
  "Create or replace a semantic control with a finite min/max/default range. Animate target=control, prop=value. Changing a range requires matching binding grids.",
@@ -5805,11 +5871,21 @@ tool(
5805
5871
  );
5806
5872
  tool(
5807
5873
  "rig_check",
5808
- "Cheap self-critique without rendering: out_of_canvas (slot leaves the canvas, worst time), static_track, loop_seam, deform_flip (lattice folds over itself \u2014 an error), mesh_fold (a skinned/deformed mesh folds over itself: error when \u2265 5 % of the mesh folds, warning below \u2014 a dense mesh bent hard at a joint pinches the odd hairline triangle), weights_sum (skinned mesh vertex weights not summing to 1), patch_offset (a slot sits away from its asset's source bbox), rest_overlap, unused_asset, never_visible, and for constraints: constraint_dead (every mix is 0 at rest and no track lifts one \u2014 the solver skips it), constraint_cycle (the target is a bone the constraint drives, or hangs off one: it reads what it writes \u2014 an error), ik_unreachable (the target sits beyond the chain's reach for over 20 % of an animation, so the limb hangs straight), path_missing (a path constraint whose target slot never shows a path attachment \u2014 an error). Motion diagnostics also cover pass_key_stop, velocity_jump, acceleration_jump, loop_velocity and constraint_order; provide secondaryBones, frontOf slot pairs and named attachment UV landmarks for brief-specific frozen-motion, ordering and swap-registration checks. Findings are sorted by severity. Run it before spending vision tokens on a preview.",
5809
- { path: Path, animation: z3.string().optional().describe("limit to one animation (rest pose is always checked)"), fps: z3.number().positive().optional(), deformFps: z3.number().positive().optional(), secondaryBones: z3.array(z3.string()).optional(), frontOf: z3.array(z3.tuple([z3.string(), z3.string()])).optional(), landmarks: z3.record(z3.record(z3.tuple([z3.number(), z3.number()]))).optional(), landmarkTolerance: z3.number().nonnegative().optional() },
5810
- async ({ path, ...opts }) => {
5874
+ "Cheap self-critique without rendering: out_of_canvas (slot leaves the canvas, worst time), static_track, loop_seam, deform_flip (lattice folds over itself \u2014 an error), mesh_fold (a skinned/deformed mesh folds over itself: error when \u2265 5 % of the mesh folds, warning below \u2014 a dense mesh bent hard at a joint pinches the odd hairline triangle), weights_sum (skinned mesh vertex weights not summing to 1), patch_offset (a slot sits away from its asset's source bbox), rest_overlap, unused_asset, never_visible, and for constraints: constraint_dead (every mix is 0 at rest and no track lifts one \u2014 the solver skips it), constraint_cycle (the target is a bone the constraint drives, or hangs off one: it reads what it writes \u2014 an error), ik_unreachable (the target sits beyond the chain's reach for over 20 % of an animation, so the limb hangs straight), path_missing (a path constraint whose target slot never shows a path attachment \u2014 an error). Motion diagnostics also cover pass_key_stop, velocity_jump, acceleration_jump, loop_velocity and constraint_order; provide secondaryBones, frontOf slot pairs and named attachment UV landmarks for brief-specific frozen-motion, ordering and swap-registration checks. Findings are sorted by severity; each line names its subject (a track as target.id.prop), animation and time. `ignore` drops findings you have read and accept \u2014 by code, narrowed by subject and animation; the reply counts them and names a rule that matched nothing. Run it before spending vision tokens on a preview.",
5875
+ {
5876
+ path: Path,
5877
+ animation: z3.string().optional().describe("limit to one animation (rest pose is always checked)"),
5878
+ fps: z3.number().positive().optional(),
5879
+ deformFps: z3.number().positive().optional(),
5880
+ secondaryBones: z3.array(z3.string()).optional(),
5881
+ frontOf: z3.array(z3.tuple([z3.string(), z3.string()])).optional(),
5882
+ landmarks: z3.record(z3.record(z3.tuple([z3.number(), z3.number()]))).optional(),
5883
+ landmarkTolerance: z3.number().nonnegative().optional(),
5884
+ ignore: z3.array(z3.object({ code: z3.string(), subject: z3.string().optional(), animation: z3.string().optional() }).strict()).optional()
5885
+ },
5886
+ async ({ path, ignore = [], ...opts }) => {
5811
5887
  try {
5812
- return text(summarize(checkRig(await load(path), opts), 100));
5888
+ return text(summarizeIgnoring(checkRig(await load(path), opts), ignore, 100));
5813
5889
  } catch (e) {
5814
5890
  return fail(e);
5815
5891
  }
@@ -6426,9 +6502,9 @@ tool(
6426
6502
  );
6427
6503
  tool(
6428
6504
  "rig_adjust_track",
6429
- "Re-tune an existing track without retyping keys: amplitude scales its deviation from the rest value (0.5 = half as strong, 2 = twice; scale props as ratios), offset adds a constant, timeScale stretches its key times (duration grows to fit). Drops the preset link.",
6505
+ "Re-tune an existing track without retyping keys: amplitude scales its deviation from the rest value (0.5 = half as strong, 2 = twice; scale props as ratios), offset adds a constant, timeScale stretches its key times (duration grows to fit), interpolation switches segment / pchip / spline. Drops the preset link.",
6430
6506
  { path: Path, animation: z3.string(), ...TrackRef, amplitude: z3.number().optional(), offset: z3.number().optional(), timeScale: z3.number().positive().optional() },
6431
- async ({ path, animation, target, id, prop, ...adj }) => mutate(path, (d) => adjustTrack(d, animation, { target, id, prop }, adj))
6507
+ async ({ path, animation, target, id, prop, interpolation, ...adj }) => mutate(path, (d) => adjustTrack(d, animation, { target, id, prop, interpolation }, adj))
6432
6508
  );
6433
6509
  tool(
6434
6510
  "rig_set_animation",
@@ -6446,7 +6522,7 @@ tool(
6446
6522
  tool(
6447
6523
  "rig_remove_keys",
6448
6524
  "Remove the keys of one track at the given times; an emptied track is dropped. Drops the preset link.",
6449
- { path: Path, animation: z3.string(), ...TrackRef, times: z3.array(z3.number()).min(1) },
6525
+ { path: Path, animation: z3.string(), ...TrackId, times: z3.array(z3.number()).min(1) },
6450
6526
  async ({ path, animation, target, id, prop, times: times2 }) => mutate(path, (d) => removeKeys(d, animation, { target, id, prop }, times2))
6451
6527
  );
6452
6528
  tool(
@@ -6624,9 +6700,15 @@ async function callToolVersioned(name2, args) {
6624
6700
 
6625
6701
  // src/tool-schema.ts
6626
6702
  import { zodToJsonSchema } from "zod-to-json-schema";
6703
+ function prefixItems(node) {
6704
+ if (Array.isArray(node)) return node.map(prefixItems);
6705
+ if (!node || typeof node !== "object") return node;
6706
+ const { items, ...rest } = Object.fromEntries(Object.entries(node).map(([k, v]) => [k, prefixItems(v)]));
6707
+ return items === void 0 ? rest : Array.isArray(items) ? { ...rest, prefixItems: items } : { ...rest, items };
6708
+ }
6627
6709
  function jsonSchemaOf(tool2) {
6628
6710
  const { $schema: _drop, ...schema } = zodToJsonSchema(tool2.input, { $refStrategy: "none" });
6629
- return schema;
6711
+ return prefixItems(schema);
6630
6712
  }
6631
6713
 
6632
6714
  // src/e8/agent.ts
@@ -6672,6 +6754,7 @@ var NESTED_TEXT_ARGS = [
6672
6754
  "animation",
6673
6755
  "b",
6674
6756
  "bone",
6757
+ "code",
6675
6758
  "control",
6676
6759
  "from",
6677
6760
  "id",
@@ -6682,6 +6765,7 @@ var NESTED_TEXT_ARGS = [
6682
6765
  "requiredAnimations",
6683
6766
  "sourceImage",
6684
6767
  "src",
6768
+ "subject",
6685
6769
  "to",
6686
6770
  "v",
6687
6771
  "whenSlotVisible"