feat(entitlement): gate 60 fps output through Steam

This commit is contained in:
2026-11-04 12:14:46 -08:00
parent 1993e7cc8b
commit 7216c95449
12 changed files with 188 additions and 72 deletions
+20 -24
View File
@@ -8,8 +8,8 @@ using ytLive.Models;
namespace ytLive.Tests;
/// <summary>
/// The output-profile rule (2026-11-01): a build without Pro may emit at most 921,600 pixels
/// at 30 fps, and this is the only place that decision is made.
/// The output-profile rule: a free build may emit every shipped resolution at 30 fps;
/// 60 fps requires Pro, independent of resolution.
///
/// <para>The one thing that must never break: a cracked or unlicensed build silently gets a
/// *smaller frame*, never a smaller feature set. If a future tier is added above the budget,
@@ -32,21 +32,21 @@ public class OutputProfileTests
private static QualityOption Tier(string name) => Shipped.Single(t => t.Display == name);
[Fact]
public void A_free_build_resolves_1080p60_down_to_720p30_with_its_own_bitrate()
public void A_free_build_resolves_1080p60_to_1080p30()
{
var resolved = OutputProfile.Resolve(Tier("1080p60"), Shipped, pro: false);
Assert.Equal(1280, resolved.Width);
Assert.Equal(720, resolved.Height);
Assert.Equal("1080p30", resolved.Display);
Assert.Equal(1920, resolved.Width);
Assert.Equal(1080, resolved.Height);
Assert.Equal(30, resolved.Fps);
// The bitrate follows the profile: a capped user is not offered an 8 Mbps budget.
Assert.Equal(6.0, resolved.Bitrate);
Assert.Equal(8.0, resolved.Bitrate);
}
[Fact]
public void A_free_build_leaves_its_own_tier_untouched()
{
var requested = Tier("720p30");
var requested = Tier("1080p30");
Assert.Same(requested, OutputProfile.Resolve(requested, Shipped, pro: false));
}
@@ -57,21 +57,15 @@ public class OutputProfileTests
Assert.Same(tier, OutputProfile.Resolve(tier, Shipped, pro: true));
}
[Theory]
[InlineData("1080p60")]
[InlineData("1080p30")]
[InlineData("720p60")]
[InlineData("720p30")]
[InlineData("Vertical 1080p30")]
[InlineData("Vertical 1080p60")]
public void No_free_selection_can_emit_more_than_the_budget(string name)
[Fact]
public void Free_access_depends_on_frame_rate_not_resolution()
{
var resolved = OutputProfile.Resolve(Tier(name), Shipped, pro: false);
Assert.True(resolved.Width * resolved.Height <= OutputProfile.FreeMaxPixels,
$"{resolved.Display} resolves to {resolved.Width * resolved.Height} px on a free build");
Assert.True(resolved.Fps <= OutputProfile.FreeMaxFps,
$"{resolved.Display} resolves to {resolved.Fps} fps on a free build");
foreach (var tier in Shipped)
{
var freeAllowed = tier.Fps <= OutputProfile.FreeMaxFps;
Assert.Equal(freeAllowed, OutputProfile.IsAllowed(tier, pro: false));
Assert.Equal(!freeAllowed, tier.RequiresPro);
}
}
[Fact]
@@ -79,9 +73,11 @@ public class OutputProfileTests
{
var resolved = OutputProfile.Resolve(Tier("1080p60"), Array.Empty<QualityOption>(), pro: false);
Assert.Equal(1280, resolved.Width);
Assert.Equal(720, resolved.Height);
Assert.Equal("1080p30", resolved.Display);
Assert.Equal(1920, resolved.Width);
Assert.Equal(1080, resolved.Height);
Assert.Equal(30, resolved.Fps);
Assert.Equal(8.0, resolved.Bitrate);
}
/// <summary>