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); } ///