commit a394cf1a7f1133e7e44029468fc834baf48179c1
parent cd08a5f7b217b47e2989a67ae057b9f46082a3f5
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Thu, 1 Oct 2026 15:47:18 -0400
plans: the teaser (T1), as built — the line shape, hits, --chrome-only building teasers, the gates
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
1 file changed, 61 insertions(+), 0 deletions(-)
diff --git a/plans/deck-posts.md b/plans/deck-posts.md
@@ -267,3 +267,64 @@ Found and left:
the last clip (2.0 s on revision 2, with cards). `xfadeGraph` now pins each input's sound to its
length in the cut (`apad=whole_dur`, `atrim=end`) before the join, so a `muteFrom` lands on its
picture too. Every crossfaded cut's graph changed; `av-sync.test.mjs` holds it with real ffmpeg.
+
+## Teaser T1, as built
+
+Branch `deck/teaser-t1` from 810e423e. A `teaser` timeline entry: a full-frame season-teaser card
+after the last clip, its words the manifest's, a trailer hit under each pop.
+
+| Commit | What |
+|---|---|
+| 3c4ab080 | `deck.mjs`: `teaser` in `CARD_TYPES` (the deck hides over it whatever `overCards` says), `teaserLines`/`teaserTail`/`teaserTitle`, `validateTeaser`/`validateTeasers`, `TEASER_MOTION` + `teaserTimes` (the one copy of the timing) and `teaserHits`; `chrome-teaser.mjs` (the page, `teaserCues`); compose-chrome region `teaser` by dynamic import; build-video `buildTeaserSegment`, `teaserAudioGraph`, `teaserEncodeArgs`, `teaserSegmentKey`, the chapter, `--chrome-only` building teasers, `--fetch-only` a no-op on one; verify-build `verifyTeasers`; `chrome-teaser.test.mjs`, `teaser-audio.test.mjs` |
+| a82aae0e | umtool: the report page's row and the export's fallback chapter name a teaser by its lines; `onscreen-fixture` carries a teaser and `onscreen.spec` checks the table, the preview's node and the row; the supporting lines a size up |
+| c51925ae | README section, four quirks, one `[Unreleased]` bullet |
+
+Where it adds to, or differs from, the brief:
+
+- **A line is a string or `{ text, break }`** — `break` is the END of `text`, drawn as a smaller,
+ wide-tracked second tier 0.3 s after the rest; the whole `text` is what the chapter and umtool
+ show. There are no quotation marks and no `quote` field (the operator dropped them).
+- **Roles follow position**: with three or more lines the first is the overline, the last the
+ kicker, the rest titles; two are overline + title; one is a title.
+- **`hits`** (default true): a synthesised hit under each pop and a swell under the tail; false is
+ digital silence. The hits are placed by `teaserTimes`, the times the cues are built from, and
+ land on their pop's sample (a real-ffmpeg test differences the graph with and without each hit).
+- **`--chrome-only` builds teaser segments** instead of refusing: a teaser is chrome (graphics made
+ from the manifest, nothing fetched). Its segment is re-encoded only when its key — the frames'
+ render key and the whole sound graph — differs from `<id>.teaser.json`'s.
+- **The deck hides over a teaser even with `overCards: "show"`**: it is full frame, never framed
+ into the footage box.
+- The fit (a line wider than 80 % of the frame shrinks) runs once the face is in, as the deck's
+ title fit does; it changes sizes, never a time.
+
+Gates on c51925ae:
+
+- Workspace tsc clean (102 s). `test:scripts` 355 pass, 1 skipped (356). Capped umtool
+ `next build` with the corpus linked exit 0 (58 s, under a concurrent encode), link removed;
+ Turbopack bundles the teaser page and its face as their own server chunk.
+- Byte-identical: `--only c07 --skip-fetch` without `render.chrome` gives md5
+ `6a92235fa12ca181bb81993129c9ee9d`. The ferret deck manifest without a teaser measures the same
+ schedule at 810e423e and at the tip (`measureChromeSchedule`, byte-equal); with the teaser, the
+ first 17 segments, the posts and the moves equal the ferret's built `schedule.json`.
+- Ferret, scratch copy with the teaser after c20, `--chrome-only` over copied segments:
+ - the teaser rendered in 77–86 s (210 frames), the deck (11,001 frames) in 4 min 7 s; a
+ re-run after a design change re-rendered the teaser only (deck and posts `cached`, 573 s);
+ - 366.7 s (360.2 + 7 − 0.5), video and audio both 366.700 s; verify-build ok, every hold
+ frozen, `teaser fin: 210/210 frame(s), segment encoded from them`;
+ - QR 17/17 deck and 7/7 posts; 18 chapters, the last "Pirate Software — The Largest Ferret
+ Rescue in the United States — February 2027 ?" at 360.2 s;
+ - the last frame is bg (Y′CbCr 30/132/128, uniform, against 31/132/128); the sound is digital
+ silence for its last 39 ms;
+ - loudness: the cut −17.7 LUFS integrated, the teaser's 7 s −19.7 LUFS, sample peak −6.0 dBFS.
+- umtool e2e `onscreen onscreen-posts build projects`: 43 passed, 1 failed (2.9 min). The failure
+ is `onscreen-posts.spec.ts:264`, and it predates this branch: 2f0e852b rounds a hold to whole
+ frames, and at the posts fixture's 15 fps 2.5 s is 38 frames (2.533 s), while the spec still
+ expects 2.5. No teaser is in that fixture.
+
+Found and left:
+
+- umtool cannot edit a teaser's lines. It needs a writer (`updateTeaser` through
+ `withManifestLock` and `validateTeaser`), a route, a form (a field per line with a break
+ picker) and a still of the composition (compose-chrome's `--still`, as the deck's still route
+ does).
+- `onscreen-posts.spec.ts`'s timings at 15 fps (above).