diff --git a/docs/dev/findings-index.csv b/docs/dev/findings-index.csv index 5db26423..c01ecd47 100644 --- a/docs/dev/findings-index.csv +++ b/docs/dev/findings-index.csv @@ -211,3 +211,4 @@ D330,FIXED,"#103 (v0.3.0, Win 11):","FIXED (port/src/input.c), build clean, bunk D331,FIXED,Ejected shell casings never rendered on PC:,"FIXED (gunfire.c #ifdef PORT, explicit template); PP7 casing verified on native Windows (before:" D332,FIXED,Reticule jitter in aim mode (RMB) —,FIXED (fix/mouse-pd-model) — D333,OPEN,"#104 (v0.3.0, Linux):","IMPLEMENTED (port/src/input.c + optionsoverlay.c, port-only); default N64 = unchanged; build + golden clean; feel check owed." +D334,?,"Native widescreen, world side (WIDESCREEN-FOV-PLAN Option B Phase 2):",IMPLEMENTED (Rule-2 approved 2026-09-23); 4:3 identity (golden 3/3); native 16:9 centre matches the 4:3 frame (mean diff 4.3 vs 12.4 stretched); 21:9 clean on Bunker/Facility/Dam. diff --git a/docs/dev/findings.md b/docs/dev/findings.md index 4b92a46d..e2674c12 100644 --- a/docs/dev/findings.md +++ b/docs/dev/findings.md @@ -619,6 +619,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121). | D331 | **Ejected shell casings never rendered on PC: `g_DefaultCasingModelRenderData` (u32[15] template) read as the wider PC `ModelRenderData` gave flags 0 (third instance of the D264 pattern; found by the D290 sibling sweep, never user-reported).** — full `## D331` entry at file tail | FIXED (gunfire.c `#ifdef PORT`, explicit template); PP7 casing verified on native Windows (before: none; after: brass casing ejects and tumbles); golden 3/3; Win + Linux builds clean. | | D332 | **Reticule jitter in aim mode (RMB) — ROOT-CAUSED from live captures; fixed by adopting the Perfect Dark port's mouse-aim model behind `Input.PdMouseAim`.** The game's damped crosshair update runs once per SIM TICK while the port's GEPD overwrite runs once per input POLL, so the value displayed is `write * damp^k` with k varying (k=1 66%, k=2 18%, k=0 17% of frames). At damp 0.8 that stepped the drawn reticule 20% whenever k changed | **FIXED (`fix/mouse-pd-model`)** — `Input.PdMouseAim` (default off, live via `GE_PDMOUSEAim`): the port accumulates only and the game's integrator is driven by the port-supplied turn with PD's near-zero damp (0.01), via a `#ifdef PORT` hook in `sub_GAME_7F067FBC`. Two earlier fixes were tried and reverted (see the `## D332` entry). **Awaiting feel-test.** | | D333 | **#104 (v0.3.0, Linux): FPS-style centred aim on RMB, delivered as `Input.AimStyle` (F10 "RMB aim style": N64 / PD DAMP / CENTRED), folding in D332's toggle.** — full `## D333` entry at file tail | IMPLEMENTED (port/src/input.c + optionsoverlay.c, port-only); default N64 = unchanged; build + golden clean; feel check owed. | +| D334 | **Native widescreen, world side (WIDESCREEN-FOV-PLAN Option B Phase 2): the world is projected at the real window aspect (Hor+) by generalising GE's own N64 16:9 mode; `Video.NativeWidescreen` (default on).** — full `## D334` entry at file tail | IMPLEMENTED (Rule-2 approved 2026-09-23); 4:3 identity (golden 3/3); native 16:9 centre matches the 4:3 frame (mean diff 4.3 vs 12.4 stretched); 21:9 clean on Bunker/Facility/Dam. HUD still stretched → Phase 3. | Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software @@ -13243,3 +13244,11 @@ Implemented as `Input.PdMouseAim` (**default off**, because it changes aim feel) **Verified:** Windows + Linux builds clean; bunker1 golden 3/3 (default style 0). Not verifiable headlessly: the scripted input source has no mouse, and the overlay row sits below the fold at every window size (it uses the same `ROW_ENUM` machinery as Texture filter). **Owed (user):** F10 → RMB aim style → CENTRED, hold RMB and move the mouse: the camera turns, the crosshair stays centred, and zoomed weapons turn slower. Also compare PD DAMP against N64 for D332's jitter fix. +## D334 — Native widescreen, world side (WIDESCREEN-FOV-PLAN Option B, Phase 2) (2026-09-23) + +**Approach:** GE already ships an N64 16:9 mode (`bondviewMovePlayerUpdateViewport`, bondview2.c): `aspect = (vp_w/vp_h) × 0.75 × 16/9`, an anamorphic pre-squash that a 16:9 TV undid. The port's canvas-to-window mapping (fast3d `RATIO_X = win_w/320`) is exactly that TV, so native widescreen is the same formula with 16/9 replaced by the real window aspect, via `portNativeAspect()` (`port/src/video.c`, clamp [0.5, 4.0]). Vertical FOV stays the game's; horizontal widens (Hor+, the PD standard). Because the game's canvas, portal scissors, culling (`currentPlayerSetPerspective` gets the same aspect through `viSetAspect`) and AI off-screen tests all stay in the 320×240 logical space with one consistent aspect, the GEVR "two notions of view width" trap (plan §6) doesn't arise. That's the same reason the N64 16:9 mode worked. + +**Game edits (`#ifdef PORT`, Rule-2 approved by the maintainer 2026-09-23), the complete set of GE's own 16:9 special cases, generalised:** (1) the camera aspect, NTSC and EU branches; the in-game "Ratio 16:9" option is ignored while native is on, since the window is the ratio; (2) the crosshair sprite half-width, where ×0.75 = (4/3)/(16/9) becomes (4/3)/aspect; (3) the rocket launcher's lowered viewmodel (`gunofs.y -= 3`) for aspect ≥ 1.55. **Port:** `Video.NativeWidescreen` (default 1, F10 "Native widescreen"); `portScaleFovY` skips the Phase-4 `sqrt(aspect/4:3)` vertical boost while native is on (it would double-count); `portRoomPoolScale` keeps its aspect term (the horizontal frustum still widens, D294). + +**Verified (native Windows, headless):** 640×480 golden 3/3, so 4:3 is identity by construction (aspect 4/3 → ×1). Quantitative check: the centre 960 columns of a native 1280×720 frame vs a 960×720 (4:3) frame of the same moment differ by mean 4.3 (run-to-run noise), against 12.4 for the old stretched+boosted mode squeezed back. 21:9 (1680×720): Bunker intro + gameplay, Facility intro room and Dam gorge all render edge to edge with no scissor slabs, missing rooms or sky gaps. Windows + Linux builds clean. **Not yet native:** HUD, text and 2D overlays (level title card, ammo counter, watch) are still stretched with the canvas. That's Phase 3 (EXT-aspect wraps). The front-end menus' fixed 3D (`viSetAspect(ASPECT_RATIO_SD)` in front.c) is still stretched too. **Owed (user):** feel check at 16:9/21:9 (aim zoom, rocket launcher, cutscenes). + diff --git a/port/src/optionsoverlay.c b/port/src/optionsoverlay.c index 12b1996a..44c9f217 100644 --- a/port/src/optionsoverlay.c +++ b/port/src/optionsoverlay.c @@ -127,6 +127,10 @@ static struct Row rows[] = { { "Video.TextureFilter", "Texture filter", ROW_ENUM, 1, kTexFilter, 0, 0, 0, 0,0,0,0,0 }, { "Video.Anisotropy", "Anisotropic", ROW_SLIDER, 1, NULL, 0, 0, 0, 0,0,0,0,0 }, { "Video.FovScale", "FOV scale %", ROW_SLIDER, 5, NULL, 0, 0, 0, 0,0,0,0,0 }, + /* D334: native widescreen (world projected at the window aspect, Hor+). + * While on, "Widescreen auto FOV" has no effect (it was the stretch-era + * vertical-FOV compensation). */ + { "Video.NativeWidescreen", "Native widescreen",ROW_TOGGLE, 1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, { "Video.WidescreenAuto", "Widescreen auto FOV",ROW_TOGGLE,1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, { "Video.SafeAreaCrop", "Crop overscan bars", ROW_TOGGLE,1, kOnOff, 0, 0, 0, 0,0,0,0,0 }, { "Video.DrawDistance", "Draw distance %", ROW_SLIDER, 25, NULL, 0, 0, 0, 0,0,0,0,0, "Video.DrawDistanceAutoFov" }, diff --git a/port/src/video.c b/port/src/video.c index cf96cc37..cc299759 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -62,6 +62,7 @@ static int cfgDetailBaseTile = 1; /* D236: TEXTURETYPE_DETAIL -> sample the bas static int cfgWrapFix = 0; /* D74 sub-tile UV pre-wrap + RC3/D167 non-PoT mask-period wrap (opt-in; GE_WRAPFIX env overrides) */ static int cfgFovScale = 100; /* D211: percent of the original vertical FOV; 100 = unchanged (byte-identical) */ static int cfgWidescreenAuto = 1; /* WIDESCREEN-FOV-PLAN Phase 4: auto-scale vertical FOV by window aspect ratio; on by default, no-op at 4:3 */ +static int cfgNativeWidescreen = 1; /* D334 (WIDESCREEN-FOV-PLAN Phase 2): project the world at the real window aspect (Hor+); no-op at 4:3 */ static int cfgDrawDistance = 150; /* D218: percent of the level's authored far-clip/fog distance. Default raised 100->150 for v0.2.0: at the authored N64 distance, props visibly fade in just before they become visible on modern displays (Dam alarms / wall switches); 150 is the value the Steam Deck preset playtest-validated. 100 = unchanged N64. */ static int cfgDrawDistanceAutoFov = 1; /* D218: couple draw distance to Video.FovScale unless DrawDistance is set explicitly */ static int cfgLodDistance = 150; /* D249: percent scale on the geometry/model LOD-swap distance. Default raised 100->150 for v0.2.0 (same pop-in family as DrawDistance: LOD-swapped props like Dam's alarms/wall switches faded in at range); 150 matches the Steam Deck preset. 100 = unchanged N64. */ @@ -143,10 +144,38 @@ f32 portFovScale = 1.0f; * auto-scale formula can otherwise degenerate toward as aspect -> 0; this * resolves WIDESCREEN-FOV-PLAN.md's open "horizontal extreme-aspect sanity * clamp" question. */ +/* D334 (WIDESCREEN-FOV-PLAN Option B, Phase 2): the world-projection aspect + * for native widescreen, or 0 when off. The game's own N64 16:9 mode + * (bondview2.c bondviewMovePlayerUpdateViewport) projects at + * viewport_ratio * 0.75 * 16/9 -- an anamorphic pre-squash the TV undid. The + * port already stretches the 4:3 logical canvas to the window (fast3d + * RATIO_X), exactly like that TV, so the same formula with 16/9 replaced by + * the real window aspect renders the world undistorted at any aspect: vertical + * FOV stays the game's, horizontal widens (Hor+, the PD standard). Canvas, + * portal scissors and culling all stay in the game's 320x240 logical space and + * see the same aspect, so there is no second "view width" to reconcile + * (WIDESCREEN-FOV-PLAN s6). Clamped to [0.5, 4.0]. At exactly 4:3 the + * formula is identity, so 4:3 renders are unchanged. */ +f32 portNativeAspect(void) +{ + f32 a = gfx_current_dimensions.aspect_ratio; + if (!cfgNativeWidescreen || a < 0.01f) { + return 0.0f; + } + if (a < 0.5f) { a = 0.5f; } + if (a > 4.0f) { a = 4.0f; } + return a; +} + f32 portScaleFovY(f32 fovy, s32 isTitleScreen) { if (!isTitleScreen) { - if (cfgWidescreenAuto && gfx_current_dimensions.aspect_ratio > 0.01f) { + /* D334: native widescreen already widens the horizontal FOV through + * the projection aspect; the Phase-4 vertical boost below was the + * stretch-era compensation and would double-count, so it is skipped + * (vertical FOV stays the game's, as in the PD port). */ + if (cfgWidescreenAuto && !cfgNativeWidescreen && + gfx_current_dimensions.aspect_ratio > 0.01f) { fovy *= sqrtf(gfx_current_dimensions.aspect_ratio / (4.0f / 3.0f)); } if (portFovScale > 0.4f && portFovScale < 2.01f && portFovScale != 1.0f) { @@ -278,7 +307,10 @@ f32 portRoomPoolScale(void) } t = portFovScale; - if (cfgWidescreenAuto && gfx_current_dimensions.aspect_ratio > 0.01f) { + /* D334: native widescreen widens the horizontal frustum too (via the + * projection aspect instead of the vertical boost), so it admits the same + * extra side rooms and keeps the pool bump. */ + if ((cfgWidescreenAuto || cfgNativeWidescreen) && gfx_current_dimensions.aspect_ratio > 0.01f) { t *= sqrtf(gfx_current_dimensions.aspect_ratio / (4.0f / 3.0f)); } if (t > scale) { scale = t; } @@ -315,6 +347,7 @@ PD_CONSTRUCTOR static void videoConfigInit(void) configRegisterInt("Video.WrapFix", &cfgWrapFix, 0, 1); configRegisterInt("Video.FovScale", &cfgFovScale, 50, 150); configRegisterInt("Video.WidescreenAuto", &cfgWidescreenAuto, 0, 1); + configRegisterInt("Video.NativeWidescreen", &cfgNativeWidescreen, 0, 1); /* D334 */ configRegisterInt("Video.DrawDistance", &cfgDrawDistance, 100, 400); configRegisterInt("Video.DrawDistanceAutoFov", &cfgDrawDistanceAutoFov, 0, 1); configRegisterInt("Video.LodDistance", &cfgLodDistance, 25, 400); diff --git a/src/game/bondview2.c b/src/game/bondview2.c index 09fd3afb..8c9415b9 100644 --- a/src/game/bondview2.c +++ b/src/game/bondview2.c @@ -8216,6 +8216,14 @@ void bondviewMovePlayerUpdateViewport(s8 stick_x, s8 stick_y, u16 buttons) } #ifdef VERSION_EU +#ifdef PORT + extern f32 portNativeAspect(void); /* D334, see the NTSC branch below */ + if (portNativeAspect() > 0.0f) + { + faspect = ((f32) bondviewGetCurrentPlayerViewportWidth() / (f32) bondviewGetCurrentPlayerViewportHeight()) * 0.75f * portNativeAspect(); + } + else +#endif if (get_screen_ratio() == SCREEN_RATIO_16_9) { faspect = ((f32) bondviewGetCurrentPlayerViewportWidth() / (f32) bondviewGetCurrentPlayerViewportHeight()) * 0.75f * WIDESCREEN_ASPECT; @@ -8235,6 +8243,19 @@ void bondviewMovePlayerUpdateViewport(s8 stick_x, s8 stick_y, u16 buttons) #else +#ifdef PORT + /* D334 (native widescreen, Rule-2 approved 2026-09-23): the N64 16:9 mode + * below with WIDESCREEN_ASPECT replaced by the real window aspect -- see + * portNativeAspect() (port/src/video.c). The in-game "Ratio 16:9" option + * is ignored while this is on (the window is the ratio). */ + extern f32 portNativeAspect(void); + if (portNativeAspect() > 0.0f) + { + set_cur_player_aspect(((f32) bondviewGetCurrentPlayerViewportWidth() / (f32) bondviewGetCurrentPlayerViewportHeight()) * 0.75f * portNativeAspect()); + viSetAspect(((f32) bondviewGetCurrentPlayerViewportWidth() / (f32) bondviewGetCurrentPlayerViewportHeight()) * 0.75f * portNativeAspect()); + } + else +#endif if (get_screen_ratio() == SCREEN_RATIO_16_9) { set_cur_player_aspect(((f32) bondviewGetCurrentPlayerViewportWidth() / (f32) bondviewGetCurrentPlayerViewportHeight()) * 0.75f * WIDESCREEN_ASPECT); diff --git a/src/game/gunfire.c b/src/game/gunfire.c index 9536c2eb..c543cd9f 100644 --- a/src/game/gunfire.c +++ b/src/game/gunfire.c @@ -500,6 +500,16 @@ void gunUpdateAndFire(GUNHAND handnum) gunofs.y += g_CurrentPlayer->ducking_height_offset / (-100.0f); gunofs.z += (3.0f * g_CurrentPlayer->ducking_height_offset) / (-100.0f); +#ifdef PORT + /* D334: a native-widescreen window counts as the N64's 16:9 ratio for + * the rocket launcher's lowered viewmodel (>= 16:10 treated as wide). */ + extern f32 portNativeAspect(void); + if ((item == ITEM_ROCKETLAUNCH) && portNativeAspect() >= 1.55f) + { + gunofs.y -= 3.0f; + } + else +#endif if ((item == ITEM_ROCKETLAUNCH) && (((cur_player_get_screen_setting() == SCREEN_SIZE_WIDESCREEN) || (cur_player_get_screen_setting() == SCREEN_SIZE_CINEMA)) || (get_screen_ratio() == SCREEN_RATIO_16_9))) { gunofs.y -= 3.0f; @@ -6476,6 +6486,15 @@ void gunDrawSight(s32 *gdl) { halfedxy[0] = 16.0f; halfedxy[1] = 16.0f; +#ifdef PORT + /* D334: native widescreen generalises the N64 16:9 sprite fix + * (x0.75 = (4/3)/(16/9)) to the window aspect, so the crosshair + * sprite isn't stretched by the canvas-to-window mapping. */ + extern f32 portNativeAspect(void); + if (portNativeAspect() > 0.0f) { + halfedxy[0] = halfedxy[0] * ((4.0f / 3.0f) / portNativeAspect()); + } else +#endif if (get_screen_ratio() == SCREEN_RATIO_16_9) { halfedxy[0] = halfedxy[0] * 0.75f; }