diff --git a/Models/OutputProfile.cs b/Models/OutputProfile.cs
index 459de35..97970ca 100644
--- a/Models/OutputProfile.cs
+++ b/Models/OutputProfile.cs
@@ -64,6 +64,6 @@ public static class OutputProfile
// No shipped tier fits the budget: build the floor explicitly rather than trusting
// the table. A build whose tiers were all redefined must still emit a legal frame.
- return candidate ?? new QualityOption(FreeFallbackName, FreeMaxFps, 8.0, 1920, 1080);
+ return candidate ?? new QualityOption(FreeFallbackName, FreeMaxFps, 10.0, 1920, 1080);
}
}
diff --git a/ViewModels/MainViewModel.Streaming.cs b/ViewModels/MainViewModel.Streaming.cs
index 0fb9c2d..38ab1b8 100644
--- a/ViewModels/MainViewModel.Streaming.cs
+++ b/ViewModels/MainViewModel.Streaming.cs
@@ -446,14 +446,22 @@ public partial class MainViewModel : ViewModelBase
private const double MasterFrameWidth = 1920;
private const double MasterFrameHeight = 1080;
+ ///
+ /// The shipped ladder. The rate on each tier is the creator-facing target the encoder is asked to
+ /// hold (CBR — see FfmpegArgs.AddRateControl), between YouTube's H.264 recommended value
+ /// (17 Mbps at 1080p60, 14 at 1080p30, 8 at 720p) and the practitioner sweet spot most guides land
+ /// on for YouTube. The old flat 8 Mbps was inherited from TASK 4 as a "mainstream ceiling" — an
+ /// unmeasured round number that produced a 5.1 Mbps average. Vertical mirrors its landscape pair:
+ /// 1080×1920 is the same pixel count as 1920×1080.
+ ///
public QualityOption[] QualityOptions { get; } =
{
- new("1080p60", 60, 8.0, 1920, 1080),
- new("1080p30", 30, 8.0, 1920, 1080),
+ new("1080p60", 60, 12.0, 1920, 1080),
+ new("1080p30", 30, 10.0, 1920, 1080),
new("720p60", 60, 6.0, 1280, 720),
new("720p30", 30, 6.0, 1280, 720),
- new("Vertical 1080p30", 30, 8.0, 1080, 1920),
- new("Vertical 1080p60", 60, 8.0, 1080, 1920),
+ new("Vertical 1080p30", 30, 10.0, 1080, 1920),
+ new("Vertical 1080p60", 60, 12.0, 1080, 1920),
};
public string ResolutionHelp { get; } =
diff --git a/docs/ai/ui-surface.md b/docs/ai/ui-surface.md
index bbd3f14..f5bc984 100644
--- a/docs/ai/ui-surface.md
+++ b/docs/ai/ui-surface.md
@@ -144,7 +144,7 @@ Placement math itself is covered by `WindowPlacementTests` (pure function, no HW
geometry fields are 44px: the measured width of `-1920` in the app's font at 11px (via WPF's
`FormattedText` — **measure, don't guess**) plus the field's padding and border. Guard:
`ytLive.Tests/GeometryFieldWidthTests.cs`
-- Resolution tiers: 1080p60@8 (default) → 1080p30@8 → 720p60@6 → 720p30@6 → **Vertical 1080p30@8 and 1080p60@8 (9:16, 1080×1920)**. The composition master frame is **always 1920×1080** — a tier is an output rect + target resolution over that master, so source geometry is never rewritten (no rounding drift). 16:9 tiers use the full frame; vertical tiers use a centered **607×1080** window and the preview dims the cropped side strips at 55% black with an accent outline (semi-crop — the cut area stays visible). A resolution badge in the preview corner shows the active tier. The resolution picker and live readouts (bitrate/FPS/dropped/duration) are in the top-left cell above the layers panel; the bottom bar is an empty height-preserving spacer. A **tooltip** explains finding upload bandwidth — an in-app speed test was deliberately dropped (unreliable). The future encoder crops the master to the rect and scales to the tier's Width×Height
+- Resolution tiers: 1080p60@12 (default) → 1080p30@10 → 720p60@6 → 720p30@6 → **Vertical 1080p30@10 and 1080p60@12 (9:16, 1080×1920)** — the `@` is the CBR target the encoder is asked to hold, between YouTube's H.264 recommendation and the practitioner sweet spot (its 14/17/8 recommendations are the max, the tier minimums 5/6/3 the step-down floor). Vertical mirrors its landscape pair: same pixel count, same rate. The old flat 8 Mbps was an inherited round number, not a measurement. The composition master frame is **always 1920×1080** — a tier is an output rect + target resolution over that master, so source geometry is never rewritten (no rounding drift). 16:9 tiers use the full frame; vertical tiers use a centered **607×1080** window and the preview dims the cropped side strips at 55% black with an accent outline (semi-crop — the cut area stays visible). A resolution badge in the preview corner shows the active tier. The resolution picker and live readouts (bitrate/FPS/dropped/duration) are in the top-left cell above the layers panel; the bottom bar is an empty height-preserving spacer. A **tooltip** explains finding upload bandwidth — an in-app speed test was deliberately dropped (unreliable). The future encoder crops the master to the rect and scales to the tier's Width×Height
- **Locked output tiers stay visible and clickable.** They are dimmed for non-owners. Choosing one keeps the free tier selected and opens About in-app; only the explicit llamacasty.com link opens a browser.
- Crash diagnosis: `AppLog` writes startup checkpoints to `%APPDATA%\ytLlive\startup.log`; `App.xaml.cs` logs `DispatcherUnhandledException`/`AppDomain.UnhandledException`. When WPF won't run from WSL, this log is how you find the failure (it caught the `MenuItemRole.Separator` XAML crash and the ComboBox SelectionBoxItem bug). **The log is SIZE-CAPPED (TASK 55, 2026-10-22):** at 5 MB `startup.log` rolls to `startup.1.log` (one rolled copy kept) — the policy lives in `Helpers/LogRotation.cs` as a pure function over a path, so `AppLog`'s 156-call-site surface never changes and the rule is testable on its own. Guard: `ytLive.Tests/AppLogRotationTests.cs`
diff --git a/ytLive.Tests/OutputProfileTests.cs b/ytLive.Tests/OutputProfileTests.cs
index 966cdfa..0253c6e 100644
--- a/ytLive.Tests/OutputProfileTests.cs
+++ b/ytLive.Tests/OutputProfileTests.cs
@@ -21,12 +21,12 @@ public class OutputProfileTests
/// step deliberately: this is the table the budget has to cover.
private static readonly QualityOption[] Shipped =
{
- new("1080p60", 60, 8.0, 1920, 1080),
- new("1080p30", 30, 8.0, 1920, 1080),
+ new("1080p60", 60, 12.0, 1920, 1080),
+ new("1080p30", 30, 10.0, 1920, 1080),
new("720p60", 60, 6.0, 1280, 720),
new("720p30", 30, 6.0, 1280, 720),
- new("Vertical 1080p30", 30, 8.0, 1080, 1920),
- new("Vertical 1080p60", 60, 8.0, 1080, 1920),
+ new("Vertical 1080p30", 30, 10.0, 1080, 1920),
+ new("Vertical 1080p60", 60, 12.0, 1080, 1920),
};
private static QualityOption Tier(string name) => Shipped.Single(t => t.Display == name);
@@ -40,7 +40,7 @@ public class OutputProfileTests
Assert.Equal(1920, resolved.Width);
Assert.Equal(1080, resolved.Height);
Assert.Equal(30, resolved.Fps);
- Assert.Equal(8.0, resolved.Bitrate);
+ Assert.Equal(10.0, resolved.Bitrate);
}
[Fact]
@@ -77,7 +77,7 @@ public class OutputProfileTests
Assert.Equal(1920, resolved.Width);
Assert.Equal(1080, resolved.Height);
Assert.Equal(30, resolved.Fps);
- Assert.Equal(8.0, resolved.Bitrate);
+ Assert.Equal(10.0, resolved.Bitrate);
}
///