The creator confirmed the clip fix ("the video plays now"), then asked where the
scrolling text was — it was invisible, for a structural reason. AlertTickerFrame
existed only as a frame-pump callback blitted into the OUTPUT; PreviewPane.xaml had
no element for it, because the strip is master-width and global, not a Source, so it
cannot ride a per-element Image. Nothing was wrong in the renderer: there was no
consumer in the preview. Same class of defect as the missing IsAlertBox trigger, one
layer up (MyMistakes RULE 5/6).
- tickerPreviewSink on AlertOverlayLayer, published from RefreshAlertPreviews() so it
is always the UI thread; MainViewModel.AlertTicker writes it into one reused
WriteableBitmap bound to a new global AlertTickerElement, mirroring SocialBarElement.
- Source.AlertDisplayMethod + panel "Display" selector: TickerScroll / Flash / Solid.
Flash pulses 0.5s on / 0.5s off for the whole alert; Solid is centred and still.
- The marquee was also unreadable: a fixed 140px/s took ~17s per pass, so a 10s alert
showed the text once, entering from the right and never crossing. Paced in reads per
alert instead (TickerReadsPerAlert = 3 inside the alert's own length, speed derived
from it) — never px/s. Research (websearch: how do OBS/Streamlabs/StreamElements
alert boxes present announcement timing?) settled the unit: Streamlabs exposes "Alert
Duration: choose how long your alert stays on your stream" and "Text Delay", never a
scroll-speed slider (https://support.streamlabs.com/hc/en-us/articles/52499995174299-Setting-up-Your-Streamlabs-Alerts).
Run is phase-started half a frame in so the first frame isn't blank.
- Persistence: AlertDisplayMethod INTEGER NOT NULL DEFAULT 0 via the idempotent
table_info migration, appended LAST in the SELECT because the Source reader is
positional (GetInt32(32..34)) — a mid-list insert would silently shift a neighbour.
Tests: 15 new facts (suite 339/339). RealApp STA host: the pane draws the strip and
collapses at alert end; the layer publishes a real 1920x48 frame for all three methods
and nothing when the ticker is off; three passes counted in 10s by the pill's leading
edge resetting (a seamless marquee never blanks, so an empty frame cannot count a pass);
Solid byte-identical at every moment; Flash on for half of each second; the panel shows
and writes back the choice; the DB round-trips all three alert fields together.
Incidental finding: a bound ItemsSource ComboBox in LeftPanel.xaml broke
LayerReorderPersistenceTests.RealMouseDrag (that test injects PHYSICAL mouse input, so a
load-time re-measure moves the rows out from under the cursor). Rewritten as inline
ComboBoxItems, the shape the chat Font selector already uses in that panel. Recorded as
MyMistakes RULE (8).
7.1 KiB
HANDOFF — 2026-09-26, end of session (take four: ticker display methods)
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.
| 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 |
e2ecc24 |
the real alert fix: PreviewPane.xaml IsAlertBox trigger + 2 Good Dog tests + resolver/preview diagnostics |
| (last, unpushed) | alert announcement strip: visible in the preview + 3 display methods (Scroll / Flash / Solid), paced in reads-per-alert |
THE ANSWER, take two (do not re-derive this)
The ticker was output-only. AlertTickerFrame existed solely as a frame-pump
callback (blitted at 0,0 into the OUTPUT). PreviewPane.xaml had no element for it
because the strip is master-width and GLOBAL, not a Source, so it cannot ride a
per-element Image. Nothing was broken in the renderer — there was simply no consumer
in the preview. Fixed with a tickerPreviewSink on AlertOverlayLayer (published from
RefreshAlertPreviews(), UI thread only) + MainViewModel.AlertTickerImageSource +
a global AlertTickerElement in PreviewPane.xaml mirroring SocialBarElement.
The marquee was also unreadably paced: a fixed 140px/s needed ~17s per pass, so a
10s alert showed the message barely once, entering from the right and never crossing. It
is now paced in reads per alert (TickerReadsPerAlert = 3 inside the alert's own
length), which is the incumbent's own unit (Streamlabs: "Alert Duration" + "Text Delay",
never a scroll-speed slider).
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.
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.
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.
Build / test commands (Windows host, always)
"/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: 339/339 pass (15 new facts for the ticker feature). Run it with ytLive CLOSED or the RealMouseDrag env flake fires.
Queued next (creator's words, 2026-09-26) — the Good Dog queue
- 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."
- 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.
- Test pull-out chat input: Enter inserts a newline, it must not send.
Verify the alert video live— DONE, the creator confirmed it plays.- Verify the three ticker display methods live (Scroll / Flash / Solid) and pick a default. Flash is 0.5s on / 0.5s off; Solid is centred; Scroll does 3 passes per 10s alert. Nothing here has been SEEN on screen yet — the tests prove pixels and bindings, not the look.
- Decide whether the once-per-second alert diagnostics stay.
ViewModels/MainViewModel.cs(ResolveOutputFrame) andServices/AlertOverlayLayer.cs(UpdatePreview) still logAlert preview:/Output resolver:once a second while an alert plays. They earned their keep; they are noise in normal use. Creator's call.
Landmines / facts worth keeping
- The pinned BtbN ffmpeg has no libx264 — generate test clips with
-c:v mpeg4. - A bound
ItemsSourceComboBox inLeftPanel.xamlbreaksLayerReorderPersistenceTests.RealMouseDrag_OnTheLayerList_PersistsTheReorder(it injects PHYSICAL mouse input, so a load-time re-measure moves the rows out from under the cursor). Use inline<ComboBoxItem>s like the chat Font selector does. If a UI-only change breaks a real-input test, suspect the control's binding STYLE first, and bisect withgit checkout <the one xaml file>. - The
Sourcerow is read back by POSITIONAL index inLayoutStore.Load.cs(GetInt32(32..34)). Append new Source columns to the END of the SELECT, or a mid-list insert silently shifts a neighbour instead of failing to load. New columns use the idempotentPRAGMA table_infoguard inLayoutStore.Migrations.cs(nouser_versionbump needed). - A seamless marquee never goes blank (the wrapped copy enters from the right as the pill clears the left), so an "empty frame" cannot count a pass — count the pill's leading edge resetting to the right edge.
SceneCompositor.Renderfills 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.sqlite3on WSL:/home/gramps/android-sdk/platform-tools/sqlite3. Extract a BLOB withSELECT writefile('/tmp/opencode/x.bin', Data) FROM Asset WHERE Id='…'(Linux paths only — aC:/…path creates a junkC:dir IN THE REPO; one was made and removed).- Layout DB:
/mnt/c/Users/gramp/AppData/Roaming/ytLlive/ytLlive.db(tables:AssetId/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.exedirectly from WSL). - Two diagnostics were ADDED and are still in the tree (once-per-second, alert only):
Output resolver: alert box …(MainViewModel.cs) andAlert preview: box …(AlertOverlayLayer.cs). Decide with the creator whether to keep them or strip them now that the root cause is known. PreviewPane.xamlhas the same latent gap forIsMediaSource(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
completetransition (403) at end-of-stream;enableAutoStopfinishes the broadcast. Harmless, already handled.