Repository navigation
bin/vh render and bin/vh determinism: an MP4 and a hard-rule-1 check for any page that meets the contract - #82
Merged
Merged
Conversation
…b-page engine's source
- engines/CONTRACT.md: what a page gives (window.film = { frame(t),
duration, ready, width?, height? }, OpenFilm's protocol 0.1 fields,
plus optional texts, events, chapters, cues), what the host does, and
how each engine meets it.
- tools/film/: the host (served on 127.0.0.1, window.filmHost and the
clock set before the page runs, ready awaited, lossless PNG captured),
the clock prelude, and the HyperFrames adapter that loads a
composition's own runtime and turns window.__player.renderSeek(t) into
frame(t).
- bin/vh frames --at / --range / --strip; CI tests on fixtures.
- ClaudeAnimationBase writes window.film; textcheck reads window.film as
it reads the older window.__vh.
- Measured: showcase 00 matches HyperFrames' snapshot pixel for pixel;
showcase 01 matches render.mjs --stills in the same browser.
- Errors count: a failed request or page error while loading, and one a frame leaves behind (checked after the last frame), now fail the run as the contract says; strip frames are numbered by what was written. - ready is called as a method (film.ready()), as OpenFilm's hosts do; the same in textcheck, and a texts function keeps its this. - The host answers /__vh/ only for its own scripts (a page asking for /__vh/constructor crashed it), pins the time zone to UTC, and the HyperFrames adapter fails in 2 s on a composition with no length. - frames: --at rounds to the millisecond and drops repeats, --range rejects a step of 0 and leaves out the film's own end, empty values and --size 0x0 are refused, every exit closes the browser. - ClaudeAnimationBase: duration follows a ?loop= page. - Docs: what wins for the size, the page's own folder as the root, the tolerance for repeated frames (showcase 00's 14 s differs by order, not from HyperFrames), Math.random as the only clock reported, the video seek and Range requests not done yet.
…for any page that meets the contract - render: every frame at k/fps in parallel browsers, each a contiguous chunk of whole GOPs piped into its own x264, joined without re-encoding; BT.709 tv range with all colour tags, a keyframe every --gop frames, <out>.render.json, a lock per output, a frame-count and colour check. HyperFrames compositions are pointed to npx hyperframes render unless --force. - determinism: the same moments drawn in order, reversed after another frame, in a second browser and without the host's clock; PSNR of the lossless PNGs, exit 1 under --min-psnr, a warning when only the clockless pass differs. - The host can capture JPEG; CI tests on the fixtures; CLAUDE.md lists both commands.
2 of 3 tasks
…nder draws A strip that starts on a frame drew its frames at t rounded to the millisecond. An engine that floors t (ClaudeAnimationBase's onTwos, shakeXY) then drew another picture at some of them: at 24 fps, frames 38 and 80 of showcase 01 came out at 1.583 s and 3.333 s instead of 38/24 and 80/24, 26 dB from the render's frame. Now the strip is on render's frame grid (37 dB from the render's frame, what the encode leaves); messages still show t to the millisecond.
- render: an encoder that stops early (killed, a full disk, a frame it refuses) ends the render at once instead of a wait on a pipe no one reads; the message says which frames and after how many. - render: Ctrl-C or SIGTERM kills the encoders and deletes the chunks so far and the lock (exit 130 / 143); a failed join deletes the chunks too. - render: an output folder it cannot make is exit 2 with the reason, not a stack trace; the lock is taken over only from a render that is gone (a dead pid, or an empty lock older than 10 s), by deleting it and creating it afresh. - render: an odd pixel size (1366x768 at --dpr 1.5) is cropped to even sizes instead of failing after the whole film was drawn; a note says so. - render: a --range that reaches the end ends on the same last frame as no range; --out must be an .mp4 or .mov file, not a folder; --crf= is refused instead of becoming crf 0. - render: the sidecar names the page relative to the video when the video is in the project, and records soft_gl; the exit codes in the header list every case. - determinism: pass D's errors are warnings, as its differences are. - bin/vh frames, render and determinism -h print the usage line and every option; the tools' help ends with a newline.
…ge path holds through a symlink
- The chunks are joined inside the parts folder and the result renamed
onto <out>: a Ctrl-C during the join used to leave a shorter, valid MP4
in place of an earlier video.
- The sidecar compares the video's real folder with the page's real
folder (macOS /tmp is /private/tmp), so a project reached through a
symlink still records "../index.html".
- A bad number is quoted in the message ("--crf: "" is not a number").
ZLHad
added a commit
that referenced
this pull request
Oct 11, 2026
With #82's bin/vh render --png --dpr 2 on showcase 01 (2 workers, 120 frames at 3840x2160): 883 ms a frame instead of 957 ms, node's peak RSS about 440 MB instead of 1040 MB, and the same MP4 byte for byte.
Merged
3 of 6 tasks
3 of 6 tasks
# Conflicts: # AGENTS.md # CHANGELOG.md # CLAUDE.md # bin/vh # engines/CONTRACT.md # tools/ci.sh # tools/film/frames.mjs # tools/film/host.mjs
ZLHad
marked this pull request as ready for review
October 11, 2026 14:21
ZLHad
enabled auto-merge (squash)
October 11, 2026 14:21
ZLHad
added a commit
that referenced
this pull request
Oct 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why · 改了什么、为什么
v0.4 计划 M1 的第三块(计划里的 A3)。自建 canvas 引擎原来靠每支片子复制一份的脚本出片(
film.mjs master),确定性检查也只有风格样片有。现在两样都建在页面合约(#81 的engines/CONTRACT.md)上,所有网页引擎都能用。这个 PR 叠在 #81 上,#81 合并后 base 会自动改回 main。bin/vh render <项目|页面>(tools/film/render.mjs):--workers个浏览器,每个浏览器渲连续的一段;截成 JPEG(--png无损)直接送进各自的 x264,最后无损拼接;--gop帧一个关键帧,从第一帧算起;分段都按整数个 GOP 切,所以拼起来以后关键帧也落在同一套格子上,以后局部重渲(patch)能在关键帧处切开;<out>.render.json,记下这支片子是怎么出的;bin/vh mux套上;npx hyperframes render,那是本仓库量过的路径;加--force才在这里渲。bin/vh determinism <项目|页面>(tools/film/determinism.mjs):同几个时刻画四遍,比较无损 PNG:B 或 C 低于
--min-psnr(默认 45 dB)时退出码 1。A、B、C 一致、只有 D 不同时只给警告:说明页面读了Math.random或真实时间,靠宿主把时钟钉住才一致,换一个不钉时钟的渲染器就会变。宿主能截 JPEG。CI 渲测试页并核对帧数、色彩标签、关键帧位置(
--gop 30分两段,关键帧在 0、1、2 s)和 sidecar;determinism 在测试页和一个带状态的页面上各跑一次。CLAUDE.md的命令列表加了两条(AGENTS.md已同步),engines/CONTRACT.md和playbook/02指向它们。English summary.
bin/vh renderrenders any page that meets the contract to an MP4 (parallel chunks of whole GOPs into x264, BT.709 tv range, fixed keyframes, a sidecar, a lock, final checks);bin/vh determinismdraws the same moments four ways and compares lossless PNGs, failing on order or process differences and warning when only the clockless pass differs.How it was verified · 怎么验证的
render.mjs --clip出的同一帧是 36.8 dB,这类带颗粒的画面本来就在这个水平。Math.random的测试页 A–C 相同,D 给出警告。--gop 60两个 worker 时关键帧在 0 s 和 2 s;--gop 30时在 0、1、2 s;tools/ci.sh --committed通过(shellcheck、pyflakes 用 uvx 跑)。Checklist · 自查
tools/ci.sh --committedpasses · 通过CHANGELOG.md→ Unreleased · 已记进 CHANGELOGCLAUDE.md→ ranbin/vh sync-agents· 命令列表加了 render、determinism,已同步LOCAL.md,projects/or render output committed · 没有提交 key、LOCAL.md、projects/ 和渲染产物styles/<slug>/media/changed only on purpose · 不适用README.mdandREADME.zh-CN.md· 不适用