# #389 SmartboxFOV — MECHANISM review of commit `7e0c1303` **Date:** 2026-08-13 **Reviewer lens:** MECHANISM (is the ported law the law retail actually runs?) **Subject:** `7e0c1303` — *fix #389: port retail's SmartboxFOV law; retire AD-89 (display slice 1)* **Oracle:** `docs/research/named-retail/acclient_2013_pseudo_c.txt` + `docs/research/named-retail/acclient.h`, **plus byte-level disassembly** of the PDB-paired binary `C:\Users\erikn\Downloads\acclient.exe` (`check_exe_pdb.py` → `MATCH`, GUID `9e847e2f-777c-4bd9-886c-22256bb87f32`, linker 2013-09-06). Every x87 comparison and division in this area is FPU-elided or ambiguous in the Binary Ninja pseudo-C, so **all polarity and operand-order claims below are decided from the instruction bytes**, not from BN's rendering. Raw disassembly is in the appendix. Report only — no files under `src/` or `tests/` were modified. --- ## Verdict summary | # | Question | Verdict | |---|---|---| | 1 | Formula transcription exact (operand order, 0.1, aspect variable, direct feed) | **CONFIRMED**, with one qualification: retail's `m_ViewportAspectRatio` is **not** raw `w/h` in general — see M1 | | 2 | What `m_bUseSmartboxFOV == 0` selects; does acdream apply the law where retail uses the fixed branch | **CONFIRMED** — no misapplication. Paperdoll/appraisal/char-gen = fixed branch (π/4, byte-verified); portal space = smartbox law, and acdream's portal camera derives from the world projection | | 3 | Does `0x00453b14` differ from the CreatureMode site | **CONFIRMED (differs, benignly)** — it is `SmartBox::RenderNormalMode`, a different class, with **no** `m_bUseSmartboxFOV` gate at all; the arithmetic is byte-identical | | 4 | Gate is open (0, π), silent keep-previous; `SetFOVInternal` applies it **vertically** | **CONFIRMED** — open on both ends, `return 0` without touching `Render::fov`; and the value reaches `D3DXMatrixPerspectiveFovLH`'s **fovy** slot. The port is **not** inverted. One NaN corner: M3 | | 5 | `0x00451e6a` is the FOV option's setter; `[10,160]`/default 90 registered | **CONFIRMED** — `SmartBox::SetDefaultFov`, fed by `Render::UpdateFromPreferences`; range and default byte-verified | | 6 | Other writers of `Render::fov` / other `SetFOVRad` callers the port ignores | **CONFIRMED (none ignored)** — only 3 `SetFOVRad` call sites exist; the one other FOV writer (`Render::set_vdst`, the teleport override) is already ported in `TeleportViewPlaneController`, and my byte-decode **vindicates** its `2·atan(1/d)` against BN's misleading `__fpatan(arg1, 1.0)` | **No MUST-FIX defect was found in the law, the gate, or its application.** Three bookkeeping/doc items are ranked below; the highest (M1) is a divergence-register obligation created by this commit, not a wrong number at retail's defaults. --- ## Q1 — Is the formula transcription exact? **CONFIRMED for the expression; QUALIFIED for what "the aspect" is.** Both smartbox sites are byte-identical arithmetic. `CreatureMode::Render` (pseudo-C line 91727, `0x00452b0d`–`0x00452b2f`): ``` 00452b0d mov ecx, [0x86f330] ; RenderDevice::render_device 00452b13 fld dword [ecx + 0xa8] ; ST0 = m_ViewportAspectRatio 00452b1a fsub dword [0x7948cc] ; ST0 = aspect - 0.1f (0x3dcccccd) 00452b20 fdivr dword [eax + 0xc0] ; ST0 = m_fGameFOV / ST0 <-- fdivr: mem / ST0 00452b26 fstp dword [esp] ; float-rounded quotient 00452b2f call 0x54b2d0 ; Render::SetFOVRad(quotient) ``` - **Operand order:** `fdivr` is *reverse* divide — `ST0 = memory / ST0`, i.e. `m_fGameFOV / (aspect − 0.1)`. The numerator is the game FOV. **Correct in the port.** (This is exactly the operand-order trap the question was aimed at; BN's `((long double)m_fGameFOV) / (aspect - 0.1)` happens to agree, but only the bytes prove it.) - **The 0.1 literal:** `[0x7948cc] = 0x3dcccccd = 0.10000000149011612`. The port's `AspectBias = 0.100000001f` parses to that identical float. **Exact.** - **Direct feed:** the quotient is stored to the stack slot and immediately passed to `SetFOVRad`; no clamp, scale, or half-angle in between. `SetFOVRad` → `SetFOVInternal(arg)` → `D3DXMatrixPerspectiveFovLH(pOut, arg, aspect, zn, zf)` (`0x0059ab71`). **No further transformation. Correct in the port.** - Retail evaluates in x87 80-bit and stores one `float`; acdream evaluates in `float`. Sub-ULP; immaterial. ### The qualification (see M1): which variable holds the aspect The divisor's left operand is `RenderDevice::m_ViewportAspectRatio` (offset `0xa8`, confirmed by the store in `RenderDevice::SetViewport` at `0054f22e fstp dword [esi+0xa8]`). That field is **not** `width/height` in the path the game view uses. `RenderDevice::ComputeAspectForViewport` (`0x0054f150`, pseudo-C line 345708) is FPU-elided in BN to a meaningless `return arg4 / arg5`. The bytes say: ``` 0054f150 mov al, [esp+0x14] ; arg6 0054f154 fild dword [esp+0xc] ; width 0054f15a je 0x54f181 ; arg6 == 0 -> second path ; arg6 != 0 : ST0 = width / height <-- raw viewport aspect 0054f17c fdivp st(1) 0054f17e ret 0x14 0054f181: ; arg6 == 0 : 0054f1a1 fdivp st(1) ; ST0 = width / height 0054f1a3 fmul dword [ecx+0xa4] ; * m_DisplayAspectRatio 0054f1a9 fmul dword [0x79b6dc] ; * 0.75 0054f1af ret 0x14 ``` `m_DisplayAspectRatio` (offset `0xa4`, confirmed by the stores at `0059fc19` / `0059fc24` / `0059fc33`) comes from the **`Render.AspectRatio` user preference** (`RenderDeviceD3D::SetupDisplayAspectRatio` `0x0059fbd0`, pseudo-C 428476): `1 → 1.33333337f`, `2 → 1.77777779f`, otherwise the display's own `w/h`. The game view's viewport is installed with `arg6 = 0` (`gmSmartBoxUI::RecvNotice_UpdateGameView`, `0x004d62f5`), so in normal gameplay: ``` m_ViewportAspectRatio = (viewportW / viewportH) × m_DisplayAspectRatio × 0.75 ``` At the registered default `AspectRatio = 1` (static initializer `Render::m_RenderPrefs.AspectRatio = 0x1` at `0x0081efa8`; also the safe-settings reset at `0054ef25`), `1.33333337f × 0.75f` rounds to **exactly 1.0f**, so `m_ViewportAspectRatio == w/h` and **the port matches retail bit-for-bit**. It diverges only if the user selects the "widescreen" preference (× 4/3, a noticeably narrower vertical FOV) — a preference acdream does not implement. That is the M1 bookkeeping item, not a wrong number today. --- ## Q2 — What `m_bUseSmartboxFOV == 0` selects, and does acdream misapply the law? **CONFIRMED — the port does not apply the smartbox law anywhere retail uses the fixed branch.** The flag lives on `CreatureMode` (`acclient.h:52561`), which is the base of `UIElement_Viewport` (`acclient.h:52574`) — i.e. **it only exists for embedded 3-D UI viewports**, never for the world. `CreatureMode::Render` `0x00452af0`: ``` 00452ae8 mov al, [esi+0x6c] ; m_bUseSmartboxFOV 00452af0 je 0x452b2b ; == 0 -> fixed branch 00452b2b mov edx, [esi+0x68] ; m_fFOVRadians 00452b2f call SetFOVRad ``` `m_fFOVRadians` (offset `0x68`) has exactly **one** read and **zero** writes in the whole binary — its value is the constructor default, byte-decoded at `004543de mov dword [esi+0x68], 0x3f490fdb` = **π/4 = 45°** (BN hid this inside a 17-byte `memcpy` literal). So the fixed branch is a constant, aspect-independent 45° vertical FOV. Who takes which branch (every caller in the binary): | Site | Caller | Branch | |---|---|---| | `0x004a5aa9` | `gmPaperDollUI` (paperdoll) — `UseSharpMode` | fixed 45° | | `0x004e0440` | char-gen 3-D view — `UseSharpMode` | fixed 45° | | `0x004ee9bc` | `gmCG3DView` — `UseSharpMode` | fixed 45° | | `0x004d6db3` | `gmSmartBoxUI::PostInit` → `m_pPortalSpace` — `UseSmartboxFOV` | **smartbox law** | `CreatureMode::UseSmartboxFOV` (`0x00452380`, line 91348) is the *only* writer of the flag and only ever sets it to 1. acdream's side, checked exhaustively (`grep FovY`, `grep CreatePerspectiveFieldOfView`): - The four cameras driven by `CameraController` (`Orbit`, `Fly`, `Chase`, `RetailChase`) are all **world** cameras — retail's world render is `SmartBox::RenderNormalMode`, which is *unconditionally* on the smartbox law. Correct. - `DollCamera.FovRadians = MathF.PI / 4f` — matches the byte-decoded fixed-branch constant. The commit's "paperdoll is exempt by design" claim is not just plausible, it is **numerically right**, and it is outside `CameraController`, so the law can never reach it. - `CreatureAppraisalPresentation`'s camera is likewise `π/4` and outside the controller — the same fixed-branch class of viewport. Correct. - **Portal space:** retail's portal space *does* take the smartbox law (`0x004d6db3`). acdream's `PortalTunnelCamera.UseSmartBoxFov(smartBoxProjection)` recovers the FOV from the world camera's projection matrix (`fov = 2·atan(1/M22)`) each draw (`PortalTunnelPresentation.Draw`, line 314), so it now inherits the new law automatically. Correct — and the "read the SmartBox each draw" shape matches retail's per-frame re-application. - `VulkanRhiScene`'s hardcoded `MathF.PI / 3f` is the RHI smoke-test scene (quadrant markers, wobble backdrop), not a world path. Not a finding. Startup ordering was also checked, because the law makes FOV aspect-dependent: `HostInputCameraCompositionPhase` calls `FramebufferResize.Resize(InitialFramebufferSize)` immediately after binding the camera target (`HostInputCameraComposition.cs:320`), so the real framebuffer aspect — not `DefaultAppliedFovY`'s assumed 16:9 — is in force before the first frame. --- ## Q3 — Does `0x00453b14` differ from the CreatureMode site? **CONFIRMED — different class, one structural difference, identical arithmetic.** `0x00453b14` is inside **`SmartBox::RenderNormalMode`** (`0x00453aa0`, pseudo-C line 92639) — the world render, not `CreatureMode`. Differences: 1. **No `m_bUseSmartboxFOV` gate exists there at all.** The function goes straight to the `m_bUseViewDistance` test (`00453ae6 mov cl,[esi+0xc8]`). The world is *always* on the smartbox law (modulo the teleport override). This is the stronger statement the port relies on, and it holds. 2. `m_fGameFOV` is read from `this` (the SmartBox, `[esi+0xc0]`) rather than from the `SmartBox::smartbox` global (`[eax+0xc0]` after `a158ca8300 mov eax,[0x83ca58]`). Same object in practice — `CreatureMode::Render` loads the global explicitly because `this` is a viewport, not the SmartBox. 3. Otherwise the instruction sequence is the same four ops (`fld [dev+0xa8]` / `fsub 0.1f` / `fdivr m_fGameFOV` / `fstp`), then `call SetFOVRad`. Both sites share the second gate the commit message does not mention: if `m_bUseViewDistance != 0`, retail calls `Render::set_vdst(m_fViewDistFOV)` **instead** — see Q6. --- ## Q4 — The gate, and is the value vertical? **CONFIRMED on both halves. The port is not inverted.** ### The acceptance interval — open (0, π), silent keep-previous `Render::SetFOVRad` (`0x0054b2d0`, pseudo-C line 342158), bytes: ``` 0054b2d0 fld dword [esp+4] 0054b2d4 fcomp dword [0x795344] ; 0x795344 = 0.0f 0054b2da fnstsw ax 0054b2dc test ah, 0x41 ; C0 (less) | C3 (equal) 0054b2df jnp 0x54b312 ; PF=0 <=> exactly one of C0/C3 <=> arg<0 or arg==0 -> reject 0054b2e1 fld dword [esp+4] 0054b2e5 fcomp qword [0x7bdd30] ; 0x7bdd30 = 3.141592653589793 0054b2ed test ah, 1 ; C0 (less) 0054b2f0 je 0x54b312 ; not-less (>= pi) -> reject 0054b2fc mov dword [0x81ec84], 0x3dcccccd ; Render::znear = 0.1f 0054b306 call [eax+0x1c] ; SetFOVInternal(arg) 0054b30c mov eax, 1 0054b311 ret 0054b312 xor eax, eax ; return 0 — no SetFOVInternal, Render::fov untouched 0054b314 ret ``` - `arg == 0` is **rejected** (C3 path) and `arg == π` is **rejected** (needs strict C0). Interval is **open on both ends**. Matches `fovY > 0f && fovY < MathF.PI`. - The reject path is `xor eax,eax; ret` — it never reaches `SetFOVInternal`, so `Render::fov`, `Render::vdst`, `Render::znear` and the view-to-clip matrix all keep their previous values, **silently** (the `int32_t` return is discarded at every call site). `CameraController.ApplyProjection`'s "return without touching `FovY`" is the exact contract. - Note the port also short-circuits `divisor <= 0`. Retail reaches the same verdict by arithmetic: a negative divisor yields a negative quotient (rejected by test 1), a zero divisor yields ±∞ (rejected by test 2). Behaviourally equivalent — except for NaN, see **M3**. ### Vertical, not horizontal — three independent proofs 1. **`Render::SetFOVInternal` (`0x0054b340`)**: `Render::vdst = ty / tan(arg × 0.5)` where `ty = (viewportHeight − 1) × 0.5 × yinvscale` — half the viewport **height** over a distance is `tan(half **vertical** FOV)`. 2. **`PrimD3DRender::SetFOVInternal` (`0x0059ab40`)** passes the same `arg2` straight into `D3DXMatrixPerspectiveFovLH(&m, arg2, m_ViewportAspectRatio, znear, zfar)`. That API's second parameter is **fovy** and its third is width/height. Decisive. 3. **`SmartBox::GetOverrideFovDistance` (`0x00451be0`)** halves the *same* smartbox expression before `fptan` and reciprocates it (`fmul 0.5` → `fptan` → `fdivr 1.0`), i.e. it returns `cot(fov/2)` — only meaningful if the expression is a full vertical angle. (It also proves `view-plane distance == M22` of a perspective matrix, which is what `TeleportViewPlaneController.Begin` assumes.) acdream feeds the value to `Matrix4x4.CreatePerspectiveFieldOfView(FovY, Aspect, …)` — same slot, same meaning. **Not inverted.** --- ## Q5 — Is `0x00451e6a` the Field of View option's setter, with [10,160]/default 90? **CONFIRMED on every part.** - Enclosing function: **`SmartBox::SetDefaultFov(SmartBox* this, float degrees)`** at `0x00451e60` (pseudo-C line 90988) — `m_fGameFOV = degrees × 0.017453292519943295`. - Its **only** caller in the binary is `Render::UpdateFromPreferences()` (`0x0054d850`, call at `0x0054d999`, pseudo-C 344365), guarded by a change test against `Current_Render_FieldOfView` and fed from `Render::m_RenderPrefs.FieldOfView`. So the chain is option slider → preference → `UpdateFromPreferences` → `SetDefaultFov` → `m_fGameFOV`. - **Range:** `UIPreferences::SetPreferenceRange(&Render_FieldOfView, 10f, 160f)` at `0x004043b2`, inside **`gmClient::InitUIPreferences` (`0x004035b0`, pseudo-C line 2740)** — exactly as the commit claims. Corroborated by the console command at `0x00455d20` which validates `>= 0xa && <= 0xa0` before `UIPreferences::ModifyPreference(&Render_FieldOfView, …)`. - **Default:** the static initializer `Render::m_RenderPrefs` at `0x0081efb8` reads `float FieldOfView = 90`. The UI row is registered via `PlayerOptionPage::AddSliderOption(&Render_FieldOfView, 1)` (`0x0049e53e`). - **Ctor default of `m_fGameFOV`:** `0x00454649` in `SmartBox::SmartBox` sets `1.57079637f` = π/2 = 90°, consistent with the degrees default. Also `m_fViewDistFOV = 0`, `m_bUseViewDistance = 0`. - acdream's `DisplaySettings.Default.FieldOfView = 90f` and the Config row (`ConfigOptionsPageController` `min: 10f, max: 160f, defaultValue: 90.0f`) now agree with each other and with retail. The pre-#389 mismatch (record default 60, slider default 90) is genuinely closed. **One behavioural difference, pre-existing (see M4):** retail applies an FOV change **immediately** — `Render::UpdateFromPreferences` is reached from `SceneTool::PrepareGraphicsDevice` (`0x0043e4f0`), a per-frame device-prep step. acdream applies `DisplaySettings.FieldOfView` only through `RuntimeSettingsController.ApplyStartup` (next launch), except while a Config draft preview is open (`WorldRenderFrameBuilder.Apply` → `ApplyFieldOfView`). --- ## Q6 — Any other writer of `Render::fov` / `SetFOVRad` caller the port ignores? **CONFIRMED — none ignored. The one "other law" is already ported, and my byte-decode corrects the decomp in the port's favour.** `SetFOVRad` has exactly **three** call sites in the binary (grep of the full pseudo-C): `0x00452b2f` (CreatureMode, both branches converge on this one call) and `0x00453b14` (SmartBox::RenderNormalMode). Nothing else calls it. `Render::fov` is written only inside `Render::SetFOVInternal`, reached from: 1. `SetFOVRad` — the ported path. 2. **`Render::set_vdst(float)` (`0x0054b240`)** — the *view-distance override*. Selected by `m_bUseViewDistance != 0` at **both** smartbox sites, i.e. it *replaces* the smartbox law while armed. Armed/disarmed by `SmartBox::SetOverrideFovDistance` (`0x00451bc0`), whose only callers are the **teleport animation** in `gmSmartBoxUI`: `EndTeleportAnimation` (`0x004d65d5`, disarm) and `UseTime` (`0x004d71c4`, `0x004d725f`, `0x004d73ce` arm; `0x004d747f` disarm), interpolating `teleportCurVDist` between the game's value and `TRANSITION_VIEW_PLANE_DISTANCE` over `TELEPORT_ANIM_FADE_TIME`. 3. `Render::set_zfar` (`0x0054b320`) — re-applies the *existing* `Render::fov` after changing the far plane. Not an independent FOV writer. acdream already ports (2) as `TeleportViewPlaneController`, and it is correct: ``` 0054b240 fld qword [0x7928c0] ; 1.0 0054b246 fld dword [esp+4] ; d (ST0=d, ST1=1.0) 0054b24a fpatan ; atan(ST1/ST0) = atan(1/d) 0054b24c fadd st(0), st(0) ; fov = 2*atan(1/d) 0054b252 fcomp dword [0x7ca9f0] ; 0.4f 0054b25d jp -> znear = 0.1f ; d < 0.4 0054b263 fmul dword [0x7c8a04] ; else znear = d * 0.25 ; then clamp fov to [0.001 (0x794720), pi (0x7bdd30)] and call SetFOVInternal ``` BN renders line 342123 as `__fpatan(arg1, 1.0)`, which reads as `atan(d)` and would have made the port's `2f * MathF.Atan(1f / distance)` look wrong. The bytes show x87 `fpatan` computes `atan(ST1/ST0)` = **`atan(1/d)`** — **the port is right and the decomp text is the misleading party.** `near = MathF.Max(0.1f, distance*0.25f)` is likewise equivalent to retail's `d < 0.4 ? 0.1 : d*0.25` at every input. Residual (pre-existing, not #389): acdream's world cameras use a 5000 m far plane where `Render::zfar` defaults to 4000 (`0x0081ec88`); `PortalTunnelCamera` already documents 4000. --- ## Findings, ranked ### M1 — REGISTER/DOC (highest): "the viewport aspect" is a preference-scaled quantity `RetailFieldOfView`'s class doc and the commit message both describe the divisor's left operand as the viewport aspect ratio. Retail's `m_ViewportAspectRatio` for the game view is `(w/h) × m_DisplayAspectRatio × 0.75`, where `m_DisplayAspectRatio` is driven by the `Render.AspectRatio` preference (`Normal`→4:3, widescreen→16:9, otherwise the display's own ratio). At the **registered default (1)** the factor is exactly 1.0f and acdream matches retail bit-for-bit; at the widescreen setting retail's effective aspect is 4/3 larger and the vertical FOV correspondingly narrower. acdream implements no aspect-ratio preference. *Why it matters:* #389 is the commit that made this field load-bearing, and the project rule is that a commit introducing a deviation carries its register row. **Action:** add a divergence-register row ("acdream feeds the raw framebuffer aspect; retail scales it by the `Render.AspectRatio` preference — identical at retail's default, divergent if that preference is ever implemented") and correct the one-line description in `RetailFieldOfView`'s doc to name `ComputeAspectForViewport @0x0054f150` as the definition. No behaviour change. ### M2 — NOTE: FOV changes do not take effect until next launch Retail's option applies within one frame (`SceneTool::PrepareGraphicsDevice` → `Render::UpdateFromPreferences` → `SetDefaultFov`; the smartbox re-reads `m_fGameFOV` every `RenderNormalMode`). acdream applies it via `ApplyStartup` only (live only during a Config draft preview). Pre-existing and documented in `DisplaySettings`/`ConfigOptionsPageController`, but the semantics change in this commit makes the lag more visible (the slider now changes the *shape* of the view, not just its zoom). Worth an issue or a register row; not a #389 defect. ### M3 — NOTE: NaN corner of the gate is inverted Retail's first test is `test ah,0x41 / jnp`: an unordered compare sets both C0 and C3 → even parity → **not** taken, and the second test's `test ah,1` sees C0 set → **accepted**. So retail would pass a NaN FOV through to `SetFOVInternal`. acdream's `fovY > 0f && fovY < MathF.PI` rejects NaN. Reaching it requires `m_fGameFOV` NaN or an exact `0/0`, neither of which the option range permits. Record it, do not "fix" it — rejecting NaN is strictly safer and unreachable. ### M4 — DOC nit: the "~85–90° horizontal" claim At the 90° default the law yields horizontal FOV ≈ **89.0°** (4:3), **83.9°** (16:9), **80.6°** (21:9). The class doc says "~85–90°"; the test asserts `[80°, 90°]` (correct). Suggest "~80–89°, near-constant" in the doc so the doc and the test agree. ### Verified-good (no action) - `AspectBias = 0.100000001f` ≡ `0x3dcccccd`. Exact. - `DefaultGameFovRadians = 1.57079637f` ≡ the `0x00454649` ctor literal. Exact. - Golden test values recomputed from the bytes: 4:3 → 1.27362 rad, 16:9 → 0.93624 rad, 21:9 → 0.70334 rad (test asserts 0.70327 at `precision: 4` — both round to 0.7033). - `DollCamera` / `CreatureAppraisalPresentation` at π/4 match the byte-decoded `CreatureMode::m_fFOVRadians` default `0x3f490fdb`. - Startup applies the real framebuffer aspect before the first frame. - `TeleportViewPlaneController`'s `2·atan(1/d)` and `max(0.1, 0.25d)` are correct against the bytes, contradicting the BN text. --- ## Appendix — primary citations | Address | Pseudo-C line | Symbol / meaning | |---|---|---| | `0x00452af0`–`0x00452b2f` | 91717–91727 | `CreatureMode::Render` FOV branch (fixed vs smartbox vs vdst) | | `0x00453ae6`–`0x00453b14` | 92655–92660 | `SmartBox::RenderNormalMode` smartbox site (no smartbox-flag gate) | | `0x00452380` | 91348 | `CreatureMode::UseSmartboxFOV` — sets the flag to 1 (only writer) | | `0x00452390` | 91356 | `CreatureMode::UseSharpMode` | | `0x004a5aa9` / `0x004e0440` / `0x004ee9bc` | 175534 / 228563 / 241997 | paperdoll + char-gen viewports (fixed branch) | | `0x004d6db3` | 219378 | `gmSmartBoxUI::PostInit` — portal space takes the smartbox law | | `0x004543de` | (in 93102 ctor) | `m_fFOVRadians = 0x3f490fdb` = π/4 (byte-decoded) | | `0x00451be0`–`0x00451c18` | 90792–90806 | `SmartBox::GetOverrideFovDistance` = `cot(fov/2)` | | `0x00451bc0` | 90783 | `SmartBox::SetOverrideFovDistance` | | `0x00451e60`/`0x00451e6a` | 90988–90991 | `SmartBox::SetDefaultFov` — degrees → radians | | `0x00454649` | 93302 | `SmartBox` ctor `m_fGameFOV = 1.57079637f` | | `0x0054b240` | 342121 | `Render::set_vdst` — `fov = 2·atan(1/d)`, `znear` rule, `[0.001, π]` clamp | | `0x0054b2d0` | 342158 | `Render::SetFOVRad` — open `(0, π)` gate, silent reject | | `0x0054b340` | 342196 | `Render::SetFOVInternal` — `vdst = ty / tan(fov/2)` (vertical) | | `0x0059ab40` | 423666 | `PrimD3DRender::SetFOVInternal` → `D3DXMatrixPerspectiveFovLH(fovy, aspect, …)` | | `0x0054f150` | 345708 | `RenderDevice::ComputeAspectForViewport` — `(w/h)·DAR·0.75` when `arg6 == 0` | | `0x0054f1c0`/`0x0054f22e` | 345720/345749 | `RenderDevice::SetViewport` — writes `m_ViewportAspectRatio` (`+0xa8`) | | `0x0059fbd0` | 428476 | `RenderDeviceD3D::SetupDisplayAspectRatio` — preference → `m_DisplayAspectRatio` | | `0x0054d850`/`0x0054d999` | 344244/344365 | `Render::UpdateFromPreferences` → `SetDefaultFov` | | `0x0043e4f0` | 69105 | `SceneTool::PrepareGraphicsDevice` → `UpdateFromPreferences` (per-frame) | | `0x004035b0`/`0x004043b2` | 2740/3261 | `gmClient::InitUIPreferences` — `SetPreferenceRange(10, 160)` | | `0x0081efb8` / `0x0081efa8` | 1102208 ff. | `m_RenderPrefs.FieldOfView = 90`, `.AspectRatio = 1` | | `0x004d65d5`, `0x004d71c4`, `0x004d725f`, `0x004d73ce`, `0x004d747f` | 219002–219775 | teleport-animation FOV override arm/disarm | Constants byte-read from the paired binary: `[0x7948cc] = 0.1f (0x3dcccccd)`, `[0x79b6dc] = 0.75f`, `[0x795344] = 0.0f`, `[0x7bdd30] = π (f64)`, `[0x794720] = 0.001 (f64)`, `[0x7928c0] = 1.0 (f64)`, `[0x7ca9f0] = 0.4f`, `[0x7c8a04] = 0.25f`.