TASK 4's ladder and TASK 33's worst-case table both quote numbers the code no longer produces. Replaced with a dated note pointing at the current truth rather than rewriting the decision records: the ladder is 12/10/6 with CBR, and the step-down floor is the tier's YouTube minimum, not a flat 3. Scope justification: Assets/starting-background.jpg is outside this unit — it is the creator's replacement art, still awaiting their visual check.
4.5 KiB
TASK 33 — Automatic step-down when the show can't hold its tier (queued 2026-09-01; v1)
Catalog:
TASKS.md— status and requirements live here. Design settled with the creator 2026-11-09. Not a magenta question of taste: this is the app admitting it is part of the problem when it is.
Goal: the stream degrades predictably and honestly instead of drowning viewers in freeze frames
— and when LlamaCasty itself is the starving process, it says so out loud instead of blaming the
creator's connection. The variable reusable stream (shipped) makes every step zero-API: a tier or
bitrate change is just an encoder relaunch at the same URL.
Cause first, lever second
| Cause | Signal | Lever |
|---|---|---|
| Machine starvation — the PC (often the game) can't make the frame | pump stalls, render time over the frame budget, frames never produced | reduce work: 60 → 30 fps, then 1080 → 720 |
| Upload congestion — the socket can't carry the bits | frames produced on schedule but rejected; encoder queue full; rising submit block time (pump already logs avg submit / avg wait) |
reduce bits: 8.0 → 6.4 → 5.1 → 4.1 Mbps (~20% steps), geometry untouched |
Resolution is not a bandwidth lever: 1080p30 and 720p60 are both 6 Mbps in the shipped table, so that step relieves nothing. Fps is not a congestion lever: same bitrate, same socket.
Shed our own load before asking the viewer to pay
- If our capture/convert cadence is what starves, halve our own capture rate first
(
ScreenCaptureFrameSource.ShedLoad, 10 ms → 33 ms between conversions) — same output tier, less of the creator's machine. It costs no step and changes nothing the viewer sees. - Only then step fps, then resolution.
Shipped 2026-11-09 — StepDownPolicy + StepDownPlanner + FramePump.ChangeEncoderAsync +
MainViewModel.StepDown, 18 tests. The shed runs on machine evidence before the budget is touched.
Budgets, floors, terminal state
| Start | Steps allowed | Worst case it can reach |
|---|---|---|
| 1080p60 | 3, any mix of the two ladders | 720p30 @ 6.4 Mbps |
| 720p60 | 1 | 720p30 @ 4.8 Mbps |
| Vertical 1080p60 | 1 | Vertical 1080p30 @ 6.4 Mbps |
Superseded 2026-11-12: the bitrate floor is the tier's YouTube minimum — 6 Mbps at 1080p60, 5 at 1080p30, 3 at 720p (
Models/RateLadder, passed intoStepDownPolicy) — and the ladder's defaults are 12/10/6. The table above records the 8-Mbps-era examples, not current output.
- Floors: 16:9 never below 720p30; vertical never below Vertical 1080p30.
- Budget exhausted → hold, keep streaming, one persistent warning naming the cause, manual step-up stays available. Never disconnect, never nag repeatedly.
- Trigger: ≥5% dropped over 15 s, twice, 30 s cooldown between steps. Mixed/ambiguous evidence does nothing. One-way within a broadcast (no climb-back; revisit with session data).
Own the mistake in plain words
A toast that reports a mechanism is a bug in the toast. Every message names what happened, who caused it, and what the app already did — no exception text, no error codes:
- ours: "LlamaCasty couldn't keep up with your scene, so it lowered the frame rate to 30. Your connection wasn't the problem."
- theirs: "Your upload couldn't hold 8 Mbps, so LlamaCasty dropped it to 6.4 to stop the freezing. It will stay there for this stream."
One toast per step, plus one Logs-tab line (quality stepped down 12:04 — encoder / — upload,
cause-tagged) so support answers from data, not memory. It is logged rather than added to the session
report on purpose: that report is the money summary, and quality chatter in it would bury the numbers. Wording rules live in
docs/ai/ui-surface.md → Toast notifications.
Tests
Pure StepDownPolicy units (classifier, both ladders, budgets, floors, cooldown, one-way, ambiguity →
no action) + the ONE integration: a fake health stream with sustained drops of each cause produces the
matching step and exactly one encoder restart.
Reference
OBS Dynamic Bitrate (27.2) is the established answer for the congestion half: bitrate-only, small steps, hysteresis. Adopted: bitrate as the sole congestion lever, stepped with cooldown. Discarded for v1: its automatic climb-back (the creator's one-way ruling). Ours: the machine-side ladder and "shed our own cost first" — OBS has no equivalent, and that is the half that matters when the game and the app share one machine.