1实现原理 · 为什么它能做到
核心是「把整部短片压成一个自包含 HTML」:画面每个像素由代码算,无图片、无字体文件、无库;再由无头浏览器逐帧 seek 成 MP4——确定性来自「每帧是帧号的纯函数」。
Make short animated films where every pixel is computed: no images, no fonts to load, no libraries. One HTML file holds the whole piece; a headless browser seeks it frame by frame into an MP4.
默认流程刻意「不停下来问」:推断形式/时长/画幅后直接跑完,只在交付时说明还能改什么;只有完全没有主题时才先问。
Run straight through. Pick sensible defaults and mention alternatives at delivery. Ask *before* starting only if there is no subject at all (e.g. "make an animation", nothing else).
画面与声音由同一份数据驱动:节拍网格、分镜表与调度先写成页面里的 BPM/SECTIONS/SHOTS/EVENTS,场景、相机与配乐都读它,于是声画对齐是结构性的。
These go into the page as `BPM`, `SECTIONS`, `SHOTS`, `EVENTS`: the scenes, camera and soundtrack all read them, so picture and sound line up by construction.
画法纪律针对手绘动画的典型失败模式:纯帧号函数(禁 rAF 状态/CSS 动画/累积物理)、boil 每 4 帧换种子而非每帧抖、身体用椭圆与圆角矩形、按最能读出姿势的角度取景、显式规定前后绘制顺序。
- **Boil, don't jitter.** Change the wobble seed every 4 frames (`BOIL`), not every frame. Every frame reads as noise; ~7.5 changes a second reads as hand-drawn.
自查是脚本门禁 + 两遍人眼:先 asset-audit / layout-check / sync-check,再走 storyboard conformance 与 director pass(2fps 接触表打分)。
Scripts first: `asset-audit` CLEAN, `layout-check` CLEAN, `sync-check` passing (a remaining STARTLE is acceptable only on a deliberate stinger you can name). Then the two passes by eye below.
文本碰撞由工具判定而非靠看:starter 的 claim()/placeFree() 登记每个文本框与关键物体的包围盒,layout-check 逐采样帧列出重叠与发生时刻。
- **Text never collides.** Claim every text box and key object; `layout-check` must print CLEAN. A caption overlapping a monitor edge or a face looks careless, and eyes on a contact sheet miss it.
真人照片被限制在「本地看」:按特征提取法把人物画成风格化角色,明确要求从不上传、不嵌入、不放进任何发布物——这是本 skill 唯一的隐私条款,且是硬指令。
Photos stay local: never upload them or put them in anything published.
2核心能力
3外部依赖
| Kind | Dependency / Endpoint |
|---|---|
| package | playwright-core(脚本用它启动 Chrome 逐帧 seek 页面);另需 Chrome 或 `npx playwright install chromium` |
| cli | ffmpeg(jpeg 管道编码 H.264、拼接触表、把离线渲染的 WAV 混进 MP4) |
| cli | 兄弟 skill 的同步检查:node ../soundtrack/scripts/sync-check.mjs(跨 skill 调用其 ffmpeg/ffprobe 分析) |
| network | 无执行期外部端点:交付页面的契约就是「不加载任何东西」,由 asset-audit 的静态规则 + 实跑网络记录双向证明 |
| package | Web Audio(浏览器内置的 OfflineAudioContext,用于离线渲染页面内合成的配乐;由 soundtrack 模板提供) |
4管控建议与判定依据
- scripts/: 执行本地脚本或命令: node scripts/render.mjs piece.html sheet.jpg --sheet 1 / --stills 90,300,61
- 网络端点: 声明外部网络访问: 本 skill 的脚本自身不发送任何业务请求:playwright 打开的是 file:// 本地页面,ffmpeg/ffprobe 只读本地文件
- 本地工作目录: 文件读写与工件输出: <out>.mp4(全片)、<out>-<frame>.jpg(静帧)、out 指定名的接触表图(render.mjs)
5第二遍独立确认
- [ok] 脚本是否真的无网络外发(蓝档的关键) — 三个 .mjs 逐个读过:render.mjs 只 spawn ffmpeg 与 playwright 打开 file:// 页面;asset-audit.mjs 的 live check 只监听请求并打印;layout-check.mjs 只 evaluate window.draw/LAYOUT。skill 目录内无 fetch/http 调用点。
- [ok] 是否有凭证/环境变量读取 — `process.env|API_KEY|TOKEN|SECRET|Authorization` 在本仓命中仅一处:skills/soundtrack/references/ai-music.md 的 $OPENROUTER_API_KEY(属 soundtrack)。本 skill 内零命中,credential_reads 为空成立。
- [ok] 是否有更高的触发条件被漏掉(红档特征) — `sandbox|automationcontrolled|no-verify|stealth|bypass|dangerously` 全仓零命中;浏览器以默认方式启动(`chromium.launch({ channel: 'chrome' })`,无 stealth 补丁、无关 TLS/沙箱开关)。
- [ok] SKILL.md 引用的脚本与模板是否都真实存在(有无示意命令) — 四条命令逐条核对到真实文件:scripts/render.mjs、scripts/asset-audit.mjs、scripts/layout-check.mjs、templates/starter.html 均在 skill 目录内;跨 skill 的 ../soundtrack/scripts/sync-check.mjs 在同仓兄弟 skill 目录内存在。无空引用。
- [ok] 「zero-asset」是否是可执行契约而非口号 — asset-audit.mjs 有 13 条静态规则(含 data: URI、@font-face、动态 import、base64 大块)与一次实跑网络记录,命中即 exit 1;qc.md 把 'asset-audit CLEAN' 列为交付前第一道门禁。契约可执行。
- [ok] 「照片只本地」的隐私声明是否两处一致且可定位 — SKILL.md 触发条件表内 'Photos stay local: never upload them or put them in anything published.';character.md 第 6 步 'Photos are for looking at locally. Never upload them, embed them, or put them in anything published.' 两处同口径,无矛盾。
- [ok] 「每帧是帧号的纯函数」是否与 starter/render 实现相符 — starter.html 顶部注释写死契约(不许 Math.random/Date,用 rng(seed)/hash2,任意帧可单独渲染),render.mjs 只调用 window.draw(f) 取 dataURL、不做时间推进;两侧一致,声明属实。
6结论
cf575d5199b6a4ed…