Files
LlamaCasty/HANDOFF.md
T

157 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HANDOFF — Session State
## 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).
**`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.
## REJECTED this session (2026-09-05) — web transparency fix FAILED, reverted
**`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.
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.
**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.
## 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.