Files
LlamaCasty/HANDOFF.md
T
gramps aea0670723 fix(alerts): wire the frame-rate probe so alert-clip VIDEO paces to real time
The 12:35 live session proved the decoder healthy (frames=240 audioChunks=156
failed=False both clips, alert audio in the mix at peakMix 0.277→0.733) yet the
creator still saw 'no video plays / you lost the video'. Root cause: pacing,
not decode. AlertClipDecoderFor built the decoder with no frameRateProbe, so
RunAsync computed frameDuration = TimeSpan.Zero and RunVideoAsync's pace step
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 froze on the LAST
frame while audio chunk-paced its real-time ~7.8s. Reads exactly like a dead
decoder on screen. The media path already wired this seam
(MainViewModel.cs:308); the alert factory never supplied one.

Derivative fix (media/decoder plumbing, cited in broad consensus of players):
pass FfmpegFrameRateProbe into the per-play alert decoder exactly like
MediaVideoSource does. Good Dog:
AlertClipDecoderTests.AlertClipDecoder_PacesVideoFramesToTheProbedFrameRate —
real AlertClipDecoder via the fake-process/fake-probe/delay-recorder seam;
red on the old factory (zero pacing delays), green with the probe (one ~10ms
delay per frame at 100fps). Full suite 321/322, one known-env flake
(RealMouseDrag reorder). Docs in this commit: task-47 addendum,
MyMistakes.md (a decoder that drops data faster than wall-clock looks
identical to a dead one), HANDOFF rewrite.
2026-09-26 12:45:40 -07:00

7.1 KiB
Raw Blame History

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.