# HANDOFF — 2026-09-17 (webcam resource lifecycle, startup slice; committed locally — no push) ## Branch / Commit State `main` HEAD = **`94a934f` webcam reader-ladder fix** (local). This work unit sits un-pushed on top as a NEW commit (same bundle of web/A/V work — stays commit-local until greenlight). Below: slice-18 C4 cache (`64a5a6d`), slice-17, slice-16, slice-15, `c01206f`, `b22d08e` (signed audio-sync, pushed). No push after this commit either — still awaiting the device take + user greenlight. ## ⚠️ Branding (2026-09-14, creator-corrected): product = **llamacasty**, internals = ytLive Product is **llamacasty**; repo path, csproj `AssemblyName`/`RootNamespace`, DB/log paths (`%APPDATA%\ytLlive\...`), most code names are legacy **ytLive/ytLlive**. User-facing text: "llamacasty" / UI labels use the catalog names ("Web Cam", "YouTube Chat", "Countdown Timer"…). ## ✅ Committed locally — webcam resource lifecycle (startup slice) **Incident review (the reason this exists):** the creator couldn't add a webcam to the Live scene (WebCam row grayed app-wide). Ground truth from `%APPDATA%\ytLlive\ytLlive.db` (NOT `layout.db`, which is a 0-byte legacy file): one `Webcam` identity (C920 `\?\USB#VID_046D&PID_082D…GLOBAL`) AND one `WebcamSceneConfig` — in the **Chat** scene. Under the single-identity rule that means Live's "+" menu is gray *by design*, but the app couldn't *say why*. Combined with the earlier crash/failure chain, the fix is a proper resource lifecycle: - **Startup poll + tri-state** (`MainViewModel.ValidateWebcamResourceStartupAsync`, fired fire-and-forget right after `LoadLayout`): 0 webcams → app runs on, layer inactive, no alarm; **1 → attempt `CameraManager.AcquireAsync` as an app-wide LOCK**; **≥2 → no auto-lock** — selection belongs to the App Settings dialog (gear) — that's the next slice. - **Persistent red alert** (`WebcamLockAlert`, bottom of the Layers panel, + Retry button): shown when the one detected camera can't be locked; **re-polls every 5 s** (`_webcamLockPollTimer`) and clears the moment a lock succeeds, or on any first real frame (`OnCameraPreviewBitmapChanged`). - **`CameraManager.IsRunning(deviceId)`** — new accessor (session exists, started OR starting) so the pass never double-acquires a lock the loaded identity's configs already hold; a rolled-back (failed) session leaves the dictionary, so the pass retries cleanly. - **Test seams mirroring `LayoutPathOverride`:** `CameraEnumeratorOverride` / `CameraFrameSourceFactoryOverride` — the startup probe must NEVER touch real hardware under test (the reason the 09:15 tests never asserted camera state). **Good Dog test** `WebcamStartupResourceTests` x3: single-cam locked + app runs on; zero-cams no-alarm no-lock; lock-fails → red alert → Retry → clears. **304/304 green, build 0 warnings.** - **Attribution correction committed to `ai.md`:** "NVIDIA Broadcast opens the webcam exclusively" is a *suspect-list* claim — `CameraConflictProbe` reads running process NAMES only, no device handles; the contention symptoms fit shared-mode/bandwidth just as well. Do not restate it as fact (creator called this out). **Task docs:** `TASKS.md` Open items + `ai.md` Webcam section updated to match. ## ⚠️ Open items - **Webcam take fix 2 of 2 — the 09:16 crash is UNFIXED (hard, untested):** hypothesis — native WMF death when `MediaCapture.Failed` fires mid-stream and `SafeStopAsync` (fire-and-forget) disposes reader+capture while the frame thread sits in `TryAcquireLatestFrame`/`Marshal.Copy` (outside the frame's try/catch). No repro, no integration test, native race not catchable — deferred per spin-guard. Record a second occurrence's pattern. - **Multi-camera slice (user-queued):** when ≥2 webcams exist, add the web-cam selection text + control to the **App Settings** dialog (gear icon, `OverlayHost` — left-click opens settings, right-click the context menu). Selecting + successfully allocating/locking activates the WebCam layer. Currently ≥2 cameras → nothing shows in settings. - **Static (+) catalog slice (user-queued):** all catalog rows always listed (Background, "Web Cam", "Countdown Timer", "Web Resource", "YouTube Chat", "YouTube Event"…), gray only for (1) system-level unavailability (persistent alert), (2) max already allocated, (3) not available for the current scene; tooltips must name the reason (e.g. "already in Chat"). Static labels only, no new code-behind. - **Device re-verify (one take session):** 1. slice-18 cache verdicts (NR/WH hits, stalls, band ~60); 2. webcam: fresh launch with the browser holding the camera must show the webcam or a NAMED chip (now also the persistent red alert, and the alert must clear after the browser closes + retry); 3. verify the Chat scene still owns the webcam (so Live's + is intentionally gray with a reason). - **No push yet** — after the take verdict, re-measure the clap offset (`/tmp/opencode/avsync.py`), then decide push with the user. ## Open threads (carried) - Audio-silence verification — fixed (`724af14`); creator heard real audio. - Webcam MJPG missing / ~10–14Hz, layer SortOrder, truncation-with-dynamic-scenes — queued. - Sync control user-doc tutorial — REQUIRED before 1.0 (creator directive; TASK 22). - Signed A/V sync: verify the negative (advance) direction on device. - Focus-loss capture lag — closed as NOT the cause (1824: delivery healthy ~60-100/s). ## Landmines - testhost shares startup.log — filter by time; the app also writes fake-device failures ('test-camera', 'dev-physical-9') from CameraManager unit tests. - `cmd.exe /c "taskkill /F /IM ytLive.exe"` (WSL double-slashes mangle) before rebuilds — a live app process locks `ytLive.exe` (MSB3021). - Build/tests: **Windows dotnet host** (`/mnt/c/Program Files/dotnet/dotnet.exe`). 0 warnings — only `./scripts/verify.sh ""`'s clean build counts. Running `ytLive.csproj` alone does NOT rebuild `ytLive.Tests.dll` — run the Tests csproj before `vstest`. Known audio flake: `Mix_HonorsProviderGains_AndGameMute_KillsTheLoopback` (float precision) — re-run if it trips alone; unrelated to camera work. - **Assert footguns in this repo's xUnit:** `Assert.Null(x, "msg")`/`Assert.Single(coll, "msg")` do NOT take a message — the 2-arg overloads mean something else (predicate/item). Empty message = 1 arg. - The webcam's real state lives in `%APPDATA%\ytLlive\ytLlive.db` (`layout.db` there is a 0-byte legacy file). sqlite3 at `/home/gramps/android-sdk/platform-tools/sqlite3`. - Contention failure modes (startup.log): "no frames within 4s" = init OK but starved; "OutputFormatNotSupported" = reader subtype below the MJPG-active camera; "file being used by another process / CaptureMode is SharedReadOnly" = re-negotiation refused while others hold the device. `CameraConflictProbe` = suspect list, not handle evidence. - FramePump tests asserting per-frame CONTENT need a STABLE scene; cache-sensitive assertions can't use a call-count resolver flip (tick resolves twice) — key alternation on `OutputIndex`. - ffmpeg/ffprobe: `/mnt/c/Program Files/Krita (x64)/bin/` with Windows paths. - `MyMistakes.md`: WINRT resource-allocation RECIPE, freeze-audit RECIPE, A/V sync recipe, deadline pacing, CoreMessaging DQ, WGC-CLIP + slice blocks. Grep before re-deriving. - `C:\tmpout` is for ffmpeg evidence artifacts; keep them out of the repo. ## Next step The multi-camera App Settings selector slice (+ static catalog rows after), then the creator's take on this build. The alert is the new expected behavior to eyeball on the take: browser holds the camera → fresh launch → red alert in Layers (unless Chat's config grabbed it) that clears after a Retry once the browser closes.