Retail's world-camera FOV is not a constant: the applied vertical FOV is m_fGameFOV / (viewportAspect - 0.1), recomputed on every aspect or game-FOV change (CreatureMode smartbox sites 0x00452b2f/0x00453b14), gated by Render::SetFOVRad's open (0, pi) acceptance (0x0054b2d0 - rejected results keep the previous FOV). m_fGameFOV defaults to pi/2 = 90 degrees (0x00454649) and is what the Field of View option sets in degrees (0x00451e6a; registered range [10,160] default 90 - gmClient::InitUIPreferences @0x004035b0). Net effect: the horizontal view stays ~85-90 degrees across aspect ratios; wide screens trim the vertical slice instead of ballooning the sides. acdream hardcoded FovY = pi/3 = 60 degrees on all four world cameras, aspect-independent, and the Config slider wrote raw vertical-FOV degrees. New: RetailFieldOfView (the law + gate, decomp-cited), CameraController.GameFovRadians + SetGameFov + one ApplyProjection chokepoint recomputing every camera on SetAspect/SetGameFov/ EnterChaseMode/RestoreState; ApplyFieldOfView now feeds the law; DisplaySettings.Default.FieldOfView 60 -> 90 (the retail registered default; the stored number changed MEANING with this commit). The same seam closes a second latent bug the 2026-08-13 "squished" gate report exposed: SetAspect only ever updated Orbit/Fly - the CHASE cameras (the ones the player looks through) kept their creation-time aspect across every mid-session resize, drawing the world at the old shape stretched onto the new viewport. The paperdoll camera stays outside the law by design (retail portrait mode is UseSharpMode, not smartbox - DollCamera's own doc). Tests: RetailFieldOfViewTests (golden law values at 4:3/16:9/21:9, the constant-horizontal property, the rejection gate, controller propagation incl. chase attach/restore + rejected-law aspect-still-propagates); DisplaySettingsTests + RuntimeSettingsControllerTests updated to the new semantics. App suite 4,953/3 skips; UI.Abstractions 916/0. AD-89 retired in this commit; user settings.json migrated 60->90 by hand (stale pre-port default). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
76 lines
2.7 KiB
C#
76 lines
2.7 KiB
C#
using AcDream.UI.Abstractions.Panels.Settings;
|
||
|
||
namespace AcDream.UI.Abstractions.Tests.Panels.Settings;
|
||
|
||
/// <summary>
|
||
/// L.0: <see cref="DisplaySettings"/> is the immutable record of
|
||
/// display-tab preferences. Defaults are pinned here so a regression
|
||
/// (e.g. someone changing the default FOV out from under users)
|
||
/// surfaces immediately.
|
||
/// </summary>
|
||
public sealed class DisplaySettingsTests
|
||
{
|
||
[Fact]
|
||
public void Default_values_match_normal_client_policy()
|
||
{
|
||
// Defaults pin the normal-client startup policy:
|
||
// · Resolution matches WindowOptions (1280×720 in GameWindow.Run)
|
||
// · FieldOfView is retail's m_fGameFOV in DEGREES — registered
|
||
// default 90, range [10,160] (gmClient::InitUIPreferences
|
||
// @0x004035b0; #389 replaced the pre-port 60, which encoded the
|
||
// retired direct-vertical-FOV semantics)
|
||
// · VSync is on so normal presentation follows monitor refresh
|
||
// · ShowFps false matches retail's initially-hidden SmartBox meter
|
||
var d = DisplaySettings.Default;
|
||
Assert.Equal("1280x720", d.Resolution);
|
||
Assert.False(d.Fullscreen);
|
||
Assert.True(d.VSync);
|
||
Assert.Equal(90f, d.FieldOfView);
|
||
Assert.Equal(1.0f, d.Gamma);
|
||
Assert.False(d.ShowFps);
|
||
}
|
||
|
||
[Fact]
|
||
public void AvailableResolutions_includes_common_16_9_options()
|
||
{
|
||
var list = DisplaySettings.AvailableResolutions;
|
||
Assert.Contains("1280x720", list);
|
||
Assert.Contains("1920x1080", list);
|
||
Assert.Contains("2560x1440", list);
|
||
Assert.Contains("3840x2160", list);
|
||
// List should be ascending so the dropdown reads naturally.
|
||
for (int i = 1; i < list.Count; i++)
|
||
{
|
||
int prevW = ParseWidth(list[i - 1]);
|
||
int curW = ParseWidth(list[i]);
|
||
Assert.True(curW >= prevW, $"Resolutions not sorted: {list[i - 1]} >= {list[i]}");
|
||
}
|
||
}
|
||
|
||
[Fact]
|
||
public void Equality_is_value_based()
|
||
{
|
||
var a = DisplaySettings.Default;
|
||
var b = DisplaySettings.Default with { Fullscreen = true };
|
||
var c = DisplaySettings.Default with { Fullscreen = true };
|
||
Assert.NotEqual(a, b);
|
||
Assert.Equal(b, c);
|
||
}
|
||
|
||
[Fact]
|
||
public void With_expression_clones_one_field()
|
||
{
|
||
var d = DisplaySettings.Default with { FieldOfView = 90f };
|
||
Assert.Equal(90f, d.FieldOfView);
|
||
// Other fields untouched.
|
||
Assert.Equal("1280x720", d.Resolution);
|
||
Assert.True(d.VSync);
|
||
Assert.False(d.ShowFps);
|
||
}
|
||
|
||
private static int ParseWidth(string res)
|
||
{
|
||
int x = res.IndexOf('x');
|
||
return int.Parse(res.AsSpan(0, x));
|
||
}
|
||
}
|