1仕組みと動作原理
3つのワークフローが単一のエンジンを共有し、入力に応じて振り分けられます:テキスト→文字の手書き、写真→再描画(静止画または自動描画動画)、動画/Live Photo→コマ送り再描画による動く絵画。すべてコード生成され、画像モデルは一切使用しません。
Everything is 100% code: p5.brush strokes rendered headlessly, no image models.
レンダリングアーキテクチャは「ジェネレータ+コマ送りスクリーンショット」です:Node がヘッドレス Chromium を起動して harness と sketch を注入。sketch の paint() はジェネレータであり、yield ごとに一筆描画し、Node 側で各ステップの画像をキャプチャし、最終的に ffmpeg で MP4 に結合します。
// draw() advances the paint() generator spf steps, then pauses; the node side
レンダリング時のネットワークおよびファイルアクセスは**厳格に隔離**されています:各ページはオープン前にリクエストインターセプトが設定され、about:/blob:/data: のみを通し、その他はすべて abort されます。写真/動画は data URL 形式(IMG_SRC)で注入されるため、sketch はローカルファイルを読み込むこともインターネットに接続することもできません。
const allowedProtocols = new Set(["about:", "blob:", "data:"]);
パラメータ注入はホワイトリスト形式の変数宣言であり、任意のコード評価ではありません:--inject "NAME=value" は `var NAME = <JSON>` にのみ変換されます。変数名は /^[A-Z][A-Z0-9_]*$/ に一致する必要があり、値は JSON 文字列/数値/真偽値のみ許可され、それ以外は即座にエラーをスローします。
const NAME = /^[A-Z][A-Z0-9_]*$/;
再現性は明示的な目標です:--seed が指定されると、ライブラリ読み込み前に mulberry32 で Math.random をオーバーライドし(p5.brush 内部のノイズや散布を再現可能にする)、ドキュメントには「同一 seed で完全一致(バイト単位)」を受入条件として明記し、一度に1つのダイヤルのみ調整することを要求しています。
// Seeded PRNG (mulberry32) installed over Math.random BEFORE libs load, so
動画フローでは「ストロークグリッドの固定」により安定性を確保しています:グリッドは第1フレームでのみ生成・固定され、以降のフレームでは色と向きのみをリサンプリングします。これにより被写体が動いても筆致自体がブレることはありません。
The stroke lattice is built ONCE from frame 1 and locked; only colors and orientations resample per frame. Strokes hold still — the painting never "boils" while the subject moves.
入力品質は成否の前提条件としてドキュメントに記載されています:写真は事前に被写体へクロップ(画面内の顔のサイズが成否を分ける)、Live Photo は事前の動きトリアージで静かな区間のみを描画、暗い素材は彩度を上げずにガンマ調整のみを行います。
**Crop to the subject first.** Face size in frame is destiny: selfie-distance faces resolve beautifully; small faces stay figures.
ディスク書き込み範囲は呼び出し元が指定した場所に制限され、自動クリーンアップされます:out.png / out.mp4(および同階層の .track.json)のみを出力します(--keep-frames 1 指定時のみ _frames_* ディレクトリを保持)。
Writes only inside paths you pass (`out.png` / `out.mp4`, plus a sibling `.track.json` and, with `--keep-frames 1`, a `_frames_*` directory).
2コア機能
3外部依存関係
| Kind | Dependency / Endpoint |
|---|---|
| package | puppeteer 25.9.0(Apache-2.0)、p5 2.3.2(LGPL-2.1)、p5.brush 2.2.1(MIT),由 lockfile + package.json 双钉 |
| cli | ffmpeg(渲染链路:把逐帧 PNG 缝成 MP4、视频流程抽帧与拼回) |
| package | Node 22.12+ 与约 500MB 磁盘(Chromium) |
| cli | 可选:npx hyperframes remove-background(仅 Flow C 主体抠像用;CLI 从 npm 取) |
| network | 无运行期外发:渲染时页面被请求拦截(只放行 about:/blob:/data:),素材以 data URL 注入 |
| package | 素材来源:调用方自备的文字/照片/视频(Live Photo 需其配对的 .mov) |
| cli | setup.sh 的一次性下载(npm ci + Puppeteer 拉 Chrome for Testing),自述无凭证、无成本、不上传 |
4推奨統制措置と判定根拠
- scripts/: ローカルスクリプトまたはコマンドの実行: node scripts/render.mjs <sketch.js> [out.png] [--size WxH] [--seed N] [--im
- 网络端点: 外部ネットワークアクセスの宣言: registry.npmjs.org:npm ci で puppeteer 25.9.0 / p5 2.3.2 / p5.brush 2.2.1 をインストール(p
- 本地工作目录: ファイル読み書きとアーティファクト出力: 呼び出し元が指定した出力パス:out.png / out.mp4。render-anim は同階層に <out>.track.json(フレームごとのペン先位置と mark)も出力
5二重目の独立検証
- [ok] 『渲染期网络与本地文件访问被挡住』是否真成立 — render.mjs / render-anim.mjs / render-video.mjs 都在 const page = await browser.newPage(); 之后立即 await hardenPage(page);page-safety.mjs 用 page.setRequestInterception(true) 后只放行 about:/blob:/data:,其余 request.abort('blockedbyclient')。素材以 `var IMG_SRC = "data:…;base64,…"` 注入,故 sketch 拿不到 file:// 也发不出请求。
- [ok] 有没有关闭沙箱或安全降级的 flag(红档的关键判据) — 三个 launcher 的 launch 参数只有 headless: "new" 与 args: ["--enable-unsafe-swiftshader"];无 --no-sandbox、--disable-web-security、--allow-file-access-from-files、--single-process、--ignore-certificate-errors。--enable-unsafe-swiftshader 是为无 GPU 环境启用软件渲染,不属安全降级面。
- [ok] 『无凭证』是否成立 — 全目录 grep process.env / os.environ 零命中;三个 harness 无 token/key 参数;setup.sh 自述 'No credentials, no cost, nothing is uploaded.';无 cookie/keychain/.env 访问。
- [ok] --inject 是否等于任意代码执行 — lib/inject.mjs 的 buildInjectDeclarations 只产出 `var NAME = <JSON.stringify(value)>;`;NAME 必须匹配 /^[A-Z][A-Z0-9_]*$/(否则 'invalid --inject name'),值非 JSON 字符串/数字/布尔即抛错,未闭合字符串亦抛错。全文件无 eval / new Function。
- [ok] seed 可复现是否真落在代码(而非文档口号) — render.mjs 与 render-anim.mjs 都构造 seedShim,并在 addScriptTag 注入 p5.min.js / p5.brush.js **之前**先注入 mulberry32 覆盖 Math.random(注释:'Seeded PRNG (mulberry32) installed over Math.random BEFORE libs load')。
- [ok] 文档声明的旋钮是否与 sketch 实现一致 — handwriting.anim.js 有 typeof PHRASE / INK / BRUSH / PAPER 守卫(const TEXT = typeof PHRASE !== "undefined" ? PHRASE : "hello world"; 等);repaint.anim.js 有 STYLE / DETAIL / LOOSE / SUBJECT(subject-first 段);repaint-video.js 有 WORLD / MASKED / BG 且 render-video.mjs 支持 --frames DIR 传已抠像帧。与 SKILL.md 的 dial 列表逐项对得上。
- [discrepancy] 『Requires: Node 22.12+』是否被运行时校验 — SKILL.md 的 Requirements 写 'Requires: Node 22.12+, ffmpeg on PATH, ~500MB disk (Chromium).',但 scripts/package.json 没有 engines 字段,setup.sh 与三个 harness 也都不做 Node 版本检查;低版本 Node 会照常尝试执行,只在运行期以别的错误暴露。因此该版本要求是文档级而非技术门槛(对 pin commit 有效的真实出入)。
- [unlocatable] Flow C 抠像『local CoreML inference, no data leaves the machine』 — remove-background 属于 HyperFrames CLI,该 CLI 不在本仓库内,其后端实现(是否 CoreML、是否在非 macOS 平台改走别的推理路径、是否会下载权重)无法在本 skill 目录内复核;本次为只读侦查且不运行该命令,故按文档声明记录,不作为已核事实。
6結論
1f0478d5e5ca2ab0…