TASK 47: draw the alert clip — the preview Image was Collapsed for AlertBox

Four builds (89/90 + two) burned proving the alert video was PERFECT: real h264
1280x720, 240 frames decoded, alert audio in the live mix, and the shipped asset
byte-identical (md5 0ee1f496…) to the creator's llamacasty-dancingLlama-thankyou.mp4
with every sampled frame full bright content. Build 90's new diagnostics then showed
the frame reaching BOTH consumers every second for the whole clip —
`Alert preview: frame=680x200 a255` and `Output resolver: frame 680x200
playing=True` — an opaque, correctly-sized frame, handed over and never seen.

Root cause: Controls/PreviewPane.xaml keeps the per-element Image Collapsed unless a
DataTrigger fires, and there was no IsAlertBox trigger (only IsImageSource / IsChatBox /
IsWebSource / IsWebcam). The alert box's Image was therefore Collapsed forever. Chat
boxes rendered because they HAVE a trigger — that asymmetry is the whole clue, and it
is why removing/re-adding the layer could never fix it.

Fix: an IsAlertBox DataTrigger beside the IsChatBox one.

Two Good Dog tests, because the existing fakes had been hiding this:
- AlertBoxPreviewVisibilityTests (red `Expected: Visible / Actual: Collapsed`, now
  green) drives the real MainWindow + PreviewPane and asserts the bound Image is
  Visible — the first alert test that crosses the XAML at all.
- AlertClipOutputTests is the first test in the repo to run a REAL codec: pinned ffmpeg
  generates a clip, the real AlertClipDecoder + AlertOverlayLayer + SceneCompositor
  composite it, and the box rect must be a colourful picture that DIFFERS from idle. It
  passed while the feature was broken in the app — which is exactly why it was needed:
  it exonerated decode+layer+compositor and pointed the hunt at the last hop.

Also keeps the two once-per-second alert diagnostics (resolver + preview) that settled
it, pending the creator's call on whether to keep them.

Notes: the pinned BtbN ffmpeg has no libx264, so the test clip is -c:v mpeg4.
SceneCompositor.Render fills its base with opaque black, so the test compares against an
idle render rather than counting non-zero bytes. MediaSource has the same missing trigger
in PreviewPane.xaml — left unfixed as out of scope, noted in HANDOFF.

Full suite 324/324. Lesson recorded in MyMistakes.md rule (5): a producer that hands over
a correct frame has still delivered nothing — when output arrives and the user still sees
nothing, stop auditing the producer and audit the consumer's VISIBILITY.
This commit is contained in:
2026-09-26 14:44:57 -07:00
parent aea0670723
commit e2ecc248e0
8 changed files with 456 additions and 99 deletions
+64 -98
View File
@@ -1,106 +1,72 @@
# HANDOFF — 2026-09-26 (TASK 47: crash + silent-void + video-pacing all fixed; 4 local commits, NOT pushed)
# HANDOFF — 2026-09-26, end of session
## Branch / Commit State
## Where we are
`main`, **5 local commits ahead of `origin/main` (= `ecb329e`), NOTHING PUSHED.**
Last pushed commit is still `ecb329e`. Do not push without the creator saying so.
`main` — four local commits ahead of `origin/main` (= `ecb329e`), **NOT pushed** (awaiting the
creator's go / milestone signal):
| commit | what |
|---|---|
| `a11b15e` | TASK 47 alert video (first landing) |
| `7b940b6` | poller-thread marshal crash fix |
| `80038ff` | silent-decoder fallback + diagnostics |
| `aea0670` | video pacing via `FfmpegFrameRateProbe` |
| (last, unpushed) | **the real alert fix: `PreviewPane.xaml` `IsAlertBox` trigger** + 2 Good Dog tests + resolver/preview diagnostics |
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.
## THE ANSWER (do not re-derive this)
The alert video was **never drawn**, never mis-decoded. Build 90's diagnostics showed
`Alert preview: frame=680x200 a255` and `Output resolver: frame 680x200 playing=True`
every second for the whole clip — a real, opaque, correctly-sized frame reaching BOTH
consumers. `Controls/PreviewPane.xaml` keeps the per-element `Image` `Collapsed` unless a
DataTrigger fires, and there was **no `IsAlertBox` trigger** (only IsImageSource /
IsChatBox / IsWebSource / IsWebcam). Chat boxes rendered; alert boxes never could.
Removing/re-adding the layer could not have fixed it.
## ✅ Crash fixed (`7b940b6`)
Clip itself is fine: DB asset `941785b0-d022-45fa-a8a2-2cd59ae2ba48` is byte-identical
(md5 `0ee1f4960dd3b23dee5930a2af79d410`) to the creator's
`C:\Users\gramp\Downloads\llamacasty-dancingLlama-thankyou.mp4` — h264 1280x720 10s,
every sampled frame full bright content.
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).
## Working tree
Clean apart from the uncommitted work unit above (it is committed; check `git status`).
If the app is running, `ytLive.dll`/`.exe` are LOCKED and any build fails with MSB3021/
MSB3027 — the creator must close ytLive first. That is normal, not a broken build.
## ✅ "Nothing in the alert box" void — fixed (`80038ff`)
## Build / test commands (Windows host, always)
```bash
"/mnt/c/Program Files/dotnet/dotnet.exe" build "C:\Users\gramp\Documents\Code\projects\ytLive\ytLive.csproj" --no-restore
"/mnt/c/Program Files/dotnet/dotnet.exe" vstest "C:\Users\gramp\Documents\Code\projects\ytLive\ytLive.Tests\bin\Debug\net8.0-windows10.0.19041.0\ytLive.Tests.dll"
```
Last full suite: **324/324 pass**. Run it with ytLive CLOSED or the RealMouseDrag env
flake fires.
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`.
## Queued next (creator's words, 2026-09-26) — the Good Dog queue
1. **Test-console chat must not clear the chat window.** "when I add a chat message from
the test console, or from any console, that chat text should not clear the current
chat window."
2. **Test pull-out chat input: 20px right padding** — the text box is clipped at the right
edge of the div; needs 20px padding against the div's right side.
3. **Test pull-out chat input: Enter inserts a newline**, it must not send.
4. Verify the alert video live on the next build (the fix is in; nobody has SEEN it play
yet). This is the confirmation step for the commit above.
## ✅ "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.
## Landmines / facts worth keeping
- The pinned BtbN ffmpeg has **no libx264** — generate test clips with `-c:v mpeg4`.
- `SceneCompositor.Render` fills its base with **opaque black**, so "any non-zero byte"
is not proof a layer painted. Compare against an idle render, or count COLOURFUL px.
- `sqlite3` on WSL: `/home/gramps/android-sdk/platform-tools/sqlite3`. Extract a BLOB with
`SELECT writefile('/tmp/opencode/x.bin', Data) FROM Asset WHERE Id='…'` (Linux paths
only — a `C:/…` path creates a junk `C:` dir IN THE REPO; one was made and removed).
- Layout DB: `/mnt/c/Users/gramp/AppData/Roaming/ytLlive/ytLlive.db` (tables: `Asset`
`Id/Hash/Data`, `Settings`, `Source`, `Scene`, …). Alert default video setting key:
`AlertDefaultVideoAssetId`. ffmpeg/ffprobe live in
`/mnt/c/Users/gramp/AppData/Roaming/ytLlive/tools/` (run the `.exe` directly from WSL).
- Two diagnostics were ADDED and are still in the tree (once-per-second, alert only):
`Output resolver: alert box …` (MainViewModel.cs) and `Alert preview: box …`
(AlertOverlayLayer.cs). Decide with the creator whether to keep them or strip them now
that the root cause is known.
- `PreviewPane.xaml` has the same latent gap for `IsMediaSource` (no trigger either) —
NOT fixed (out of scope), noted as a follow-up.
- Known perf item, untouched: FramePump stalls ~140-180ms renders
(`render=full-render split=0 … totalMs=140`). Unrelated to the alert bug.
- YouTube rejects the `complete` transition (403) at end-of-stream; `enableAutoStop`
finishes the broadcast. Harmless, already handled.