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:
parent
a1efc8bcb3
commit
7e0c130344
13 changed files with 334 additions and 22 deletions
|
|
@ -45,10 +45,20 @@ public sealed class CameraController
|
|||
private enum Mode { Orbit, Fly, Chase }
|
||||
private Mode _mode = Mode.Orbit;
|
||||
|
||||
/// <summary>Retail <c>m_fGameFOV</c> (#389): the user-facing FOV the Config
|
||||
/// slider sets in degrees, default 90°. The APPLIED per-camera vertical FOV
|
||||
/// is derived from it and the current aspect via
|
||||
/// <see cref="RetailFieldOfView.TryAppliedVerticalFov"/> — see that class's
|
||||
/// doc for the decomp anchors.</summary>
|
||||
public float GameFovRadians { get; private set; } = RetailFieldOfView.DefaultGameFovRadians;
|
||||
|
||||
private float _aspect = 16f / 9f;
|
||||
|
||||
public CameraController(OrbitCamera orbit, FlyCamera fly)
|
||||
{
|
||||
Orbit = orbit;
|
||||
Fly = fly;
|
||||
ApplyProjection();
|
||||
}
|
||||
|
||||
public void ToggleFly()
|
||||
|
|
@ -65,6 +75,14 @@ public sealed class CameraController
|
|||
{
|
||||
Chase = legacy;
|
||||
RetailChase = retail;
|
||||
// #389: freshly attached chase cameras carry their own initialiser
|
||||
// aspect/FOV — bring them onto the controller's current aspect +
|
||||
// smartbox FOV immediately (before this, a resize while chase cameras
|
||||
// were attached never reached them at all: SetAspect only updated
|
||||
// Orbit/Fly, so the world rendered at the creation-time aspect
|
||||
// stretched onto the new viewport — the 2026-08-13 "squished" gate
|
||||
// report's second mechanism).
|
||||
ApplyProjection();
|
||||
_mode = Mode.Chase;
|
||||
ModeChanged?.Invoke(IsChaseMode);
|
||||
}
|
||||
|
|
@ -83,8 +101,41 @@ public sealed class CameraController
|
|||
|
||||
public void SetAspect(float aspect)
|
||||
{
|
||||
Orbit.Aspect = aspect;
|
||||
Fly.Aspect = aspect;
|
||||
_aspect = aspect;
|
||||
ApplyProjection();
|
||||
}
|
||||
|
||||
/// <summary>Sets retail's <c>m_fGameFOV</c> (#389) and recomputes every
|
||||
/// camera's applied FOV. Driven by the Config tab's Field of View value
|
||||
/// through <c>RuntimeSettingsStartupTargets.ApplyFieldOfView</c>.</summary>
|
||||
public void SetGameFov(float gameFovRadians)
|
||||
{
|
||||
GameFovRadians = gameFovRadians;
|
||||
ApplyProjection();
|
||||
}
|
||||
|
||||
/// <summary>Pushes the current aspect + smartbox-derived vertical FOV onto
|
||||
/// every attached camera. When the law's result fails retail's
|
||||
/// <c>SetFOVRad</c> gate (see <see cref="RetailFieldOfView.TryAppliedVerticalFov"/>),
|
||||
/// the cameras keep their previous FOV — retail's exact behavior — but the
|
||||
/// aspect still propagates (retail's viewport aspect is likewise updated
|
||||
/// independently of the FOV gate).</summary>
|
||||
private void ApplyProjection()
|
||||
{
|
||||
bool fovAccepted = RetailFieldOfView.TryAppliedVerticalFov(
|
||||
GameFovRadians, _aspect, out float fovY);
|
||||
|
||||
Orbit.Aspect = _aspect;
|
||||
Fly.Aspect = _aspect;
|
||||
if (Chase is { } chase) chase.Aspect = _aspect;
|
||||
if (RetailChase is { } retailChase) retailChase.Aspect = _aspect;
|
||||
|
||||
if (!fovAccepted)
|
||||
return;
|
||||
Orbit.FovY = fovY;
|
||||
Fly.FovY = fovY;
|
||||
if (Chase is { } chaseFov) chaseFov.FovY = fovY;
|
||||
if (RetailChase is { } retailChaseFov) retailChaseFov.FovY = fovY;
|
||||
}
|
||||
|
||||
internal CameraState CaptureState() =>
|
||||
|
|
@ -101,6 +152,10 @@ public sealed class CameraController
|
|||
|
||||
Chase = state.Chase;
|
||||
RetailChase = state.RetailChase;
|
||||
// #389: the aspect/game-FOV may have changed while these cameras were
|
||||
// captured (a resize during a reset) — reconverge them before anyone
|
||||
// reads a projection.
|
||||
ApplyProjection();
|
||||
_mode = (Mode)state.ModeCode;
|
||||
ModeChanged?.Invoke(IsFlyMode || IsChaseMode);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue