Skip to content

t27f: lay the reel's captions, cards and address over the beat video, LR_MODE=5 (Closes #10609) - #10610

Closed
gHashTag wants to merge 7 commits into
masterfrom
claude/t27f-beat-overlay
Closed

gHashTag wants to merge 7 commits into
masterfrom
claude/t27f-beat-overlay

Conversation

@gHashTag

Copy link
Copy Markdown
Owner

Closes #10609. Part R4c of epic #10560. Stacked on #10605 (R4b.1, the beat graph), which is stacked on #10598 (R4a). Those should merge first; until then this diff also shows their commits.

What

specs/t27f/lesson_reel.t27:

  • graph() takes a beats flag.
    • Without it nothing changes: the picture is input 0, the wavs are 1..3 and the music is 4.
    • With it, the picture is the beat chain over inputs 0..2 (open, main and close clips), and the overlays follow on the same stream. The wavs move to 3..5 and the music to 6.
  • beat_graph() is now beat_chain() plus its [v] label, so mode 4 prints exactly what it printed before.
  • beat_video_inputs, beat_voice_input and beat_music_input number the inputs.
  • LR_MODE=5 prints the tall graph, then the wide one. It refuses a voice that does not fit, as modes 3 and 4 do.

Tests

On the Railway lab: 19/19 pass, 0 vacuous. The seal is saved and verified. The t27b ledger row is unchanged (blocked, type [N]T).

test asserts
with and without beats, every input from the picture to the music is used exactly once 16
control: without beats the inputs are mode 3's 6

Regression

On the lab, 96 graphs were compared byte for byte, and 0 differ:

Measured with ffmpeg

ffmpeg 5.1.9 on the lab, lesson 1 in English, voice 1300/4200/6800 ms.

  • Inputs: solid red, green and blue clips at each orientation's size; sine phrases of 1.3, 4.2 and 6.8 s; a 16 s sine as music.
  • The text files come from mode 1 (24 files). The graph is 2 lines with 4 xfades and 104 drawtexts.
orientation frames audio pixel (4,4) runs pixel (6,960) runs
tall 1080x1920 450 15.000 s 50 red / 332 green / 38 blue 50 / 332 / 38
wide 1920x1080 450 15.000 s 50 red / 332 green / 38 blue 50 / 332 / 38

The solid runs equal beat_solo_frames, the same as mode 4 in #10605.

Not claimed

🤖 Generated with Claude Code

gHashTag and others added 6 commits October 19, 2026 01:33
…s, crossfaded (Closes #10596)

R4a of epic #10560. lesson_reel.t27 gains a beat clock: cut 0 at the goal
phrase's start (held to a 1250 ms open floor), cut 1 at cta_from, each a
crossfade of reel.t27's TRANSITION_FRAMES centred on the cut. Seven tests:
frame constants, lesson 1's measured cuts, the short-title floor, a grid of
80 voices x every lesson x both languages (full clock, voice too short for an
open scene, refused), weights summing to 1000 at every 10 ms, exactly two
beat changes over 450 frames, and a fixed-thirds control that lands
mid-phrase.

The clock lives in lesson_reel.t27, not in the beat_clock.t27 module the
issue names: importing phrase_start or cta_from into another module leaves
them UNRESOLVED because token_floor_mux.t27 declares the same names, and
the splicer drops ambiguous imports even on an explicit path (#10556).

Lab: 13/13 tests pass, 0 vacuous; seal saved and verified. t27b still
blocked on type [N]T, as the ledger row already says; max_not_pass unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eel (Refs #10596)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ock's cuts, on whole frames (Closes #10603)

lesson_reel.t27 gains:
- beat_xfade_frame: the last base frame at or before c - BEAT_HALF_MS.
- beat_solo_frames: a floor on how many frames each beat shows alone.
- beat_graph: inputs 0..2 (the open, main and close scene clips), crossfaded with xfade at
  offset beat_frame_ms(f) in AV_TIME_BASE, then trimmed to 450 frames.
- LR_MODE=4, which prints the graph and refuses a voice that does not fit, as mode 3 does.

Tests: 17/17 pass, 0 vacuous.
- New: every cut from 1250 to 13500 ms lands on its frame.
- New: the offset for every frame 0..450 selects that frame.
- New control: an offset rounded up misses 300 of 451 frames.
- New: on R4a's voice grid, every beat keeps MIN_SCENE_FRAMES whole frames, except the counted
  early case.

Measured with ffmpeg 5.1 on the Railway lab, on solid-colour inputs, 450 frames each:
- 1300/4200/6800 ms: runs 50 / 332 / 38.
- 2500/8000/9000 ms: runs 88 / 235 / 98.
- 200/1500/13100 ms: runs 28 / 30 / 362.
- 9000/9000/9000 ms: refused.

Every run equals beat_solo_frames, with one extra frame when the offset falls on a whole ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r runs (Refs #10603)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… LR_MODE=5 (Closes #10609)

lesson_reel.t27:
- graph() takes a beats flag. With it, the picture is the beat chain over inputs 0..2 instead of
  input 0, and the voice and music inputs move past the three clips (3..5 and 6).
- beat_graph() is now beat_chain() plus its [v] label, so mode 5 goes on filtering the same stream.
- beat_video_inputs / beat_voice_input / beat_music_input number the inputs.
- LR_MODE=5 prints both orientations and refuses a voice that does not fit.

Tests: 19/19 pass, 0 vacuous. New: every input from the picture to the music is used exactly once
in both layouts. New control: without beats the inputs are mode 3's.

Regression on the Railway lab: 96 graphs (4 lessons, 2 languages, 4 voices; mode 3 against master
and the beat-graph branch, mode 4 against the beat-graph branch) are byte-identical.

Mode 5 rendered with ffmpeg 5.1 from solid-colour clips, sine phrases and music. Tall and wide
each give 450 frames and 15.000 s of audio. Pixels outside the text run 50 red, 332 green and 38
blue frames, which equals beat_solo_frames for 1300/4200/6800 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…th cmp, and three lab traps (Refs #10609)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… clock (Refs #10609)

specs/skills/t27-reel-beat.t27 is a spec-only skill card, in the same shape as the four
t27-social-visual cards from #10570. It points at specs/t27f/lesson_reel.t27 (beat_*
functions, LR_MODE=4 and LR_MODE=5) and copies none of it. It does not cite
reel_factory.t27: lesson_reel does not read the scene plan yet, and the card says so.

Railway lab: 1/1 test, 9 runtime asserts, 0 vacuous. Seal saved and verified. t27b's front end
accepts it (x86 lab: JIT-only CRASH, as for t27-self-host), so the ledger row is pass.

The ledger row sits between providers-steward and self-host. That keeps it one line away from
the four rows #10613 adds after self-host, so the two PRs do not conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 18, 2026
@gHashTag

Copy link
Copy Markdown
Owner Author

Carried by #10627, which contains this branch's commits merged with master (the master merge resolves the conflict #10598's squash-merge left here). Merging #10627 lands this work too; this PR can then be closed.

@gHashTag

Copy link
Copy Markdown
Owner Author

Carried by #10627. That PR's branch holds every commit of this one, and it is mergeable with master: master was merged into it in 919c636e8 and the seals were re-verified on the Railway lab.

This PR is not getting its own master merge. The pre-commit foreign-budget hook refuses any merge that brings in master's #10587 lines (cli/tri/src/main.rs); see #10414. Merging #10627 lands this work. After that, this PR can be closed with no diff left.

@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-18 20:06:48 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 44
PRs with All Checks Green 6
READY 1
FAILING 44
PENDING 0
NO CHECKS YET 0

These columns do not partition: 1 + 44 + 0 + 0 = 45, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=fad12492937a != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@gHashTag
gHashTag enabled auto-merge (squash) October 18, 2026 20:07
gHashTag added a commit that referenced this pull request Oct 18, 2026
…ossfade (#10627)

* t27f: beat clock -- the reel's three beats cut where the voice changes, crossfaded (Closes #10596)

R4a of epic #10560. lesson_reel.t27 gains a beat clock: cut 0 at the goal
phrase's start (held to a 1250 ms open floor), cut 1 at cta_from, each a
crossfade of reel.t27's TRANSITION_FRAMES centred on the cut. Seven tests:
frame constants, lesson 1's measured cuts, the short-title floor, a grid of
80 voices x every lesson x both languages (full clock, voice too short for an
open scene, refused), weights summing to 1000 at every 10 ms, exactly two
beat changes over 450 frames, and a fixed-thirds control that lands
mid-phrase.

The clock lives in lesson_reel.t27, not in the beat_clock.t27 module the
issue names: importing phrase_start or cta_from into another module leaves
them UNRESOLVED because token_floor_mux.t27 declares the same names, and
the splicer drops ambiguous imports even on an explicit path (#10556).

Lab: 13/13 tests pass, 0 vacuous; seal saved and verified. t27b still
blocked on type [N]T, as the ledger row already says; max_not_pass unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f skill: the splicer trap that kept the beat clock inside lesson_reel (Refs #10596)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f: beat graph -- the reel's three scenes crossfaded at the beat clock's cuts, on whole frames (Closes #10603)

lesson_reel.t27 gains:
- beat_xfade_frame: the last base frame at or before c - BEAT_HALF_MS.
- beat_solo_frames: a floor on how many frames each beat shows alone.
- beat_graph: inputs 0..2 (the open, main and close scene clips), crossfaded with xfade at
  offset beat_frame_ms(f) in AV_TIME_BASE, then trimmed to 450 frames.
- LR_MODE=4, which prints the graph and refuses a voice that does not fit, as mode 3 does.

Tests: 17/17 pass, 0 vacuous.
- New: every cut from 1250 to 13500 ms lands on its frame.
- New: the offset for every frame 0..450 selects that frame.
- New control: an offset rounded up misses 300 of 451 frames.
- New: on R4a's voice grid, every beat keeps MIN_SCENE_FRAMES whole frames, except the counted
  early case.

Measured with ffmpeg 5.1 on the Railway lab, on solid-colour inputs, 450 frames each:
- 1300/4200/6800 ms: runs 50 / 332 / 38.
- 2500/8000/9000 ms: runs 88 / 235 / 98.
- 200/1500/13100 ms: runs 28 / 30 / 362.
- 9000/9000/9000 ms: refused.

Every run equals beat_solo_frames, with one extra frame when the offset falls on a whole ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f skill: cut video on a frame and check it by counting solid-colour runs (Refs #10603)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f: lay the reel's captions, cards and address over the beat video, LR_MODE=5 (Closes #10609)

lesson_reel.t27:
- graph() takes a beats flag. With it, the picture is the beat chain over inputs 0..2 instead of
  input 0, and the voice and music inputs move past the three clips (3..5 and 6).
- beat_graph() is now beat_chain() plus its [v] label, so mode 5 goes on filtering the same stream.
- beat_video_inputs / beat_voice_input / beat_music_input number the inputs.
- LR_MODE=5 prints both orientations and refuses a voice that does not fit.

Tests: 19/19 pass, 0 vacuous. New: every input from the picture to the music is used exactly once
in both layouts. New control: without beats the inputs are mode 3's.

Regression on the Railway lab: 96 graphs (4 lessons, 2 languages, 4 voices; mode 3 against master
and the beat-graph branch, mode 4 against the beat-graph branch) are byte-identical.

Mode 5 rendered with ffmpeg 5.1 from solid-colour clips, sine phrases and music. Tall and wide
each give 450 frames and 15.000 s of audio. Pixels outside the text run 50 red, 332 green and 38
blue frames, which equals beat_solo_frames for 1300/4200/6800 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f skill: add a graph mode without moving the old ones, prove it with cmp, and three lab traps (Refs #10609)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* skills: t27/reel-beat card -- TRI CLAW cuts a lesson reel on the beat clock (Refs #10609)

specs/skills/t27-reel-beat.t27 is a spec-only skill card, in the same shape as the four
t27-social-visual cards from #10570. It points at specs/t27f/lesson_reel.t27 (beat_*
functions, LR_MODE=4 and LR_MODE=5) and copies none of it. It does not cite
reel_factory.t27: lesson_reel does not read the scene plan yet, and the card says so.

Railway lab: 1/1 test, 9 runtime asserts, 0 vacuous. Seal saved and verified. t27b's front end
accepts it (x86 lab: JIT-only CRASH, as for t27-self-host), so the ledger row is pass.

The ledger row sits between providers-steward and self-host. That keeps it one line away from
the four rows #10613 adds after self-host, so the two PRs do not conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f: mode 5 overlays live inside one scene -- no caption, tag or address on a beat crossfade (Closes #10614)

LR_MODE=5 now takes every caption, tag and address window from the beat clock, on whole
frames. beat_scene_lo/hi give the frames each scene owns outside its crossfades;
beat_piece_lo/hi clip an overlay to them; beat_piece_kept drops a piece with no room
(fewer than BEAT_OVERLAY_MIN_FRAMES and not the whole overlay) instead of squeezing it; and
print_pieces prints exactly those frames as between(n,lo,hi) terms. Modes 3 and 4 print the
same bytes as before.

Railway lab:
- t27c test-report: 25/25, 0 vacuous; seal saved and verified.
- The grid test covers every lesson, both languages and every voice the clock accepts:
  85437 asserts that no enabled frame is on a crossfade. A control shows mode 3's windows
  over mode 4's cuts put the goal's first caption on one.
- Regression: 216 graphs (modes 3 and 4, 27 lessons, 2 languages, 2 voices) cmp equal to
  the #10610 binary.
- ffmpeg, mode 5 rendered from solid-colour clips, both orientations: a 40-row band over
  the captions shows text on 405 frames and on 0 of the 30 crossfade frames; the tag band,
  404 and 0.

Costs, named in tests and the header: a short caption whose scene ends under it is dropped
(every phrase keeps one); on a voice the reel speeds up the address is held for less than
REEL_CTA_MIN_MS; the cards are still on their own frames, since token_floor_mux prints
their enable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f skill: keep overlays off a crossfade on whole frames, count what is dropped, and two lab traps (Refs #10614)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@gHashTag

Copy link
Copy Markdown
Owner Author

Landed on master through #10627 (squash d61e981), whose branch contains this PR's head commit; its issue is closed. Closing as carried.

@gHashTag gHashTag closed this Oct 18, 2026
auto-merge was automatically disabled October 18, 2026 20:20

Pull request was closed

gHashTag added a commit that referenced this pull request Oct 18, 2026
…ed at BEAT_CUT1_LAST_MS (Closes #10629) (#10632)

* t27f: beat clock -- the reel's three beats cut where the voice changes, crossfaded (Closes #10596)

R4a of epic #10560. lesson_reel.t27 gains a beat clock: cut 0 at the goal
phrase's start (held to a 1250 ms open floor), cut 1 at cta_from, each a
crossfade of reel.t27's TRANSITION_FRAMES centred on the cut. Seven tests:
frame constants, lesson 1's measured cuts, the short-title floor, a grid of
80 voices x every lesson x both languages (full clock, voice too short for an
open scene, refused), weights summing to 1000 at every 10 ms, exactly two
beat changes over 450 frames, and a fixed-thirds control that lands
mid-phrase.

The clock lives in lesson_reel.t27, not in the beat_clock.t27 module the
issue names: importing phrase_start or cta_from into another module leaves
them UNRESOLVED because token_floor_mux.t27 declares the same names, and
the splicer drops ambiguous imports even on an explicit path (#10556).

Lab: 13/13 tests pass, 0 vacuous; seal saved and verified. t27b still
blocked on type [N]T, as the ledger row already says; max_not_pass unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f skill: the splicer trap that kept the beat clock inside lesson_reel (Refs #10596)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f: beat graph -- the reel's three scenes crossfaded at the beat clock's cuts, on whole frames (Closes #10603)

lesson_reel.t27 gains:
- beat_xfade_frame: the last base frame at or before c - BEAT_HALF_MS.
- beat_solo_frames: a floor on how many frames each beat shows alone.
- beat_graph: inputs 0..2 (the open, main and close scene clips), crossfaded with xfade at
  offset beat_frame_ms(f) in AV_TIME_BASE, then trimmed to 450 frames.
- LR_MODE=4, which prints the graph and refuses a voice that does not fit, as mode 3 does.

Tests: 17/17 pass, 0 vacuous.
- New: every cut from 1250 to 13500 ms lands on its frame.
- New: the offset for every frame 0..450 selects that frame.
- New control: an offset rounded up misses 300 of 451 frames.
- New: on R4a's voice grid, every beat keeps MIN_SCENE_FRAMES whole frames, except the counted
  early case.

Measured with ffmpeg 5.1 on the Railway lab, on solid-colour inputs, 450 frames each:
- 1300/4200/6800 ms: runs 50 / 332 / 38.
- 2500/8000/9000 ms: runs 88 / 235 / 98.
- 200/1500/13100 ms: runs 28 / 30 / 362.
- 9000/9000/9000 ms: refused.

Every run equals beat_solo_frames, with one extra frame when the offset falls on a whole ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f skill: cut video on a frame and check it by counting solid-colour runs (Refs #10603)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f: lay the reel's captions, cards and address over the beat video, LR_MODE=5 (Closes #10609)

lesson_reel.t27:
- graph() takes a beats flag. With it, the picture is the beat chain over inputs 0..2 instead of
  input 0, and the voice and music inputs move past the three clips (3..5 and 6).
- beat_graph() is now beat_chain() plus its [v] label, so mode 5 goes on filtering the same stream.
- beat_video_inputs / beat_voice_input / beat_music_input number the inputs.
- LR_MODE=5 prints both orientations and refuses a voice that does not fit.

Tests: 19/19 pass, 0 vacuous. New: every input from the picture to the music is used exactly once
in both layouts. New control: without beats the inputs are mode 3's.

Regression on the Railway lab: 96 graphs (4 lessons, 2 languages, 4 voices; mode 3 against master
and the beat-graph branch, mode 4 against the beat-graph branch) are byte-identical.

Mode 5 rendered with ffmpeg 5.1 from solid-colour clips, sine phrases and music. Tall and wide
each give 450 frames and 15.000 s of audio. Pixels outside the text run 50 red, 332 green and 38
blue frames, which equals beat_solo_frames for 1300/4200/6800 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f skill: add a graph mode without moving the old ones, prove it with cmp, and three lab traps (Refs #10609)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* skills: t27/reel-beat card -- TRI CLAW cuts a lesson reel on the beat clock (Refs #10609)

specs/skills/t27-reel-beat.t27 is a spec-only skill card, in the same shape as the four
t27-social-visual cards from #10570. It points at specs/t27f/lesson_reel.t27 (beat_*
functions, LR_MODE=4 and LR_MODE=5) and copies none of it. It does not cite
reel_factory.t27: lesson_reel does not read the scene plan yet, and the card says so.

Railway lab: 1/1 test, 9 runtime asserts, 0 vacuous. Seal saved and verified. t27b's front end
accepts it (x86 lab: JIT-only CRASH, as for t27-self-host), so the ledger row is pass.

The ledger row sits between providers-steward and self-host. That keeps it one line away from
the four rows #10613 adds after self-host, so the two PRs do not conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f: mode 5 overlays live inside one scene -- no caption, tag or address on a beat crossfade (Closes #10614)

LR_MODE=5 now takes every caption, tag and address window from the beat clock, on whole
frames. beat_scene_lo/hi give the frames each scene owns outside its crossfades;
beat_piece_lo/hi clip an overlay to them; beat_piece_kept drops a piece with no room
(fewer than BEAT_OVERLAY_MIN_FRAMES and not the whole overlay) instead of squeezing it; and
print_pieces prints exactly those frames as between(n,lo,hi) terms. Modes 3 and 4 print the
same bytes as before.

Railway lab:
- t27c test-report: 25/25, 0 vacuous; seal saved and verified.
- The grid test covers every lesson, both languages and every voice the clock accepts:
  85437 asserts that no enabled frame is on a crossfade. A control shows mode 3's windows
  over mode 4's cuts put the goal's first caption on one.
- Regression: 216 graphs (modes 3 and 4, 27 lessons, 2 languages, 2 voices) cmp equal to
  the #10610 binary.
- ffmpeg, mode 5 rendered from solid-colour clips, both orientations: a 40-row band over
  the captions shows text on 405 frames and on 0 of the 30 crossfade frames; the tag band,
  404 and 0.

Costs, named in tests and the header: a short caption whose scene ends under it is dropped
(every phrase keeps one); on a voice the reel speeds up the address is held for less than
REEL_CTA_MIN_MS; the cards are still on their own frames, since token_floor_mux prints
their enable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f skill: keep overlays off a crossfade on whole frames, count what is dropped, and two lab traps (Refs #10614)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f: R4f -- mode 5 holds the address for REEL_CTA_MIN_MS, cut 1 capped at BEAT_CUT1_LAST_MS (Closes #10629)

On a voice that runs long the reel speeds it up so the address starts
at REEL_MS - REEL_CTA_MIN_MS, but the beat clock centred crossfade 1 on
that start, so the close beat owned fewer than 45 frames and mode 5
held the address for less than REEL_CTA_MIN_MS.

beat_cut_ms now caps cut 1 at BEAT_CUT1_LAST_MS = 13249, the latest cut
whose close beat still owns 45 frames (one ms later it owns 44). Tests:
the bound itself with a +1 ms control; every lesson, both languages,
every grid voice the clock accepts holds the address for the floor;
the cost -- on a moved cut crossfade 1 starts under the task's last
words, by at most 184 ms, and lesson 1's measured voice is one of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ledger: t27b row for specs/claw/token_shape.t27 (Refs #10633)

Same line as #10634, so the ratchet on this stack reports UNLISTED 0
before #10634 lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
gHashTag added a commit that referenced this pull request Oct 18, 2026
…s a caller window (Closes #10628) (#10647)

* t27f: beat clock -- the reel's three beats cut where the voice changes, crossfaded (Closes #10596)

R4a of epic #10560. lesson_reel.t27 gains a beat clock: cut 0 at the goal
phrase's start (held to a 1250 ms open floor), cut 1 at cta_from, each a
crossfade of reel.t27's TRANSITION_FRAMES centred on the cut. Seven tests:
frame constants, lesson 1's measured cuts, the short-title floor, a grid of
80 voices x every lesson x both languages (full clock, voice too short for an
open scene, refused), weights summing to 1000 at every 10 ms, exactly two
beat changes over 450 frames, and a fixed-thirds control that lands
mid-phrase.

The clock lives in lesson_reel.t27, not in the beat_clock.t27 module the
issue names: importing phrase_start or cta_from into another module leaves
them UNRESOLVED because token_floor_mux.t27 declares the same names, and
the splicer drops ambiguous imports even on an explicit path (#10556).

Lab: 13/13 tests pass, 0 vacuous; seal saved and verified. t27b still
blocked on type [N]T, as the ledger row already says; max_not_pass unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f skill: the splicer trap that kept the beat clock inside lesson_reel (Refs #10596)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f: beat graph -- the reel's three scenes crossfaded at the beat clock's cuts, on whole frames (Closes #10603)

lesson_reel.t27 gains:
- beat_xfade_frame: the last base frame at or before c - BEAT_HALF_MS.
- beat_solo_frames: a floor on how many frames each beat shows alone.
- beat_graph: inputs 0..2 (the open, main and close scene clips), crossfaded with xfade at
  offset beat_frame_ms(f) in AV_TIME_BASE, then trimmed to 450 frames.
- LR_MODE=4, which prints the graph and refuses a voice that does not fit, as mode 3 does.

Tests: 17/17 pass, 0 vacuous.
- New: every cut from 1250 to 13500 ms lands on its frame.
- New: the offset for every frame 0..450 selects that frame.
- New control: an offset rounded up misses 300 of 451 frames.
- New: on R4a's voice grid, every beat keeps MIN_SCENE_FRAMES whole frames, except the counted
  early case.

Measured with ffmpeg 5.1 on the Railway lab, on solid-colour inputs, 450 frames each:
- 1300/4200/6800 ms: runs 50 / 332 / 38.
- 2500/8000/9000 ms: runs 88 / 235 / 98.
- 200/1500/13100 ms: runs 28 / 30 / 362.
- 9000/9000/9000 ms: refused.

Every run equals beat_solo_frames, with one extra frame when the offset falls on a whole ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f skill: cut video on a frame and check it by counting solid-colour runs (Refs #10603)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f: lay the reel's captions, cards and address over the beat video, LR_MODE=5 (Closes #10609)

lesson_reel.t27:
- graph() takes a beats flag. With it, the picture is the beat chain over inputs 0..2 instead of
  input 0, and the voice and music inputs move past the three clips (3..5 and 6).
- beat_graph() is now beat_chain() plus its [v] label, so mode 5 goes on filtering the same stream.
- beat_video_inputs / beat_voice_input / beat_music_input number the inputs.
- LR_MODE=5 prints both orientations and refuses a voice that does not fit.

Tests: 19/19 pass, 0 vacuous. New: every input from the picture to the music is used exactly once
in both layouts. New control: without beats the inputs are mode 3's.

Regression on the Railway lab: 96 graphs (4 lessons, 2 languages, 4 voices; mode 3 against master
and the beat-graph branch, mode 4 against the beat-graph branch) are byte-identical.

Mode 5 rendered with ffmpeg 5.1 from solid-colour clips, sine phrases and music. Tall and wide
each give 450 frames and 15.000 s of audio. Pixels outside the text run 50 red, 332 green and 38
blue frames, which equals beat_solo_frames for 1300/4200/6800 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f skill: add a graph mode without moving the old ones, prove it with cmp, and three lab traps (Refs #10609)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* skills: t27/reel-beat card -- TRI CLAW cuts a lesson reel on the beat clock (Refs #10609)

specs/skills/t27-reel-beat.t27 is a spec-only skill card, in the same shape as the four
t27-social-visual cards from #10570. It points at specs/t27f/lesson_reel.t27 (beat_*
functions, LR_MODE=4 and LR_MODE=5) and copies none of it. It does not cite
reel_factory.t27: lesson_reel does not read the scene plan yet, and the card says so.

Railway lab: 1/1 test, 9 runtime asserts, 0 vacuous. Seal saved and verified. t27b's front end
accepts it (x86 lab: JIT-only CRASH, as for t27-self-host), so the ledger row is pass.

The ledger row sits between providers-steward and self-host. That keeps it one line away from
the four rows #10613 adds after self-host, so the two PRs do not conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f: mode 5 overlays live inside one scene -- no caption, tag or address on a beat crossfade (Closes #10614)

LR_MODE=5 now takes every caption, tag and address window from the beat clock, on whole
frames. beat_scene_lo/hi give the frames each scene owns outside its crossfades;
beat_piece_lo/hi clip an overlay to them; beat_piece_kept drops a piece with no room
(fewer than BEAT_OVERLAY_MIN_FRAMES and not the whole overlay) instead of squeezing it; and
print_pieces prints exactly those frames as between(n,lo,hi) terms. Modes 3 and 4 print the
same bytes as before.

Railway lab:
- t27c test-report: 25/25, 0 vacuous; seal saved and verified.
- The grid test covers every lesson, both languages and every voice the clock accepts:
  85437 asserts that no enabled frame is on a crossfade. A control shows mode 3's windows
  over mode 4's cuts put the goal's first caption on one.
- Regression: 216 graphs (modes 3 and 4, 27 lessons, 2 languages, 2 voices) cmp equal to
  the #10610 binary.
- ffmpeg, mode 5 rendered from solid-colour clips, both orientations: a 40-row band over
  the captions shows text on 405 frames and on 0 of the 30 crossfade frames; the tag band,
  404 and 0.

Costs, named in tests and the header: a short caption whose scene ends under it is dropped
(every phrase keeps one); on a voice the reel speeds up the address is held for less than
REEL_CTA_MIN_MS; the cards are still on their own frames, since token_floor_mux prints
their enable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f skill: keep overlays off a crossfade on whole frames, count what is dropped, and two lab traps (Refs #10614)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f: R4f -- mode 5 holds the address for REEL_CTA_MIN_MS, cut 1 capped at BEAT_CUT1_LAST_MS (Closes #10629)

On a voice that runs long the reel speeds it up so the address starts
at REEL_MS - REEL_CTA_MIN_MS, but the beat clock centred crossfade 1 on
that start, so the close beat owned fewer than 45 frames and mode 5
held the address for less than REEL_CTA_MIN_MS.

beat_cut_ms now caps cut 1 at BEAT_CUT1_LAST_MS = 13249, the latest cut
whose close beat still owns 45 frames (one ms later it owns 44). Tests:
the bound itself with a +1 ms control; every lesson, both languages,
every grid voice the clock accepts holds the address for the floor;
the cost -- on a moved cut crossfade 1 starts under the task's last
words, by at most 184 ms, and lesson 1's measured voice is one of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ledger: t27b row for specs/claw/token_shape.t27 (Refs #10633)

Same line as #10634, so the ratchet on this stack reports UNLISTED 0
before #10634 lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* t27f: R4e -- mode 5 cards stay inside one scene, token_floor_mux's card printer takes a caller window (Closes #10628)

In LR_MODE=5 the captions, tags and address were clipped to their
scene's own frames, but the cards kept token_floor_mux's enable= and
rose in under crossfade 1 (frames 393..404 on lesson 1's voice).

token_floor_mux: print_ramp, print_rise_from and print_alpha take the
window; print_rise(o, c, dy) and print_fade(c) are thin wrappers that
pass CARD_ON, so token_floor_mux's own graph is byte-identical.

lesson_reel: with beats a card is clipped with beat_piece_* like a
caption, dropped when no piece is kept, rises from its first kept frame
and fades out on its last (new beat_last_hi); card_hi clamps CARD_ON's
450 to the reel's last frame. Modes 3 and 4 are unchanged.

Tests: card windows and beat_last_hi; every lesson, both languages,
every voice the clock accepts puts no card piece on a crossfade, with a
control that token_floor_mux's own windows do meet one (lesson 1's
last card from frame 392 under crossfade 1 at 389, moved to 405).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ledger: t27b row for specs/analog/sar_comparator.t27, UNLISTED 1 -> 0 (Refs #10649)

Same row as #10650 on master, so this PR's ratchet goes green first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

t27f R4c: lay the reel's captions, cards and address over the beat video (LR_MODE=5)

1 participant