Files
LlamaCasty/ViewModels/MainViewModel.OutputProfile.cs
gramps 1e3a4f7fe0 feat(entitlement): pre-release switch so Release builds can test 60 fps
The frame-rate boundary is correct as a product rule, but it only bites in a
Release build, where DevBuild.IsDevelopment is false and DeveloperMode cannot
unlock anything — exactly the build whose performance numbers are worth reading
(see docs/ai/environment.md: Debug pixel paths are several times slower). 60 fps
could not be tested at all.

OutputProfile.EntitlementEnforced is false: every shipped tier is selectable by
any build, and HasProOutput — the one property the tier setter, the dropdown's
strikethrough and the locked-item style all read — answers yes regardless of
ownership. static readonly rather than const, so the consumed `||` does not fold
into unreachable operand code (same reason as DevBuild.IsDevelopment).

The licence indicator row is suppressed while the switch is off: it exists to
explain what the boundary costs, and claiming "Steam ID Limit: 60fps" to an
unowned build would be a false statement. Docs corrected in place — the enforced
rule is unchanged, only its state is recorded.

Tests: one integration case covers both switch states (unowned build reaches
1080p60 with the switch off, 1080p30 with it on), so flipping at launch leaves no
red test behind. The two suites that anchor on the pinned licence row early-return
while it is absent; EntitlementTests covers that state meanwhile.

667 pass, build 0 warnings.
2026-11-12 07:50:59 -08:00

93 lines
3.2 KiB
C#

using ytLive.Helpers;
using ytLive.Models;
using ytLive.Services.Entitlement;
namespace ytLive.ViewModels;
/// <summary>
/// The app-wide Steam ownership flag and its output-profile consumer.
///
/// <para>Only the entitlement service decides whether the current Steam account owns the paid app.
/// Development builds use a fake owned result; Release builds use Steam.</para>
/// </summary>
public partial class MainViewModel
{
private IEntitlement _entitlement = CreateEntitlement();
private bool _isAppOwned;
private string? _entitlementErrorMessage;
public bool IsAppOwned
{
get => _isAppOwned;
private set
{
var changed = SetProperty(ref _isAppOwned, value);
EntitlementErrorMessage = _entitlement.ErrorMessage;
if (!changed) return;
OnPropertyChanged(nameof(HasProOutput));
OnPropertyChanged(nameof(LicenceStateLabel));
RefreshSteamLicenceAlert();
if (!HasProOutput && _selectedQuality is { RequiresPro: true } && CanChangeQuality)
SelectedQuality = QualityOptions.Single(option => option.Display == OutputProfile.FreeFallbackName);
}
}
public string? EntitlementErrorMessage
{
get => _entitlementErrorMessage;
private set => SetProperty(ref _entitlementErrorMessage, value);
}
/// <summary>
/// May this run emit 60 fps? True while the pre-release switch is off
/// (<see cref="OutputProfile.EntitlementEnforced"/>), or for a development build's override, or
/// when Steam says the paid app is owned. Everything that gates on the frame rate reads this one
/// property: the tier setter, the dropdown's strikethrough, the locked-item style.
/// </summary>
public bool HasProOutput => !OutputProfile.EntitlementEnforced || DeveloperMode || IsAppOwned;
private static IEntitlement CreateEntitlement()
{
#if DEBUG
return new FakeEntitlement(true);
#else
return SteamEntitlement.NotConfigured();
#endif
}
/// <summary>Test seam: swap the ownership source without a Steam client.</summary>
internal IEntitlement EntitlementOverride
{
get => _entitlement;
set
{
_entitlement = value;
IsAppOwned = value.IsAppOwned;
EntitlementErrorMessage = value.ErrorMessage;
}
}
private bool _developerMode;
/// <summary>
/// Development-only override: ignore the output budget and allow every shipped tier.
///
/// <para>There is no UI to reach this in a Release build — the panel that hosts the
/// checkbox is excluded from the Release compile — and the setter refuses the value
/// unless <see cref="DevBuild.IsDevelopment"/> is true, so even a hand-edited settings
/// file cannot unlock it.</para>
/// </summary>
public bool DeveloperMode
{
get => _developerMode;
set
{
if (!DevBuild.IsDevelopment) return;
if (!SetProperty(ref _developerMode, value)) return;
OnPropertyChanged(nameof(HasProOutput));
OnPropertyChanged(nameof(LicenceStateLabel));
RefreshSteamLicenceAlert();
}
}
}