HyperFrames Seam Gate 解析:用 ledger.json + 双脚本为多场景视频切点做"生成—验证"闭环
【免费下载链接】hyperframesWrite HTML. Render video. Built for agents.项目地址: https://gitcode.com/GitHub_Trending/hy/hyperframes
HyperFrames 的 motion-doctrine 技能用一套零依赖的 Node 脚本(seam-stamp.mjs生成 +seam-gate.mjs验证)把"切点处进出向量必须一致"这条运动法则从口头规范变成了可执行断言:以项目根目录的ledger.json作为向量账本(vector ledger),先由 stamp 脚本从账本生成主时间轴上的接缝(seam)代码,再由 gate 脚本驱动无头 Chrome 逐帧测量、判定 exit 0/1。读完本文,你能完整掌握ledger.json的 schema 与每个字段的取值约定、stamp/gate 两条命令的全部参数与默认值,以及 gate 报告中每一行检查(ledger、zero-overlap、z-sign-scan、carrier-*等)背后的判定阈值与测量原理。
Seam Gate 在整个 motion-doctrine 中的位置
motion-doctrine 是 HyperFrames 动画技能体系中的"总纲":它要求整片只选一个主导运动方向("the current",默认向左),并且每个切点(seam)处,前一场景如何退出决定后一场景如何进入——同轴、同向、速度匹配、两侧都切在运动中途(向量法则,见 SKILL.md 的 Part 1)。
但法则必须落到构建门禁(build gate)上才有约束力,这就是 Seam Gate 的角色。官方给出的制作顺序是:
vector ledger (ledger.json) → STAMP 主时间轴接缝 (scripts/seam-stamp.mjs) → 每个阶段选一条持续运动路线 → 构建 comps → VERIFY (scripts/seam-gate.mjs)其中手写代码只用于 Tier-A 的 morph / match-cut;由 stamp 生成的接缝"天然通过 gate"(pass by construction),因为它们是同一份账本的确定性投影。
两个脚本与运行前提
Seam Gate 是一对"生成 + 验证"脚本,位于 .claude/skills/motion-doctrine/scripts/ 目录:
- seam-stamp.mjs(143 行)— 从
ledger.json生成主时间轴的接缝代码块; - seam-gate.mjs(600 行)— 数值验证器,提供
verify与probe两个子命令。
运行前提(源自 seam-gate.md 与 seam-gate.mjs 头部注释):
- 零 npm 依赖:只需 Node ≥ 22(gate 直接使用 Node 22 内置的
WebSocket/fetch,走原始 CDP 协议)+ 一个本地 Chrome; - Chrome 自动发现顺序(见 findChrome):环境变量
CHROME_PATH→~/.cache/puppeteer/chrome-headless-shell→~/.cache/puppeteer/chrome→ macOS 系统 Chrome;找不到则报错提示设置CHROME_PATH; - 页面以1920×1080视口加载(
Emulation.setDeviceMetricsOverride),等待 HyperFrames 运行时就绪信号window.__playerReady && window.__renderReady && window.__player(见 openPage),该运行时契约与 packages/core 中注入到页面的全局对象一致。
核心命令三件套(<SKILL_DIR>指.claude/skills/motion-doctrine):
# STAMP: 从 ledger 写主接缝块(base sets + 全部 wrapper tweens) node <SKILL_DIR>/scripts/seam-stamp.mjs --ledger ledger.json --write index.html # verify: 验证 ledger 中的每一个 seam(exit 0 = 门禁通过) node <SKILL_DIR>/scripts/seam-gate.mjs verify --ledger ledger.json --project <project-dir> # 复用已在运行的 preview server(改完 comp 后要重启它——bundle 会过期!) node <SKILL_DIR>/scripts/seam-gate.mjs verify --ledger ledger.json --url http://localhost:5244 # probe: 发现某切点时刻附近的所有运动元素(用于编写/修正 ledger 行) node <SKILL_DIR>/scripts/seam-gate.mjs probe --t 44.8 --project <project-dir>两种服务模式的取舍(源码见 ensureServer):
| 模式 | 行为 | 适用场景 |
|---|---|---|
--project <dir>(推荐) | 随机取 5380–5399 端口,npx --yes hyperframes preview --foreground --no-open --port <port>起一个全新preview server,等待/api/projects就绪(120s 超时),结束后自动 kill | 避免旧 bundle 假象;且启动时显式删除环境变量HYPERFRAME_RUNTIME_URL——源码注释指出:该变量取值错误时会让请求"静默失败成 200 HTML",必须保证采样到的是真实构建产物 |
--url <preview-url> | 复用你正在运行的 server | 快速迭代时复用 IDE 预览;comp 有编辑后必须重启 server,否则你验证的是过期构建 |
server 启动后,gate 会请求/api/projects解析第一个项目 id,最终导航到/api/projects/<id>/preview/comp/index.html这个 comp 预览页。验证完成后,退出码语义为:任一检查 FAIL → exit 1;全部 PASS/WARN → exit 0;参数/环境错误 → exit 2(见 main),因此可以直接串进 CI。--json输出机器可读的完整结果数组,--fps默认 30(ledger.json顶层的fps优先)。
STAMP:把账本行编译成主时间轴代码
node <SKILL_DIR>/scripts/seam-stamp.mjs --ledger ledger.json --write index.htmlstamp 的输出是替换/插入index.html中// <seams:auto> … // </seams:auto>标记之间的代码块(带"do not hand-edit"头注释和再生成命令)。若标记不存在,它会定位window.__timelines["main"] = tl;注册行并插在其后——定位不到两者之一则直接抛错(见 写入逻辑)。这正是 changelog-video 技能 模板骨架中预留接缝块的原因:该技能的 build spec 明确要求主时间轴必须命名为tl,因为 stamp 生成的都是tl.to / tl.set调用。
账本里每条 seam 的可选 stamp 参数
| 参数 | 含义 | 默认值(源码核对) |
|---|---|---|
exit.dur/entry.dur | 覆盖出/入时长 | X/Y seam:exit0.34s/ entry0.42s;Z seam:exit0.21s/ entry0.5s(seam-stamp.mjs#L92-L120) |
entry.travel | 入场起始偏移量(xPercent/yPercent) | 10;想要"软入场"观感用8 |
blur | Z seam 的模糊峰值(px) | 18(整帧 zoom);文字缩放场景用10 |
生成代码的两类形态
stamp 会先做静态账本自检:同一行的exit.axis/dir与entry.axis/dir不一致时直接抛错mismatched … fix the PLAN, not the stamp——与 SKILL 中"修计划,别修缓动"的口号对应(seam-stamp.mjs#L85-L90)。
X/Y seam(power3.in出 +power4.out入的镜像缓动对):
tl.to("#el-hook", { xPercent: -12, autoAlpha: 0, duration: 0.34, ease: "power3.in" }, cut - 0.34); tl.set("#el-hook", { autoAlpha: 0 }, cut); // 硬切:切点帧直接归零,杜绝叠化 tl.fromTo("#el-claim", { xPercent: 10, autoAlpha: 0.35 }, { xPercent: 0, autoAlpha: 1, duration: 0.42, ease: "power4.out", immediateRender: false }, cut);注意三处设计:出方在cut - dur就启动、切点帧仍在运动中("cut mid-motion");tl.set在切点把出方强制隐藏(保证 gate 的 zero-overlap 检查);入方fromTo起点带 0.35 透明度且immediateRender: false,避免时间轴注册瞬间就把未轮到的场景画出来。
Z seam(scale = "Z 轴",blur 作为景深伴随效果):出方向dir=-1(pull)收到scale 0.8、dir=+1(push)推到scale 1.18,均配power3.in+blur(18px)+autoAlpha→0(none缓动);入方fromTo从超规格状态起飞——pull 从scale 1.25("以更大的规模抵达")、push 从scale 0.78,落点scale 1.0、expo.out、透明度 0.15→1、blur 归零。这些预置状态同时写进文件开头的 basegsap.set清单(见 场景清单与 Z 预置):第一个场景可见,其余autoAlpha: 0,Z 入场元素额外携带起始 scale 与 blur。
match-cut / morph 行只生成两个可见性 set(切点帧出方autoAlpha: 0、入方autoAlpha: 1)加一行注释:载体交接(carrier handoff)是 Tier-A 工作,必须手写,但载体行必须保留在ledger.json里供 gate 检查位置连续性。
ledger.json:向量账本的数据格式
ledger.json放在项目根目录,一个 seam 一行,"向量账本即数据"(the vector ledger as data)。完整示例(原文档示例,可直接作为起步模板):
{ "fps": 30, "seams": [ { "id": "hook→claim", "cut": 4.2, "technique": "cut-the-curve LEFT", "exit": { "selector": "#el-hook", "axis": "x", "dir": -1 }, "entry": { "selector": "#el-claim", "axis": "x", "dir": -1 } }, { "id": "claim→payoff", "cut": 10.2, "technique": "inverse zoom-through", "exit": { "selector": "#el-claim", "axis": "z", "dir": -1 }, "entry": { "selector": "#el-payoff", "axis": "z", "dir": -1, "scanRoot": "#el-payoff" } }, { "id": "ui→player (match cut)", "cut": 60.6, "type": "match-cut", "carrier": { "out": "#resting-card", "in": "#product-video" } } ] }字段约定(与源码解析逻辑逐一对应):
cut— 主时间轴(master clock)上的秒数,即"入方点火"的那一帧;type—"cut"(默认,做完整向量检查)、"match-cut"/"morph"(只查载体连续性 + overlap;运动允许恰好在边界开始);axis—"x"、"y"或"z"(z 即 scale)。dir是运动符号:x 轴 −1 = 向左;y 轴 −1 = 向上;z 轴 +1 = push(变大)、−1 = pull(变小)。gate 与 stamp 都按此约定把axis映射到xPercent/yPercent/scale;selector— 真正承载接缝运动的元素。主时间轴动的是 wrapper 时用 wrapper 选择器;接缝运动写在 sub-comp 内部时用 comp 内 hero(id 或[data-hf-id=…])。probe子命令会告诉你该用哪个;entry.scanRoot(Z seam)— 用于扫描"入场元素内部自带入场动效是否在和 Z 符号打架"的子树根,默认取 entry selector;carrier— 普通"cut"行可选;match-cut/morph 行必填。out/in两个 rect 必须在切点 ±1 帧处匹配(中心 12px / 尺寸 5%容差,且祖先 transform 自动计入——因为测量走getBoundingClientRect)。
technique是给人看的标注(如cut-the-curve LEFT、inverse zoom-through),不参与判定。
VERIFY:每条检查行到底在断言什么
verify对每个 seam 在四个时刻采样:cut-0.1s、cut-1帧、cut+1帧、cut+0.1s(dt = 1/fps,fps 取自 ledger 顶层,缺省 30)。页内 harness(__seamGate)的测量原语(HARNESS)值得单独说明,因为它决定了所有判定口径:
- 累计不透明度
cumOp:沿祖先链连乘opacity,途中遇到display:none或visibility:hidden直接归 0; - rect 测量:
getBoundingClientRect()取中心点(x/y 速度)与宽度比es = rect.width / layoutWidth(z 速度)。由于getBoundingClientRect天然包含祖先 wrapper 的 transform,外层 scale 无需单独补偿——这正是原文档最后一句"Velocities are measured ongetBoundingClientRect(x/y 用中心,z 用 width-ratio),祖先 transform 自动计入"的实现依据; - 可见性判定:累计 opacity >
0.04且面积 > 16px² 且在 1920×1080 屏内。
判定阈值常量(seam-gate.mjs#L39-L44):
const VIS = 0.04; // 累计不透明度低于此值 = 不可见 const EPS_XY = 15; // px/s —— 慢于此视为"静止"(exit 没在动 / entry 从静止起) const EPS_Z = 0.04; // effective-scale 单位/s const SPEED_RATIO = 3; // 入/出速度比超过 3:1(或 <1:3)→ WARN const CARRIER_POS_TOL = 12; // 载体中心偏移容差 px const CARRIER_SIZE_TOL = 0.05; // 载体尺寸差容差 5%原文档给出的"报告行 ↔ 规则"对照表,结合源码的完整解读如下:
| 报告行 | 对应法则 | 源码中的判定逻辑 |
|---|---|---|
ledger | 计划内一致性:exit/entry 向量(axis + dir)必须匹配 | 纯静态比较,运行采样前执行;不匹配即 FAIL 并报"mirrored/mixed vector in the PLAN"(verify) |
exit-moving/entry-moving | 规则 1/3——不许"已静止再切",不许"从静止起入" | 双侧速度绝对值须 ≥ EPS(15 px/s 或 0.04 es/s),否则报 "exit settled before the boundary" / "entry starts from rest" |
exit-visible/entry-visible | 采样窗口内该侧必须真实可见 | 出方在cut-0.1s可见、入方在cut+0.1s可见,否则报具体 op 值 |
exit-direction/entry-direction | 规则 3——实测符号必须等于账本符号 | sgn(velocity) !== dir即 FAIL;z 轴会额外标注 "(mirrored zoom)" |
speed-match(WARN) | 向量法则第 3 条——入场初速 ≈ 出场末速 | 速度比 >3 或 <1/3 才 WARN("want ~1"),不阻断门禁 |
zero-overlap | 规则 6——每一帧只允许一侧可见,切点不是叠化 | 入方在cut-1帧或出方在cut+1帧仍可见即 FAIL,报文中点明"reads as a dissolve" |
z-sign-scan | 规则 7——新场景自带的入场不得与接缝 Z 符号相斗 | 对scanRoot子树(上限 900 元素、忽略 <32px 与 op≤0.1 的元素)在cut+1帧 → cut+0.1s间测 scale 速度,符号与entry.dir相反的逐个列出(最多前 5 个) |
carrier-position/carrier-size/carrier | 规则 3/4——载体 rect 连续性,祖先 scale 计入 | 切点两侧载体中心差 ≤12px 且宽度差 ≤5% 才 PASS,报实测 Δpos/Δsize;match-cut/morph 无 carrier 行会 WARN "nothing to verify" |
输出形态:人读模式下每个 seam 一块(■ id (cut @Xs, type) ✓/✗ N FAIL+ 逐行PASS/WARN/FAIL check detail),末尾汇总SEAM GATE: PASSED/FAILED — N fail, M warn across K seams并据此定退出码;--json则直接 dump 全部结果数组。
一个典型失败案例的排障链路(结合 SKILL 中的经验):zero-overlapFAIL 的常见根因是 clip-gating——data-start早于入场 tween 的 clip 会在初始不透明度下被提前 un-hide。修复口径:初始autoAlpha: 0且data-start= 切点时间,不要更早。此外记住"编辑重开接缝":任何对场景首/尾约 1 秒的改动(包括按新配音重排时间)都使该边界审计失效,必须重跑 verifier。
PROBE:切点附近的"运动元素探测器"
编写账本行时最常遇到的问题是:这个切点真正在动的是哪个元素、符号是什么?probe就是为此设计的:
node <SKILL_DIR>/scripts/seam-gate.mjs probe --t 44.8 --project <project-dir>工作原理(probe):在#root下做四次全树扫描(t-±window与t±1帧,--window默认 0.1s),两两配对求vx(中心 x 速度)、vy(中心 y)、vscale(宽度比速度)与透明度变化,过滤掉静止元素(阈值同 EPS),按速度量级降序取前 14 个输出,并附上人类语义提示:
PROBE @ 44.8s (window ±0.1s, 1f = 0.033s) — OUTGOING side (44.70 → 44.77) — movers: #el-claim vx -820 vy 0 vscale 0.000 op 1.00→0.41 (640×280) ... Use these selectors + signs to write the ledger row (x-: left, y-: up, scale+: push, scale-: pull).拿到 selector 与符号后照抄进ledger.json即可——这条"probe 发现 → ledger 落行 → stamp 生成 → verify 关闭"的循环,就是 Seam Gate 的日常使用闭环。
小结:门禁、账本与"修计划不修缓动"
把 seam-gate.md 放回上下文,Seam Gate 的工程价值在于三点:其一,把运动法则中"人眼可辨但难以复核"的要求(同轴同向、切在运动中、零叠化、Z 符号一致、载体连续)翻译成带明确阈值(15 px/s、0.04 es/s、12px/5%、3:1)的数值断言,可进 CI;其二,"账本 → 生成"让普通接缝零手写——stamp 与 gate 共享同一份ledger.json,生成物按构造通过验证,人力集中到 Tier-A 的 carrier 交接上;其三,probe把"找运动载体"这一最费时的定位工作自动化,保证账本里的 selector 与真实 DOM 运动一一对应。
脚本查不到的仍归作者:编辑重开接缝、音频即时钟(按 VO 真实词时间戳重排场景)、clip-gating 陷阱——这三条在 motion-doctrine/SKILL.md 的 Seam Gate 一节有完整表述。验证通过(exit 0)之后,一个 seam 才算"完成"。
【免费下载链接】hyperframesWrite HTML. Render video. Built for agents.项目地址: https://gitcode.com/GitHub_Trending/hy/hyperframes
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考