feat(video): a Window choice that fills the actual window, Hor+ and Vert+ #142

Open
simonwjackson wants to merge 9 commits from simonwjackson/Longshot:feat/window-aspect into main
Contributor

Why

Monitor's shape follows the monitor, not a resized window. Window fills the actual window, showing more at the sides on wide shapes and preserving the 16:9 horizontal view on narrower shapes.

What changed

  • Adds Window to the existing Aspect ratio row (ultrawide=2), opt-in and selected after restart. No new option ID; existing monitor/output modes keep their defaults.
  • Host startup sizes the picture from the initial drawable. Camera/culling, centred HUD, movie margins, menu sides and save-thumbnail cropping follow the live shape. Render targets and hos_wide_ratio remain fixed at startup for upscaling/DLSS jitter.
  • hos_wide_resize updates squeeze/margin; THUMB uses its own live getter. Startup and live shapes use main's 5:4..48:9 clamp, including renderer draw-time UI correction. Cached canvases refit without recreating guest targets.
  • Retires projection records when an address is observed as an unrelated view; caches HOS_ASPECT once; gives headless mode the boot aspect fallback; initializes aspect_started distinctly from Window.
  • Merged main f3fe1763 through 072c38a, keeping both Window's README paragraph and #55's saved-render-scale paragraph. Later commits add tests and measured-run rows.

Flags and settings

Flag What it does Default now Recommendation Helps on Doesn't help on Untested on
Aspect ratio → Window; ultrawide=2 Uses the actual drawable, clamped to 5:4..48:9; Hor+/Vert+, centred UI, live side margins Monitor's shape (1), unchanged Keep opt-in Resized windows, including 2448×1848 Reallocating targets on resize; HUD docking Windows, other GPUs, real handhelds, HDR, DLSS/FSR/FG
HOS_ASPECT (existing) Explicit startup override disables Window mode Unset Leave unset for Window Fixed-aspect diagnostics Live resizing Same platform gaps
Output resolution (existing row) Window uses its height, not its shape Existing default unchanged Choose before restarting Startup quality/budget Increasing target detail after live widening Other temporal upscalers

The picture size is set by the window's shape at start. A windowed start is normally 1280×720. Widening it later can look softer than Monitor's shape because the targets do not grow. Start full screen or use HOS_WINDOW_SIZE to choose another initial shape. These limits are in the hint, README and options-file reference.

Measurements

main: f3fe1763dd (2026-11-13)
this PR: feat/window-aspect @ 6f39e8dc82

Those are the built and measured sources. Current head f8d5b964f427cb0290923ef9d2d4ffb4d24e8fc7 adds only tests/documentation after that runtime build. Main f3fe1763 was current when this revision started and was merged in 072c38a. Main advanced during the serialized builds to 514591a3; a final three-way merge check is clean. That newer snapshot is not included or claimed tested here.

Hardware: RX 7900 XT, RADV Mesa 26.2.3; Ryzen 5 7600X, 32 GB; NixOS 26.11, Linux 7.2.7, private Sway/Xwayland.
Scene: Castle Town wooden door (Market_Child), identical copied save, stationary. 1280×720 drawable, 1920×1080 output, render 100%, game's upscaler, compute mesh fallback, FG/HDR off, uncapped, V-Sync Auto. Audio disabled.
Method: fresh Linux builds and native shader preparation; two interleaved pairs, main/PR/main/PR, Monitor's shape on both. Warm-up before each 30 s sample; median of its last five presents-log readings. No other heavy build during these paired samples.
How to reproduce: load the same Castle Town save without moving, use the settings above and ultrawide=1, warm up, then retain five steady vk: ... presents, ... fps readings. For the live check use Window, restart, open Items, widen to 2520×1080, switch blurred/black sides, and return to 1280×720.

main @ f3fe1763, Monitor this PR @ 6f39e8dc, Monitor
Run medians (FPS), first / second 56.30 / 56.50 56.60 / 56.30
Median of run medians (FPS) 56.40 56.45

The 0.05 FPS difference is inside the run-to-run spread; these two pairs show no observed default-path regression in this scene, not a general speed claim. The bridge reported 16:9 (monitor's) in all four runs.

Exact presents-log samples, in run order
Run 1: main
[hos Presentation Thread] vk: 23280 presents, 55.6 fps; a frame: 1291 draws (0 skipped), 106 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (3032 ms), 0 programs (10 failed), 1279 textures (3053 MB)
[hos Presentation Thread] vk: 23400 presents, 56.5 fps; a frame: 1282 draws (0 skipped), 107 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (3032 ms), 0 programs (10 failed), 1282 textures (3074 MB)
[hos Presentation Thread] vk: 23520 presents, 56.1 fps; a frame: 1288 draws (0 skipped), 106 dispatches, 159 passes, 157 barriers, 6 submits, 0.2 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (3032 ms), 0 programs (10 failed), 1298 textures (3077 MB)
[hos Presentation Thread] vk: 23640 presents, 56.3 fps; a frame: 1294 draws (0 skipped), 106 dispatches, 158 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (3032 ms), 0 programs (10 failed), 1301 textures (3095 MB)
[hos Presentation Thread] vk: 23760 presents, 56.6 fps; a frame: 1292 draws (0 skipped), 107 dispatches, 158 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (3032 ms), 0 programs (10 failed), 1304 textures (3091 MB)
Run 2: this PR
[hos Presentation Thread] vk: 26400 presents, 57.3 fps; a frame: 1287 draws (0 skipped), 105 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (887 ms), 0 programs (10 failed), 1307 textures (3088 MB)
[hos Presentation Thread] vk: 26520 presents, 56.9 fps; a frame: 1287 draws (0 skipped), 105 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.1 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (887 ms), 0 programs (10 failed), 1306 textures (3079 MB)
[hos Presentation Thread] vk: 26640 presents, 56.2 fps; a frame: 1282 draws (0 skipped), 106 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (887 ms), 0 programs (10 failed), 1308 textures (3088 MB)
[hos Presentation Thread] vk: 26760 presents, 56.6 fps; a frame: 1292 draws (0 skipped), 106 dispatches, 159 passes, 157 barriers, 6 submits, 0.1 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (887 ms), 0 programs (10 failed), 1325 textures (3101 MB)
[hos Presentation Thread] vk: 26880 presents, 56.6 fps; a frame: 1293 draws (0 skipped), 105 dispatches, 158 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (887 ms), 0 programs (10 failed), 1327 textures (3119 MB)
Run 3: main
[hos Presentation Thread] vk: 26880 presents, 56.6 fps; a frame: 1277 draws (0 skipped), 105 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (886 ms), 0 programs (10 failed), 1303 textures (3109 MB)
[hos Presentation Thread] vk: 27000 presents, 56.5 fps; a frame: 1286 draws (0 skipped), 105 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (886 ms), 0 programs (10 failed), 1303 textures (3109 MB)
[hos Presentation Thread] vk: 27120 presents, 56.5 fps; a frame: 1293 draws (0 skipped), 105 dispatches, 160 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (886 ms), 0 programs (10 failed), 1301 textures (3090 MB)
[hos Presentation Thread] vk: 27240 presents, 56.6 fps; a frame: 1288 draws (0 skipped), 105 dispatches, 159 passes, 157 barriers, 6 submits, 0.1 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (886 ms), 0 programs (10 failed), 1318 textures (3103 MB)
[hos Presentation Thread] vk: 27360 presents, 56.3 fps; a frame: 1289 draws (0 skipped), 107 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (886 ms), 0 programs (10 failed), 1318 textures (3103 MB)
Run 4: this PR
[hos Presentation Thread] vk: 27120 presents, 56.6 fps; a frame: 1283 draws (0 skipped), 106 dispatches, 158 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (891 ms), 0 programs (10 failed), 1287 textures (3094 MB)
[hos Presentation Thread] vk: 27240 presents, 56.4 fps; a frame: 1293 draws (0 skipped), 106 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (891 ms), 0 programs (10 failed), 1284 textures (3066 MB)
[hos Presentation Thread] vk: 27360 presents, 56.1 fps; a frame: 1285 draws (0 skipped), 105 dispatches, 159 passes, 157 barriers, 6 submits, 0.1 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (891 ms), 0 programs (10 failed), 1290 textures (3078 MB)
[hos Presentation Thread] vk: 27480 presents, 56.2 fps; a frame: 1283 draws (0 skipped), 107 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (891 ms), 0 programs (10 failed), 1290 textures (3078 MB)
[hos Presentation Thread] vk: 27600 presents, 56.3 fps; a frame: 1286 draws (0 skipped), 106 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (891 ms), 0 programs (10 failed), 1288 textures (3059 MB)

Functional proof at this PR @ 6f39e8dc82:

Start Startup picture Live sequence Inspected result
1280×720 1920×1080 Items menu → 2520×1080 (21:9), blurred then black sides → 1280×720 Sides follow the new shape; no leftover bars at 16:9. Gameplay shows Hor+ and centred HUD.
2448×1848 1920×1448 Same sequence, then gameplay Vert+ at startup; sides follow widening and disappear at 16:9.
[hos host] display: fill window 1280x720, picture 1920x1080, scene 1440x810, camera 1.777778 (Hor+)
[hos host] display: fill window 2448x1848, picture 1920x1448, scene 1440x1086, camera 1.324675 (Vert+)
[hos Presentation Thread] vk: swapchain 2520x1080, 4 images, mailbox (V-Sync Auto)
[hos Presentation Thread] vk: ultrawide menu sides off at present 10183 (the world last drawn at present 8274)
[hos Presentation Thread] vk: swapchain 1280x720, 4 images, mailbox (V-Sync Auto)

The two functional runs overlapped another CPU build. Their diagnostic medians (43.2 and 44.8 FPS) are recorded in the ledger but are not a performance comparison. The earlier four runs at main 316f9319 / PR 1c66d19b are also recorded there as historical evidence, not measurements of this revision.

Testing

Tested: fresh Linux NUP builds, source audits (2183 PR / 2177 main archived files, no mismatches), native shader preparation, paired default-path runs and the live resize/menu checks above. Inspected screenshots; both private test units stopped after each session. Only private copies, copied saves and a private compositor were used; the shared GPU lock covered every preparation and run.

On zao, flock /tmp/upstream-prs/suite.lock python -I -B tests/run_all.py --jobs 8 --timeout 1200: 516 passed, 4 failed, 16 skipped. Two new source-reading fixture failures were fixed in aa44d39 by including the actual helper/header; the subsequent focused run passed 13/13. The remaining test_linux_tools.py (Nix PATH fixture) and test_menu_i18n.py (stale existing translation key) fail on untouched f3fe1763 too. Main's semaphore/upscaler fixtures pass. No stale translation key is unique to this PR. The full suite was not repeated after the fixture/documentation-only commits.

The regression test executes actual host_wide_menus → wide_sides code for blurred/black/off modes; it fails on pre-fix PR 072c38a and passes now. Other tests execute the injected MOVIE/THUMB sites, verify fixed DLSS ratio, checked aspect bounds, recycled-view retirement, FOV interaction, zero-size resize handling, real option saving and headless camera fallback. There is no standalone formatter/typechecker; native fixtures compile, the suite byte-compiles Python, and git diff --check passes.

Not tested: Windows build/gameplay and non-ASCII Windows paths; other GPUs and physical handhelds; HDR and DLSS/FSR/FG; full playthrough; visual movie playback and newly saved thumbnail contents after resizing (those sites have game-free coverage); native Wayland's asynchronous fullscreen transition; headless gameplay. The tested Xwayland starts reported the requested drawable before picture sizing, but that does not establish native Wayland timing.

Needs other testers:

  • T-ADMJ — Windows/llvm-mingw: build both native binaries with a non-ASCII game path; compare Monitor and Window at 1920×1080, 2448×1848, 2560×1080 and 1280×1024, then resize live. Expect unchanged default behaviour, Hor+/Vert+, centred HUD/sides and retained Window after an unrelated save/restart. No Windows machine is available here.
  • T-6SJP — #95 integration: compare #142 alone with a combined #142/#95 build at named commits through area loading and live resizes. Expect full loading fades and correct HUD projection. The ownership proposal is drafted; agreement and combined-build testing remain pending.

Screenshots

These are attachments to this public PR, not raw links into the private fork. Anonymous browser GETs returned 200 and matched the uploaded bytes. All shots are from the same Castle Town spot; menu captures are from that session.

main @ f3fe1763, Monitor, resized to 2520×1080: fixed 16:9 view with bars

this PR @ 6f39e8dc, Window, same spot resized to 2520×1080: Hor+

this PR @ 6f39e8dc, Window, startup 2448×1848: Vert+

this PR @ 6f39e8dc, Window, startup 1280×720

this PR @ 6f39e8dc, 1280×720 start widened to 21:9: blurred menu sides

this PR @ 6f39e8dc, same live 21:9 menu: black sides

this PR @ 6f39e8dc, restored to 16:9: no leftover bars

this PR @ 6f39e8dc, 2448×1848 start then 21:9 then 16:9: no leftover bars

Risk and rollback

Narrow startup shapes cost more pixels. Widening a small startup picture can look softer; extreme shapes are clamped, not unlimited portrait/surround support. Projection/UI cases outside the tested scene, temporal upscalers and asynchronous fullscreen backends still need coverage. Choose Monitor's shape or an explicit aspect and restart to disable Window. No public host/renderer interface or generated shader changes.

Reviewer notes

Review 1152's margin fix is in 6f39e8dc82ae. hos_wide_ratio is deliberately not live: DLSS jitter needs the allocated target shape. #95 should own its base layout using hos_wide_aspect(), with oot_ui_squeeze.h retained as the live correction for cached canvases. This is a coordination proposal, not a claim that the branches have been combined. #57's keep-mask handling remains intact; #199's FOV interaction checks pass.

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Merged main f3fe1763 (current when this revision started) into the branch in 072c38a; no rebase or force-push.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above). The two new fixture failures were corrected and their focused reruns pass; the exact full-run result is above.
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Flags noted, and the switch reference updated (docs/development.md, the README).
  • Screenshots for a change that shows on screen.
  • A row in docs/tested-configurations.md for each measured run.
  • A game-free test added for the fix, failing on main and passing here. The live-side regression was reproduced on pre-fix PR 072c38a, not untouched main, which has no Window mode; the new regression passes here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. Linux built and ran; no Windows machine is available. Windows/non-ASCII path verification remains T-ADMJ.
  • Docs updated.
## Why Monitor's shape follows the monitor, not a resized window. Window fills the actual window, showing more at the sides on wide shapes and preserving the 16:9 horizontal view on narrower shapes. ## What changed - Adds **Window** to the existing Aspect ratio row (`ultrawide=2`), opt-in and selected after restart. No new option ID; existing monitor/output modes keep their defaults. - Host startup sizes the picture from the initial drawable. Camera/culling, centred HUD, movie margins, menu sides and save-thumbnail cropping follow the live shape. Render targets and `hos_wide_ratio` remain fixed at startup for upscaling/DLSS jitter. - `hos_wide_resize` updates squeeze/margin; THUMB uses its own live getter. Startup and live shapes use main's 5:4..48:9 clamp, including renderer draw-time UI correction. Cached canvases refit without recreating guest targets. - Retires projection records when an address is observed as an unrelated view; caches HOS_ASPECT once; gives headless mode the boot aspect fallback; initializes `aspect_started` distinctly from Window. - Merged main `f3fe1763` through `072c38a`, keeping both Window's README paragraph and #55's saved-render-scale paragraph. Later commits add tests and measured-run rows. ## Flags and settings | Flag | What it does | Default now | Recommendation | Helps on | Doesn't help on | Untested on | |---|---|---|---|---|---|---| | Aspect ratio → Window; `ultrawide=2` | Uses the actual drawable, clamped to 5:4..48:9; Hor+/Vert+, centred UI, live side margins | Monitor's shape (1), unchanged | Keep opt-in | Resized windows, including 2448×1848 | Reallocating targets on resize; HUD docking | Windows, other GPUs, real handhelds, HDR, DLSS/FSR/FG | | `HOS_ASPECT` (existing) | Explicit startup override disables Window mode | Unset | Leave unset for Window | Fixed-aspect diagnostics | Live resizing | Same platform gaps | | Output resolution (existing row) | Window uses its height, not its shape | Existing default unchanged | Choose before restarting | Startup quality/budget | Increasing target detail after live widening | Other temporal upscalers | The picture size is set by the window's shape at start. A windowed start is normally 1280×720. Widening it later can look softer than Monitor's shape because the targets do not grow. Start full screen or use `HOS_WINDOW_SIZE` to choose another initial shape. These limits are in the hint, README and options-file reference. ## Measurements main: f3fe1763dda267a134494f1d21e6ad10b1ec4cd8 (2026-11-13) this PR: feat/window-aspect @ 6f39e8dc82aecb7741f1b06e2966adf82632c8a9 Those are the built and measured sources. Current head `f8d5b964f427cb0290923ef9d2d4ffb4d24e8fc7` adds only tests/documentation after that runtime build. Main `f3fe1763` was current when this revision started and was merged in `072c38a`. Main advanced during the serialized builds to `514591a3`; a final three-way merge check is clean. That newer snapshot is not included or claimed tested here. Hardware: RX 7900 XT, RADV Mesa 26.2.3; Ryzen 5 7600X, 32 GB; NixOS 26.11, Linux 7.2.7, private Sway/Xwayland. Scene: Castle Town wooden door (`Market_Child`), identical copied save, stationary. 1280×720 drawable, 1920×1080 output, render 100%, game's upscaler, compute mesh fallback, FG/HDR off, uncapped, V-Sync Auto. Audio disabled. Method: fresh Linux builds and native shader preparation; two interleaved pairs, main/PR/main/PR, **Monitor's shape on both**. Warm-up before each 30 s sample; median of its last five presents-log readings. No other heavy build during these paired samples. How to reproduce: load the same Castle Town save without moving, use the settings above and `ultrawide=1`, warm up, then retain five steady `vk: ... presents, ... fps` readings. For the live check use Window, restart, open Items, widen to 2520×1080, switch blurred/black sides, and return to 1280×720. | | main @ f3fe1763, Monitor | this PR @ 6f39e8dc, Monitor | |---|---|---| | Run medians (FPS), first / second | 56.30 / 56.50 | 56.60 / 56.30 | | Median of run medians (FPS) | 56.40 | 56.45 | The 0.05 FPS difference is inside the run-to-run spread; these two pairs show no observed default-path regression in this scene, not a general speed claim. The bridge reported `16:9 (monitor's)` in all four runs. <details> <summary>Exact presents-log samples, in run order</summary> ```text Run 1: main [hos Presentation Thread] vk: 23280 presents, 55.6 fps; a frame: 1291 draws (0 skipped), 106 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (3032 ms), 0 programs (10 failed), 1279 textures (3053 MB) [hos Presentation Thread] vk: 23400 presents, 56.5 fps; a frame: 1282 draws (0 skipped), 107 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (3032 ms), 0 programs (10 failed), 1282 textures (3074 MB) [hos Presentation Thread] vk: 23520 presents, 56.1 fps; a frame: 1288 draws (0 skipped), 106 dispatches, 159 passes, 157 barriers, 6 submits, 0.2 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (3032 ms), 0 programs (10 failed), 1298 textures (3077 MB) [hos Presentation Thread] vk: 23640 presents, 56.3 fps; a frame: 1294 draws (0 skipped), 106 dispatches, 158 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (3032 ms), 0 programs (10 failed), 1301 textures (3095 MB) [hos Presentation Thread] vk: 23760 presents, 56.6 fps; a frame: 1292 draws (0 skipped), 107 dispatches, 158 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (3032 ms), 0 programs (10 failed), 1304 textures (3091 MB) Run 2: this PR [hos Presentation Thread] vk: 26400 presents, 57.3 fps; a frame: 1287 draws (0 skipped), 105 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (887 ms), 0 programs (10 failed), 1307 textures (3088 MB) [hos Presentation Thread] vk: 26520 presents, 56.9 fps; a frame: 1287 draws (0 skipped), 105 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.1 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (887 ms), 0 programs (10 failed), 1306 textures (3079 MB) [hos Presentation Thread] vk: 26640 presents, 56.2 fps; a frame: 1282 draws (0 skipped), 106 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (887 ms), 0 programs (10 failed), 1308 textures (3088 MB) [hos Presentation Thread] vk: 26760 presents, 56.6 fps; a frame: 1292 draws (0 skipped), 106 dispatches, 159 passes, 157 barriers, 6 submits, 0.1 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (887 ms), 0 programs (10 failed), 1325 textures (3101 MB) [hos Presentation Thread] vk: 26880 presents, 56.6 fps; a frame: 1293 draws (0 skipped), 105 dispatches, 158 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (887 ms), 0 programs (10 failed), 1327 textures (3119 MB) Run 3: main [hos Presentation Thread] vk: 26880 presents, 56.6 fps; a frame: 1277 draws (0 skipped), 105 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (886 ms), 0 programs (10 failed), 1303 textures (3109 MB) [hos Presentation Thread] vk: 27000 presents, 56.5 fps; a frame: 1286 draws (0 skipped), 105 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (886 ms), 0 programs (10 failed), 1303 textures (3109 MB) [hos Presentation Thread] vk: 27120 presents, 56.5 fps; a frame: 1293 draws (0 skipped), 105 dispatches, 160 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (886 ms), 0 programs (10 failed), 1301 textures (3090 MB) [hos Presentation Thread] vk: 27240 presents, 56.6 fps; a frame: 1288 draws (0 skipped), 105 dispatches, 159 passes, 157 barriers, 6 submits, 0.1 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (886 ms), 0 programs (10 failed), 1318 textures (3103 MB) [hos Presentation Thread] vk: 27360 presents, 56.3 fps; a frame: 1289 draws (0 skipped), 107 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24463 pipelines (886 ms), 0 programs (10 failed), 1318 textures (3103 MB) Run 4: this PR [hos Presentation Thread] vk: 27120 presents, 56.6 fps; a frame: 1283 draws (0 skipped), 106 dispatches, 158 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (891 ms), 0 programs (10 failed), 1287 textures (3094 MB) [hos Presentation Thread] vk: 27240 presents, 56.4 fps; a frame: 1293 draws (0 skipped), 106 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (891 ms), 0 programs (10 failed), 1284 textures (3066 MB) [hos Presentation Thread] vk: 27360 presents, 56.1 fps; a frame: 1285 draws (0 skipped), 105 dispatches, 159 passes, 157 barriers, 6 submits, 0.1 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (891 ms), 0 programs (10 failed), 1290 textures (3078 MB) [hos Presentation Thread] vk: 27480 presents, 56.2 fps; a frame: 1283 draws (0 skipped), 107 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (891 ms), 0 programs (10 failed), 1290 textures (3078 MB) [hos Presentation Thread] vk: 27600 presents, 56.3 fps; a frame: 1286 draws (0 skipped), 106 dispatches, 159 passes, 157 barriers, 6 submits, 0.0 uploads (0.0 MB), 82 ub updates (57 overlays), GPU-bound wait 0.0 ms; 24465 pipelines (891 ms), 0 programs (10 failed), 1288 textures (3059 MB) ``` </details> **Functional proof at this PR @ 6f39e8dc82ae:** | Start | Startup picture | Live sequence | Inspected result | |---|---|---|---| | 1280×720 | 1920×1080 | Items menu → 2520×1080 (21:9), blurred then black sides → 1280×720 | Sides follow the new shape; no leftover bars at 16:9. Gameplay shows Hor+ and centred HUD. | | 2448×1848 | 1920×1448 | Same sequence, then gameplay | Vert+ at startup; sides follow widening and disappear at 16:9. | ```text [hos host] display: fill window 1280x720, picture 1920x1080, scene 1440x810, camera 1.777778 (Hor+) [hos host] display: fill window 2448x1848, picture 1920x1448, scene 1440x1086, camera 1.324675 (Vert+) [hos Presentation Thread] vk: swapchain 2520x1080, 4 images, mailbox (V-Sync Auto) [hos Presentation Thread] vk: ultrawide menu sides off at present 10183 (the world last drawn at present 8274) [hos Presentation Thread] vk: swapchain 1280x720, 4 images, mailbox (V-Sync Auto) ``` The two functional runs overlapped another CPU build. Their diagnostic medians (43.2 and 44.8 FPS) are recorded in the ledger but are **not** a performance comparison. The earlier four runs at main `316f9319` / PR `1c66d19b` are also recorded there as historical evidence, not measurements of this revision. ## Testing **Tested:** fresh Linux NUP builds, source audits (2183 PR / 2177 main archived files, no mismatches), native shader preparation, paired default-path runs and the live resize/menu checks above. Inspected screenshots; both private test units stopped after each session. Only private copies, copied saves and a private compositor were used; the shared GPU lock covered every preparation and run. On zao, `flock /tmp/upstream-prs/suite.lock python -I -B tests/run_all.py --jobs 8 --timeout 1200`: **516 passed, 4 failed, 16 skipped**. Two new source-reading fixture failures were fixed in `aa44d39` by including the actual helper/header; the subsequent focused run passed **13/13**. The remaining `test_linux_tools.py` (Nix PATH fixture) and `test_menu_i18n.py` (stale existing translation key) fail on untouched `f3fe1763` too. Main's semaphore/upscaler fixtures pass. No stale translation key is unique to this PR. The full suite was not repeated after the fixture/documentation-only commits. The regression test executes actual `host_wide_menus` → `wide_sides` code for blurred/black/off modes; it fails on pre-fix PR `072c38a` and passes now. Other tests execute the injected MOVIE/THUMB sites, verify fixed DLSS ratio, checked aspect bounds, recycled-view retirement, FOV interaction, zero-size resize handling, real option saving and headless camera fallback. There is no standalone formatter/typechecker; native fixtures compile, the suite byte-compiles Python, and `git diff --check` passes. **Not tested:** Windows build/gameplay and non-ASCII Windows paths; other GPUs and physical handhelds; HDR and DLSS/FSR/FG; full playthrough; visual movie playback and newly saved thumbnail contents after resizing (those sites have game-free coverage); native Wayland's asynchronous fullscreen transition; headless gameplay. The tested Xwayland starts reported the requested drawable before picture sizing, but that does not establish native Wayland timing. **Needs other testers:** - **T-ADMJ — Windows/llvm-mingw:** build both native binaries with a non-ASCII game path; compare Monitor and Window at 1920×1080, 2448×1848, 2560×1080 and 1280×1024, then resize live. Expect unchanged default behaviour, Hor+/Vert+, centred HUD/sides and retained Window after an unrelated save/restart. No Windows machine is available here. - **T-6SJP — #95 integration:** compare #142 alone with a combined #142/#95 build at named commits through area loading and live resizes. Expect full loading fades and correct HUD projection. The ownership proposal is drafted; agreement and combined-build testing remain pending. ## Screenshots These are attachments to this public PR, not raw links into the private fork. Anonymous browser GETs returned 200 and matched the uploaded bytes. All shots are from the same Castle Town spot; menu captures are from that session. ![main @ f3fe1763, Monitor, resized to 2520×1080: fixed 16:9 view with bars](https://git.magicbean.dev/attachments/17feba60-cef3-4197-900a-28c525adaeb3) ![this PR @ 6f39e8dc, Window, same spot resized to 2520×1080: Hor+](https://git.magicbean.dev/attachments/f1bfb022-818b-4529-b26a-90c16402f911) ![this PR @ 6f39e8dc, Window, startup 2448×1848: Vert+](https://git.magicbean.dev/attachments/b4244225-0545-4351-9b9a-fdb7451a4c2b) ![this PR @ 6f39e8dc, Window, startup 1280×720](https://git.magicbean.dev/attachments/f64d0a23-e374-415b-946b-4da45c27ced6) ![this PR @ 6f39e8dc, 1280×720 start widened to 21:9: blurred menu sides](https://git.magicbean.dev/attachments/abeb2a3d-e459-4728-b20f-6efb86dba7c2) ![this PR @ 6f39e8dc, same live 21:9 menu: black sides](https://git.magicbean.dev/attachments/fc340ffd-0045-45d2-9880-44ac93b27ee9) ![this PR @ 6f39e8dc, restored to 16:9: no leftover bars](https://git.magicbean.dev/attachments/1854d9d4-f378-4098-81e6-bcd2b69a5f24) ![this PR @ 6f39e8dc, 2448×1848 start then 21:9 then 16:9: no leftover bars](https://git.magicbean.dev/attachments/c520b26b-228e-4434-b2e9-c55ef2bbaa42) ## Risk and rollback Narrow startup shapes cost more pixels. Widening a small startup picture can look softer; extreme shapes are clamped, not unlimited portrait/surround support. Projection/UI cases outside the tested scene, temporal upscalers and asynchronous fullscreen backends still need coverage. Choose Monitor's shape or an explicit aspect and restart to disable Window. No public host/renderer interface or generated shader changes. ## Reviewer notes Review 1152's margin fix is in `6f39e8dc82ae`. `hos_wide_ratio` is deliberately **not** live: DLSS jitter needs the allocated target shape. #95 should own its base layout using `hos_wide_aspect()`, with `oot_ui_squeeze.h` retained as the live correction for cached canvases. This is a coordination proposal, not a claim that the branches have been combined. #57's keep-mask handling remains intact; #199's FOV interaction checks pass. ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [x] Merged main `f3fe1763` (current when this revision started) into the branch in `072c38a`; no rebase or force-push. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). The two new fixture failures were corrected and their focused reruns pass; the exact full-run result is above. - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [x] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Flags noted, and the switch reference updated (docs/development.md, the README). - [x] Screenshots for a change that shows on screen. - [x] A row in `docs/tested-configurations.md` for each measured run. - [ ] A game-free test added for the fix, failing on `main` and passing here. The live-side regression was reproduced on pre-fix PR `072c38a`, not untouched main, which has no Window mode; the new regression passes here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. Linux built and ran; no Windows machine is available. Windows/non-ASCII path verification remains **T-ADMJ**. - [x] Docs updated.
feat(video): a Window choice that fills the actual window, Hor+ and Vert+
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 6s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
1c66d19bab
Also proposed on Hookshot: simonwjackson/Hookshot feat-window-aspect.
Cherry-picked source: e7481e2, 4813a27, 92b3eb5, a71778d, d1d9cdc.
Resolve rename/context conflicts without changing feature behaviour.
Keep measured configurations in the PR description, not the shared table.

Assisted-by: OpenAI GPT-6 Astra
Co-Authored-By: OpenAI GPT-6 Astra <[email protected]>
Author
Contributor

Added a fresh interleaved main/PR Linux measurement session at the named commits, the last-five-sample FPS medians, feature proof and inspected screenshots. Windows testing requests are open; the description names the remaining gaps. The branch also rebases cleanly onto main 1be180bb with an identical stable patch ID and passing focused tests; the force-with-lease push is still gated, so the measured remote head stays pinned. No new baseline FPS is claimed for the local rebase. The four drawable shapes and live resize were checked; #95 loading-fade integration has its own open request.

Added a fresh interleaved main/PR Linux measurement session at the named commits, the last-five-sample FPS medians, feature proof and inspected screenshots. Windows testing requests are open; the description names the remaining gaps. The branch also rebases cleanly onto main 1be180bb with an identical stable patch ID and passing focused tests; the force-with-lease push is still gated, so the measured remote head stays pinned. No new baseline FPS is claimed for the local rebase. The four drawable shapes and live resize were checked; #95 loading-fade integration has its own open request.
simonwjackson changed title from WIP: feat(video): a Window choice that fills the actual window, Hor+ and Vert+ to feat(video): a Window choice that fills the actual window, Hor+ and Vert+ 2026-11-13 07:49:58 +00:00
Merge current Longshot main into feat/window-aspect
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 3s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
Website / Website on Cloudflare (pull_request) Has been cancelled
1adca03782
Keep current menu documentation with the Window aspect choice, and retain both default-output and Window cases in the output-resolution test.

Assisted-by: OpenAI GPT-6 Astra
Co-Authored-By: OpenAI GPT-6 Astra <[email protected]>
Author
Contributor

Merged main snapshot dd02c6cca9 with a merge commit and normal push; resolved README using main’s current rows plus Window, and retained both main’s new default-output cases and our Window/save cases in test_output_resolution.py. Eleven focused checks pass; full suite: 506 passed, 3 failures reproduced on untouched main, 16 skipped; Linux build and isolated Castle Town smoke pass, and newer main fdc2840b still merges cleanly (details in Testing).

Merged main snapshot dd02c6cca9582f5bb1b992d5c43268bf5d846c3f with a merge commit and normal push; resolved README using main’s current rows plus Window, and retained both main’s new default-output cases and our Window/save cases in test_output_resolution.py. Eleven focused checks pass; full suite: 506 passed, 3 failures reproduced on untouched main, 16 skipped; Linux build and isolated Castle Town smoke pass, and newer main fdc2840b still merges cleanly (details in Testing).
HumanBean added this to the v3.0.0 milestone 2026-11-13 17:53:56 +00:00
HumanBean left a comment

Thanks. This is careful work. The Hor+/Vert+ projection is correct: the tangent is multiplied by 16:9 over the window's shape only when the window is narrower, so the horizontal view stays the 16:9 one. Keeping each projection's original tangent stops the FOV growing on repeated updates, and that also holds with the FOV slider's hook (the new block in tests/test_oot_fov.py). A minimised window (0x0) is ignored, both in rx_resize and in hos_wide_resize. A live resize changes no render target, so no upscaler history or guest heap has to be rebuilt. The swapchain is still remade the way main does it (swap_resize folds a burst of resizes into one remake at the next present). The default doesn't change: ultrawide=0/1 and a missing line read and save as before, and hos_wide_window is set only by ultrawide=2. HOS_ASPECT still overrides. No MkwVkHost/MkwVkRenderer change, third_party/ untouched, no generated files.

I read the code only. I didn't build it or run any checks.

Must change

  1. It doesn't merge with current main. Main is now at fbd42024. README.md conflicts with the bullet that #55 added in the same place (f647a072, "Changing the Upscaler keeps your chosen render scale..."). Keep both bullets. Every other file merges cleanly (I checked with a three-way merge). The "Rebased on current main" box is false until then.

  2. After a live resize, movies, the menus' sides and the save picture still use the startup shape. hos_wide_resize updates only the camera/HUD aspect. hos_wide_margin is set only in hos_wide_start, and two things still read it:

    • the MOVIE site, so pre-rendered movies are no longer in the middle 16:9;
    • host_wide_menus → vk_framegen.cpp wide_sides(), which places the blurred or black sides of the pause and map menus.

    The THUMB site likewise reads the startup hos_wide_ratio. Two examples:

    • Start full screen on a 21:9 screen, then go back to a 16:9 window: the menus are drawn across the full 16:9, but the blur/bars still cover about 12% on each side.
    • Start at 1280x720 and widen to 21:9: movies are stretched and the menus get no side treatment.

    Recompute hos_wide_squeeze/hos_wide_margin from the live aspect in hos_wide_resize. Give THUMB a live ratio of its own. hos_wide_ratio has to stay at the startup value, because the DLSS jitter (rx_backend.c dlss_jitter_clip) needs the shape the render targets actually have. These are the paths the description lists as untested; "Follows window resizes" isn't true for them yet.

  3. The checklist doesn't match the template.

    • The template's "Host code builds on both Windows (llvm-mingw) and Linux" box was reworded and ticked. No Windows build was made, so it should stay unticked, with the reason, until someone builds it. T-ADMJ can cover this.
    • "A row in docs/tested-configurations.md" was changed to "the folded table below". Add the rows to the file, as AGENTS.md asks.
  4. The screenshots return 404. git.magicbean.dev/simonwjackson/Longshot/raw/commit/02f8cb12.../*.png doesn't resolve: the fork or that commit isn't reachable. Attach them to the PR so a reviewer can see Hor+/Vert+ and the centred HUD. If you can, add a main @ hash shot of the same spot at one of the resized sizes.

Should change

  • Clamp the shape.
    • hos_wide_picture_size takes any aspect: main's hos_wide_clamp (5:4 to 48:9) isn't applied. Its pixel cap can also break the "do not shrink below 1920x1080" comment. Below about 1:3, the width drops under 1920 (540x2160 → 1656x6648). Above about 10:1, the height drops under 1080, and from about 28:1 the width goes past 16384. You can only get there at startup with HOS_WINDOW_SIZE or full screen.
    • The live aspect isn't clamped at all. A 1600x40 or 40x1600 window gives a view of about 175° across or up and down. With the 300% FOV of #199 it goes further still.
    • Clamp both, the startup shape and the live one, to a range you've checked.
  • The picture keeps the window's startup shape, and that is usually 16:9. In Window mode the window opens at 1280x720 unless it starts full screen or HOS_WINDOW_SIZE is set. So a player who picks Window and maximises on a 21:9 screen gets a 2560x1440 picture stretched across 3440, which is softer than Monitor's shape. Say so in the hint and the README ("the picture's size is set by the window's shape at start"), or open the window at the size it last had.
    • On SDL, is hos_sdl_pixel_size right after set_fullscreen already the full-screen size? On X11/Wayland, going full screen can be asynchronous. I haven't checked this.
  • window_fit_active() calls getenv("HOS_ASPECT") on every draw and every 0x90 uniform block in Window mode. Read it once.
  • Clean up the hos_wide_projection table.
    • It only grows: one entry per projection address ever seen, kept sorted with a memmove.
    • A recycled address keeps valid. In a window whose aspect equals the stored patched_aspect (for example a square window), a new square shadow view at that address is then taken for a patched camera.
    • Drop an entry once its aspect is neither 16:9 nor the patched value, and don't take that path for a view with no entry.
  • Window + HOS_HEADLESS: the picture keeps the monitor's shape from hos_main.c, but hos_wide_aspect() is 0, so the cameras stay at 16:9 and the picture is stretched. The live _ratio doesn't fall back to _ratio_fixed. This is a minor dev-only case.

The other layout work

  • #95 rewrites the same HUD string in nx2/oot_wide.py, so they will conflict as text, and whichever lands second has to port. As written, Window mode replaces #95's HUD outright:

    • #95's hos_ui_qx/qy and edge-anchored panes don't apply in Window mode;
    • #95's hos_wide_ui_tick reads the startup hos_wide_squeeze/hos_wide_ratio, which a live resize doesn't change.

    Please agree with #95's author which one owns the HUD projection. I'd suggest #95's layout taking hos_wide_aspect() as its shape, with the draw-time refit (oot_ui_squeeze.h) staying as the live correction.

  • #57 only changes which keep-mask bits the menus clear. Window's HUD branch still honours hos_wide_keep_mask, so the two are compatible. Its fades still depend on the side margin from point 2 above.

  • #199 and this PR edit different parts of tests/test_oot_fov.py.

  • The Output resolution row: in Window mode only the chosen size's height is used, and its shape is ignored. That matches picture_for; the hint could say so.

Nits

  • The measurement compares main with this PR in Window mode. This PR's own Monitor's-shape path, which every player runs, wasn't measured. On a 16:9 window the run medians are 53.85 vs 53.10. Since hos_display_start now waits for the window for everyone, one interleaved pair on the default would settle it.
  • aspect_started starts at -1, which is now Window's value until outputs_sync runs.
Thanks. This is careful work. The Hor+/Vert+ projection is correct: the tangent is multiplied by 16:9 over the window's shape only when the window is narrower, so the horizontal view stays the 16:9 one. Keeping each projection's original tangent stops the FOV growing on repeated updates, and that also holds with the FOV slider's hook (the new block in `tests/test_oot_fov.py`). A minimised window (0x0) is ignored, both in `rx_resize` and in `hos_wide_resize`. A live resize changes no render target, so no upscaler history or guest heap has to be rebuilt. The swapchain is still remade the way main does it (`swap_resize` folds a burst of resizes into one remake at the next present). The default doesn't change: `ultrawide=0/1` and a missing line read and save as before, and `hos_wide_window` is set only by `ultrawide=2`. `HOS_ASPECT` still overrides. No `MkwVkHost`/`MkwVkRenderer` change, `third_party/` untouched, no generated files. I read the code only. I didn't build it or run any checks. ## Must change 1. **It doesn't merge with current main.** Main is now at `fbd42024`. `README.md` conflicts with the bullet that https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/55 added in the same place (f647a072, "Changing the Upscaler keeps your chosen render scale..."). Keep both bullets. Every other file merges cleanly (I checked with a three-way merge). The "Rebased on current `main`" box is false until then. 2. **After a live resize, movies, the menus' sides and the save picture still use the startup shape.** `hos_wide_resize` updates only the camera/HUD aspect. `hos_wide_margin` is set only in `hos_wide_start`, and two things still read it: - the MOVIE site, so pre-rendered movies are no longer in the middle 16:9; - `host_wide_menus` → `vk_framegen.cpp wide_sides()`, which places the blurred or black sides of the pause and map menus. The THUMB site likewise reads the startup `hos_wide_ratio`. Two examples: - Start full screen on a 21:9 screen, then go back to a 16:9 window: the menus are drawn across the full 16:9, but the blur/bars still cover about 12% on each side. - Start at 1280x720 and widen to 21:9: movies are stretched and the menus get no side treatment. Recompute `hos_wide_squeeze`/`hos_wide_margin` from the live aspect in `hos_wide_resize`. Give THUMB a live ratio of its own. `hos_wide_ratio` has to stay at the startup value, because the DLSS jitter (`rx_backend.c dlss_jitter_clip`) needs the shape the render targets actually have. These are the paths the description lists as untested; "Follows window resizes" isn't true for them yet. 3. **The checklist doesn't match the template.** - The template's "Host code builds on both Windows (llvm-mingw) and Linux" box was reworded and ticked. No Windows build was made, so it should stay unticked, with the reason, until someone builds it. T-ADMJ can cover this. - "A row in `docs/tested-configurations.md`" was changed to "the folded table below". Add the rows to the file, as AGENTS.md asks. 4. **The screenshots return 404.** `git.magicbean.dev/simonwjackson/Longshot/raw/commit/02f8cb12.../*.png` doesn't resolve: the fork or that commit isn't reachable. Attach them to the PR so a reviewer can see Hor+/Vert+ and the centred HUD. If you can, add a main @ hash shot of the same spot at one of the resized sizes. ## Should change - **Clamp the shape.** - `hos_wide_picture_size` takes any aspect: main's `hos_wide_clamp` (5:4 to 48:9) isn't applied. Its pixel cap can also break the "do not shrink below 1920x1080" comment. Below about 1:3, the width drops under 1920 (540x2160 → 1656x6648). Above about 10:1, the height drops under 1080, and from about 28:1 the width goes past 16384. You can only get there at startup with `HOS_WINDOW_SIZE` or full screen. - The live aspect isn't clamped at all. A 1600x40 or 40x1600 window gives a view of about 175° across or up and down. With the 300% FOV of https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/199 it goes further still. - Clamp both, the startup shape and the live one, to a range you've checked. - **The picture keeps the window's startup shape, and that is usually 16:9.** In Window mode the window opens at 1280x720 unless it starts full screen or `HOS_WINDOW_SIZE` is set. So a player who picks Window and maximises on a 21:9 screen gets a 2560x1440 picture stretched across 3440, which is softer than Monitor's shape. Say so in the hint and the README ("the picture's size is set by the window's shape at start"), or open the window at the size it last had. - On SDL, is `hos_sdl_pixel_size` right after `set_fullscreen` already the full-screen size? On X11/Wayland, going full screen can be asynchronous. I haven't checked this. - **`window_fit_active()` calls `getenv("HOS_ASPECT")` on every draw and every 0x90 uniform block in Window mode.** Read it once. - **Clean up the `hos_wide_projection` table.** - It only grows: one entry per projection address ever seen, kept sorted with a `memmove`. - A recycled address keeps `valid`. In a window whose aspect equals the stored `patched_aspect` (for example a square window), a new square shadow view at that address is then taken for a patched camera. - Drop an entry once its aspect is neither 16:9 nor the patched value, and don't take that path for a view with no entry. - **Window + `HOS_HEADLESS`:** the picture keeps the monitor's shape from `hos_main.c`, but `hos_wide_aspect()` is 0, so the cameras stay at 16:9 and the picture is stretched. The live `_ratio` doesn't fall back to `_ratio_fixed`. This is a minor dev-only case. ## The other layout work - **https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/95** rewrites the same `HUD` string in `nx2/oot_wide.py`, so they will conflict as text, and whichever lands second has to port. As written, Window mode replaces #95's HUD outright: - #95's `hos_ui_qx/qy` and edge-anchored panes don't apply in Window mode; - #95's `hos_wide_ui_tick` reads the startup `hos_wide_squeeze`/`hos_wide_ratio`, which a live resize doesn't change. Please agree with #95's author which one owns the HUD projection. I'd suggest #95's layout taking `hos_wide_aspect()` as its shape, with the draw-time refit (`oot_ui_squeeze.h`) staying as the live correction. - **https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/57** only changes which keep-mask bits the menus clear. Window's HUD branch still honours `hos_wide_keep_mask`, so the two are compatible. Its fades still depend on the side margin from point 2 above. - **https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/199** and this PR edit different parts of `tests/test_oot_fov.py`. - **The Output resolution row:** in Window mode only the chosen size's height is used, and its shape is ignored. That matches `picture_for`; the hint could say so. ## Nits - The measurement compares main with this PR in Window mode. This PR's own Monitor's-shape path, which every player runs, wasn't measured. On a 16:9 window the run medians are 53.85 vs 53.10. Since `hos_display_start` now waits for the window for everyone, one interleaved pair on the default would settle it. - `aspect_started` starts at -1, which is now Window's value until `outputs_sync` runs.
@ -497,6 +497,12 @@ HDR, Detail, and GPU and memory (last: the rows to change when something is wron
and **Sharpness** sharpens it, by the picture's pixels: when the window is much larger than the picture (the Handheld
Profile's 1536x864 on a 4K screen), a Sharpness kept from an older version is stronger than it was, so a lower one may
suit.
- **Window aspect ratio** (opt-in): fills the actual window. Wider than 16:9 shows more at the sides (Hor+);
Owner

This conflicts with current main (fbd42024): #55 added the 'Changing the Upscaler keeps your chosen render scale' bullet in the same place. Keep both.

This conflicts with current main (`fbd42024`): https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/55 added the 'Changing the Upscaler keeps your chosen render scale' bullet in the same place. Keep both.
@ -3989,6 +4006,8 @@ void draw_startup(ui::Ctx& c) {
} // namespace
bool window_fit_active() { return ultrawide_read == 2 && !getenv("HOS_ASPECT"); }
Owner

This is called per draw (draw()) and per 0x90 uniform block (uniform_range) in Window mode, so it calls getenv on the hot path. Cache HOS_ASPECT once.

This is called per draw (`draw()`) and per 0x90 uniform block (`uniform_range`) in Window mode, so it calls `getenv` on the hot path. Cache `HOS_ASPECT` once.
@ -0,0 +16,4 @@
float hos_wide_aspect(void) { return hos_wide_window ? atomic_load_explicit(&window_aspect, memory_order_relaxed) : hos_aspect_ratio; }
void hos_wide_resize(unsigned width, unsigned height) {
Owner

Only the camera/HUD aspect follows a resize here. hos_wide_margin/hos_wide_squeeze, set once in hos_wide_start, still drive the MOVIE site and the menus' blurred/black sides (host_wide_menus -> vk_framegen.cpp wide_sides), and THUMB reads the startup hos_wide_ratio. After a resize, movies and menu sides keep the startup shape. Recompute squeeze/margin here from the live aspect, and give THUMB its own live ratio. Keep hos_wide_ratio as it is for the DLSS jitter, since the targets keep their startup size. The live aspect is also unclamped: a 1600x40 or 40x1600 window gives a view of about 175 degrees.

Only the camera/HUD aspect follows a resize here. `hos_wide_margin`/`hos_wide_squeeze`, set once in `hos_wide_start`, still drive the MOVIE site and the menus' blurred/black sides (`host_wide_menus` -> `vk_framegen.cpp wide_sides`), and THUMB reads the startup `hos_wide_ratio`. After a resize, movies and menu sides keep the startup shape. Recompute squeeze/margin here from the live aspect, and give THUMB its own live ratio. Keep `hos_wide_ratio` as it is for the DLSS jitter, since the targets keep their startup size. The live aspect is also unclamped: a 1600x40 or 40x1600 window gives a view of about 175 degrees.
@ -0,0 +21,4 @@
if (aspect < 16.0 / 9.0) oh = (unsigned)(ow / aspect / 8 + 0.5) * 8;
else ow = (unsigned)(oh * aspect / 8 + 0.5) * 8;
/* Upstream's render-target pool guard (issue 1031): cap pixels, not shape. */
if ((double)ow * oh > hos_wide_max_px()) {
Owner

There's no hos_wide_clamp here, unlike main's path, and this cap can break the comment above. Below about 1:3 the width drops under 1920 (540x2160 -> 1656x6648). Above about 10:1 the height drops under 1080, and from about 28:1 the width passes 16384. Clamp the aspect first (main's 5:4 to 48:9, or a portrait floor you've checked).

There's no `hos_wide_clamp` here, unlike main's path, and this cap can break the comment above. Below about 1:3 the width drops under 1920 (540x2160 -> 1656x6648). Above about 10:1 the height drops under 1080, and from about 28:1 the width passes 16384. Clamp the aspect first (main's 5:4 to 48:9, or a portrait floor you've checked).
@ -300,1 +312,4 @@
if (getenv("HOS_FULLSCREEN") && atoi(getenv("HOS_FULLSCREEN"))) set_fullscreen(1);
int pixel_w = 0, pixel_h = 0;
hos_sdl_pixel_size(window, &pixel_w, &pixel_h);
hos_wide_start(pixel_w, pixel_h);
Owner

Is the pixel size already the full-screen size right after set_fullscreen(1)? On X11/Wayland the switch can be asynchronous, and the picture is sized only from this value. Otherwise, a windowed start opens at 1280x720 in Window mode, so the picture is nearly always 16:9 and a maximised ultrawide window stretches 2560 pixels across 3440. Worth saying in the hint/README.

Is the pixel size already the full-screen size right after `set_fullscreen(1)`? On X11/Wayland the switch can be asynchronous, and the picture is sized only from this value. Otherwise, a windowed start opens at 1280x720 in Window mode, so the picture is nearly always 16:9 and a maximised ultrawide window stretches 2560 pixels across 3440. Worth saying in the hint/README.
@ -51,1 +87,4 @@
HUD = "{ extern int hos_wide_window; if (hos_wide_window) " + HUD + " else " + HUD_FIXED + " }"
MOVIE = ("{ extern volatile float hos_wide_margin; const float _m = hos_wide_margin; if (_m > 0.0f) { "
Owner

MOVIE still reads hos_wide_margin, which is only set at startup in Window mode, so movies leave the middle 16:9 after a live resize (see host/hos_wide.c).

MOVIE still reads `hos_wide_margin`, which is only set at startup in Window mode, so movies leave the middle 16:9 after a live resize (see host/hos_wide.c).
@ -48,6 +72,21 @@ HUD = ("{ extern volatile float hos_wide_squeeze; const float _q = hos_wide_sque
"memcpy(c->vreg[1], _row, 16); } } } } /* (nx2/oot_wide.py: HUD in the middle 16:9) */"
% (UI_M00, UI_M11, " : ".join("_vt == 0x%xULL ? %du" % (v, 1 << k) for k, v in enumerate(KEEP_VTABLES)) + " : 0u"))
HUD = ("{ extern float hos_wide_aspect(void); const float _w = hos_wide_aspect(); if (_w > 0.0f) { float _m00, _m11; "
Owner

#95 rewrites this same HUD string (hos_ui_qx/qy, edge-anchored panes). In Window mode this branch replaces it outright, and #95's hos_wide_ui_tick reads the startup squeeze/ratio. Please agree with #95 which one owns the HUD projection before either lands.

https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/95 rewrites this same HUD string (`hos_ui_qx/qy`, edge-anchored panes). In Window mode this branch replaces it outright, and #95's `hos_wide_ui_tick` reads the startup squeeze/ratio. Please agree with #95 which one owns the HUD projection before either lands.
Keep both the window-aspect and upscaler render-scale documentation.

Assisted-by: OpenAI GPT-6 Astra
Co-Authored-By: OpenAI GPT-6 Astra <[email protected]>
Clamp startup and live aspect to 5:4..48:9, preserve the render-target ratio for DLSS jitter, and use the live shape for save thumbnails. Retire recycled projection records, cache the aspect override and retain a headless fallback. Document startup sizing.

Assisted-by: OpenAI GPT-6 Astra
Co-Authored-By: OpenAI GPT-6 Astra <[email protected]>
Assisted-by: OpenAI GPT-6 Astra
Co-Authored-By: OpenAI GPT-6 Astra <[email protected]>
Move the four historical runs from the PR description into the tested configurations ledger, without claiming they ran on the review-fix build.

Assisted-by: OpenAI GPT-6 Astra
Co-Authored-By: OpenAI GPT-6 Astra <[email protected]>
The current-main upscaler fixture now extracts the real Window sentinel. The swapchain fixture includes the shared checked aspect range. Both pass without weakening assertions.

Assisted-by: OpenAI GPT-6 Astra
Co-Authored-By: OpenAI GPT-6 Astra <[email protected]>
Verify the headless camera fallback in the host fixture. Record the 1280x720 and 2448x1848 startup runs and their live 21:9/16:9 menu checks. These functional runs overlapped a CPU build; their FPS readings are not a performance comparison.

Assisted-by: OpenAI GPT-6 Astra
Co-Authored-By: OpenAI GPT-6 Astra <[email protected]>
docs(video): record interleaved default-path acceptance
Some checks failed
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Failing after 5s
f8d5b964f4
Main f3fe1763 and PR runtime 6f39e8dc used Monitor shape on both sides: main/PR/main/PR, no concurrent heavy build. Run medians were 56.30/56.60/56.50/56.30 FPS. Attach the same resized Castle Town spot from main for the visual comparison.

Assisted-by: OpenAI GPT-6 Astra
Co-Authored-By: OpenAI GPT-6 Astra <[email protected]>
Author
Contributor

Addressed review 1152 in head f8d5b964f427. The runtime fix is 6f39e8dc82ae; later commits add regression coverage and the measured-run ledger.

Review point Resolution
Must 1: current main / README conflict Merge commit 072c38a brings in f3fe1763. Both #55's saved-render-scale paragraph and Window's paragraph remain. No rebase or force-push. Main advanced during the serialized builds to 514591a3; a final three-way merge check is clean, but that newer snapshot is not included or claimed tested. The checklist now says what was actually merged.
Must 2: live movie/menu margins and save picture hos_wide_resize updates squeeze/margin. MOVIE and host_wide_menus → wide_sides now receive the live margin. THUMB calls hos_wide_thumb_ratio; hos_wide_ratio remains the startup value for DLSS jitter. Actual injected MOVIE/THUMB code and the host-to-renderer blur/black-side path have executable regression tests. The new menu-side test fails before the fix and passes after it.
Must 3: checklist and ledger Restored the original Windows/Linux build box, unticked: no Windows machine; T-ADMJ remains open. Historical and new runs are rows in docs/tested-configurations.md, below the day's existing rows. No folded-table substitute.
Must 4: inaccessible images Replaced fork raw links with PR attachments. Anonymous browser GETs return 200 with matching SHA-256. Includes a current-main shot of the same Castle Town spot at the resized 2520×1080 size.
Clamp startup/live shape Both use the existing 5:4..48:9 clamp, including renderer draw-time refit. Tests cover extreme 40×1600/1600×40 inputs and the picture cap. 2448×1848 lies inside the range and was run in-game.
Startup picture / Output resolution hints Hint, README and options-file reference now say that the picture size is set by the window's shape at start, and only the selected output's height is used. Widening the usual 1280×720 start can be softer than Monitor's shape. Both tested Xwayland starts used the requested drawable before sizing; native Wayland's asynchronous fullscreen transition remains unverified.
Per-draw getenv window_fit_active caches the HOS_ASPECT presence check once.
Projection table An observed aspect that is neither 16:9 nor the saved patched aspect retires its entry. Untracked non-16:9 views do not enter the table. A recycled-address regression checks this.
Window + headless Without a live drawable, hos_wide_aspect falls back to the boot picture's aspect. The actual projection path is covered by a game-free test. No headless gameplay claim.
#95 ownership Prepared a proposal for #95's layout to use hos_wide_aspect(), keeping oot_ui_squeeze.h as draw-time live correction. The proposal is drafted, not an agreement yet. Combined-build coverage remains T-6SJP.
#57 / #199 Keep-mask handling is preserved; the corrected margin also reaches menu fades. FOV interaction tests pass. No unrelated #57/#199 changes are folded in.
Default-path measurement / initial sentinel New interleaved main/PR runs use Monitor's shape on both sides; run medians are main 56.30/56.50 and PR 56.60/56.30 FPS (medians of medians 56.40/56.45), in the description and ledger. aspect_started now starts at -3, distinct from Window (-1), monitor (-2), and shape indices.

Linux acceptance: start 1280×720, open Items, widen to 2520×1080; both blurred and black sides appear. Return to 1280×720: neither leaves bars. Repeat from a 2448×1848 startup. Inspected screenshots show Hor+/Vert+ and centred HUD. Movies and resized thumbnail contents have game-free coverage only; those visual checks, Windows, native Wayland fullscreen, HDR and temporal upscalers remain stated gaps.

Checks: full suite once, 516 passed / 4 failed / 16 skipped. Two new source-reading fixture failures were fixed without weakening assertions (aa44d39), then the focused 13-check run passed. The remaining test_linux_tools and test_menu_i18n failures reproduce on untouched f3fe1763; no stale translation key is unique to this PR. Both fresh Linux builds and their native shader preparations passed. No separate formatter/typechecker is configured; native fixtures compile, Python is byte-compiled by the suite, and git diff --check passes.

Please re-review the code and Linux evidence. Windows and #95 integration remain explicit test requests, not claimed passes.

Addressed review 1152 in head `f8d5b964f427`. The runtime fix is `6f39e8dc82ae`; later commits add regression coverage and the measured-run ledger. | Review point | Resolution | |---|---| | Must 1: current main / README conflict | Merge commit `072c38a` brings in `f3fe1763`. Both #55's saved-render-scale paragraph and Window's paragraph remain. No rebase or force-push. Main advanced during the serialized builds to `514591a3`; a final three-way merge check is clean, but that newer snapshot is not included or claimed tested. The checklist now says what was actually merged. | | Must 2: live movie/menu margins and save picture | `hos_wide_resize` updates squeeze/margin. MOVIE and `host_wide_menus` → `wide_sides` now receive the live margin. THUMB calls `hos_wide_thumb_ratio`; `hos_wide_ratio` remains the startup value for DLSS jitter. Actual injected MOVIE/THUMB code and the host-to-renderer blur/black-side path have executable regression tests. The new menu-side test fails before the fix and passes after it. | | Must 3: checklist and ledger | Restored the original Windows/Linux build box, unticked: no Windows machine; T-ADMJ remains open. Historical and new runs are rows in `docs/tested-configurations.md`, below the day's existing rows. No folded-table substitute. | | Must 4: inaccessible images | Replaced fork raw links with PR attachments. Anonymous browser GETs return 200 with matching SHA-256. Includes a current-main shot of the same Castle Town spot at the resized 2520×1080 size. | | Clamp startup/live shape | Both use the existing 5:4..48:9 clamp, including renderer draw-time refit. Tests cover extreme 40×1600/1600×40 inputs and the picture cap. 2448×1848 lies inside the range and was run in-game. | | Startup picture / Output resolution hints | Hint, README and options-file reference now say that the picture size is set by the window's shape at start, and only the selected output's height is used. Widening the usual 1280×720 start can be softer than Monitor's shape. Both tested Xwayland starts used the requested drawable before sizing; native Wayland's asynchronous fullscreen transition remains unverified. | | Per-draw getenv | `window_fit_active` caches the HOS_ASPECT presence check once. | | Projection table | An observed aspect that is neither 16:9 nor the saved patched aspect retires its entry. Untracked non-16:9 views do not enter the table. A recycled-address regression checks this. | | Window + headless | Without a live drawable, `hos_wide_aspect` falls back to the boot picture's aspect. The actual projection path is covered by a game-free test. No headless gameplay claim. | | #95 ownership | Prepared a proposal for #95's layout to use `hos_wide_aspect()`, keeping `oot_ui_squeeze.h` as draw-time live correction. The proposal is drafted, not an agreement yet. Combined-build coverage remains T-6SJP. | | #57 / #199 | Keep-mask handling is preserved; the corrected margin also reaches menu fades. FOV interaction tests pass. No unrelated #57/#199 changes are folded in. | | Default-path measurement / initial sentinel | New interleaved main/PR runs use Monitor's shape on both sides; run medians are main 56.30/56.50 and PR 56.60/56.30 FPS (medians of medians 56.40/56.45), in the description and ledger. `aspect_started` now starts at -3, distinct from Window (-1), monitor (-2), and shape indices. | Linux acceptance: start 1280×720, open Items, widen to 2520×1080; both blurred and black sides appear. Return to 1280×720: neither leaves bars. Repeat from a 2448×1848 startup. Inspected screenshots show Hor+/Vert+ and centred HUD. Movies and resized thumbnail contents have game-free coverage only; those visual checks, Windows, native Wayland fullscreen, HDR and temporal upscalers remain stated gaps. Checks: full suite once, 516 passed / 4 failed / 16 skipped. Two new source-reading fixture failures were fixed without weakening assertions (`aa44d39`), then the focused 13-check run passed. The remaining `test_linux_tools` and `test_menu_i18n` failures reproduce on untouched `f3fe1763`; no stale translation key is unique to this PR. Both fresh Linux builds and their native shader preparations passed. No separate formatter/typechecker is configured; native fixtures compile, Python is byte-compiled by the suite, and `git diff --check` passes. Please re-review the code and Linux evidence. Windows and #95 integration remain explicit test requests, not claimed passes.
Some checks failed
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Failing after 5s
This pull request doesn't have enough approvals yet. 0 of 1 approvals granted.
Some workflows are waiting to be reviewed.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'feat/window-aspect:simonwjackson-feat/window-aspect'
git switch 'simonwjackson-feat/window-aspect'
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!142
No description provided.