Files
goldeneye-pc-port/docs/dev/WIDESCREEN-FOV-PLAN.md
T
James Dansereau 7bcfbf313c feat(D211-D213): PC QoL image options — FOV slider, anisotropic filtering, on-screen FPS
Three port-only knobs, each defaulting to byte-identical behaviour:

- Video.FovScale (D211): multiplicative half-angle scale on every perspective
  projection matrix as it is loaded (gfx_apply_fov_scale, gfx_pc.cpp). Columns
  0+1 scaled together so H/V FOV widen uniformly and aspect is preserved; ortho
  (HUD/menu) matrices untouched. Composes with the game's per-frame aim-zoom
  rewrites for free. Percent of vertical FOV, 50-150, default 100 => guarded
  early-return (zero FP ops, golden dumps byte-identical). WIDESCREEN-FOV-PLAN
  Phase 4, landed standalone.

- Video.Anisotropy (D212): expose fast3d's already-implemented GL anisotropy
  hook as config (1-16). New gfx_set_anisotropy_level() extern-C wrapper clamps
  to the GL max. Default 4 = the value fast3d already hardcoded => no delta.

- Video.DisplayFPS (D213): PD-parity top-right FPS readout, sampled once per
  emitted frame (optionsoverlay.c fpsTick). Config-only; default 0 => emit
  still returns NULL with the panel closed.

F10 overlay gains 'Anisotropic' and 'FOV scale %' rows (DisplayFPS is file-only
- the panel is at its 13-row layout limit, matching PD).

Build clean (ntsc-final); bunker1 framediff worst_cell 12.219 vs 12.221
baseline = render noise, no-op confirmed. Interactive eyeballs owed (see
docs/HANDOFF.md M-83).

findings.md: D211-D213 rows + index regen. WIDESCREEN-FOV-PLAN Phase 4 and
QOL-INVENTORY rows marked done.
2026-09-09 00:02:47 -04:00

249 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Widescreen & FOV Plan
Status: **proposal, parked** — like `UNLOCKED-FPS-PLAN.md`, resume after audio
work (project Phase 3). Research current as of this writing.
Scope decision (agreed): **target exactly two display aspects — 4:3 and 16:9.**
Arbitrary aspect ratios are TBD and out of scope; the window manager should
snap/letterbox to one of the two rather than trying to serve anything else.
Standard (agreed): **Option B (native 16:9 render, PD-parity) is the goal.**
The PD PC port's widescreen architecture is the reference standard for this
engine family. Option A (4:3-internal + stretch) remains an acceptable
**case-by-case workaround** only where B proves impractical for a specific
surface — each such fallback documented individually.
## Problem
At high resolutions (e.g. 4K) in a 16:9 window the game is disorienting. Root
cause, confirmed in code: the port renders in *window-resolution space*. All
320×240 logical screen coordinates are scaled per-axis by
`RATIO_X = win_w/320`, `RATIO_Y = win_h/240` (`port/fast3d/gfx_pc.cpp:53-54`).
In a 16:9 window everything — world geometry **and** HUD — is stretched 4/3
horizontally: circles become ellipses, and the horizontal FOV balloons from
~78° to ~91° while vertical stays 60°. That stretch is the disorientation.
## Research findings
### 1. The FOV model (where FOV lives)
- Vertical FOV is a fixed 60° base: `FOV_Y_F` (`src/fr.h:137`); per-player
`fovy`/`aspect` fields default to 60 / `DEFAULT_ASPECT`
(`src/game/player.c:454-455`).
- Setters: `viSetFovY` / `viSetAspect` / `viSetFov(fovx, fovy)`
(`src/fr.c:911-934`) → `guPerspectiveF(g_viProjectionMatrixF, …, fovy,
aspect, znear, zfar, 1.0f)` (`src/fr.c:718`).
- **FOV is dynamic**: aim zoom rewrites fovy every frame via
`viSetFovY(g_CurrentPlayer->zoominfovy)` (`src/game/bondview2.c:3123`), and
the camera path resets fovy/aspect per frame
(`bondviewMovePlayerUpdateViewport`, `src/game/bondview2.c:7967-8034`).
⇒ Any FOV option must be a *multiplicative correction composable with zoom*,
not an override of a constant.
### 2. The game ships with a (stretch-based) 16:9 path
This decomp contains a player-facing widescreen path from the original:
options-menu **"Ratio": Normal / 16:9** (`GAME_OPTIONS_INDEX_RATIO`, string
`OPTION_STR_23_169_LF`, `src/game/options.c:129`), save bit
`OPTION_SCREENRATIO` (`src/game/file2.c:1266-1270, 1323`),
`get/set_screen_ratio()` (`src/game/options.c:553-558`). When active, the
camera sets `aspect = (vp_w/vp_h) × 0.75 × 16/9` (= viewport ratio × 4/3,
`src/game/bondview2.c:8007-8034`) — the pre-distortion for a **4:3 render
stretched to 16:9** (N64-out-4:3, TV stretches). HUD compensation exists for
crosshair (`gunfire.c:6316-6318`, half-width ×0.75) and rocket aim
(`gunfire.c:501`); EU-only extras need an NTSC audit.
⇒ This path is exactly **Option A's** game-side machinery. It is *not* the
PD standard (which renders native 16:9), so under Option B it stays dormant
except as fallback.
### 3. Current port rendering pipeline (what to build on)
- Render happens at window size; an FBO path already exists and activates when
internal dims ≠ window dims or MSAA > 1 (`gfx_pc.cpp:3041-3115+`).
- Native-res bookkeeping: `GE_NATIVE_W/H` = 640×480 (NTSC) / 640×400 (PAL)
(`port/src/video.c:40-44`); VI size requests clamp to ≤640
(`port/src/libultra.c:816`). Auto window defaults to 4:3
(`port/fast3d/gfx_sdl2.cpp:107`).
### 4. fast3d EXT aspect commands — already present in GE's RSP
GE's software RSP (adapted from PD's) implements Rare's PC-port extension
geometry-mode commands `G_ASPECT_{LEFT,RIGHT,WIDE,CENTER}_EXT`
(`gfx_pc.cpp:1954-1979`, decoder at `gfx_sp_extra_geometry_mode`), and
`port/fast3d/gbiex.h` carries the `gSP…EXT` command macros. **The RSP side of
the PD standard already works in this codebase**; only the game-code emission
sites are missing.
### 5. The PD standard (reference architecture — what Option B replicates)
From the `pd_port` checkout (N64 PD had no widescreen; the port added it):
1. **Projection aspect comes from the port**: `videoGetAspect()` returns the
real window aspect (`gfx_current_dimensions.aspect_ratio`,
`port/src/video.c:232-235`). Game code calls `viSetAspect(videoGetAspect())`
(e.g. `title.c:2495`; even `#define TITLE_ASPECT (videoGetAspect())`).
Native 16:9 projection ⇒ correct perspective, vertical FOV preserved.
2. **Per-element screen-space corrections** where an element is positioned in
screen coords: `sightGetAdjustedX(x)`, `×(SCREEN_ASPECT / videoGetAspect())`
scalings (`sight.c:77,921`, `sky.c:2728+`, `menu.c:2332,4771`,
`player.c:3108`).
3. **HUD alignment via the EXT commands**: screen-space draw segments wrapped
in `gSPSetExtraGeometryModeEXT(gdl, G_ASPECT_CENTER_EXT)` …
`gSPClearExtraGeometryModeEXT(…)`, all under `#ifndef PLATFORM_N64`. With
the mode active, fast3d scales 4:3-logical screen coords by the *native*
aspect and centers (or left/right-aligns) them in the wider window — no
distortion. PD scale: **52 emission sites across 10 game files** (sight,
hudmsg, bondgun, menu, radar, title, propobj, credits, activemenu,
game_1531a0).
4. User-facing: vsync/framerate options + HUD-centering option
(`g_HudCenter` → `g_HudAlignModeL/R`, `port/src/main.c:77-83`).
## Option B — the standard (native 16:9, PD-parity)
### Architecture
Render directly at the 16:9 window resolution. World: projection aspect =
window aspect (vertical FOV preserved, no post-stretch). HUD/screen-space:
4:3-logical coordinates mapped undistorted via the EXT aspect modes
(centered by default; left/right alignment available à la PD). 4:3 windows:
byte-identical to today.
### Change inventory
**Port layer (no rules issues):**
- [ ] `videoGetAspect()` equivalent in `port/src/video.c` (return window
aspect; GE already tracks `gfx_current_dimensions.aspect_ratio`).
- [ ] Window/aspect policy: `Video.Aspect` {4:3, 16:9}, snap on create/resize;
out-of-scope aspects → nearest + note (TBD).
- [ ] Make `port/fast3d/gbiex.h` reachable from game TUs (include path or a
shim header) — build-system only.
- [ ] HUD alignment mode plumbing if we adopt PD's center/wide option
(`g_HudAlignModeL/R` pattern) — decide in Phase 1; default = centered.
**Game code (exception class — see below):**
- **(a) Projection aspect path.** `bondviewMovePlayerUpdateViewport`
(`bondview2.c:8007-8034`): under PORT + 16:9, use window aspect instead
of the ×4/3 stretch formula. Plus the menu/title/briefing call sites
`viSetFovY(FOV_Y_F); viSetAspect(ASPECT_RATIO_SD)` (`front.c` — ~7+
sites; enumerate in Phase 1) → PD pattern `viSetAspect(videoGetAspect())`.
- **(b) HUD / screen-space draw sites.** Audit + wrap with EXT aspect modes
and/or per-element corrections, per surface: gameplay HUD (crosshair —
replaces the existing ×0.75 compensation; ammo/timer/text via
`textrelated.c`), menus (`front.c`), title/briefing/credits, letterbox
bars (`fr.c` gDPFillRectangle calls). GE's known screen-space emitters:
`front.c`, `textrelated.c`, `gunfire.c`, `bondwalk2.c` (initial grep;
Phase 1 completes the audit). Expect PD-comparable scale (~10 files),
possibly less.
- **(c) Anything the audit surfaces** (e.g. mouse→screen mapping for menus,
cutscene camera framing) — each item logged individually.
### Rule #2 exception framing (needs explicit sign-off to start Phase 2)
This is a behavior-affecting game-code edit class, outside the ABI-only D3x
exception. The case for granting it:
1. **Direct sister-engine precedent**: PD's port made exactly these edits on
the same Rare engine/fast3d; the project designates PD as standing
reference for port patterns. We replicate a proven, bounded change class,
not inventing one.
2. **Bounded and gated**: every edit is `#ifdef PORT` (and where sensible
runtime aspect-gated) so the N64 build and the 4:3 default path stay
byte-identical; golden/determinism tests unaffected at 4:3.
3. **The decomp already contains** both a shipped 16:9 branch and `#ifdef
PORT` precedents in game files — the pattern is not foreign to this tree.
Conditions: explicit user sign-off before Phase 2; each change (or tight
cluster) gets a findings.md entry with §F index label citing the PD analogue;
4:3 byte-identity verified per phase; any edit that would alter *sim* behavior
(not just presentation) stops the work and comes back for review.
### Fallback (case-by-case, documented)
If the Phase 1 audit shows a specific surface is disproportionately costly or
can't be made clean under B (candidate: complex cutscene framing), that
surface may fall back to **Option A** — 4:3-internal render + edge-to-edge
stretch using the game's shipped `SCREEN_RATIO_16_9` path (`set_screen_ratio`
is an existing API; FBO+blit infra exists) — recorded as a finding with the
reason. Whole-feature fallback to A only if B fails verification overall.
## FOV adjustment design (port-only, applies to both options)
- **Hook: software RSP projection post-scale.** Wherever the RSP applies the
uploaded projection matrix, scale its vertical row by
`tan(fov_orig/2) / tan(fov_adj/2)`. Zoom rewrites the matrix every frame, so
post-scaling whatever arrives composes with aim zoom for free. No game code.
- Rejected: editing `viSetFovY` call sites (game code); writing
`g_ViBackData` from the port (racy).
- UI: `Video.FovScale` (percent of original vertical FOV, e.g. 50–150),
default 100 = byte-identical to today.
## Phases
### Phase 0 — Verification research (port-only, read-mostly)
- [ ] Playtest the in-game Ratio option as-is on PC; document + screenshot
what the shipped stretch path does under the current pipeline.
- [ ] Measure baseline FOVs (4:3 and current 16:9-stretch) from the projection
matrix or a calibration render; record numbers.
- [ ] Confirm `gbiex.h` EXT commands round-trip through GE's RSP with a test
DL (they should — decoder exists).
- [ ] Audit EU-only aspect constants (`g_GunSightAspectRatio`,
`EU_CAMERA_8003642C_ASPECT`) for NTSC/PAL effect.
- [ ] Record a Dxxx finding consolidating baselines.
### Phase 1 — Port foundation + full change audit (port-only)
- [ ] `videoGetAspect()`, window/aspect policy, gbiex.h include path.
- [ ] Complete the screen-space draw-site audit for inventory item (b): every
emitter, its coordinate assumptions, proposed wrap/correction, PD
analogue cited. Output = the signed change list for Phase 2/3.
- [ ] Decide HUD alignment default (centered vs PD's center/wide option).
### Phase 2 — Projection aspect (game-code exception class, small wave)
- [ ] Item (a): camera path + front.c call sites, `#ifdef PORT`-gated.
- [ ] Verify: world perspective correct at 16:9 (FOV measurement vs Phase 0),
aim zoom composes, 4:3 byte-identical.
### Phase 3 — HUD alignment wave (game-code exception class)
- [ ] Item (b), per surface in order: gameplay HUD → menus →
title/briefing/credits → letterbox/misc. Each surface: change, visual
verification at 4:3 + 16:9, finding entry. Fallback-to-A decisions
happen here, per surface, with documented reason.
### Phase 4 — FOV slider (port-only) — **LANDED (M-83, D211)**
- [x] RSP projection post-scale (`gfx_apply_fov_scale()` in
`port/fast3d/gfx_pc.cpp`, load-path only); `Video.FovScale` config
(percent of vertical FOV, 50–150, default 100 = byte-identical) + F10
overlay row. Perspective-only (ortho/HUD untouched); columns 0+1 scaled
together so aspect is preserved. Build clean, `bunker1` no-op confirmed.
- [ ] Owed: interactive eyeball of composition with aim-zoom at scale ≠ 100.
This phase does **not** depend on the Phase 2/3 game-code exception — it
shipped standalone ahead of the rest of the plan.
### Phase 5 — Verification & sign-off
- [ ] FOV measurements per mode/scale vs Phase 0 baselines.
- [ ] Full HUD pass at 4:3 + 16:9; cosmetics → `GRAPHICS-BACKLOG.md`.
- [ ] 4K performance check; MSAA interaction; config persistence; screenshots.
- [ ] `./build-pc.sh` + `/linkcheck`; findings complete with §F index labels.
## Open questions
- HUD alignment UX: always-centered (simple) vs PD's center/wide option
(more faithful, more surface area). Decide Phase 1.
- In-game Ratio menu entry under Option B: the shipped option means "stretch
mode" — keep it as the *fallback* trigger, hide it, or repurpose? Decide
Phase 1 (leaning: leave dormant; port config owns display mode).
- Mouse/menu coordinate mapping at 16:9 (PD had `videoGetAspect()`-scaled
mouse math in menu.c) — confirm GE's menu input path needs the same.
- Ordering vs `UNLOCKED-FPS-PLAN.md`: both parked behind audio; B renders
native so the FPS plan's present-stage duplication slots in unchanged.
Decide order when audio lands.
- PAL: `GE_NATIVE` 640×400 bookkeeping — confirm aspect math per region.
- Split-screen viewports under 16:9 — likely out of scope for PC; note and close.