Files
LlamaCasty/HANDOFF.md
T
gramps 80038ff152 fix(alerts): silent decoder falls back to the six animations after a 1s no-frame grace
Routing and ruling both good: 'procs in the chat windows but nothing in the alert box'
with zero exceptions — every failure stage was silent by design (Debug.WriteLine-only
preview catch, bare catch{} in RunAsync firing Completed regardless). A decode run that
starts but yields neither a frame nor an audio chunk left _clip != null with
_latestClipFrame == null, so RenderClipFrame returned null forever: the box stayed
transparent for the whole alert and the six-animation fallback (which only ran on
setup-time throws) never fired.

Fix: AlertOverlayLayer.Advance gives a started clip a NoFrameFallbackSeconds (1.0s)
grace — produce no frame AND no audio inside it and the clip is torn down, _elapsed
resets, and the SAME alert continues as the animation branch (never blank, never drained
early). Diagnostics stop the lying silence: BeginClip logs box id/path/size + setup
failures; AlertClipDecoder.RunAsync logs frame/chunk end-state and the exception it used
to swallow.

Good Dog test: SilentDecoder_FallsBackToTheAnimationAfterTheNoFrameGrace (transparent
inside grace, disposed past it, animation renders, drains to idle).

Reference: alert playback must degrade to its fallback on dead-air, never vanish —
same contract as every player/booth failure budget (e.g. OBS source fallbacks).
https://obsproject.com/forum/threads/source-visibility-fallback.187383/
2026-09-26 12:08:27 -07:00

6.6 KiB
Raw Blame History

HANDOFF — 2026-09-26 (TASK 47 shipping crash + the "nothing in the box" void both fixed; 3 local commits, NOT pushed)

Branch / Commit State

main — three 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, both verified). Full suite 320/320.
  3. (uncommitted next — this session) — silent-decoder fallback + diagnostics (see below), scope: Services/AlertOverlayLayer.cs, Services/AlertClipDecoder.cs, ytLive.Tests/AlertLayerVideoTests.cs + docs (task-47 addendum, MyMistakes.md lesson, this file). New Good Dog test green; build 0 warnings; full suite 321 with the known env flake (RealMouseDrag reorder failing while windows are up — passes with the desktop clean; unrelated to this change). 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). MyMistakes.md: every INPC-raising seam fed by the poller must marshal WPF-object writes.

⚠️ The "nothing in the alert box" void — diagnosed + fixed (this unit)

After the crash fix the creator reported: On-Air → test → remove and re-add the Stream Alerts layer → TEST event → procs in the chat windows but nothing in the alert box. startup.log 11:44: peakMix 0.105 (no alert audio) vs 11:21's 0.375–0.891. No exception anywhere — every stage of the clip path was silent by design (UpdatePreview catch = Debug.WriteLine-only; RunAsync bare catch { } firing Completed regardless). Established the guaranteed-invisible fail: a decoder whose Start() succeeds but yields no frames AND no audio leaves _clip != null with _latestClipFrame == null → RenderClipFrame returns null forever → box transparent FOREVER; the six-animation fallback only ran on setup-time throws. (Hunted & ruled out meanwhile: LiveScene is never assigned — the encoder always renders StagedScene (MainViewModel.cs:323), so the re-added box IS in the render graph; the bake invalidates per element on add; DB row c85b46ac-… X=602 Y=26 680×200 healthy; pinned ffmpeg decodes the default clip to scale=680:200 rawvideo correctly to a file. Frame pacing silently off too: no probe → frameDuration=0.)

Fix (uncommitted): AlertOverlayLayer.Advance — a started clip that produced neither a frame nor an audio chunk inside NoFrameFallbackSeconds (1.0 s) is torn down, _elapsed resets, and the SAME alert continues as the six-animation render (never blank, never drained early). Diagnostics: BeginClip logs box id / resolved path / size + setup failure; AlertClipDecoder.RunAsync logs frame/chunk end-state + the swallowed exception. Good Dog test SilentDecoder_FallsBackToTheAnimationAfterTheNoFrameGrace: transparent inside the grace → decoder disposed past it → animation renders → drains to idle. MyMistakes.md: an alert that plays is a promise — degrade on dead-air, and a catch hiding WHY output vanished is a lie.

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 blank after this fix + relaunch: the new logs make the failure conclusive (look for Alert clip start: → Alert clip decode failed → Alert clip end:). The creator's Live scene also has a WebSource "Web Resource-0" bottom-right — confirm which element they watch when describing the "stream-alerts layer".
  • 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.
  • ChatOverlayLayer.OnMessageReceived marshals the poller seam; AlertOverlayLayer mirrors it; the no-frame fallback lives in Advance (has the 1 s grace + diagnostics).
  • 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 "Services/AlertOverlayLayer.cs" "Services/AlertClipDecoder.cs" "ytLive.Tests/AlertLayerVideoTests.cs".
  2. Push the three local commits at the creator's go / milestone signal.
  3. Creator to relaunch and re-run the repro (compare the 11:21 vs 11:44 behavior) — the new logs will say exactly what the decoder did.

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.