feat: an optional item wheel for the R/L item row, Twilight Princess style #2261

Open
tapxgamestudio-coder wants to merge 2 commits from tapxgamestudio-coder/feat/item-wheel into main
tapxgamestudio-coder commented 2026-11-11 12:25:24 +00:00 (Migrated from github.com)

Picking an item from the R/L item row means scrolling through it one item at a time, which gets slow with a full inventory. This adds an optional Twilight Princess style item wheel: hold R or L, point the left stick or the mouse at any item, and let go to equip it.

It is off by default (Insert menu > Gameplay > Item wheel), so nothing changes for anyone who does not turn it on. Off, the game's own row, its sounds and the menus' mouse support all work as before.

Screenshots

The wheel (2560x1152 scene on a 3440x1440 window, cropped to 16:9): every item in a ring over the blurred, dimmed game, the equipped one in gold, its name on a banner, counts as the HUD shows them.

The item wheel

The mouse pointing at an item (ringed in white); a click equips it and closes the wheel.

The item wheel with the mouse

The setting, and the game's own row with the setting off (unchanged).

The Item wheel setting

Item wheel off: the game's own row

What changed

  • host/oot_wheel.c (new): reads the item row's screen (vtable 0x84c0d430) on each update. It is called first from host/oot_menu.c's hos_oot_menu_tick and returns 1 while the wheel has the row, so the menus' mouse leaves that screen alone. The full item list is not in memory, so items are learned from the panes the row loads; slots not loaded yet are filled by briefly turning the hidden row and back (once per inventory change). A pick turns the row there with D-pad taps and keeps R held until it arrives, so letting go early still equips the item pointed at.
  • gpu/vk/vk_menu.cpp: draw_wheel draws the wheel with Dear ImGui under the menu and the FPS counter. It uses one ring, or two staggered rings from 17 items so slots stay large. The item icons are the game's own, put into one texture from wheel/icons.bin. A short opening animation spreads the slots out.
  • gpu/vk/vk_framegen.cpp, vk_draw.cpp: the backdrop is the scene before the HUD (the gameplay upscale's output, from TAAU, the temporal upscalers or FSR 3.1), blurred and dimmed, so the row, its hints and the HUD are gone from behind the wheel. Only with the Item wheel row on: off, the upscale hook returns at once and nothing is made or recorded. Frame interpolation is unchanged (the backdrop is painted onto the game's picture before it reads it).
  • host/nn_audio.c: the row's own open, select and close sounds play as usual. Its scroll tick, which would play once per hidden tap, is silenced while the wheel steers, and the wheel plays the same tick once per new slot instead (from wheel/sounds.bin).
  • host/display.c: while the wheel is open the mouse is released and its pointer centred; hovering points, and a click picks the item and closes the wheel.
  • If the game closes the row itself (a text box, damage, B) while the wheel is still turning it, the R it keeps held and the D-pad taps stop at once: the row counts as closed once the game's other screens have updated for 60 ms without it.
  • nx2/prompt_assets.py, nx2/wheel_sounds.py: the build makes wheel/icons.bin (prompt_assets.py --wheel-icons, with the prompts' decoders, so Pillow and texture2ddecoder stay in prompt_assets.py; a run of its own at every build, about 3 s, outside the prompts' reuse from #2698, which covers only the prompts' inputs and outputs) and wheel/sounds.bin (standard library and zstandard) from the player's RomFS. As with the prompts, nothing made from the game leaves the PC. Without the files the wheel shows item names and its tick is silent; a failure does not stop the build.
  • Docs: the Item wheel row in the readme's options; HOS_ITEM_WHEEL, HOS_WHEEL_TRACE and RX_VK_WHEEL_BACKDROP in docs/development.md.
  • New option id 60 (HOS_OPT_ITEM_WHEEL), MKWVK_API_VERSION 22 (MkwVkHost.wheel, after upstream's screen_size at 20 and #2760's 21; it moves up again if another PR that bumps it lands first), and window message WM_APP + 20 for the wheel's mouse.

Testing

Windows 11, NVIDIA GeForce RTX 3080 (driver 621.01), 3440x1440 window, frame generation x2. In game:

  • Inventories: the Deku Tree with a child save (14 items) and with a save holding few items (2 and 3 items). Adult Link (16 items) through the age switch.
  • Item changes: adding and removing items mid-session, and the Hookshot to Longshot change (same list length).
  • Picking: the stick, mouse hover and click, R or L held.
  • While doing other things: ZR held, swimming, the pause menu (R still changes its tabs, no wheel).
  • The setting: switched on and off live in the menu. Off, with frame generation x2: the game's row, and no wheel lines in the log (nothing made or recorded).
  • The game closing the row mid-pick: B pressed while the wheel was still turning the row with R kept held: the trace shows the wheel letting go of R within one tap, and the row doesn't reopen.
  • Sounds: checked in a recording of the audio output: open, one tick per pick, select and close.

Game-free checks: tests/run_all.py, with the Vulkan and SPIR-V headers found (RX_CPM), fails the same 6 tests on this PC as clean main does (tools missing here), and passes the rest, test_framegen_resources and test_present_source_crop included. New tests/test_wheel_assets.py checks that the build makes both files in the layouts their readers expect, and that every sound the host plays is one the build writes. test_fps_overlay_order.py now covers the wheel's place among the overlays.

Not tested:

  • A text box or cutscene opening during the wheel (couldn't trigger one; B closing the row is the case tested above).
  • Epona, and Linux (the wheel has no mouse support there yet; the stick works).
  • GPUs where the gameplay network does not translate (temporal_rejected_network): there the backdrop falls back to the game's picture with its row.
  • A game stall while the wheel holds R should still count as open (no screen updates in a stall), but I couldn't reproduce one to test.

Assisted-by: Claude Code (Claude Opus 5.5)

🤖 Generated with Claude Code

Picking an item from the R/L item row means scrolling through it one item at a time, which gets slow with a full inventory. This adds an optional Twilight Princess style item wheel: hold R or L, point the left stick or the mouse at any item, and let go to equip it. It is **off by default** (Insert menu > Gameplay > **Item wheel**), so nothing changes for anyone who does not turn it on. Off, the game's own row, its sounds and the menus' mouse support all work as before. ## Screenshots The wheel (2560x1152 scene on a 3440x1440 window, cropped to 16:9): every item in a ring over the blurred, dimmed game, the equipped one in gold, its name on a banner, counts as the HUD shows them. ![The item wheel](https://raw.githubusercontent.com/tapxgamestudio-coder/Hookshot/17c14fe9990250d71df5be405f9f7c99d383109f/item-wheel/item-wheel-open.png) The mouse pointing at an item (ringed in white); a click equips it and closes the wheel. ![The item wheel with the mouse](https://raw.githubusercontent.com/tapxgamestudio-coder/Hookshot/17c14fe9990250d71df5be405f9f7c99d383109f/item-wheel/item-wheel-mouse.png) The setting, and the game's own row with the setting off (unchanged). ![The Item wheel setting](https://raw.githubusercontent.com/tapxgamestudio-coder/Hookshot/17c14fe9990250d71df5be405f9f7c99d383109f/item-wheel/item-wheel-setting.png) ![Item wheel off: the game's own row](https://raw.githubusercontent.com/tapxgamestudio-coder/Hookshot/17c14fe9990250d71df5be405f9f7c99d383109f/item-wheel/item-wheel-off-game-row.png) ## What changed - **`host/oot_wheel.c`** (new): reads the item row's screen (vtable `0x84c0d430`) on each update. It is called first from `host/oot_menu.c`'s `hos_oot_menu_tick` and returns 1 while the wheel has the row, so the menus' mouse leaves that screen alone. The full item list is not in memory, so items are learned from the panes the row loads; slots not loaded yet are filled by briefly turning the hidden row and back (once per inventory change). A pick turns the row there with D-pad taps and keeps R held until it arrives, so letting go early still equips the item pointed at. - **`gpu/vk/vk_menu.cpp`**: `draw_wheel` draws the wheel with Dear ImGui under the menu and the FPS counter. It uses one ring, or two staggered rings from 17 items so slots stay large. The item icons are the game's own, put into one texture from `wheel/icons.bin`. A short opening animation spreads the slots out. - **`gpu/vk/vk_framegen.cpp`, `vk_draw.cpp`**: the backdrop is the scene before the HUD (the gameplay upscale's output, from TAAU, the temporal upscalers or FSR 3.1), blurred and dimmed, so the row, its hints and the HUD are gone from behind the wheel. Only with the Item wheel row on: off, the upscale hook returns at once and nothing is made or recorded. Frame interpolation is unchanged (the backdrop is painted onto the game's picture before it reads it). - **`host/nn_audio.c`**: the row's own open, select and close sounds play as usual. Its scroll tick, which would play once per hidden tap, is silenced while the wheel steers, and the wheel plays the same tick once per new slot instead (from `wheel/sounds.bin`). - **`host/display.c`**: while the wheel is open the mouse is released and its pointer centred; hovering points, and a click picks the item and closes the wheel. - If the game closes the row itself (a text box, damage, B) while the wheel is still turning it, the R it keeps held and the D-pad taps stop at once: the row counts as closed once the game's other screens have updated for 60 ms without it. - **`nx2/prompt_assets.py`, `nx2/wheel_sounds.py`**: the build makes `wheel/icons.bin` (`prompt_assets.py --wheel-icons`, with the prompts' decoders, so Pillow and texture2ddecoder stay in `prompt_assets.py`; a run of its own at every build, about 3 s, outside the prompts' reuse from #2698, which covers only the prompts' inputs and outputs) and `wheel/sounds.bin` (standard library and zstandard) from the player's RomFS. As with the prompts, nothing made from the game leaves the PC. Without the files the wheel shows item names and its tick is silent; a failure does not stop the build. - **Docs**: the Item wheel row in the readme's options; `HOS_ITEM_WHEEL`, `HOS_WHEEL_TRACE` and `RX_VK_WHEEL_BACKDROP` in `docs/development.md`. - New option id 60 (`HOS_OPT_ITEM_WHEEL`), `MKWVK_API_VERSION` 22 (`MkwVkHost.wheel`, after upstream's `screen_size` at 20 and #2760's 21; it moves up again if another PR that bumps it lands first), and window message `WM_APP + 20` for the wheel's mouse. ## Testing Windows 11, NVIDIA GeForce RTX 3080 (driver 621.01), 3440x1440 window, frame generation x2. In game: - **Inventories:** the Deku Tree with a child save (14 items) and with a save holding few items (2 and 3 items). Adult Link (16 items) through the age switch. - **Item changes:** adding and removing items mid-session, and the Hookshot to Longshot change (same list length). - **Picking:** the stick, mouse hover and click, R or L held. - **While doing other things:** ZR held, swimming, the pause menu (R still changes its tabs, no wheel). - **The setting:** switched on and off live in the menu. Off, with frame generation x2: the game's row, and no wheel lines in the log (nothing made or recorded). - **The game closing the row mid-pick:** B pressed while the wheel was still turning the row with R kept held: the trace shows the wheel letting go of R within one tap, and the row doesn't reopen. - **Sounds:** checked in a recording of the audio output: open, one tick per pick, select and close. Game-free checks: `tests/run_all.py`, with the Vulkan and SPIR-V headers found (`RX_CPM`), fails the same 6 tests on this PC as clean `main` does (tools missing here), and passes the rest, `test_framegen_resources` and `test_present_source_crop` included. New `tests/test_wheel_assets.py` checks that the build makes both files in the layouts their readers expect, and that every sound the host plays is one the build writes. `test_fps_overlay_order.py` now covers the wheel's place among the overlays. Not tested: - A text box or cutscene opening during the wheel (couldn't trigger one; B closing the row is the case tested above). - Epona, and Linux (the wheel has no mouse support there yet; the stick works). - GPUs where the gameplay network does not translate (`temporal_rejected_network`): there the backdrop falls back to the game's picture with its row. - A game stall while the wheel holds R should still count as open (no screen updates in a stall), but I couldn't reproduce one to test. Assisted-by: Claude Code (Claude Opus 5.5) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Joey-Magnus (Migrated from github.com) requested changes 2026-11-11 20:07:31 +00:00
Joey-Magnus (Migrated from github.com) left a comment

The wheel itself looks great in the screenshots, and the host side is careful: the seqlock publish/read pair is correct, the renderer only ever reads the host's snapshot (never guest memory), and a stale row closes the wheel from the renderer's side. I focused on the parts that affect players who never turn the wheel on, on the repo rules, and on the input injection. I ran the touched checks on the PR merged with current main (no textual conflicts). I didn't review nn_audio.c's sound path or the ImGui drawing in detail.

blocking gpu/vk/vk_framegen.cpp:1131-1135, 1200: the "off by default" isn't true for the renderer. wheel_state() returns host->wheel(), which is never null (hos_wheel_state always returns &wheel_state), so framegen_on() || wheel_state() is always true. On every frame, for every player, whatever the Item wheel row says, framegen_hudless now builds the four HUD pipelines once, makes two scene-sized images, and records a vkCmdCopyImage of the gameplay upscale's output with two full_barriers. That's paid on every hardware tier, including the Deck and integrated GPUs, where a full barrier mid-replay is not cheap. Please gate it on the option (and the wheel being open), for example wheel_open() or opts.item_wheel, so the default path records nothing new.

blocking gpu/vk/vk_framegen.cpp:987, 1044 (and the hud_put calls): with frame generation on, the interpolated frames are now made from fg.hudless and the HUD is pasted back from the nearest real frame, for everyone, not just wheel users. The FSR 3.1 frame interpolation input (in.color) changes too. That's a separate feature ("HUD-aware frame generation") riding in an opt-in PR. It relies on a heuristic (scene vs. picture difference after pow(1/2.2), full weight at 1/8) that has only been seen on one RTX 3080, and it's on by default with RX_VK_FRAMEGEN_HUD=0 as the way out. Please split it into its own PR with before/after captures on AMD (FSR 3.1 FI) and NVIDIA, and keep this PR to the wheel's backdrop.

blocking: tests/test_framegen_resources.py fails at this head and merged with main (FAIL line 416: live.size() == 53 && barriers == 12 && framegen_on()), and passes on main. It compiles the real framegen constructors, and ensure_size now makes hudless/masks too. Your local run probably skipped it, since it needs Vulkan headers. Please update the check's expected counts in this PR, as AGENTS.md asks, rather than loosening it. CI has only run the title check here so far.

blocking nx2/wheel_icons.py:27, nx2/make_game.py:1890: CONTRIBUTING limits Pillow, texture2ddecoder, astc-encoder-py and fontTools to nx2/prompt_assets.py and nx2/prompt_draw.py. wheel_icons.py imports PIL and decodes through prompt_assets.decode (texture2ddecoder/astc). This needs a maintainer's explicit OK and a CONTRIBUTING update, or the icon decoding moved behind prompt_assets.py (for example a --wheel-icons output of the prompts step) so the dependency stays where it is allowed.

should fix host/oot_wheel.c:454, 569: R is kept held for the game for up to 4 s after the player lets go (the comment says 1.5 s), and while R is kept, a row that has stopped updating counts as open for ROW_STALL_NS (500 ms). If the game closes the row itself during that time (a text box, a cutscene, damage), the synthetic R and the D-pad taps reach gameplay for up to half a second. Your own comment notes that LEFT opens the pause menu there. You list this case as untested. Could you either drop the synthetic input as soon as the row's screen is gone, rather than after the stall window, or test it (for example opening the wheel as a Navi prompt fires)? Please also fix the comment.

should fix (docs): the new Gameplay row "Item wheel" needs a line in the readme's option table (#2105 adds a check that will fail without it). The new switches (HOS_ITEM_WHEEL, HOS_WHEEL_BACKDROP, HOS_WHEEL_SURVEY, HOS_WHEEL_TRACE, HOS_WHEEL_PROBE, RX_VK_FRAMEGEN_HUD) should go into docs/development.md's switch tables, as CONTRIBUTING asks.

coordination: MKWVK_API_VERSION 18 is also what #2317 uses for MkwVkHost.data_dir, so whichever lands second needs 19 and its field after the other's. The "(wheel mod)" tags in comments read like a fork's labels. The repo marks its own additions "(hookshot)".

The wheel itself looks great in the screenshots, and the host side is careful: the seqlock publish/read pair is correct, the renderer only ever reads the host's snapshot (never guest memory), and a stale row closes the wheel from the renderer's side. I focused on the parts that affect players who never turn the wheel on, on the repo rules, and on the input injection. I ran the touched checks on the PR merged with current main (no textual conflicts). I didn't review `nn_audio.c`'s sound path or the ImGui drawing in detail. **blocking** `gpu/vk/vk_framegen.cpp:1131-1135, 1200`: the "off by default" isn't true for the renderer. `wheel_state()` returns `host->wheel()`, which is never null (`hos_wheel_state` always returns `&wheel_state`), so `framegen_on() || wheel_state()` is always true. On every frame, for every player, whatever the Item wheel row says, `framegen_hudless` now builds the four HUD pipelines once, makes two scene-sized images, and records a `vkCmdCopyImage` of the gameplay upscale's output with two `full_barrier`s. That's paid on every hardware tier, including the Deck and integrated GPUs, where a full barrier mid-replay is not cheap. Please gate it on the option (and the wheel being open), for example `wheel_open()` or `opts.item_wheel`, so the default path records nothing new. **blocking** `gpu/vk/vk_framegen.cpp:987, 1044` (and the `hud_put` calls): with frame generation on, the interpolated frames are now made from `fg.hudless` and the HUD is pasted back from the nearest real frame, for everyone, not just wheel users. The FSR 3.1 frame interpolation input (`in.color`) changes too. That's a separate feature ("HUD-aware frame generation") riding in an opt-in PR. It relies on a heuristic (scene vs. picture difference after `pow(1/2.2)`, full weight at 1/8) that has only been seen on one RTX 3080, and it's on by default with `RX_VK_FRAMEGEN_HUD=0` as the way out. Please split it into its own PR with before/after captures on AMD (FSR 3.1 FI) and NVIDIA, and keep this PR to the wheel's backdrop. **blocking**: `tests/test_framegen_resources.py` fails at this head and merged with main (`FAIL line 416: live.size() == 53 && barriers == 12 && framegen_on()`), and passes on main. It compiles the real framegen constructors, and `ensure_size` now makes `hudless`/`masks` too. Your local run probably skipped it, since it needs Vulkan headers. Please update the check's expected counts in this PR, as AGENTS.md asks, rather than loosening it. CI has only run the title check here so far. **blocking** `nx2/wheel_icons.py:27`, `nx2/make_game.py:1890`: CONTRIBUTING limits Pillow, texture2ddecoder, astc-encoder-py and fontTools to `nx2/prompt_assets.py` and `nx2/prompt_draw.py`. `wheel_icons.py` imports `PIL` and decodes through `prompt_assets.decode` (texture2ddecoder/astc). This needs a maintainer's explicit OK and a CONTRIBUTING update, or the icon decoding moved behind `prompt_assets.py` (for example a `--wheel-icons` output of the prompts step) so the dependency stays where it is allowed. **should fix** `host/oot_wheel.c:454, 569`: R is kept held for the game for up to 4 s after the player lets go (the comment says 1.5 s), and while R is kept, a row that has stopped updating counts as open for `ROW_STALL_NS` (500 ms). If the game closes the row itself during that time (a text box, a cutscene, damage), the synthetic R and the D-pad taps reach gameplay for up to half a second. Your own comment notes that LEFT opens the pause menu there. You list this case as untested. Could you either drop the synthetic input as soon as the row's screen is gone, rather than after the stall window, or test it (for example opening the wheel as a Navi prompt fires)? Please also fix the comment. **should fix** (docs): the new Gameplay row "Item wheel" needs a line in the readme's option table (#2105 adds a check that will fail without it). The new switches (`HOS_ITEM_WHEEL`, `HOS_WHEEL_BACKDROP`, `HOS_WHEEL_SURVEY`, `HOS_WHEEL_TRACE`, `HOS_WHEEL_PROBE`, `RX_VK_FRAMEGEN_HUD`) should go into `docs/development.md`'s switch tables, as CONTRIBUTING asks. **coordination**: `MKWVK_API_VERSION` 18 is also what #2317 uses for `MkwVkHost.data_dir`, so whichever lands second needs 19 and its field after the other's. The "(wheel mod)" tags in comments read like a fork's labels. The repo marks its own additions "(hookshot)".
Joey-Magnus (Migrated from github.com) requested changes 2026-11-11 21:23:55 +00:00
Joey-Magnus (Migrated from github.com) left a comment

Confirmed blockers:

  • wheel_state() (gpu/vk/vk_framegen.cpp:1131-1136) returns host->wheel(), which is never null, so framegen_on() || wheel_state() at :1200 is always true. Every player pays for the HUD pipelines, two scene-sized images, a copy and two full barriers, including Steam Deck and iGPUs.
  • tests/test_framegen_resources.py fails at the head (line 416: live.size() == 53 && barriers == 12) and passes on main.
  • HUD-aware frame generation is a second feature, on by default for everyone with frame generation (RX_VK_FRAMEGEN_HUD=0 turns it off). It changes FSR 3.1's input and was only seen on one RTX 3080. Please split it into its own PR.
  • nx2/wheel_icons.py:27 imports Pillow outside the two files CONTRIBUTING allows.
  • MKWVK_API_VERSION 18 is also claimed by #2317, #2459, #2478 and #2479.
  • Comments say "(wheel mod)" where the repo uses "(hookshot)".
Confirmed blockers: - `wheel_state()` (`gpu/vk/vk_framegen.cpp:1131-1136`) returns `host->wheel()`, which is never null, so `framegen_on() || wheel_state()` at `:1200` is always true. Every player pays for the HUD pipelines, two scene-sized images, a copy and two full barriers, including Steam Deck and iGPUs. - `tests/test_framegen_resources.py` fails at the head (`line 416: live.size() == 53 && barriers == 12`) and passes on main. - HUD-aware frame generation is a second feature, on by default for everyone with frame generation (`RX_VK_FRAMEGEN_HUD=0` turns it off). It changes FSR 3.1's input and was only seen on one RTX 3080. Please split it into its own PR. - `nx2/wheel_icons.py:27` imports Pillow outside the two files CONTRIBUTING allows. - `MKWVK_API_VERSION` 18 is also claimed by #2317, #2459, #2478 and #2479. - Comments say "(wheel mod)" where the repo uses "(hookshot)".
hckrman101 commented 2026-11-11 22:17:23 +00:00 (Migrated from github.com)

This now conflicts with main in gpu/vk/vk_api.h. Could you merge main into the branch (or rebase) when you get a chance? I'll take another look once it's up to date.

This now conflicts with main in `gpu/vk/vk_api.h`. Could you merge main into the branch (or rebase) when you get a chance? I'll take another look once it's up to date.
tapxgamestudio-coder commented 2026-11-11 23:35:20 +00:00 (Migrated from github.com)

Thanks for the careful review. Every point was right. All of it is addressed in the new head.

Your second pass looks like it read the earlier head (e2dffe5): wheel_state(), the HUD-aware passes, nx2/wheel_icons.py and the "(wheel mod)" labels were already gone in bbfa193, and are still gone now. The one new point, the API version, was real: #1990 took 18 on main today, so the branch is rebased onto current main and the wheel is 19 now. Point by point:

Blocking

  • The cost with the wheel off (vk_framegen.cpp): you were right, wheel_state() was never null, so every player paid for the copy and the barriers. Now framegen_wheel_on() gates everything on the Item wheel row (or HOS_ITEM_WHEEL): off, the gameplay upscale's hook returns at once, and no pipeline, image or command is made. The copy has to run every frame while the row is on, not only while the wheel is open: the game stops drawing its scene while its item row is shown, so the frame before the row opens is the only clean scene there is. I checked this in game: gating on wheel_open() alone left the game's row and HUD behind the wheel. With the row off, a run with frame generation x2 logs no wheel lines at all.
  • HUD-aware frame generation: removed. Interpolation, the FSR 3.1 FI input and the made frames are upstream's again (no hudless/masks images, no split/put passes, no RX_VK_FRAMEGEN_HUD). The backdrop is painted onto the game's picture before frame interpolation reads it, so the wheel doesn't need it. vk_framegen.cpp's diff against main is now only the backdrop (two passes: blur, then blur and dim).
  • test_framegen_resources: it was skipped here for lack of Vulkan headers. With them (RX_CPM pointed at the CPM cache), it passes at this head, with the counts unchanged, because ensure_size no longer makes the extra images.
  • Pillow and texture2ddecoder: nx2/wheel_icons.py is gone. The icons are now write_wheel_icons in nx2/prompt_assets.py, made by the prompts step with --wheel-icons <game>/wheel/icons.bin. The output is byte-identical to before. nx2/wheel_sounds.py uses only the standard library and zstandard (through prompt_assets.read_zs). tests/test_wheel_assets.py now fails if it imports one of the four prompt-only packages, or if wheel_icons.py comes back.

Should fix

  • Synthetic input after the game closes the row (host/oot_wheel.c): a stall and a close looked the same before. Now the menu tick also records when any other screen updates (other_tick_ns). If other screens go on updating for 60 ms without the row, the game has closed it (in a stall, nothing updates), so the wheel closes and R and the taps stop at once instead of after ROW_STALL_NS. Tested in game: pick an item 8 slots away, let go of R (R kept held), then press B in the middle of the taps. The trace shows the row closes (the game went on without it; R kept held: let go now) one tap after B, and the row doesn't reopen. Early-release picks still land (Slingshot to Nayru's Love with R let go after 0.7 s), and the trace showed no false closes in these runs. A game stall should still count as open, since no screen updates then, but I couldn't reproduce the 157 ms stall to test it. The comment now says 4 s and why.
  • Docs: the Item wheel row is in the readme's option table, with a line under the options. docs/development.md has HOS_ITEM_WHEEL, HOS_WHEEL_TRACE and RX_VK_WHEEL_BACKDROP (renamed from HOS_WHEEL_BACKDROP, since the renderer reads it). I removed the reverse-engineering probe and its switches (HOS_WHEEL_PROBE, HOS_WHEEL_SCAN) and the survey's HOS_WHEEL_SURVEY, so there are fewer switches to document.

Coordination

  • API version: 19 now, since #1990 took 18 (its change added no MkwVkHost field, so wheel still comes right after prompts_live). If #2317, #2459, #2478 or #2479 lands first, I'll rebase again, take the next number and put wheel after their fields.
  • Labels: every "(wheel mod)" label is now "(hookshot)".

Tested on Windows 11, RTX 3080, frame generation x2, with the row off, the row on, early-release picks and B mid-pick, then again on the wheel after rebasing onto current main (fce5345). tests/run_all.py with the Vulkan and SPIR-V headers available: the same 4 failures as clean main on this PC (tools missing here), everything else passes. AMD, Intel and Linux are still untested.

Thanks for the careful review. Every point was right. All of it is addressed in the new head. Your second pass looks like it read the earlier head (e2dffe5): `wheel_state()`, the HUD-aware passes, `nx2/wheel_icons.py` and the "(wheel mod)" labels were already gone in bbfa193, and are still gone now. The one new point, the API version, was real: #1990 took 18 on main today, so the branch is rebased onto current main and the wheel is 19 now. Point by point: **Blocking** - **The cost with the wheel off** (`vk_framegen.cpp`): you were right, `wheel_state()` was never null, so every player paid for the copy and the barriers. Now `framegen_wheel_on()` gates everything on the Item wheel row (or `HOS_ITEM_WHEEL`): off, the gameplay upscale's hook returns at once, and no pipeline, image or command is made. The copy has to run every frame while the row is on, not only while the wheel is open: the game stops drawing its scene while its item row is shown, so the frame before the row opens is the only clean scene there is. I checked this in game: gating on `wheel_open()` alone left the game's row and HUD behind the wheel. With the row off, a run with frame generation x2 logs no wheel lines at all. - **HUD-aware frame generation**: removed. Interpolation, the FSR 3.1 FI input and the made frames are upstream's again (no `hudless`/`masks` images, no split/put passes, no `RX_VK_FRAMEGEN_HUD`). The backdrop is painted onto the game's picture before frame interpolation reads it, so the wheel doesn't need it. `vk_framegen.cpp`'s diff against main is now only the backdrop (two passes: blur, then blur and dim). - **`test_framegen_resources`**: it was skipped here for lack of Vulkan headers. With them (`RX_CPM` pointed at the CPM cache), it passes at this head, with the counts unchanged, because `ensure_size` no longer makes the extra images. - **Pillow and texture2ddecoder**: `nx2/wheel_icons.py` is gone. The icons are now `write_wheel_icons` in `nx2/prompt_assets.py`, made by the prompts step with `--wheel-icons <game>/wheel/icons.bin`. The output is byte-identical to before. `nx2/wheel_sounds.py` uses only the standard library and zstandard (through `prompt_assets.read_zs`). `tests/test_wheel_assets.py` now fails if it imports one of the four prompt-only packages, or if `wheel_icons.py` comes back. **Should fix** - **Synthetic input after the game closes the row** (`host/oot_wheel.c`): a stall and a close looked the same before. Now the menu tick also records when any other screen updates (`other_tick_ns`). If other screens go on updating for 60 ms without the row, the game has closed it (in a stall, nothing updates), so the wheel closes and R and the taps stop at once instead of after `ROW_STALL_NS`. Tested in game: pick an item 8 slots away, let go of R (R kept held), then press B in the middle of the taps. The trace shows `the row closes (the game went on without it; R kept held: let go now)` one tap after B, and the row doesn't reopen. Early-release picks still land (Slingshot to Nayru's Love with R let go after 0.7 s), and the trace showed no false closes in these runs. A game stall should still count as open, since no screen updates then, but I couldn't reproduce the 157 ms stall to test it. The comment now says 4 s and why. - **Docs**: the Item wheel row is in the readme's option table, with a line under the options. `docs/development.md` has `HOS_ITEM_WHEEL`, `HOS_WHEEL_TRACE` and `RX_VK_WHEEL_BACKDROP` (renamed from `HOS_WHEEL_BACKDROP`, since the renderer reads it). I removed the reverse-engineering probe and its switches (`HOS_WHEEL_PROBE`, `HOS_WHEEL_SCAN`) and the survey's `HOS_WHEEL_SURVEY`, so there are fewer switches to document. **Coordination** - **API version**: 19 now, since #1990 took 18 (its change added no `MkwVkHost` field, so `wheel` still comes right after `prompts_live`). If #2317, #2459, #2478 or #2479 lands first, I'll rebase again, take the next number and put `wheel` after their fields. - **Labels**: every "(wheel mod)" label is now "(hookshot)". Tested on Windows 11, RTX 3080, frame generation x2, with the row off, the row on, early-release picks and B mid-pick, then again on the wheel after rebasing onto current main (fce5345). `tests/run_all.py` with the Vulkan and SPIR-V headers available: the same 4 failures as clean main on this PC (tools missing here), everything else passes. AMD, Intel and Linux are still untested.
Owner

@tapxgamestudio-coder commented on GitHub · 2026-11-12 06:35 UTC · original

Rebased onto current main. The wheel is API version 20 now (#1930 took 19), and the wheel's sounds moved out of the source slices the new audio tests compile. Ready for another look.

> [@tapxgamestudio-coder](https://github.com/tapxgamestudio-coder) commented on GitHub · 2026-11-12 06:35 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2261#issuecomment-6816599853) Rebased onto current main. The wheel is API version 20 now (#1930 took 19), and the wheel's sounds moved out of the source slices the new audio tests compile. Ready for another look. <!-- github-sync:c:6816599853 -->
Owner

@Joey-Magnus requested changes on GitHub · 2026-11-12 14:32 UTC · original

Thanks, the earlier blockers are fixed. With the row off, nothing is recorded or made (framegen_wheel_on). HUD-aware frame generation is gone. The icons are made in prompt_assets.py --wheel-icons. The readme and switch-table lines are in, and the labels say "(hookshot)". I merged it into current main and ran tests/run_all.py with the Vulkan headers: 469 passed. The only failures were test_lift_cache, which fails on main here, and a test_recorder_sync load timeout, which passes alone. test_framegen_resources and #2105's readme check pass too.

One bug: gpu/vk/vk_framegen.cpp:1038-1039 reads RX_VK_WHEEL_BACKDROP and HOS_ITEM_WHEEL with env_int, while host/oot_wheel.c:473 reads HOS_ITEM_WHEEL with env_on. With HOS_ITEM_WHEEL=on, the host turns the wheel on but the renderer follows the menu row and draws no backdrop. With =off and the row on, the wheel is off but the scene is still copied every frame. RX_VK_WHEEL_BACKDROP=off doesn't turn the backdrop off either. Please use env_on/env_value_off on both sides.

API version 20 is also claimed by #2317, #2566, #2594, #2600, #2604 and #2760, so whichever lands second has to bump it.

> [@Joey-Magnus](https://github.com/Joey-Magnus) requested changes on GitHub · 2026-11-12 14:32 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2261#pullrequestreview-5876207253) Thanks, the earlier blockers are fixed. With the row off, nothing is recorded or made (`framegen_wheel_on`). HUD-aware frame generation is gone. The icons are made in `prompt_assets.py --wheel-icons`. The readme and switch-table lines are in, and the labels say "(hookshot)". I merged it into current main and ran `tests/run_all.py` with the Vulkan headers: 469 passed. The only failures were `test_lift_cache`, which fails on main here, and a `test_recorder_sync` load timeout, which passes alone. `test_framegen_resources` and #2105's readme check pass too. One bug: `gpu/vk/vk_framegen.cpp:1038-1039` reads `RX_VK_WHEEL_BACKDROP` and `HOS_ITEM_WHEEL` with `env_int`, while `host/oot_wheel.c:473` reads `HOS_ITEM_WHEEL` with `env_on`. With `HOS_ITEM_WHEEL=on`, the host turns the wheel on but the renderer follows the menu row and draws no backdrop. With `=off` and the row on, the wheel is off but the scene is still copied every frame. `RX_VK_WHEEL_BACKDROP=off` doesn't turn the backdrop off either. Please use `env_on`/`env_value_off` on both sides. API version 20 is also claimed by #2317, #2566, #2594, #2600, #2604 and #2760, so whichever lands second has to bump it. <!-- github-sync:r:5876207253 -->
Owner

@tapxgamestudio-coder commented on GitHub · 2026-11-12 15:42 UTC · original

Thanks. Fixed in 2fb1ed96: the renderer reads HOS_ITEM_WHEEL and RX_VK_WHEEL_BACKDROP with env_value_off now, the same as the host, and test_wheel_assets.py fails if the two readings differ or a wheel switch goes back to env_int. Checked in game with HOS_ITEM_WHEEL=on (backdrop drawn) and RX_VK_WHEEL_BACKDROP=off (none), and both readings compiled against hookshot_env.h agree for unset, 0, 1, on, off, no, false, yes, true and 2. On the API version: understood, whichever lands second bumps it.

> [@tapxgamestudio-coder](https://github.com/tapxgamestudio-coder) commented on GitHub · 2026-11-12 15:42 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2261#issuecomment-6828229191) Thanks. Fixed in 2fb1ed96: the renderer reads `HOS_ITEM_WHEEL` and `RX_VK_WHEEL_BACKDROP` with `env_value_off` now, the same as the host, and `test_wheel_assets.py` fails if the two readings differ or a wheel switch goes back to `env_int`. Checked in game with `HOS_ITEM_WHEEL=on` (backdrop drawn) and `RX_VK_WHEEL_BACKDROP=off` (none), and both readings compiled against `hookshot_env.h` agree for unset, `0`, `1`, `on`, `off`, `no`, `false`, `yes`, `true` and `2`. On the API version: understood, whichever lands second bumps it. <!-- github-sync:c:6828229191 -->
Commenting is not possible because the repository is archived.
No description provided.