@genex-ai/cli-demo 0.87.0-dev.218 → 0.90.0-dev.227

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -402,7 +402,7 @@ async function syncSkillsForTarget(target, templatesDir = getTemplatesDir(), ver
402
402
  if (await readSkillsMarker(skillsDir) === version) return false;
403
403
  const src = target.full ? templatesDir : path4.join(templatesDir, "skills");
404
404
  const dest = target.full ? target.baseDir : skillsDir;
405
- await copyTemplates(src, dest, { exclude: ["controllers"] });
405
+ await copyTemplates(src, dest, { exclude: ["controllers", "motion"] });
406
406
  await pruneRemovedSkills(skillsDir, log);
407
407
  await writeSkillsMarker(skillsDir, version);
408
408
  return true;
@@ -1315,7 +1315,7 @@ async function runInit(opts) {
1315
1315
  const dest = t.full ? t.baseDir : path9.join(t.baseDir, "skills");
1316
1316
  const { copied, updated } = await copyTemplates(src, dest, {
1317
1317
  force: opts.force,
1318
- exclude: ["controllers"]
1318
+ exclude: ["controllers", "motion"]
1319
1319
  });
1320
1320
  await pruneRemovedSkills(path9.join(t.baseDir, "skills"), log);
1321
1321
  await writeSkillsMarker(path9.join(t.baseDir, "skills"));
@@ -2071,9 +2071,9 @@ function measureSeam(png, tolerance = DEFAULT_SEAM_TOLERANCE) {
2071
2071
  }
2072
2072
  baseH = Math.max(baseH / Math.max(1, nH), 1e-6);
2073
2073
  baseV = Math.max(baseV / Math.max(1, nV), 1e-6);
2074
- const round = (n) => Math.round(n * 100) / 100;
2075
- const ratioHorizontal = round(seamH / baseH);
2076
- const ratioVertical = round(seamV / baseV);
2074
+ const round2 = (n) => Math.round(n * 100) / 100;
2075
+ const ratioHorizontal = round2(seamH / baseH);
2076
+ const ratioVertical = round2(seamV / baseV);
2077
2077
  const worstRatio = Math.max(ratioHorizontal, ratioVertical);
2078
2078
  return {
2079
2079
  width: w,
@@ -3606,11 +3606,11 @@ async function uiAudit(opts, log) {
3606
3606
  return;
3607
3607
  }
3608
3608
  const hard = findings.filter((finding) => HARD_AUDIT_KINDS.has(finding.kind));
3609
- const soft = findings.filter((finding) => !HARD_AUDIT_KINDS.has(finding.kind));
3609
+ const soft2 = findings.filter((finding) => !HARD_AUDIT_KINDS.has(finding.kind));
3610
3610
  for (const finding of hard) log.error(`[${finding.kind}] ${finding.message}`);
3611
- if (soft.length > 0) {
3612
- log.warn(`${soft.length} finding(s). These are heuristics \u2014 check each one; some may be fine:`);
3613
- for (const finding of soft) log.warn(` [${finding.kind}] ${finding.message}`);
3611
+ if (soft2.length > 0) {
3612
+ log.warn(`${soft2.length} finding(s). These are heuristics \u2014 check each one; some may be fine:`);
3613
+ for (const finding of soft2) log.warn(` [${finding.kind}] ${finding.message}`);
3614
3614
  }
3615
3615
  if (opts.strict || hard.length > 0) process.exitCode = 1;
3616
3616
  }
@@ -4259,6 +4259,8 @@ var WAIT_TIMEOUT_MS = 10 * 60 * 1e3;
4259
4259
  var KIND_WAIT_TIMEOUT_MS = {
4260
4260
  video: 15 * 60 * 1e3,
4261
4261
  // 15 min
4262
+ motion: 20 * 60 * 1e3,
4263
+ // 8 takes ≈ 8-9 GPU min + a serverless cold start
4262
4264
  character_concept: 15 * 60 * 1e3,
4263
4265
  character_preview: 30 * 60 * 1e3,
4264
4266
  character: 30 * 60 * 1e3,
@@ -4374,6 +4376,7 @@ function printHint(kind, view, files, log) {
4374
4376
  texture: `Load each map with TextureLoader (RepeatWrapping) into a MeshStandardMaterial \u2014 see genex-ai-texture. Use the URLs above by role.`,
4375
4377
  image: `Load with TextureLoader (set colorSpace = SRGBColorSpace) onto any mesh/plane/sprite \u2014 see genex-ai-image. url = "${url}"`,
4376
4378
  video: `Wire an HTMLVideoElement (crossOrigin="anonymous", muted, loop, playsInline) into a THREE.VideoTexture \u2014 see genex-ai-video. url = "${url}"`,
4379
+ motion: `Raw ARDY takes + verify report \u2014 download the takes, rename them meaningfully (routing is by filename), then: genex motion verify \u2192 compile \u2192 install. Full procedure: the genex-ai-character skill's motion reference.`,
4377
4380
  character_concept: "Show all three concept candidates to the user and wait for an explicit selection.",
4378
4381
  character_preview: "Show all four preview views to the user and wait for explicit approval before finalizing.",
4379
4382
  character: `Install the controller and current manifest with genex controller character --character ${view.id}.`,
@@ -4724,6 +4727,17 @@ async function deployGame(ctx, opts, log) {
4724
4727
  log.error(`No index.html in ${rel} \u2014 a game needs one to serve. Nothing deployed.`);
4725
4728
  return false;
4726
4729
  }
4730
+ const lfsPointers = files.filter(
4731
+ (f) => f.bytes.length < 1024 && f.bytes.subarray(0, 64).toString("latin1").startsWith("version https://git-lfs.github.com/spec/v")
4732
+ );
4733
+ if (lfsPointers.length > 0) {
4734
+ log.error(
4735
+ `${lfsPointers.length} file${lfsPointers.length > 1 ? "s" : ""} in ${rel} ${lfsPointers.length > 1 ? "are" : "is"} an unresolved Git LFS pointer, not the real asset \u2014 the game would fail to load at runtime. Run \`git lfs pull\` and rebuild. Nothing deployed.`
4736
+ );
4737
+ for (const p of lfsPointers.slice(0, 8)) log.dim(` ${p.relPath}`);
4738
+ if (lfsPointers.length > 8) log.dim(` \u2026and ${lfsPointers.length - 8} more`);
4739
+ return false;
4740
+ }
4727
4741
  printMobilePreflight(files, log);
4728
4742
  await printUiAuditPreflight(log);
4729
4743
  await printPipelineStatePreflight(log);
@@ -14986,17 +15000,17 @@ function searchMeshyAnimations(query, options = {}) {
14986
15000
  if (options.category && entry.category.toLowerCase() !== options.category.toLowerCase()) continue;
14987
15001
  if (options.inPlace !== void 0 && entry.inPlace !== options.inPlace) continue;
14988
15002
  const matched = [];
14989
- let score = 0;
15003
+ let score2 = 0;
14990
15004
  if (String(entry.actionId) === rawQuery) {
14991
- score += 1e5;
15005
+ score2 += 1e5;
14992
15006
  matched.push("action id");
14993
15007
  }
14994
15008
  if (entry.key.toLowerCase() === rawQuery) {
14995
- score += 8e4;
15009
+ score2 += 8e4;
14996
15010
  matched.push("stable key");
14997
15011
  }
14998
15012
  if (entry.name.toLowerCase() === rawQuery) {
14999
- score += 6e4;
15013
+ score2 += 6e4;
15000
15014
  matched.push("exact name");
15001
15015
  }
15002
15016
  const haystack = new Set(tokens([
@@ -15014,13 +15028,13 @@ function searchMeshyAnimations(query, options = {}) {
15014
15028
  coveredGroups++;
15015
15029
  const original = queryTokens[index];
15016
15030
  const best = hits.includes(original) ? original : hits[0];
15017
- score += best === original ? 400 : 140;
15031
+ score2 += best === original ? 400 : 140;
15018
15032
  matched.push(best);
15019
15033
  }
15020
- if (entry.inPlace && (expanded.has("walk") || expanded.has("run"))) score += 35;
15021
- const exact = score >= 6e4;
15022
- if (score > 0 && (exact || coveredGroups === groups.length)) {
15023
- results.push({ entry, score, matched: [...new Set(matched)] });
15034
+ if (entry.inPlace && (expanded.has("walk") || expanded.has("run"))) score2 += 35;
15035
+ const exact = score2 >= 6e4;
15036
+ if (score2 > 0 && (exact || coveredGroups === groups.length)) {
15037
+ results.push({ entry, score: score2, matched: [...new Set(matched)] });
15024
15038
  }
15025
15039
  }
15026
15040
  return results.sort((a, b) => b.score - a.score || a.entry.name.localeCompare(b.entry.name) || a.entry.actionId - b.entry.actionId).slice(0, options.limit ?? 20);
@@ -15604,9 +15618,9 @@ async function installMeshyCharacterManifest(args) {
15604
15618
  `
15605
15619
  );
15606
15620
  args.log.dim(` public/assets/meshy-character.json (${args.characterId}, current revision)`);
15607
- const pack = manifest.controllerPack;
15608
- if (typeof pack?.key === "string" && typeof pack.version === "number") {
15609
- args.log.success(`Meshy controller pack ${pack.key} v${pack.version}`);
15621
+ const pack2 = manifest.controllerPack;
15622
+ if (typeof pack2?.key === "string" && typeof pack2.version === "number") {
15623
+ args.log.success(`Meshy controller pack ${pack2.key} v${pack2.version}`);
15610
15624
  } else {
15611
15625
  args.log.warn("Legacy Meshy manifest \u2014 regenerate the character for the immutable preview-reviewed neutral-v3 locomotion pack.");
15612
15626
  }
@@ -15667,37 +15681,37 @@ function incompletePack(message) {
15667
15681
  throw new Error(`Incomplete Meshy controller pack: ${message}`);
15668
15682
  }
15669
15683
  function assertCompleteMeshyControllerPack(manifest) {
15670
- const pack = manifest.controllerPack;
15671
- if (pack === void 0) return;
15672
- if (typeof pack.key !== "string" || pack.key.length === 0 || typeof pack.version !== "number" || !Number.isInteger(pack.version)) {
15684
+ const pack2 = manifest.controllerPack;
15685
+ if (pack2 === void 0) return;
15686
+ if (typeof pack2.key !== "string" || pack2.key.length === 0 || typeof pack2.version !== "number" || !Number.isInteger(pack2.version)) {
15673
15687
  incompletePack("invalid key or version.");
15674
15688
  }
15675
- if (typeof pack.fingerprint !== "string" || pack.fingerprint.length === 0) {
15676
- incompletePack(`${pack.key} v${pack.version} has no immutable fingerprint.`);
15689
+ if (typeof pack2.fingerprint !== "string" || pack2.fingerprint.length === 0) {
15690
+ incompletePack(`${pack2.key} v${pack2.version} has no immutable fingerprint.`);
15677
15691
  }
15678
- if (!Array.isArray(pack.actionIds) || pack.actionIds.length === 0 || pack.actionIds.some((actionId) => typeof actionId !== "number" || !Number.isInteger(actionId))) {
15679
- incompletePack(`${pack.key} v${pack.version} has no valid action snapshot.`);
15692
+ if (!Array.isArray(pack2.actionIds) || pack2.actionIds.length === 0 || pack2.actionIds.some((actionId) => typeof actionId !== "number" || !Number.isInteger(actionId))) {
15693
+ incompletePack(`${pack2.key} v${pack2.version} has no valid action snapshot.`);
15680
15694
  }
15681
- const actionIds = pack.actionIds;
15695
+ const actionIds = pack2.actionIds;
15682
15696
  const packActionIds = new Set(actionIds);
15683
15697
  if (packActionIds.size !== actionIds.length) {
15684
- incompletePack(`${pack.key} v${pack.version} repeats an action ID.`);
15698
+ incompletePack(`${pack2.key} v${pack2.version} repeats an action ID.`);
15685
15699
  }
15686
15700
  const bindings = manifest.locomotion?.bindings;
15687
- if (!bindings) incompletePack(`${pack.key} v${pack.version} has no authoritative locomotion bindings.`);
15701
+ if (!bindings) incompletePack(`${pack2.key} v${pack2.version} has no authoritative locomotion bindings.`);
15688
15702
  const missingSlots = REQUIRED_MESHY_CONTROLLER_SLOTS.filter((slot) => bindings[slot] === void 0);
15689
15703
  if (missingSlots.length > 0) {
15690
- incompletePack(`${pack.key} v${pack.version} is missing ${missingSlots.join(", ")}.`);
15704
+ incompletePack(`${pack2.key} v${pack2.version} is missing ${missingSlots.join(", ")}.`);
15691
15705
  }
15692
15706
  const clips = manifest.clips ?? [];
15693
15707
  const boundActionIds = /* @__PURE__ */ new Set();
15694
15708
  const snapshotBindings = {};
15695
15709
  for (const [slot, binding] of Object.entries(bindings)) {
15696
15710
  if (typeof binding.actionId !== "number" || !Number.isInteger(binding.actionId) || typeof binding.clip !== "string" || binding.clip.length === 0 || binding.mode !== "loop" && binding.mode !== "one-shot" && binding.mode !== "pose") {
15697
- incompletePack(`${pack.key} v${pack.version} has an invalid ${slot} binding.`);
15711
+ incompletePack(`${pack2.key} v${pack2.version} has an invalid ${slot} binding.`);
15698
15712
  }
15699
15713
  if (binding.phase !== void 0 && (typeof binding.phase !== "number" || !Number.isFinite(binding.phase) || binding.phase < 0 || binding.phase > 1)) {
15700
- incompletePack(`${pack.key} v${pack.version} has an invalid ${slot} phase.`);
15714
+ incompletePack(`${pack2.key} v${pack2.version} has an invalid ${slot} phase.`);
15701
15715
  }
15702
15716
  const actionId = binding.actionId;
15703
15717
  boundActionIds.add(actionId);
@@ -15716,21 +15730,21 @@ function assertCompleteMeshyControllerPack(manifest) {
15716
15730
  }
15717
15731
  const unboundActionIds = actionIds.filter((actionId) => !boundActionIds.has(actionId));
15718
15732
  if (unboundActionIds.length > 0) {
15719
- incompletePack(`${pack.key} v${pack.version} action snapshot contains unbound actions ${unboundActionIds.join(", ")}.`);
15733
+ incompletePack(`${pack2.key} v${pack2.version} action snapshot contains unbound actions ${unboundActionIds.join(", ")}.`);
15720
15734
  }
15721
15735
  let computedFingerprint;
15722
15736
  try {
15723
15737
  computedFingerprint = createMeshyControllerPackSnapshot({
15724
- key: pack.key,
15725
- version: pack.version,
15738
+ key: pack2.key,
15739
+ version: pack2.version,
15726
15740
  actionIds,
15727
15741
  bindings: snapshotBindings
15728
15742
  }).fingerprint;
15729
15743
  } catch (error) {
15730
15744
  incompletePack(error instanceof Error ? error.message : "the immutable snapshot is invalid.");
15731
15745
  }
15732
- if (computedFingerprint !== pack.fingerprint) {
15733
- incompletePack(`${pack.key} v${pack.version} fingerprint does not match its action bindings.`);
15746
+ if (computedFingerprint !== pack2.fingerprint) {
15747
+ incompletePack(`${pack2.key} v${pack2.version} fingerprint does not match its action bindings.`);
15734
15748
  }
15735
15749
  }
15736
15750
  async function installOwnerAvatar(args) {
@@ -16375,16 +16389,1559 @@ function rank(items, query) {
16375
16389
  const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
16376
16390
  return items.map((p) => {
16377
16391
  const hay = `${p.title} ${p.description} ${p.categories.join(" ")}`.toLowerCase();
16378
- let score = 0;
16392
+ let score2 = 0;
16379
16393
  for (const t of terms) {
16380
- if (p.title.toLowerCase().includes(t)) score += 3;
16381
- if (p.categories.some((cat) => cat.includes(t))) score += 2;
16382
- if (hay.includes(t)) score += 1;
16394
+ if (p.title.toLowerCase().includes(t)) score2 += 3;
16395
+ if (p.categories.some((cat) => cat.includes(t))) score2 += 2;
16396
+ if (hay.includes(t)) score2 += 1;
16383
16397
  }
16384
- return { p, score };
16398
+ return { p, score: score2 };
16385
16399
  }).filter((x) => x.score > 0).sort((a, b) => b.score - a.score || b.p.playsCount - a.p.playsCount).map((x) => x.p);
16386
16400
  }
16387
16401
 
16402
+ // src/commands/motion.ts
16403
+ import fs18 from "fs/promises";
16404
+ import path18 from "path";
16405
+
16406
+ // src/lib/motion/npz.ts
16407
+ import zlib from "zlib";
16408
+ function decodeUcs4(b) {
16409
+ let out = "";
16410
+ for (let i = 0; i + 4 <= b.length; i += 4) {
16411
+ const cp = b.readUInt32LE(i);
16412
+ if (cp === 0) break;
16413
+ out += cp > 1114111 ? "\uFFFD" : String.fromCodePoint(cp);
16414
+ }
16415
+ return out;
16416
+ }
16417
+ function readNpy(b) {
16418
+ if (b.toString("latin1", 1, 6) !== "NUMPY") throw new Error("not an .npy member");
16419
+ const major = b[6];
16420
+ const hlen = major >= 2 ? b.readUInt32LE(8) : b.readUInt16LE(8);
16421
+ const hstart = major >= 2 ? 12 : 10;
16422
+ const header = b.toString("latin1", hstart, hstart + hlen);
16423
+ const descr = /'descr':\s*'([^']+)'/.exec(header)?.[1];
16424
+ const fortran = /'fortran_order':\s*(True|False)/.exec(header)?.[1] === "True";
16425
+ const shapeRaw = /'shape':\s*\(([^)]*)\)/.exec(header)?.[1];
16426
+ if (!descr || shapeRaw === void 0) throw new Error(`unparseable .npy header: ${header}`);
16427
+ if (fortran) throw new Error("fortran-order .npy is unsupported");
16428
+ const shape = shapeRaw.split(",").map((s) => s.trim()).filter(Boolean).map(Number);
16429
+ const data = Buffer.from(b.subarray(hstart + hlen));
16430
+ if (descr === "<f4") return { shape, descr, data: new Float32Array(data.buffer, data.byteOffset, data.length / 4) };
16431
+ if (descr === "<f8") return { shape, descr, data: new Float64Array(data.buffer, data.byteOffset, data.length / 8) };
16432
+ if (descr === "<i8") return { shape, descr, data: new BigInt64Array(data.buffer, data.byteOffset, data.length / 8) };
16433
+ if (descr === "|b1") return { shape, descr, data: new Uint8Array(data.buffer, data.byteOffset, data.length) };
16434
+ if (descr.startsWith("<U")) return { shape, descr, data: decodeUcs4(data) };
16435
+ if (descr === "|O") throw new Error(".npz member is a pickled object array \u2014 regenerate the take with a current ARDY");
16436
+ throw new Error(`unsupported .npy dtype ${descr}`);
16437
+ }
16438
+ function readNpz(buf) {
16439
+ let e = buf.length - 22;
16440
+ while (e >= 0 && buf.readUInt32LE(e) !== 101010256) e--;
16441
+ if (e < 0) throw new Error("not a ZIP/.npz file (no end-of-central-directory)");
16442
+ const count = buf.readUInt16LE(e + 10);
16443
+ let off = buf.readUInt32LE(e + 16);
16444
+ const out = {};
16445
+ for (let i = 0; i < count; i++) {
16446
+ if (buf.readUInt32LE(off) !== 33639248) throw new Error("corrupt ZIP central directory");
16447
+ const method = buf.readUInt16LE(off + 10);
16448
+ const csize = buf.readUInt32LE(off + 20);
16449
+ const nameLen = buf.readUInt16LE(off + 28);
16450
+ const extraLen = buf.readUInt16LE(off + 30);
16451
+ const cmtLen = buf.readUInt16LE(off + 32);
16452
+ const lho = buf.readUInt32LE(off + 42);
16453
+ const name = buf.toString("utf8", off + 46, off + 46 + nameLen);
16454
+ const lNameLen = buf.readUInt16LE(lho + 26);
16455
+ const lExtraLen = buf.readUInt16LE(lho + 28);
16456
+ const dataStart = lho + 30 + lNameLen + lExtraLen;
16457
+ const raw = buf.subarray(dataStart, dataStart + csize);
16458
+ const bytes = method === 8 ? zlib.inflateRawSync(raw) : Buffer.from(raw);
16459
+ if (method !== 0 && method !== 8) throw new Error(`unsupported ZIP method ${method} for ${name}`);
16460
+ out[name.replace(/\.npy$/, "")] = readNpy(bytes);
16461
+ off += 46 + nameLen + extraLen + cmtLen;
16462
+ }
16463
+ return out;
16464
+ }
16465
+ function floats(npz, key) {
16466
+ const m = npz[key];
16467
+ if (!m) throw new Error(`.npz is missing "${key}" \u2014 not an ARDY take?`);
16468
+ const d = m.data;
16469
+ if (d instanceof Float64Array) return { shape: m.shape, a: d };
16470
+ if (d instanceof Float32Array) return { shape: m.shape, a: Float64Array.from(d) };
16471
+ if (d instanceof Uint8Array && m.descr === "|b1") return { shape: m.shape, a: Float64Array.from(d) };
16472
+ throw new Error(`"${key}" has dtype ${m.descr}, expected floats`);
16473
+ }
16474
+
16475
+ // src/lib/motion/core27.ts
16476
+ var JOINTS = ["Hips", "Spine", "Spine1", "Spine2", "Spine3", "Neck", "Head", "RightShoulder", "RightArm", "RightForeArm", "RightHand", "RightHandEnd", "RightHandThumb1", "LeftShoulder", "LeftArm", "LeftForeArm", "LeftHand", "LeftHandEnd", "LeftHandThumb1", "RightUpLeg", "RightLeg", "RightFoot", "RightToeBase", "LeftUpLeg", "LeftLeg", "LeftFoot", "LeftToeBase"];
16477
+ var PARENTS = [-1, 0, 1, 2, 3, 4, 5, 4, 7, 8, 9, 10, 10, 4, 13, 14, 15, 16, 16, 0, 19, 20, 21, 0, 23, 24, 25];
16478
+ var REST_OFFSETS = [[0, 0, 0], [0, 0.071, -0.0473], [0, 0.0932, -0.0164], [0, 0.0943, -82e-4], [0, 0.0947, -0], [0, 0.2485, 0.0355], [-0, 0.1282, 0.0226], [-0.032, 0.1728, 0.0533], [-0.1589, -0, 0], [-0.2954, -0, -0], [-0.2327, -0, -0], [-0.0696, -0, 0], [-0.0278, -0.0186, 0.0464], [0.032, 0.1728, 0.0533], [0.1589, -0, -0], [0.2954, 0, 0], [0.2327, -0, 0], [0.0696, 0, 0], [0.0278, -0.0186, 0.0464], [-0.0949, -0.0277, -0], [-0, -0.4121, -0], [0, -0.4561, -0], [0, -0.0585, 0.1607], [0.0949, -0.0277, 0], [-0, -0.4121, 0], [-0, -0.4561, -0], [0, -0.0585, 0.1607]];
16479
+ var REST_POSITIONS = [[0, 0, 0], [0, 0.071, -0.0473], [0, 0.1642, -0.0637], [0, 0.2585, -0.0719], [0, 0.3532, -0.0719], [0, 0.6017, -0.0364], [0, 0.7299, -0.0138], [-0.032, 0.526, -0.0186], [-0.1909, 0.526, -0.0186], [-0.4863, 0.526, -0.0186], [-0.719, 0.526, -0.0186], [-0.7886, 0.526, -0.0186], [-0.7468, 0.5074, 0.0278], [0.032, 0.526, -0.0186], [0.1909, 0.526, -0.0186], [0.4863, 0.526, -0.0186], [0.719, 0.526, -0.0186], [0.7886, 0.526, -0.0186], [0.7468, 0.5074, 0.0278], [-0.0949, -0.0277, 0], [-0.0949, -0.4398, 0], [-0.0949, -0.8959, 0], [-0.0949, -0.9544, 0.1607], [0.0949, -0.0277, 0], [0.0949, -0.4398, 0], [0.0949, -0.8959, 0], [0.0949, -0.9544, 0.1607]];
16480
+ var HIPS_REST_Y = 0.9544;
16481
+ var J = JOINTS.length;
16482
+
16483
+ // src/lib/motion/skeleton.ts
16484
+ var ji = (name) => {
16485
+ const i = JOINTS.indexOf(name);
16486
+ if (i < 0) throw new Error(`unknown CoreSkeleton27 joint ${name}`);
16487
+ return i;
16488
+ };
16489
+ function matMul(a, b) {
16490
+ return [
16491
+ a[0] * b[0] + a[1] * b[3] + a[2] * b[6],
16492
+ a[0] * b[1] + a[1] * b[4] + a[2] * b[7],
16493
+ a[0] * b[2] + a[1] * b[5] + a[2] * b[8],
16494
+ a[3] * b[0] + a[4] * b[3] + a[5] * b[6],
16495
+ a[3] * b[1] + a[4] * b[4] + a[5] * b[7],
16496
+ a[3] * b[2] + a[4] * b[5] + a[5] * b[8],
16497
+ a[6] * b[0] + a[7] * b[3] + a[8] * b[6],
16498
+ a[6] * b[1] + a[7] * b[4] + a[8] * b[7],
16499
+ a[6] * b[2] + a[7] * b[5] + a[8] * b[8]
16500
+ ];
16501
+ }
16502
+ function matVec(m, v) {
16503
+ return [
16504
+ m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
16505
+ m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
16506
+ m[6] * v[0] + m[7] * v[1] + m[8] * v[2]
16507
+ ];
16508
+ }
16509
+ function matT(m) {
16510
+ return [m[0], m[3], m[6], m[1], m[4], m[7], m[2], m[5], m[8]];
16511
+ }
16512
+ function rotY(a) {
16513
+ const c2 = Math.cos(a), s = Math.sin(a);
16514
+ return [c2, 0, s, 0, 1, 0, -s, 0, c2];
16515
+ }
16516
+ function rotAxis(axis, angle) {
16517
+ const [x, y, z] = [axis[0], axis[1], axis[2]];
16518
+ const c2 = Math.cos(angle), s = Math.sin(angle), t = 1 - c2;
16519
+ return [
16520
+ t * x * x + c2,
16521
+ t * x * y - s * z,
16522
+ t * x * z + s * y,
16523
+ t * x * y + s * z,
16524
+ t * y * y + c2,
16525
+ t * y * z - s * x,
16526
+ t * x * z - s * y,
16527
+ t * y * z + s * x,
16528
+ t * z * z + c2
16529
+ ];
16530
+ }
16531
+ function quatFromMat(m) {
16532
+ const tr = m[0] + m[4] + m[8];
16533
+ let x, y, z, w;
16534
+ if (tr > 0) {
16535
+ const s = Math.sqrt(tr + 1) * 2;
16536
+ w = s / 4;
16537
+ x = (m[7] - m[5]) / s;
16538
+ y = (m[2] - m[6]) / s;
16539
+ z = (m[3] - m[1]) / s;
16540
+ } else if (m[0] > m[4] && m[0] > m[8]) {
16541
+ const s = Math.sqrt(1 + m[0] - m[4] - m[8]) * 2;
16542
+ w = (m[7] - m[5]) / s;
16543
+ x = s / 4;
16544
+ y = (m[1] + m[3]) / s;
16545
+ z = (m[2] + m[6]) / s;
16546
+ } else if (m[4] > m[8]) {
16547
+ const s = Math.sqrt(1 + m[4] - m[0] - m[8]) * 2;
16548
+ w = (m[2] - m[6]) / s;
16549
+ x = (m[1] + m[3]) / s;
16550
+ y = s / 4;
16551
+ z = (m[5] + m[7]) / s;
16552
+ } else {
16553
+ const s = Math.sqrt(1 + m[8] - m[0] - m[4]) * 2;
16554
+ w = (m[3] - m[1]) / s;
16555
+ x = (m[2] + m[6]) / s;
16556
+ y = (m[5] + m[7]) / s;
16557
+ z = s / 4;
16558
+ }
16559
+ const n = Math.hypot(x, y, z, w);
16560
+ return [x / n, y / n, z / n, w / n];
16561
+ }
16562
+ function matFromQuat(q) {
16563
+ const [x, y, z, w] = [q[0], q[1], q[2], q[3]];
16564
+ const n = x * x + y * y + z * z + w * w || 1;
16565
+ const s = 2 / n;
16566
+ const xx = x * x * s, yy = y * y * s, zz = z * z * s;
16567
+ const xy = x * y * s, xz = x * z * s, yz = y * z * s;
16568
+ const wx = w * x * s, wy = w * y * s, wz = w * z * s;
16569
+ return [
16570
+ 1 - yy - zz,
16571
+ xy - wz,
16572
+ xz + wy,
16573
+ xy + wz,
16574
+ 1 - xx - zz,
16575
+ yz - wx,
16576
+ xz - wy,
16577
+ yz + wx,
16578
+ 1 - xx - yy
16579
+ ];
16580
+ }
16581
+ function quatMul(a, b) {
16582
+ const [ax, ay, az, aw] = [a[0], a[1], a[2], a[3]];
16583
+ const [bx, by, bz, bw] = [b[0], b[1], b[2], b[3]];
16584
+ return [
16585
+ aw * bx + ax * bw + ay * bz - az * by,
16586
+ aw * by - ax * bz + ay * bw + az * bx,
16587
+ aw * bz + ax * by - ay * bx + az * bw,
16588
+ aw * bw - ax * bx - ay * by - az * bz
16589
+ ];
16590
+ }
16591
+ var mean = (a) => {
16592
+ let s = 0;
16593
+ for (let i = 0; i < a.length; i++) s += a[i];
16594
+ return a.length ? s / a.length : NaN;
16595
+ };
16596
+ function percentile(values, q) {
16597
+ const a = Array.from(values).sort((x, y) => x - y);
16598
+ if (a.length === 0) return NaN;
16599
+ const pos = (a.length - 1) * q / 100;
16600
+ const lo = Math.floor(pos), hi = Math.ceil(pos);
16601
+ if (lo === hi) return a[lo];
16602
+ return a[lo] * (hi - pos) + a[hi] * (pos - lo);
16603
+ }
16604
+ var median2 = (a) => percentile(a, 50);
16605
+ function std(a) {
16606
+ const m = mean(a);
16607
+ let s = 0;
16608
+ for (let i = 0; i < a.length; i++) s += (a[i] - m) ** 2;
16609
+ return Math.sqrt(a.length ? s / a.length : NaN);
16610
+ }
16611
+ function unwrap(a) {
16612
+ const out = [a[0] ?? 0];
16613
+ for (let i = 1; i < a.length; i++) {
16614
+ let d = a[i] - a[i - 1];
16615
+ d -= 2 * Math.PI * Math.round(d / (2 * Math.PI));
16616
+ out.push(out[i - 1] + d);
16617
+ }
16618
+ return out;
16619
+ }
16620
+ function lstsqLinear(t, y) {
16621
+ const n = t.length;
16622
+ const mt = mean(t), my = mean(y);
16623
+ let sxx = 0, sxy = 0;
16624
+ for (let i = 0; i < n; i++) {
16625
+ sxx += (t[i] - mt) ** 2;
16626
+ sxy += (t[i] - mt) * (y[i] - my);
16627
+ }
16628
+ const slope = sxx > 0 ? sxy / sxx : 0;
16629
+ return { slope, intercept: my - slope * mt };
16630
+ }
16631
+ function loadTake(buf) {
16632
+ const npz = readNpz(buf);
16633
+ const posed = floats(npz, "posed_joints");
16634
+ const rootRaw = floats(npz, "root_positions");
16635
+ const lm = floats(npz, "local_rot_mats");
16636
+ const fcRaw = floats(npz, "foot_contacts");
16637
+ const F = posed.shape[0];
16638
+ if (lm.shape[1] !== J) throw new Error(`take has ${lm.shape[1]} joints, expected ${J}`);
16639
+ const P = [];
16640
+ const root = [];
16641
+ const localMats = [];
16642
+ const quat = [];
16643
+ const heading = [];
16644
+ const fc = [];
16645
+ for (let f = 0; f < F; f++) {
16646
+ const pf = [];
16647
+ for (let j = 0; j < J; j++) {
16648
+ const o = (f * J + j) * 3;
16649
+ pf.push([posed.a[o], posed.a[o + 1], posed.a[o + 2]]);
16650
+ }
16651
+ P.push(pf);
16652
+ root.push([rootRaw.a[f * 3], rootRaw.a[f * 3 + 1], rootRaw.a[f * 3 + 2]]);
16653
+ const mf = [];
16654
+ const qf = [];
16655
+ for (let j = 0; j < J; j++) {
16656
+ const o = (f * J + j) * 9;
16657
+ const m = Array.from(lm.a.subarray(o, o + 9));
16658
+ mf.push(m);
16659
+ qf.push(quatFromMat(m));
16660
+ }
16661
+ localMats.push(mf);
16662
+ quat.push(qf);
16663
+ const m0 = mf[0];
16664
+ heading.push(Math.atan2(m0[2], m0[8]));
16665
+ fc.push([fcRaw.a[f * 4] !== 0, fcRaw.a[f * 4 + 1] !== 0, fcRaw.a[f * 4 + 2] !== 0, fcRaw.a[f * 4 + 3] !== 0]);
16666
+ }
16667
+ for (let j = 0; j < J; j++) {
16668
+ for (let f = 1; f < F; f++) {
16669
+ const a = quat[f][j], b = quat[f - 1][j];
16670
+ if (a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3] < 0) {
16671
+ quat[f][j] = [-a[0], -a[1], -a[2], -a[3]];
16672
+ }
16673
+ }
16674
+ }
16675
+ let gm = null;
16676
+ if (npz.global_rot_mats) {
16677
+ const g = floats(npz, "global_rot_mats");
16678
+ gm = [];
16679
+ for (let f = 0; f < F; f++) {
16680
+ const gf = [];
16681
+ for (let j = 0; j < J; j++) {
16682
+ const o = (f * J + j) * 9;
16683
+ gf.push(Array.from(g.a.subarray(o, o + 9)));
16684
+ }
16685
+ gm.push(gf);
16686
+ }
16687
+ }
16688
+ const fpsMember = npz.fps;
16689
+ const fps = fpsMember && typeof fpsMember.data !== "string" ? Number(fpsMember.data[0]) : 20;
16690
+ const text = typeof npz.text?.data === "string" ? npz.text.data : "";
16691
+ return { F, fps, text, P, root, quat, heading, fc, gm, localMats };
16692
+ }
16693
+ function fkRots(localFrame) {
16694
+ const W = new Array(J);
16695
+ for (let j = 0; j < J; j++) {
16696
+ const p = PARENTS[j];
16697
+ W[j] = p < 0 ? localFrame[j] : matMul(W[p], localFrame[j]);
16698
+ }
16699
+ return W;
16700
+ }
16701
+ var I_HIPS = 0;
16702
+ function charFrame(P, heading) {
16703
+ const out = [];
16704
+ for (let f = 0; f < P.length; f++) {
16705
+ const rot = rotY(-heading[f]);
16706
+ const hx = P[f][I_HIPS][0], hz = P[f][I_HIPS][2];
16707
+ const cf = [];
16708
+ for (let j = 0; j < J; j++) {
16709
+ cf.push(matVec(rot, [P[f][j][0] - hx, P[f][j][1], P[f][j][2] - hz]));
16710
+ }
16711
+ out.push(cf);
16712
+ }
16713
+ return out;
16714
+ }
16715
+ function poseDist(quat, a, b) {
16716
+ let s = 0;
16717
+ for (let j = 0; j < J; j++) {
16718
+ const qa = quat[a][j], qb = quat[b][j];
16719
+ const d = Math.min(1, Math.abs(qa[0] * qb[0] + qa[1] * qb[1] + qa[2] * qb[2] + qa[3] * qb[3]));
16720
+ s += 2 * Math.acos(d);
16721
+ }
16722
+ return s / J;
16723
+ }
16724
+
16725
+ // src/lib/motion/normalize.ts
16726
+ var ARM_ROOTS = [ji("RightArm"), ji("LeftArm")];
16727
+ var I_RARM = ji("RightArm");
16728
+ var I_LARM = ji("LeftArm");
16729
+ var NECK_HEAD = [ji("Neck"), ji("Head")];
16730
+ var SPINE_IDX = ["Spine", "Spine1", "Spine2", "Spine3"].map(ji);
16731
+ var I_HIPS2 = ji("Hips");
16732
+ var I_RH = ji("RightHand");
16733
+ var I_LH = ji("LeftHand");
16734
+ var I_HEAD = ji("Head");
16735
+ var deg = Math.PI / 180;
16736
+ function deroot(quatCyc, rootCyc, fps, refHeading, targetHeading = 0) {
16737
+ const F = rootCyc.length;
16738
+ const ts = [], xs = [], zs = [];
16739
+ for (let f = 0; f < F; f++) {
16740
+ ts.push(f / fps);
16741
+ xs.push(rootCyc[f][0]);
16742
+ zs.push(rootCyc[f][2]);
16743
+ }
16744
+ const fx = lstsqLinear(ts, xs), fz = lstsqLinear(ts, zs);
16745
+ for (let f = 0; f < F; f++) {
16746
+ rootCyc[f][0] -= fx.intercept + fx.slope * ts[f];
16747
+ rootCyc[f][2] -= fz.intercept + fz.slope * ts[f];
16748
+ }
16749
+ const theta = targetHeading - refHeading;
16750
+ const rotM = rotY(theta);
16751
+ const half = theta / 2;
16752
+ const rotQ = [0, Math.sin(half), 0, Math.cos(half)];
16753
+ for (let f = 0; f < F; f++) {
16754
+ const r = matVec(rotM, rootCyc[f]);
16755
+ rootCyc[f][0] = r[0];
16756
+ rootCyc[f][2] = r[2];
16757
+ quatCyc[f][0] = quatMul(rotQ, quatCyc[f][0]);
16758
+ }
16759
+ const v = matVec(rotM, [fx.slope, 0, fz.slope]);
16760
+ const speed = Math.hypot(v[0], v[2]);
16761
+ return { speed, dir: speed > 1e-6 ? [v[0] / speed, v[2] / speed] : [0, 0] };
16762
+ }
16763
+ function levelArms(qframe, Pf, pitchDeg) {
16764
+ const ax = [
16765
+ Pf[I_LARM][0] - Pf[I_RARM][0],
16766
+ Pf[I_LARM][1] - Pf[I_RARM][1],
16767
+ Pf[I_LARM][2] - Pf[I_RARM][2]
16768
+ ];
16769
+ const n = Math.hypot(...ax) + 1e-9;
16770
+ const Rax = rotAxis([ax[0] / n, ax[1] / n, ax[2] / n], pitchDeg * deg);
16771
+ const local = qframe.map(matFromQuat);
16772
+ const W = fkRots(local);
16773
+ const out = qframe.slice();
16774
+ for (const j of ARM_ROOTS) {
16775
+ const Wp = W[PARENTS[j]];
16776
+ out[j] = quatFromMat(matMul(matT(Wp), matMul(Rax, matMul(Wp, local[j]))));
16777
+ }
16778
+ return out;
16779
+ }
16780
+ function levelHead(qframe, Pf, excessDeg) {
16781
+ const ax = [
16782
+ Pf[I_LARM][0] - Pf[I_RARM][0],
16783
+ Pf[I_LARM][1] - Pf[I_RARM][1],
16784
+ Pf[I_LARM][2] - Pf[I_RARM][2]
16785
+ ];
16786
+ const n = Math.hypot(...ax) + 1e-9;
16787
+ const Rax = rotAxis([ax[0] / n, ax[1] / n, ax[2] / n], -excessDeg / 2 * deg);
16788
+ const local = qframe.map(matFromQuat);
16789
+ const W = fkRots(local);
16790
+ const out = qframe.slice();
16791
+ for (const j of NECK_HEAD) {
16792
+ const Wp = W[PARENTS[j]];
16793
+ out[j] = quatFromMat(matMul(matT(Wp), matMul(Rax, matMul(Wp, local[j]))));
16794
+ local[j] = matFromQuat(out[j]);
16795
+ W[j] = matMul(Wp, local[j]);
16796
+ }
16797
+ return out;
16798
+ }
16799
+ function headPitchFrames(localMats, s, e) {
16800
+ const vals = [];
16801
+ for (let f = s; f < e; f++) {
16802
+ const W = fkRots(localMats[f]);
16803
+ const fzY = W[NECK_HEAD[1]][5];
16804
+ vals.push(Math.asin(Math.max(-1, Math.min(1, -fzY))) / deg);
16805
+ }
16806
+ return mean(vals);
16807
+ }
16808
+ function pitched(q, theta) {
16809
+ const out = q.slice();
16810
+ const half = theta / SPINE_IDX.length / 2;
16811
+ const step = [Math.sin(half), 0, 0, Math.cos(half)];
16812
+ for (const si of SPINE_IDX) out[si] = quatMul(step, out[si]);
16813
+ return out;
16814
+ }
16815
+ function yawed(q, theta) {
16816
+ const out = q.slice();
16817
+ const half = theta / SPINE_IDX.length / 2;
16818
+ const step = [0, Math.sin(half), 0, Math.cos(half)];
16819
+ for (const si of SPINE_IDX) out[si] = quatMul(step, out[si]);
16820
+ return out;
16821
+ }
16822
+ function gripLineChar(P, heading) {
16823
+ const yaws = [], pitches = [];
16824
+ for (let f = 0; f < P.length; f++) {
16825
+ const rot = rotY(-heading[f]);
16826
+ const rel = (j) => matVec(rot, [
16827
+ P[f][j][0] - P[f][I_HIPS2][0],
16828
+ P[f][j][1] - P[f][I_HIPS2][1],
16829
+ P[f][j][2] - P[f][I_HIPS2][2]
16830
+ ]);
16831
+ const rh = rel(I_RH), lh = rel(I_LH);
16832
+ const v = [lh[0] - rh[0], lh[1] - rh[1], lh[2] - rh[2]];
16833
+ yaws.push(Math.atan2(v[0], Math.max(v[2], 1e-6)) / deg);
16834
+ pitches.push(Math.atan2(v[1], Math.hypot(v[0], v[2]) + 1e-9) / deg);
16835
+ }
16836
+ return { yaws, pitches };
16837
+ }
16838
+
16839
+ // src/lib/motion/verify.ts
16840
+ var I_HIPS3 = ji("Hips");
16841
+ var I_HEAD2 = ji("Head");
16842
+ var I_RSH = ji("RightShoulder");
16843
+ var I_LSH = ji("LeftShoulder");
16844
+ var I_RF = ji("RightFoot");
16845
+ var I_RT = ji("RightToeBase");
16846
+ var I_LF = ji("LeftFoot");
16847
+ var I_LT = ji("LeftToeBase");
16848
+ var I_NECK = ji("Neck");
16849
+ var I_RH2 = ji("RightHand");
16850
+ var I_LH2 = ji("LeftHand");
16851
+ var I_CHEST = ji("Spine2");
16852
+ var RAMP = 50;
16853
+ var TAIL = 10;
16854
+ var D = Math.sqrt(0.5);
16855
+ var EXPECT_DIR = {
16856
+ // ARDY frame: +z forward, +x LEFT of the character
16857
+ forward: [0, 1],
16858
+ back: [0, -1],
16859
+ "strafe-left": [1, 0],
16860
+ "strafe-right": [-1, 0],
16861
+ fl: [D, D],
16862
+ fr: [-D, D],
16863
+ bl: [D, -D],
16864
+ br: [-D, -D]
16865
+ };
16866
+ function classify(stemRaw) {
16867
+ const stem = stemRaw.replace(/-s\d+$/, "");
16868
+ if (stem.includes("jump") || stem.includes("ollie") || stem.includes("trick")) {
16869
+ return { kind: "oneshot", expect: null };
16870
+ }
16871
+ for (const [key, dxz] of Object.entries(EXPECT_DIR)) {
16872
+ if (stem.endsWith(`-${key}`) || stem.includes(`-run-${key}`)) return { kind: "loco", expect: dxz };
16873
+ }
16874
+ if (stem.includes("-idle") || stem.includes("stance")) return { kind: "idle", expect: null };
16875
+ if (stem.includes("push")) return { kind: "travel", expect: null };
16876
+ return { kind: "freeform", expect: null };
16877
+ }
16878
+ function mSlip(t, w) {
16879
+ const { P, fc, fps } = t;
16880
+ const vals = [];
16881
+ for (const [cA, cB, iF, iT] of [[0, 1, I_LF, I_LT], [2, 3, I_RF, I_RT]]) {
16882
+ const s = [];
16883
+ for (let f = w[0]; f < w[1] - 1; f++) {
16884
+ if (!(fc[f][cA] || fc[f][cB])) continue;
16885
+ const sp = (j) => Math.hypot(P[f + 1][j][0] - P[f][j][0], P[f + 1][j][2] - P[f][j][2]) * fps;
16886
+ s.push(Math.min(sp(iF), sp(iT)));
16887
+ }
16888
+ if (s.length < 3) continue;
16889
+ vals.push(mean(s));
16890
+ }
16891
+ return vals.length ? mean(vals) * 100 : null;
16892
+ }
16893
+ function mTravel(t, w) {
16894
+ const ts = [], xs = [], zs = [];
16895
+ for (let f = w[0]; f < w[1]; f++) {
16896
+ ts.push(f / t.fps);
16897
+ xs.push(t.root[f][0]);
16898
+ zs.push(t.root[f][2]);
16899
+ }
16900
+ const vx = lstsqLinear(ts, xs).slope;
16901
+ const vz = lstsqLinear(ts, zs).slope;
16902
+ const v = matVec(rotY(-mean(t.heading.slice(w[0], w[1]))), [vx, 0, vz]);
16903
+ const speed = Math.hypot(v[0], v[2]);
16904
+ return { speed, dir: speed > 1e-6 ? [v[0] / speed, v[2] / speed] : null };
16905
+ }
16906
+ var mHeadingStd = (t, w) => std(unwrap(t.heading.slice(w[0], w[1]))) * 180 / Math.PI;
16907
+ function mBlade(C, w) {
16908
+ let ss = 0, cs = 0, n = 0;
16909
+ for (let f = w[0]; f < w[1]; f++) {
16910
+ const shx = C[f][I_RSH][0] - C[f][I_LSH][0];
16911
+ const shz = C[f][I_RSH][2] - C[f][I_LSH][2];
16912
+ const ang = Math.atan2(shz, -shx);
16913
+ ss += Math.sin(ang);
16914
+ cs += Math.cos(ang);
16915
+ n++;
16916
+ }
16917
+ return Math.abs(Math.atan2(ss / n, cs / n)) * (180 / Math.PI);
16918
+ }
16919
+ function mSpeedCv(t, w) {
16920
+ const n = Math.max(2, Math.floor(t.fps / 2));
16921
+ const sp = [];
16922
+ for (let s = w[0]; s < w[1] - n; s += n) {
16923
+ sp.push(Math.hypot(t.root[s + n][0] - t.root[s][0], t.root[s + n][2] - t.root[s][2]) / (n / t.fps));
16924
+ }
16925
+ return sp.length >= 3 ? std(sp) / (mean(sp) + 1e-6) : null;
16926
+ }
16927
+ function mRoughness(t, w) {
16928
+ const F = w[1] - w[0];
16929
+ if (F < 5) return null;
16930
+ let accSq = 0, velSq = 0, n = 0;
16931
+ for (let f = w[0] + 1; f < w[1] - 1; f++) {
16932
+ for (let j = 0; j < t.P[f].length; j++) {
16933
+ for (let k = 0; k < 3; k++) {
16934
+ const a = t.P[f + 1][j][k] - 2 * t.P[f][j][k] + t.P[f - 1][j][k];
16935
+ const v = (t.P[f + 1][j][k] - t.P[f - 1][j][k]) / 2;
16936
+ accSq += a * a;
16937
+ velSq += v * v;
16938
+ n++;
16939
+ }
16940
+ }
16941
+ }
16942
+ const velRms = Math.max(Math.sqrt(velSq / n), 6e-3);
16943
+ return Math.sqrt(accSq / n) / velRms;
16944
+ }
16945
+ function mHeadyaw(t, w) {
16946
+ if (!t.gm) return null;
16947
+ let ss = 0, cs = 0, n = 0;
16948
+ for (let f = w[0]; f < w[1]; f++) {
16949
+ const yawOf = (idx) => {
16950
+ const m = t.gm[f][idx];
16951
+ return Math.atan2(m[2], m[8]) * 180 / Math.PI;
16952
+ };
16953
+ const dDeg = ((yawOf(I_HEAD2) - yawOf(I_HIPS3) + 180) % 360 + 360) % 360 - 180;
16954
+ const d = dDeg * Math.PI / 180;
16955
+ ss += Math.sin(d);
16956
+ cs += Math.cos(d);
16957
+ n++;
16958
+ }
16959
+ return Math.abs(Math.atan2(ss / n, cs / n) * 180 / Math.PI);
16960
+ }
16961
+ function mHeadpitch(t, w) {
16962
+ if (!t.gm) return null;
16963
+ let s = 0, n = 0;
16964
+ for (let f = w[0]; f < w[1]; f++) {
16965
+ s += t.gm[f][I_HEAD2][5];
16966
+ n++;
16967
+ }
16968
+ const v = Math.max(-1, Math.min(1, -(s / n)));
16969
+ return Math.asin(v) * 180 / Math.PI;
16970
+ }
16971
+ function mGround(t, w) {
16972
+ const feet = [I_LF, I_LT, I_RF, I_RT];
16973
+ let minAll = Infinity;
16974
+ const minOn = [];
16975
+ for (let f = w[0]; f < w[1]; f++) {
16976
+ let mn = Infinity;
16977
+ for (const j of feet) mn = Math.min(mn, t.P[f][j][1]);
16978
+ minAll = Math.min(minAll, mn);
16979
+ if (t.fc[f].some(Boolean)) minOn.push(mn);
16980
+ }
16981
+ return {
16982
+ pen: Math.max(0, -minAll) * 100,
16983
+ float: minOn.length >= 3 ? median2(minOn) * 100 : null
16984
+ };
16985
+ }
16986
+ function mHipsRatio(t, w) {
16987
+ const ys = [];
16988
+ for (let f = w[0]; f < w[1]; f++) if (t.fc[f].some(Boolean)) ys.push(t.P[f][I_HIPS3][1]);
16989
+ return ys.length >= 3 ? median2(ys) / HIPS_REST_Y : null;
16990
+ }
16991
+ function mSeam(t, w, kind) {
16992
+ if (kind === "loco" || kind === "travel") {
16993
+ const ls = [];
16994
+ for (let f = w[0] + 1; f < w[1]; f++) {
16995
+ const on = (i) => t.fc[i][0] || t.fc[i][1];
16996
+ if (on(f) && !on(f - 1)) ls.push(f);
16997
+ }
16998
+ let best2 = null;
16999
+ for (let i = 0; i < ls.length - 1; i++) {
17000
+ const s = ls[i];
17001
+ for (const e of [ls[i + 1] - 1, ls[i + 1], ls[i + 1] + 1]) {
17002
+ if (e <= s + 6 || e >= w[1]) continue;
17003
+ const d = poseDist(t.quat, s, e);
17004
+ if (best2 === null || d < best2) best2 = d;
17005
+ }
17006
+ }
17007
+ return best2 === null ? null : best2 * 180 / Math.PI;
17008
+ }
17009
+ let best = null;
17010
+ const [lo, hi] = w;
17011
+ for (let s = lo; s < hi - 30; s += 2) {
17012
+ for (let L = Math.floor(t.fps); L < Math.floor(t.fps * 3); L += 4) {
17013
+ const e = s + L;
17014
+ if (e >= hi) continue;
17015
+ const d = poseDist(t.quat, s, e);
17016
+ if (best === null || d < best) best = d;
17017
+ }
17018
+ }
17019
+ return best === null ? null : best * 180 / Math.PI;
17020
+ }
17021
+ function mOneshot(t, w) {
17022
+ let maxY = -Infinity;
17023
+ const early = [];
17024
+ let air = 0;
17025
+ for (let f = w[0]; f < w[1]; f++) {
17026
+ maxY = Math.max(maxY, t.root[f][1]);
17027
+ if (f < w[0] + 10) early.push(t.root[f][1]);
17028
+ if (!t.fc[f].some(Boolean)) air++;
17029
+ }
17030
+ return { rise: maxY - median2(early), air };
17031
+ }
17032
+ function mAimhold(C, w) {
17033
+ const hunchF = [], dyF = [], gapF = [];
17034
+ const yawSigned = [], pitchF = [], archF = [];
17035
+ const fwdSrc = [];
17036
+ const handFwd = [], rhClear = [];
17037
+ for (let f = w[0]; f < w[1]; f++) {
17038
+ const up = [
17039
+ C[f][I_HEAD2][0] - C[f][I_HIPS3][0],
17040
+ C[f][I_HEAD2][1] - C[f][I_HIPS3][1],
17041
+ C[f][I_HEAD2][2] - C[f][I_HIPS3][2]
17042
+ ];
17043
+ const upN = Math.hypot(...up) + 1e-9;
17044
+ hunchF.push(Math.acos(Math.max(-1, Math.min(1, up[1] / upN))) * 180 / Math.PI);
17045
+ const rh = C[f][I_RH2], lh = C[f][I_LH2];
17046
+ dyF.push(0.5 * (rh[1] + lh[1]) - C[f][I_HEAD2][1]);
17047
+ handFwd.push(0.5 * (rh[2] + lh[2]));
17048
+ const a = C[f][I_HIPS3], b = C[f][I_NECK], p = rh;
17049
+ const ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
17050
+ const tt = Math.max(0, Math.min(
17051
+ 1,
17052
+ ((p[0] - a[0]) * ab[0] + (p[1] - a[1]) * ab[1] + (p[2] - a[2]) * ab[2]) / (ab[0] ** 2 + ab[1] ** 2 + ab[2] ** 2 + 1e-9)
17053
+ ));
17054
+ rhClear.push(Math.hypot(p[0] - (a[0] + tt * ab[0]), p[1] - (a[1] + tt * ab[1]), p[2] - (a[2] + tt * ab[2])));
17055
+ const v = [lh[0] - rh[0], lh[1] - rh[1], lh[2] - rh[2]];
17056
+ const L = Math.hypot(...v);
17057
+ gapF.push(L);
17058
+ fwdSrc.push({ v, L });
17059
+ yawSigned.push(Math.atan2(v[0], Math.max(v[2], 1e-6)) * 180 / Math.PI);
17060
+ pitchF.push(Math.atan2(v[1], Math.hypot(v[0], v[2]) + 1e-9) * 180 / Math.PI);
17061
+ const c2 = C[f][I_CHEST], h2 = C[f][I_HEAD2];
17062
+ const ab2 = [h2[0] - a[0], h2[1] - a[1], h2[2] - a[2]];
17063
+ const t2 = ((c2[0] - a[0]) * ab2[0] + (c2[1] - a[1]) * ab2[1] + (c2[2] - a[2]) * ab2[2]) / (ab2[0] ** 2 + ab2[1] ** 2 + ab2[2] ** 2 + 1e-9);
17064
+ archF.push(c2[2] - (a[2] + t2 * ab2[2]));
17065
+ }
17066
+ const yawMed = median2(yawSigned);
17067
+ const dev = yawSigned.map((y) => Math.abs(y - yawMed));
17068
+ const r = yawMed * Math.PI / 180;
17069
+ const axis = [Math.sin(r), 0, Math.cos(r)];
17070
+ const fwdF = fwdSrc.map(({ v, L }) => (v[0] * axis[0] + v[1] * axis[1] + v[2] * axis[2]) / (L + 1e-9));
17071
+ let run2 = 0, best = 0;
17072
+ for (let i = 0; i < fwdF.length; i++) {
17073
+ const ok = dev[i] < 15 && dyF[i] < -0.22 && gapF[i] > 0.18 && gapF[i] < 0.46 && fwdF[i] > 0.75 && hunchF[i] < 16;
17074
+ run2 = ok ? run2 + 1 : 0;
17075
+ best = Math.max(best, run2);
17076
+ }
17077
+ let holdBest = 0, holdBestScore = -Infinity;
17078
+ for (let i = 0; i < fwdF.length; i++) {
17079
+ const s = fwdF[i] - dev[i] / 45 - Math.abs(gapF[i] - 0.32);
17080
+ if (s > holdBestScore) {
17081
+ holdBestScore = s;
17082
+ holdBest = i;
17083
+ }
17084
+ }
17085
+ return {
17086
+ hunch_deg: percentile(hunchF, 90),
17087
+ hand_dy_m: mean(dyF),
17088
+ hand_fwd_m: mean(handFwd),
17089
+ rh_clear_m: mean(rhClear),
17090
+ hand_gap_m: percentile(gapF, 90),
17091
+ line_fwd: mean(fwdF),
17092
+ line_yaw_deg: percentile(dev, 90),
17093
+ line_yaw_med_deg: yawMed,
17094
+ line_pitch_deg: median2(pitchF),
17095
+ arch_m: median2(archF),
17096
+ hold_run: best,
17097
+ hold_best_f: holdBest + w[0]
17098
+ };
17099
+ }
17100
+ var DEFAULT_GATES = {
17101
+ pass: 70,
17102
+ warn: 50,
17103
+ common: {
17104
+ slip: { w: 3, good: 12, bad: 45 },
17105
+ rough: { w: 2, good: 0.4, bad: 0.8 },
17106
+ pen: { w: 1, good: 1.5, bad: 8 },
17107
+ float: { w: 1, good: 4, bad: 12 },
17108
+ headyaw: { w: 2, good: 15, bad: 45 }
17109
+ },
17110
+ aim: {
17111
+ hunch: { w: 3, good: 10, bad: 18 },
17112
+ handhigh: { w: 2, good: -0.32, bad: -0.22 },
17113
+ handfwd: { w: 2, good: 0.3, bad: 0.12, invert: true },
17114
+ handclear: { w: 2, good: 0.16, bad: 0.07, invert: true },
17115
+ twohand: { w: 3, good: 0.35, bad: 0.48 },
17116
+ lineFwd: { w: 3, good: 0.88, bad: 0.75, invert: true },
17117
+ lineYaw: { w: 3, good: 8, bad: 18 },
17118
+ lineMed: { w: 2, good: 45, bad: 65 },
17119
+ holdRun: { w: 3, good: 15, bad: 6, invert: true },
17120
+ linePitch: { w: 2, good: 8, bad: 22 },
17121
+ arch: { w: 1, good: -0.1, bad: -0.16, invert: true },
17122
+ headDown: { w: 2, good: 15, bad: 30 },
17123
+ tiptoe: { w: 1, good: 1.03, bad: 1.07 }
17124
+ },
17125
+ loco: {
17126
+ dir: { w: 3, good: 0.92, bad: 0.6, invert: true },
17127
+ headstd: { w: 1, good: 8, bad: 30 },
17128
+ blade: { w: 2, good: 28, bad: 60 },
17129
+ speedcv: { w: 1, good: 0.18, bad: 0.5 },
17130
+ seam: { w: 2, good: 6, bad: 20 },
17131
+ hips: { w: 1, good: 0.82, bad: 0.55, invert: true }
17132
+ },
17133
+ idle: {
17134
+ still: { w: 2, good: 0.06, bad: 0.35 },
17135
+ blade: { w: 1, good: 30, bad: 65 },
17136
+ seam: { w: 2, good: 6, bad: 20 },
17137
+ hips: { w: 2, good: 0.82, bad: 0.55, invert: true }
17138
+ },
17139
+ oneshot: {
17140
+ hips: { w: 1, good: 0.8, bad: 0.5, invert: true }
17141
+ },
17142
+ freeform: {
17143
+ seam: { w: 1, good: 8, bad: 25 }
17144
+ }
17145
+ };
17146
+ function soft(x, good, bad, invert = false) {
17147
+ if (x === null || x === void 0) return null;
17148
+ if (invert) {
17149
+ x = -x;
17150
+ const g = -good, b = -bad;
17151
+ good = g;
17152
+ bad = b;
17153
+ }
17154
+ return Math.max(0, Math.min(1, (bad - x) / (bad - good)));
17155
+ }
17156
+ var WHY = {
17157
+ slip: { fmt: (v) => `${v.toFixed(0)} cm/s`, why: "feet skate during stance" },
17158
+ rough: { fmt: (v) => v.toFixed(2), why: "jittery motion" },
17159
+ pen: { fmt: (v) => `${v.toFixed(1)} cm`, why: "feet clip the floor" },
17160
+ float: { fmt: (v) => `${v.toFixed(1)} cm`, why: "floats above the floor" },
17161
+ headyaw: { fmt: (v) => `${v.toFixed(0)}\xB0`, why: "head/body twisted \u2014 bladed stance" },
17162
+ hunch: { fmt: (v) => `${v.toFixed(0)}\xB0`, why: "hunched marksman crouch" },
17163
+ handhigh: { fmt: (v) => `${v.toFixed(2)} m`, why: "hands at face height (cheek weld)" },
17164
+ handfwd: { fmt: (v) => `${v.toFixed(2)} m`, why: "weapon held tight to the chest" },
17165
+ handclear: { fmt: (v) => `${v.toFixed(2)} m`, why: "trigger hand hugs the torso" },
17166
+ twohand: { fmt: (v) => `${v.toFixed(2)} m`, why: "support hand off the weapon (one-handed hold)" },
17167
+ lineFwd: { fmt: (v) => v.toFixed(2), why: "support hand off the take's own barrel axis" },
17168
+ lineYaw: { fmt: (v) => `${v.toFixed(0)}\xB0`, why: "grip line wanders \u2014 no stable barrel axis" },
17169
+ lineMed: { fmt: (v) => `${v.toFixed(0)}\xB0`, why: "median line beyond bladed-stance range (cross-chest carry)" },
17170
+ holdRun: { fmt: (v) => `${v.toFixed(0)}f`, why: "no sustained clean hold segment to pluck/loop" },
17171
+ linePitch: { fmt: (v) => `${v.toFixed(0)}\xB0`, why: "grip line slopes up/down \u2014 barrel not level" },
17172
+ arch: { fmt: (v) => `${v.toFixed(2)} m`, why: "leaning-back spine bow (hips forward, chest behind)" },
17173
+ headDown: { fmt: (v) => `${v.toFixed(0)}\xB0`, why: "head tilted down at the hold" },
17174
+ tiptoe: { fmt: (v) => v.toFixed(2), why: "stands taller than rest \u2014 short-legged rigs dangle/float" },
17175
+ dir: { fmt: (v) => v.toFixed(2), why: "doesn't travel the commanded direction" },
17176
+ headstd: { fmt: (v) => `${v.toFixed(0)}\xB0`, why: "heading wanders" },
17177
+ blade: { fmt: (v) => `${v.toFixed(0)}\xB0`, why: "bladed stance" },
17178
+ speedcv: { fmt: (v) => v.toFixed(2), why: "speed surges" },
17179
+ seam: { fmt: (v) => `${v.toFixed(1)}\xB0`, why: "no clean loop boundary" },
17180
+ hips: { fmt: (v) => v.toFixed(2), why: "crouched/collapsed" },
17181
+ still: { fmt: (v) => `${v.toFixed(2)} m/s`, why: "idle drifts" }
17182
+ };
17183
+ function score(kind, m, gates = DEFAULT_GATES) {
17184
+ const subs = {};
17185
+ const reasons = [];
17186
+ const add = (name, band, val, whyKey = name) => {
17187
+ if (!band) return;
17188
+ const s = soft(val ?? null, band.good, band.bad, band.invert);
17189
+ if (s === null) return;
17190
+ subs[name] = [s, band.w];
17191
+ const info = WHY[whyKey];
17192
+ if (s < 0.5 && info && val !== null && val !== void 0) {
17193
+ reasons.push(`${name} ${info.fmt(val)} (${info.why})`);
17194
+ }
17195
+ };
17196
+ const num = (k) => typeof m[k] === "number" && Number.isFinite(m[k]) ? m[k] : null;
17197
+ add("slip", gates.common.slip, num("slip_cms"));
17198
+ add("rough", gates.common.rough, num("roughness"));
17199
+ add("pen", gates.common.pen, num("pen_cm"));
17200
+ if (num("float_cm") !== null) add("float", gates.common.float, num("float_cm"));
17201
+ if (kind !== "oneshot" && kind !== "freeform" && !m.twist_ok) {
17202
+ add("headyaw", gates.common.headyaw, num("headyaw_deg"));
17203
+ }
17204
+ if (m.aim) {
17205
+ add("hunch", gates.aim.hunch, num("hunch_deg"));
17206
+ add("handhigh", gates.aim.handhigh, num("hand_dy_m"));
17207
+ add("handfwd", gates.aim.handfwd, num("hand_fwd_m"));
17208
+ add("handclear", gates.aim.handclear, num("rh_clear_m"));
17209
+ add("twohand", gates.aim.twohand, num("hand_gap_m"));
17210
+ add("lineFwd", gates.aim.lineFwd, num("line_fwd"));
17211
+ add("lineYaw", gates.aim.lineYaw, num("line_yaw_deg"));
17212
+ const lm = num("line_yaw_med_deg");
17213
+ add("lineMed", gates.aim.lineMed, lm === null ? null : Math.abs(lm));
17214
+ add("holdRun", gates.aim.holdRun, num("hold_run"));
17215
+ const lp = num("line_pitch_deg");
17216
+ add("linePitch", gates.aim.linePitch, lp === null ? null : Math.abs(lp));
17217
+ add("arch", gates.aim.arch, num("arch_m"));
17218
+ add("headDown", gates.aim.headDown, num("headpitch_deg"));
17219
+ add("tiptoe", gates.aim.tiptoe, num("hips_ratio"));
17220
+ }
17221
+ if (kind === "loco") {
17222
+ add("dir", gates.loco.dir, num("dir_dot"));
17223
+ add("headstd", gates.loco.headstd, num("heading_std"));
17224
+ add("blade", gates.loco.blade, num("blade_deg"));
17225
+ add("speedcv", gates.loco.speedcv, num("speed_cv"));
17226
+ add("seam", gates.loco.seam, num("seam_deg"));
17227
+ add("hips", gates.loco.hips, num("hips_ratio"));
17228
+ } else if (kind === "idle" || kind === "travel") {
17229
+ if (kind === "idle") add("still", gates.idle.still, num("speed"));
17230
+ add("blade", gates.idle.blade, num("blade_deg"));
17231
+ add("seam", gates.idle.seam, num("seam_deg"));
17232
+ add("hips", gates.idle.hips, num("hips_ratio"), "hips");
17233
+ } else if (kind === "oneshot") {
17234
+ const rise = num("rise_m") ?? 0, air = num("air_frames") ?? 0;
17235
+ if (air >= 3 && rise < 0.12) {
17236
+ subs.airlie = [0, 2];
17237
+ reasons.push(`contacts say airborne x${air} but rise ${rise.toFixed(2)} m`);
17238
+ }
17239
+ add("hips", gates.oneshot.hips, num("hips_ratio"));
17240
+ } else {
17241
+ add("seam", gates.freeform.seam, num("seam_deg"));
17242
+ }
17243
+ let total = 0, wsum = 0;
17244
+ for (const [s, w] of Object.values(subs)) {
17245
+ total += s * w;
17246
+ wsum += w;
17247
+ }
17248
+ const pts = wsum ? Math.round(100 * total / wsum) : 0;
17249
+ const zeros = Object.values(subs).filter(([s]) => s === 0).map(([, w]) => w);
17250
+ const cap = zeros.some((w) => w >= 3) || zeros.length >= 2 ? "FAIL" : zeros.length ? "WARN" : null;
17251
+ const subscores = {};
17252
+ for (const [k, [s]] of Object.entries(subs)) subscores[k] = Math.round(s * 100) / 100;
17253
+ return { pts, subscores, reasons, cap };
17254
+ }
17255
+ function analyzeTake(stem, buf, gates = DEFAULT_GATES) {
17256
+ const t = loadTake(buf);
17257
+ const { kind, expect } = classify(stem);
17258
+ const F = t.F;
17259
+ const lo = kind !== "oneshot" ? Math.min(RAMP, Math.max(4, Math.floor(F / 4))) : 8;
17260
+ const w = [lo, F - Math.min(TAIL, Math.max(2, Math.floor(F / 8)))];
17261
+ const C = charFrame(t.P, t.heading);
17262
+ const m = { frames: F, fps: t.fps, kind };
17263
+ m.twist_ok = stem.startsWith("skate-");
17264
+ m.slip_cms = mSlip(t, w);
17265
+ m.roughness = mRoughness(t, w);
17266
+ const g = mGround(t, w);
17267
+ m.pen_cm = g.pen;
17268
+ m.float_cm = g.float;
17269
+ m.hips_ratio = mHipsRatio(t, w);
17270
+ m.blade_deg = mBlade(C, w);
17271
+ m.headyaw_deg = mHeadyaw(t, w);
17272
+ m.aim = /aim|rifle|pistol/.test(stem);
17273
+ if (m.aim) {
17274
+ Object.assign(m, mAimhold(C, w));
17275
+ m.headpitch_deg = mHeadpitch(t, w);
17276
+ }
17277
+ const travel = mTravel(t, w);
17278
+ m.speed = travel.speed;
17279
+ if (kind === "loco") {
17280
+ m.dir_dot = travel.dir && expect ? travel.dir[0] * expect[0] + travel.dir[1] * expect[1] : 0;
17281
+ m.heading_std = mHeadingStd(t, w);
17282
+ m.speed_cv = mSpeedCv(t, w);
17283
+ }
17284
+ if (kind === "loco" || kind === "idle" || kind === "travel" || kind === "freeform") {
17285
+ m.seam_deg = mSeam(t, w, kind);
17286
+ }
17287
+ if (kind === "oneshot") {
17288
+ const o = mOneshot(t, w);
17289
+ m.rise_m = o.rise;
17290
+ m.air_frames = o.air;
17291
+ }
17292
+ const { pts, subscores, reasons, cap } = score(kind, m, gates);
17293
+ let verdict = pts >= gates.pass ? "PASS" : pts >= gates.warn ? "WARN" : "FAIL";
17294
+ if (cap === "WARN" && verdict === "PASS") verdict = "WARN";
17295
+ else if (cap === "FAIL") verdict = "FAIL";
17296
+ const metrics = {};
17297
+ for (const [k, v] of Object.entries(m)) {
17298
+ metrics[k] = typeof v === "number" ? Math.round(v * 1e3) / 1e3 : v;
17299
+ }
17300
+ return { take: stem, kind, score: pts, verdict, reasons, metrics, subscores, prompt: t.text.slice(0, 140) };
17301
+ }
17302
+
17303
+ // src/lib/motion/compile.ts
17304
+ var deg2 = Math.PI / 180;
17305
+ var D2 = Math.sqrt(0.5);
17306
+ var DEFAULT_MOTION_CONFIG = {
17307
+ rampFrames: 50,
17308
+ tailSkip: 10,
17309
+ idleLoop: { minFrames: 40, maxFrames: 70, step: 2 },
17310
+ headGazeTargetDeg: 6,
17311
+ aimPitchDeg: 40,
17312
+ aimCandidates: 3,
17313
+ hipsClampM: 5e-3,
17314
+ minDirDot: 0.9
17315
+ };
17316
+ var GAIT_BY_SUFFIX = [
17317
+ ["forward", "forward", [0, 1]],
17318
+ ["back", "back", [0, -1]],
17319
+ ["strafe-left", "strafeLeft", [1, 0]],
17320
+ ["strafe-right", "strafeRight", [-1, 0]],
17321
+ ["fl", "forwardLeft", [D2, D2]],
17322
+ ["fr", "forwardRight", [-D2, D2]],
17323
+ ["bl", "backLeft", [D2, -D2]],
17324
+ ["br", "backRight", [-D2, -D2]]
17325
+ ];
17326
+ var round = (v, p) => {
17327
+ const m = 10 ** p;
17328
+ return Math.round(v * m) / m;
17329
+ };
17330
+ function pack(quatCyc, hipsPos, contacts, fps, speed, dxz, loop = true) {
17331
+ return {
17332
+ fps,
17333
+ frames: quatCyc.length,
17334
+ speed: round(speed, 3),
17335
+ dir: [round(dxz[0], 3), round(dxz[1], 3)],
17336
+ loop,
17337
+ localQuat: quatCyc.map((f) => f.map((q) => q.map((v) => round(v, 6)))),
17338
+ hipsPos: hipsPos.map((p) => p.map((v) => round(v, 5))),
17339
+ contacts
17340
+ };
17341
+ }
17342
+ var folded = (t) => t.fc.map((f) => [f[0] || f[1] ? 1 : 0, f[2] || f[3] ? 1 : 0]);
17343
+ function strikes(cc, col, lo, hi) {
17344
+ const out = [];
17345
+ for (let f = lo + 1; f < hi; f++) if (cc[f][col] === 1 && cc[f - 1][col] === 0) out.push(f);
17346
+ return out;
17347
+ }
17348
+ var slice2 = (a, s, e) => a.slice(s, e);
17349
+ var copyQuats = (q, s, e) => q.slice(s, e).map((f) => f.map((x) => x.slice()));
17350
+ var copyRoots = (r, s, e) => r.slice(s, e).map((v) => v.slice());
17351
+ function cutCycle(stem, t, expect, cfg) {
17352
+ const cc = folded(t);
17353
+ const lo = cfg.rampFrames, hi = t.F - cfg.tailSkip;
17354
+ const ls = strikes(cc, 0, lo, hi);
17355
+ if (ls.length < 3) {
17356
+ throw new Error(
17357
+ `${stem}: only ${ls.length} left-foot strikes in the steady window \u2014 the take never settles into a gait (longer --duration, or regenerate).`
17358
+ );
17359
+ }
17360
+ let best = null;
17361
+ for (let i = 0; i < ls.length - 1; i++) {
17362
+ const s2 = ls[i];
17363
+ for (const e2 of [ls[i + 1] - 1, ls[i + 1], ls[i + 1] + 1]) {
17364
+ if (e2 <= s2 + 6 || e2 >= hi) continue;
17365
+ const d = poseDist(t.quat, s2, e2);
17366
+ if (!best || d < best[0]) best = [d, s2, e2];
17367
+ }
17368
+ }
17369
+ if (!best) throw new Error(`${stem}: no valid strike pair for a loop cycle.`);
17370
+ const [seam, s, e] = best;
17371
+ const qc = copyQuats(t.quat, s, e);
17372
+ const rc = copyRoots(t.root, s, e);
17373
+ const { speed, dir } = deroot(qc, rc, t.fps, mean(t.heading.slice(s, e)));
17374
+ const dot = dir[0] * expect[0] + dir[1] * expect[1];
17375
+ if (dot <= cfg.minDirDot) {
17376
+ throw new Error(
17377
+ `${stem}: cycle travels (${dir[0].toFixed(2)}, ${dir[1].toFixed(2)}) vs expected (${expect[0].toFixed(2)}, ${expect[1].toFixed(2)}) \u2014 wrong direction; check the constraint/prompt.`
17378
+ );
17379
+ }
17380
+ return { track: pack(qc, rc, slice2(cc, s, e), t.fps, speed, dir), seamDeg: seam / deg2 };
17381
+ }
17382
+ function bestLoop(t, cfg) {
17383
+ const lo = 10, hi = t.F - 5;
17384
+ let best = null;
17385
+ for (let s = lo; s < hi - cfg.idleLoop.minFrames; s++) {
17386
+ for (let L = cfg.idleLoop.minFrames; L <= cfg.idleLoop.maxFrames; L += cfg.idleLoop.step) {
17387
+ const e = s + L;
17388
+ if (e >= hi) continue;
17389
+ const d = poseDist(t.quat, s, e);
17390
+ if (!best || d < best[0]) best = [d, s, e];
17391
+ }
17392
+ }
17393
+ if (!best) throw new Error("take too short for an idle loop window");
17394
+ return { seam: best[0], s: best[1], e: best[2] };
17395
+ }
17396
+ function compileIdle(stem, t, aim, cfg) {
17397
+ const log = [];
17398
+ const { seam, s, e } = bestLoop(t, cfg);
17399
+ const qc = copyQuats(t.quat, s, e);
17400
+ const rc = copyRoots(t.root, s, e);
17401
+ const cc = folded(t);
17402
+ if (!aim) {
17403
+ deroot(qc, rc, t.fps, mean(t.heading.slice(s, e)));
17404
+ return { track: pack(qc, rc, slice2(cc, s, e), t.fps, 0, [0, 0]), seamDeg: seam / deg2, log };
17405
+ }
17406
+ const { yaws, pitches } = gripLineChar(t.P, t.heading);
17407
+ const yawMed = median2(yaws.slice(s, e));
17408
+ const pitchMed = median2(pitches.slice(s, e));
17409
+ const headExcess = headPitchFrames(t.localMats, s, e) - cfg.headGazeTargetDeg;
17410
+ log.push(
17411
+ `idle root-yaw ${(-yawMed).toFixed(1)}\xB0 (whole body blades) \xB7 arm-level ${(-pitchMed).toFixed(1)}\xB0 \xB7 head-raise ${Math.max(0, headExcess).toFixed(1)}\xB0`
17412
+ );
17413
+ for (let f = 0; f < qc.length; f++) {
17414
+ qc[f] = levelArms(qc[f], t.P[s + f], pitchMed);
17415
+ if (headExcess > 0) qc[f] = levelHead(qc[f], t.P[s + f], headExcess);
17416
+ }
17417
+ deroot(qc, rc, t.fps, mean(t.heading.slice(s, e)) + yawMed * deg2);
17418
+ const over = mean(rc.map((r) => r[1])) - HIPS_REST_Y;
17419
+ if (over > cfg.hipsClampM) {
17420
+ log.push(`hips tall-stance clamp: -${over.toFixed(3)} m`);
17421
+ for (const r of rc) r[1] -= over;
17422
+ }
17423
+ return { track: pack(qc, rc, slice2(cc, s, e), t.fps, 0, [0, 0]), seamDeg: seam / deg2, log };
17424
+ }
17425
+ function cutOneshot(t, cfg) {
17426
+ const cc = folded(t);
17427
+ const air = cc.map((c2) => c2[0] === 0 && c2[1] === 0);
17428
+ let best = null;
17429
+ let run2 = null;
17430
+ for (let f = 0; f < air.length; f++) {
17431
+ if (air[f]) run2 = run2 ? [run2[0], f] : [f, f];
17432
+ else {
17433
+ if (run2 && (!best || run2[1] - run2[0] > best[1] - best[0])) best = run2;
17434
+ run2 = null;
17435
+ }
17436
+ }
17437
+ if (run2 && (!best || run2[1] - run2[0] > best[1] - best[0])) best = run2;
17438
+ let s, e;
17439
+ if (best) {
17440
+ s = best[0];
17441
+ while (s > 8 && !air[s - 1]) s--;
17442
+ e = Math.min(t.F - 4, Math.floor(best[1] + 0.6 * t.fps));
17443
+ } else {
17444
+ s = 8;
17445
+ e = t.F - 4;
17446
+ }
17447
+ void cfg;
17448
+ const qc = copyQuats(t.quat, s, e);
17449
+ const rc = copyRoots(t.root, s, e);
17450
+ deroot(qc, rc, t.fps, mean(t.heading.slice(s, Math.min(s + 10, e))));
17451
+ return pack(qc, rc, slice2(cc, s, e), t.fps, 0, [0, 0], false);
17452
+ }
17453
+ function aimPoseTracks(takes, cfg) {
17454
+ const I_HIPS4 = ji("Hips"), I_HEAD3 = ji("Head");
17455
+ const I_RH3 = ji("RightHand"), I_LH3 = ji("LeftHand");
17456
+ const cands = [];
17457
+ for (const { stem, take: t } of takes) {
17458
+ const { yaws, pitches } = gripLineChar(t.P, t.heading);
17459
+ const lo = Math.min(cfg.rampFrames, Math.floor(t.F / 3));
17460
+ const hiY = t.F - cfg.tailSkip;
17461
+ const yawMed = median2(yaws.slice(lo, hiY));
17462
+ const handVel = [];
17463
+ for (let f = 0; f < t.F - 1; f++) {
17464
+ const sp = (j) => Math.hypot(
17465
+ t.P[f + 1][j][0] - t.P[f][j][0],
17466
+ t.P[f + 1][j][1] - t.P[f][j][1],
17467
+ t.P[f + 1][j][2] - t.P[f][j][2]
17468
+ ) * t.fps;
17469
+ handVel.push(Math.max(sp(I_RH3), sp(I_LH3)));
17470
+ }
17471
+ for (let f = lo; f < hiY; f++) {
17472
+ const rot = rotY(-t.heading[f]);
17473
+ const rel = (j) => matVec(rot, [
17474
+ t.P[f][j][0] - t.P[f][I_HIPS4][0],
17475
+ t.P[f][j][1] - t.P[f][I_HIPS4][1],
17476
+ t.P[f][j][2] - t.P[f][I_HIPS4][2]
17477
+ ]);
17478
+ const rh = rel(I_RH3), lh = rel(I_LH3), head = rel(I_HEAD3), hips = rel(I_HIPS4);
17479
+ const up = [head[0] - hips[0], head[1] - hips[1], head[2] - hips[2]];
17480
+ const hunch = Math.acos(Math.max(-1, Math.min(1, up[1] / (Math.hypot(...up) + 1e-9)))) / deg2;
17481
+ const v = [lh[0] - rh[0], lh[1] - rh[1], lh[2] - rh[2]];
17482
+ const L = Math.hypot(...v);
17483
+ if (L < 0.15) continue;
17484
+ const yaw = Math.atan2(v[0], Math.max(v[2], 1e-6)) / deg2;
17485
+ const ry = yawMed * deg2;
17486
+ const fwdN = (v[0] * Math.sin(ry) + v[2] * Math.cos(ry)) / L;
17487
+ const dy = 0.5 * (rh[1] + lh[1]) - head[1];
17488
+ const score2 = fwdN - Math.abs(yaw - yawMed) / 30 - 4 * Math.max(0, dy + 0.25) - 2.5 * Math.abs(L - 0.32) - Math.max(0, hunch - 14) / 10 - 0.5 * Math.min(handVel[Math.min(f, handVel.length - 1)], 1);
17489
+ cands.push({ score: score2, stem, f, take: t, yaw, pitch: pitches[f] });
17490
+ }
17491
+ }
17492
+ cands.sort((a, b) => b.score - a.score);
17493
+ const picked = [];
17494
+ for (const c2 of cands) {
17495
+ if (picked.some((p) => p.stem === c2.stem)) continue;
17496
+ picked.push(c2);
17497
+ if (picked.length === cfg.aimCandidates) break;
17498
+ }
17499
+ const tracks = {};
17500
+ const log = [];
17501
+ const PITCH = cfg.aimPitchDeg * deg2;
17502
+ picked.forEach((c2, i) => {
17503
+ log.push(
17504
+ `aim${i}: ${c2.stem} frame ${c2.f} score ${c2.score >= 0 ? "+" : ""}${c2.score.toFixed(2)} spine-yaw ${(-c2.yaw).toFixed(1)}\xB0 arm-level ${(-c2.pitch).toFixed(1)}\xB0`
17505
+ );
17506
+ let q = levelArms(c2.take.quat[c2.f].map((x) => x.slice()), c2.take.P[c2.f], c2.pitch);
17507
+ const hx = headPitchFrames(c2.take.localMats, c2.f, c2.f + 1) - cfg.headGazeTargetDeg;
17508
+ if (hx > 0) q = levelHead(q, c2.take.P[c2.f], hx);
17509
+ q = yawed(q, -c2.yaw * deg2);
17510
+ const hip = [0, c2.take.P[c2.f][ji("Hips")][1], 0];
17511
+ for (const [suffix, qv] of [["", q], ["Up", pitched(q, -PITCH)], ["Dn", pitched(q, PITCH)]]) {
17512
+ tracks[`aim${i}${suffix}`] = pack([qv], [hip], [[0, 0]], 30, 0, [0, 0]);
17513
+ }
17514
+ });
17515
+ return { tracks, log };
17516
+ }
17517
+ function compileSet(inputs, setName, cfg = DEFAULT_MOTION_CONFIG) {
17518
+ const log = [];
17519
+ const gaits = {};
17520
+ const aimTakes = [];
17521
+ for (const { stem, take } of inputs) {
17522
+ const { kind } = classify(stem);
17523
+ const aim = /aim|rifle|pistol/.test(stem);
17524
+ if (aim && (kind === "idle" || kind === "freeform")) aimTakes.push({ stem, take });
17525
+ if (kind === "loco") {
17526
+ const hit = GAIT_BY_SUFFIX.find(
17527
+ ([suffix]) => stem.replace(/-s\d+$/, "").endsWith(`-${suffix}`) || stem.includes(`-run-${suffix}`)
17528
+ );
17529
+ if (!hit) throw new Error(`${stem}: locomotion take with no recognizable direction suffix.`);
17530
+ const { track, seamDeg } = cutCycle(stem, take, hit[2], cfg);
17531
+ gaits[hit[1]] = track;
17532
+ log.push(`${hit[1]}: ${track.frames} frames speed ${track.speed} m/s seam ${seamDeg.toFixed(1)}\xB0`);
17533
+ } else if (kind === "idle") {
17534
+ const { track, seamDeg, log: l } = compileIdle(stem, take, aim, cfg);
17535
+ gaits.idle = track;
17536
+ log.push(`idle: ${track.frames} frames seam ${seamDeg.toFixed(1)}\xB0`, ...l);
17537
+ } else if (kind === "oneshot") {
17538
+ const clean2 = stem.replace(/-s\d+$/, "");
17539
+ const name = /jump.*run|run.*jump/.test(clean2) ? "jumpRun" : /jump/.test(clean2) ? "jumpStand" : clean2.replace(/[^a-zA-Z0-9]+(.)/g, (_, ch) => ch.toUpperCase());
17540
+ const track = cutOneshot(take, cfg);
17541
+ gaits[name] = track;
17542
+ log.push(`${name}: ${track.frames} frames (one-shot)`);
17543
+ } else if (kind === "travel" || kind === "freeform") {
17544
+ const { track, seamDeg } = compileIdle(stem, take, false, cfg);
17545
+ const name = stem.replace(/-s\d+$/, "").replace(/[^a-zA-Z0-9]+(.)/g, (_, ch) => ch.toUpperCase());
17546
+ gaits[name] = track;
17547
+ log.push(`${name}: ${track.frames} frames seam ${seamDeg.toFixed(1)}\xB0 (freeform loop)`);
17548
+ }
17549
+ }
17550
+ if (aimTakes.length > 0) {
17551
+ const { tracks, log: l } = aimPoseTracks(aimTakes, cfg);
17552
+ Object.assign(gaits, tracks);
17553
+ log.push(...l);
17554
+ }
17555
+ return {
17556
+ data: {
17557
+ version: 1,
17558
+ set: setName,
17559
+ skeleton: "core27",
17560
+ joints: [...JOINTS],
17561
+ parents: [...PARENTS],
17562
+ restOffsets: REST_OFFSETS.map((o) => [...o]),
17563
+ restPositions: REST_POSITIONS.map((o) => [...o]),
17564
+ hipsRestY: HIPS_REST_Y,
17565
+ gaits
17566
+ },
17567
+ log
17568
+ };
17569
+ }
17570
+
17571
+ // src/lib/motion/constraints.ts
17572
+ var FPS = 20;
17573
+ var RAMP_S = 1.5;
17574
+ var WAYPOINT_EVERY = 10;
17575
+ var FIRST_FRAME = 10;
17576
+ var D3 = Math.sqrt(0.5);
17577
+ var MOTION_DIRECTIONS = {
17578
+ forward: [0, 1],
17579
+ back: [0, -1],
17580
+ "strafe-left": [1, 0],
17581
+ "strafe-right": [-1, 0],
17582
+ "forward-left": [D3, D3],
17583
+ "forward-right": [-D3, D3],
17584
+ "back-left": [D3, -D3],
17585
+ "back-right": [-D3, -D3]
17586
+ };
17587
+ function distAt(t, speed) {
17588
+ if (t <= RAMP_S) return 0.5 * speed * t * t / RAMP_S;
17589
+ return 0.5 * speed * RAMP_S + speed * (t - RAMP_S);
17590
+ }
17591
+ function directionConstraint(dir, speed, durationS) {
17592
+ const frames = [];
17593
+ const pts = [];
17594
+ const nFrames = Math.floor(durationS * FPS);
17595
+ for (let f = FIRST_FRAME; f <= nFrames; f += WAYPOINT_EVERY) {
17596
+ const t = f / FPS;
17597
+ const d = distAt(t, speed);
17598
+ frames.push(f);
17599
+ pts.push([Math.round(dir[0] * d * 1e4) / 1e4, Math.round(dir[1] * d * 1e4) / 1e4]);
17600
+ }
17601
+ return [
17602
+ {
17603
+ type: "root2d",
17604
+ frame_indices: frames,
17605
+ root_2d: pts,
17606
+ global_root_heading: frames.map(() => 0)
17607
+ }
17608
+ ];
17609
+ }
17610
+
17611
+ // src/commands/motion.ts
17612
+ var MOTION_USAGE = `${c.bold("Options for `motion` (custom character animations \u2014 text \u2192 CoreSkeleton27 takes \u2192 compiled set)")}
17613
+ motion gen "<prompt>" Generate N raw motion takes + the objective verify
17614
+ report on the GPU service (bills per take; default 4).
17615
+ [--takes 1-8] [--duration 2-10 s] [--seeds 0,1,2]
17616
+ [--constraints file.json] [--no-wait]
17617
+ motion verify <takes\u2026> LOCALLY score .npz takes/dirs against the objective
17618
+ gates (same math the service ran \u2014 reproducible).
17619
+ [--gates gates.json] [--json]
17620
+ motion compile <takes\u2026> --out src/motion/<set>.json \u2014 cut loop cycles,
17621
+ deroot, grip-line normalize, pack the game-ready set.
17622
+ Take FILENAMES drive routing (rifle-run-forward.npz,
17623
+ rifle-idle.npz, jump-stand.npz\u2026). [--set <name>]
17624
+ [--config motion.config.json]
17625
+ motion install Vendor the motion runtime INTO this game: retarget
17626
+ formulas (src/motion/rigs.js), the clip player
17627
+ (anim-runtime.js), and motion.config.json \u2014 files the
17628
+ agent then owns and can edit. [--set rifle: also drop
17629
+ the proven rifle starter set] [--force]
17630
+ motion constraints Write an ARDY root-2d constraint JSON (heading-pinned
17631
+ straight run) for gen. [--dir forward|back|strafe-left|
17632
+ strafe-right|forward-left|\u2026] [--speed 2.5] [--duration 5]
17633
+ [--out constraints.json]`;
17634
+ async function motionGen(opts, log) {
17635
+ const prompt = opts.query?.trim();
17636
+ if (!prompt) {
17637
+ log.error(`Missing prompt. Usage: ${c.cyan('genex motion gen "<prompt>"')}`);
17638
+ process.exitCode = 1;
17639
+ return;
17640
+ }
17641
+ const generationOptions = {};
17642
+ if (opts.takes !== void 0) generationOptions.takes = opts.takes;
17643
+ if (opts.duration !== void 0) generationOptions.duration = opts.duration;
17644
+ if (opts.seeds !== void 0) {
17645
+ const seeds = opts.seeds.split(",").map((s) => Number(s.trim()));
17646
+ if (seeds.length === 0 || seeds.length > 8 || seeds.some((s) => !Number.isInteger(s) || s < 0)) {
17647
+ log.error(`Invalid --seeds value: ${opts.seeds} (expected 1-8 comma-separated non-negative integers).`);
17648
+ process.exitCode = 1;
17649
+ return;
17650
+ }
17651
+ generationOptions.seeds = seeds;
17652
+ }
17653
+ if (opts.constraintsPath !== void 0) {
17654
+ try {
17655
+ const raw = await fs18.readFile(opts.constraintsPath, "utf8");
17656
+ generationOptions.constraints = JSON.parse(raw);
17657
+ } catch {
17658
+ log.error(`Couldn't read the --constraints JSON at ${opts.constraintsPath}.`);
17659
+ process.exitCode = 1;
17660
+ return;
17661
+ }
17662
+ }
17663
+ const genOpts = {
17664
+ prompt,
17665
+ generationOptions,
17666
+ apiUrl: opts.apiUrl,
17667
+ envPath: opts.envPath,
17668
+ token: opts.token,
17669
+ noWait: opts.noWait,
17670
+ json: opts.json,
17671
+ quiet: opts.quiet,
17672
+ open: opts.open
17673
+ };
17674
+ await runGenerate("motion", genOpts);
17675
+ }
17676
+ async function expandTakes(selectors) {
17677
+ const out = [];
17678
+ for (const sel of selectors) {
17679
+ const st = await fs18.stat(sel).catch(() => null);
17680
+ if (st?.isDirectory()) {
17681
+ const names = await fs18.readdir(sel);
17682
+ for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path18.join(sel, n));
17683
+ } else if (st?.isFile()) {
17684
+ out.push(sel);
17685
+ } else {
17686
+ throw new Error(`No such take file or directory: ${sel}`);
17687
+ }
17688
+ }
17689
+ return out;
17690
+ }
17691
+ function mergeGates(base, patch) {
17692
+ const out = structuredClone(base);
17693
+ for (const [k, v] of Object.entries(patch)) {
17694
+ if (k === "pass" || k === "warn") {
17695
+ if (typeof v === "number") out[k] = v;
17696
+ } else if (v && typeof v === "object" && k in out) {
17697
+ const section = out[k];
17698
+ for (const [name, band] of Object.entries(v)) {
17699
+ if (band && typeof band === "object") {
17700
+ section[name] = { ...section[name], ...band };
17701
+ }
17702
+ }
17703
+ }
17704
+ }
17705
+ return out;
17706
+ }
17707
+ async function motionVerify(opts, log) {
17708
+ const selectors = opts.selectors ?? [];
17709
+ if (selectors.length === 0) {
17710
+ log.error(`Missing takes. Usage: ${c.cyan("genex motion verify <take.npz\u2026|dir> [--gates gates.json] [--json]")}`);
17711
+ process.exitCode = 1;
17712
+ return;
17713
+ }
17714
+ let gates = DEFAULT_GATES;
17715
+ if (opts.gatesPath) {
17716
+ try {
17717
+ gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs18.readFile(opts.gatesPath, "utf8")));
17718
+ } catch {
17719
+ log.error(`Couldn't read the --gates JSON at ${opts.gatesPath}.`);
17720
+ process.exitCode = 1;
17721
+ return;
17722
+ }
17723
+ }
17724
+ let files;
17725
+ try {
17726
+ files = await expandTakes(selectors);
17727
+ } catch (err) {
17728
+ log.error(err instanceof Error ? err.message : String(err));
17729
+ process.exitCode = 1;
17730
+ return;
17731
+ }
17732
+ if (files.length === 0) {
17733
+ log.error("No .npz takes found.");
17734
+ process.exitCode = 1;
17735
+ return;
17736
+ }
17737
+ const reports = [];
17738
+ for (const file of files) {
17739
+ const stem = path18.basename(file).replace(/\.npz$/, "");
17740
+ try {
17741
+ reports.push(analyzeTake(stem, await fs18.readFile(file), gates));
17742
+ } catch (err) {
17743
+ reports.push({
17744
+ take: stem,
17745
+ kind: "?",
17746
+ score: 0,
17747
+ verdict: "ERROR",
17748
+ reasons: [err instanceof Error ? err.message : String(err)],
17749
+ metrics: {},
17750
+ subscores: {},
17751
+ prompt: ""
17752
+ });
17753
+ }
17754
+ }
17755
+ reports.sort((a, b) => a.score - b.score);
17756
+ if (opts.json) {
17757
+ writeJson(reports);
17758
+ return;
17759
+ }
17760
+ const wide = Math.max(...reports.map((r) => r.take.length));
17761
+ for (const r of reports) {
17762
+ const why = r.reasons.length ? ` \u2014 ${r.reasons.join("; ")}` : "";
17763
+ log.plain(`${r.verdict.padEnd(5)} ${String(r.score).padStart(3)} ${r.take.padEnd(wide)} [${r.kind}]${why}`);
17764
+ }
17765
+ log.plain("");
17766
+ log.dim(" The report is data, not a verdict on the verb \u2014 pick the takes whose motion");
17767
+ log.dim(" reads right, then: genex motion compile <picked\u2026> --out src/motion/<set>.json");
17768
+ }
17769
+ async function motionCompile(opts, log) {
17770
+ const selectors = opts.selectors ?? [];
17771
+ if (selectors.length === 0 || !opts.out) {
17772
+ log.error(`Usage: ${c.cyan("genex motion compile <take.npz\u2026|dir> --out src/motion/<set>.json [--config motion.config.json]")}`);
17773
+ process.exitCode = 1;
17774
+ return;
17775
+ }
17776
+ let cfg = DEFAULT_MOTION_CONFIG;
17777
+ if (opts.configPath) {
17778
+ try {
17779
+ const patch = JSON.parse(await fs18.readFile(opts.configPath, "utf8"));
17780
+ cfg = { ...DEFAULT_MOTION_CONFIG, ...patch, idleLoop: { ...DEFAULT_MOTION_CONFIG.idleLoop, ...patch.idleLoop } };
17781
+ } catch {
17782
+ log.error(`Couldn't read the --config JSON at ${opts.configPath}.`);
17783
+ process.exitCode = 1;
17784
+ return;
17785
+ }
17786
+ }
17787
+ let files;
17788
+ try {
17789
+ files = await expandTakes(selectors);
17790
+ } catch (err) {
17791
+ log.error(err instanceof Error ? err.message : String(err));
17792
+ process.exitCode = 1;
17793
+ return;
17794
+ }
17795
+ const inputs = [];
17796
+ for (const file of files) {
17797
+ const stem = path18.basename(file).replace(/\.npz$/, "");
17798
+ try {
17799
+ inputs.push({ stem, take: loadTake(await fs18.readFile(file)) });
17800
+ } catch (err) {
17801
+ log.error(`${stem}: ${err instanceof Error ? err.message : String(err)}`);
17802
+ process.exitCode = 1;
17803
+ return;
17804
+ }
17805
+ }
17806
+ const setName = opts.set ?? path18.basename(opts.out).replace(/\.json$/, "");
17807
+ let result;
17808
+ try {
17809
+ result = compileSet(inputs, setName, cfg);
17810
+ } catch (err) {
17811
+ log.error(err instanceof Error ? err.message : String(err));
17812
+ process.exitCode = 1;
17813
+ return;
17814
+ }
17815
+ const trackCount = Object.keys(result.data.gaits).length;
17816
+ if (trackCount === 0) {
17817
+ log.error("No tracks compiled \u2014 check the take filenames (routing is by stem: rifle-run-forward, rifle-idle, jump-stand\u2026).");
17818
+ process.exitCode = 1;
17819
+ return;
17820
+ }
17821
+ await fs18.mkdir(path18.dirname(path18.resolve(opts.out)), { recursive: true });
17822
+ const json = JSON.stringify(result.data);
17823
+ await fs18.writeFile(opts.out, json);
17824
+ if (opts.json) {
17825
+ writeJson({ out: opts.out, set: setName, tracks: Object.keys(result.data.gaits), bytes: json.length });
17826
+ return;
17827
+ }
17828
+ for (const line of result.log) log.dim(` ${line}`);
17829
+ log.success(`Wrote ${opts.out} (${(json.length / 1e3).toFixed(0)} KB, ${trackCount} tracks).`);
17830
+ log.dim(" Play it with the vendored runtime (genex motion install) \u2014 see the genex-ai-character skill's motion reference.");
17831
+ }
17832
+ var MOTION_RUNTIME_FILES = [
17833
+ "rigs.js",
17834
+ "anim-runtime.js",
17835
+ "ik.js",
17836
+ "motion.config.json"
17837
+ ];
17838
+ var MOTION_PRESETS = {
17839
+ rifle: ["sets/rifle.json", "sets/jumps.json"]
17840
+ };
17841
+ var MOTION_DEST = path18.join("src", "motion");
17842
+ async function motionInstall(opts, log) {
17843
+ const srcDir = path18.join(getTemplatesDir(), "motion");
17844
+ const root = opts.cwd ?? process.cwd();
17845
+ const preset = opts.set;
17846
+ if (preset !== void 0 && !MOTION_PRESETS[preset]) {
17847
+ log.error(`Unknown motion preset "${preset}". Available: ${Object.keys(MOTION_PRESETS).join(", ")}.`);
17848
+ process.exitCode = 1;
17849
+ return;
17850
+ }
17851
+ const files = [...MOTION_RUNTIME_FILES, ...preset ? MOTION_PRESETS[preset] : []];
17852
+ log.plain(c.bold(`genex motion install${preset ? ` --set ${preset}` : ""}`));
17853
+ log.plain("");
17854
+ log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path18.sep)}`);
17855
+ let copied = 0, skipped = 0;
17856
+ try {
17857
+ for (const rel of files) {
17858
+ const dest = path18.join(root, MOTION_DEST, rel);
17859
+ const exists4 = await fs18.access(dest).then(() => true, () => false);
17860
+ if (!opts.force && exists4) {
17861
+ skipped++;
17862
+ log.dim(` skipped ${path18.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
17863
+ continue;
17864
+ }
17865
+ await fs18.mkdir(path18.dirname(dest), { recursive: true });
17866
+ await fs18.copyFile(path18.join(srcDir, rel), dest);
17867
+ copied++;
17868
+ log.dim(` ${path18.join(MOTION_DEST, rel)}`);
17869
+ }
17870
+ } catch (err) {
17871
+ log.error(`Copy failed: ${String(err)}`);
17872
+ process.exitCode = 1;
17873
+ return;
17874
+ }
17875
+ log.success(`Motion runtime ready (${copied} copied${skipped > 0 ? `, ${skipped} skipped` : ""}).`);
17876
+ log.plain("");
17877
+ log.plain(c.bold("Next steps"));
17878
+ log.plain(" 1. No new dependencies for GLB rigs (three is in the scaffold); VRM avatars also need @pixiv/three-vrm.");
17879
+ log.plain(` 2. Read the ${c.cyan("genex-ai-character")} skill's motion reference for wiring, the prompt wording ladder, and the gen \u2192 verify \u2192 compile loop.`);
17880
+ log.plain(" 3. Wiring sketch:");
17881
+ for (const line of [
17882
+ `const rig = await loadRig("./assets/character.glb", set, scene); rig.computeCorrection();`,
17883
+ `const restAnkle = captureRestAnkle(rig, scene); reanchorFeet(rig, set, scene); // BEFORE ClipSet`,
17884
+ `const clipSet = new ClipSet(rig, set, set.gaits); const anim = new Animator(clipSet);`,
17885
+ `groundCalibrate(rig, clipSet, scene, restAnkle); curlFingers(rig, scene); // once, at load`,
17886
+ `anim.setLoops({ idle: 1 - mag, ...dir8Weights(x, z, mag) }, dt); maskW = aimMaskStep(maskW, { mag, grounded, pitch, dt });`,
17887
+ `anim.setMask("aim0", maskW, pitch / 0.6); anim.update(dt); // per frame \u2014 src/motion/ is YOURS to tune`
17888
+ ]) {
17889
+ log.dim(` ${line}`);
17890
+ }
17891
+ }
17892
+ async function motionConstraints(opts, log) {
17893
+ const dirName = opts.dir ?? "forward";
17894
+ const dir = MOTION_DIRECTIONS[dirName];
17895
+ if (!dir) {
17896
+ log.error(`Unknown --dir "${dirName}". Directions: ${Object.keys(MOTION_DIRECTIONS).join(", ")}.`);
17897
+ process.exitCode = 1;
17898
+ return;
17899
+ }
17900
+ const speed = opts.speed ?? 2.5;
17901
+ const duration = opts.duration ?? 5;
17902
+ if (!(speed > 0) || !(duration >= 2 && duration <= 10)) {
17903
+ log.error("Expected --speed > 0 and --duration 2-10 seconds.");
17904
+ process.exitCode = 1;
17905
+ return;
17906
+ }
17907
+ const doc = directionConstraint(dir, speed, duration);
17908
+ const out = opts.out ?? "constraints.json";
17909
+ await fs18.writeFile(out, JSON.stringify(doc));
17910
+ if (opts.json) {
17911
+ writeJson({ out, dir: dirName, speed, duration, waypoints: doc[0].frame_indices.length });
17912
+ return;
17913
+ }
17914
+ log.success(`Wrote ${out} \u2014 ${dirName} at ${speed} m/s for ${duration}s (heading pinned +Z).`);
17915
+ log.dim(` Use it with: ${c.cyan(`genex motion gen "<gait prompt>" --constraints ${out}`)}`);
17916
+ }
17917
+ async function runMotion(opts) {
17918
+ const log = createLogger({ quiet: opts.quiet || opts.json });
17919
+ switch (opts.sub?.trim()) {
17920
+ case "gen":
17921
+ await motionGen(opts, log);
17922
+ return;
17923
+ case "verify":
17924
+ await motionVerify(opts, log);
17925
+ return;
17926
+ case "compile":
17927
+ await motionCompile(opts, log);
17928
+ return;
17929
+ case "install":
17930
+ await motionInstall(opts, log);
17931
+ return;
17932
+ case "constraints":
17933
+ await motionConstraints(opts, log);
17934
+ return;
17935
+ default: {
17936
+ const bare = createLogger({ quiet: opts.quiet });
17937
+ bare.error(
17938
+ opts.sub ? `Unknown motion command "${opts.sub}". Commands: gen, verify, compile, install, constraints.` : 'Missing motion command. Usage: genex motion <gen|verify|compile|install|constraints> \u2014 e.g. genex motion gen "soldier low-ready rifle idle".'
17939
+ );
17940
+ process.exitCode = 1;
17941
+ }
17942
+ }
17943
+ }
17944
+
16388
17945
  // src/index.ts
16389
17946
  var GEN_KINDS = /* @__PURE__ */ new Set(["model", "skybox", "sfx", "music", "voice", "texture", "image", "video"]);
16390
17947
  var HELP = `${c.bold("genex")} \u2014 set up your project's agent workspace, authorize, and publish 3D games.
@@ -16427,6 +17984,14 @@ ${c.bold("Usage")}
16427
17984
  bind library clips via --animation. No approval
16428
17985
  ceremony, no player controller pack.
16429
17986
  genex animations search "<intent>" Search Meshy's animation library by gameplay intent.
17987
+ genex motion <sub> [args] Custom character animations from text (ARDY):
17988
+ gen "<prompt>" (GPU service, bills per take) |
17989
+ verify <takes\u2026> | compile <takes\u2026> --out \u2026 |
17990
+ install [--set rifle] | constraints \u2014 the
17991
+ local toolkit + vendored runtime the agent
17992
+ owns. Catalog actions (genex animations
17993
+ search) stay the first stop; motion covers
17994
+ the verbs the catalog lacks.
16430
17995
  genex wait <id> Attach to a generation enqueued with --no-wait and
16431
17996
  print its asset URL(s) when it finishes. Safe to
16432
17997
  re-run \u2014 it never creates a new generation.
@@ -16578,6 +18143,8 @@ ${c.bold("Options for `controller character` / legacy `controller anims`")}
16578
18143
 
16579
18144
  ${UI_USAGE}
16580
18145
 
18146
+ ${MOTION_USAGE}
18147
+
16581
18148
  ${c.bold("Global")}
16582
18149
  --quiet Reduce output.
16583
18150
  -h, --help Show this help.
@@ -16626,6 +18193,10 @@ ${c.bold("Examples")}
16626
18193
  genex controller anims sword pistol
16627
18194
  genex controller touch
16628
18195
  genex controller networked-physics
18196
+ genex motion gen "soldier rifle low-ready idle, fully upright, head held high" --takes 4 --no-wait
18197
+ genex motion verify takes/ --json
18198
+ genex motion compile takes/rifle-idle.npz takes/rifle-run-forward.npz --out src/motion/rifle.json
18199
+ genex motion install --set rifle
16629
18200
  `;
16630
18201
  function parseArgs(argv) {
16631
18202
  const parsed = {
@@ -16687,7 +18258,15 @@ function parseArgs(argv) {
16687
18258
  "--candidate",
16688
18259
  "--approve-remesh",
16689
18260
  "--category",
16690
- "--limit"
18261
+ "--limit",
18262
+ // `genex motion` value flags.
18263
+ "--takes",
18264
+ "--seeds",
18265
+ "--constraints",
18266
+ "--gates",
18267
+ "--config",
18268
+ "--set",
18269
+ "--speed"
16691
18270
  ]);
16692
18271
  let i = 0;
16693
18272
  while (i < argv.length) {
@@ -16814,6 +18393,12 @@ function parseArgs(argv) {
16814
18393
  }
16815
18394
  } else if (parsed.command === "creature") {
16816
18395
  parsed.options.name = `${parsed.options.name} ${arg}`;
18396
+ } else if (parsed.command === "motion") {
18397
+ if (parsed.options.name === "gen") {
18398
+ parsed.options.query = parsed.options.query ? `${parsed.options.query} ${arg}` : arg;
18399
+ } else {
18400
+ (parsed.options.selectors ??= []).push(arg);
18401
+ }
16817
18402
  } else if (parsed.command === "animations") {
16818
18403
  parsed.options.query = parsed.options.query ? `${parsed.options.query} ${arg}` : arg;
16819
18404
  } else {
@@ -16929,6 +18514,37 @@ function applyValueFlag(options, flag, value) {
16929
18514
  case "--category":
16930
18515
  options.category = value;
16931
18516
  break;
18517
+ case "--takes": {
18518
+ const n = Number(value);
18519
+ if (!Number.isInteger(n) || n < 1 || n > 8) {
18520
+ throw new Error(`Invalid --takes value: ${value} (expected an integer 1-8)`);
18521
+ }
18522
+ options.takes = n;
18523
+ break;
18524
+ }
18525
+ case "--seeds":
18526
+ options.seeds = value;
18527
+ break;
18528
+ case "--constraints":
18529
+ options.constraintsPath = value;
18530
+ break;
18531
+ case "--gates":
18532
+ options.gatesPath = value;
18533
+ break;
18534
+ case "--config":
18535
+ options.configPath = value;
18536
+ break;
18537
+ case "--set":
18538
+ options.set = value;
18539
+ break;
18540
+ case "--speed": {
18541
+ const n = Number(value);
18542
+ if (!Number.isFinite(n) || n <= 0) {
18543
+ throw new Error(`Invalid --speed value: ${value}`);
18544
+ }
18545
+ options.speed = n;
18546
+ break;
18547
+ }
16932
18548
  case "--limit": {
16933
18549
  const n = Number(value);
16934
18550
  if (!Number.isInteger(n) || n < 1 || n > 100) {
@@ -17155,6 +18771,9 @@ async function main() {
17155
18771
  case "ui":
17156
18772
  await runUi({ ...parsed.options, sub: parsed.options.name });
17157
18773
  break;
18774
+ case "motion":
18775
+ await runMotion({ ...parsed.options, sub: parsed.options.name });
18776
+ break;
17158
18777
  case "wait":
17159
18778
  await runWait({ ...parsed.options, id: parsed.options.name });
17160
18779
  break;