Skip to content

Commit bd21d00

Browse files
committed
refactor(producer): /simplify Phase 4 distributed-rendering changes
Address findings from a three-agent code-review pass over the Phase 4 stack: - regression-harness: hoist `readdirSync` out of the per-checkpoint failure-extraction loop (was running 20 redundant syscalls on every failing png-sequence test). Drop redundant `existsSync` guards before `mkdirSync(recursive: true)` and `rmSync(force: true)`. Replace the three-deep ternary that built the output filename suffix with a single `Record<format, ext>` lookup. - regression-harness-distributed: flatten the `format === "mp4" ? {...} : {...}` branching in the `plan()` call into a single config object with a conditional spread. `plan()` already accepts `codec: undefined` for non-mp4 formats, so the duplicate object was unnecessary. - chunkBoundary.test: rename the stale "byte-identical mp4" test title to "byte-identical frames" (the test now uses png-sequence). Trim the 10-line comment justifying `rejectOnSystemFonts: false` to the essential WHY. - renderChunk / plan.test / regression-harness: drop trailing-edge comment phrases that pinned the prose to the PR's calendar context ("today", "v1.5", "pre-codec-knob output", section-numbered cross- references to the planning doc). No behavior change. All 49 distributed unit tests pass. Smoke + four distributed format fixtures pass in --mode=distributed-simulated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent 0b31465 commit bd21d00

4 files changed

Lines changed: 71 additions & 101 deletions

File tree

packages/producer/src/regression-harness-distributed.ts

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -153,38 +153,25 @@ export async function runDistributedSimulatedRender(
153153
mkdirSync(planDir, { recursive: true });
154154
mkdirSync(chunksDir, { recursive: true });
155155

156-
// Step A: plan. `codec` is only forwarded when the format actually
157-
// accepts it — `plan()` throws if codec is set for a non-mp4 format,
158-
// and a caller passing `format: "mov", codec: undefined` would still
159-
// surface that field in the resulting object. We omit it conditionally
160-
// to keep the off-path planDir identical to pre-codec-knob output.
156+
// Step A: plan. `plan()` throws when `codec` is set with a non-mp4 format,
157+
// but `codec: undefined` is a no-op — so we forward it directly for mp4
158+
// and elide it for the others rather than branching the entire config.
159+
// hdrMode is pinned to force-sdr so the harness's behavior is independent
160+
// of any future auto-detect changes.
161161
const planResult = await plan(
162162
input.projectDir,
163-
input.format === "mp4"
164-
? {
165-
fps: input.fps,
166-
width: 1920,
167-
height: 1080,
168-
format: "mp4",
169-
codec: input.codec,
170-
chunkSize: input.chunkSize,
171-
maxParallelChunks: input.maxParallelChunks,
172-
hdrMode: "force-sdr",
173-
}
174-
: {
175-
fps: input.fps,
176-
// Required-by-type but overridden by the composition's own attrs;
177-
// see docstring above. Any positive integer works.
178-
width: 1920,
179-
height: 1080,
180-
format: input.format,
181-
chunkSize: input.chunkSize,
182-
maxParallelChunks: input.maxParallelChunks,
183-
// Force the SDR path explicitly — `auto` would still resolve to
184-
// force-sdr in distributed mode, but pinning it here keeps the
185-
// harness's behavior independent of any future auto-detect changes.
186-
hdrMode: "force-sdr",
187-
},
163+
{
164+
fps: input.fps,
165+
// Required-by-type but overridden by the composition's `data-width` /
166+
// `data-height` attrs; any positive integer works.
167+
width: 1920,
168+
height: 1080,
169+
format: input.format,
170+
...(input.format === "mp4" && input.codec !== undefined ? { codec: input.codec } : {}),
171+
chunkSize: input.chunkSize,
172+
maxParallelChunks: input.maxParallelChunks,
173+
hdrMode: "force-sdr",
174+
},
188175
planDir,
189176
);
190177

packages/producer/src/regression-harness.ts

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -372,12 +372,10 @@ function discoverTestSuites(
372372
if (!statSync(dir).isDirectory()) continue;
373373
if (entry === "node_modules" || entry.startsWith(".")) continue;
374374

375-
// `tests/distributed/<name>/` is the home for fixtures authored
376-
// specifically for the distributed pipeline (see tests/README.md and
377-
// DISTRIBUTED-RENDERING-PLAN.md §10.2). Recurse one level deeper so
378-
// each `<name>` becomes a first-class fixture ID (`mp4-h264-sdr`,
379-
// `mov-prores`, …) the user can target on the CLI without their
380-
// namespace prefix.
375+
// `tests/distributed/<name>/` holds fixtures authored for the
376+
// distributed pipeline. Recurse one level deeper so each `<name>`
377+
// becomes a first-class fixture ID the user can target on the CLI
378+
// without a namespace prefix.
381379
if (entry === "distributed") {
382380
for (const sub of readdirSync(dir)) {
383381
const subDir = join(dir, sub);
@@ -547,9 +545,7 @@ function saveFailureDetails(
547545
snapshotHtml?: string,
548546
): void {
549547
const failuresDir = join(suite.dir, "failures");
550-
if (!existsSync(failuresDir)) {
551-
mkdirSync(failuresDir, { recursive: true });
552-
}
548+
mkdirSync(failuresDir, { recursive: true });
553549

554550
// Save compilation failures
555551
if (result.compilation && !result.compilation.passed) {
@@ -608,30 +604,36 @@ function saveFailureDetails(
608604
const framesToExtract = failedCheckpoints.slice(0, 10);
609605
if (framesToExtract.length > 0) {
610606
const framesDir = join(failuresDir, "frames");
611-
if (!existsSync(framesDir)) {
612-
mkdirSync(framesDir, { recursive: true });
613-
}
607+
mkdirSync(framesDir, { recursive: true });
614608

615609
const renderedIsDir =
616610
existsSync(renderedVideoPath) && statSync(renderedVideoPath).isDirectory();
617611
logPretty(`Extracting ${framesToExtract.length} failed frames...`, "📸");
618612

613+
// For directory output, sort both frame lists once — they're static for
614+
// the duration of the failure-extraction loop, so the per-checkpoint
615+
// readdir+filter+sort the loop did before was wasted syscalls.
616+
const renderedDirFrames = renderedIsDir
617+
? readdirSync(renderedVideoPath)
618+
.filter((n) => n.toLowerCase().endsWith(".png"))
619+
.sort()
620+
: null;
621+
const snapshotDirFrames = renderedIsDir
622+
? readdirSync(snapshotVideoPath)
623+
.filter((n) => n.toLowerCase().endsWith(".png"))
624+
.sort()
625+
: null;
626+
619627
for (const checkpoint of framesToExtract) {
620628
const timeStr = checkpoint.time.toFixed(2).replace(".", "_");
621629
try {
622-
if (renderedIsDir) {
630+
if (renderedDirFrames && snapshotDirFrames) {
623631
const frameIndex = Math.max(
624632
0,
625633
Math.round(checkpoint.time * fpsToNumber(suite.meta.renderConfig.fps)),
626634
);
627-
const renderedFrames = readdirSync(renderedVideoPath)
628-
.filter((n) => n.toLowerCase().endsWith(".png"))
629-
.sort();
630-
const snapshotFrames = readdirSync(snapshotVideoPath)
631-
.filter((n) => n.toLowerCase().endsWith(".png"))
632-
.sort();
633-
const renderedFrame = renderedFrames[frameIndex];
634-
const snapshotFrame = snapshotFrames[frameIndex];
635+
const renderedFrame = renderedDirFrames[frameIndex];
636+
const snapshotFrame = snapshotDirFrames[frameIndex];
635637
if (renderedFrame !== undefined) {
636638
copyFileSync(
637639
join(renderedVideoPath, renderedFrame),
@@ -717,17 +719,15 @@ async function runTestSuite(
717719
const tempDownloadDir = join(tempRoot, "downloads");
718720
const outputFormat = suite.meta.renderConfig.format ?? "mp4";
719721
const isPngSequence = outputFormat === "png-sequence";
720-
// png-sequence output is a directory; encoded video outputs (mp4/mov/webm)
721-
// are single files. `outputSuffix` is appended to the in-temp + baseline
722-
// names so both shapes round-trip cleanly.
723-
const outputSuffix = isPngSequence
724-
? ""
725-
: outputFormat === "mp4"
726-
? ".mp4"
727-
: outputFormat === "mov"
728-
? ".mov"
729-
: ".webm";
730-
const outputBasename = isPngSequence ? "frames" : `output${outputSuffix}`;
722+
// png-sequence output is a directory (basename = "frames"); encoded video
723+
// formats produce a single file (basename = "output.<ext>"). One lookup
724+
// covers both shapes for the in-temp render and the on-disk baseline.
725+
const VIDEO_EXT: Record<"mp4" | "mov" | "webm", string> = {
726+
mp4: ".mp4",
727+
mov: ".mov",
728+
webm: ".webm",
729+
};
730+
const outputBasename = isPngSequence ? "frames" : `output${VIDEO_EXT[outputFormat]}`;
731731
const renderedOutputPath = join(tempRoot, outputBasename);
732732

733733
// Snapshot files stored in test's output/ directory. For png-sequence the
@@ -882,10 +882,9 @@ async function runTestSuite(
882882
}
883883
if (isPngSequence) {
884884
// Frames directory — recursive copy so every PNG lands at
885-
// `<snapshotDir>/frames/<frame-N>.png`.
886-
if (existsSync(snapshotVideoPath)) {
887-
rmSync(snapshotVideoPath, { recursive: true, force: true });
888-
}
885+
// `<snapshotDir>/frames/<frame-N>.png`. `rmSync(..., force: true)`
886+
// tolerates a missing path, so the prior existsSync gate was redundant.
887+
rmSync(snapshotVideoPath, { recursive: true, force: true });
889888
cpSync(renderedOutputPath, snapshotVideoPath, { recursive: true });
890889
} else {
891890
copyFileSync(renderedOutputPath, snapshotVideoPath);

packages/producer/src/services/distributed/chunkBoundary.test.ts

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,23 @@
11
/**
22
* Per-adapter chunk-boundary contract: rendering the same composition at
33
* chunkSize=N (single chunk, no seams) vs chunkSize=N/4 (four chunks, three
4-
* seams at frames 15, 30, 45) MUST produce byte-identical *frames*. This
5-
* is the strongest contract a distributed render can satisfy — anything
4+
* seams at frames 15, 30, 45) MUST produce byte-identical *frames*. Anything
65
* weaker means the worker's seek-determinism leaks across chunk boundaries.
76
*
8-
* Output format is png-sequence rather than mp4 because mp4 bitstreams
9-
* encode keyframe placement directly: chunkSize=60 emits 1 IDR; chunkSize=15
10-
* emits 4 IDRs at frames 0/15/30/45. Those are legitimately different bytes
11-
* even when the captured pixels are identical. The png-sequence assemble
12-
* path merges chunk frame directories with no re-encode, so per-frame
13-
* byte equality round-trips a pixel-level contract.
7+
* Output is png-sequence rather than mp4 because mp4 bitstreams encode
8+
* keyframe placement directly: chunkSize=60 emits 1 IDR; chunkSize=15 emits
9+
* 4 IDRs at frames 0/15/30/45. Those are legitimately different bytes even
10+
* when the captured pixels are identical. The png-sequence assemble path
11+
* merges chunk frame directories with no re-encode, so per-frame byte
12+
* equality is exactly pixel equality.
1413
*
1514
* For each first-party adapter (GSAP, Anime.js, Three.js, Lottie, CSS,
1615
* WAAPI), `tests/distributed/<adapter>-boundary/src/index.html` is a
1716
* 60-frame composition that drives the adapter through its registered seek
18-
* hook. The test:
19-
*
20-
* 1. plan() + renderChunk() × N + assemble() at chunkSize=60 → N=1 chunk.
21-
* 2. Same at chunkSize=15 → N=4 chunks.
22-
* 3. Per-frame `Buffer.equals` across the two output frame directories.
23-
*
24-
* Fixtures with no checked-in baseline aren't compared by the regression
25-
* harness — they're driven from here via `bun test`. CI exercises them
26-
* through the same `bun test` step inside `Dockerfile.test`.
27-
*
28-
* Soft-skip behavior matches `renderChunk.test.ts`: if the host's
29-
* `chrome-headless-shell` can't render (no SwiftShader, missing GL stack),
30-
* the test logs a warning and returns. The Docker harness covers the real
31-
* contract against a known-good image.
17+
* hook. The fixtures intentionally lack a `meta.json` so they're invisible
18+
* to the regression harness; this test owns them. On hosts whose
19+
* chrome-headless-shell can't render (no SwiftShader / missing GL stack),
20+
* each subtest soft-skips and the Docker harness covers the contract.
3221
*/
3322

3423
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
@@ -86,15 +75,11 @@ async function planAndAssemble(input: {
8675
// affects keyframe placement in the bitstream.
8776
format: "png-sequence",
8877
chunkSize: input.chunkSize,
89-
// Some adapter bundles (notably anime.js's IIFE) embed CSS-shaped
90-
// strings inside their JS — `font-family: ui-monospace, monospace`
91-
// for internal devtools styling. `validateNoSystemFonts` scans the
92-
// entire compiled HTML and matches those JS string literals, which
93-
// would false-positive every chunk-boundary fixture that loads
94-
// such a bundle. Disable the check for this test only; the fixtures
95-
// never display text and the byte-identity contract is independent
96-
// of which fonts the page would resolve. This is the documented
97-
// escape hatch for the option.
78+
// anime.js's IIFE bundle embeds `font-family: ui-monospace, monospace`
79+
// as a string literal inside its JS, which `validateNoSystemFonts`'s
80+
// document-wide regex false-positives. These fixtures display no text,
81+
// so disabling the check (the documented escape hatch on this flag) is
82+
// safe.
9883
rejectOnSystemFonts: false,
9984
},
10085
planDir,
@@ -122,7 +107,7 @@ describe("per-adapter chunk-boundary byte equality", () => {
122107

123108
for (const adapter of ADAPTERS) {
124109
it(
125-
`${adapter}: chunkSize=60 (N=1) vs chunkSize=15 (N=4) produces byte-identical mp4`,
110+
`${adapter}: chunkSize=60 (N=1) vs chunkSize=15 (N=4) produces byte-identical frames`,
126111
async () => {
127112
const fixtureDir = join(testsDistributedDir, `${adapter}-boundary`);
128113
if (!existsSync(join(fixtureDir, "src", "index.html"))) {

packages/producer/src/services/distributed/plan.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,8 @@ describe("plan() — codec knob", () => {
242242
readFileSync(join(planDir, "meta", "encoder.json"), "utf-8"),
243243
) as Record<string, unknown>;
244244
expect(encoder.encoder).toBe("libx265-software");
245-
// SDR 8-bit yuv420p, same as h264. Distributed mode is SDR-only —
246-
// anyone reading this and tempted to bump to 10-bit, that's HDR
247-
// territory and lives in v1.5.
245+
// SDR 8-bit yuv420p, same as h264 — distributed mode is SDR-only and
246+
// 10-bit / HDR pixelFormat selection is not exposed on this surface.
248247
expect(encoder.pixelFormat).toBe("yuv420p");
249248
},
250249
TIMEOUT_MS,

0 commit comments

Comments
 (0)