Skip to content

feat(menu): the Aspect ratio row (was Ultrawide), at the monitor's shape by default - #2119

Merged
hckrman101 merged 6 commits into
NotRexed:mainfrom
pmwork-personal:feat/aspect-monitor-default
Nov 11, 2026
Merged

hckrman101 merged 6 commits into
NotRexed:mainfrom
pmwork-personal:feat/aspect-monitor-default

Conversation

@pmwork-personal

@pmwork-personal pmwork-personal commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

A player on an ultrawide, 16:10 or 4:3 screen starts with the game's 16:9 picture until they find the Graphics tab's Ultrawide row, change it and restart. The name also hides that the same row shapes the picture on screens narrower than 16:9. PC games usually follow the monitor's shape from the first start, with 16:9 as a choice.

What changed

  • The Ultrawide row is now Aspect ratio, and Monitor's shape is its default (gpu/vk/vk_menu.h; host/hos_main.c reads an options file with no ultrawide= line as Monitor's shape). 16:9 stays a choice. On a 16:9 screen nothing changes.
  • The saved key stays ultrawide= (ini_key), so a saved ultrawide=0 still means 16:9.
  • Older options files on a screen narrower than 16:9. The game saves every row, so a file from before this PR holds
    ultrawide=0, the old default, whether chosen or not. On a screen narrower than 16:9 (16:10 such as the Steam Deck's
    1280x800, or 4:3), where 16:9 only adds bars above and below, that file's 16:9 moves to Monitor's shape; an ultrawide keeps
    its 16:9 (often a real choice there). The rule is one function, hos_aspect_on in host/hos_wide_size.h: host/hos_main.c
    starts the game with it, and the menu's row follows what the host started with (host->aspect_ratio(), in menu_init
    before restart_init), so the two agree on Linux too, where the menu has no screen size of its own. save() writes
    options_version=6, so a 16:9 chosen after the update stays. Not in this PR: skipping the move in runs with HOS_ASPECT set (Joey's point)
    came after the merge and follows in its own PR.
  • The other value reads 16:9 again instead of fix(menu): the Ultrawide row says Off (16:9) when it is off #2078's "Off (16:9)". fix(menu): the Ultrawide row says Off (16:9) when it is off #2078 added "Off" because "Ultrawide: 16:9" read as if ultrawide were already on; under the name Aspect ratio the value is a shape, and "Aspect ratio: Off (16:9)" would not read well. Happy to keep "Off (16:9)" if you prefer it.
  • recomplauncher.json: the same label and help, and its default is 1, so the launcher does not write 16:9 over the new default.
  • New help text for the row, and for Ultrawide menus, whose text named the old row. The readme's settings table and Output resolution notes, menu_restart.h and the comments that named the row follow (feat(video): support 48:9 triple-monitor picture geometry #2052's wording kept, with the new name).
  • Tests: test_menu_restart.py and test_menu_restart.cpp use the new name; test_audio_output_menu.py and test_stick_dead_zone.py give their option stubs the ultrawide field that ini_key now reads. New test_aspect_default.py: hos_aspect_on over 16:10, 4:3, 5:4, 16:9, 1366x768, 21:9, 32:9, portrait and no screen size, plus the host and menu source checks.

For the release notes (this changes a default for everyone, and only a 21:9 PC ran it):

The Graphics tab's Ultrawide row is now Aspect ratio, and it follows your monitor's shape by default: wider on an ultrawide, narrower on 16:10 (the Steam Deck) or 4:3. On a screen narrower than 16:9, an options file from an earlier version also moves from 16:9 to the monitor's shape; set Aspect ratio to 16:9 to get the old picture back. On an ultrawide, an options file from an earlier version keeps the shape it saved.

Tested

Not tested

Screenshots

From a headless run of this branch (head 2a74ec4) on Windows 11, RTX 5090, with a 5120x2160 (21:9) monitor and no options file, so a first start (HOS_HEADLESS=1, RX_VK_SHOTS at 2560x1080; the save loaded with HOS_INPUT_SCRIPT presses). The log: widescreen: picture 3416x1440, scene 2560x1080, camera ratio 2.3722.

The Graphics tab: Aspect ratio at Monitor's shape, the default, selected with its help

First start on the 21:9 monitor: Kokiri Forest at the monitor's shape, the HUD in the middle 16:9

The menu shot is the middle 16:9 of the frame: behind it the title movie stays 16:9 with black sides. Its build had one extra line so the list scrolls headless (ease_step = 1.0f in menu_record_image), not part of this PR.

The widescreen cap PR (#2120) edited the same readme paragraph; it has merged, and the paragraph is merged with it here.

options_version: #2197 (merged) took 5 for the Esc default, so this writes 6 and moves an older file's 16:9 from files
before 6 (version < 6 in hos_aspect_on and menu_init). #2110 also writes 5 today; whichever of the two lands later
takes the next number. #2197's test_menu_keys.py compiles menu_init's options read, which now also reads
host->aspect_ratio, so its host stub gains that member. Its check of older files expected every version below the one
save() writes to move Insert or F1 to Insert, F1 or Esc, which would move a version 5 file's chosen Insert or F1 again; it
now reads the move's version from menu_init and checks that files from it up to save()'s version keep their choice.

🤖 Generated with Claude Code

pmwork-personal and others added 2 commits November 10, 2026 21:17
…ape by default

A player on an ultrawide, 16:10 or 4:3 screen starts with the game's
16:9 picture on it until they find the Graphics tab's Ultrawide row,
change it and restart. The name also hides that the same row shapes the
picture on screens narrower than 16:9. PC games usually follow the
monitor's shape from the first start, with 16:9 as a choice.

What changed:
- The Ultrawide row is now Aspect ratio, and Monitor's shape is its
  default (gpu/vk/vk_menu.h; host/hos_main.c reads an options file with
  no ultrawide= line as Monitor's shape). 16:9 stays a choice. On a 16:9
  screen nothing changes.
- The row keeps its saved key, ultrawide= (ini_key), so a saved
  ultrawide=0 still means 16:9 and older options files load as before.
- New help text for the row, and for Ultrawide menus, whose text named
  the old row. The readme's settings table and Output resolution notes,
  menu_restart.h and the comments that named the row follow.
- Tests: test_menu_restart.py finds the row by its new name;
  test_audio_output_menu.py and test_stick_dead_zone.py give their
  option stubs the ultrawide field that ini_key now reads.

Tested:
- In game on Windows 11, RTX 5090 (driver 621.14), 5120x2160 (21:9)
  monitor, in one build with the widescreen cap change from
  feat/wide-past-4k: the picture takes the monitor's shape, and Kokiri
  Forest and Castle Town load. This change itself only moves a default
  and renames a row; the code that sizes the picture is unchanged.
- test_menu_restart.py, test_audio_output_menu.py,
  test_stick_dead_zone.py, and the other tests that build ini_key or
  the row (test_framegen_preset, test_menu_env_on,
  test_recomplauncher_json, test_rumble_menu, test_upscaler_rows,
  test_output_resolution, test_widescreen_shapes) pass with llvm-mingw.
- tests/run_all.py on Windows: 326 passed, 26 skipped, 4 failed. The
  4 touch none of these files and fail the same on feat/wide-past-4k,
  which lacks this change: test_recorder_sync (its exit case),
  test_precompile_compute_guard (no windows.h for the MSVC target on
  this PC), nx2/test_nup_read_name (no zstandard module here),
  test_make_game_ngx (the Linux builder test's stub has no
  switching()).

Not tested:
- 16:10, 4:3 and 32:9 screens in game (test_widescreen_shapes.py covers
  their sizes).
- Linux and the Steam Deck: SDL gives the screen's size there too, so a
  Steam Deck (16:10) now starts with a 16:10 picture instead of 16:9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in NotRexed#2052 (48:9), NotRexed#2078 (the off value's label) and NotRexed#2041 (the
V-Sync row). Conflicts resolved with upstream's text kept and the row's
new name on top:
- vk_menu.cpp picture_for and hos_main.c's widescreen comment: NotRexed#2052's
  wording, "Ultrawide" read as the Aspect ratio.
- tx_ultrawide: 16:9 again instead of NotRexed#2078's "Off (16:9)". NotRexed#2078 added
  "Off" because "Ultrawide: 16:9" read as already on; under the name
  Aspect ratio the value is a shape, and "Aspect ratio: Off (16:9)"
  would not read well.
- readme: the settings table and NotRexed#2052's Output resolution notes, with
  the row's new name.
- test_audio_output_menu.py, test_stick_dead_zone.py: the option stubs
  take both NotRexed#2041's vsync and the ultrawide key.
Also follows the rename where the first commit missed it:
recomplauncher.json (label, help, and its default now 1, so the
launcher does not write 16:9 over the new default) and
tests/test_menu_restart.cpp's sample row names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-default

# Conflicts:
#	gpu/vk/vk_menu.cpp
#	readme.md
…reen narrower than 16:9

The game saves every row, so an options file from before this PR holds
ultrawide=0, the 16:9 that was the default, whether chosen or not: the new
default would reach new installs only. On a screen narrower than 16:9
(16:10 such as the Steam Deck's 1280x800, or 4:3) 16:9 only adds bars above
and below, so that file's 16:9 moves to the monitor's shape there; an
ultrawide keeps its 16:9, which may have been chosen.

- host/hos_wide_size.h hos_aspect_on: the rule, shared. A file from
  options_version 5 on keeps whichever it says.
- host/hos_main.c starts the game with it (ultrawide= and options_version=).
- The Insert menu follows what the host started with (host->aspect_ratio,
  in menu_init before restart_init), so the two agree on Linux too, where
  the menu has no screen size of its own. save() writes options_version 5.
- readme.md: the move, in the Output resolution notes.
- New tests/test_aspect_default.py: the rule over 16:10, 4:3, 5:4, 16:9,
  1366x768, 21:9, 32:9, portrait and no screen size, and the source checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@Joey-Magnus Joey-Magnus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good change, and it's well-contained. The host and the menu share one rule (hos_aspect_on), so they can't disagree. The saved key stays ultrawide=, so existing files keep working. The migration only moves the old default where 16:9 can only add bars. host->aspect_ratio was already in MkwVkHost, so no API bump is needed. On Linux, GetSystemMetrics gets SDL's screen size, which display_sdl.c sets before game_main, so the host side works there too. The merge with main is clean. The one red CI job (Windows part 2/2) is tests/test_native_astc_texture_count.py, which fails the same way on other PRs today and isn't related.

should fix: screenshots. The Graphics tab's row is renamed and the default picture changes shape on non-16:9 screens. CONTRIBUTING asks for screenshots when a change shows on screen. A shot of the renamed row, and the first-start picture on the 21:9 screen you tested, would cover it.

minor gpu/vk/vk_menu.cpp:2959-2962 (at the PR's head): menu_init infers "the host started this older file at the monitor's shape" from host->aspect_ratio() alone. That value is also set when HOS_ASPECT is given (host/hos_main.c uses it instead of the file). With an older file holding ultrawide=0, running once with, say, HOS_ASPECT=1.6 flips the row to Monitor's shape, and the next save() writes ultrawide=1 with options_version=5. That stores a choice the player never made. It's rare (a debugging switch), but an easy fix is to skip the move when getenv("HOS_ASPECT") is set, which is what hos_main.c checks.

minor: as you note, #2197 and #2110 also claim options_version=5. Whichever lands second has to bump the number and its version < N checks (here in hos_aspect_on and menu_init). It might be worth a line in the save() comment so the next person sees all three migrations. Steam Deck players with an existing options file will see the picture change to 16:10 after this update without choosing it. That is the intent, but it should go in the changelog/release notes, since the Deck wasn't run.

hckrman101 pushed a commit that referenced this pull request Nov 11, 2026
… 21:9 at the 4K output (#2120)

On a 5120x2160 (21:9) monitor, the 3840x2160 output with Ultrawide on
made a 4416x1864 picture, which the window copy then stretched to the
screen: the monitor never got a native frame, and the Output resolution
row showed 4416x1864. The cap behind it, 3840x2160's pixels, came from
issue 1031, where a 32:9 screen's 7680x2160 picture ran its render
targets' pool out. A 21:9 screen's native picture needs a third more
pixels than 4K, not twice as many, and the pool already grows with the
picture.

**What changed**
- `host/hos_wide_size.h`: a picture wider or narrower than 16:9 takes at
most 5120x2160's pixels (was 3840x2160's). At the 4K output a 21:9
screen gets its native 5120x2160, and the Output resolution row says
"5120x2160 (native)". A 32:9 screen at the 4K output is still capped,
now at 6256x1760 (was 5400x1520), its shape kept.
- No pool change: `host/nn_applet.c` already scales the render-target
pool with the picture's pixels, to 915 MB at 5120x2160
(`test_rt_pool_minimum.py` now checks that case).
- `HOS_WIDE_MAX_PX=<pixels>` sets another cap without a rebuild (8294400
is the old 3840x2160's), in case a GPU needs the smaller picture. Listed
in `docs/development.md`; the readme gives the new cap.

**Relation to #2052**
Merged with current main; #2052's code is kept as merged (the output
limits, `hos_wide_clamp` up to 48:9, the 24-row alignment at 48:9). With
the new cap:
- The 7680x1440 output renders at full size, since it has exactly
5120x2160's pixels, instead of 6528x1224. Its pool stays 900 MB, set by
the output's own size as before; the 5120x2880 output already plays in
that pool with more pixels.
- 48:9 at the 4K output is kept to 7680x1440, exactly 48:9, with a 915
MB pool: the same pixels and pool as 21:9's 5120x2160.
- `test_widescreen_shapes.py` keeps #2052's 6528x1224 cases (the 24-row
alignment and the four-decimal `5.3333`) under
`HOS_WIDE_MAX_PX=8294400`, the old cap, and adds the full-size 7680x1440
and 48:9 at 2160. `test_output_resolution.py`'s 7680x1440 output is full
size; `test_rt_pool_minimum.py` adds 48:9 at the 4K output.

**Tested**
- In game on Windows 11, RTX 5090 (driver 621.14), 5120x2160 (21:9)
monitor, 4K output, picture at the monitor's shape: picture 5120x2160,
pool 915 MB, DLAA at 5120x2160. Kokiri Forest and Castle Town, where
issue 1031's pool ran out, load. About 57 fps at native DLAA (GPU about
12 ms a frame). That run was before the merge with #2052.
- `tests/run_all.py` on Windows (llvm-mingw) after the merge: 339
passed, 30 skipped, 4 failed. The 4 also fail on untouched main on this
PC: `test_recorder_sync` (its exit case),
`test_precompile_compute_guard`, `test_bake_portability`, and
`nx2/test_nup_read_name` (no zstandard here).
`test_widescreen_shapes.py`, `test_output_resolution.py`,
`test_rt_pool_minimum.py` and `gpu/vk/tests/present_rect_test.cpp` pass.

**Not tested**
- 48:9 and 32:9 in game at the new sizes (the full-size 7680x1440
output, 48:9 and 32:9 at the 4K output).
- GPUs with less memory than the 5090 at these sizes, and Linux.

This and the Aspect ratio PR (#2119) edit the same readme paragraph, so
whichever merges second needs a one-line merge.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
hckrman101 and others added 2 commits November 11, 2026 15:39
NotRexed#2197 (Esc opens the menu by default) took options_version 5 on main, so the
Aspect ratio's move of an older file's 16:9 is now version 6: save() writes 6,
menu_init and host/hos_wide_size.h hos_aspect_on move ultrawide=0 from files
before 6, and both moves are kept in menu_init.

tests/test_menu_keys.py (from NotRexed#2197) expected every version below the one
save() writes to move Insert or F1 to Insert, F1 or Esc, which would move a
version 5 file's chosen Insert or F1 again. It now reads the move's version
from menu_init and checks that files from it up to save()'s version keep their
choice; its host stub gains aspect_ratio, which menu_init's Aspect ratio move
asks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@Joey-Magnus Joey-Magnus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • This changes a default for everyone: new players, and Steam Deck or 16:10 players with an older options file, get the monitor's shape (hos_aspect_on). It's only tested on one 21:9 machine, so it belongs in the release notes.
  • The earlier HOS_ASPECT point is still open: the migration at vk_menu.cpp:3121 trusts host->aspect_ratio() even when HOS_ASPECT set it, so a debug run can save ultrawide=1 with options_version=5.
  • Screenshots are still missing.
  • options_version=5 is also used by #2197 and #2110, and this PR conflicts with #2177 over the Ultrawide row. Suggested order: #2197, then #2177, then this rebased.

pmwork-personal added a commit to pmwork-personal/Hookshot-pr that referenced this pull request Nov 11, 2026
…Rexed#2119's Aspect ratio row and a first start at 21:9

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@hckrman101 hckrman101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. The default flips to the monitor's shape, the saved key stays ultrawide=, and the older-file move is one function (hos_aspect_on) that the host and the menu both follow, so they can't disagree. I read the diff and the tests: test_aspect_default.py covers the shapes (16:10, 4:3, 5:4, 1366x768, ultrawides, portrait, no screen size), test_menu_keys.py now reads the old-shortcut version from menu_init so the version 6 bump doesn't move a chosen Insert/F1 again, and the ultrawide stubs in the other two tests match what ini_key now reads. save() writes 6, so a 16:9 chosen after the update stays. All CI jobs are green on this head.

Since Joey-Magnus's earlier reviews: the screenshots he asked for are in the description now (both links load), and the merge with main is clean. Still open, both small and neither a blocker:

  • nit: Joey's HOS_ASPECT point (vk_menu.cpp, the version < 6 block in menu_init) is not fixed. host->aspect_ratio() is also set by HOS_ASPECT and by a custom output wider than 32:9, so a run with one of those on an older ultrawide=0 file on a 16:10 screen can save ultrawide=1 the player did not pick. It's a debug switch and a rare setup, so fine to follow up, but skipping the move when getenv("HOS_ASPECT") is set would close it.
  • options_version=6 and the ordering with #2110 and #2177 are covered in the description; whichever lands later takes the next number. Because the default changes for everyone (Steam Deck and 16:10 players included, untested on a real 16:10 screen), it belongs in the release notes.

The decision to merge is yours (and NotRexed's, as maintainer); nothing here objects.

@hckrman101
hckrman101 merged commit 145eed2 into NotRexed:main Nov 11, 2026
14 checks passed
@pmwork-personal

Copy link
Copy Markdown
Contributor Author

Thanks for the reviews and the merge. The HOS_ASPECT guard landed on the branch right after the merge, so it is its own PR now: #2541. menu_init skips the move when HOS_ASPECT is set, as hos_main.c does, and test_aspect_default.py pins it.

The description now has the screenshots (the renamed row, and a first start on the 21:9 monitor) and a suggested release-notes line for the default change, Steam Deck and 16:10 included.

pmwork-personal added a commit to pmwork-personal/Hookshot-pr that referenced this pull request Nov 11, 2026
…and NotRexed#2496)

NotRexed#2119 renamed the Screen section's Ultrawide row to Aspect ratio and changed
Ultrawide menus' help; NotRexed#2481 added HDR tone mapper after HDR contrast. Both
changed rows inside the block this PR moves into sections: the sections keep
main's definitions word for word (every one of main's 81 rows matches), with
HDR tone mapper last in HDR. The readme's table takes main's Aspect ratio and
HDR tone mapper lines in the section order. test_menu_sections.py also pins
Profile, Aspect ratio, Ultrawide menus and HDR tone mapper in their sections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hckrman101 pushed a commit that referenced this pull request Nov 11, 2026
The Graphics tab is one list of 24 settings, with the window, HDR,
upscaling, frame generation and GPU options mixed
together, and about ten more are on their way in open PRs. This puts its
rows under section titles, so the upscaler, frame
generation or HDR can be found without scrolling the whole list. It is
the first step of a menu organization laid out below
for discussion.

**What changed**
- The Graphics rows in six sections, under heading rows like the Debug
tab's Developer (a gold title, never selected,
  nothing to click):
  - Quality preset (first: it sets rows in every section)
- **Resolution and upscaling:** Output resolution, Profile, Upscaler,
DLSS model, Render resolution, Sharpness
- **Frame generation and latency:** Frame generation, Frame pacing, Game
frame rate cap, NVIDIA Reflex, AMD Anti-Lag 2
- **Screen:** Fullscreen, V-Sync, Window scaling filter, Aspect ratio
(#2119), Ultrawide menus
- **HDR:** HDR, HDR paper white, HDR peak brightness, HDR contrast
(#2342), HDR tone mapper (#2481)
  - **Detail:** Anisotropic filtering
- **GPU and memory:** GPU device, VRAM budget (#1859), Mesh shaders
(Robust buffers is on Debug ▸ Developer since #2042)
- Moves only: every row's definition is unchanged, word for word; no row
is renamed and no value or saved key changes, so
options files carry over, and #1864's tables (keyed by the English text)
still find every row. They need the six
  headings.
- No new drawing or input code: the menu already draws heading rows on
any tab, and the keys, the pad and the mouse step
  over them.
- `readme.md`: a sentence on the sections, and the option table in the
new order. (Ultrawide menus and Mesh shaders have
no line yet; #2105 adds them.) The **Frame generation by** row from
#2118 gets its line in the table here too: main's
readme has none yet. So do #1859's **VRAM budget** (under GPU and
memory) and #2342's **HDR contrast** (under HDR).
- `tests/test_upscaler_rows.py`: its order list follows the picture's
path (output, upscaler, render size, sharpening,
then the frame rows, then the window). New
`tests/test_menu_sections.py`: the Graphics sections and which rows they
hold,
and on every tab no section that `row_shown` could leave empty (a title
over nothing on a GPU without Reflex, say).

**The plan this is step 1 of (for discussion)**

Each tab answers one question; a section groups the rows you set
together; a row that depends on another follows it; vendor
rows sit beside their counterpart and hide where they can't run (no
vendor sections, so nothing is left empty on AMD); rows
to touch only when something is wrong go last; moves only, no renames.

| Tab | Holds | Sections |
|---|---|---|
| **Game** (was Gameplay; first, where the menu opens) | how the game
behaves and reads, and Restart and Quit, as a pause menu | flat; Restart
and Quit last |
| Graphics | how the picture is made, and what it costs | Quality preset
· Resolution and upscaling · Frame generation and latency · Detail · GPU
and memory |
| **Display** (new) | how it gets to your screen, and what is drawn over
it | Screen · HDR · Overlays (FPS counter, Menu size, from Game) |
| Audio | sound out and in | + Microphone (from Game: it picks the input
device) |
| Controls | how you play | Camera · Controller · Buttons (optional) |
| Debug | tools and developer switches | Robust buffers in Developer
(#2042, merged) |

Next PRs, one topic each: (2) a Display tab, taking the Screen and HDR
sections over whole and adding Overlays; (3) the
Gameplay tab titled Game, first in the strip, where the menu opens; (4)
Microphone to Audio; (5) optional, sections on
Controls. If a sixth tab is a no, this PR stands on its own.

Open questions:
1. A sixth tab (Display)?
2. V-Sync on Display (the PC convention) or with frame generation (it
changes with it)?
3. Sharpness with the upscaling rows (where players look for it) or with
the window filter (where it runs)?
4. DLSS 5 (#1590) and #1834's Neural rows as a page opened from a
Graphics row, like Motion, rather than a seventh tab?

<details><summary>Where open PRs' rows would go</summary>

- Resolution and upscaling: Sharpening method (#1979), DLSS 5 page
(#1590), Native 3D resolution (#2017)
- Frame generation and latency: Frame generation by (#2118, merged: in
this section now), Frame generation method (#1834)
- Detail: Shadow resolution (#1892), Screen filter and Filter strength
(#1488)
- GPU and memory: Texture streaming cache (#2017), Refresh memory
(#888); VRAM budget (#1859, merged) is in this section now
- Game (was Gameplay): Game language and Menu language (#1864), Skip
title screen (#1560), Amiibo (#1521)
- Controls: Camera speed (#1945)
- #2042 (merged) moved Robust buffers to Debug ▸ Developer.
- #2105: its readme check would read the heading rows as rows; it needs
to skip `{TAB_..., "...", nullptr, 0, 0, 0, ...,
  true}` headings, or this PR adds that if it lands first.

Whichever PR lands second moves one row on rebase; happy to do it for
anyone.

</details>

**Testing**
- `tests/run_all.py` on Windows 11 (llvm-mingw clang, 8 at once) after
merging main at 2dfe901 (with #2119's Aspect ratio, #2481's HDR tone
mapper, #2118's Frame generation by,
#2342's HDR contrast and #1859's VRAM budget): 422 passed, 33 skipped, 2
failed, both outside this PR:
`test_vram_budget_row.py` (fails the same on main's own sources here)
and `test_portable_builder_startup.py` (its
`dotnet publish` needs packages this PC does not have). A build of the
branch compiles. All 81 of main's rows keep
main's definitions word for word; the only additions are the six
headings.
`test_menu_sections.py` fails on main as it stands (no sections) and
passes here; a layout with a section of Reflex and
  Anti-Lag 2 alone fails it.
- A build of this branch (Windows 11, RTX 5090), headless with scripted
menu keys at Small, Medium and Large: 12 Downs
from the Quality preset land on V-Sync and 13 Ups come back past it to
the last row, so every heading is stepped over
both ways and Up from the first row wraps to the last; the help box
always shows the selected row (at the first head,
when Robust buffers was the last row). Again at this head (25feb16,
after #2042, #1859 and #2119): Up from the Quality preset
wraps to Mesh shaders, the last row now, under VRAM budget (the bottom
screenshot).

**Not tested**: in a window with a pad, the mouse wheel and a click just
under a heading (the headless runs drive
the keys); values kept across a restart (no row's saved key changes);
AMD and Intel (their rows hide through `row_shown` as before; the new
check makes sure no section can end up
empty), Linux.

**Screenshots**

The Graphics tab at 1920x1080, Medium menu size: the top, then scrolled
with the keys to V-Sync and to the last row
(the selection steps over every heading; Up from the first row wraps to
the last row). The bottom one is retaken at this
head (25feb16, a 16:9 picture: `HOS_ASPECT=1.7778`, as on a 16:9
monitor); the others are from the first head (634ef65).

![Graphics tab, top: Quality preset, then Resolution and upscaling and
Frame generation and
latency](https://raw.githubusercontent.com/pmwork-personal/Hookshot-pr/pr-screenshots/menu-sections/graphics-sections-medium-1.webp)

![Graphics tab, middle: Frame generation and latency, then Screen,
V-Sync
selected](https://raw.githubusercontent.com/pmwork-personal/Hookshot-pr/pr-screenshots/menu-sections/graphics-sections-medium-2.webp)

![Graphics tab, bottom: Screen with Aspect ratio, HDR, Detail, then GPU
and memory with GPU device, VRAM budget and Mesh shaders, Mesh shaders
selected](https://raw.githubusercontent.com/pmwork-personal/Hookshot-pr/pr-screenshots/menu-sections/graphics-sections-medium-3c.webp)

Small and Large:

![Graphics tab at Small menu
size](https://raw.githubusercontent.com/pmwork-personal/Hookshot-pr/pr-screenshots/menu-sections/graphics-sections-small.webp)

![Graphics tab at Large menu
size](https://raw.githubusercontent.com/pmwork-personal/Hookshot-pr/pr-screenshots/menu-sections/graphics-sections-large.webp)

_Taken from headless runs of this branch (`HOS_HEADLESS=1`,
`RX_VK_SHOT_MENU=1`, the keys from `RX_VK_MENU_SCRIPT`), RTX
5090. Headless, the list never scrolls on main either:
`menu_record_image` leaves `ease_step` at 0, so the build behind
the scrolled shots had one more line there (`ease_step = 1.0f`, hence
"-dirty" in the first head's build label), not part of
this PR.
The windowed menu eases as before._

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
pmwork-personal added a commit to pmwork-personal/Hookshot-pr that referenced this pull request Nov 11, 2026
…2119, NotRexed#2197, NotRexed#2481, NotRexed#2496)

NotRexed#2177 landed (squashed), so this takes main's sections as they shipped and
keeps this PR's change on top: the Screen, HDR and Overlays rows on the
Display tab. Main's newer rows go with them, word for word: NotRexed#2119's Aspect
ratio and Ultrawide menus text and NotRexed#2481's HDR tone mapper (last in Display's
HDR). Every one of main's 88 rows keeps main's definition; the only changes
are the moved rows' tab and the Overlays heading. draw_options keeps main's
Save my pipeline list line (NotRexed#2447) with the restart line on Display too. The
readme takes main's Menu shortcut text (NotRexed#2197) with six tabs, NotRexed#2177's VRAM
budget line, and main's HDR tone mapper and Field of view lines.
test_menu_sections.py pins Aspect ratio by name and HDR tone mapper in
Display's HDR; test_scene_tonemap_pass.py (NotRexed#2481) finds the tone mapper row on Display.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pmwork-personal added a commit to pmwork-personal/Hookshot-pr that referenced this pull request Nov 11, 2026
NotRexed#2197, NotRexed#2481, NotRexed#2496) into feat/menu-game-tab

Takes NotRexed#2178 as merged with main (cf90c14f) and keeps this PR's change on
top: the Game tab first, where the menu opens. Main's new Gameplay row,
Field of view, goes with the Game rows at the table's top, word for word,
after Game speed. Every one of main's 88 rows keeps main's definition; the
only changes are the moved rows' tab (Display) and the Overlays heading.
The readme takes main's Menu shortcut text (NotRexed#2197) with this PR's tab
sentence, and Field of view among Game's rows. The tab enum, the strip's
titles and the control bridge's names follow the same Game-first order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nullifyshot added a commit to nullifyshot/Hookshot that referenced this pull request Nov 12, 2026
…display modes

The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a
21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers:

- Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes
  (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses.
- Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own
  marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else
  the largest no taller than the screen.
- A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and
  ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before.
- ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old
  saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before.
  HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes.
- The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio
  first, as the arrows keep to one shape.

gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut
out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and
test_menu_setting_walk walks to a size of another shape through the Aspect ratio.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NotRexed pushed a commit that referenced this pull request Nov 12, 2026
…S_ASPECT (#2541)

#2119's review asked for this, and it came after the merge. `menu_init`
takes the shape the host started with as the move of an older options
file's 16:9. `HOS_ASPECT`, a debugging switch `host/hos_main.c` takes
instead of the options file, sets that shape too: one run with
`HOS_ASPECT=1.6` and an older `ultrawide=0` moved the row to Monitor's
shape, and the next save kept it, a choice the player never made.

**What changed**
- `gpu/vk/vk_menu.cpp` `menu_init`: the move skips runs with
`HOS_ASPECT` set, as `hos_main.c` does.
- `tests/test_aspect_default.py` pins the condition. Without it the
check fails (tried by taking the condition out).

**Tested**
- `tests/run_all.py` on Windows (llvm-mingw) on main 2dfe901: 421
passed, 33 skipped, 2 failed, both outside this change:
`test_vram_budget_row` (fails the same on main here: clang stops on the
unused `kVramMinimumOnly`) and `test_portable_builder_startup` (its
`dotnet publish` needs packages this PC does not have).
- Not run in game: it only changes what a run with the debugging switch
saves.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
nullifyshot added a commit to nullifyshot/Hookshot that referenced this pull request Nov 12, 2026
…display modes

The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a
21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers:

- Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes
  (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses.
- Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own
  marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else
  the largest no taller than the screen.
- A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and
  ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before.
- ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old
  saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before.
  HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes.
- The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio
  first, as the arrows keep to one shape.

gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut
out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and
test_menu_setting_walk walks to a size of another shape through the Aspect ratio.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nullifyshot added a commit to nullifyshot/Hookshot that referenced this pull request Nov 12, 2026
…display modes

The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a
21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers:

- Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes
  (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses.
- Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own
  marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else
  the largest no taller than the screen.
- A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and
  ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before.
- ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old
  saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before.
  HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes.
- The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio
  first, as the arrows keep to one shape.

gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut
out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and
test_menu_setting_walk walks to a size of another shape through the Aspect ratio.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nullifyshot added a commit to nullifyshot/Hookshot that referenced this pull request Nov 12, 2026
…display modes

The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a
21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers:

- Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes
  (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses.
- Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own
  marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else
  the largest no taller than the screen.
- A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and
  ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before.
- ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old
  saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before.
  HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes.
- The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio
  first, as the arrows keep to one shape.

gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut
out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and
test_menu_setting_walk walks to a size of another shape through the Aspect ratio.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nullifyshot added a commit to nullifyshot/Hookshot that referenced this pull request Nov 12, 2026
…display modes

The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a
21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers:

- Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes
  (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses.
- Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own
  marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else
  the largest no taller than the screen.
- A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and
  ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before.
- ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old
  saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before.
  HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes.
- The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio
  first, as the arrows keep to one shape.

gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut
out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and
test_menu_setting_walk walks to a size of another shape through the Aspect ratio.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nullifyshot added a commit to nullifyshot/Hookshot that referenced this pull request Nov 12, 2026
…display modes

The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a
21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers:

- Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes
  (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses.
- Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own
  marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else
  the largest no taller than the screen.
- A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and
  ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before.
- ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old
  saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before.
  HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes.
- The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio
  first, as the arrows keep to one shape.

gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut
out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and
test_menu_setting_walk walks to a size of another shape through the Aspect ratio.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nullifyshot added a commit to nullifyshot/Hookshot that referenced this pull request Nov 12, 2026
…display modes

The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a
21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers:

- Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes
  (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses.
- Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own
  marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else
  the largest no taller than the screen.
- A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and
  ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before.
- ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old
  saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before.
  HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes.
- The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio
  first, as the arrows keep to one shape.

gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut
out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and
test_menu_setting_walk walks to a size of another shape through the Aspect ratio.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nullifyshot added a commit to nullifyshot/Hookshot that referenced this pull request Nov 12, 2026
…display modes

The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a
21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers:

- Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes
  (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses.
- Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own
  marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else
  the largest no taller than the screen.
- A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and
  ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before.
- ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old
  saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before.
  HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes.
- The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio
  first, as the arrows keep to one shape.

gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut
out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and
test_menu_setting_walk walks to a size of another shape through the Aspect ratio.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nullifyshot added a commit to nullifyshot/Hookshot that referenced this pull request Nov 12, 2026
…display modes

The Output resolution row offered six fixed sizes, and the Aspect ratio row (NotRexed#2119) only 16:9 or the monitor's shape, so a
21:9, 16:10 or 4:3 size the screen has needed a hand-written output_res. The two rows now list what the screen offers:

- Aspect ratio: 16:9, Monitor's shape (the default, its shape named), then the other shapes of the screen's display modes
  (5:4, 4:3, 16:10, 21:9, 32:9...). It sits right above the Output resolution, whose sizes it chooses.
- Output resolution: the sizes of that shape (each side rounded down to 8, those the game accepts), the screen's own
  marked native, and the game's own 2048x1152 at 16:9. Choosing a shape takes the screen's own size when it has it, else
  the largest no taller than the screen.
- A size more than 1 % off 16:9 sets the picture's shape (cameras, the HUD in the middle 16:9), over HOS_ASPECT and
  ultrawide=, in host/hos_main.c and the menu alike (hos_output_own_shape); ultrawide= shapes a 16:9 size, as before.
- ultrawide= stays 0 or 1 with main's default and its move for older files; another shape is output_res's own. Old
  saved output numbers still load (kOutputs). Where the modes are not known (Linux), the sizes offered before.
  HOS_DISPLAY_MODES=<w>x<h>,... stands in for the screen's modes.
- The control bridge's option_set output_res still reaches every size: one of another shape moves the Aspect ratio
  first, as the arrows keep to one shape.

gpu/vk/display_modes.h holds the rules as plain code; tests/test_display_modes.py checks them. The menu's tests that cut
out the output code follow the new numbering, test_menu_sections has the Aspect ratio above the Output resolution, and
test_menu_setting_walk walks to a size of another shape through the Aspect ratio.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants