Skip to content

feat(effects): a vhs tape-tear scene effect - #396

Merged
LeadcodeDev merged 1 commit into
mainfrom
feat/vhs-tear
Sep 27, 2026
Merged

LeadcodeDev merged 1 commit into
mainfrom
feat/vhs-tear

Conversation

@LeadcodeDev

Copy link
Copy Markdown
Owner

Closes #386. Refs #388.

PostEffect had grain, vignette, pixelate, progressive_blur and flash. None displaces bands of the image. The channel-splitting effects act on a cut (chromatic_wipe) or on one node (chromatic_aberration), never on the whole frame as a timed state — so grain plus aberration was the best available approximation, and it does not read as a tape tear.

"effects": [
  { "type": "vhs", "at": 0.4, "duration": 0.8,
    "bands": 12, "offset": 60, "noise": 0.3, "scanlines": 0.2,
    "tracking_line": { "color": "#3DA5FF", "speed": 1.5 }, "seed": 3 }
]

Two decisions worth recording

The band layout is redrawn twelve times a second, not every frame. A tear that changes at 30 or 60 fps shimmers instead of catching; twelve is slow enough to read as a mechanical fault. The seed and the instant decide together, so two renders of the same file are byte-identical.

A slid band leaves the freed edge black, not smeared or wrapped. That gap is what makes the displacement read as a tear rather than a blur. On a light background it is very visible — the rule file says to lower offset rather than hope it blends.

It is a beat, not a filter

at and duration bound it like flash, because the reference holds it for well under a second. Outside the window the buffer is not touched — not lightly, not at all, which one of the tests asserts byte-for-byte.

Verification

Five tests. Three fail without the effect:

assertion `left != right` failed: the frame must be torn inside the window
even rows must be darkened and odd ones left alone: 128 vs 128
the tracking line must paint its own colour across a band of rows

The other two assert equality and pass both ways on purpose — outside the window nothing changes, and one seed at one instant is deterministic.

End to end on a contact sheet at 0.10 / 0.40 / 0.60 / 1.20 s: clean, torn with the tracking line near the top, torn with it lower, clean again.

cargo fmt --all --check and cargo clippy --workspace --all-targets -- -D warnings clean; cargo test --workspace 1632.

Two ffmpeg-dependent tests (integration_audio_track_from_embedded_video, successful_extraction_leaves_no_partial_file_behind) failed once in a full run under heavy parallel load and pass 23/23 in isolation. That is the third distinct wall-clock-dependent test to do this today under agent load; worth its own issue, unrelated to this change.

Written comment-free, per the codebase-wide rule from #345.

`PostEffect` had `grain`, `vignette`, `pixelate`, `progressive_blur` and
`flash`. None of them displaces bands of the image, and grain plus aberration
does not read as a tape tear — the channel-splitting effects act on a cut
(`chromatic_wipe`) or on one node (`chromatic_aberration`), never on the whole
frame as a timed state.

`vhs` breaks the frame into horizontal bands slid sideways, over noise,
scanlines and a tracking line travelling down. Bounded by `at` and `duration`
like `flash`, and for the same reason: a reference reel holds it for well under
a second on a rewind. Outside its window the buffer is not touched at all.

Two decisions worth recording.

The band layout is redrawn twelve times a second rather than every frame. A
tear that changes at 30 or 60 fps shimmers instead of catching; twelve is slow
enough to read as a mechanical fault. The seed and the instant decide together,
so two renders of the same file are byte-identical.

A band slid sideways leaves the freed edge black, not smeared or wrapped. That
gap is what makes the displacement read as a tear rather than as a blur.

Five tests. Three fail without the effect — the frame must differ inside the
window, even rows must darken, the tracking line must paint its colour. The
other two assert equality and pass both ways on purpose: outside the window
nothing changes, and one seed at one instant is deterministic.

Verified end to end on a contact sheet: clean at 0.10 s, torn with the tracking
line near the top at 0.40 s, torn with it lower at 0.60 s, clean again at 1.20 s.

Closes #386
@LeadcodeDev LeadcodeDev added the enhancement New feature or request label Sep 27, 2026
@LeadcodeDev LeadcodeDev self-assigned this Sep 27, 2026
@LeadcodeDev
LeadcodeDev merged commit 109868e into main Sep 27, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A VHS / horizontal-tear post effect

1 participant