Files
goldeneye-pc-port/docs/dev/WIDESCREEN-FOV-PLAN.md
T
James Dansereau 6736d6ee16 docs(dev): add live research/plan docs (D172, D176a, viewmodel, QOL, widescreen, unlocked-fps)
All referenced from HANDOFF.md / GRAPHICS-BACKLOG.md but untracked:
- D172-PARTICLE-COLOR-RESEARCH.md: magenta/cyan blood root-cause candidate
  (unhandled GBI opcode 0xB9 in ROM particle records + D146 guard)
- D176a-SKY-NOTES.md: RDPHALF sky-tri wire format decoded; Path B draft state
- VIEWMODEL-RESEARCH.md: 1P weapon model static audit, three ranked suspects
- QOL-INVENTORY.md / WIDESCREEN-FOV-PLAN.md / UNLOCKED-FPS-PLAN.md: parked
  Phase-4 proposals
2026-09-05 19:07:43 -04:00

12 KiB
Raw Blame History

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)

  • RSP projection post-scale; Video.FovScale config + F10 overlay; verify composition with aim zoom at non-100 scale.

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.