@energy8platform/golem 0.6.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.
- package/README.md +14 -0
- package/dist/editor.css +3 -4
- package/dist/editor.js +202 -101
- package/dist/lib/cli.js +225 -118
- package/dist/lib/cli.js.map +4 -4
- package/dist/lib/e8/agent.d.ts +35 -0
- package/dist/lib/e8/host.d.ts +22 -0
- package/dist/lib/e8/node.d.ts +66 -0
- package/dist/lib/e8/runtime.d.ts +10 -0
- package/dist/lib/e8/schema.d.ts +3 -0
- package/dist/lib/e8/types.d.ts +80 -0
- package/dist/lib/e8-agent.js +6865 -0
- package/dist/lib/e8-agent.js.map +7 -0
- package/dist/lib/e8-client.js +116 -0
- package/dist/lib/e8-host.js +7005 -0
- package/dist/lib/e8-host.js.map +7 -0
- package/dist/lib/e8-runtime.js +2079 -0
- package/dist/lib/e8-runtime.js.map +7 -0
- package/dist/lib/e8-schema.js +7 -0
- package/dist/lib/e8-schema.js.map +7 -0
- package/dist/lib/editor/api.d.ts +1 -0
- package/dist/lib/editor/embed.d.ts +9 -0
- package/dist/lib/editor/server.d.ts +23 -1
- package/dist/lib/editor/store.d.ts +34 -32
- package/dist/lib/editor/timeline.d.ts +15 -0
- package/dist/lib/editor-entry.d.ts +1 -1
- package/dist/lib/editor-entry.js +226 -118
- package/dist/lib/editor-entry.js.map +4 -4
- package/dist/lib/harness.js +54 -1
- package/dist/lib/interactive-editor.js.map +1 -1
- package/dist/lib/rig-check.d.ts +11 -0
- package/dist/lib/rig-control-qa.d.ts +8 -8
- package/dist/lib/rig-format.d.ts +184 -184
- package/dist/lib/rig-import-layers.d.ts +2 -0
- package/dist/lib/rig-symbol.d.ts +5 -2
- package/dist/lib/rig-tools.d.ts +2 -1
- package/dist/lib/rig-version.d.ts +12 -0
- package/dist/lib/runtime.js +60 -3
- package/dist/lib/runtime.js.map +3 -3
- package/dist/lib/tool-schema.d.ts +3 -0
- package/dist/lib/tools.d.ts +2 -0
- package/dist/lib/tools.js +179 -58
- package/dist/lib/tools.js.map +4 -4
- package/editor.html +2 -2
- package/package.json +38 -3
- package/skills/e8-golem/SKILL.md +71 -0
- package/skills/golem-symbol-animation/SKILL.md +87 -0
- package/skills/golem-symbol-animation/agents/openai.yaml +4 -0
- package/skills/golem-symbol-animation/references/facial-controls.md +48 -0
- package/skills/golem-symbol-animation/references/game-engine-integration.md +133 -0
- package/skills/golem-symbol-animation/references/golem-authoring.md +138 -0
- package/skills/golem-symbol-animation/references/interactive-editor.md +17 -0
- package/skills/golem-symbol-animation/references/motion-craft.md +116 -0
- package/skills/golem-symbol-animation/references/packed-delivery.md +88 -0
- package/skills/golem-symbol-animation/references/quantitative-qa.md +109 -0
- package/skills/golem-symbol-animation/references/spine-rive-study.md +87 -0
- package/skills/golem-symbol-animation/scripts/atlas_parts.py +87 -0
- package/skills/golem-symbol-animation/scripts/check_package.py +71 -0
- package/skills/golem-symbol-animation/scripts/image_gates.py +143 -0
- package/skills/golem-symbol-cutting/SKILL.md +70 -0
- package/skills/golem-symbol-cutting/agents/openai.yaml +4 -0
- package/skills/golem-symbol-cutting/assets/h1/h1.atlas.png +0 -0
- package/skills/golem-symbol-cutting/assets/h1/h1.png +0 -0
- package/skills/golem-symbol-cutting/references/cutting-workflow.md +92 -0
- package/skills/golem-symbol-cutting/references/facial-layers.md +44 -0
- package/skills/golem-symbol-cutting/references/generation-workflow.md +109 -0
- package/skills/golem-symbol-cutting/references/golem-handoff.md +62 -0
- package/skills/golem-symbol-cutting/references/h1-example.md +46 -0
- package/skills/golem-symbol-cutting/references/hybrid-workflow.md +119 -0
- package/skills/golem-symbol-cutting/references/registration-and-motion.md +89 -0
- package/skills/golem-symbol-cutting/references/spine-rive-construction.md +73 -0
- package/skills/golem-symbol-cutting/scripts/atlas_parts.py +87 -0
- package/skills/golem-symbol-cutting/scripts/hybrid_parts.py +279 -0
package/dist/lib/editor-entry.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/editor/server.ts
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
|
-
import { readFile as readFile10, stat as
|
|
3
|
+
import { readFile as readFile10, stat as stat4, readdir as readdir5, mkdir as mkdir7, open } from "node:fs/promises";
|
|
4
4
|
import { watch as fsWatch } from "node:fs";
|
|
5
|
-
import { resolve as resolve2, dirname as dirname9, basename as basename5, extname as extname2, sep, join as join10, relative as
|
|
5
|
+
import { resolve as resolve2, dirname as dirname9, basename as basename5, extname as extname2, sep as sep2, join as join10, relative as relative4 } from "node:path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7
7
|
|
|
8
8
|
// src/rig-format.ts
|
|
@@ -248,7 +248,7 @@ var Key = z.object({
|
|
|
248
248
|
/** Optional independent easing for numeric-array components (for example Spine RGBA curves). */
|
|
249
249
|
componentEase: z.array(Ease).min(1).optional()
|
|
250
250
|
});
|
|
251
|
-
var Interpolation = { interpolation: z.enum(["segment", "pchip"]).optional() };
|
|
251
|
+
var Interpolation = { interpolation: z.enum(["segment", "pchip", "spline"]).optional() };
|
|
252
252
|
var Track = z.discriminatedUnion("target", [
|
|
253
253
|
z.object({ target: z.literal("control"), id: Id, prop: z.literal("value"), ...Interpolation, keys: z.array(Key).min(1) }),
|
|
254
254
|
z.object({ target: z.literal("bone"), id: Id, prop: BoneProp, ...Interpolation, keys: z.array(Key).min(1) }),
|
|
@@ -568,7 +568,7 @@ function parse(input) {
|
|
|
568
568
|
// src/rig-api.ts
|
|
569
569
|
import { z as z3 } from "zod";
|
|
570
570
|
import { readFile as readFile9, writeFile as writeFile7, stat as stat2, mkdir as mkdir6, readdir as readdir4 } from "node:fs/promises";
|
|
571
|
-
import { dirname as dirname8, join as join9, relative as
|
|
571
|
+
import { dirname as dirname8, join as join9, relative as relative3, isAbsolute as isAbsolute2 } from "node:path";
|
|
572
572
|
|
|
573
573
|
// src/rig-io.ts
|
|
574
574
|
import { mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
@@ -591,8 +591,10 @@ var saveJson = (path, value) => saveBytes(path, JSON.stringify(value, null, 2));
|
|
|
591
591
|
// src/rig-queue.ts
|
|
592
592
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
593
593
|
import { resolve } from "node:path";
|
|
594
|
-
var pending
|
|
595
|
-
|
|
594
|
+
var { pending, active } = globalThis[Symbol.for("golem.rigQueue")] ??= {
|
|
595
|
+
pending: /* @__PURE__ */ new Map(),
|
|
596
|
+
active: new AsyncLocalStorage()
|
|
597
|
+
};
|
|
596
598
|
async function withRigQueue(path, op) {
|
|
597
599
|
const key = resolve(path);
|
|
598
600
|
const held = active.getStore();
|
|
@@ -732,6 +734,57 @@ function smoothValue(track, i, t, component, loop) {
|
|
|
732
734
|
const u = (t - k[i].t) / h(i), u2 = u * u, u3 = u2 * u;
|
|
733
735
|
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);
|
|
734
736
|
}
|
|
737
|
+
function tridiagonal(a, b, c, r) {
|
|
738
|
+
const n = b.length, cp = [], x = [];
|
|
739
|
+
let beta = b[0];
|
|
740
|
+
x[0] = r[0] / beta;
|
|
741
|
+
for (let i = 1; i < n; i++) {
|
|
742
|
+
cp[i] = c[i - 1] / beta;
|
|
743
|
+
beta = b[i] - a[i] * cp[i];
|
|
744
|
+
x[i] = (r[i] - a[i] * x[i - 1]) / beta;
|
|
745
|
+
}
|
|
746
|
+
for (let i = n - 2; i >= 0; i--) x[i] -= cp[i + 1] * x[i + 1];
|
|
747
|
+
return x;
|
|
748
|
+
}
|
|
749
|
+
function splineMoments(t, v, periodic) {
|
|
750
|
+
const n = t.length, h = (j2) => t[j2 + 1] - t[j2], d = (j2) => (v[j2 + 1] - v[j2]) / h(j2);
|
|
751
|
+
if (!periodic) {
|
|
752
|
+
const a2 = [], b2 = [], c2 = [], r2 = [];
|
|
753
|
+
for (let i = 0; i < n; i++) {
|
|
754
|
+
const hp = i > 0 ? h(i - 1) : 0, hn = i < n - 1 ? h(i) : 0;
|
|
755
|
+
a2.push(hp);
|
|
756
|
+
b2.push(2 * (hp + hn));
|
|
757
|
+
c2.push(hn);
|
|
758
|
+
r2.push(6 * ((i < n - 1 ? d(i) : 0) - (i > 0 ? d(i - 1) : 0)));
|
|
759
|
+
}
|
|
760
|
+
return tridiagonal(a2, b2, c2, r2);
|
|
761
|
+
}
|
|
762
|
+
const m = n - 1, a = [], b = [], c = [], r = [];
|
|
763
|
+
for (let i = 0; i < m; i++) {
|
|
764
|
+
const p = (i + m - 1) % m;
|
|
765
|
+
a.push(h(p));
|
|
766
|
+
b.push(2 * (h(p) + h(i)));
|
|
767
|
+
c.push(h(i));
|
|
768
|
+
r.push(6 * (d(i) - d(p)));
|
|
769
|
+
}
|
|
770
|
+
const top = a[0], bottom = c[m - 1], gamma = -b[0];
|
|
771
|
+
const bb = [...b];
|
|
772
|
+
bb[0] -= gamma;
|
|
773
|
+
bb[m - 1] -= bottom * top / gamma;
|
|
774
|
+
const x = tridiagonal(a, bb, c, r), u = new Array(m).fill(0);
|
|
775
|
+
u[0] = gamma;
|
|
776
|
+
u[m - 1] = bottom;
|
|
777
|
+
const z4 = tridiagonal(a, bb, c, u);
|
|
778
|
+
const f = (x[0] + top * x[m - 1] / gamma) / (1 + z4[0] + top * z4[m - 1] / gamma);
|
|
779
|
+
const M = x.map((xi, i) => xi - f * z4[i]);
|
|
780
|
+
return [...M, M[0]];
|
|
781
|
+
}
|
|
782
|
+
function splineValue(track, i, t, component, loop) {
|
|
783
|
+
const k = track.keys, ts = k.map((x) => x.t), vs = k.map((x) => component === void 0 ? x.v : x.v[component]);
|
|
784
|
+
const M = splineMoments(ts, vs, loop && k.length > 3 && vs[0] === vs[k.length - 1]);
|
|
785
|
+
const H = ts[i + 1] - ts[i], A = (ts[i + 1] - t) / H, B = (t - ts[i]) / H;
|
|
786
|
+
return A * vs[i] + B * vs[i + 1] + ((A ** 3 - A) * M[i] + (B ** 3 - B) * M[i + 1]) * H * H / 6;
|
|
787
|
+
}
|
|
735
788
|
function sample(track, t, loop = false) {
|
|
736
789
|
const keys = track.keys;
|
|
737
790
|
if (t <= keys[0].t) return keys[0].v;
|
|
@@ -743,11 +796,13 @@ function sample(track, t, loop = false) {
|
|
|
743
796
|
if (a.ease === "step") return a.v;
|
|
744
797
|
if (typeof a.v === "number" && typeof b.v === "number") {
|
|
745
798
|
if (track.interpolation === "pchip") return smoothValue(track, i, t, void 0, loop);
|
|
799
|
+
if (track.interpolation === "spline") return splineValue(track, i, t, void 0, loop);
|
|
746
800
|
const u = ease(a.ease)((t - a.t) / (b.t - a.t));
|
|
747
801
|
return a.v + (b.v - a.v) * u;
|
|
748
802
|
}
|
|
749
803
|
if (Array.isArray(a.v) && Array.isArray(b.v) && typeof a.v[0] === "number") {
|
|
750
804
|
if (track.interpolation === "pchip") return a.v.map((_, j2) => smoothValue(track, i, t, j2, loop));
|
|
805
|
+
if (track.interpolation === "spline") return a.v.map((_, j2) => splineValue(track, i, t, j2, loop));
|
|
751
806
|
const phase = (t - a.t) / (b.t - a.t), av = a.v, bv = b.v;
|
|
752
807
|
return av.map((v, k) => v + ((bv[k] ?? v) - v) * ease(a.componentEase?.[k] ?? a.ease)(phase));
|
|
753
808
|
}
|
|
@@ -2008,8 +2063,9 @@ function createDoc(name, width, height, notes) {
|
|
|
2008
2063
|
function createSymbol(name, asset, margin = 0.15, notes) {
|
|
2009
2064
|
const mx = Math.ceil(asset.width * margin), my = Math.ceil(asset.height * margin);
|
|
2010
2065
|
const width = asset.width + 2 * mx, height = asset.height + 2 * my;
|
|
2011
|
-
const
|
|
2012
|
-
const
|
|
2066
|
+
const stem = asset.src.replace(/^.*\//, "").replace(/\.[^.]+$/, "").toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
2067
|
+
const fromFile = stem.replace(/^[^a-z]+/, "") || (stem ? `symbol_${stem}` : "");
|
|
2068
|
+
const id = asset.id ?? (ID_RE.test(fromFile) ? fromFile : ID_RE.test(name) ? name : "symbol");
|
|
2013
2069
|
let doc = createDoc(name, width, height, notes);
|
|
2014
2070
|
doc = { ...doc, bones: [{ ...doc.bones[0], transform: { ...doc.bones[0].transform, x: width / 2, y: height - my } }] };
|
|
2015
2071
|
doc = { ...doc, assets: [RigSchema.shape.assets.element.parse({ id, src: asset.src, width: asset.width, height: asset.height })] };
|
|
@@ -2478,7 +2534,8 @@ function adjustTrack(doc, animId, track, adj) {
|
|
|
2478
2534
|
return { ...k, t: r42(adj.timeScale ? k.t * adj.timeScale : k.t), v: r42(v) };
|
|
2479
2535
|
});
|
|
2480
2536
|
const duration = Math.max(anim.duration, keys[keys.length - 1].t);
|
|
2481
|
-
const
|
|
2537
|
+
const interpolation = track.interpolation ?? tr.interpolation;
|
|
2538
|
+
const next = { ...anim, duration, tracks: anim.tracks.map((t) => same(t) ? { ...t, keys, ...interpolation && { interpolation } } : t), preset: void 0 };
|
|
2482
2539
|
return { ...doc, animations: doc.animations.map((a) => a.id === animId ? next : a) };
|
|
2483
2540
|
}
|
|
2484
2541
|
function removeAnimation(doc, id) {
|
|
@@ -2765,7 +2822,7 @@ function setGoal(doc, bone, x, y, opts) {
|
|
|
2765
2822
|
|
|
2766
2823
|
// src/rig-import-layers.ts
|
|
2767
2824
|
import { readdir, readFile } from "node:fs/promises";
|
|
2768
|
-
import { join as join2, basename as basename2 } from "node:path";
|
|
2825
|
+
import { join as join2, basename as basename2, isAbsolute, relative, sep } from "node:path";
|
|
2769
2826
|
var DEFAULT_PARENT = {
|
|
2770
2827
|
head: "neck",
|
|
2771
2828
|
neck: "torso",
|
|
@@ -2791,6 +2848,10 @@ function pngSize(buf) {
|
|
|
2791
2848
|
if (buf.readUInt32BE(12) !== 1229472850) throw new Error("not a PNG (IHDR missing)");
|
|
2792
2849
|
return [buf.readUInt32BE(16), buf.readUInt32BE(20)];
|
|
2793
2850
|
}
|
|
2851
|
+
function outsideDir(dir, file) {
|
|
2852
|
+
const r = relative(dir, join2(dir, file));
|
|
2853
|
+
return isAbsolute(file) || r === ".." || r.startsWith(".." + sep);
|
|
2854
|
+
}
|
|
2794
2855
|
async function importLayers(doc, dir, relPrefix = "layers") {
|
|
2795
2856
|
let specs;
|
|
2796
2857
|
try {
|
|
@@ -2803,6 +2864,7 @@ async function importLayers(doc, dir, relPrefix = "layers") {
|
|
|
2803
2864
|
let next = doc;
|
|
2804
2865
|
const parsed = [];
|
|
2805
2866
|
for (const s of specs) {
|
|
2867
|
+
if (outsideDir(dir, s.file)) throw new Error(`layers.json file "${s.file}" is outside the layers folder (${dir})`);
|
|
2806
2868
|
const buf = await readFile(join2(dir, s.file));
|
|
2807
2869
|
const [w, h] = pngSize(buf);
|
|
2808
2870
|
const m = basename2(s.file, ".png").match(/^(?:(\d+)_)?(.+)$/);
|
|
@@ -2890,19 +2952,20 @@ var requireSlot = (doc, id) => {
|
|
|
2890
2952
|
if (!s) throw new Error(`unknown slot "${id}"`);
|
|
2891
2953
|
return s;
|
|
2892
2954
|
};
|
|
2893
|
-
function sampled(duration, n, f
|
|
2955
|
+
function sampled(duration, n, f) {
|
|
2894
2956
|
const keys = [];
|
|
2895
2957
|
for (let i = 0; i <= n; i++) {
|
|
2896
2958
|
const t = +(duration * i / n).toFixed(4);
|
|
2897
|
-
keys.push({ t, v: +f(t).toFixed(4) + 0, ease:
|
|
2959
|
+
keys.push({ t, v: +f(t).toFixed(4) + 0, ease: "linear" });
|
|
2898
2960
|
}
|
|
2899
2961
|
return keys;
|
|
2900
2962
|
}
|
|
2901
|
-
|
|
2902
|
-
|
|
2963
|
+
var smooth = "spline";
|
|
2964
|
+
function boneTrack(bone, prop, keys, interpolation) {
|
|
2965
|
+
return { target: "bone", id: bone.id, prop, keys, ...interpolation && { interpolation } };
|
|
2903
2966
|
}
|
|
2904
|
-
function deformTrack(slot, prop, keys) {
|
|
2905
|
-
return slot.attachment ? { target: "deform", id: slot.attachment, prop, keys } : void 0;
|
|
2967
|
+
function deformTrack(slot, prop, keys, interpolation) {
|
|
2968
|
+
return slot.attachment ? { target: "deform", id: slot.attachment, prop, keys, ...interpolation && { interpolation } } : void 0;
|
|
2906
2969
|
}
|
|
2907
2970
|
var pushTrack = (tracks, t) => {
|
|
2908
2971
|
if (t) tracks.push(t);
|
|
@@ -2926,18 +2989,18 @@ var idleBreathing = {
|
|
|
2926
2989
|
const armsR = byRole(doc, "arm_r", "arm_upper_r");
|
|
2927
2990
|
const chest = slotOfBone(doc, torso);
|
|
2928
2991
|
if (chest?.attachment) {
|
|
2929
|
-
pushTrack(tracks, deformTrack(chest, "bulge", sampled(T2, 8, (t) => p.amplitude * Math.sin(w * t),
|
|
2992
|
+
pushTrack(tracks, deformTrack(chest, "bulge", sampled(T2, 8, (t) => p.amplitude * Math.sin(w * t)), smooth));
|
|
2930
2993
|
} else {
|
|
2931
2994
|
const r = torso.transform;
|
|
2932
|
-
tracks.push(boneTrack(torso, "scaleY", sampled(T2, 8, (t) => r.scaleY + p.amplitude * Math.sin(w * t),
|
|
2933
|
-
tracks.push(boneTrack(torso, "scaleX", sampled(T2, 8, (t) => r.scaleX - p.amplitude * 0.4 * Math.sin(w * t),
|
|
2995
|
+
tracks.push(boneTrack(torso, "scaleY", sampled(T2, 8, (t) => r.scaleY + p.amplitude * Math.sin(w * t)), smooth));
|
|
2996
|
+
tracks.push(boneTrack(torso, "scaleX", sampled(T2, 8, (t) => r.scaleX - p.amplitude * 0.4 * Math.sin(w * t)), smooth));
|
|
2934
2997
|
}
|
|
2935
2998
|
for (const h of heads)
|
|
2936
|
-
tracks.push(boneTrack(h, "y", sampled(T2, 8, (t) => h.transform.y - p.headBob * Math.sin(w * t + 0.4),
|
|
2999
|
+
tracks.push(boneTrack(h, "y", sampled(T2, 8, (t) => h.transform.y - p.headBob * Math.sin(w * t + 0.4)), smooth));
|
|
2937
3000
|
for (const a of armsL)
|
|
2938
|
-
tracks.push(boneTrack(a, "rotation", sampled(T2, 8, (t) => a.transform.rotation + p.armSway * Math.sin(w * t + 0.8),
|
|
3001
|
+
tracks.push(boneTrack(a, "rotation", sampled(T2, 8, (t) => a.transform.rotation + p.armSway * Math.sin(w * t + 0.8)), smooth));
|
|
2939
3002
|
for (const a of armsR)
|
|
2940
|
-
tracks.push(boneTrack(a, "rotation", sampled(T2, 8, (t) => a.transform.rotation - p.armSway * Math.sin(w * t + 0.8),
|
|
3003
|
+
tracks.push(boneTrack(a, "rotation", sampled(T2, 8, (t) => a.transform.rotation - p.armSway * Math.sin(w * t + 0.8)), smooth));
|
|
2941
3004
|
return finish(this.name, p.id, T2, true, tracks, p);
|
|
2942
3005
|
}
|
|
2943
3006
|
};
|
|
@@ -2946,22 +3009,20 @@ var winBounce = {
|
|
|
2946
3009
|
defaults: { id: "win", duration: 1.2, height: 40, bounces: 2, squash: 0.15 },
|
|
2947
3010
|
build(doc, p) {
|
|
2948
3011
|
const b = root(doc), r = b.transform, tracks = [];
|
|
2949
|
-
const yK = [[0, r.y,
|
|
2950
|
-
const sxK = [[0, r.scaleX, "ease_out"]];
|
|
2951
|
-
const syK = [[0, r.scaleY, "ease_out"]];
|
|
3012
|
+
const yK = [[0, r.y]], sxK = [[0, r.scaleX]], syK = [[0, r.scaleY]];
|
|
2952
3013
|
const per = p.duration / p.bounces;
|
|
2953
3014
|
for (let i = 0; i < p.bounces; i++) {
|
|
2954
3015
|
const t0 = i * per, h = p.height * 0.55 ** i, s = p.squash * 0.7 ** i;
|
|
2955
|
-
sxK.push([t0 + per * 0.08, r.scaleX * (1 + s)
|
|
2956
|
-
syK.push([t0 + per * 0.08, r.scaleY * (1 - s)
|
|
2957
|
-
yK.push([t0 + per * 0.08, r.y
|
|
3016
|
+
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)]);
|
|
3017
|
+
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)]);
|
|
3018
|
+
yK.push([t0 + per * 0.08, r.y], [t0 + per * 0.5, r.y - h], [t0 + per * 0.95, r.y]);
|
|
2958
3019
|
}
|
|
2959
3020
|
yK.push([p.duration, r.y]);
|
|
2960
3021
|
sxK.push([p.duration, r.scaleX]);
|
|
2961
3022
|
syK.push([p.duration, r.scaleY]);
|
|
2962
|
-
tracks.push(boneTrack(b, "y", K(...yK)), boneTrack(b, "scaleX", K(...sxK)), boneTrack(b, "scaleY", K(...syK)));
|
|
3023
|
+
tracks.push(boneTrack(b, "y", K(...yK), "pchip"), boneTrack(b, "scaleX", K(...sxK), smooth), boneTrack(b, "scaleY", K(...syK), smooth));
|
|
2963
3024
|
for (const h of byRole(doc, "head"))
|
|
2964
|
-
tracks.push(boneTrack(h, "rotation", K([0, h.transform.rotation], [p.duration * 0.25, h.transform.rotation - 6
|
|
3025
|
+
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));
|
|
2965
3026
|
const anim = finish(this.name, p.id, p.duration, false, tracks, p);
|
|
2966
3027
|
anim.events.push({ t: 0, name: "win_start" }, { t: per * 0.95, name: "land" });
|
|
2967
3028
|
return anim;
|
|
@@ -2974,8 +3035,8 @@ var anticipationShake = {
|
|
|
2974
3035
|
const b = root(doc), r = b.transform, tracks = [];
|
|
2975
3036
|
const n = Math.ceil(p.duration * p.frequency * 4);
|
|
2976
3037
|
const env = (t) => p.grow ? t / p.duration : 1;
|
|
2977
|
-
tracks.push(boneTrack(b, "x", sampled(p.duration, n, (t) => r.x + p.amplitude * env(t) * Math.sin(2 * Math.PI * p.frequency * t),
|
|
2978
|
-
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),
|
|
3038
|
+
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));
|
|
3039
|
+
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));
|
|
2979
3040
|
tracks.push(boneTrack(b, "scaleX", K([0, r.scaleX, "ease_in"], [p.duration, r.scaleX * p.scaleUp])));
|
|
2980
3041
|
tracks.push(boneTrack(b, "scaleY", K([0, r.scaleY, "ease_in"], [p.duration, r.scaleY * p.scaleUp])));
|
|
2981
3042
|
return finish(this.name, p.id, p.duration, false, tracks, p);
|
|
@@ -3000,6 +3061,7 @@ var mouthTalk = {
|
|
|
3000
3061
|
if (!p.frames.length) throw new Error("mouth_talk: frames must not be empty");
|
|
3001
3062
|
const n = Math.max(1, Math.round(p.duration * p.rate));
|
|
3002
3063
|
const keys = S(...Array.from({ length: n }, (_, i) => [+(i / p.rate).toFixed(4), p.frames[i % p.frames.length]]));
|
|
3064
|
+
if (keys.at(-1).t < p.duration && keys.at(-1).v !== keys[0].v) keys.push(...S([p.duration, keys[0].v]));
|
|
3003
3065
|
return finish(this.name, p.id, p.duration, true, [slotTrack(slot, "attachment", keys)], p);
|
|
3004
3066
|
}
|
|
3005
3067
|
};
|
|
@@ -3017,10 +3079,10 @@ var symbolIdle = {
|
|
|
3017
3079
|
build(doc, p) {
|
|
3018
3080
|
const b = root(doc), r = b.transform, T2 = p.period, w = 2 * Math.PI / T2, tracks = [];
|
|
3019
3081
|
const rise = (t) => 0.5 - 0.5 * Math.cos(w * t);
|
|
3020
|
-
tracks.push(boneTrack(b, "y", sampled(T2, 8, (t) => r.y - p.float * rise(t),
|
|
3021
|
-
if (p.tilt) tracks.push(boneTrack(b, "rotation", sampled(T2, 8, (t) => r.rotation + p.tilt * Math.sin(w * t),
|
|
3082
|
+
tracks.push(boneTrack(b, "y", sampled(T2, 8, (t) => r.y - p.float * rise(t)), smooth));
|
|
3083
|
+
if (p.tilt) tracks.push(boneTrack(b, "rotation", sampled(T2, 8, (t) => r.rotation + p.tilt * Math.sin(w * t)), smooth));
|
|
3022
3084
|
if (p.pulse) for (const s of doc.slots.filter((s2) => s2.bone === b.id && s2.attachment))
|
|
3023
|
-
pushTrack(tracks, deformTrack(s, "bulge", sampled(T2, 8, (t) => p.pulse * rise(t),
|
|
3085
|
+
pushTrack(tracks, deformTrack(s, "bulge", sampled(T2, 8, (t) => p.pulse * rise(t)), smooth));
|
|
3024
3086
|
return finish(this.name, p.id, T2, true, tracks, p);
|
|
3025
3087
|
}
|
|
3026
3088
|
};
|
|
@@ -3030,12 +3092,12 @@ var symbolWin = {
|
|
|
3030
3092
|
build(doc, p) {
|
|
3031
3093
|
const b = root(doc), r = b.transform, d = p.duration, tracks = [];
|
|
3032
3094
|
for (const [prop, rest] of [["scaleX", r.scaleX], ["scaleY", r.scaleY]])
|
|
3033
|
-
tracks.push(boneTrack(b, prop, K([0, rest
|
|
3095
|
+
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));
|
|
3034
3096
|
if (p.wobble) {
|
|
3035
3097
|
const n = Math.max(8, p.wobbles * 4);
|
|
3036
|
-
const keys = sampled(d, n, (t) => p.wobble * Math.exp(-3 * t / d) * Math.sin(2 * Math.PI * p.wobbles * t / d)
|
|
3098
|
+
const keys = sampled(d, n, (t) => p.wobble * Math.exp(-3 * t / d) * Math.sin(2 * Math.PI * p.wobbles * t / d));
|
|
3037
3099
|
keys[keys.length - 1].v = 0;
|
|
3038
|
-
for (const s of doc.slots.filter((s2) => s2.bone === b.id && s2.attachment)) pushTrack(tracks, deformTrack(s, "sway", keys));
|
|
3100
|
+
for (const s of doc.slots.filter((s2) => s2.bone === b.id && s2.attachment)) pushTrack(tracks, deformTrack(s, "sway", keys, smooth));
|
|
3039
3101
|
}
|
|
3040
3102
|
const anim = finish(this.name, p.id, d, false, tracks, p);
|
|
3041
3103
|
anim.events.push({ t: 0, name: "win_start" });
|
|
@@ -3049,11 +3111,11 @@ var weightShift = {
|
|
|
3049
3111
|
const T2 = p.period, w = 2 * Math.PI / T2, N = 16, tracks = [];
|
|
3050
3112
|
const hips = byRole(doc, "hips")[0] ?? root(doc), hr = hips.transform;
|
|
3051
3113
|
const s = (t) => Math.sin(w * t);
|
|
3052
|
-
tracks.push(boneTrack(hips, "x", sampled(T2, N, (t) => hr.x + p.shift * s(t))));
|
|
3053
|
-
if (p.dip) tracks.push(boneTrack(hips, "y", sampled(T2, N, (t) => hr.y + p.dip * s(t) ** 2)));
|
|
3114
|
+
tracks.push(boneTrack(hips, "x", sampled(T2, N, (t) => hr.x + p.shift * s(t)), smooth));
|
|
3115
|
+
if (p.dip) tracks.push(boneTrack(hips, "y", sampled(T2, N, (t) => hr.y + p.dip * s(t) ** 2), smooth));
|
|
3054
3116
|
const torso = byRole(doc, "torso", "body")[0];
|
|
3055
|
-
if (torso && torso !== hips) tracks.push(boneTrack(torso, "rotation", sampled(T2, N, (t) => torso.transform.rotation - p.lean * s(t))));
|
|
3056
|
-
for (const h of byRole(doc, "head")) tracks.push(boneTrack(h, "rotation", sampled(T2, N, (t) => h.transform.rotation + p.lean / 2 * s(t))));
|
|
3117
|
+
if (torso && torso !== hips) tracks.push(boneTrack(torso, "rotation", sampled(T2, N, (t) => torso.transform.rotation - p.lean * s(t)), smooth));
|
|
3118
|
+
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));
|
|
3057
3119
|
for (const leg of byRole(doc, "leg_l", "leg_r")) {
|
|
3058
3120
|
const slot = slotOfBone(doc, leg), att = slot && doc.attachments.find((a) => a.id === slot.attachment && a.type === "region");
|
|
3059
3121
|
const asset = att && att.type === "region" ? doc.assets.find((a) => a.id === att.asset) : void 0;
|
|
@@ -3063,8 +3125,8 @@ var weightShift = {
|
|
|
3063
3125
|
length = Math.max(...[[0, -h / 2], [w2 / 2, 0], [0, h / 2], [-w2 / 2, 0]].map(([x, y]) => Math.hypot(...apply(m, x, y))));
|
|
3064
3126
|
}
|
|
3065
3127
|
if (!(length > p.shift)) continue;
|
|
3066
|
-
tracks.push(boneTrack(leg, "rotation", sampled(T2, N, (t) => leg.transform.rotation + Math.asin(p.shift * s(t) / length) * 180 / Math.PI)));
|
|
3067
|
-
if (p.dip) tracks.push(boneTrack(leg, "y", sampled(T2, N, (t) => leg.transform.y - p.dip * s(t) ** 2)));
|
|
3128
|
+
tracks.push(boneTrack(leg, "rotation", sampled(T2, N, (t) => leg.transform.rotation + Math.asin(p.shift * s(t) / length) * 180 / Math.PI), smooth));
|
|
3129
|
+
if (p.dip) tracks.push(boneTrack(leg, "y", sampled(T2, N, (t) => leg.transform.y - p.dip * s(t) ** 2), smooth));
|
|
3068
3130
|
}
|
|
3069
3131
|
return finish(this.name, p.id, T2, true, tracks, p);
|
|
3070
3132
|
}
|
|
@@ -3300,13 +3362,13 @@ function motionQuality(doc, anim, opts = {}) {
|
|
|
3300
3362
|
const before = delta(values(k.v), values(p.v)), after = delta(values(n.v), values(k.v));
|
|
3301
3363
|
const pass = before.some((v, j2) => v * after[j2] > 1e-6);
|
|
3302
3364
|
const speed = Math.max(magnitude(before) / (k.t - p.t), magnitude(after) / (n.t - k.t));
|
|
3303
|
-
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);
|
|
3304
3366
|
const jump = magnitude(delta(left, right));
|
|
3305
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);
|
|
3306
3368
|
const acceleration = (a, b, c) => values(at(b)).map((v, j2) => (values(at(c))[j2] - 2 * v + values(at(a))[j2]) / (h * h));
|
|
3307
3369
|
const al = acceleration(k.t - 2 * h, k.t - h, k.t), ar = acceleration(k.t, k.t + h, k.t + 2 * h);
|
|
3308
3370
|
const aj = magnitude(delta(al, ar));
|
|
3309
|
-
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");
|
|
3310
3372
|
}
|
|
3311
3373
|
}
|
|
3312
3374
|
const steps = Math.max(2, Math.ceil(D * 60));
|
|
@@ -3788,6 +3850,17 @@ function checkRig(doc, opts = {}) {
|
|
|
3788
3850
|
out.push({ level: "warn", code: "never_visible", subject: s.id, message: `slot "${s.id}" has no attachment and no animation ever shows one` });
|
|
3789
3851
|
return out;
|
|
3790
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` : ""}`;
|
|
3791
3864
|
function summarize(findings, max = 12) {
|
|
3792
3865
|
if (!findings.length) return "check: OK";
|
|
3793
3866
|
const rank = { error: 0, warn: 1, info: 2 };
|
|
@@ -3802,7 +3875,7 @@ function summarize(findings, max = 12) {
|
|
|
3802
3875
|
const lines = [];
|
|
3803
3876
|
for (const f of sorted) {
|
|
3804
3877
|
const shown = shownPerCode.get(f.code) ?? 0;
|
|
3805
|
-
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}`);
|
|
3806
3879
|
else if (shown === PER_CODE) lines.push(` \u2026 ${codeTotal.get(f.code) - PER_CODE} more ${f.code}`);
|
|
3807
3880
|
shownPerCode.set(f.code, shown + 1);
|
|
3808
3881
|
}
|
|
@@ -3993,7 +4066,7 @@ function packRects(rects, opts = {}) {
|
|
|
3993
4066
|
shelf = Math.max(shelf, r.height);
|
|
3994
4067
|
}
|
|
3995
4068
|
const height = y + shelf + pad;
|
|
3996
|
-
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 };
|
|
3997
4070
|
if (width >= max) throw new Error(`atlas does not fit in ${max}\xD7${max} (needs ${width}\xD7${height})`);
|
|
3998
4071
|
width *= 2;
|
|
3999
4072
|
}
|
|
@@ -4052,7 +4125,7 @@ async function buildAtlas(doc, assetsDir, outDir, opts = {}) {
|
|
|
4052
4125
|
|
|
4053
4126
|
// src/spine-import.ts
|
|
4054
4127
|
import { readFile as readFile7, stat, writeFile as writeFile5 } from "node:fs/promises";
|
|
4055
|
-
import { basename as basename4, dirname as dirname6, extname, join as join8, relative } from "node:path";
|
|
4128
|
+
import { basename as basename4, dirname as dirname6, extname, join as join8, relative as relative2 } from "node:path";
|
|
4056
4129
|
|
|
4057
4130
|
// src/ktx2.ts
|
|
4058
4131
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
@@ -4201,13 +4274,13 @@ var Ids = class {
|
|
|
4201
4274
|
var spineToDoc = (x, y, originX, originY) => [x + originX, originY - y];
|
|
4202
4275
|
var r4 = (v) => Math.round(v * 1e6) / 1e6 + 0;
|
|
4203
4276
|
var hex8 = (c) => `#${(c ?? "ffffffff").toLowerCase().padEnd(8, "f").slice(0, 8)}`;
|
|
4204
|
-
function importSpine(
|
|
4277
|
+
function importSpine(json2, atlasText, opts = {}) {
|
|
4205
4278
|
const warnings = [];
|
|
4206
4279
|
const atlas = parseAtlas(atlasText);
|
|
4207
|
-
const version = String(
|
|
4208
|
-
if (
|
|
4280
|
+
const version = String(json2.skeleton?.spine ?? "4.0");
|
|
4281
|
+
if (json2.physics?.length) warnings.push(`${json2.physics.length} Spine physics constraints are not imported; bake their motion before delivery`);
|
|
4209
4282
|
const major = parseFloat(version);
|
|
4210
|
-
const bounds = { x:
|
|
4283
|
+
const bounds = { x: json2.skeleton?.x ?? 0, y: json2.skeleton?.y ?? 0, w: json2.skeleton?.width ?? 0, h: json2.skeleton?.height ?? 0 };
|
|
4211
4284
|
const margin = opts.margin ?? 0;
|
|
4212
4285
|
let originX = -bounds.x + margin, originY = bounds.y + bounds.h + margin;
|
|
4213
4286
|
const toDoc = (x, y) => spineToDoc(x, y, originX, originY);
|
|
@@ -4224,7 +4297,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4224
4297
|
}
|
|
4225
4298
|
return id;
|
|
4226
4299
|
};
|
|
4227
|
-
const spineBones =
|
|
4300
|
+
const spineBones = json2.bones ?? [];
|
|
4228
4301
|
const boneIndexByName = new Map(spineBones.map((b, i) => [b.name, i]));
|
|
4229
4302
|
const bones = [{ id: ROOT, transform: { x: r4(originX), y: r4(originY) }, role: "root" }];
|
|
4230
4303
|
for (const b of spineBones) {
|
|
@@ -4237,7 +4310,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4237
4310
|
...b.color ? { color: b.color } : {}
|
|
4238
4311
|
});
|
|
4239
4312
|
}
|
|
4240
|
-
const skins = Array.isArray(
|
|
4313
|
+
const skins = Array.isArray(json2.skins) ? json2.skins : Object.entries(json2.skins ?? {}).map(([name, attachments2]) => ({ name, attachments: attachments2 }));
|
|
4241
4314
|
const selectedSkin = opts.skin ? skins.find((s) => s.name === opts.skin) : skins.find((s) => s.name === "default") ?? skins[0];
|
|
4242
4315
|
if (opts.skin && !selectedSkin) throw new Error(`Spine skin "${opts.skin}" not found; available: ${skins.map((s) => s.name).join(", ")}`);
|
|
4243
4316
|
const lookupSkins = [selectedSkin, skins.find((s) => s.name === "default")].filter((s, i, list) => s && list.indexOf(s) === i);
|
|
@@ -4261,7 +4334,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4261
4334
|
const resolved = skinnedName.get(`${slot} ${name}`);
|
|
4262
4335
|
return resolved === void 0 ? null : attachmentId(slot, resolved);
|
|
4263
4336
|
};
|
|
4264
|
-
const spineSlots =
|
|
4337
|
+
const spineSlots = json2.slots ?? [];
|
|
4265
4338
|
const slotIndexByName = new Map(spineSlots.map((s, i) => [s.name, i]));
|
|
4266
4339
|
const blendOf = (b) => b === "additive" ? "add" : b === "multiply" ? "multiply" : b === "screen" ? "screen" : "normal";
|
|
4267
4340
|
const slots = spineSlots.map((s, i) => ({
|
|
@@ -4427,7 +4500,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4427
4500
|
}
|
|
4428
4501
|
}
|
|
4429
4502
|
const activeConstraint = (kind, c) => !c.skin || lookupSkins.some((s) => s[kind]?.includes(c.name));
|
|
4430
|
-
const ik = (
|
|
4503
|
+
const ik = (json2.ik ?? []).filter((c) => activeConstraint("ik", c)).map((c) => ({
|
|
4431
4504
|
id: ikIds.get(c.name),
|
|
4432
4505
|
order: c.order ?? 0,
|
|
4433
4506
|
bones: c.bones.map((b) => boneIds.get(b)),
|
|
@@ -4440,7 +4513,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4440
4513
|
stretch: !!c.stretch,
|
|
4441
4514
|
uniform: !!c.uniform
|
|
4442
4515
|
}));
|
|
4443
|
-
const transform = (
|
|
4516
|
+
const transform = (json2.transform ?? []).filter((c) => activeConstraint("transform", c)).map((c) => ({
|
|
4444
4517
|
id: tcIds.get(c.name),
|
|
4445
4518
|
order: c.order ?? 0,
|
|
4446
4519
|
bones: c.bones.map((b) => boneIds.get(b)),
|
|
@@ -4460,7 +4533,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4460
4533
|
mixScaleY: c.mixScaleY ?? c.mixScaleX ?? c.scaleMix ?? 1,
|
|
4461
4534
|
mixShearY: c.mixShearY ?? c.shearMix ?? 1
|
|
4462
4535
|
}));
|
|
4463
|
-
const path = (
|
|
4536
|
+
const path = (json2.path ?? []).filter((c) => activeConstraint("path", c)).map((c) => ({
|
|
4464
4537
|
id: pcIds.get(c.name),
|
|
4465
4538
|
order: c.order ?? 0,
|
|
4466
4539
|
bones: c.bones.map((b) => boneIds.get(b)),
|
|
@@ -4514,7 +4587,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4514
4587
|
return keys;
|
|
4515
4588
|
};
|
|
4516
4589
|
const animations = [];
|
|
4517
|
-
for (const [animName, a] of Object.entries(
|
|
4590
|
+
for (const [animName, a] of Object.entries(json2.animations ?? {})) {
|
|
4518
4591
|
const tracks = [], events = [];
|
|
4519
4592
|
let duration = 0;
|
|
4520
4593
|
const seen = (frames) => {
|
|
@@ -4795,7 +4868,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4795
4868
|
const build = () => ({
|
|
4796
4869
|
format: "energy8.rig",
|
|
4797
4870
|
version: 2,
|
|
4798
|
-
meta: { name: opts.name ??
|
|
4871
|
+
meta: { name: opts.name ?? json2.skeleton?.hash ?? "spine", width, height, notes: opts.notes ?? `imported from Spine ${version}` },
|
|
4799
4872
|
assets: [...assets.values()],
|
|
4800
4873
|
bones,
|
|
4801
4874
|
slots,
|
|
@@ -4848,7 +4921,7 @@ async function firstExisting(candidates, what) {
|
|
|
4848
4921
|
throw new Error(`${what} not found, tried: ${candidates.join(", ")}`);
|
|
4849
4922
|
}
|
|
4850
4923
|
async function importSpineProject(opts) {
|
|
4851
|
-
const
|
|
4924
|
+
const json2 = JSON.parse(await readFile7(opts.json, "utf8"));
|
|
4852
4925
|
const stem = opts.json.replace(/\.json$/i, "");
|
|
4853
4926
|
const atlasPath = opts.atlas ?? await firstExisting([stem + ".atlas", stem + ".atlas.txt"], "atlas");
|
|
4854
4927
|
const atlasText = await readFile7(atlasPath, "utf8");
|
|
@@ -4858,6 +4931,7 @@ async function importSpineProject(opts) {
|
|
|
4858
4931
|
const rigDir = dirname6(opts.rigPath);
|
|
4859
4932
|
const pagePaths = /* @__PURE__ */ new Map();
|
|
4860
4933
|
for (const page2 of pages) {
|
|
4934
|
+
if (!opts.image && outsideDir(dirname6(atlasPath), page2.name)) throw new Error(`atlas page "${page2.name}" is outside the atlas folder (${dirname6(atlasPath)})`);
|
|
4861
4935
|
const pageStem = join8(dirname6(atlasPath), page2.name.replace(/\.[a-z0-9]+$/i, ""));
|
|
4862
4936
|
const imagePath = opts.image ?? await firstExisting([join8(dirname6(atlasPath), page2.name), pageStem + ".png", pageStem + ".ktx2", ...pages.length === 1 ? [stem + ".png", stem + ".ktx2"] : []], `atlas page image ${page2.name}`);
|
|
4863
4937
|
let pngPath = imagePath;
|
|
@@ -4870,10 +4944,10 @@ async function importSpineProject(opts) {
|
|
|
4870
4944
|
}
|
|
4871
4945
|
pagePaths.set(page2.name, pngPath);
|
|
4872
4946
|
}
|
|
4873
|
-
const pageImage = (page2) =>
|
|
4947
|
+
const pageImage = (page2) => relative2(rigDir, pagePaths.get(page2)).split("\\").join("/");
|
|
4874
4948
|
const name = opts.name ?? basename4(stem);
|
|
4875
4949
|
const imagePaths = [...pagePaths.values()];
|
|
4876
|
-
return { ...importSpine(
|
|
4950
|
+
return { ...importSpine(json2, atlasText, { name, skin: opts.skin, pageImage, margin: opts.margin, notes: opts.notes }), imagePath: imagePaths[0], imagePaths };
|
|
4877
4951
|
}
|
|
4878
4952
|
|
|
4879
4953
|
// src/rig-templates.ts
|
|
@@ -5562,8 +5636,8 @@ async function load(path) {
|
|
|
5562
5636
|
}
|
|
5563
5637
|
var save = (path, doc) => saveJson(path, doc);
|
|
5564
5638
|
var exists2 = (p) => stat2(p).then(() => true, () => false);
|
|
5565
|
-
var resolveFrom = (rigPath, p) =>
|
|
5566
|
-
var srcFor = (rigPath, file) =>
|
|
5639
|
+
var resolveFrom = (rigPath, p) => isAbsolute2(p) ? p : join9(dirname8(rigPath), p);
|
|
5640
|
+
var srcFor = (rigPath, file) => relative3(dirname8(rigPath), file).split("\\").join("/");
|
|
5567
5641
|
var libraryFor = (rigPath, lib) => lib ? resolveFrom(rigPath, lib) : join9(dirname8(rigPath), "presets.json");
|
|
5568
5642
|
async function pngInfo(rigPath, file) {
|
|
5569
5643
|
const abs = resolveFrom(rigPath, file);
|
|
@@ -5598,7 +5672,8 @@ function previewCheck(doc, animation) {
|
|
|
5598
5672
|
const a = animation ? doc.animations.find((x) => x.id === animation) : void 0;
|
|
5599
5673
|
return a ? `check: OK for "${a.id}" (${a.tracks.length} tracks, ${a.duration}s${a.loop ? " loop" : ""})` : "check: OK (rest pose)";
|
|
5600
5674
|
}
|
|
5601
|
-
var
|
|
5675
|
+
var TrackId = { target: z3.enum(["bone", "slot", "deform", "ik", "transform", "path", "drawOrder", "control"]), id: z3.string(), prop: z3.string() };
|
|
5676
|
+
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 };
|
|
5602
5677
|
tool(
|
|
5603
5678
|
"rig_set_control",
|
|
5604
5679
|
"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.",
|
|
@@ -5791,11 +5866,21 @@ tool(
|
|
|
5791
5866
|
);
|
|
5792
5867
|
tool(
|
|
5793
5868
|
"rig_check",
|
|
5794
|
-
"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.",
|
|
5795
|
-
{
|
|
5796
|
-
|
|
5869
|
+
"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.",
|
|
5870
|
+
{
|
|
5871
|
+
path: Path,
|
|
5872
|
+
animation: z3.string().optional().describe("limit to one animation (rest pose is always checked)"),
|
|
5873
|
+
fps: z3.number().positive().optional(),
|
|
5874
|
+
deformFps: z3.number().positive().optional(),
|
|
5875
|
+
secondaryBones: z3.array(z3.string()).optional(),
|
|
5876
|
+
frontOf: z3.array(z3.tuple([z3.string(), z3.string()])).optional(),
|
|
5877
|
+
landmarks: z3.record(z3.record(z3.tuple([z3.number(), z3.number()]))).optional(),
|
|
5878
|
+
landmarkTolerance: z3.number().nonnegative().optional(),
|
|
5879
|
+
ignore: z3.array(z3.object({ code: z3.string(), subject: z3.string().optional(), animation: z3.string().optional() }).strict()).optional()
|
|
5880
|
+
},
|
|
5881
|
+
async ({ path, ignore = [], ...opts }) => {
|
|
5797
5882
|
try {
|
|
5798
|
-
return text(
|
|
5883
|
+
return text(summarizeIgnoring(checkRig(await load(path), opts), ignore, 100));
|
|
5799
5884
|
} catch (e) {
|
|
5800
5885
|
return fail(e);
|
|
5801
5886
|
}
|
|
@@ -5814,9 +5899,9 @@ tool(
|
|
|
5814
5899
|
margin: z3.number().optional().describe("canvas margin around the skeleton bounds, px"),
|
|
5815
5900
|
notes: z3.string().optional()
|
|
5816
5901
|
},
|
|
5817
|
-
async ({ path, json, atlas, image, name, skin: skin2, margin, notes }) => {
|
|
5902
|
+
async ({ path, json: json2, atlas, image, name, skin: skin2, margin, notes }) => {
|
|
5818
5903
|
try {
|
|
5819
|
-
const r = await importSpineProject({ json: resolveFrom(path,
|
|
5904
|
+
const r = await importSpineProject({ json: resolveFrom(path, json2), atlas: atlas && resolveFrom(path, atlas), image: image && resolveFrom(path, image), rigPath: path, name, skin: skin2, margin, notes });
|
|
5820
5905
|
const res = await create(path, parse(r.doc));
|
|
5821
5906
|
if (r.warnings.length) res.text += `
|
|
5822
5907
|
warnings:
|
|
@@ -6412,9 +6497,9 @@ tool(
|
|
|
6412
6497
|
);
|
|
6413
6498
|
tool(
|
|
6414
6499
|
"rig_adjust_track",
|
|
6415
|
-
"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.",
|
|
6500
|
+
"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.",
|
|
6416
6501
|
{ path: Path, animation: z3.string(), ...TrackRef, amplitude: z3.number().optional(), offset: z3.number().optional(), timeScale: z3.number().positive().optional() },
|
|
6417
|
-
async ({ path, animation, target, id, prop, ...adj }) => mutate(path, (d) => adjustTrack(d, animation, { target, id, prop }, adj))
|
|
6502
|
+
async ({ path, animation, target, id, prop, interpolation, ...adj }) => mutate(path, (d) => adjustTrack(d, animation, { target, id, prop, interpolation }, adj))
|
|
6418
6503
|
);
|
|
6419
6504
|
tool(
|
|
6420
6505
|
"rig_set_animation",
|
|
@@ -6432,7 +6517,7 @@ tool(
|
|
|
6432
6517
|
tool(
|
|
6433
6518
|
"rig_remove_keys",
|
|
6434
6519
|
"Remove the keys of one track at the given times; an emptied track is dropped. Drops the preset link.",
|
|
6435
|
-
{ path: Path, animation: z3.string(), ...
|
|
6520
|
+
{ path: Path, animation: z3.string(), ...TrackId, times: z3.array(z3.number()).min(1) },
|
|
6436
6521
|
async ({ path, animation, target, id, prop, times: times2 }) => mutate(path, (d) => removeKeys(d, animation, { target, id, prop }, times2))
|
|
6437
6522
|
);
|
|
6438
6523
|
tool(
|
|
@@ -6588,40 +6673,41 @@ tool(
|
|
|
6588
6673
|
}
|
|
6589
6674
|
);
|
|
6590
6675
|
|
|
6676
|
+
// src/rig-version.ts
|
|
6677
|
+
import { stat as stat3 } from "node:fs/promises";
|
|
6678
|
+
async function rigVersion(abs) {
|
|
6679
|
+
try {
|
|
6680
|
+
const s = await stat3(abs);
|
|
6681
|
+
return `${s.mtimeMs}:${s.size}`;
|
|
6682
|
+
} catch {
|
|
6683
|
+
return "missing";
|
|
6684
|
+
}
|
|
6685
|
+
}
|
|
6686
|
+
|
|
6591
6687
|
// src/editor/server.ts
|
|
6592
6688
|
var TYPES = { ".html": "text/html; charset=utf-8", ".js": "text/javascript", ".css": "text/css", ".map": "application/json", ".json": "application/json", ".png": "image/png", ".jpg": "image/jpeg", ".webp": "image/webp" };
|
|
6593
|
-
|
|
6689
|
+
var json = (res, status, body) => {
|
|
6690
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
6691
|
+
res.end(JSON.stringify(body));
|
|
6692
|
+
};
|
|
6693
|
+
function createEditorHandler(opts) {
|
|
6594
6694
|
const root2 = resolve2(opts.root);
|
|
6595
6695
|
const assets = resolve2(opts.assets ?? fileURLToPath3(new URL("../..", import.meta.url)));
|
|
6596
6696
|
const inside = (rel) => {
|
|
6597
6697
|
const abs = resolve2(root2, rel);
|
|
6598
|
-
return abs === root2 || abs.startsWith(root2 +
|
|
6599
|
-
};
|
|
6600
|
-
const version = async (abs) => {
|
|
6601
|
-
try {
|
|
6602
|
-
const s = await stat3(abs);
|
|
6603
|
-
return `${s.mtimeMs}:${s.size}`;
|
|
6604
|
-
} catch {
|
|
6605
|
-
return "missing";
|
|
6606
|
-
}
|
|
6698
|
+
return abs === root2 || abs.startsWith(root2 + sep2) ? abs : void 0;
|
|
6607
6699
|
};
|
|
6608
6700
|
const readDoc = async (abs) => RigDocument.parse(JSON.parse(await readFile10(abs, "utf8")));
|
|
6609
6701
|
const readDocAt = async (abs) => {
|
|
6610
|
-
let v = await
|
|
6702
|
+
let v = await rigVersion(abs);
|
|
6611
6703
|
for (let i = 0; i < 3; i++) {
|
|
6612
6704
|
const doc = await readDoc(abs);
|
|
6613
|
-
const after = await
|
|
6705
|
+
const after = await rigVersion(abs);
|
|
6614
6706
|
if (after === v) return { doc, version: v };
|
|
6615
6707
|
v = after;
|
|
6616
6708
|
}
|
|
6617
6709
|
return { doc: await readDoc(abs), version: v };
|
|
6618
6710
|
};
|
|
6619
|
-
const json = (res, status, body2) => {
|
|
6620
|
-
res.writeHead(status, { "content-type": "application/json" });
|
|
6621
|
-
res.end(JSON.stringify(body2));
|
|
6622
|
-
};
|
|
6623
|
-
let allowedHosts = /* @__PURE__ */ new Set();
|
|
6624
|
-
const hostOk = (req) => allowedHosts.has(String(req.headers.host ?? "").toLowerCase());
|
|
6625
6711
|
const clients = /* @__PURE__ */ new Map();
|
|
6626
6712
|
const watchers = /* @__PURE__ */ new Map();
|
|
6627
6713
|
function ensureWatch(abs) {
|
|
@@ -6633,7 +6719,7 @@ async function startEditorServer(opts) {
|
|
|
6633
6719
|
const schedule = () => {
|
|
6634
6720
|
clearTimeout(entry.timer);
|
|
6635
6721
|
entry.timer = setTimeout(async () => {
|
|
6636
|
-
const v = await
|
|
6722
|
+
const v = await rigVersion(abs);
|
|
6637
6723
|
if (v === entry.last) return;
|
|
6638
6724
|
entry.last = v;
|
|
6639
6725
|
for (const res of clients.get(abs) ?? []) res.write(`event: changed
|
|
@@ -6642,7 +6728,7 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6642
6728
|
`);
|
|
6643
6729
|
}, 50);
|
|
6644
6730
|
};
|
|
6645
|
-
|
|
6731
|
+
rigVersion(abs).then((v) => {
|
|
6646
6732
|
entry.last = v;
|
|
6647
6733
|
});
|
|
6648
6734
|
watchers.set(abs, entry);
|
|
@@ -6668,7 +6754,7 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6668
6754
|
name = String(JSON.parse(await readFile10(abs, "utf8")).meta?.name ?? "?");
|
|
6669
6755
|
} catch {
|
|
6670
6756
|
}
|
|
6671
|
-
out.push({ rig:
|
|
6757
|
+
out.push({ rig: relative4(root2, abs).split(sep2).join("/"), name, mtimeMs: (await stat4(abs)).mtimeMs });
|
|
6672
6758
|
}
|
|
6673
6759
|
};
|
|
6674
6760
|
await walk(root2);
|
|
@@ -6702,20 +6788,22 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6702
6788
|
}
|
|
6703
6789
|
return Buffer.concat(chunks);
|
|
6704
6790
|
}
|
|
6705
|
-
|
|
6791
|
+
async function handle(req, res, rawUrl = req.url ?? "/") {
|
|
6706
6792
|
try {
|
|
6707
|
-
|
|
6708
|
-
const url = new URL(req.url ?? "/", "http://x");
|
|
6793
|
+
const url = new URL(rawUrl, "http://x");
|
|
6709
6794
|
if (url.pathname === "/") {
|
|
6710
|
-
res.writeHead(302, { location: "
|
|
6711
|
-
return res.end();
|
|
6795
|
+
res.writeHead(302, { location: "editor.html" + url.search });
|
|
6796
|
+
return void res.end();
|
|
6797
|
+
}
|
|
6798
|
+
if (url.pathname.startsWith("/api/")) {
|
|
6799
|
+
await api(url, req, res);
|
|
6800
|
+
return;
|
|
6712
6801
|
}
|
|
6713
|
-
if (url.pathname.startsWith("/api/")) return await api(url, req, res);
|
|
6714
6802
|
const STATIC = /* @__PURE__ */ new Set(["/editor.html", "/dist/editor.js", "/dist/editor.js.map", "/dist/editor.css", "/dist/editor.css.map"]);
|
|
6715
6803
|
const abs = url.pathname.startsWith("/rig/") ? inside(decodeURIComponent(url.pathname.slice(5))) : STATIC.has(url.pathname) ? join10(assets, url.pathname) : void 0;
|
|
6716
6804
|
if (!abs) {
|
|
6717
6805
|
res.writeHead(403);
|
|
6718
|
-
return res.end("forbidden");
|
|
6806
|
+
return void res.end("forbidden");
|
|
6719
6807
|
}
|
|
6720
6808
|
try {
|
|
6721
6809
|
const data = await readFile10(abs);
|
|
@@ -6733,7 +6821,7 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6733
6821
|
}
|
|
6734
6822
|
json(res, 500, { isError: true, text: `ERROR: ${e.message}` });
|
|
6735
6823
|
}
|
|
6736
|
-
}
|
|
6824
|
+
}
|
|
6737
6825
|
async function api(url, req, res) {
|
|
6738
6826
|
const rigOf = (rel) => {
|
|
6739
6827
|
if (!rel) return void 0;
|
|
@@ -6750,7 +6838,7 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6750
6838
|
res.writeHead(403);
|
|
6751
6839
|
return res.end();
|
|
6752
6840
|
}
|
|
6753
|
-
if (!await
|
|
6841
|
+
if (!await stat4(abs).then(() => true, () => false)) return json(res, 404, { isError: true, text: `ERROR: ${url.searchParams.get("rig")} does not exist under ${root2}` });
|
|
6754
6842
|
ensureWatch(abs);
|
|
6755
6843
|
res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive" });
|
|
6756
6844
|
res.write(`: connected
|
|
@@ -6772,7 +6860,7 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6772
6860
|
if (!abs) return json(res, 403, { isError: true, text: "rig outside root" });
|
|
6773
6861
|
ensureWatch(abs);
|
|
6774
6862
|
return await withRigQueue(abs, async () => {
|
|
6775
|
-
if (baseVersion !== void 0 && await
|
|
6863
|
+
if (baseVersion !== void 0 && await rigVersion(abs) !== baseVersion) {
|
|
6776
6864
|
const at = await readDocAt(abs);
|
|
6777
6865
|
return json(res, 409, {
|
|
6778
6866
|
isError: true,
|
|
@@ -6781,8 +6869,15 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6781
6869
|
text: `ERROR: the rig changed on disk (expected ${baseVersion}, found ${at.version}); nothing was written`
|
|
6782
6870
|
});
|
|
6783
6871
|
}
|
|
6784
|
-
const
|
|
6785
|
-
|
|
6872
|
+
const tool2 = url.pathname.slice(10);
|
|
6873
|
+
let given = { ...args ?? {}, path: abs };
|
|
6874
|
+
try {
|
|
6875
|
+
if (opts.args) given = opts.args(tool2, given);
|
|
6876
|
+
} catch (e) {
|
|
6877
|
+
return json(res, 400, { isError: true, text: `ERROR: ${e instanceof Error ? e.message : String(e)}` });
|
|
6878
|
+
}
|
|
6879
|
+
const r = await callTool(tool2, given);
|
|
6880
|
+
return json(res, 200, { text: r.text, doc: r.doc, image: r.image?.toString("base64"), isError: r.isError, version: await rigVersion(abs) });
|
|
6786
6881
|
});
|
|
6787
6882
|
}
|
|
6788
6883
|
if (url.pathname === "/api/rigs" && req.method === "GET") return json(res, 200, await listRigs());
|
|
@@ -6793,11 +6888,11 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6793
6888
|
if (typeof rig !== "string" || !/(^|\/)rig\.json$/.test(rig)) return json(res, 400, { isError: true, text: "ERROR: a project is a directory with a rig.json \u2014 the path must end with /rig.json" });
|
|
6794
6889
|
const abs = inside(rig);
|
|
6795
6890
|
if (!abs) return json(res, 403, { isError: true, text: "rig outside root" });
|
|
6796
|
-
if (await
|
|
6891
|
+
if (await stat4(abs).then(() => true, () => false)) return json(res, 409, { isError: true, text: `ERROR: ${rig} already exists \u2014 open it, or pick another directory` });
|
|
6797
6892
|
await mkdir7(dirname9(abs), { recursive: true });
|
|
6798
6893
|
const r = await callTool("rig_create", { path: abs, name: name ?? basename5(dirname9(abs)), width: width ?? 640, height: height ?? 700 });
|
|
6799
6894
|
ensureWatch(abs);
|
|
6800
|
-
return json(res, r.isError ? 400 : 200, { text: r.text, doc: r.doc, isError: r.isError, version: await
|
|
6895
|
+
return json(res, r.isError ? 400 : 200, { text: r.text, doc: r.doc, isError: r.isError, version: await rigVersion(abs) });
|
|
6801
6896
|
}
|
|
6802
6897
|
if (url.pathname === "/api/files" && req.method === "GET") {
|
|
6803
6898
|
const abs = rigOf(url.searchParams.get("rig"));
|
|
@@ -6811,8 +6906,8 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6811
6906
|
if (!/^[A-Za-z0-9._-]+\.png$/i.test(name) || name.startsWith(".")) return json(res, 400, { isError: true, text: `ERROR: bad file name "${name}" \u2014 letters, digits, . _ - and a .png extension` });
|
|
6812
6907
|
if (!String(req.headers["content-type"] ?? "").toLowerCase().startsWith("image/png")) return json(res, 415, { isError: true, text: "ERROR: upload needs content-type: image/png" });
|
|
6813
6908
|
const target = join10(dirname9(abs), name);
|
|
6814
|
-
if (!inside(
|
|
6815
|
-
if (await
|
|
6909
|
+
if (!inside(relative4(root2, target))) return json(res, 403, { isError: true, text: "file outside root" });
|
|
6910
|
+
if (await stat4(target).then(() => true, () => false)) return json(res, 409, { isError: true, text: `ERROR: ${name} already exists next to the rig \u2014 rename the file` });
|
|
6816
6911
|
const data = await bytes(req, UPLOAD_MAX);
|
|
6817
6912
|
if (!data) return json(res, 413, { isError: true, text: `ERROR: ${name} is larger than ${UPLOAD_MAX / 1048576} MB` });
|
|
6818
6913
|
let size;
|
|
@@ -6826,6 +6921,22 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6826
6921
|
}
|
|
6827
6922
|
json(res, 404, { isError: true, text: "unknown api route" });
|
|
6828
6923
|
}
|
|
6924
|
+
return { handle, close() {
|
|
6925
|
+
for (const w of watchers.values()) {
|
|
6926
|
+
clearTimeout(w.timer);
|
|
6927
|
+
w.w.close();
|
|
6928
|
+
}
|
|
6929
|
+
for (const set of clients.values()) for (const c of set) c.end();
|
|
6930
|
+
} };
|
|
6931
|
+
}
|
|
6932
|
+
async function startEditorServer(opts) {
|
|
6933
|
+
const editor = createEditorHandler(opts);
|
|
6934
|
+
let allowedHosts = /* @__PURE__ */ new Set();
|
|
6935
|
+
const server = createServer((req, res) => {
|
|
6936
|
+
if (!allowedHosts.has(String(req.headers.host ?? "").toLowerCase()))
|
|
6937
|
+
return json(res, 403, { isError: true, text: `ERROR: forbidden Host "${req.headers.host ?? ""}" \u2014 this server answers only on 127.0.0.1 / localhost` });
|
|
6938
|
+
void editor.handle(req, res);
|
|
6939
|
+
});
|
|
6829
6940
|
await new Promise((ok, fail2) => {
|
|
6830
6941
|
server.once("error", fail2);
|
|
6831
6942
|
server.listen(opts.port ?? 8765, "127.0.0.1", () => {
|
|
@@ -6836,15 +6947,12 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6836
6947
|
const port = server.address().port;
|
|
6837
6948
|
allowedHosts = /* @__PURE__ */ new Set([`127.0.0.1:${port}`, `localhost:${port}`, `[::1]:${port}`]);
|
|
6838
6949
|
return { port, url: `http://127.0.0.1:${port}`, close: async () => {
|
|
6839
|
-
|
|
6840
|
-
clearTimeout(w.timer);
|
|
6841
|
-
w.w.close();
|
|
6842
|
-
}
|
|
6843
|
-
for (const set of clients.values()) for (const c of set) c.end();
|
|
6950
|
+
editor.close();
|
|
6844
6951
|
await new Promise((r) => server.close(() => r()));
|
|
6845
6952
|
} };
|
|
6846
6953
|
}
|
|
6847
6954
|
export {
|
|
6955
|
+
createEditorHandler,
|
|
6848
6956
|
startEditorServer
|
|
6849
6957
|
};
|
|
6850
6958
|
//# sourceMappingURL=editor-entry.js.map
|