diff --git a/ViewModels/MainViewModel.License.cs b/ViewModels/MainViewModel.License.cs
index 522246d..9e83a5a 100644
--- a/ViewModels/MainViewModel.License.cs
+++ b/ViewModels/MainViewModel.License.cs
@@ -23,7 +23,7 @@ public partial class MainViewModel
if (!SetProperty(ref _isPremium, value)) return;
// The flash is composited into the broadcast, so the gate has to be pushed
// all the way into the renderer — not just the preview.
- _brandFlash.Enabled = !value;
+ _brandFlash.Enabled = ShowBrandFlash && !value;
}
}
diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs
index d725538..1738803 100644
--- a/ViewModels/MainViewModel.cs
+++ b/ViewModels/MainViewModel.cs
@@ -51,11 +51,9 @@ public partial class MainViewModel : ViewModelBase
private WebView2Manager? _webView2Manager;
- // Branding flash (monetization): a full-frame "made with LlamaCasty!" shown
- // for ~1s every 300s on the live output, ~25% opacity. Paid unlock sets
- // BrandFlashEnabled = false. See ai.md "Monetization".
- // The free-tier branding credit. Composed into the OUTPUT by FramePump, not just
- // previewed — see BrandFlashPresenter. Gated on IsPremium (Polar), never a toggle.
+ // Branding credit: "made with LlamaCasty!" composed into the OUTPUT by FramePump, not
+ // just previewed — see BrandFlashPresenter. OFF by default since 2026-10-09
+ // (ShowBrandFlash = false, free-forever: no branding on the stream); the code stays.
private readonly BrandFlashPresenter _brandFlash;
public SceneElement? SelectedElement
@@ -122,11 +120,17 @@ public partial class MainViewModel : ViewModelBase
public bool ShowPreviewPlaceholder => !ShowEmptySceneHint && ActiveBackgroundImage == null && BackgroundImage == null;
- // Paid unlock flips this off (see ai.md "Monetization"). When disabled the
- // presenter stops emitting, so any active credit dies on the next tick — and a
- // credit already in flight is cut, not run out. Derived, never assignable: the
- // whole point is that nobody can turn the advertising on a paying stream.
- public bool BrandFlashEnabled => !IsPremium;
+ /// Master switch for the "made with LlamaCasty!" credit (creator ruling
+ /// 2026-10-09: turn it OFF, but keep the feature). false = the credit never
+ /// shows — not in the preview, the stream, or recordings. Read when the view model is
+ /// constructed (and on every licence change), so set it before a MainWindow is
+ /// created; the tests that exercise the credit opt in that way.
+ internal static bool ShowBrandFlash { get; set; } = false;
+
+ // When disabled the presenter stops emitting, so any active credit dies on the next
+ // tick — and a credit already in flight is cut, not run out. Derived, never
+ // assignable from the UI: the master switch AND the licence must both allow it.
+ public bool BrandFlashEnabled => ShowBrandFlash && !IsPremium;
// Window geometry pass-through (code-behind owns the WPF specifics).
public (double Left, double Top, double Width, double Height, string State)? RestoreWindowState()
@@ -184,7 +188,7 @@ public partial class MainViewModel : ViewModelBase
// — 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.Enabled = BrandFlashEnabled;
_brandFlash.Start();
_healthPollTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(30) };
diff --git a/ai.md b/ai.md
index ebcbc46..f3133ed 100644
--- a/ai.md
+++ b/ai.md
@@ -1767,6 +1767,15 @@ Apply this to every UI decision:
## Monetization (design decision — the branding flash is the sword)
+> **⛔ Superseded 2026-10-08/09 — free forever, no branding on the stream.** The credit is now
+> **OFF by default**: `MainViewModel.ShowBrandFlash` (internal static, default `false`) is the
+> master switch, and `BrandFlashEnabled => ShowBrandFlash && !IsPremium` feeds the presenter at
+> construction and on every licence change. The code (`BrandFlashPresenter`, the FramePump
+> `brandFlash:` seam, the preview layer) is kept, not deleted — creator ruling: "don't delete it,
+> just make it conditionally displayed". `BrandFlashOutputTests` opt in by setting the switch
+> before constructing a `MainWindow`; `Credit_IsOffByDefault_EvenOnTheFreeTier` guards the default.
+> Everything below is the historical paid-tier design.
+
Full pricing and the Year 1 revenue forecast in `MONETIZATION.md` (gitignored). ⚠️ **No Polar
product, discount code, or license key exists any more** — billing is Microsoft Store IAP.
Brand palette and assets in `MARCOM.md` (gitignored). This section covers the in-app model.
diff --git a/ytLive.Tests/BrandFlashOutputTests.cs b/ytLive.Tests/BrandFlashOutputTests.cs
index 69faa6b..7ad5722 100644
--- a/ytLive.Tests/BrandFlashOutputTests.cs
+++ b/ytLive.Tests/BrandFlashOutputTests.cs
@@ -164,6 +164,7 @@ public sealed class BrandFlashOutputTests
{
var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-test-{Guid.NewGuid():N}.db");
MainViewModel.LayoutPathOverride = tempDb;
+ MainViewModel.ShowBrandFlash = true; // the credit is off by default (2026-10-09)
var window = new MainWindow();
var vm = (MainViewModel)window.DataContext;
try
@@ -202,6 +203,7 @@ public sealed class BrandFlashOutputTests
{
window.Close();
MainViewModel.LayoutPathOverride = null;
+ MainViewModel.ShowBrandFlash = false;
SqliteConnection.ClearAllPools();
try { File.Delete(tempDb); } catch { /* best-effort cleanup */ }
}
@@ -381,6 +383,36 @@ public sealed class BrandFlashOutputTests
Assert.True(prop.CanRead, "the preview needs to read the gate state");
}
+ /// Creator ruling 2026-10-09: the credit is OFF by default — a freshly
+ /// constructed app with no licence still emits nothing to the preview or the output.
+ [Fact]
+ public void Credit_IsOffByDefault_EvenOnTheFreeTier()
+ {
+ _app.Run(() =>
+ {
+ Assert.False(MainViewModel.ShowBrandFlash);
+ var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-test-{Guid.NewGuid():N}.db");
+ MainViewModel.LayoutPathOverride = tempDb;
+ var window = new MainWindow();
+ var vm = (MainViewModel)window.DataContext;
+ try
+ {
+ Assert.False(vm.IsPremium);
+ Assert.False(vm.BrandFlashEnabled);
+ vm.AdvanceBrandFlash(120); // past several cadence windows
+ Assert.Null(vm.BrandFlashFrame());
+ Assert.False(vm.BrandFlashVisible);
+ }
+ finally
+ {
+ window.Close();
+ MainViewModel.LayoutPathOverride = null;
+ SqliteConnection.ClearAllPools();
+ try { File.Delete(tempDb); } catch { /* best-effort cleanup */ }
+ }
+ });
+ }
+
// ---------- the credit is NOT a go-live-only behaviour ----------
/// Reported 2026-09-26: "the made with llamacasty flash should appear in
@@ -400,6 +432,7 @@ public sealed class BrandFlashOutputTests
{
var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-test-{Guid.NewGuid():N}.db");
MainViewModel.LayoutPathOverride = tempDb;
+ MainViewModel.ShowBrandFlash = true; // the credit is off by default (2026-10-09)
var window = new MainWindow();
var vm = (MainViewModel)window.DataContext;
try
@@ -441,6 +474,7 @@ public sealed class BrandFlashOutputTests
{
window.Close();
MainViewModel.LayoutPathOverride = null;
+ MainViewModel.ShowBrandFlash = false;
SqliteConnection.ClearAllPools();
try { File.Delete(tempDb); } catch { /* best-effort cleanup */ }
}