commit cd3b127f7411811d5d559e5af12a35d1ae3f4809
parent ab896086dcd349edcd13f4b1b4d8db304408ffce
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Thu, 1 Oct 2026 14:35:48 -0400
plans: finale B2 as built — the review fixes, muteFrom, render.endFade, the fitted QR host label, the gates
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
1 file changed, 64 insertions(+), 0 deletions(-)
diff --git a/plans/deck-posts.md b/plans/deck-posts.md
@@ -205,3 +205,67 @@ Left for R2 (umtool):
for a cut that was never fully built.
- The preview's posts geometry is now the frame's edge (`postsGeometry`). The preview does not
show the footage move or the hold; both happen in ffmpeg, at the join.
+
+Both items above were done after R1: R2 (b584c129) shows the move and the hold in the preview, and
+df062b37 reads a deck cut's offsets from its `schedule.json` when there is no `chapters.ffmeta`.
+
+## Finale B2, as built
+
+Branch `deck/finale-b2` from df062b37. It starts with the fixes from the read-only review of the
+room work (SHIP AFTER FIXES).
+
+| Commit | What |
+|---|---|
+| 2f0e852b | Review fixes. The footage move now runs AFTER the hold, so a first post inside the hold still moves the frozen frame (before, it never moved or froze part-way). verify-build's freeze check samples only between the hold's start (or the move's landing) and the dissolve, and reports under three frames of still picture as not checked. `postHolds` rounds a hold to whole frames. The README and changelog say `--no-chrome` still holds and moves. |
+| 2ea53246 | A clip's `muteFrom` and `render.endFade`, joined on that input's chain; the `<id>.cut.json` record beside each clip segment; validation; the QR host label fitted to the code's height |
+| ebe10c84 | README, quirks, one `[Unreleased]` bullet |
+
+Where it adds to the rulings above:
+
+- **`muteFrom`** is in SOURCE seconds and must lie within the clip's `start`–`end`. The sound is
+ silent from that second, after a 40 ms `afade` that ENDS there, and it is digital zeros after
+ that. A hold on the clip stays silent. The mute is mapped to the segment's clock through
+ `<id>.cut.json`, which every clip build now writes: the source seconds the segment was really
+ cut from after snapping. A segment with no record, or one whose record does not match its
+ length, falls back to the unsnapped `playWindow` start, and the build says so in a note.
+- **`render.endFade`** applies to the cut's last segment, whatever it is, hold included. The
+ picture reaches `palette.bg` and the sound reaches silence on the last frame. The picture fade
+ is a `geq` blend in yuv420p, enabled from its first frame. `fade=…:color=` would work too, but
+ only in RGB, and the concat filter would then convert every segment of the cut to rgb24 and back.
+- Both are joins (`withCutEdits`, `cutJoins`), so they apply with or without the deck, on
+ crossfades and hard cuts, and `--chrome-only` changes them without rebuilding a segment. The
+ hard-cut record names them only when present. `validateCutEdits` (in `deck.mjs`) is what the
+ build refuses with, and `validateChrome` also checks `endFade`.
+- **The QR's host label** is sized at load from the string's ink in the loaded face (canvas
+ `measureText`). Tracking counts between letters only, and the first side bearing is indented
+ away. On the ferret cut its ink covers rows 31–180, the same rows as the code. Before, it
+ covered 54–180.
+
+Gates on ebe10c84:
+
+- Workspace tsc clean. `test:scripts` 341 pass, 2 skipped. Capped umtool `next build` with the
+ corpus linked exit 0 (31 s), link removed.
+- Byte-identical: `--only c07 --skip-fetch` without `render.chrome` gives md5
+ `6a92235fa12ca181bb81993129c9ee9d`. The unchanged ferret deck manifest writes a `schedule.json`
+ byte-equal at df062b37 and at the tip. With no muteFrom or endFade, the crossfade graph, the
+ hard-cut graph and the record equal 2f0e852b's. Against df062b37 the only difference is the
+ move and hold order on the four carrying clips.
+- Ferret, scratch copy with c20 `muteFrom: 24029.30` and `endFade: 1.0`, `--chrome-only` over
+ copied segments with no cut records:
+ - c20 muted 6.70 s into its segment, falling back to the unsnapped start, which the run notes;
+ - 360.2 s, verify-build ok, all four freezes found;
+ - QR 17/17 deck and 7/7 posts;
+ - the last sample that is not zero is at 359.613 s of the audio, and everything after it is
+ digital silence;
+ - the last frame above the deck is bg (mean 1.0, worst 3 levels over rows 0–881).
+
+Found and left:
+
+- **The crossfade concat drifts the sound ahead of the picture, before this branch too.**
+ Crossfaded segments have audio up to 40 ms shorter than their video (AAC framing), and
+ `acrossfade` joins the sound by its own lengths while `xfade` uses the picture's. The ferret
+ cut's audio is 359.901 s against 360.200 s of picture, as in the build made before this branch, so by the last
+ clip the sound runs 0.3 s early. A `muteFrom` silences the right sound in the clip's own audio,
+ but in the final it falls up to that drift before the matching picture. Padding each input's
+ sound to its picture's length (`apad=whole_dur`) before the join would fix it, but that changes
+ every crossfaded cut's graph.