fix(skills): consolidate animation-map capture reliability (#2409)

* fix(skills): pass rational fps to capture helpers

* fix(skills): batch animation map sampling

* chore(skills): refresh animation manifests

* fix(skills): parse exact animation map frame rates
This commit is contained in:
Miguel Ángel
2026-07-14 18:04:32 -04:00
committed by GitHub
parent 9ce44b6603
commit eb731b6a8a
6 changed files with 216 additions and 36 deletions
@@ -16,19 +16,23 @@
import { mkdir, writeFile } from "node:fs/promises";
import { resolve, join } from "node:path";
import { sampleTweenBboxes } from "./animation-map-sampling.mjs";
import { hyperframesPackageSpec, importPackagesOrBootstrap } from "./package-loader.mjs";
const packages = await importPackagesOrBootstrap(["@hyperframes/producer", "@hyperframes/core"], {
npmPackages: [
hyperframesPackageSpec("@hyperframes/producer"),
hyperframesPackageSpec("@hyperframes/core"),
],
});
const {
createFileServer,
createCaptureSession,
initializeSession,
closeCaptureSession,
getCompositionDuration,
} = (
await importPackagesOrBootstrap(["@hyperframes/producer"], {
npmPackages: [hyperframesPackageSpec("@hyperframes/producer")],
})
)["@hyperframes/producer"];
} = packages["@hyperframes/producer"];
const { parseFps } = packages["@hyperframes/core"];
// ─── CLI ─────────────────────────────────────────────────────────────────────
@@ -40,7 +44,9 @@ const OUT_DIR = resolve(args.out ?? ".hyperframes/anim-map");
const MIN_DUR = Number(args["min-duration"] ?? 0.15);
const WIDTH = Number(args.width ?? 1920);
const HEIGHT = Number(args.height ?? 1080);
const FPS = Number(args.fps ?? 30);
const parsedFps = parseFps(args.fps ?? 30);
if (!parsedFps.ok) die(`Invalid --fps "${args.fps ?? ""}": ${parsedFps.reason}`);
const FPS = parsedFps.value;
const COMP_DIR = resolve(args.composition);
await mkdir(OUT_DIR, { recursive: true });
@@ -77,12 +83,7 @@ try {
(_, k) => +(tw.start + ((k + 0.5) / FRAMES) * (tw.end - tw.start)).toFixed(3),
);
const bboxes = [];
for (const t of times) {
await seekTo(session, t);
const bbox = await measureTarget(session, tw.selectorHint);
bboxes.push({ t, ...bbox });
}
const bboxes = await sampleTweenBboxes(session.page, tw.selectorHint, times);
const animProps = tw.props.filter(
(p) => !["parent", "overwrite", "immediateRender", "startAt", "runBackwards"].includes(p),
@@ -205,23 +206,6 @@ async function enumerateTweens(session) {
});
}
async function measureTarget(session, selector) {
return await session.page.evaluate((sel) => {
const el = document.querySelector(sel);
if (!el) return { x: 0, y: 0, w: 0, h: 0, missing: true };
const r = el.getBoundingClientRect();
const cs = getComputedStyle(el);
return {
x: Math.round(r.x),
y: Math.round(r.y),
w: Math.round(r.width),
h: Math.round(r.height),
opacity: parseFloat(cs.opacity),
visible: cs.visibility !== "hidden" && cs.display !== "none",
};
}, selector);
}
// ─── Tween description (the key output for agents) ──────────────────────────
function describeTween(tw, props, bboxes, flags) {