# HANDOFF — current state **Branch:** `main` (pre-1.0, no feature branches — creator ruling 2026-08-24). **Pushed 2026-10-04:** `origin/main` = `048cab7`. **NOT PUSHED since — local `main` is ahead and the tree is clean.** The stale-push-state failure `MyMistakes.md` warns about has now bitten twice, so the rule stands: **check `git rev-list --count origin/main..main` before trusting any line here about push state.** Unpushed: `5ad0106` broadcast diagnostic → `74da99c` + `0fb2f9c` privacy/Test rulings → `8fbc370` diagnostic parse fix → `07a088a` **the explicit-bind fix** → `509ef36` window placement → `b2f2197` handoff → `UNIT 0` frame-cadence measurement. ## 🚧 IN FLIGHT 2026-10-05 — UNIT 0 shipped: a dropped webcam frame is finally measurable **The measurement the map never had.** Take `ty-20261005-1001-821-0001.mp4` (85.6s, 1920×1080, H.264 NVENC, 5124 frames @60) holds **25.6 distinct webcam images/s** in its lower-right region against the camera's 30fps: 1,153 normal 33ms intervals, **281 gaps of 50–100ms**, 96 bunched 17ms gaps. One hitch in five — the judder a DHH creator lipreads against, invisible in a file that muxes flawlessly. Measured with `signalstats`/YDIF over `crop=480:270:1400:780`; the bundled ffmpeg has no `mpdecimate`. ⛔ The old "webcam renders at ~18fps" claim was wrong (different session, counted all newly rendered layers) — corrected in `ai.md`. **Shipped:** `Helpers/FrameCadence.cs` (pure counter: frames, gaps, hitches ≥50ms, longest/avg), fed from three points — `MediaCaptureFrameSource` logs `Camera cadence WxH: arrivals … | camera …` (our delivery vs the device's own `SystemRelativeTime`), and `FramePump` logs distinct webcam `Epoch`s that reached a composite. Full detail in `ai.md` → "Frame cadence". **Also fixed, and it was a real measurement bug:** every pump average was divided by the number of frames **submitted**, which deadline padding always fills to the full 60fps target — so a pump fed one webcam frame per 200ms logged "300/300 frames, avg render 12.4ms". Render/resolve/wait now divide by `statRenders` and the line reads `300/300 frames per 5s from 60 renders`. That pair IS the duplicate-on-lag ratio. Test: `FramePumpTests.Stats_ReportRealRendersAndDistinctWebcamFrames_…`. **⏭ NEXT STEP — needs the creator, one short recording (60s is plenty):** record with the webcam visible and paste nothing — I read `%APPDATA%\ytLlive\startup.log` for the `Camera cadence` and `FramePump stats` lines. Reading them together names the culprit: camera steady + arrivals ragged = we drop it (reader/compositor); ragged in both = device/USB/driver; pump `webcam` below `arrivals` = the compositor missed frames the reader already had. **Then Unit 1** (exclusive lock + 720p, see below) is judged against those numbers. **Agreed but NOT yet built (decided 2026-10-05, so a later session does not re-derive it):** - **Unit 1 — exclusive camera lock + 720p on the camera the creator picked.** Open with `MediaCaptureSharingMode.ExclusiveControl`; if init is refused, reopen the **SAME camera** in `SharedReadOnly`. Target MJPG ≥30fps up to 1280×720, ranked by frame rate then size (this also fixes the existing candidate-ordering bug). **The creator REVERSED the ban on taking an exclusive camera lock** — that ban was never written into the map, which is why the reversal must be recorded this time. Accepted trade-off: an app needing camera *control* that opens it after us will fail. - ✅ **API probe DONE 2026-10-05 against the actual 19041.57 projection** (a throwaway compile-probe file, since deleted — do not re-derive, and note the build cost of finding out): `MediaCaptureSharingMode.ExclusiveControl` **is** projected, so the old comment in `MediaCaptureFrameSource.cs` ("Exclusive is not projected by the 19041 SDK") is wrong and must be corrected. `Exclusive` and `SharedReadOnly` are the **only two** `MediaCaptureSharingMode` values. `MediaFrameSource.SupportedFormats` and `CurrentFormat` exist. ⛔ **`MediaFrameSource.SetFormatAsync(MediaFrameFormat)` is a DEAD END here** — it takes a `MediaFrameFormat`, and that type has **no public constructor and no static factory** in the projection (it is only obtainable from a live frame's `VideoMediaFrame.Format`). The two-argument `(subtype, properties)` overload in Microsoft's docs does not exist here. So the format change must go through the `VideoDeviceController.SetMediaStreamPropertiesAsync` path the code ALREADY has — it is only refused because `SharingMode` is `SharedReadOnly`. Under an exclusive lock the existing negotiation should simply start working, which is a much smaller change than planned. - **Remaining unknown (needs a real device, not research):** whether `SetMediaStreamPropertiesAsync` actually succeeds under `ExclusiveControl` on this C920. The `MediaFrameSource.SupportedFormats` route is the fallback probe if it refuses — enumerate, and log what the device offers before assuming anything. - **Unit 2 — blinking "SD" badge in the preview only**, when a better format was available and we did not get it. Top-right corner of the webcam, or the 1:30 point ON THE RIM for a round clip (a round webcam has no corner); outside the mirror transform. ~1Hz opacity blink (under the 3/sec photosensitivity limit), tooltip on hover only, **must never appear in the recording or stream** (the compositor draws from camera frames, not the WPF tree). Style lives in `Themes/Controls.xaml` — a DataTrigger storyboard cannot be stopped inside a DataTemplate (`PreviewPane.xaml:168-171`). - ⛔ **NO fallback-camera setting, ever** — creator ruling: *"no fallback - that's bypassing the user's explicit choice."* Picking a vcam is a user action in the camera picker. Corollary, also recorded in `ai.md`: **Windows Studio Effects is NOT a vcam** — it runs inside the physical camera's driver and never appears as a selectable device. Only Broadcast / XSplit VCam install one. - ⛔ **Background removal: permanently out.** Creator confirmed the existing note: *"no - not doing it. the note is correct and accurate."* `ai.md` said "milestone 2", which was stale — now corrected. ## 🚨 FIXED 2026-10-04 — broadcasts never went live because they were never BOUND **Root cause, proven by the diagnostic rather than inferred:** `contentDetails.boundStreamId` on `liveBroadcasts.insert` **does not bind the broadcast.** The log line read `lifeCycle=created privacy=private autoStart=true bound=NONE boundMatches=NO-BINDING | streamStatus=active`. `created` is Google's *"incomplete settings"* state, so `enableAutoStart` had no bound stream to fire from, and close-out 403'd `invalidTransition` because `created → complete` is not a legal edge. The reason it survived a week: **a reusable stream accepts bytes for a broadcast that is not watching it**, so a perfectly healthy 99 MB RTMP session is exactly what an unbound broadcast looks like. Google's migration guide states the rule outright — *"a liveBroadcast must be bound to exactly one stream before the live broadcast can actually start"* — and it was in the docs read during the original design. Shipped `07a088a`: `BindBroadcastAsync` → `POST liveBroadcasts/bind?part=id&id&streamId` (no body), called right after `CreateBroadcast` and before the pump; failure is fatal to the session because an unbound broadcast can never go live. `boundStreamId` and `CreateBroadcast`'s `streamId` parameter both removed. The old test asserting insert-time binding **laundered a guess into a guarantee** and was replaced. Also corrected the TASK 44 claim that `liveChatId` only appears once live — it is present while lifecycle is `created`; that claim came from reading a sample's active-status guard as a spec. **⏭ NEXT STEP — needs the creator, one 60-second Test:** confirm `Broadcast bound:` appears and lifecycle walks `created → ready → live`. If it binds but still won't auto-start, the *next* suspect is an explicit `transition`, not the binding — do not re-open the binding question without log proof. **Coverage gap:** no automated test asserts `PrepareAndStartLiveAsync` actually *calls* bind — the service method is tested in isolation, but the wiring in the ViewModel is not. Acceptable only because the end-to-end symptom was the whole point of the diagnostic; revisit if the wiring is ever touched again. ## 🚨 FIXED 2026-09-27 — "Change Account" no longer signs you out before the browser opens **Reported as:** *"I clicked on my signed-in account pic, then clicked sign-in to another account. It opened the browser page to login under a different account and I canceled the request. But when returned to the app, I see logged out — and should not have happened! Cannot sign-in because the callback url was not released."* Also reported as hitting **both** running instances. **Two independent defects, and the second one was masking a race:** 1. **The sign-out ran first.** `ChangeAccountAsync` was `await SignOutYouTubeAsync(); await SignInAsync();`. `SignOutYouTubeAsync` → `TokenStore.Clear()` → **deletes** `ytLlive.auth`. So the working account's token was destroyed *before the browser even opened*; cancelling the chooser left nothing. Now `ChangeAccountAsync` is just `await SignInAsync()` — success overwrites the token via the `sessionChanged` hook, cancel/failure keeps the current session. No sign-out is needed at all. 2. **The retry couldn't rebind the callback port.** Abandoning the browser gives the app no signal, so the attempt held `localhost:8765` for its full 5-minute timeout. A new `AuthenticateAsync` now cancels and awaits the abandoned attempt before binding, **serialised by a `SemaphoreSlim`** (`_authGate`) — without the gate, the takeover window let a third caller see a free port and race to bind it, i.e. the takeover reproduced the original failure. My own test caught this. **The cross-instance claim was NOT the cause** and is still open as a *separate* question: no code path can sign out a second instance (`TokenStore` is per-`InstanceProfile.DataRoot`, `Clear()` is a local file delete with no server-side revoke, and no second instance had a token at inspection time). Verify both processes carried distinct `YTLIVE_INSTANCE` values and ran Debug builds before chasing it. Test: `YouTubeAuthServiceTests.RetryingAfterAnAbandonedAttempt_ReleasesTheCallbackPort`. Full suite **370/370**, 0 warnings (build `ytLive.Tests.csproj`, not just the app project, or VSTest runs a stale test DLL). Added an `openBrowser` ctor seam to `YouTubeAuthService` so tests never launch a real browser. **Coverage gap:** the *ordering* fix (defect 1) has no automated test — `_youtubeAuth` is hardcoded in `MainViewModel` and `ChangeAccountAsync` is private, so a real test would shell out to the browser. Wiring an auth seam into `MainViewModel` is a deliberate follow-up, not a bug-fix extra. ## 🗄 PARKED — Google OAuth verification: attempted, hit a Google-side bug, tabled by ruling **Creator ruling: unimportant for where we currently sit.** Verification is *not* a live blocker and **must not be re-attempted, re-derived, or re-researched** without a new ruling. The 2026-09-27 entry that preceded this one wrongly sat at `⏭ NEXT STEP` and framed it as the top priority — that was the plan going in, not the outcome, and the outcome was never written down. Corrected 2026-09-28. **What happened:** the verification attempt was made and failed on a bug **on Google's side** — not something we can fix, work around, or retry our way past. ⛔ **The exact mechanism was never recorded and is deliberately left blank** rather than reconstructed from memory: a confidently wrong note here is worse than none, because the next session would trust it. Do not go looking for the cause. **Owning account (creator-confirmed): `llamachile.shop@gmail.com` ("gramps").** Cloud project number `198374416003` (from the ClientId) — banked in `ai.md:233`, **do not re-hunt it.** The one-hop console link is `https://console.cloud.google.com/apis/credentials?project=198374416003` (picker defaults to **"Recent"** — switch to **"All"**). ⛔ Not the same account as the YouTube channel signed into the app. **What tabling costs, stated honestly:** if the consent screen is still in **Testing**, the 100-user cap and the **7-day refresh-token expiry** both stand — that expiry is why the creator kept having to re-consent. Parking verification does not fix that; it accepts it for the solo-use phase. It becomes real work the moment a *second real user* needs to sign in, or at Store submission (TASK 48). The sensitive-vs-restricted classification was never read and is moot until then. **One of these two survives the tabling, and the other was a bad idea (corrected 2026-09-28):** - ✅ **DONE — `Services/YouTubeAuthService.cs` no longer requests `auth/youtube.force-ssl`.** It is **redundant**: its grant is *"see, edit, and permanently delete your YouTube videos, ratings, comments and captions"*, a strict subset of `auth/youtube`'s *"Manage your YouTube account"*, which is the scope we keep. Google's own `liveBroadcasts.insert` reference lists `auth/youtube` as an accepted **alternative** to `force-ssl`, so every endpoint this app calls (`channels`, `liveBroadcasts`, `liveStreams`, `liveChat`, `playlistItems`) is covered. Two consent lines became one. **No forced re-consent** — a refresh token keeps the scopes it was originally granted, so existing sessions are untouched; the reduced set applies at the next fresh sign-in. Removing a scope cannot narrow capability when the one kept is broader. - ❌ **NOT doing this, and the earlier note recommending it was wrong: moving `playlistItems.list` off the user's session token.** It was written during the verification hunt on the theory that "public data should not ride on user credentials". Walking the code kills it: `ChannelStatsService` is built as `new ChannelStatsService(_youtubeAuth)` with **one** `HttpClient`, and `channels?mine=true` (same service) **genuinely needs the session**. To split it you would need a second unauthenticated client **plus a Google API key**, because every YouTube Data API request needs a credential — without a key the call 403s. So the "fix" means **shipping a new extractable credential in the binary, subject to quota abuse, to replace a token the app already holds.** It buys **zero** verification-surface reduction (the data is public; no scope is involved) and zero correctness. Strictly worse. ⛔ Do not "fix" this later without a new ruling. ## ✅ DECIDED 2026-09-27 — pricing: `$10/mo` subscription, or `$400` lifetime, **no annual** **Creator's ruling, verbatim: *"Let's just go with $10 monthly recurring subscription for option A and, for option B, a lifetime sub for $400. No annual."* ⛔ **No `.99` prices** (earlier ruling: *"Let's stop with the x.99 stuff - I find that irritating. Just say: ten bucks a month"*). This **supersedes** the 2026-09-21 one-time `$29 → $49` model and the old `$99/yr` sub. No feature gating: free gets everything, the branding flash is the only paid delta. **✅ Store revenue share VERIFIED — 15%, and subscriptions are NOT penalised.** Read from the ADA v8.10 PDF itself (downloaded, text-extracted — not a search excerpt). §6(b)(i) charges **15%** on "any Apps (and any In-App Products in such Apps) that are **not listed in Section 6(b)(iii)**"; 12% is Games-only; 30% covers Xbox console apps/games, Xbox non-subscription IAP, and Windows 8/Phone 8. LlamaCasty is a Windows PC App — not a Game, not Xbox, not Win8 — so **6(b)(i) governs both tiers.** The agreement's own changelog (**v8.0, Oct 26 2017**) says it outright: *"implement the **85/15 revenue share for non-Game subscriptions**."* ⇒ **`$10/mo` nets `$8.50/mo` (~$102/yr). `$400` lifetime nets `$340`.** Also confirmed: the 15% applies *after* VAT/GST (Net Receipts), payouts are monthly above a **$50** threshold, and no better small-indie rate was found in the standard terms. **⛔ Two obligations §6(h) attaches to the recurring tier** (why lifetime is the hedge): (1) we must **fulfil the subscription for the entire period as marketed**, and on breach Microsoft may refund the customer *"the full amount, plus taxes... in Microsoft's sole discretion"*; (2) **raising the price disables auto-renew** — so `$10` is effectively locked for the product's life absent Microsoft's price-change process. **⏳ Still open:** the `$50` subscriber→lifetime upgrade-discount SKU was discussed and is **not approved** — do not declare it. Individual vs Company Partner Center account still unanswered (get it right before enrolling). ## ✅ DECIDED 2026-09-27 — distribution is the Microsoft Store: MSIX + Store IAP **Creator's criteria, verbatim: "zero headaches, minimal maintenance (for me) while still providing accountability and a reasonably easy upgrade flow."** Route A is the only option where all four are solved by handing the work to Microsoft rather than to a certificate vendor: **$0/yr, no certificate, no HSM, no annual renewal, no SmartScreen ramp**, plus Store auto-update, Store-side payments/entitlements/refunds/support, and Microsoft review as the accountability layer. The rejected options and their reasons are in `TASKS/research-store-certification.md` §3 — **read that before reopening the question, not before re-deriving it.** **The big consequence: the whole licensing backend is deleted.** `PolarLicenseService`, `PolarLicense`, `MainViewModel.License.cs` (`PremiumUrl`, customer portal, the `OfflineGracePeriod = 14 days` subscription-era artifact, renewal/lapse copy) and the wrong "Polar unlocks alerts" string all become dead code. `IsPremium` comes from the **Store entitlement** instead of an HTTP call — which also deletes the entire "network flaky → app thinks I'm expired" bug class. **Velopack, the update URL and the DO droplet go too.** **What does not change: the entitlement.** Free gets everything; the branding flash stays the ONLY paid delta. Store IAP changes how `IsPremium` is *obtained*, never what it *gates*. **⛔ The old "still open: the price" note is retired — the price is settled, see the top of this file.** The old "~$69 floor" and "don't break the launch-price promise" notes refer to the old Polar storefront and are **not** current. ### ✅ DONE 2026-09-28 — TASK 48 item 1: bundle ffmpeg (was the "first code unit") The app no longer downloads an executable at runtime. `scripts/fetch-ffmpeg.sh` populates a gitignored `ffmpeg/` from a **SHA-256-verified** pinned month-end BtbN artifact; `ytLive.csproj` copies it to output *and publish* and **fails the build** if it's missing; `FfmpegLocator` lost its downloader, its zip extraction and the `%APPDATA%\ytLlive\tools` cache, and now resolves **bundle → PATH** with a loud, actionable failure. The pin that 404'd is gone, so the 2026-09-01 failure class cannot recur. Suite **379/379 at that point** (380 now — see Test state); 16 locator facts including an integration test that **executes the bundled binary** (which is what proves the sibling DLLs resolve). `THIRD-PARTY-NOTICES.txt` corrected — it still named the *stale* tag `autobuild-2026-08-09-13-03`, i.e. the one that 404'd. **Fresh-clone cost, deliberately accepted:** `./scripts/fetch-ffmpeg.sh` once (~65 MB). 135 MB of DLLs in git would tax every commit on this 9p tree; the URL + SHA-256 are the audit trail. **Answering "other 3p binaries needing bundling?" — no, verified.** ffmpeg was the only download-and-execute. `e_sqlite3.dll` + `WebView2Loader.dll` already ship via NuGet (`runtimes\win-x64\native\`), OpenH264 is compiled *into* `avcodec-63.dll`, the rest are managed. Two gaps remain that bundling cannot fix, and they are the next real risks: the **WebView2 Evergreen runtime** is a machine prerequisite (item 2's manifest dependency + a graceful-failure path), and **Velopack's updater downloads and executes**, so its teardown in step 3 is a compliance action. ### Deferred, not next: the packaging project + `Package.appxmanifest` (TASK 48 items 2–3) **Creator ruling 2026-10-04: still in active development, so ship-time work waits and queued features come first.** The unit is unchanged and ready when it is wanted: Full trust, `webcam`/`microphone`, English only; Velopack deleted. Add the `DefaultRecordFolder()` comment in the same unit (the mediumIL invariant below becomes load-bearing the moment the manifest lands). Full-trust writes under a real package identity are still **unverified** — confirm on a real packaged build before trusting them with recordings. Carried over from the research unit, still open: - **⛔ `Distribution.md:318` recommended a $400+/yr EV certificate for a SmartScreen bypass Microsoft removed in March 2024.** Fixed then — had it shipped, it would have cost $400+/yr to buy what $150 buys. Now moot anyway (MSIX is signed by Microsoft), but the lesson in `MyMistakes.md` stands: a policy citation is a claim about **scope**, not just text. - **WebView2 Evergreen runtime** is a machine prerequisite the app will hard-fail without. The manifest needs the framework dependency, and WebView2 init needs a graceful error instead of a crash. New, found while bundling ffmpeg. ### ⛔ Two invariants that break silently if touched - **Full trust, or recording breaks.** `DefaultRecordFolder()` writes to Downloads/MyVideos; that passes through unvirtualized **only** at `mediumIL`. Flip the manifest to `appContainer` and output vanishes with no error. A comment is owed there **when the manifest lands** (TASK 48 item 6) — not before. - **Chat is rendered, never stored.** That non-persistence half is the load-bearing part of the 11.12 certification answer. No chat history, moderation log, analytics, or crash payload may be added without re-arguing 11.12 first. ## 🚨 OPEN 2026-09-28 — the RTMP push never lands: recordings reach YouTube as EMPTY placeholders **This is a live bug, not a queued feature**, and it reframes a complaint the map previously treated as closed. It was found on 2026-09-28 while scoping TASK 50, and the handoff had no entry for it at all — corrected here. **What the creator observed:** the channel carries **20+ private videos, all content-less placeholders** (creator-inspected, 2026-09-28). This is the real explanation of the 2026-09-26 report "I still cannot get private test recordings saved" — it was never true that the app could not *find* an archived VOD. **No video content is ever produced on the channel.** **Mechanism (verified against the code AND the channel):** `liveBroadcasts.insert` succeeds and creates the placeholder; everything after it is what fails. Content only exists if all three of these land, and at least one does not: 1. broadcast reaches **LIVE** via `liveBroadcasts/transition`, 2. ffmpeg actually pushes RTMP to the bound stream key, 3. the broadcast ends and YouTube transcodes it into a VOD. `enableAutomaticArchiving` is **never set**, so YouTube's `true` default applies — the archiving route exists but has nothing to archive. Broadcasts are inserted `private` (`Services/YouTubeStreamService.cs:49`), so the orphans are not public, and the app has **no delete path** — the creator clears each one by hand in Studio. **✅ DIAGNOSTIC ANSWERED 2026-10-04 — ffmpeg reaches YouTube; the broadcast never goes LIVE.** One test-mode push, read from `startup.log`. The three steps, resolved: 1. `new MediaFoundationReader`/RTMP path — **WORKS.** ffmpeg opened `rtmp://a.rtmp.youtube.com/live2/4pew-…`, muxed **7.4 MB video + 46 KB audio**, and finalised the FLV with no socket error. Step 2 is fine. 2. **The broadcast stays `ready` — this is the break.** `enableAutoStart = true` *is* sent on insert (`Services/YouTubeStreamService.cs:73`), yet at close-out YouTube refused `transition=complete` with **403 `Invalid transition`** — which is only legal from `live`. So YouTube never flipped `ready → live`. 3. Never reached (nothing to archive because nothing went live). **Two unknowns, and the honest state of each:** - **The push ran only 8 seconds** (08:48:23.9 → 08:48:32.8). YouTube may simply need longer to accept and validate the ingest before it auto-starts. Untested — do not assume. - **The app cannot see this either way.** During a stream it polls **stream** health (`liveStreams`) and never the **broadcast** status; `GetLifeCycleStatusAsync` is called *only* from the end path (`YouTubeStreamService.cs:146`). So the log contains no evidence either way, and `EndBroadcastAsync` skips only on `complete`/`revoked`, meaning it tries `complete` against a `ready` broadcast and earns the 403. **⇒ Next step, small and cheap:** log the broadcast's `lifeCycleStatus` while streaming (reuse the existing health poll timer — no new loop), then run **one 60-second** test. That single run answers it. Do not go hunting beyond it until it has. **Sequencing constraint — do not flip the flag early.** `enableAutomaticArchiving: false` is the correct end state (TASK 50 will upload the local file deliberately), but archiving is **currently the only route a recording takes to YouTube** — flipping it before TASK 50 ships would leave test streams with nothing at all and re-create the 2026-09-26 complaint in a worse form. Ship the upload, flip the flag in the same task, never before. Detail and the open 11.12 re-check: [`TASKS/task-50-publish-recording.md`](TASKS/task-50-publish-recording.md). ## Landmines - **Frame pump jitter — counters now honest, cause NOT yet named (2026-10-05).** The same test stream logged **86 stalls per 5s** (`FramePump stall: iteration 40–50ms` against a 17ms budget) while `FramePump stats` read 301/300 frames, 0 dropped. UNIT 0 removed the reason those two numbers could disagree (the averages were diluted by padded submits) and added the counters that separate reader from compositor — **but no one has read a post-UNIT-0 log yet.** A creator recording is required first. Do not "fix" the pump before that reading. - **Stale fact inside `TASKS.md` (left alone, flagged here per the Scope Lock).** The "1.0 gates" section asserts "**TASK 36 is now shipped**", but the catalog row 36 reads **☐ Queued** and `task-36-gold-pass.md` still shows items 1–6 unchecked. The line most likely means *item 2* (branding flash goes live) shipped at `9761b1d`. **Needs a one-line fix, not a code change** — flagging rather than fixing because it is not this unit's job. - **A stale `ytLive.exe` (PID 2544) locked `bin/.../ytLive.exe`** and broke `dotnet run` with MSB3027. Killed. **If the build fails to copy `ytLive.exe`, check for a running instance first.** - **`LayerReorderPersistenceTests.RealMouseDrag_OnTheLayerList_PersistsTheReorder` needs an interactive desktop session.** As of 2026-10-05 it fails deterministically here (`ImgC` vs `ImgA`), verified identical on a clean tree. **Run the suite with ytLive CLOSED** and expect this one to fail in this shell. Not a regression — don't diagnose it from a WSL-shell failure. - **`RealAppHost.RunAsync` exists for a reason** — a frame-pump test MUST `await` inside it. A blocking wait occupies the one shared STA thread and **hangs the whole suite with no output.** Always background a `vstest` run and poll the log; a foreground piped `vstest` returns nothing in this shell even on success. - **`GlobalHotkeys`: two instances registering the SAME global hotkey** — Windows refuses the second. Left alone deliberately. - **MSIX writes under a real package identity are unverified.** The docs say full trust passes user-profile writes through, but confirm on a real packaged build before trusting it with recordings (TASK 48 item 6). ## Test state **402/402, 0 warnings, verified 2026-10-05** (395 + 6 `FrameCadenceTests` + 1 pump-stats integration test). Build the **test** project, not just the app project, or VSTest runs a stale DLL. **Launch exe for the creator's test run: `bin/Debug/net8.0-windows10.0.19041.0/ytLive.exe`.** **Confirmed environmental flake, NOT a regression (re-checked 2026-10-05, and it now fails deterministically rather than 2-of-3):** `LayerReorderPersistenceTests.RealMouseDrag_OnTheLayerList_ PersistsTheReorder` fails with `Expected "ImgC", Actual "ImgA"` at `RealAppHost.Run` line 68. **It fails identically on a stashed clean tree**, so it has nothing to do with UNIT 0 — it needs an interactive desktop session this shell does not have (the mouse drag never lands). Do not chase it; it is listed under Landmines as the known interactive-session test. **⚠️ New flake, do not chase it as a regression:** on the *first* full-suite run, `AudioPipelineTests.MasterLimiter_CapsTheLiveMix_OnThePipe` failed with `Expected 0.891, Actual 0` — a **zero**, not a slightly-off value, which is the signature of the pipe not being fed yet rather than the limiter misbehaving. It passed 27/27 as a class run and 380/380 on an immediate full-suite re-run. It reads the live mix **off the named pipe**, so it is a whole-suite scheduling artifact of the same family as the `RealAppHost` STA landmine below — **if it ever goes red, re-run before believing it.** ## Uncommitted / untracked - `ffmpeg/` — the 9 bundled binaries (~135 MB), **gitignored by design**. Populated by `scripts/fetch-ffmpeg.sh`. Do not commit it. - `MARCOM.md`, `MONETIZATION.md` — both edited (privacy-copy guard; the Polar-obsolete banner and the re-opened-price note) and both **gitignored by design** (confidential business files, `.gitignore:10-11`). They stay local, as intended. Same for `CREDENTIALS.md` — **never commit those.** ## Next 1. **~PUSH, then the creator's 60-second Test** — see the FIXED box above. Packaging is NOT next; the creator's ruling 2026-10-04: still in active development, so ship-time work waits and queued features come first. 2. **Webcam cadence → Unit 1 (exclusive lock + 720p).** UNIT 0 is shipped and the counters are in `startup.log`; the reading needs one 60-second creator recording (details + the exact read-the-log-together table in the IN FLIGHT box above). Then Unit 1: exclusive camera lock + 720p, and Unit 2: the blinking SD badge. **Creator to-do, not yet queued: long-run resource/consumption/recycling benchmarking** (how long the app stays healthy under full burden) — record only, do not start it. 2. **The packaging project + `Package.appxmanifest`** (TASK 48 items 2–3) — the next code unit. Full trust, `webcam`/`microphone`, English only; Velopack deleted. Add the `DefaultRecordFolder()` comment in the same unit, and the WebView2 framework dependency + graceful-failure path. 3. **TASK 50 — one-button "Publish this video"** (`TASKS/task-50-publish-recording.md`). Queued behind the Store submission by its own precedence note ("MSIX first, publish second — unless the creator reverses that"). Scoping is done; the build is not. ⛔ Do **not** flip `enableAutomaticArchiving` until it ships. It also carries an **open 11.12 re-check**: a publish feature makes the app *push* creator content to a third party, so the IARC answers must not go to certification describing an app that cannot publish. 4. **Declare the IAP products in Partner Center** — the price is **settled** ($10/mo, $400 lifetime, no annual, 15% verified), so this is unblocked. **Blocks:** the Store listing and the `OverlayHost.xaml` copy. ⛔ The Individual-vs-Company account question is still unanswered — get it right before enrolling. 5. **Polar teardown** (TASK 48 item 0) — `PolarLicenseService`, `PolarLicense`, `MainViewModel.License.cs`, the `OverlayHost.xaml` string, the `csproj`/`App.xaml.cs` Velopack sites. `IsPremium` ← Store entitlement. **Do this as one unit** — a half-torn-down licensing path is worse than either end state. 6. **Vertical recording verification** — the compositor tier is proven by `Render_VerticalTier_Outputs_1080x1920_From_The_Center_Crop`; the *record* path has never been run. `FramePump.cs:645` flags off-size tiers as a known follow-up. 7. **WACK + certification notes + the privacy policy** (TASK 48 items 4–5) — the policy must state camera/mic is OS-gated, nothing is retained, and **nothing is fetched at runtime** (now literally true — the ffmpeg bundle is what makes that sentence defensible). 8. **Post-session efficacy report** — roll up `CurrentHealth` (dropped frames, duration, health message) when a stream or recording ends. `SessionTeardownTests` is the natural home. 9. **Measure whether the Windows camera toggle gates DShow** — gates all privacy-forward copy (`MARCOM.md` guard). Not a certification risk; a trust risk. 10. **TASK 36:212 stale "shipped" line** — one-line fix, needs a hand. **Dropped from the list** because the ruling made them moot: multi-instance's ffmpeg tools-dir race (item 1 deleted the tools dir entirely), `scripts/publish.sh` + the `LlamaCasty.exe` rename (the Store packages and delivers — *revisit only if we ever ship outside the Store*), and the alert-gating copy (the wrong string dies with the Polar teardown in step 3). **Everything else in the v1 queue:** stream resilience (32), bandwidth step-down (33), scheduled streams (34), scene-linked audio (35), the master limiter (12), text source (3.16), reward events (3.20), the media picker (21), webcam identity (9.5), settings units A/C/D (40), and the defaults split (37). Ship-checklist items live in `TASKS.md` -> "1.0 gates".