Files
LlamaCasty/HANDOFF.md
T
gramps 7b940b6a5a fix(alerts): AlertOverlayLayer marshals the chat-poller seam to the UI thread
Live test session proved the native alert box DID play (the alert ring was the
only source in the mix: micLevel/loopLevel 0.000 while peakMix went
0.375->0.733->0.891 after the sim injections) but the app crashed at
11:22:01.301 the moment a REAL message round-tripped through the poller:

  System.ArgumentException: Must create DependencySource on same Thread as
  the DependencyObject
  at ...MS.Internal.Data.DataBindEngine.ProcessCrossThreadRequests()

OnMessageReceived ran RefreshAlertPreviews on the MTA poller thread and
stamped the WPF-bound VideoImageSource with a WriteableBitmap created there;
the binding engine's cross-thread re-bind killed the process. ChatOverlayLayer
already marshals this exact seam (Dispatcher.Invoke) - mirror it, guarded for
Application.Current null so the pure test seams still run inline.

Good Dog test:
AlertLayerVideoTests.OnMessageReceived_FromPollerThread_MarshalsPreviewWritesToTheUiThread
calls the seam from a raw MTA Thread while the RealApp loop runs, then asserts
on the UI thread that the preview bitmap's Dispatcher is the App's (red on the
old seam, green on the fix). WPF cross-thread rule recorded in MyMistakes.md.

Full suite 320/320, clean build 0 warnings, scope-check green.
2026-09-26 11:28:36 -07:00

5.4 KiB
Raw Blame History

HANDOFF — 2026-09-26 (TASK 47 shipping crash fixed; 2 local commits, NOT pushed)

Branch / Commit State

main — two local commits ahead of origin/main (= 2c9af94... actually 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, 318/319 with the known env flake → final full run 319/319 green).
  2. (uncommitted next) — the 2026-09-26 live-crash fix (see below), scope = 3 files + docs: Services/AlertOverlayLayer.cs, ytLive.Tests/AlertLayerVideoTests.cs, plus docs (TASKS/task-47-alert-videos.md addendum, MyMistakes.md lesson, this file). Full suite 320/320 green, alert tests 3/3, scope-check.sh passed.

⚠️ The live crash + fix (this unit)

The creator ran the shipped build in a test session: test-tab alerts DID fire (button commands run on the UI thread — startup.log 11:21:53 shows the sims and alert-clip audio in the live mix: micLevel=0.000 loopLevel=0.000 yet peakMix 0.375 → 0.733 → 0.891, which can only be the alert ring; the default clip is materialized at %TEMP%\ytLive-alert-941785b0-….mp4, the video pipe verified against the pinned ffmpeg), but the app crashed at 11:22:01.301 — the first REAL message that round-tripped through the chat poller:

System.ArgumentException: Must create DependencySource on same Thread as the DependencyObject.
at ...MS.Internal.Data.DataBindEngine.ProcessCrossThreadRequests()

Root cause: AlertOverlayLayer.OnMessageReceived had NO UI-thread marshal while ChatOverlayLayer.OnMessageReceived has one. A polled message ran RefreshAlertPreviews → UpdatePreview on the MTA poller thread and stamped alertBox.VideoImageSource (INPC-raised + WPF-bound) with a WriteableBitmap created there → binding engine cross-thread re-bind → crash. Fix: marshal the whole ingest via Application.Current.Dispatcher.Invoke (null-guarded so pure seams still run inline). Good Dog regression test AlertLayerVideoTests.OnMessageReceived_FromPollerThread_MarshalsPreviewWritesToTheUiThread (calls the seam from a raw MTA Thread, returns to the message loop, asserts on the UI thread that VideoImageSource.Dispatcher is the App's dispatcher — red pre-fix, green post-fix, verified both ways). Rule written into MyMistakes.md: every INPC-raising seam fed by the chat poller must marshal WPF-object writes to the UI thread.

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 — all queued unchanged.
  • If the creator still reports "nothing in the box" after this fix (their Live scene's "Stream Alerts" AlertBox is at 612,45 680×200; there is also a WebSource "Web Resource-0" bottom-right — confirm which element they're watching), investigate the video-render path live — but note all evidence (audio in the mix, materialized clip, verified pipe) says the native box played.

Around the task (carried facts)

  • RealMouseDrag tests no-op while a game/fullscreen window steals the mouse (POE 2 seen 2026-09-22) — 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. $99/yr Polar must become one-time before launch. MARCOM.md/MONETIZATION.md gitignored — never commit.
  • API facts (TASK 44 + 45): liveChatId unavailable before the broadcast is live (poll until active); 400 MISSING_REQUIRED_FIELD from liveChat/messages.insert = wrong body shape (missing snippet.type), NOT auth/scope.
  • 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, never the refcounted MediaVideoSourceManager. DB stays user_version 10 — TASK 47's Source columns are additive guarded ALTERs. Recipe in MyMistakes.md.
  • ChatOverlayLayer.OnMessageReceived marshals the poller seam; AlertOverlayLayer now mirrors it.
  • Every committed change needs a close + relaunch of the running app to be seen.

Next step

  1. Commit the crash fix (declared scope + same-commit docs), then run ./scripts/scope-check.sh "Services/AlertOverlayLayer.cs" "ytLive.Tests/AlertLayerVideoTests.cs".
  2. Full clean build (0 warnings, Windows dotnet host) + full vstest 320/320.
  3. Push a11b15e + the fix commit at the creator's go / milestone signal.
  4. Creator to relaunch and re-run the test-tab alerts (one of each) + post a real chat message — the crash is fixed; then confirm which visual element they call the "web-alert box".

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 (verify only, never incremental). One runtime model.
  • No menus/polls with the creator; decisions asked once then held; do the work, then report.