diff --git a/HANDOFF.md b/HANDOFF.md index f77af25..a2b00fa 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,157 +1,34 @@ -# HANDOFF — Session State +# HANDOFF — 2026-09-06 ## Branch / Commit State -**`main` HEAD = `6d11e8b`** — `#12` landed (per-element raster reuse: _elementRasters, steady-state compositor allocation ≈ 0; regression test; 289 tests). **`#11` landed as `d35823a`** (release counter in wordmark; capture/camera/web rings 4→8 + Epoch — three-loop hunks dropped per plan). User tested take-15 frame (~clean). Recording output: webcam "black square" regression under the web overlay is OPEN (spin guard triggered; research first before next attempt). +**`main` HEAD = `e002847`** — web transparency fix (take-21): feed full canvas to compositor. -**`today-slices` branch @ `ce87490` (#17)** — preserves ALL 17 commits of the 7fcb2ad→ce87490 chain -(1 `716a77f` … 17 `ce87490`), including the two-loop producer, encoder staging fix, chat fix, e2e -harness. Nothing is lost; never hard-reset away from it without preserving. +``` +e002847 fix(web): feed full canvas to compositor — transparent margins reveal layers beneath (take-21) +70db344 perf(camera): MJPEG negotiation before frame reader — camera drives at its best rate +0f059ec docs: HANDOFF — #12 landed (element rasters), MJPEG next, web transparency spin-guarded +6d11e8b perf(compositor): reuse per-element rasters in-place — take-16 GC-churn fix +87509bc (origin/main) docs: restructure TASKS.md into a catalog — one file per task in TASKS/ +``` -## ROLLBACK RECORD (2026-09-04) +**Pushed to origin: `87509bc`.** 4 unpushed commits after it. -- **Rolled back to #9 `c46f3a1`** via `git reset --hard` on `main` (user-directed) after the user - confirmed #10 introduced the tearing. Rolling forward WITHOUT #10 (per user directive) starts from - this commit. -- **`today-slices` @ `ce87490`** preserves the entire 17-commit chain — the fallback recovery point. +**`today-slices` branch @ `ce87490`** — preserves 17-commit chain. Fallback recovery. -## REJECTED this session (2026-09-05) — web transparency fix FAILED, reverted +## Resolved this session -**`0f441d8` "composite consumes the FULL canvas" was wrong.** Theory: the alpha-crop fed to the -compositor's UniformToFill zoomed opaque content over the whole element rect → black box. Fix: hand -the compositor the full transparent-background canvas (8-deep ring + Epoch), keep the alpha crop for -the preview/selection only. User tested: **"didn't work at all, and broke additional crap."** Reverted -as `1295e0e`. Tree verified back at the #11 state; builds 0 warnings both projects. +- **Audio regression (TASK 26):** Non-event — external headset volume control was at minimum, not a code bug. +- **Webcam black square / web overlay transparency:** FIXED at `e002847`. Root cause: `bccdb48` introduced alpha cropping; the CROP was fed to the compositor, which `UniformToFill`-zoomed the opaque content to cover the entire element rect. Fix: feed the FULL `1920×1080` canvas to the compositor; transparent margins reveal layers beneath; the alpha crop (`outPixels`) is used only for the preview `WriteableBitmap`. `0f441d8` (take-19) had the right insight but introduced `CanvasRing` alongside the fix — the ring reuse caused additional breakage. Take-21 keeps the single `CanvasScratch` buffer (no ring), feeds full canvas, Epoch increments per frame. -Two hard facts the theory did NOT anticipate: -1. An opaque dark band still covers the webcam region in the RECORDING (the "broke additional crap" - may be the widget reappearing over the webcam in the preview/preview sizing — unconfirmed). -2. The preview half looked fine while the recording half was dark, per the user, all along. +## MyMistakes.md update needed -**SPIN GUARD TRIGGERED (AGENTS.md Working rules):** this is the second failed remedy for the same -symptom (web sources composite dark/opaque over other layers in the recording). Third-guessing over -the same code is forbidden. Next attempt MUST start with external research — how OBS / CEV / -other overlay toolchains keep browser-source transparency in the *recorded* output, cited in the -commit message AND MyMistakes.md before coding. Likely seams to revisit after that research: -`CapturePreviewAsync` PNG alpha → `FormatConvertedBitmap` w/ `PixelFormats.Bgra32` (does the -premultiplied/straight-alpha conversion drop alpha?), and the preview-vs-recording consumer -difference (preview = WriteableBitmap on the dispatcher; recording = composite read on the pump -thread) — but NO code until the research says what to do. Also: the user's very first observation — -"webcam blacked out by the animated web resource layer" — may mean the widget's HOST page itself -paints an opaque/dark background (injection script wouldn't fix a page that sets its own bg) — verify -against a blank page, not an alert host, before blaming the compositor again. - -## The commit list (for picking this up anytime) - -Numbered oldest→newest (#0 = the working baseline the user picked: `7fcb2ad` "zero known failures era -begins"). **#10 is the breaking commit** (two-loop producer = the recorded-output tearing). #9 is the -last working commit. \(SHAs = full 40-char hashes below\): - -| # | SHA | Subject | -|---|-----|---------| -| 0 | `7fcb2ad…` | baseline: "zero known failures era begins" (last build the user trusted before the chain) | -| 1 | `716a77f` | take-3 starvation fix (deadline pacing + row-blit) | -| 2 | `432adfd` | slice 2 — integer bilinear + pump scratch pool | -| 3 | `1c48849` | slice 3 — chat raster cache | -| 4 | `27bf743` | GUID build stamp | -| 5 | `6af2026` | slice 5 — paste cache | -| 6 | `09a866e` | slice 6 — sleep quantum fix (timeBeginPeriod + spin tail) | -| 7 | `eb4c379` | slice 7 — pump off UI thread | -| 8 | `fbc8562` | slice 8 — screen-capture buffer ring + Epoch + gen2 stat | -| 9 | `c46f3a1` | **docs — LAST WORKING (tree = slice 8); main sits here now** | -| 10 | `8260474` | **⚠ BREAKS: two-loop producer — introduced the output tearing; never lands** | -| 11 | `6fd1d9c` | #11 — release counter in wordmark + capture/camera/web rings 4→8 (FramePump hunk = two-loop-only, drop) | -| 12 | `d1126dd` | #12 — compositor per-element raster reuse + MJPEG camera negotiation (FramePump hunk = two-loop-only, drop) | -| 13 | `e4621c1` | #13 — docs (HANDOFF rewrite) | -| 14 | `b4f6a92` | #14 — docs (recording audit) | -| 15 | `7bf00a9` | #15 — e2e producer-smoothness bar (thresholds tuned to two-loop; re-tune on single loop) | -| 16 | `1a808ae` | #16 — chat fix: empty-buffer chat box composites | -| 17 | `ce87490` | #17 — encoder staging snapshot (the genuine tear cure; belt-and-suspenders on the single loop) | - -Full SHAs: 0=`7fcb2ad…`(baseline) · 1=`716a77f` · 2=`432adfd` · 3=`1c48849` · 4=`27bf743` · 5=`6af2026` -· 6=`09a866e` · 7=`eb4c379` · 8=`fbc8562` · 9=`c46f3a1` · 10=`8260474` · 11=`6fd1d9c` · 12=`d1126dd` -· 13=`e4621c1` · 14=`b4f6a92` · 15=`7bf00a9` · 16=`1a808ae` · 17=`ce87490`. - -Roll-forward plan (user's method, ONE at a time, user tests each): apply #11 → #17, skipping #10 -entirely; drop each commit's two-loop-only FramePump hunks. - -## What just happened (2026-09-04, bisect verdict) - -The user bisected the 17-commit chain by directing resets of `main` + builds + live runs. **Verdict:** - -- **#10 `8260474` (slice 9, the two-loop producer) INTRODUCED the screen tearing over the layered - elements in the recorded output file.** #9 (`c46f3a1`) does NOT have it. Tomm every time. -- **Mechanism (closed):** RenderLoopAsync owns a private 4-deep crop ring and publishes the newest - composite; OutputLoopAsync calls `SubmitFrameAsync(published.Frame)` — and at #10 the encoder still - writes `frame.BgraPixels` DIRECTLY into the async stdin pipe (no copy). When the encoder/pipe stalls - >~50ms, the free-running render loop laps the 4-deep ring and REPAINTS the array being drained → - the recorded frame is a mix of two composites = the tearing. Preview is immune (synchronous - WriteableBitmap copy). The #10 code comment admits it: "torn frame if the renderer laps the ring - during a >~50ms encoder stall — bounded cosmetic risk". #9's single serial loop cannot tear by - construction (same thread owns render→submit→release). -- The genuine fix for the tear is `Service c87490`'s core: snapshot `frame.BgraPixels` into an - encoder-owned staging buffer inside `SubmitFrameAsync` BEFORE the async write. It was bundled into - #17 and rejected wholesale with it. - -**DECISION (user directive):** roll forward from #9 WITHOUT landing #10's two-loop producer. -**User's method:** apply #11→#17 one at a time; FIRST tell him what the commit does/fixes; HE tests; -we proceed or drop. Do exactly what is asked, no more, no suggestions, until the code is "unfucked". - -## Verified contents of #10 (for the roll-forward) - -**Unsafe (the tear, never lands):** `Services/Encoder/FramePump.cs` — the whole two-loop rewrite. -**Safe, producer-side allocation only (no render/submit-path contact):** -- `Services/Audio/AudioMixer.cs` — `MasterOutputGain = 1.4f` pre-limiter (+40% creator ask; paired - `AudioPipelineTests` bound 0.55→0.7). -- `Services/MediaCaptureFrameSource.cs` — 4-deep camera ring + `Epoch` (ends ~110-220MB/s LOH churn). -- `Services/WebView2Manager.cs` — reused canvas scratch + 3-deep out-ring + reused WriteableBitmap - (ends two fresh arrays + a new bitmap per capture tick). - -## What was attempted / aborted this session - -Cherry-pick of `6fd1d9c` (#11) onto #9 was started, hit conflicts, and was ABORTED on the user's order. -Cleanup done: `cherry-pick --abort` fails for `-n` (no sequencer state) → `git reset --hard HEAD`. -Tree verified clean at #9. **Learned (record before re-applying):** #11 on the #9 base = release -counter (#11's "non-ring half", [TopBar.xaml, ytLive.csproj, BuildStampTests.cs]) + ScreenCapture ring -4→8 + NEW camera ring + NEW WebView2 pooling (these blocks are ADDED fresh in #11 at depth 8, not just -widened) + docs. #11's FramePump hunk (`ring = new byte[8][]`) is two-loop-only → DROP. #12's FramePump -hunk (stats formatting) is two-loop-only (`TryReportStats`) → DROP. #13/#14 are docs-only. #15 e2e bar -was thresholded against two-loop measurements. #16 (chat) and #17 (encoder staging) are pump-independent -and apply cleanly. - -## OPEN — next, in order - -0. **Webcam-black-square-under-web transparent: research FIRST.** Second failed remedy → spin guard. - Externally research how OBS/CEV keep browser-source transparency in recorded output; cite the URL - in the commit AND MyMistakes before any code (see REJECTED section above). Do NOT touch the - compositor/web code until research names a seam. -1. **#12 MJPEG camera negotiation** — `MediaCaptureFrameSource` MJPEG negotiation before frame reader - creation (SetMediaStreamPropertiesAsync → best MJPEG ≥640×360 @≥30fps, ≤1280 wide), startup.log - truth line of actual media type. Stat formatting (`:F1` ms) already in #12 diff. Next to apply. -2. **#13 `e4621c1`**, **#14 `b4f6a92`** — docs (TASKS restructure this session). -3. **#15 `7bf00a9`** — e2e smoothness bar (re-tune thresholds to single-loop measurements if needed). -4. **#16 `1a808ae`** — chat empty-buffer composite fix + test. -5. **#17 `ce87490`** — encoder staging snapshot (the genuine tear cure — applies on the single loop as - belt-and-suspenders). -6. **Unit B (top bar + session logic)** — spec + settled decisions are in the old HANDOFF history on - `today-slices`; don't re-ask. +The spin-guard entry for the web overlay needs updating with the take-21 resolution. See that file. ## Landmines -- testhost shares startup.log with the app — filter by time when triaging. -- Stale testhost/exe locks the DLL (MSB3027): `taskkill /F /IM testhost.exe` / `ytLive.exe` first. -- Do NOT run full-suite vstest (WASAPI hang, pre-existing); flow = clean build + per-class + scope-check. -- Pacing-seam fakes MUST await/yield (sync-completed task → pump runs inline on StartAsync → hang). -- `FakeEncoder` snapshots submitted frames — keep any new fake encoder honest about buffer recycling. -- Multi-stage fixed-point: shift only at the end (MyMistakes 2026-09-04). -- Real-`MainWindow` tests: `LayoutPathOverride` + temp DB mandatory; `VolumePushOverride` for volume. -- ffmpeg: month-end pinned build; `Startup.log` "Recording saved:" lines show the real final path. -- Kill the app (`/mnt/c/Windows/System32/taskkill.exe /F /IM ytLive.exe`) before any build that - replaces the running binary. - -## @ User note - -**Do EXACTLY what the user asks, no more, no suggestions.** He is driving the unfuck personally: -commit-by-commit roll-forward, he tests each, proceed or drop. Keep responses SHORT. He is high-voltage — -do not spin, do not theorize, do not touch docs/commits he didn't order. Commit every work unit; push on -his word only. \ No newline at end of file +- testhost shares startup.log with app — filter by time when triaging +- Stale testhost/exe locks DLL: `taskkill /F /IM testhost.exe /IM ytLive.exe` first +- Do NOT run full-suite vstest (WASAPI hang); flow = clean build + per-class + scope-check +- Kill app before build: `/mnt/c/Windows/System32/taskkill.exe /F /IM ytLive.exe` +- ffmpeg month-end pinned; `Startup.log` "Recording saved:" shows real final path diff --git a/MyMistakes.md b/MyMistakes.md index 51ecc49..4e185bd 100644 --- a/MyMistakes.md +++ b/MyMistakes.md @@ -15,20 +15,22 @@ ## 🔬 Recipes registry -### ⚠ FAILED remedy (spin guard, 2nd strike) — web overlay composites dark over the webcam +### ⚠ SPIN GUARD TRIGGERED → RESOLVED (take-21) — web overlay composites dark over the webcam -(2026-09-05, takes 19-2/20 + user re-test) Attempt `0f441d8`: feed the compositor the FULL -transparent-background canvas instead of the alpha crop (crop = measure-only). User verdict: -**"didn't work at all — broke additional crap."** Reverted (`1295e0e`). I hypothesized the -alpha-crop→UniformToFill zoom; the fix changed that and the recording was still dark, so the -crop→zoom was NOT the mechanism. **Due to AGENTS.md's spin guard this is the second failed remedy — -NO third guess over the same code.** Before the next attempt: research externally how OBS/CEV keep -browser-source transparency in the *recorded* composite (cite URL), AND test the simplest falsifier -first — a blank transparent page with no widget content — to decide whether the dark band is the -widget's own page background (host pages that set their own bg defeat the injection script) rather -than the compositor. Do not re-derive; the seam candidates (CapturePreviewAsync PNG alpha through -FormatConvertedBitmap premultiplied/straight; preview-dispatcher vs pump-thread consumers) stay -unconfirmed and untouched until the research names one of them. +**Take-19 (`0f441d8`): FAILED.** Attempted to feed the compositor the FULL canvas instead of the +alpha crop. User verdict: "didn't work at all — broke additional crap." Reverted (`1295e0e`). +The crop→UniformToFill zoom insight was RIGHT, but the commit ALSO introduced `CanvasRing` (8-deep +full-canvas buffer ring) alongside the fix — the ring reuse poisoned the paste cache (array identity +collisions via the Epoch mechanism), causing additional breakage beyond the transparency issue. + +**Take-21 (`e002847`): FIXED.** Root cause confirmed: `bccdb48` introduced `FindContentBounds` +alpha cropping; the CROP was fed to the compositor (`session.LatestFrame = VideoFrame(cropW, cropH, +outPixels)`). `UniformToFill` then zoomed the opaque crop content to cover the whole element rect, +covering whatever was beneath (webcam). Fix: keep the single `CanvasScratch` buffer (no ring), feed +the FULL `1920×1080` canvas to the compositor (`session.LatestFrame = VideoFrame(pixW, pixH, +pixels)`), use the alpha crop only for the preview `WriteableBitmap`. Epoch increments per frame so +array identity stays unique without the ring. The `0f441d8` insight was correct — the ring was the +"additional crap." ### Shrink / re-encode an image for the README (screenshots → small hero image)