Skip to content

sidecar: hand a state input's earlier rows at the times they arrived - #10

Merged
imbcmdth merged 1 commit into
mainfrom
fix/rows-across-workers
Oct 16, 2026
Merged

imbcmdth merged 1 commit into
mainfrom
fix/rows-across-workers

Conversation

@imbcmdth

Copy link
Copy Markdown
Owner

A node spread over several workers is handed the rows of the ticks it did not render, so the output is the same at any worker count. On ffrwd 0.30.1 that broke for blitz's compose fed by a rows producer on the GPU path: the frames flipped between older and newer documents every few seconds, while --jobs 1 was right.

Cause. The replay recorded each handed-on row at its tick's pts in the clock's time base, while the node reads rows in the rows stream's own time base. On the GPU path the two differ: compose's picture is the one the region decodes from the packets (1/90000 for MPEG-TS), and the producer reads the same input's raw picture off the pipe (1/61440), whose base its rows take. A replayed row was therefore read about 1.46 times later than it arrived, blitz held it as future, and every instance that had not rendered that row's own tick showed an older document. A row stamped between two ticks was also moved to its tick's time even on one base.

Fix. The replay records each row at its own pts in its stream's time base, a message's pts or the pts of the frame it rode on, in the order the SDK folds them. The earlier-rows doc in the world and NODE-CLI.md name the base; no world bump. Neither blitz nor a producer needs a change.

Measured on the 4090. A 20 s MPEG-TS input into compose with blitz's emit_changes, default workers against --jobs 1: 521 of 600 frames differed before, 0 after; --jobs 1 unchanged. Through a realtime input: 34 differed before, 0 after.

Tests. A fleet test in node_shapes runs a state node at -jobs 1, 2 and 4 with cues falling between its ticks and compares the bytes and each tick's newest-row time, failing 5 of 5 without the fix. An exec test runs blitz's emit_changes into compose over a generated MPEG-TS file at --jobs 1 and the default and compares framemd5 frame for frame, skipping where the blitz checkout is absent.

Checks. cargo fmt and clippy -D warnings clean with default features and --no-default-features; node_shapes 33; ruff and mypy clean; unit tier 5143; the exec tests touched pass.

🤖 Generated with Claude Code

An instance of a frame-parallel node with a state input is handed the
rows of the ticks it did not process. Each tick's rows were recorded at
the tick's pts, in the clock's time base, and a node reads them in the
rows stream's own time base, as it reads the messages of its own tick.
When the two bases differ, the rows another instance rendered were
folded at the wrong time. blitz's compose over a picture decoded in the
region (1/90000 from MPEG-TS) with change rows from a producer clocked
by the same input's raw picture (1/61440 on the pipe) scheduled every
row it was handed that way about 1.46 times later than it arrived, so
each worker showed a different, older document, while --jobs 1 was
right. A row stamped between two ticks was moved to the tick's time
even on one base.

Each row is now recorded at its own pts: a message's, or the frame's it
rode, in its stream's time base, in the order the node reads them.

shape_state now also says when its newest word arrived, and a fleet test
reads cues on 1/48000 that fall between its ticks on 1/10, the same at
-jobs 1, 2 and 4. An exec test runs blitz's emit_changes into compose
over an MPEG-TS input at --jobs 1 and the default, frame for frame,
where a blitz checkout sits beside this one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@imbcmdth
imbcmdth merged commit 0a247ec into main Oct 16, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant