feat(profile): the free budget is pixels and fps, enforced once — with a dev-only release hatch
The paid delta is output quality, so the rule needs to be measurable rather than a word:
921,600 pixels and 30 fps, fail-closed. Models/OutputProfile.cs is that rule as pure
policy — a permitted tier passes through untouched (Pro, or the free tier's own 720p30),
a disallowed one resolves to the best tier the budget allows and carries ITS bitrate, so
a capped user is never offered an 8 Mbps budget for a 720p frame.
Applied once, in BuildEncoderOptions. That is genuinely one place: the stream and the
local recording are two output blocks of ONE ffmpeg process fed by one EncoderOptions,
so there is no second encoder to forget. (commercial.md claimed "both encoders — never
two checks"; the code is stronger than the doc, and the doc is corrected.)
Developer override, as asked: a "Developer mode — all resolutions" checkbox in App
Settings, bound to MainViewModel.DeveloperMode, which feeds HasProOutput. It is not
hidden in a release build — Controls/DeveloperPanel's Page and Compile items are removed
when Configuration != Debug, and OverlayHost adds it at runtime inside #if DEBUG, so a
shipped exe has no BAML for it at all. The setter also refuses the value when
DevBuild.IsDevelopment is false, which is now the single source for that question (the
scoreboard partial had its own duplicate const).
Two traps worth their lines: a `const bool` dev flag makes `if (!Flag) return;` an
unreachable-code warning in one configuration or the other (CS0162), so DevBuild uses
static readonly; and a Condition on an ItemGroup is invisible to a test reading
item.Attribute("Condition") — read the parent.
Tests: 14 pass across OutputProfileTests, AssemblyIdentityTests and StorePackagingTests.
The invariant test is the one that matters: iterate every shipped tier under a free
entitlement and assert none emits more than the budget, so a future 4K tier fails here
instead of shipping to everyone. App builds Debug and Release at 0 warnings.
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
<UserControl x:Class="ytLive.Controls.DeveloperPanel"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- ⛔ THIS FILE IS EXCLUDED FROM THE RELEASE COMPILE (see ytLive.csproj: Page Remove +
|
||||
Compile Remove under '$(Configuration) != Debug'). It is not merely hidden in a
|
||||
Release build — its BAML never exists, so there is nothing to reveal. Do not add it
|
||||
to a window or reference it from code that compiles in Release; the only host is
|
||||
OverlayHost.xaml.cs inside #if DEBUG. -->
|
||||
<StackPanel>
|
||||
<TextBlock Text="Developer" Foreground="{StaticResource Brush.Attention}" FontSize="11"
|
||||
FontWeight="SemiBold" Margin="0,10,0,2"/>
|
||||
|
||||
<CheckBox Margin="10,2,0,0" IsChecked="{Binding DeveloperMode, Mode=TwoWay}"
|
||||
Foreground="{StaticResource Brush.OnSurface}"
|
||||
Content="Developer mode — all resolutions, output budget ignored"/>
|
||||
|
||||
<TextBlock Text="Debug builds only. Overrides the free output profile (921,600 px / 30 fps). Never present in a release build."
|
||||
Foreground="{StaticResource Brush.Attention}" FontSize="10" TextWrapping="Wrap"
|
||||
Margin="10,4,0,8"/>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace ytLive.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// The developer affordances that must never exist in a shipped build: today, the
|
||||
/// all-resolutions override for the output profile.
|
||||
///
|
||||
/// <para><b>This file is excluded from the Release compile</b> (<c>ytLive.csproj</c> removes
|
||||
/// the Page and this Compile item when <c>Configuration != Debug</c>), so a Release binary
|
||||
/// contains no BAML for it and cannot reveal it. That is deliberate over the cheaper
|
||||
/// "collapse the panel" approach: a collapsed panel still exists in the visual tree of every
|
||||
/// shipped copy, and "not visible" is a much weaker claim than "not compiled".</para>
|
||||
/// </summary>
|
||||
public partial class DeveloperPanel : UserControl
|
||||
{
|
||||
public DeveloperPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,11 @@
|
||||
<Slider Minimum="100" Maximum="2000" TickFrequency="50" IsSnapToTickEnabled="True"
|
||||
Value="{Binding TransitionDurationMs, Mode=TwoWay}" VerticalAlignment="Center"/>
|
||||
</DockPanel>
|
||||
|
||||
<!-- Development panel host. Empty in a Release build: the panel itself is
|
||||
excluded from the Release compile, and OverlayHost.xaml.cs only fills
|
||||
this inside #if DEBUG. Never put content here directly. -->
|
||||
<StackPanel x:Name="DeveloperPanelHost"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Report Bug -->
|
||||
|
||||
@@ -9,6 +9,12 @@ public partial class OverlayHost : UserControl
|
||||
public OverlayHost()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
// Development affordances are added at runtime, never declared in XAML: the panel is
|
||||
// excluded from the Release compile (ytLive.csproj), so this line is the only
|
||||
// reference to it and a Release build has none.
|
||||
DeveloperPanelHost.Children.Add(new DeveloperPanel());
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Is the enlarged brand-logo preview showing? Test seam — a Popup is a separate
|
||||
|
||||
Reference in New Issue
Block a user