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.
This commit is contained in:
2026-11-09 13:05:44 -08:00
parent 134103c08b
commit 0fa3006da7
7 changed files with 73 additions and 10 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ public sealed class StepDownPlannerTests
new StepDownPolicy(vertical: false, startFps: 60, startBitrateMbps: 8.0), now);
StepDownDecision decision = default;
for (var i = 0; i < 4 && !decision.Acted; i++)
for (var i = 0; i < 20 && !(decision.Acted && decision.Action != StepAction.ShedCaptureCadence); i++)
{
now = now.AddSeconds(5);
decision = planner.Observe(Strained(), now);
+23 -4
View File
@@ -30,20 +30,39 @@ public sealed class StepDownPolicyTests
new(WindowSeconds: 5, PumpStalls: 0, PumpDrops: 0, EncoderDrops: 0,
AvgRenderMs: 1, AvgSubmitWaitMs: 1);
/// <summary>Feeds windows every 5 s until the policy acts; returns that decision, or the last
/// decision it reached when it never acted (so the "holding" reason is visible to the test).</summary>
private static StepDownDecision Run(StepDownPolicy policy, StepDownSample sample, ref double t, int maxWindows = 12)
/// <summary>Feeds windows every 5 s until the policy changes QUALITY; the capture-load shed is a
/// pre-step (no budget, nothing the viewer sees) so it is skipped here, and tested on its own.</summary>
private static StepDownDecision Run(StepDownPolicy policy, StepDownSample sample, ref double t, int maxWindows = 24)
{
var last = new StepDownDecision(StepCause.None, StepAction.None, null);
for (var i = 0; i < maxWindows; i++)
{
t += 5;
last = policy.Observe(sample, TimeSpan.FromSeconds(t));
if (last.Acted) return last;
if (last.Acted && last.Action != StepAction.ShedCaptureCadence) return last;
}
return last;
}
[Fact]
public void MachineStarvation_ShedsOurOwnCaptureCostBeforeLoweringQuality()
{
var policy = new StepDownPolicy(vertical: false, startFps: 60, startBitrateMbps: 8.0);
var t = 0.0;
StepDownDecision first = default;
for (var i = 0; i < 6 && !first.Acted; i++)
{
t += 5;
first = policy.Observe(Machine(), TimeSpan.FromSeconds(t));
}
Assert.Equal(StepAction.ShedCaptureCadence, first.Action);
Assert.Equal(60, policy.Fps);
Assert.Equal(0, policy.StepsUsed); // shedding is not a quality step
Assert.Equal(1920, policy.Width);
}
[Fact]
public void MachineStarvation_LowersFrameRateBeforeAnythingElse()
{