# HANDOFF — 2026-09-26 (TASK 47: crash + silent-void + video-pacing all fixed; 4 local commits, NOT pushed) ## Branch / Commit State `main` — four local commits ahead of `origin/main` (= `ecb329e`), **NOT pushed** (awaiting the creator's go / milestone signal): 1. `a11b15e` — **TASK 47** alert box video (built-in/custom clip + read-time fade + message ticker + unity audio) + `AlertLayerVideoTests` (fake decoder, 0 warnings, 319/319 green). 2. `7b940b6` — **crash fix**: AlertOverlayLayer marshals the chat-poller seam to the UI thread (regression test red pre-fix / green post-fix). Full suite 320/320. 3. `80038ff` — **silent-decoder fallback + diagnostics** (no-frame grace 1.0s → six-animation; BeginClip/RunAsync logging). Full suite 321 with the **known env flake** (RealMouseDrag reorder failing while windows are up). 4. *(uncommitted next — this session)* — **video pacing fix** (below). Scope: `ViewModels/MainViewModel.Chat.cs`, `ytLive.Tests/AlertClipDecoderTests.cs` (new), docs (task-47 addendum, `MyMistakes.md`, this file). Test green; build 0 warnings; `scope-check.sh` pending, then commit. ## ✅ Crash fixed (`7b940b6`) The creator's first live session: test-tab alerts DID fire (11:21 startup.log: alert-clip audio in the mix, `peakMix 0.375 → 0.891` with mic+loop at 0.000) but the app crashed at `11:22:01.301` on the first REAL polled message: `Must create DependencySource on same Thread as the DependencyObject` in `DataBindEngine.ProcessCrossThreadRequests`. `AlertOverlayLayer.OnMessageReceived` had no UI-thread marshal (`ChatOverlayLayer` has one); it ran `RefreshAlertPreviews` → `UpdatePreview` on the MTA poller thread and stamped `alertBox.VideoImageSource` (INPC + WPF-bound) with a WriteableBitmap created there. Fix + Good Dog regression test `OnMessageReceived_FromPollerThread_MarshalsPreviewWritesToTheUiThread` (red/green verified). ## ✅ "Nothing in the alert box" void — fixed (`80038ff`) After the crash fix: On-Air → test → **remove and re-add** the Stream Alerts layer → TEST event → procs in chat but **nothing in the alert box**. startup.log 11:44 `peakMix 0.105` (no alert audio) vs 11:21's 0.375–0.891. Every stage of the clip path was silent by design; a decoder whose `Start()` succeeds but yields **no frames AND no audio** left `_clip != null` with `_latestClipFrame == null` → blank box forever. Fix: no-frame grace (1.0s) in `AlertOverlayLayer.Advance` → tear down + same alert as six-animation; BeginClip/RunAsync diagnostics stop the silence. Good Dog: `SilentDecoder_FallsBackToTheAnimationAfterTheNoFrameGrace`. ## ✅ "No video plays" — decoder HEALTHY, video UNPACED — fixed (this unit) Creator repro after `80038ff` (12:35 session): still "no video plays in the web-alert box", "like you lost the video" — but the new diagnostics PROVED the decoder healthy: `Alert clip start: box …` → `Alert clip end: … frames=240 audioChunks=156 failed=False` twice, alert audio in the live mix (`peakMix 0.277 → 0.733`, mic/loop 0.000). Root cause was **pacing, never decode**: `AlertClipDecoderFor` (MainViewModel.Chat.cs:113) built the decoder with **no frame-rate probe** → `RunAsync` computed `frameDuration = TimeSpan.Zero` → `RunVideoAsync`'s pace step (`if (frameDuration > 0)`) was dead code → all 240 frames of the 10s clip dumped through the pipe in the first ~1-2s (130MB as fast as ffmpeg read), then the box sat frozen on the LAST frame while the audio pipeline paced 156 chunks at real-time ~7.8s. Reading exactly like "no video / you lost it". The media path already had the seam wired (`MediaVideoSource` gets `frameRateProbe: new FfmpegFrameRateProbe(…)`, MainViewModel.cs:308) — the alert factory just never passed one. **Fix (uncommitted):** `AlertClipDecoderFor` passes `frameRateProbe: new FfmpegFrameRateProbe(new FfmpegLocator(), () => new FfmpegDecodeProcess())` so video paces at the clip's native ~24fps (10s real-time) in lockstep with audio. Good Dog test `AlertClipDecoderTests.AlertClipDecoder_PacesVideoFramesToTheProbedFrameRate` drives the REAL `AlertClipDecoder` through the media-style fake process + fake probe + recorded delay seam (3 frames at 100fps → one ~10ms delay per frame; red on the old factory, green now). Full suite 321/322 (one known-env flake). `MyMistakes.md`: **a decoder that drops data faster than wall-clock looks identical to a dead decoder on screen — "plays but you don't see it" is a PACING bug before it's a decode bug.** Rules: never ship a pipe whose `frameDuration` can be zero; check pacing (bytes/s vs wall-clock) before re-auditing the codec. ## Follow-ups queued (NOT done in this unit) - **TASK 3 item 20 persistence half** — canonical `RewardEvents` SQLite table + `superChatEvents.list` (30-day) backfill + session-report rollup (still in-memory only). - TASK 3 item 16 (Text source); TASK 40 units A/C/D; TASK 32–36; TASK 12 master limiter — queued. - If the alert box is STILL "no video" after this fix + relaunch: check the 12:35-style `Alert clip end: … frames=240` line (now logged) — a healthy decode with no visual means the painter/render path, not the decoder; the creator's Live scene also has a WebSource "Web Resource-0" bottom-right — confirm which element they watch when describing the "web-alert / stream-alerts" box. - The known pump stall (`render=full-render … totalMs=147`, ~6fps worst) is recorded as a pre-existing perf item, NOT part of this unit. ## Around the task (carried facts) - RealMouseDrag tests no-op while a game/fullscreen window steals the mouse — **close fullscreen windows before full-suite runs**; passes in isolation. - Test-env trap: saved OAuth session loads synchronously → force `vm.IsConnected` in signed-in tests. - `subscriberCount` YPP slice 2 needs re-consent — do not merge with other units. `MARCOM.md`/ `MONETIZATION.md` gitignored — never commit. - API facts (TASK 44 + 45): liveChatId unavailable until broadcast live; `400 MISSING_REQUIRED_FIELD` from `liveChat/messages.insert` = wrong body shape (missing `snippet.type`). - TASK 47 sticky facts: bgra from ffmpeg is opaque (alpha=255); audio pipe = carry-buffer loop; default clip = `%TEMP%\ytLive-alert-{id}.mp4`; decoder per-play; DB stays `user_version 10`. - Every committed change needs a **close + relaunch** of the running app to be seen. ## Next step 1. Commit this unit with the declared scope `./scripts/scope-check.sh "ViewModels/MainViewModel.Chat.cs" "ytLive.Tests/AlertClipDecoderTests.cs"`. 2. Push the four local commits at the creator's go / milestone signal. 3. Creator to relaunch and re-run the repro — with the probe wired the clip plays real-time (~10s of motion, not a blur + frozen frame); the `Alert clip end:` line confirms decode health. ## Critical working rules (unchanged, still binding) - **Good Dog = ONE integration test per change.** Scope lock + `./scripts/scope-check.sh` before commit. - Windows dotnet host for all WSL builds (`/mnt/c/Program Files/dotnet/dotnet.exe`, quoted paths). - 0 warnings on real clean builds. One commit = fix + its memory (task doc, MyMistakes, handoff). - No menus/polls with the creator; decisions asked once then held; do the work, then report.