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

95 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.