Files
LlamaCasty/HANDOFF.md
T
gramps 5e78065c2d fix(web): web-layer capture cadence 10Hz → ~30Hz while recording — widget animations no longer play ~1/6 speed
The recording is 60fps but WebView2 capture was a blind 100ms DispatcherTimer = 10Hz;
each captured web frame repeated ~6x into the file caps web animation at the capture
rate, not the page's (user: 'the animation appears to be too slow').

- New CaptureScheduler (Services/CaptureScheduler.cs): per-session dispatcher timer
  that DROPS a tick while a capture is in flight (latest-wins, never queues) — the
  guard that makes a higher cadence safe: concurrent full-HD PNG CapturePreviewAsync
  calls (~10-30ms each, slow per WebView2Feedback#20) would stack CPU and publish
  stale-after-fresh. Effective cadence = max(interval, capture duration).
- Cadence: SetCaptureInterval(33) on record/stream start, (200) idle — applied via
  MainViewModel.Streaming.Operations.cs.
- De-throttle the hidden page: shared CoreWebView2Environment created BEFORE
  EnsureCoreWebView2Async with --disable-backgrounding-occluded-windows
  --disable-renderer-backgrounding --disable-features=CalculateNativeWinOcclusion.
  Off-screen WebView2 is a hidden page when the host window is unfocused/covered and
  Chromium then parks rAF and clamps timers to ~1s (WebView2Feedback#1172/#3070,
  Chrome-88 timer-throttling blog).
- Telemetry: first 30 captures per session log elapsed ms (PNG encode + decode) to
  startup.log — that decides whether ~30Hz stays or drops to ~20Hz; the FramePump
  drops frames (never time-lapses, slice 10) if UI-thread GC churn starves it.
- ONE test: CaptureScheduler_Drops_Ticks_While_Capture_InFlight_And_Resumes
  (deterministic TCS-driven, no WebView2 runtime). Suite 290/291 — sole failure the
  pre-existing compositor pixel test.
- Docs same-commit: ai.md slice 11, MyMistakes.md, HANDOFF.

References: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1172
https://github.com/MicrosoftEdge/WebView2Feedback/issues/3070
https://github.com/MicrosoftEdge/WebView2Feedback/issues/20
https://developer.chrome.com/blog/timer-throttling-in-chrome-88
2026-09-10 10:25:41 -07:00

5.4 KiB
Raw Blame History

HANDOFF — 2026-09-10 (evening)

Branch / Commit State

main HEAD currently = c45cbc9 (slice 10). Slice 11 is uncommitted in the working tree (WebView2 capture cadence + de-throttle; see below). ahead of origin by 16, NOT pushing — user ruling (2026-09-10): do not push until the web-overlay transparency AND audio-silence issues are addressed. Both are open (see Still Open). Milestone tag milestone-recording-timing (annotated) on c45cbc9 — rollback point: git reset --hard milestone-recording-timing.

Timing saga — CLOSED (verified)

  • slice 9 bd396e4: duration == wall time (count-based CFR, deadline never rebased, -re removed).
  • slice 10 c45cbc9: bounded Channel encoder queue + drop-newest policy + burned-in dot-matrix frame counter (bottom-right). Verified on two takes (ty-20260910-0949…-2.mp4, 2383 frames 39.92s; ty-20260910-0957…-2.mp4, 2270 frames 38.05s): every frame decodes, counter advances +1/frame, zero gaps/dups. Content cadence both: median 2 frames, max gap 9, longest frozen run 8 (133ms). User: "I think we're good on this issue" → timing CLOSED.

The web-overlay threads (the active push-gate work) — Slice 11 in tree

Two user-reframed symptoms converged on ONE root cause and one change is in the tree (uncommitted):

  1. "The widget is an animated resource — the animation looks too slow."
  2. (earlier) "image renders but the transparent pixels are black."

Root cause of #1 (code-proven): the recording is 60fps, but the WebView2 capture loop was a blind 100ms DispatcherTimer = 10Hz → the widget's motion is sampled 6× under rate → repeated-frame slow-mo. PLUS Chromium hidden-page throttling (rAF parked, timers→1s; WebView2Feedback#1172/#3070) when the off-screen page's host window is unfocused/covered. #2 remains UNVERIFIED (the diagnostic PNG+alpha log is bound to the FIRST capture ever = the initial about:blank doc, so it can never see the widget — 5/5 sessions logged alpha=0 blank; instrument is blind, not the capture).

Slice 11 (uncommitted, files below):

  • Services/CaptureScheduler.cs (new): per-session dispatcher timer that DROPS ticks while a capture is in flight (latest-wins, never queues). Interval owner-configurable: 33ms recording / 200ms idle.
  • Services/WebView2Manager.cs: scheduler replaces the raw timer; shared CoreWebView2Environment created BEFORE EnsureCoreWebView2Async with --disable-backgrounding-occluded-windows --disable-renderer-backgrounding --disable-features=CalculateNativeWinOcclusion (GetEnvironmentAsync); SetCaptureInterval(int); capture-cost telemetry (first 30 captures/ session → startup.log).
  • ViewModels/MainViewModel.Streaming.Operations.cs: SetCaptureInterval(33) on record/stream start, (200) on stop.
  • ytLive.Tests/WebView2ManagerTests.cs: ONE test CaptureScheduler_Drops_Ticks_While_Capture_InFlight_And_Resumes (deterministic TCS-driven, no WebView2 runtime).
  • Docs same commit: ai.md slice 11, MyMistakes.md recipe "web widget captured at 10Hz…".

Suite: 290/291 pass — sole failure the PRE-EXISTING Composite_FullScene_MasterPixels pixel (1380,700) cyan-vs-magenta (out of scope; fails with any fix stashed).

NEXT STEP (the take that closes the web animation speed)

  1. Commit slice 11 (scope-check first; commit cites WebView2Feedback#1172/#3070/#20 + Chrome-88 blog).
  2. Run a normal recording with the animated widget on screen for ~20s. Expected:
    • startup.log: capture #1..#30 … took Nms lines (tell us the per-capture cost) — this decides whether 30Hz stays or drops to ~20Hz; also FramePump stats: should show no new dropped-frame growth vs before (GC-churn check).
    • Widget animation ~2-3× closer to real-time (10→30Hz).
    • If it only became jittery instead of fast: check the FramePump dropped-frame counters.
  3. Then, separately, the TRANSPARENCY verification (still open): re-point the first-capture dump/ alpha-log at a POST-PAINT capture of the real widget document (currently bound to about:blank) — the change that makes "are transparent margins real?" answerable. Intended next change.

Still Open

  • Web transparency UNVERIFIED — the diagnostic instrument is blind (fires on about:blank); last known visual state take-25 black box; user's "transparent pixels black" may be the opacity bug OR the preview surface. Needs the post-paint instrument fix (above) then a take. Push gate reason #1.
  • Audio silence — silent audio (−91dB full-length) in take-15; named-pipe audio delivers ~nothing. Queued follow-up. Push gate reason #2.
  • Pre-existing compositor pixel test failure (never in scope).

Landmines

  • testhost shares startup.log with app — filter by time when triaging.
  • Locked DLLs: taskkill //F //IM testhost.exe //IM ytLive.exe before rebuild.
  • Build/tests via /mnt/c/Program Files/dotnet/dotnet.exe build … / … vstest "C:\Users\gramp\Documents\Code\projects\ytLive\ytLive.Tests\bin\Debug\net8.0-windows10.0.19041.0\ytLive.Tests.dll".
  • Probing recordings: /mnt/c/Program Files/Krita (x64)/bin/ffmpeg.exe / ffprobe.exe (Windows-form paths).
  • tools/ticker: constant ~+982ms offset on the FIRST line is cosmetic.
  • WebView2Manager Ran into LOH churn worry: each capture allocs a BitmapImage raster (~8.3MB) that is garbage per capture; at 30Hz that's ~250MB/s LOH on the UI thread → potential gen2 pauses showing as FramePump dropped frames. Slice 12 candidate if the take's stats show it.