docs(handoff): bitrate closed, audio drift closed, one sync number left

Scope justification: Assets/starting-background.jpg is outside this unit — the creator's
replacement art, still awaiting their visual check (recorded in the landmines).
This commit is contained in:
2026-11-12 13:58:20 -08:00
parent b216cbc56f
commit bb3bc12784
+31 -31
View File
@@ -1,40 +1,40 @@
# HANDOFF — 2026-11-12 (bitrate correction: all four units landed) # HANDOFF — 2026-11-12 (bitrate + audio-clock fixes; sync number pending)
## State ## State
- `main`; `b265d42` is the latest pushed commit. Everything after it is local. - `main`; `b265d42` is the latest pushed commit. Everything after is local.
- **Bitrate saga closed.** An @8 stream averaged 5.1 Mbps because the args were capped VBR, not CBR: - **Bitrate saga closed.** An @8 stream averaged 5.1 Mbps because the args were capped VBR, not CBR
measured on the bundled build, a static frame at `-b:v 2000k` gave 9 kbps with NVENC's default and (measured: 9 kbps vs 1951 kbps at a 2000k target on a static frame). `700c7a8` adds `-rc cbr` for
1951 kbps with `-rc cbr`. Not the machine (1.8 ms render, 0 stalls) and not YouTube (no step-down NVENC/AMF, nothing for QSV (infers CBR from bitrate == maxrate) or OpenH264 (no measurable gain).
logged). `700c7a8` fixes it: `-rc cbr` for NVENC/AMF, nothing for QSV (it infers CBR from - Ladder `1708667` (12/10/6); floor `93719a2` (tier YouTube minimum); rate box `f2cab5c`; rate history +
bitrate == maxrate) and nothing for OpenH264 (no measurable difference). consent proposal `b0f8263`; pre-release 60 fps switch `1e3a4f7`.
- `1e3a4f7` pre-release switch (`OutputProfile.EntitlementEnforced = false`) lets a Release build select - **Audio drift closed** (`47f409f`, `102cb96`, `751d9b1`): the mix is driven by the monotonic clock,
60 fps, and suppresses the licence row while nothing is limited. Flip at launch. counts only audio whose write reached the pipe, and starts its clock at session open. Measured
- `1708667` ladder retargeted to 1080p60@12 / 1080p30@10 / 720p@6; `93719a2` makes the step-down floor across three recordings: +2.1% long → 226 ms short → 137 ms short → **lengths now match (34 ms in
the tier's YouTube minimum (6/5/3) instead of a flat 3. 13 s)**.
- `f2cab5c` the creator sets the rate (per-tier Mbps box, persisted, clamped only at the YouTube minimum); - **What is left is one number.** `5ae5280` gives the mic delay a 0..500 slider (off/175/350) and logs
`b0f8263` files each session locally and proposes a rate between sessions on consent alone. the applied value. Measured on the last clip: `-185 ms`, i.e. sound still 185 ms EARLY, so the value
- Verification: **672 tests green, clean build with 0 warnings** (`./scripts/verify.sh`). to try is **350–360 ms**. `Audio.MicSyncOffsetMs` is stored per profile.
- **675 tests green, 0 warnings.** `scripts/avsync.py` measures any recording in seconds.
## Next action ## Next action
- **Creator check pending in a real Release stream**: 60 fps selectable, live bitrate sitting near the - Creator: set the mic delay to 350, record ~15 s, run `./scripts/avsync.py "<clip>" --crop
rated number instead of drifting, the Mbps box and the rate prompt behaving. 764x764+1032+159`. Target: `offset` inside ±30 ms and `lengths match`.
- Resume the queue: **40·A/C/D → Move → 9·5 → 14·16/17/18 → 66 → 67 → 68 → 69 → 70 → 36**. - Then resume the queue: **40·A/C/D → Move → 9·5 → 14·16/17/18 → 66 → 67 → 68 → 69 → 70 → 36**.
- Ask creator before TASK 68 (first-run ON-AIR default); settle the Release single-instance mutex - Do not push app commits absent a checkpoint request.
before the gold pass. Do not push absent a checkpoint request.
## Landmines ## Landmines
- `Assets/starting-background.jpg` is modified in the working tree, **awaiting the creator's visual check** — - `Assets/starting-background.jpg` is modified in the working tree, **awaiting the creator's visual
do not commit it with another unit. check** — do not commit it with another unit.
- **Two load-sensitive flakes, not regressions**: `ChromaKeyRealClipTests.RealClip_PlaysAtItsNativeRate` - **Never read a field the constructor assigns later** (`RefreshRateProposal()` at ctor 354 reads
and `AudioPipelineTests.Mix_WithFiltersDuckAndGain_Lands_On_AudioPipe` failed once each in a full run `_framePump`, built at 468 — 8 failing tests, `e654a16`).
and passed alone; verify.sh only tolerates its known pair, so rerun the single test first. - **Never pipe a long job through `tail`** — it buffers, so a live gate looks dead, and verify's phase-3
- `RatedFor` must not cache before `_layoutStore` exists (the ctor resolves the default tier first), or build writes to `/dev/null`. Use `vstest --logger:"console;verbosity=normal"`; it names the failure.
the shipped default shadows the creator's stored rate for the whole run. - A killed test run leaves `testhost.exe` holding `LlamaCasty.dll`; every later build then fails on the
- Every rate change is a full encoder relaunch (`ChangeEncoderAsync`), so mid-show rate changes stay out. copy. `taskkill.exe /F /IM testhost.exe` before rebuilding.
- A tier's rate is read through `MainViewModel.EffectiveQuality`; a call site using the raw tier - `ChromaKeyRealClipTests`, `AudioPipelineTests.Mix_*`, `AppLogRotationTests` and
streams the shipped default. `NotificationAreaIntegrationTests` flake under load; each passes alone.
- `MainWindow` tests need a temporary `LayoutPathOverride`, cleared in `finally`; build with the Windows - Every rate change is a full encoder relaunch; rates go through `MainViewModel.EffectiveQuality`.
`dotnet.exe` host from WSL and read render numbers in Release. - `MainWindow` tests need a temporary `LayoutPathOverride` cleared in `finally`; read render numbers in Release.