Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ effort 管 agent 自己查得多细,导演模式管人拍板哪些事,两个
| 3 分钟以上的长片,或靠故事、谜题推进的片子(角色短片、长讲解) | 主类型文档,加上 `playbook/09-narrative.md`(骨架、节拍表、张力曲线、换挡) | 按主类型 | — |
| 要发短视频平台:开头钩子、标题、封面 | `playbook/10-hooks-and-packaging.md`,加上主类型文档 | 按主类型 | — |
| 要写有篇章、有主题的配乐(MV、介绍片和发布片、45 s 以上靠音乐撑起结构的片子、`studio` 档位,或者人要亲自定主题和 BGM) | `playbook/11-composition.md`;`score.json` 的写法见 `playbook/04-audio.md` | `bin/vh music` | — |
| 3D 场景、着色器短片(Three.js) | 暂无专门的类型文档:以 `03-product-promo.md` 的运动规则为准,加上 `playbook/08-vfx-and-motion-sources.md`(一镜到底、特效预设栈、子帧运动模糊)。要路径追踪的光影(玻璃、皮肤、体积光)、物理模拟或真实景深时,读 `engines/blender.md`(实验性:维护者的 Mac 没装 Blender,没验证过,渲染时间先渲 5 帧校准) | HyperFrames + Three.js 层;重光影的镜头用 Blender | `showcase/04-intro-film/`、`cases/opus55-gallery.md` 的 3D 一节和第 6 节(Austerlitz 长片深读) |
| 3D 场景、着色器短片(Three.js) | 暂无专门的类型文档:以 `03-product-promo.md` 的运动规则为准,加上 `playbook/08-vfx-and-motion-sources.md`(一镜到底、特效预设栈、子帧运动模糊)。要路径追踪的光影(玻璃、皮肤、体积光)、物理模拟或真实景深时,读 `engines/blender.md`(部分验证:风格样片 `tabletop-miniature` 已用 Blender 5.2.2 渲染,项目用的命令还有几条没跑过;渲染时间先渲 3–5 帧校准) | HyperFrames + Three.js 层;重光影的镜头用 Blender | `showcase/04-intro-film/`、`cases/opus55-gallery.md` 的 3D 一节和第 6 节(Austerlitz 长片深读) |
| 想要新点子、立意,一句话需求想做得出彩,或者不想千篇一律 | `playbook/12-ideation.md`,再加主类型文档 | 按主类型 | `cases/oneshot-five.md`、`cases/explainer-interstellar-blackhole.md` |
| 想要某种风格、参考某部名作,或者不想每支片子都一个口味 | `styles/README.md`,再读选中预设的 `styles/<slug>/STYLE.md` | 随主引擎 | 每个预设的 `media/swatch.mp4`,总览 `styles/gallery.jpg` |
| 想知道某种镜头怎么动(开场、字卡、转场、卡点、收尾),或想要"专业的节奏" | `recipes/README.md`,再读 `recipes/sequences/` 里合适的骨架 | 随主引擎 | `cases/promo-video-shotcraft.md` |
Expand Down
55 changes: 55 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,61 @@

## Unreleased

**Style swatches can be Blender scenes; a 29th style, tabletop-miniature (桌面微缩剧场)**
- Why: every swatch is drawn on a canvas, and none looks like a lit, physical set. Taking apart a community film (Kevin Ngo's piano short, made with Python and rendered in Blender) gave a stop-motion miniature grammar whose look rests on path-traced light and real depth of field, so the swatch renderer needed a Blender path. Blender 5.2.2 is now installed on the maintainer's Mac.
- `styles/_swatch/`: a style folder with `swatch.py` instead of `swatch.js` renders in Blender (Cycles). `blender_render.py` runs the scene's `build(env)` once per process and `apply(t, env)` before every frame, setting properties directly (no keyframes, no handlers, so no motion blur, which suits stop-motion). It writes PNG frames, and `render.sh` encodes them into the same 1080p BT.709 `hf.mp4` the HyperFrames path makes, so the frame checks, music, foley, mix, `bin/vh qa`, encode, poster and contact sheet are shared. New `render.sh` options for Blender scenes: `--frames 149,90,12` (with `--png`: only those frames, in that order) and `--stamp`.
- Final renders use Cycles on the CPU. Measured on an M3 Max: the same frame rendered twice is pixel-identical on the CPU, but not on Metal (92 dB) or EEVEE (85 dB), and this pipeline's CRF-to-size loop amplifies small differences. `--draft` uses the GPU. tabletop-miniature: about 15 s per frame on the CPU at 32 spp + OIDN (64 spp looked the same side by side), about 1.6 s on Metal.
- Safety, following `engines/blender.md`, in three layers; the third is the boundary:
- `blender_prep.py scan` reads `swatch.py` as a syntax tree before anything runs it. It is a lint that catches mistakes and the plain ways out, not a wall. Imports are allow-listed (`bpy bmesh mathutils math colorsys json sys`, so no `random` or `time` either; of `json` only `dumps`/`loads`, of `sys` only `argv`). Names and attributes starting with `_` and strings containing `__` are refused, which closes the dunder routes an independent reviewer found (`bpy.utils._os`, `x.__dict__["__import__"]`). So are `eval exec compile open getattr setattr type dir globals vars`, and bpy that loads, saves or runs anything (.blend files, text blocks, drivers, `bpy.utils`, `save_render`, handlers, timers). `foley.mjs` runs the same scan before it reads `FOLEY`, and on macOS reads it inside its own sandbox.
- Blender runs under `env -i` (no API keys), with its HOME and TMPDIR in `out/<slug>/.blender/`.
- On macOS, under `sandbox-exec`: no network, no handing URLs or Apple events to other apps, writes only to `out/<slug>/` and Blender's own subfolder of the per-user cache (Metal keeps compiled shaders there; denied, every frame recompiles, 13 s instead of 2 s). Final (CPU) renders also cannot read the home folder except the repo, Blender and `~/Library/Fonts`; frame 90 rendered under it is pixel-identical to the frame rendered without it. Drafts on Metal cannot take that rule: Metal crashes loading cached kernels when the home folder is unreadable. Linux has no sandbox, so render only scenes you have read.
- A cold Metal kernel compile is silent for about 110 s, so the watchdog's stall limit for Blender scenes is 300 s. The `--python-exit-code 1` runs are started with job control off, so the watchdog's process-group kill reaches them; with it on, the Blenders landed in their own groups. When one of several workers fails, the others are stopped.
- Fonts: a scene's `FONTS` are fontconfig patterns; `blender_prep.py fonts` resolves them with `fc-match` to the same system fonts `fonts.css` uses, and stops the render when the match is another family (a Chinese role on a Latin font prints tofu). Blender loads only face 0 of a `.ttc`, so another face (Songti SC Bold is face 1) is written out with fontTools to `out/<slug>/fonts/` on every run, atomically, a local copy that is never committed.
- `determinism.sh` on a Blender scene re-renders 12 frames spread over the clip, last to first, in 3 fresh Blender processes and compares them pixel by pixel with the final render's frames (`out/<slug>/frames/`, used when their `inputs.txt` stamp still matches: the Blender version, every file in the style folder except `media/`, docs and audio, and the two renderer scripts, taken when the render starts; otherwise it renders all frames in order first and keeps them there). It earned its place on the first final render: with `render.use_persistent_data` on, Cycles on the CPU rendered the teacup black on frames 27–44 of the in-order render and grey when those frames were rendered alone, and the check failed on frame 30 (24.5 dB). The renderer now keeps persistent data off (per-frame time barely changed, 15–19 s). `foley.mjs` reads `FOLEY` from `swatch.py` (after the scan). `bin/vh style` skips the HyperFrames install for a Blender scene.
- Licence (the maintainer's decision, 2026-10-01): files that import bpy are GPL-3.0-or-later with an SPDX header, as Blender asks of published bpy scripts: `styles/_swatch/blender_render.py` and `styles/tabletop-miniature/swatch.py`. The rest of the repo stays MIT. Both READMEs' licence sections and `engines/blender.md` ("许可证") say so. `tools/ci.sh` runs the scan on every `styles/*/swatch.py` and fails a `.py` that imports bpy without the header.
- The style (`styles/tabletop-miniature/`):
- Learned from Aardman's *A Grand Day Out* (1989), Laika's *Coraline* (2009), Olivo Barbieri's tilt-shift *Site Specific* series and the piano film.
- The grammar:
- a real-scale tabletop, with a geometric puppet that acts with its gait and two bead eyes;
- only practical light, whose colour and angle carry time;
- the camera at the puppet's eye height, f/4.5, so only a few millimetres are sharp;
- puppets on twos with a hand-placed jitter, camera and lights on ones, no motion blur;
- one acoustic instrument, with each action a note.
- The swatch: the camera starts tight on a sleeping felt puck. The desk lamp clicks on, the puck wakes, the camera pulls back and the blocks slide in, and once the camera has slowed a playbill card is lowered on two threads. The puck walks to three wooden blocks and hops up them: 大纲 is raw wood, 分镜 is half-dipped in red, 初版 is all red, so the paint is the progress. For its payoff the camera cranes up to the puck's eye height and swings round the set, bringing the window in behind it. The lamp goes off on the last downbeat and the puck flinches; the camera drifts on through the dark, the morning comes through the window behind it, and the puck falls asleep. The puppet is on ones while the camera follows it: on twos it stepped back 8–16 px on every odd frame.
- The score is one upright piano: the low register pedalled underneath, a note on each takeoff and a dry high note on each landing, and a V7 chord held through the dark until dawn resolves it. Seven foley sounds are synthesized in `custom_sfx.py`: lamp switch on and off, felt on wood ×3, the thread pulled tight, a bird.
- Its layout was checked frame by frame with a pinhole projection before rendering: the hanging card clears the highest hop by 20 px.
- Seven rounds of independent "harsh motion director" review on fresh contexts, with the worst issues fixed between rounds. The lowest score was 6 in the first round and 7 in every round after; no round had all seven dimensions at 8 or above (the score had seven dimensions then; 立意 came later). Scores below are hook, phone, motion, variety, polish, accuracy, sync.
- Round 4 (8, 8, 7, 8, 7, 8, 9), then fixed:
- the last hop moved to ones (it had 3 air drawings on twos);
- a cool moonlight fill and a dark window frame were added, because the blackout had pure-black blocks while the frame glowed;
- a half-lid and a nod come before sleep.
- Round 5 (8, 8, 7, 8, 8, 9, 9), then fixed:
- the small hop on 初版 was one airborne pose held for 4 frames; it is now a crouch, rise, apex and fall;
- nothing moved in the dark: the puck now flinches at the lamp-off, and the camera keeps drifting and hands over to the dawn move at the same speed;
- the title card and the blocks arrived in the same 0.6 s, and the 大纲 label sat cut by the frame edge for 0.4 s: the opening now starts with the blocks out of frame, and the card comes in once the pull-back has slowed;
- the moonlight fill is brighter (初版 at night 1.6:1 → 2.1:1).
- Round 6 (8, 8, 8, 7, 7, 9, 9), then fixed:
- the payoff played in the same wide shot: the camera now cranes up to the puck's eye height and swings round the set, bringing the window in behind it (a push-in was not possible with the title and all three labels kept inside the safe area);
- the stitched "^ ^" eyes were flat arcs that floated off the round face and showed past its edge as it turned; they now lie on the surface;
- the moon's glint on the varnished desk was the brightest thing in the frame (luma 236, the title card 184): the moon now sits higher and the glint mostly falls below the frame (178).
- Round 7, the version shipped here (8, 8, 8, 7, 8, 9, 8). What it found, not fixed:
- variety: the swing round the set moves the background more than the set, so 3.0, 4.0 and 5.0 s read as one composition. Its suggestion is to push in at the lamp-off and end on a medium close-up of the puck against the morning window, at the cost of the title leaving the frame;
- the blocks are on screen from 0.5 s, before the title, rather than arriving in the 2.0–4.0 s motif window (counted as a deviation from the content spec since round 5);
- light: until the lamp goes off, the desk glint is still the brightest thing in the lower half; a strip beside 初版 clips to white at dawn; in moonlight 初版 drops to about 2.3:1;
- sound: the "air" riser starts when the dawn is already mostly up, and the bird sits under the piano.
- `engines/blender.md` changes from "experimental, never run" to "partly verified". The first-smoke items that were run are written back as 【实测 5.2.2】:
- render repeatability by device;
- speeds;
- what `sandbox-exec` must allow;
- the `.ttc` face limit;
- Mix-node socket identifiers;
- dynamic `view_transform` enums;
- stdout buffering in background mode;
- job control.

`engines/README.md` and `CLAUDE.md` (and `AGENTS.md`) now point to the working swatch path.
- Docs: `styles/README.md` (table row, sound row, how to add a Blender style), `styles/_swatch/README.md` ("Blender 场景"), both READMEs (29 styles). The gallery is rebuilt with the new poster and clip.

**Research note 06: concept-first against floors only, and two self-checks for quick**
- `docs/research/06-concept-first-ab.md` (and `en/`): two arms, two requests (a 30 s Milky Way explainer, a 20 s bakery opening, both silent), four films by the same model, scored blind by a fresh reviewer on the eight dimensions. Both times the reviewer found the workflow's film more original (concept 7 vs 6, 8 vs 7) and chose the floors-only film to post; the means differ by 0.3 and 0.7, likely within run-to-run noise, so the pairwise choices are what the note reads. Its reasons: labels sitting exactly at the 44 px floor, about 8 px on a phone, and a key transformation done as a dissolve. Slowness does not explain it: measured the same way (frame differences), both arms are about as still (opposite ways in the two pairs); arm B's stillness starts at the very beginning, but the hook scores do not tie that to the result. In one pair (Milky Way, 8/10 similar) the model reached much the same idea without the workflow. All four hooks scored 3–5; time and tokens about equal. Figure: five frames per film.
- Changes it led to: the quick card's self-check (and `playbook/02`'s effort note) adds a phone-size contact sheet and a first-2 s strip, with the strip command and what 44 px becomes on a phone; `playbook/12-ideation.md` §6 says readouts people must read should not sit at the floor; the digital-silence floor says it applies to films with sound (CLAUDE.md, playbook 12, TASTE_CHECKLIST #18), which both workflow agents had to reason out; type 02's caption weight falls back to the local font's heaviest; type 03's checks add searching a fictional brand name for a real one (one film named its bakery 一条, a known brand). Open: whether the 44 px floor, written for 1080 wide, should scale for landscape.
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ effort 管 agent 自己查得多细,导演模式管人拍板哪些事,两个
| 3 分钟以上的长片,或靠故事、谜题推进的片子(角色短片、长讲解) | 主类型文档,加上 `playbook/09-narrative.md`(骨架、节拍表、张力曲线、换挡) | 按主类型 | — |
| 要发短视频平台:开头钩子、标题、封面 | `playbook/10-hooks-and-packaging.md`,加上主类型文档 | 按主类型 | — |
| 要写有篇章、有主题的配乐(MV、介绍片和发布片、45 s 以上靠音乐撑起结构的片子、`studio` 档位,或者人要亲自定主题和 BGM) | `playbook/11-composition.md`;`score.json` 的写法见 `playbook/04-audio.md` | `bin/vh music` | — |
| 3D 场景、着色器短片(Three.js) | 暂无专门的类型文档:以 `03-product-promo.md` 的运动规则为准,加上 `playbook/08-vfx-and-motion-sources.md`(一镜到底、特效预设栈、子帧运动模糊)。要路径追踪的光影(玻璃、皮肤、体积光)、物理模拟或真实景深时,读 `engines/blender.md`(实验性:维护者的 Mac 没装 Blender,没验证过,渲染时间先渲 5 帧校准) | HyperFrames + Three.js 层;重光影的镜头用 Blender | `showcase/04-intro-film/`、`cases/opus55-gallery.md` 的 3D 一节和第 6 节(Austerlitz 长片深读) |
| 3D 场景、着色器短片(Three.js) | 暂无专门的类型文档:以 `03-product-promo.md` 的运动规则为准,加上 `playbook/08-vfx-and-motion-sources.md`(一镜到底、特效预设栈、子帧运动模糊)。要路径追踪的光影(玻璃、皮肤、体积光)、物理模拟或真实景深时,读 `engines/blender.md`(部分验证:风格样片 `tabletop-miniature` 已用 Blender 5.2.2 渲染,项目用的命令还有几条没跑过;渲染时间先渲 3–5 帧校准) | HyperFrames + Three.js 层;重光影的镜头用 Blender | `showcase/04-intro-film/`、`cases/opus55-gallery.md` 的 3D 一节和第 6 节(Austerlitz 长片深读) |
| 想要新点子、立意,一句话需求想做得出彩,或者不想千篇一律 | `playbook/12-ideation.md`,再加主类型文档 | 按主类型 | `cases/oneshot-five.md`、`cases/explainer-interstellar-blackhole.md` |
| 想要某种风格、参考某部名作,或者不想每支片子都一个口味 | `styles/README.md`,再读选中预设的 `styles/<slug>/STYLE.md` | 随主引擎 | 每个预设的 `media/swatch.mp4`,总览 `styles/gallery.jpg` |
| 想知道某种镜头怎么动(开场、字卡、转场、卡点、收尾),或想要"专业的节奏" | `recipes/README.md`,再读 `recipes/sequences/` 里合适的骨架 | 随主引擎 | `cases/promo-video-shotcraft.md` |
Expand Down
Loading
Loading