Files
LlamaCasty/ytLive.Tests/StepDownPlannerTests.cs
T
gramps 0fa3006da7 feat(33): shed our own capture cost first; the task is done
Last slice. 590 green, 0 warnings.

- **Shed before you charge the viewer.** On machine evidence the policy's first act is
  `ShedCaptureCadence`: `ScreenCaptureFrameSource` widens its conversion interval 10 ms → 33 ms (≈100
  → 30 conversions/s) through a new `ScreenCaptureManager.ShedLoad`. The output tier, the declared fps
  and the viewer's picture are untouched — we stop paying for readbacks we were throwing away. It
  costs no step budget; if the starvation persists, the fps ladder runs on the next clean window.
- The shed is reset on every stop path, so a cheap session cannot leave a later one capped.
- **The session line is a Logs-tab line, not a session-report line.** The session report is the money
  summary (Super Chats, per currency); quality chatter there would bury the numbers it exists for. The
  cause-tagged line already lands in the log at the moment of the step.

TASK 33 is complete: two ladders, budgets, floors, ambiguity, one-way with recovery behind a flag, the
relaunch, the copy, and the cost shed. 18 tests pin the ladders, the copy and the restart.
2026-11-09 13:05:44 -08:00

103 lines
4.1 KiB
C#

using System;
using Xunit;
using ytLive.Models;
using ytLive.Services;
using ytLive.Services.Encoder;
namespace ytLive.Tests;
/// <summary>
/// TASK 33 slice 2 — the adaptor between the pump's structured stats and the pure policy, plus the two
/// sentences a creator ever sees. The copy is pinned here because "own the mistake" is a product rule,
/// not a wording preference: a mechanism string in a toast is the bug.
/// </summary>
public sealed class StepDownPlannerTests
{
private static FramePumpStats Strained(double renderMs = 45, int stalls = 12, int dropped = 0) =>
new(WindowSeconds: 5, FramesSubmitted: 150, FramesRendered: 150,
AvgRenderMs: renderMs, AvgSubmitMs: 1, AvgWaitMs: 14,
WorstRenderMs: renderMs * 2, WorstSubmitMs: 2, Dropped: dropped, Stalls: stalls);
[Fact]
public void MachineEvidenceAfterThreeWindows_LowersTheFrameRateAndSaysWhoseFaultItWas()
{
var now = new DateTime(2026, 11, 9, 12, 0, 0, DateTimeKind.Utc);
var planner = new StepDownPlanner(
new StepDownPolicy(vertical: false, startFps: 60, startBitrateMbps: 8.0), now);
StepDownDecision decision = default;
for (var i = 0; i < 20 && !(decision.Acted && decision.Action != StepAction.ShedCaptureCadence); i++)
{
now = now.AddSeconds(5);
decision = planner.Observe(Strained(), now);
}
Assert.True(decision.Acted);
Assert.Equal(StepAction.LowerFrameRate, decision.Action);
var landed = planner.Current;
Assert.Equal(30, landed.Fps);
var copy = StepDownPlanner.Describe(decision, landed);
Assert.Contains("LlamaCasty couldn't keep up", copy);
Assert.Contains("connection wasn't the problem", copy);
Assert.DoesNotContain("dropped", copy, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void EncoderDrops_LowerTheBitrateAndTheCopyBlamesTheUpload()
{
var now = new DateTime(2026, 11, 9, 12, 0, 0, DateTimeKind.Utc);
var planner = new StepDownPlanner(
new StepDownPolicy(vertical: false, startFps: 60, startBitrateMbps: 8.0), now);
StepDownDecision decision = default;
for (var i = 0; i < 4 && !decision.Acted; i++)
{
now = now.AddSeconds(5);
decision = planner.Observe(Strained(renderMs: 1, stalls: 0, dropped: 30), now);
}
Assert.Equal(StepAction.LowerBitrate, decision.Action);
var landed = planner.Current;
Assert.Equal(60, landed.Fps);
Assert.Equal(1920, landed.Width);
Assert.True(landed.BitrateMbps < 8.0);
var copy = StepDownPlanner.Describe(decision, landed);
Assert.Contains("upload couldn't hold", copy);
Assert.DoesNotContain("couldn't keep up", copy);
}
[Fact]
public void TheSessionReportLine_NamesTheCauseInOnePlace()
{
var when = new DateTime(2026, 11, 9, 12, 4, 0);
var machine = new StepDownDecision(StepCause.Machine, StepAction.LowerFrameRate, "frame rate 60 → 30");
var upload = new StepDownDecision(StepCause.Upload, StepAction.LowerBitrate, "8.0 → 7.2 Mbps on 10% loss");
Assert.Equal("12:04 quality stepped down — encoder (frame rate 60 → 30)",
StepDownPlanner.ReportLine(when, machine));
Assert.Equal("12:04 quality stepped down — upload (8.0 → 7.2 Mbps on 10% loss)",
StepDownPlanner.ReportLine(when, upload));
Assert.Equal(string.Empty,
StepDownPlanner.ReportLine(when, new StepDownDecision(StepCause.None, StepAction.None, null)));
}
[Fact]
public void ShortWindows_AreIgnored_SoAStartupBurstCannotTriggerAStep()
{
var now = new DateTime(2026, 11, 9, 12, 0, 0, DateTimeKind.Utc);
var planner = new StepDownPlanner(
new StepDownPolicy(vertical: false, startFps: 60, startBitrateMbps: 8.0), now);
var burst = Strained() with { WindowSeconds = 1 };
for (var i = 0; i < 20; i++)
{
now = now.AddSeconds(1);
Assert.False(planner.Observe(burst, now).Acted);
}
}
}