fix #389: port retail's SmartboxFOV law; retire AD-89 (display slice 1)

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>
This commit is contained in:
Erik 2026-08-13 17:08:41 +02:00
parent a1efc8bcb3
commit 7e0c130344
13 changed files with 334 additions and 22 deletions

View file

@ -79,15 +79,18 @@ public sealed record DisplaySettings(
{
/// <summary>Values used on first launch / when settings.json is absent.
/// Geometry defaults preserve the pre-L.0 runtime state: Resolution
/// matches the WindowOptions startup size (1280×720) and FieldOfView
/// matches camera FovY (60°). VSync defaults on so normal rendering is
/// synchronized to the active monitor, while
/// matches the WindowOptions startup size (1280×720). FieldOfView is
/// retail's <c>m_fGameFOV</c> in degrees — registered default 90, range
/// [10,160] (<c>gmClient::InitUIPreferences @0x004035b0</c>; #389
/// corrected the pre-port 60, which encoded the old direct-vertical-FOV
/// semantics this record no longer means). VSync defaults on so normal
/// rendering is synchronized to the active monitor, while
/// ShowFps matches retail's initially-hidden SmartBox FPS readout.</summary>
public static DisplaySettings Default { get; } = new(
Resolution: "1280x720",
Fullscreen: false,
VSync: true,
FieldOfView: 60f,
FieldOfView: 90f,
Gamma: 1.0f,
ShowFps: false,
Quality: QualityPreset.High,