# HANDOFF — Session State ## Branch / Commit State **`main` HEAD = `c46f3a1` (#9)** — tree identical to `fbc8562` (slice 8, single-loop pump). Working tree CLEAN. This is the user-confirmed **last working commit** — output recordings were tear-free here and time-lapse again (the 7fcb2ad-era pacing, expected pre-buffer-ring). **`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. ## ROLLBACK RECORD (2026-09-04) - **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. ## 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 1. **#11 `6fd1d9c`** — tell user what it does (release counter; capture rings 4→8 + new camera/web rings), apply via cherry-pick with the two-loop-only FramePump hunk dropped; user tests; proceed or drop. 2. **#12 `d1126dd`** — compositor raster reuse + MJPEG camera negotiation + stats formatting (FramePump hunk dropped). 3. **#13 `e4621c1`**, **#14 `b4f6a92`** — docs. 4. **#15 `7bf00a9`** — e2e smoothness bar (re-tune thresholds to single-loop measurements if needed). 5. **#16 `1a808ae`** — chat empty-buffer composite fix + test. 6. **#17 `ce87490`** — encoder staging snapshot (the genuine tear cure — applies on the single loop as belt-and-suspenders). 7. **Unit B (top bar + session logic)** — spec + settled decisions are in the old HANDOFF history on `today-slices`; don't re-ask. ## 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.