branding credit: run in every scene and in recordings, not only while live

Creator 2026-09-26: "the made with llamacasty flash should appear in all scenes,
not just live" and "should also appear in recordings". The presenter was
Start()/Stop()-ed from UpdateLiveVisuals()'s IsLive branch, so a recording made
WITHOUT ever going live carried no credit at all -- the exact case the creator hit
while judging compositing from local recordings.

It is now Start()ed once in the MainViewModel ctor and never stopped on live-state
churn. One start covers every scene, the preview, the stream and the recording,
because go-live and local recording are the SAME FramePump: both
Streaming.Operations.cs:68 and :199 call StartAsync with the same brandFlash:
delegate. Verified by reading both call sites, not assumed -- there is no second
encoder path that needed a "redirect". The licence gate needs no live branch at
all: IsPremium's setter already pushes BrandFlashPresenter.Enabled from anywhere.

Fixed a trap that app-lifetime exposed: Enabled = false stops the presenter's
DispatcherTimer to cut the advertisement mid-credit. When Start() was per-go-live
the next go-live restarted it; with a single app-lifetime Start() nothing would,
so a key entered mid-session would leave the credit dead until the process was
restarted. The setter now restarts the timer when re-enabling while _running.

Tests (12 facts in BrandFlashOutputTests, +2):
  Credit_IsComposited_WithNoLiveSession_AndSoARecordingCarriesIt -- drives a real
    never-live VM past the 5s first-flash delay and asks the frame the pump would
    composite. Uses a new internal AdvanceBrandFlash seam; because Advance only
    advances the cadence while the presenter is running, a credit coming out
    proves Start() happened at construction.
  ADowngradeMidSession_RestartsTheCadenceTimer -- asserts the premium/downgrade
    edge decision directly (IsCadenceTimerEnabled) instead of sleeping through a
    30-60s interval, which a synchronous test body cannot observe.

Full suite 364/364.
This commit is contained in:
2026-09-27 09:33:30 -07:00
parent e3e69d941d
commit 9761b1d4be
8 changed files with 193 additions and 25 deletions
+9
View File
@@ -152,6 +152,15 @@ public partial class MainViewModel : ViewModelBase
_brandFlash = new BrandFlashPresenter();
_brandFlash.OnFrame = PublishBrandFlashPreview;
// Start the cadence ONCE for the life of the app, here. CREATOR RULING
// 2026-09-26: the credit must show in every scene and in recordings, so it
// cannot be tied to IsLive (that gate meant a recording made without going
// live carried no credit). The pump reads BrandFlashFrame() whenever it runs
// — go-live and local recording are the same FramePump, so one start covers
// output, recording and the preview pane alike. Enabled is seeded from the
// current licence state; ValidateLicenseAtStartupAsync re-pushes it later.
_brandFlash.Enabled = !IsPremium;
_brandFlash.Start();
_healthPollTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(30) };
_healthPollTimer.Tick += OnHealthPollTick;