27bf74389d
Take 6 measured render 35-41ms — WORSE than take 5's 25.5 — and the run could not be attributed to a binary: exe mtime != build contents (incremental builds serve stale exes; a source edit without rebuild is a silent old binary). Three takes of a perf saga had been judged against builds nobody could prove. - ytLive.csproj GenerateBuildStamp target: fresh GUID per compile (writes obj/BuildStamp.g.cs -> Helpers/BuildStamp.Id/BuiltLocal). Deliberately defeats incremental lies: every 'dotnet build' recompiles the app project. - Wordmark shows the id as a superscript (TopBar.xaml, x:Static, 9px grey BaselineAlignment=Superscript); startup.log records 'Build <id> (compiled <time>)' so every take is cross-readable with the visible UI. - FramePump stats split the tick: 'avg render Xms (resolve Y), avg submit Z' — the resolver is timed separately (wrapper resolver on per-tick paths; bake keeps the raw one) so take 7 names the hot half of 'render' with data. - Fixed a latent transition-clock bug found on the way: lastTick now restarts every frame (the branch rework had restarted it only during transitions, letting a transition begun after idle complete instantly on its first Tick). ONE integration test family: BuildStampTests (unit: shape) + BuildStampDisplayTests (RealApp, namescoped FindName on TopBar proves the wordmark SHOWS the id). 47/47 per-class green, clean build 0 warnings. Docs same commit. User's top-bar/session spec (re-sent twice) + settled Q&A decisions folded into HANDOFF Unit B — next work unit after take 7 verdict.
105 lines
7.7 KiB
Markdown
105 lines
7.7 KiB
Markdown
# HANDOFF — Session State
|
|
|
|
## Branch / Commit State
|
|
|
|
**`main`** — Unit A (render starvation, slice 2) committed locally this session; push on the user's word
|
|
(his pattern: says "push" explicitly). Slice 1 (deadline pacing + row-blit, take-3 fix) is already on
|
|
`origin/main` as `716a77f`.
|
|
|
|
## What just happened (2026-09-04, Unit A slice 2)
|
|
|
|
Take 4 verdict: pacing HELD (no stall cliff, sync intact — user confirmed game+webcam in-sync) but render
|
|
stayed **58.9ms** (budget 16.7) → file still ~3.4x time-lapse. Root: the 2M-iteration managed row walk +
|
|
8.3MB fresh buffer every tick. Shipped:
|
|
|
|
1. **`VideoFrame.IsOpaque`** producer-contract flag — set ONLY by `ScreenCaptureFrameSource` +
|
|
`MediaCaptureFrameSource` (DWM/MF fill alpha 255). Full-canvas aligned blit of an opaque frame =
|
|
ONE `Buffer.BlockCopy`; black pre-fill skipped when the backdrop covers.
|
|
2. **Integer fixed-point bilinear** in `BlitContent` general path (webcam: round/mirror/scaled) — no
|
|
divisions, no `Math.Round`; ±1 of the float reference (tests allow ±2).
|
|
3. **Pump scratch pool** — `AcquireScratch`/`ReleaseScratch` (max 4, length-keyed, owned-by-reference so
|
|
bake-cache/social-bar/static-art arrays can never be captured). Release strictly AFTER
|
|
`SubmitFrameAsync` returns (stdin write copies). `FakeEncoder` now snapshots frames like the real
|
|
encoder (holds would race legitimate recycling).
|
|
4. **Dead code kill:** the per-tick `fromScene` render in the transition branch fed NOTHING
|
|
(`BlendFrame` uses `TransitionService.FromFrame` captured at `Start`) — removed with the
|
|
`fromSceneProvider` seam + `MainViewModel.cs` call site. Transition cost halves as a side effect.
|
|
|
|
Bugs caught by the pixel probes before shipping (see MyMistakes take-4 follow-ups): first `Bilinear`
|
|
double-shifted (both stages scaled → solid-255 sampled to ~1 → general path drew nothing); sentinel
|
|
0xAB collided with a legitimate `x+y` value; pacing-fake synchronous completion hangs vstest (known,
|
|
re-trod).
|
|
|
|
**Verification:** clean build 0 warnings (both projects); per-class vstest 59/59 (FramePump 11 incl. the
|
|
new pooling test, SceneCompositor incl. NEW `Composite_OpaqueFullCover...`, SceneGraph, SocialBar, StretchMath,
|
|
Camera/ScreenCapture/MediaVideoSource producers, WebcamOutputKey, SessionTeardown) + SourceNaming
|
|
RealApp boot-smoke. Scope-check passed.
|
|
|
|
## OPEN — next, in order
|
|
|
|
1. **Take 5 happened (2026-09-04 10:49):** `138/300 frames per 5s, avg render 25.5ms` — blits
|
|
fixed but the resolver's `RenderChatBox` full-rasterized the chat box EVERY tick whenever the
|
|
message buffer was non-empty (the buffer survives sessions — a signed-out record-only take paid
|
|
chat render cost!). **Slice 3 shipped same day:** `ChatOverlayLayer` content-versioned cache —
|
|
raster on message/config change, blit the cached frame every tick (OBS text-source pattern).
|
|
Tests: `ChatOverlayLayerCacheTests` (the ONE, RealApp) + full regression green (62 across
|
|
touched classes), clean build 0 warnings.
|
|
2. **BUILD STAMP shipped (slice 4, same day):** take 6 came back 35-41ms — WORSE than take 5 —
|
|
and attribution was impossible (exe timestamp ≠ binary contents; incremental builds served
|
|
unverified exes). Every build now stamps a fresh GUID (`ytLive.csproj GenerateBuildStamp` →
|
|
`Helpers/BuildStamp`), shown as the wordmark superscript (build id) + `Build xxxxxxxx (compiled
|
|
...)` in startup.log; stats report `avg render Xms (resolve Y)` so the hot half of the tick is
|
|
named. Tests `BuildStampTests` + `BuildStampDisplayTests` (real window, namescoped FindName).
|
|
3. **Take 7 (user, ~30s record-only):** FIRST read the superscript + startup.log Build line — the
|
|
take is meaningless without it. Expect slice 3 in any post-stamp build: `avg render ≤ ~10ms
|
|
(resolve ≪ render), ≈300/300`. If resolve dominates → resolver-side surprise (chat config thrash
|
|
/ web frame); if blit dominates → measure the general-path element sizes. If chat-burst sags
|
|
`n/300`: debounced off-tick re-render slice. If clean → recording saga CLOSED, Unit B.
|
|
4. **Unit B — the top bar + session logic (user spec 2026-09-04 re-sent twice + decisions settled in Q&A):**
|
|
- Two-line top bar. Line 1: center = REC + **LIVE** pills (text renamed from ON-AIR; pills become
|
|
mutually-exclusive RADIOS — record-OR-stream ruling), right = avatar + **Login/Logout** button
|
|
(no account status light). Line 2: centered primary **Start** (grayed while NO pill armed —
|
|
INVERTS the 2026-09-01 "unarmed Start records" rule; fix the map when landing) that becomes the
|
|
Stop/End button while active.
|
|
- Avatar right-click → **Change Account** (creator: "standard google thing ... on a portrait
|
|
right-click"). Login = `SignInCommand` direct (context menu on Start dies; "Choose Record Folder"
|
|
lives in gear → App Settings only).
|
|
- LIVE pill stays login-gated (`CanToggleOnAir` exists ✓ 3a.viii).
|
|
- REC+Start → `Microsoft.Win32.SaveFileDialog` (InitialDirectory = settings folder, default name
|
|
`ty-…-0000.mp4`, NATIVE overwrite prompt covers exists/validate, Enter confirms). Cancel →
|
|
abort + disarm pill (lit pill with no session is a lie). Up-front naming RETIRES the stop-time
|
|
rename modal (assumption stated; user's dialog answer was about Go-Live confirmation).
|
|
- LIVE+Start → **Go-Live dialog stays** as preflight: prefilled from Text-drawer `Broadcast.*`,
|
|
unfilled fields visibly prompted, explicit confirm → `PrepareAndStartLiveAsync` (user: "going
|
|
live is scary — confirmation allows back-out + testing up to go-live").
|
|
- Bottom-bar metrics init/maintain: `ResetHealth` + `HealthUpdated` exist — verify on take 6.
|
|
- F6 "start/end" hotkey routes through `HandleHotkey` — check it honors the new grayed-Start gate.
|
|
- Login button text: "Login" (disconnected, LIVE pill greyed) → "Logout" (connected, avatar
|
|
appears left of it). The account status LIGHT is deleted per spec 2a.
|
|
- Primary button: grayed "Start" when NO pill armed (inverts 2026-09-01 rule — fix the map in
|
|
the landing commit); enabled when either armed; REC path = file dialog flow; LIVE path =
|
|
go-live after dialog confirm; becomes the Stop/End face while active (user's "Stop button
|
|
never active" complaint gets a hermetic test pinning visibility+CanExecute).
|
|
- ONE integration test (hermetic): pills↔button state machine + record-path seam (an
|
|
`internal static Func<SaveFileDialog-ish prompt>` override seam mirroring `RegistrarOverride`
|
|
— never pop real dialogs in tests).
|
|
3. **Follow-ups recorded (don't fix opportunistically):** vertical-tier `BilinearScale` per-frame
|
|
alloc; cosmetic `FramePump: encoder stop failed: No process is associated` double-stop race;
|
|
1440p capture downscale alloc.
|
|
|
|
## 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.
|
|
|
|
## @ User note
|
|
|
|
Recording fix FIRST (his order), UX queue right behind — spec + settled decisions above, don't re-ask.
|
|
Keep responses SHORT; one integration test per change; commit every unit; push on his word only.
|