Files
LlamaCasty/HANDOFF.md
T
gramps 1e4017df03 feat(webcam): resource lifecycle startup slice — poll-on-start, single-cam lock, persistent Layers alert
The creator couldn't add a webcam to Live (grayed app-wide) and nothing in the
app explained why. Ground truth from the live DB: one Webcam identity AND one
WebcamSceneConfig in the Chat scene — so the gray was the single-identity rule
working, but the reason was unobservable. This slice makes the webcam a
resource the app validates and locks, mirroring how OBS reserves its devices.

At startup we enumerate the OS once (ValidateWebcamResourceStartupAsync, fired
after LoadLayout, stored as WebcamStartupValidationTask for tests to await):
- 0 webcams -> app runs on, layer inactive, no alarm
- exactly 1 -> attempt CameraManager.AcquireAsync as an app-wide lock; on
  failure show a persistent red alert at the bottom of the Layers panel
  (WebcamLockAlert + Retry) that re-polls every 5s and clears itself the
  moment the camera locks, or on any first real frame
- >=2 -> deliberately no auto-lock; camera selection belongs to the App
  Settings dialog (gear) — next slice

CameraManager.IsRunning(deviceId) tells the pass a session already exists
(started OR still starting) so loaded identity configs count as the lock and
the pass never double-acquires. Test seams mirror LayoutPathOverride:
CameraEnumeratorOverride / CameraFrameSourceFactoryOverride so the startup
probe never touches real hardware under test.

Good Dog test: WebcamStartupResourceTests x3 — single-cam locked + app runs on,
zero-cams no-alarm/no-lock, lock-fails -> red alert -> Retry -> clears. Full
suite 304/304, build 0 warnings, scope-check passed.

Docs in-commit: TASKS Open items + ai.md Webcam section + HANDOFF rewrite.
Also corrects the record: 'NVIDIA Broadcast opens the webcam exclusively' was a
suspect-list claim (CameraConflictProbe reads process names only, no handles)
— not restated as fact.
2026-09-17 08:03:58 -07:00

108 lines
7.7 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 — 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 "<files>"`'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.