@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.
@@ -1,3 +1,3 @@
1
1
  import type { ToolDef } from "./rig-api";
2
- /** draft-07, every ref inlined, no `$schema` key — the tool's own strict zod object, so unknown keys stay refused */
2
+ /** draft 2020-12, every ref inlined, no `$schema` key — the tool's own strict zod object, so unknown keys stay refused */
3
3
  export declare function jsonSchemaOf(tool: ToolDef): Record<string, unknown>;
package/dist/lib/tools.js CHANGED
@@ -293,7 +293,7 @@ var Key = z.object({
293
293
  /** Optional independent easing for numeric-array components (for example Spine RGBA curves). */
294
294
  componentEase: z.array(Ease).min(1).optional()
295
295
  });
296
- var Interpolation = { interpolation: z.enum(["segment", "pchip"]).optional() };
296
+ var Interpolation = { interpolation: z.enum(["segment", "pchip", "spline"]).optional() };
297
297
  var Track = z.discriminatedUnion("target", [
298
298
  z.object({ target: z.literal("control"), id: Id, prop: z.literal("value"), ...Interpolation, keys: z.array(Key).min(1) }),
299
299
  z.object({ target: z.literal("bone"), id: Id, prop: BoneProp, ...Interpolation, keys: z.array(Key).min(1) }),
@@ -727,6 +727,57 @@ function smoothValue(track, i, t, component, loop) {
727
727
  const u = (t - k[i].t) / h(i), u2 = u * u, u3 = u2 * u;
728
728
  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);
729
729
  }
730
+ function tridiagonal(a, b, c, r) {
731
+ const n = b.length, cp = [], x = [];
732
+ let beta = b[0];
733
+ x[0] = r[0] / beta;
734
+ for (let i = 1; i < n; i++) {
735
+ cp[i] = c[i - 1] / beta;
736
+ beta = b[i] - a[i] * cp[i];
737
+ x[i] = (r[i] - a[i] * x[i - 1]) / beta;
738
+ }
739
+ for (let i = n - 2; i >= 0; i--) x[i] -= cp[i + 1] * x[i + 1];
740
+ return x;
741
+ }
742
+ function splineMoments(t, v, periodic) {
743
+ const n = t.length, h = (j2) => t[j2 + 1] - t[j2], d = (j2) => (v[j2 + 1] - v[j2]) / h(j2);
744
+ if (!periodic) {
745
+ const a2 = [], b2 = [], c2 = [], r2 = [];
746
+ for (let i = 0; i < n; i++) {
747
+ const hp = i > 0 ? h(i - 1) : 0, hn = i < n - 1 ? h(i) : 0;
748
+ a2.push(hp);
749
+ b2.push(2 * (hp + hn));
750
+ c2.push(hn);
751
+ r2.push(6 * ((i < n - 1 ? d(i) : 0) - (i > 0 ? d(i - 1) : 0)));
752
+ }
753
+ return tridiagonal(a2, b2, c2, r2);
754
+ }
755
+ const m = n - 1, a = [], b = [], c = [], r = [];
756
+ for (let i = 0; i < m; i++) {
757
+ const p = (i + m - 1) % m;
758
+ a.push(h(p));
759
+ b.push(2 * (h(p) + h(i)));
760
+ c.push(h(i));
761
+ r.push(6 * (d(i) - d(p)));
762
+ }
763
+ const top = a[0], bottom = c[m - 1], gamma = -b[0];
764
+ const bb = [...b];
765
+ bb[0] -= gamma;
766
+ bb[m - 1] -= bottom * top / gamma;
767
+ const x = tridiagonal(a, bb, c, r), u = new Array(m).fill(0);
768
+ u[0] = gamma;
769
+ u[m - 1] = bottom;
770
+ const z4 = tridiagonal(a, bb, c, u);
771
+ const f = (x[0] + top * x[m - 1] / gamma) / (1 + z4[0] + top * z4[m - 1] / gamma);
772
+ const M = x.map((xi, i) => xi - f * z4[i]);
773
+ return [...M, M[0]];
774
+ }
775
+ function splineValue(track, i, t, component, loop) {
776
+ const k = track.keys, ts = k.map((x) => x.t), vs = k.map((x) => component === void 0 ? x.v : x.v[component]);
777
+ const M = splineMoments(ts, vs, loop && k.length > 3 && vs[0] === vs[k.length - 1]);
778
+ const H = ts[i + 1] - ts[i], A = (ts[i + 1] - t) / H, B = (t - ts[i]) / H;
779
+ return A * vs[i] + B * vs[i + 1] + ((A ** 3 - A) * M[i] + (B ** 3 - B) * M[i + 1]) * H * H / 6;
780
+ }
730
781
  function sample(track, t, loop = false) {
731
782
  const keys = track.keys;
732
783
  if (t <= keys[0].t) return keys[0].v;
@@ -738,11 +789,13 @@ function sample(track, t, loop = false) {
738
789
  if (a.ease === "step") return a.v;
739
790
  if (typeof a.v === "number" && typeof b.v === "number") {
740
791
  if (track.interpolation === "pchip") return smoothValue(track, i, t, void 0, loop);
792
+ if (track.interpolation === "spline") return splineValue(track, i, t, void 0, loop);
741
793
  const u = ease(a.ease)((t - a.t) / (b.t - a.t));
742
794
  return a.v + (b.v - a.v) * u;
743
795
  }
744
796
  if (Array.isArray(a.v) && Array.isArray(b.v) && typeof a.v[0] === "number") {
745
797
  if (track.interpolation === "pchip") return a.v.map((_, j2) => smoothValue(track, i, t, j2, loop));
798
+ if (track.interpolation === "spline") return a.v.map((_, j2) => splineValue(track, i, t, j2, loop));
746
799
  const phase = (t - a.t) / (b.t - a.t), av = a.v, bv = b.v;
747
800
  return av.map((v, k) => v + ((bv[k] ?? v) - v) * ease(a.componentEase?.[k] ?? a.ease)(phase));
748
801
  }
@@ -2474,7 +2527,8 @@ function adjustTrack(doc, animId, track, adj) {
2474
2527
  return { ...k, t: r42(adj.timeScale ? k.t * adj.timeScale : k.t), v: r42(v) };
2475
2528
  });
2476
2529
  const duration = Math.max(anim.duration, keys[keys.length - 1].t);
2477
- const next = { ...anim, duration, tracks: anim.tracks.map((t) => same(t) ? { ...t, keys } : t), preset: void 0 };
2530
+ const interpolation = track.interpolation ?? tr.interpolation;
2531
+ const next = { ...anim, duration, tracks: anim.tracks.map((t) => same(t) ? { ...t, keys, ...interpolation && { interpolation } } : t), preset: void 0 };
2478
2532
  return { ...doc, animations: doc.animations.map((a) => a.id === animId ? next : a) };
2479
2533
  }
2480
2534
  function removeAnimation(doc, id) {
@@ -2891,19 +2945,20 @@ var requireSlot = (doc, id) => {
2891
2945
  if (!s) throw new Error(`unknown slot "${id}"`);
2892
2946
  return s;
2893
2947
  };
2894
- function sampled(duration, n, f, ease2 = "linear") {
2948
+ function sampled(duration, n, f) {
2895
2949
  const keys = [];
2896
2950
  for (let i = 0; i <= n; i++) {
2897
2951
  const t = +(duration * i / n).toFixed(4);
2898
- keys.push({ t, v: +f(t).toFixed(4) + 0, ease: ease2 });
2952
+ keys.push({ t, v: +f(t).toFixed(4) + 0, ease: "linear" });
2899
2953
  }
2900
2954
  return keys;
2901
2955
  }
2902
- function boneTrack(bone, prop, keys) {
2903
- return { target: "bone", id: bone.id, prop, keys };
2956
+ var smooth = "spline";
2957
+ function boneTrack(bone, prop, keys, interpolation) {
2958
+ return { target: "bone", id: bone.id, prop, keys, ...interpolation && { interpolation } };
2904
2959
  }
2905
- function deformTrack(slot, prop, keys) {
2906
- return slot.attachment ? { target: "deform", id: slot.attachment, prop, keys } : void 0;
2960
+ function deformTrack(slot, prop, keys, interpolation) {
2961
+ return slot.attachment ? { target: "deform", id: slot.attachment, prop, keys, ...interpolation && { interpolation } } : void 0;
2907
2962
  }
2908
2963
  var pushTrack = (tracks, t) => {
2909
2964
  if (t) tracks.push(t);
@@ -2927,18 +2982,18 @@ var idleBreathing = {
2927
2982
  const armsR = byRole(doc, "arm_r", "arm_upper_r");
2928
2983
  const chest = slotOfBone(doc, torso);
2929
2984
  if (chest?.attachment) {
2930
- pushTrack(tracks, deformTrack(chest, "bulge", sampled(T2, 8, (t) => p.amplitude * Math.sin(w * t), "ease_in_out")));
2985
+ pushTrack(tracks, deformTrack(chest, "bulge", sampled(T2, 8, (t) => p.amplitude * Math.sin(w * t)), smooth));
2931
2986
  } else {
2932
2987
  const r = torso.transform;
2933
- tracks.push(boneTrack(torso, "scaleY", sampled(T2, 8, (t) => r.scaleY + p.amplitude * Math.sin(w * t), "ease_in_out")));
2934
- tracks.push(boneTrack(torso, "scaleX", sampled(T2, 8, (t) => r.scaleX - p.amplitude * 0.4 * Math.sin(w * t), "ease_in_out")));
2988
+ tracks.push(boneTrack(torso, "scaleY", sampled(T2, 8, (t) => r.scaleY + p.amplitude * Math.sin(w * t)), smooth));
2989
+ tracks.push(boneTrack(torso, "scaleX", sampled(T2, 8, (t) => r.scaleX - p.amplitude * 0.4 * Math.sin(w * t)), smooth));
2935
2990
  }
2936
2991
  for (const h of heads)
2937
- tracks.push(boneTrack(h, "y", sampled(T2, 8, (t) => h.transform.y - p.headBob * Math.sin(w * t + 0.4), "ease_in_out")));
2992
+ tracks.push(boneTrack(h, "y", sampled(T2, 8, (t) => h.transform.y - p.headBob * Math.sin(w * t + 0.4)), smooth));
2938
2993
  for (const a of armsL)
2939
- tracks.push(boneTrack(a, "rotation", sampled(T2, 8, (t) => a.transform.rotation + p.armSway * Math.sin(w * t + 0.8), "ease_in_out")));
2994
+ tracks.push(boneTrack(a, "rotation", sampled(T2, 8, (t) => a.transform.rotation + p.armSway * Math.sin(w * t + 0.8)), smooth));
2940
2995
  for (const a of armsR)
2941
- tracks.push(boneTrack(a, "rotation", sampled(T2, 8, (t) => a.transform.rotation - p.armSway * Math.sin(w * t + 0.8), "ease_in_out")));
2996
+ tracks.push(boneTrack(a, "rotation", sampled(T2, 8, (t) => a.transform.rotation - p.armSway * Math.sin(w * t + 0.8)), smooth));
2942
2997
  return finish(this.name, p.id, T2, true, tracks, p);
2943
2998
  }
2944
2999
  };
@@ -2947,22 +3002,20 @@ var winBounce = {
2947
3002
  defaults: { id: "win", duration: 1.2, height: 40, bounces: 2, squash: 0.15 },
2948
3003
  build(doc, p) {
2949
3004
  const b = root(doc), r = b.transform, tracks = [];
2950
- const yK = [[0, r.y, "ease_out"]];
2951
- const sxK = [[0, r.scaleX, "ease_out"]];
2952
- const syK = [[0, r.scaleY, "ease_out"]];
3005
+ const yK = [[0, r.y]], sxK = [[0, r.scaleX]], syK = [[0, r.scaleY]];
2953
3006
  const per = p.duration / p.bounces;
2954
3007
  for (let i = 0; i < p.bounces; i++) {
2955
3008
  const t0 = i * per, h = p.height * 0.55 ** i, s = p.squash * 0.7 ** i;
2956
- 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"]);
2957
- 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"]);
2958
- 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"]);
3009
+ 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)]);
3010
+ 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)]);
3011
+ yK.push([t0 + per * 0.08, r.y], [t0 + per * 0.5, r.y - h], [t0 + per * 0.95, r.y]);
2959
3012
  }
2960
3013
  yK.push([p.duration, r.y]);
2961
3014
  sxK.push([p.duration, r.scaleX]);
2962
3015
  syK.push([p.duration, r.scaleY]);
2963
- tracks.push(boneTrack(b, "y", K(...yK)), boneTrack(b, "scaleX", K(...sxK)), boneTrack(b, "scaleY", K(...syK)));
3016
+ tracks.push(boneTrack(b, "y", K(...yK), "pchip"), boneTrack(b, "scaleX", K(...sxK), smooth), boneTrack(b, "scaleY", K(...syK), smooth));
2964
3017
  for (const h of byRole(doc, "head"))
2965
- 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])));
3018
+ 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));
2966
3019
  const anim = finish(this.name, p.id, p.duration, false, tracks, p);
2967
3020
  anim.events.push({ t: 0, name: "win_start" }, { t: per * 0.95, name: "land" });
2968
3021
  return anim;
@@ -2975,8 +3028,8 @@ var anticipationShake = {
2975
3028
  const b = root(doc), r = b.transform, tracks = [];
2976
3029
  const n = Math.ceil(p.duration * p.frequency * 4);
2977
3030
  const env = (t) => p.grow ? t / p.duration : 1;
2978
- 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")));
2979
- 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")));
3031
+ 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));
3032
+ 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));
2980
3033
  tracks.push(boneTrack(b, "scaleX", K([0, r.scaleX, "ease_in"], [p.duration, r.scaleX * p.scaleUp])));
2981
3034
  tracks.push(boneTrack(b, "scaleY", K([0, r.scaleY, "ease_in"], [p.duration, r.scaleY * p.scaleUp])));
2982
3035
  return finish(this.name, p.id, p.duration, false, tracks, p);
@@ -3001,6 +3054,7 @@ var mouthTalk = {
3001
3054
  if (!p.frames.length) throw new Error("mouth_talk: frames must not be empty");
3002
3055
  const n = Math.max(1, Math.round(p.duration * p.rate));
3003
3056
  const keys = S(...Array.from({ length: n }, (_, i) => [+(i / p.rate).toFixed(4), p.frames[i % p.frames.length]]));
3057
+ if (keys.at(-1).t < p.duration && keys.at(-1).v !== keys[0].v) keys.push(...S([p.duration, keys[0].v]));
3004
3058
  return finish(this.name, p.id, p.duration, true, [slotTrack(slot, "attachment", keys)], p);
3005
3059
  }
3006
3060
  };
@@ -3018,10 +3072,10 @@ var symbolIdle = {
3018
3072
  build(doc, p) {
3019
3073
  const b = root(doc), r = b.transform, T2 = p.period, w = 2 * Math.PI / T2, tracks = [];
3020
3074
  const rise = (t) => 0.5 - 0.5 * Math.cos(w * t);
3021
- tracks.push(boneTrack(b, "y", sampled(T2, 8, (t) => r.y - p.float * rise(t), "ease_in_out")));
3022
- if (p.tilt) tracks.push(boneTrack(b, "rotation", sampled(T2, 8, (t) => r.rotation + p.tilt * Math.sin(w * t), "ease_in_out")));
3075
+ tracks.push(boneTrack(b, "y", sampled(T2, 8, (t) => r.y - p.float * rise(t)), smooth));
3076
+ if (p.tilt) tracks.push(boneTrack(b, "rotation", sampled(T2, 8, (t) => r.rotation + p.tilt * Math.sin(w * t)), smooth));
3023
3077
  if (p.pulse) for (const s of doc.slots.filter((s2) => s2.bone === b.id && s2.attachment))
3024
- pushTrack(tracks, deformTrack(s, "bulge", sampled(T2, 8, (t) => p.pulse * rise(t), "ease_in_out")));
3078
+ pushTrack(tracks, deformTrack(s, "bulge", sampled(T2, 8, (t) => p.pulse * rise(t)), smooth));
3025
3079
  return finish(this.name, p.id, T2, true, tracks, p);
3026
3080
  }
3027
3081
  };
@@ -3031,12 +3085,12 @@ var symbolWin = {
3031
3085
  build(doc, p) {
3032
3086
  const b = root(doc), r = b.transform, d = p.duration, tracks = [];
3033
3087
  for (const [prop, rest] of [["scaleX", r.scaleX], ["scaleY", r.scaleY]])
3034
- 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])));
3088
+ 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));
3035
3089
  if (p.wobble) {
3036
3090
  const n = Math.max(8, p.wobbles * 4);
3037
- 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");
3091
+ const keys = sampled(d, n, (t) => p.wobble * Math.exp(-3 * t / d) * Math.sin(2 * Math.PI * p.wobbles * t / d));
3038
3092
  keys[keys.length - 1].v = 0;
3039
- for (const s of doc.slots.filter((s2) => s2.bone === b.id && s2.attachment)) pushTrack(tracks, deformTrack(s, "sway", keys));
3093
+ for (const s of doc.slots.filter((s2) => s2.bone === b.id && s2.attachment)) pushTrack(tracks, deformTrack(s, "sway", keys, smooth));
3040
3094
  }
3041
3095
  const anim = finish(this.name, p.id, d, false, tracks, p);
3042
3096
  anim.events.push({ t: 0, name: "win_start" });
@@ -3050,11 +3104,11 @@ var weightShift = {
3050
3104
  const T2 = p.period, w = 2 * Math.PI / T2, N = 16, tracks = [];
3051
3105
  const hips = byRole(doc, "hips")[0] ?? root(doc), hr = hips.transform;
3052
3106
  const s = (t) => Math.sin(w * t);
3053
- tracks.push(boneTrack(hips, "x", sampled(T2, N, (t) => hr.x + p.shift * s(t))));
3054
- if (p.dip) tracks.push(boneTrack(hips, "y", sampled(T2, N, (t) => hr.y + p.dip * s(t) ** 2)));
3107
+ tracks.push(boneTrack(hips, "x", sampled(T2, N, (t) => hr.x + p.shift * s(t)), smooth));
3108
+ if (p.dip) tracks.push(boneTrack(hips, "y", sampled(T2, N, (t) => hr.y + p.dip * s(t) ** 2), smooth));
3055
3109
  const torso = byRole(doc, "torso", "body")[0];
3056
- if (torso && torso !== hips) tracks.push(boneTrack(torso, "rotation", sampled(T2, N, (t) => torso.transform.rotation - p.lean * s(t))));
3057
- for (const h of byRole(doc, "head")) tracks.push(boneTrack(h, "rotation", sampled(T2, N, (t) => h.transform.rotation + p.lean / 2 * s(t))));
3110
+ if (torso && torso !== hips) tracks.push(boneTrack(torso, "rotation", sampled(T2, N, (t) => torso.transform.rotation - p.lean * s(t)), smooth));
3111
+ 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));
3058
3112
  for (const leg of byRole(doc, "leg_l", "leg_r")) {
3059
3113
  const slot = slotOfBone(doc, leg), att = slot && doc.attachments.find((a) => a.id === slot.attachment && a.type === "region");
3060
3114
  const asset = att && att.type === "region" ? doc.assets.find((a) => a.id === att.asset) : void 0;
@@ -3064,8 +3118,8 @@ var weightShift = {
3064
3118
  length = Math.max(...[[0, -h / 2], [w2 / 2, 0], [0, h / 2], [-w2 / 2, 0]].map(([x, y]) => Math.hypot(...apply(m, x, y))));
3065
3119
  }
3066
3120
  if (!(length > p.shift)) continue;
3067
- tracks.push(boneTrack(leg, "rotation", sampled(T2, N, (t) => leg.transform.rotation + Math.asin(p.shift * s(t) / length) * 180 / Math.PI)));
3068
- if (p.dip) tracks.push(boneTrack(leg, "y", sampled(T2, N, (t) => leg.transform.y - p.dip * s(t) ** 2)));
3121
+ tracks.push(boneTrack(leg, "rotation", sampled(T2, N, (t) => leg.transform.rotation + Math.asin(p.shift * s(t) / length) * 180 / Math.PI), smooth));
3122
+ if (p.dip) tracks.push(boneTrack(leg, "y", sampled(T2, N, (t) => leg.transform.y - p.dip * s(t) ** 2), smooth));
3069
3123
  }
3070
3124
  return finish(this.name, p.id, T2, true, tracks, p);
3071
3125
  }
@@ -3308,13 +3362,13 @@ function motionQuality(doc, anim, opts = {}) {
3308
3362
  const before = delta(values(k.v), values(p.v)), after = delta(values(n.v), values(k.v));
3309
3363
  const pass = before.some((v, j2) => v * after[j2] > 1e-6);
3310
3364
  const speed = Math.max(magnitude(before) / (k.t - p.t), magnitude(after) / (n.t - k.t));
3311
- 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);
3365
+ 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);
3312
3366
  const jump = magnitude(delta(left, right));
3313
3367
  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);
3314
3368
  const acceleration = (a, b, c) => values(at(b)).map((v, j2) => (values(at(c))[j2] - 2 * v + values(at(a))[j2]) / (h * h));
3315
3369
  const al = acceleration(k.t - 2 * h, k.t - h, k.t), ar = acceleration(k.t, k.t + h, k.t + 2 * h);
3316
3370
  const aj = magnitude(delta(al, ar));
3317
- 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");
3371
+ 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");
3318
3372
  }
3319
3373
  }
3320
3374
  const steps = Math.max(2, Math.ceil(D * 60));
@@ -3796,6 +3850,17 @@ function checkRig(doc, opts = {}) {
3796
3850
  out.push({ level: "warn", code: "never_visible", subject: s.id, message: `slot "${s.id}" has no attachment and no animation ever shows one` });
3797
3851
  return out;
3798
3852
  }
3853
+ function summarizeIgnoring(findings, rules, max) {
3854
+ const hits = (r, f) => r.code === f.code && (r.subject ?? f.subject) === f.subject && (r.animation ?? f.animation) === f.animation;
3855
+ const kept = findings.filter((f) => !rules.some((r) => hits(r, f)));
3856
+ const idle = rules.filter((r) => !findings.some((f) => hits(r, f)));
3857
+ const notes = [
3858
+ ...kept.length < findings.length ? [` (${findings.length - kept.length} ignored)`] : [],
3859
+ ...idle.map((r) => ` ignore matched nothing: ${JSON.stringify(r)}`)
3860
+ ];
3861
+ return [summarize(kept, max), ...notes].join("\n");
3862
+ }
3863
+ var where = (f) => `${f.subject}${f.animation ? ` in ${f.animation}` : ""}${f.time !== void 0 ? ` @${+f.time.toFixed(4)}s` : ""}`;
3799
3864
  function summarize(findings, max = 12) {
3800
3865
  if (!findings.length) return "check: OK";
3801
3866
  const rank = { error: 0, warn: 1, info: 2 };
@@ -3810,7 +3875,7 @@ function summarize(findings, max = 12) {
3810
3875
  const lines = [];
3811
3876
  for (const f of sorted) {
3812
3877
  const shown = shownPerCode.get(f.code) ?? 0;
3813
- if (shown < PER_CODE) lines.push(` [${f.level}] ${f.code}: ${f.message}`);
3878
+ if (shown < PER_CODE) lines.push(` [${f.level}] ${f.code} ${where(f)}: ${f.message}`);
3814
3879
  else if (shown === PER_CODE) lines.push(` \u2026 ${codeTotal.get(f.code) - PER_CODE} more ${f.code}`);
3815
3880
  shownPerCode.set(f.code, shown + 1);
3816
3881
  }
@@ -4001,7 +4066,7 @@ function packRects(rects, opts = {}) {
4001
4066
  shelf = Math.max(shelf, r.height);
4002
4067
  }
4003
4068
  const height = y + shelf + pad;
4004
- if (height <= max) return { width, height, frames };
4069
+ if (height <= max) return { width: Math.max(pad, ...[...frames.values()].map(([fx, , fw]) => fx + fw)) + pad, height, frames };
4005
4070
  if (width >= max) throw new Error(`atlas does not fit in ${max}\xD7${max} (needs ${width}\xD7${height})`);
4006
4071
  width *= 2;
4007
4072
  }
@@ -5608,7 +5673,8 @@ function previewCheck(doc, animation) {
5608
5673
  const a = animation ? doc.animations.find((x) => x.id === animation) : void 0;
5609
5674
  return a ? `check: OK for "${a.id}" (${a.tracks.length} tracks, ${a.duration}s${a.loop ? " loop" : ""})` : "check: OK (rest pose)";
5610
5675
  }
5611
- 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() };
5676
+ var TrackId = { target: z3.enum(["bone", "slot", "deform", "ik", "transform", "path", "drawOrder", "control"]), id: z3.string(), prop: z3.string() };
5677
+ 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 };
5612
5678
  tool(
5613
5679
  "rig_set_control",
5614
5680
  "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.",
@@ -5801,11 +5867,21 @@ tool(
5801
5867
  );
5802
5868
  tool(
5803
5869
  "rig_check",
5804
- "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.",
5805
- { 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() },
5806
- async ({ path, ...opts }) => {
5870
+ "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.",
5871
+ {
5872
+ path: Path,
5873
+ animation: z3.string().optional().describe("limit to one animation (rest pose is always checked)"),
5874
+ fps: z3.number().positive().optional(),
5875
+ deformFps: z3.number().positive().optional(),
5876
+ secondaryBones: z3.array(z3.string()).optional(),
5877
+ frontOf: z3.array(z3.tuple([z3.string(), z3.string()])).optional(),
5878
+ landmarks: z3.record(z3.record(z3.tuple([z3.number(), z3.number()]))).optional(),
5879
+ landmarkTolerance: z3.number().nonnegative().optional(),
5880
+ ignore: z3.array(z3.object({ code: z3.string(), subject: z3.string().optional(), animation: z3.string().optional() }).strict()).optional()
5881
+ },
5882
+ async ({ path, ignore = [], ...opts }) => {
5807
5883
  try {
5808
- return text(summarize(checkRig(await load(path), opts), 100));
5884
+ return text(summarizeIgnoring(checkRig(await load(path), opts), ignore, 100));
5809
5885
  } catch (e) {
5810
5886
  return fail(e);
5811
5887
  }
@@ -6422,9 +6498,9 @@ tool(
6422
6498
  );
6423
6499
  tool(
6424
6500
  "rig_adjust_track",
6425
- "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.",
6501
+ "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.",
6426
6502
  { path: Path, animation: z3.string(), ...TrackRef, amplitude: z3.number().optional(), offset: z3.number().optional(), timeScale: z3.number().positive().optional() },
6427
- async ({ path, animation, target, id, prop, ...adj }) => mutate(path, (d) => adjustTrack(d, animation, { target, id, prop }, adj))
6503
+ async ({ path, animation, target, id, prop, interpolation, ...adj }) => mutate(path, (d) => adjustTrack(d, animation, { target, id, prop, interpolation }, adj))
6428
6504
  );
6429
6505
  tool(
6430
6506
  "rig_set_animation",
@@ -6442,7 +6518,7 @@ tool(
6442
6518
  tool(
6443
6519
  "rig_remove_keys",
6444
6520
  "Remove the keys of one track at the given times; an emptied track is dropped. Drops the preset link.",
6445
- { path: Path, animation: z3.string(), ...TrackRef, times: z3.array(z3.number()).min(1) },
6521
+ { path: Path, animation: z3.string(), ...TrackId, times: z3.array(z3.number()).min(1) },
6446
6522
  async ({ path, animation, target, id, prop, times: times2 }) => mutate(path, (d) => removeKeys(d, animation, { target, id, prop }, times2))
6447
6523
  );
6448
6524
  tool(
@@ -6620,9 +6696,15 @@ async function callToolVersioned(name, args) {
6620
6696
 
6621
6697
  // src/tool-schema.ts
6622
6698
  import { zodToJsonSchema } from "zod-to-json-schema";
6699
+ function prefixItems(node) {
6700
+ if (Array.isArray(node)) return node.map(prefixItems);
6701
+ if (!node || typeof node !== "object") return node;
6702
+ const { items, ...rest } = Object.fromEntries(Object.entries(node).map(([k, v]) => [k, prefixItems(v)]));
6703
+ return items === void 0 ? rest : Array.isArray(items) ? { ...rest, prefixItems: items } : { ...rest, items };
6704
+ }
6623
6705
  function jsonSchemaOf(tool2) {
6624
6706
  const { $schema: _drop, ...schema } = zodToJsonSchema(tool2.input, { $refStrategy: "none" });
6625
- return schema;
6707
+ return prefixItems(schema);
6626
6708
  }
6627
6709
  export {
6628
6710
  callTool,