Skip to content

t27f R4b.1: beat graph -- the reel's three scenes crossfaded at the beat clock's cuts, on whole frames #10603

Closed

Description

@gHashTag

Sub-issue of epic #10560 (content factory), R4b part 1. It builds on R4a (#10596, PR #10598).

R4a's beat clock says when each of a reel's three beats (open, main, close) is on screen. Nothing yet turns those cuts into video.

How the reference tools do it:

  • Remotion's TransitionSeries takes per-sequence frame counts and overlapping transitions.
  • ffmpeg's xfade takes an offset in seconds and starts reading the incoming stream at that offset.

Cuts given in milliseconds also do not sit on frames. A crossfade that starts between two frames shifts by up to one frame, and two cuts rounded independently could take a frame from a scene that has exactly MIN_SCENE_FRAMES.

Boundary

User Scenarios

  • A frame renderer that has the three scene clips runs one ffmpeg command with the mode 4 graph. It gets a 450-frame reel whose picture changes when the goal starts being spoken and when the address appears.
  • A reviewer reads the crossfade start frames for any lesson and voice, without rendering.

Requirements

  • R1. The crossfade for cut c starts at frame floor((c - 250) * BASE_FPS / 1000) and lasts TRANSITION_FRAMES.
  • R2. On every voice of R4a's grid where the clock gives every beat a scene, every beat is alone for at least MIN_SCENE_FRAMES whole frames once both cuts are on frames.
  • R3. The xfade offset printed for a start frame f is beat_frame_ms(f). It selects frame f and no earlier one: beat_frame_ms(f) * BASE_FPS lies in ((f - 1) * 1000, f * 1000].
  • R4. Mode 4 refuses a voice that does not fit, exactly as mode 3 does.
  • R5. Measured on the Railway lab with ffmpeg: the graph run on three solid-colour 15 s inputs gives 450 frames. The frame just before each crossfade is still the outgoing colour, and the frame just after it is the incoming colour.

Success Criteria

  • t27c test-report specs/t27f/lesson_reel.t27 passes on the lab with 0 vacuous tests.
  • Seal saved and verified; the ledger row is unchanged; max_not_pass is unchanged.
  • The PR records the ffmpeg measurement: frame count, and the pixel colour at the frames around each cut.

Requirements

  • FR-001: The crossfade for cut c MUST start at frame floor((c - 250) * BASE_FPS / 1000) and MUST last TRANSITION_FRAMES.
  • FR-002: On every voice of R4a's grid where the clock gives every beat a scene, every beat MUST be alone for at least MIN_SCENE_FRAMES whole frames once both cuts are on frames.
  • FR-003: The xfade offset printed for a start frame f MUST be beat_frame_ms(f). It MUST select frame f and no earlier one: beat_frame_ms(f) * BASE_FPS MUST lie in ((f - 1) * 1000, f * 1000].
  • FR-004: Mode 4 MUST refuse a voice that does not fit, exactly as mode 3 does.
  • FR-005: Measured on the Railway lab with ffmpeg, the graph run on three solid-colour 15 s inputs MUST give 450 frames. The frame just before each crossfade MUST still be the outgoing colour, and the frame just after it MUST be the incoming colour.

Activity

  1. added
    shape-refusedClosed by the issue-shape gate (specs/policy/issue_shape.t27); a passing edit reopens it
    on Oct 18, 2026
  2. github-actions commented on Oct 18, 2026

    @github-actions
    Contributor

    Closed by the issue-shape gate (specs/policy/issue_shape.t27, #7498): an issue must have the template's sections. Edit the body; an edit that passes reopens it.

    • missing: ## Requirements -- numbered obligations, FR-001: ... MUST ...
  3. added 3 commits that reference this issue on Oct 18, 2026
    d61e981
    8c1ca72
    8c7a2c2
  4. removed
    shape-refusedClosed by the issue-shape gate (specs/policy/issue_shape.t27); a passing edit reopens it
    on Oct 19, 2026
  5. gHashTag commented on Oct 19, 2026

    @gHashTag
    OwnerAuthor

    Reopened by the self-heal of #11007. The issue-shape gate closed this issue with a queued run that judged an older body from its event payload; the current body passes gen/c/policy/issue_shape.c, and the gate's own contract is that a passing body stays open. If the work is in fact finished or obsolete, close it as completed.

  6. gHashTag commented on Oct 19, 2026

    @gHashTag
    OwnerAuthor

    Landed on master by #10627 (merged 2026-10-18, d61e981), which carried this work after its own PR was closed. Closing as done. Tracked in mega epic #11038.

    🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions