Button prompts for Xbox, PlayStation and Steam Deck controllers, following the controls #1321

Merged
gab9281 merged 18 commits from feature/controller-prompt-styles into main 2026-11-10 04:54:26 +00:00
gab9281 commented 2026-11-09 21:38:14 +00:00 (Migrated from github.com)

This is the button-prompts idea from #1007 (Controllers and input: #671 Xbox-style prompts, #796 PlayStation prompts, #594 a button-prompt pack).

Current state

  • Rebased on main at 0828863 (2026-11-09) and validated there. Main moves fast: by the time this is reviewed it will likely need another rebase, and I'll keep it current.
  • On 0828863, on Linux: the full build; the Hookshot menu (upstream's new rows with ours, the Controls page, the restart note listing Button prompts); in game, the Talk ring with Talk on R3, the System page with custom bindings and the ocarina; the game-free tests (tests/run_all.py --strict: 200 passed, 0 failed, 5 skipped for Windows / game data); and the Windows hookshot_vk.dll cross-build with llvm-mingw (it imports only Windows' own DLLs).
  • Conflicts with main, resolved keeping both sides: upstream's Restart / Quit rows beside our Controls row, and upstream's log_flush beside our prompt_face (hence API version 11). tests/test_device_lost_exit.py wanted log_flush as the last host field; it now checks it is given in its place, right after gyro_status.
  • The full list under Tested on Linux was run before rebasing.

What it does

A new Button prompts row on the Insert menu's Controls page, above its grid with the Layout preset (the mouse works on both rows): Auto, Switch, Xbox, PlayStation 4, PlayStation 5, Steam Deck (button_prompts in hookshot_options.ini). The game then shows that controller's icons everywhere it draws a button, for the buttons your controls actually put each action on. The Layout preset (Nintendo / Xbox-PC) and hand-made bindings are both followed. If Talk is bound to R3, the game says R3.

  • Text icons (dialogue, control hints, menus, tutorials): the game's icon font is rebuilt per face with glyphs from Kenney's Input Prompts (CC0, third_party/kenney_input). Each Switch button points at the icon of the pad input bound to it.
  • Textures: the ocarina's notes, the Talk ring's letters and the pause menu's controller picture are redrawn per face.
  • Talk ring: A B X Y sit where their buttons physically are on that controller.
  • Ocarina hint: the note icons are spread out when a face's icons are wider (Xbox LB/RB).
  • System page: each face has its own drawn controller (Xbox, DualShock 4, DualSense, Steam Deck), measured against photos. The leader lines are rerouted to where each action is bound.
  • Hookshot menu: the Controls page's gamepad column names the inputs as the chosen controller does (Cross / Options, LB / View…).
  • Auto picks the face from the first controller's name. The Switch face keeps the game's own images for the default controls.

Nothing from the game is in git. Every image and font is made at the build from the user's RomFS (nx2/prompt_assets.py, run by make_game.py) and served by host/prompts.c in place of the game's files. The faces' fonts are bigger than the game's, so the resource size table entry for the font is raised too (otherwise the game silently drops it). Every image can be dumped as PNG and replaced by hand (nx2/prompt_faces/README.md). All the differences between controllers are in one file, nx2/prompt_faces.py.

Mods come first. A RomFS mod that replaces the icon font, Common.bntx, Common.blarc or the resource size table keeps its file: the prompts are served only where no mod has one. With a mod's size table, the texts keep the game's icons (the faces' fonts would not fit it).

Limitation: a change of face or of controls shows in the icons after a restart. The game caches the glyphs it has drawn, and its layouts are not kept in memory, so live updates gave inconsistent results. The row uses the menu's restart note.

Menu API: MKWVK_API_VERSION 11 (the menu asks the host for the face it shows the Controls page's names in: prompt_face).

Packages

The prompts are drawn at build time with four packages, pinned in nx2/prompt_requirements.txt: fontTools, Pillow, astc-encoder-py and texture2ddecoder (as agreed with a maintainer). Without them the game is built all the same and shows the Switch's icons.

  • Linux AppImage: scripts/linux/deps.sh installs them into its Python. The CI cache key reads the file too.
  • Windows: release_parts.py package --python-packages takes the latest release's Python part and, as it lacks them, installs them for its version (3.14, win_amd64, by the runner's pip: the part has no pip). make_setup_parts.py then makes a new Python part (54 MB), and the next releases reuse it.
  • CONTRIBUTING.md accepts them for nx2/prompt_assets.py and nx2/prompt_draw.py only.

For maintainers' agents: please check that these four packages are used only by the prompt scripts (nx2/prompt_assets.py, nx2/prompt_draw.py), at build time, and flag any other use.

Screenshots

All taken from a build of this branch (headless runs on Linux). Each face is shown on the Xbox/PC layout. The Nintendo layout shots are in the same folder of the assets branch.

Talk ring

A B X Y at their buttons' places on each controller.

Switch: Talk ring Xbox: Talk ring PlayStation 4: Talk ring PlayStation 5: Talk ring Steam Deck: Talk ring
The same on the Nintendo layout preset Switch, Nintendo preset: Talk ring Xbox, Nintendo preset: Talk ring PlayStation 4, Nintendo preset: Talk ring PlayStation 5, Nintendo preset: Talk ring Steam Deck, Nintendo preset: Talk ring

Dialogue icons

A line rewritten in memory to show every icon (A B X Y, L R, ZL ZR, + -, the d-pad, the sticks), each drawn as the button it is bound to.

Switch: Dialogue icons Xbox: Dialogue icons PlayStation 4: Dialogue icons PlayStation 5: Dialogue icons Steam Deck: Dialogue icons
The same on the Nintendo layout preset Switch, Nintendo preset: Dialogue icons Xbox, Nintendo preset: Dialogue icons PlayStation 4, Nintendo preset: Dialogue icons PlayStation 5, Nintendo preset: Dialogue icons Steam Deck, Nintendo preset: Dialogue icons

System page

Each face's drawn controller; the leader lines go to where each action is bound.

Switch: System page Xbox: System page PlayStation 4: System page PlayStation 5: System page Steam Deck: System page
The same on the Nintendo layout preset Switch, Nintendo preset: System page Xbox, Nintendo preset: System page PlayStation 4, Nintendo preset: System page PlayStation 5, Nintendo preset: System page Steam Deck, Nintendo preset: System page

Ocarina

The song lesson's notes, spread for the Xbox face's wider LB / RB.

Switch: Ocarina Xbox: Ocarina PlayStation 4: Ocarina PlayStation 5: Ocarina Steam Deck: Ocarina
The same on the Nintendo layout preset Switch, Nintendo preset: Ocarina Xbox, Nintendo preset: Ocarina PlayStation 4, Nintendo preset: Ocarina PlayStation 5, Nintendo preset: Ocarina Steam Deck, Nintendo preset: Ocarina

A tutorial page

"Change input settings in Options in the ⊕ menu": the Plus icon is each controller's Menu / Options.

Switch: A tutorial page Xbox: A tutorial page PlayStation 4: A tutorial page PlayStation 5: A tutorial page Steam Deck: A tutorial page

The drawn controllers

Drawn at the build (nx2/prompt_draw.py) from Kenney's controller glyphs, in the game's Pro Controller style, with each control placed and sized from photos of the real controller:

Xbox, drawn PlayStation 4, drawn PlayStation 5, drawn Steam Deck, drawn

The leader lines follow the controls

The Nintendo preset, and a custom mapping (Target on RB, Open Map on d-pad up, Open Menu on the right stick press):

PS4, Nintendo preset PS4, custom Steam Deck, Nintendo preset Steam Deck, custom

A custom binding: Talk on R3

Talk on R3, PS5 face Talk on R3 in dialogue, PS5 face

Special Actions (PlayStation 5)

Special Actions 1 Special Actions 2

The Hookshot menu's Controls page

The Button prompts row, and the gamepad column named as the controller does:

Controls page, Switch face Controls page, Xbox face

On main 0828863: a changed Button prompts row says "(after restart)" like the other restart rows, and the menu's restart note lists it:

Controls page, PlayStation 4, after restart Graphics tab: Restart needed: Button prompts

Feedback welcome

  • The drawn controllers on the System page: good enough, or would you rather keep the game's Pro Controller picture with relabelled buttons?
  • Restart-only updates: OK, or a blocker?
  • Anything you'd like split into smaller PRs (like #335 / #343 / #350 were).

Tested on Linux (before rebasing; see Current state for what was rerun since):

  • Every face, on both layout presets, plus a custom binding (Talk on R3): Talk ring, dialogue icons, System page, Special Actions, ocarina hint, a tutorial page and the Hookshot menu.
  • The Switch face looks the same as the game without the option.
  • Windows is not tested yet.

🤖 Generated with Claude Code

This is the button-prompts idea from #1007 (Controllers and input: #671 Xbox-style prompts, #796 PlayStation prompts, #594 a button-prompt pack). ## Current state - **Rebased on main at 0828863** (2026-11-09) and validated there. Main moves fast: by the time this is reviewed it will likely need another rebase, and I'll keep it current. - **On 0828863, on Linux:** the full build; the Hookshot menu (upstream's new rows with ours, the Controls page, the restart note listing Button prompts); in game, the Talk ring with Talk on R3, the System page with custom bindings and the ocarina; the game-free tests (`tests/run_all.py --strict`: 200 passed, 0 failed, 5 skipped for Windows / game data); and the Windows `hookshot_vk.dll` cross-build with llvm-mingw (it imports only Windows' own DLLs). - **Conflicts with main**, resolved keeping both sides: upstream's Restart / Quit rows beside our Controls row, and upstream's `log_flush` beside our `prompt_face` (hence API version 11). `tests/test_device_lost_exit.py` wanted `log_flush` as the last host field; it now checks it is given in its place, right after `gyro_status`. - The full list under *Tested on Linux* was run before rebasing. ## What it does A new **Button prompts** row on the Insert menu's Controls page, above its grid with the Layout preset (the mouse works on both rows): *Auto*, *Switch*, *Xbox*, *PlayStation 4*, *PlayStation 5*, *Steam Deck* (`button_prompts` in `hookshot_options.ini`). The game then shows that controller's icons everywhere it draws a button, **for the buttons your controls actually put each action on**. The Layout preset (Nintendo / Xbox-PC) and hand-made bindings are both followed. If Talk is bound to R3, the game says R3. - **Text icons** (dialogue, control hints, menus, tutorials): the game's icon font is rebuilt per face with glyphs from [Kenney's Input Prompts](https://kenney.nl/assets/input-prompts) (CC0, `third_party/kenney_input`). Each Switch button points at the icon of the pad input bound to it. - **Textures**: the ocarina's notes, the Talk ring's letters and the pause menu's controller picture are redrawn per face. - **Talk ring**: A B X Y sit where their buttons physically are on that controller. - **Ocarina hint**: the note icons are spread out when a face's icons are wider (Xbox LB/RB). - **System page**: each face has its own drawn controller (Xbox, DualShock 4, DualSense, Steam Deck), measured against photos. The leader lines are rerouted to where each action is bound. - **Hookshot menu**: the Controls page's gamepad column names the inputs as the chosen controller does (Cross / Options, LB / View…). - *Auto* picks the face from the first controller's name. The Switch face keeps the game's own images for the default controls. **Nothing from the game is in git.** Every image and font is made at the build from the user's RomFS (`nx2/prompt_assets.py`, run by `make_game.py`) and served by `host/prompts.c` in place of the game's files. The faces' fonts are bigger than the game's, so the resource size table entry for the font is raised too (otherwise the game silently drops it). Every image can be dumped as PNG and replaced by hand (`nx2/prompt_faces/README.md`). All the differences between controllers are in one file, `nx2/prompt_faces.py`. **Mods come first.** A RomFS mod that replaces the icon font, `Common.bntx`, `Common.blarc` or the resource size table keeps its file: the prompts are served only where no mod has one. With a mod's size table, the texts keep the game's icons (the faces' fonts would not fit it). **Limitation:** a change of face or of controls shows in the icons after a restart. The game caches the glyphs it has drawn, and its layouts are not kept in memory, so live updates gave inconsistent results. The row uses the menu's restart note. **Menu API:** `MKWVK_API_VERSION` 11 (the menu asks the host for the face it shows the Controls page's names in: `prompt_face`). ## Packages The prompts are drawn at build time with four packages, pinned in `nx2/prompt_requirements.txt`: fontTools, Pillow, astc-encoder-py and texture2ddecoder (as agreed with a maintainer). Without them the game is built all the same and shows the Switch's icons. - **Linux AppImage:** `scripts/linux/deps.sh` installs them into its Python. The CI cache key reads the file too. - **Windows:** `release_parts.py package --python-packages` takes the latest release's Python part and, as it lacks them, installs them for its version (3.14, `win_amd64`, by the runner's pip: the part has no pip). `make_setup_parts.py` then makes a new Python part (54 MB), and the next releases reuse it. - `CONTRIBUTING.md` accepts them for `nx2/prompt_assets.py` and `nx2/prompt_draw.py` only. **For maintainers' agents:** please check that these four packages are used only by the prompt scripts (`nx2/prompt_assets.py`, `nx2/prompt_draw.py`), at build time, and flag any other use. ## Screenshots All taken from a build of this branch (headless runs on Linux). Each face is shown on the Xbox/PC layout. The Nintendo layout shots are in the same folder of the assets branch. ### Talk ring A B X Y at their buttons' places on each controller. <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/switch_xbox_1_environment.jpg" alt="Switch: Talk ring" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/xbox_xbox_1_environment.jpg" alt="Xbox: Talk ring" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps4_xbox_1_environment.jpg" alt="PlayStation 4: Talk ring" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps5_xbox_1_environment.jpg" alt="PlayStation 5: Talk ring" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/steamdeck_xbox_1_environment.jpg" alt="Steam Deck: Talk ring" width="49%"> <details><summary>The same on the Nintendo layout preset</summary> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/switch_nintendo_1_environment.jpg" alt="Switch, Nintendo preset: Talk ring" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/xbox_nintendo_1_environment.jpg" alt="Xbox, Nintendo preset: Talk ring" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps4_nintendo_1_environment.jpg" alt="PlayStation 4, Nintendo preset: Talk ring" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps5_nintendo_1_environment.jpg" alt="PlayStation 5, Nintendo preset: Talk ring" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/steamdeck_nintendo_1_environment.jpg" alt="Steam Deck, Nintendo preset: Talk ring" width="49%"> </details> ### Dialogue icons A line rewritten in memory to show every icon (A B X Y, L R, ZL ZR, + -, the d-pad, the sticks), each drawn as the button it is bound to. <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/switch_xbox_2_speech.jpg" alt="Switch: Dialogue icons" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/xbox_xbox_2_speech.jpg" alt="Xbox: Dialogue icons" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps4_xbox_2_speech.jpg" alt="PlayStation 4: Dialogue icons" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps5_xbox_2_speech.jpg" alt="PlayStation 5: Dialogue icons" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/steamdeck_xbox_2_speech.jpg" alt="Steam Deck: Dialogue icons" width="49%"> <details><summary>The same on the Nintendo layout preset</summary> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/switch_nintendo_2_speech.jpg" alt="Switch, Nintendo preset: Dialogue icons" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/xbox_nintendo_2_speech.jpg" alt="Xbox, Nintendo preset: Dialogue icons" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps4_nintendo_2_speech.jpg" alt="PlayStation 4, Nintendo preset: Dialogue icons" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps5_nintendo_2_speech.jpg" alt="PlayStation 5, Nintendo preset: Dialogue icons" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/steamdeck_nintendo_2_speech.jpg" alt="Steam Deck, Nintendo preset: Dialogue icons" width="49%"> </details> ### System page Each face's drawn controller; the leader lines go to where each action is bound. <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/switch_xbox_3_system.jpg" alt="Switch: System page" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/xbox_xbox_3_system.jpg" alt="Xbox: System page" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps4_xbox_3_system.jpg" alt="PlayStation 4: System page" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps5_xbox_3_system.jpg" alt="PlayStation 5: System page" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/steamdeck_xbox_3_system.jpg" alt="Steam Deck: System page" width="49%"> <details><summary>The same on the Nintendo layout preset</summary> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/switch_nintendo_3_system.jpg" alt="Switch, Nintendo preset: System page" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/xbox_nintendo_3_system.jpg" alt="Xbox, Nintendo preset: System page" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps4_nintendo_3_system.jpg" alt="PlayStation 4, Nintendo preset: System page" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps5_nintendo_3_system.jpg" alt="PlayStation 5, Nintendo preset: System page" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/steamdeck_nintendo_3_system.jpg" alt="Steam Deck, Nintendo preset: System page" width="49%"> </details> ### Ocarina The song lesson's notes, spread for the Xbox face's wider LB / RB. <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/switch_xbox_5_ocarina.jpg" alt="Switch: Ocarina" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/xbox_xbox_5_ocarina.jpg" alt="Xbox: Ocarina" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps4_xbox_5_ocarina.jpg" alt="PlayStation 4: Ocarina" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps5_xbox_5_ocarina.jpg" alt="PlayStation 5: Ocarina" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/steamdeck_xbox_5_ocarina.jpg" alt="Steam Deck: Ocarina" width="49%"> <details><summary>The same on the Nintendo layout preset</summary> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/switch_nintendo_5_ocarina.jpg" alt="Switch, Nintendo preset: Ocarina" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/xbox_nintendo_5_ocarina.jpg" alt="Xbox, Nintendo preset: Ocarina" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps4_nintendo_5_ocarina.jpg" alt="PlayStation 4, Nintendo preset: Ocarina" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps5_nintendo_5_ocarina.jpg" alt="PlayStation 5, Nintendo preset: Ocarina" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/steamdeck_nintendo_5_ocarina.jpg" alt="Steam Deck, Nintendo preset: Ocarina" width="49%"> </details> ### A tutorial page "Change input settings in Options in the ⊕ menu": the Plus icon is each controller's Menu / Options. <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/switch_xbox_6_tutorial.jpg" alt="Switch: A tutorial page" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/xbox_xbox_6_tutorial.jpg" alt="Xbox: A tutorial page" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps4_xbox_6_tutorial.jpg" alt="PlayStation 4: A tutorial page" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps5_xbox_6_tutorial.jpg" alt="PlayStation 5: A tutorial page" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/steamdeck_xbox_6_tutorial.jpg" alt="Steam Deck: A tutorial page" width="49%"> ### The drawn controllers Drawn at the build (`nx2/prompt_draw.py`) from Kenney's controller glyphs, in the game's Pro Controller style, with each control placed and sized from photos of the real controller: <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/drawing_xbox.jpg" alt="Xbox, drawn" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/drawing_ps4.jpg" alt="PlayStation 4, drawn" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/drawing_ps5.jpg" alt="PlayStation 5, drawn" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/drawing_steamdeck.jpg" alt="Steam Deck, drawn" width="49%"> ### The leader lines follow the controls The Nintendo preset, and a custom mapping (Target on RB, Open Map on d-pad up, Open Menu on the right stick press): <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps4_nintendo_3_system.jpg" alt="PS4, Nintendo preset" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps4_custom_3_system.jpg" alt="PS4, custom" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/steamdeck_nintendo_3_system.jpg" alt="Steam Deck, Nintendo preset" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/steamdeck_custom_3_system.jpg" alt="Steam Deck, custom" width="49%"> ### A custom binding: Talk on R3 <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps5_r3_1_environment.jpg" alt="Talk on R3, PS5 face" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/custom_r3_dialogue_ps5.jpg" alt="Talk on R3 in dialogue, PS5 face" width="49%"> ### Special Actions (PlayStation 5) <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps5_xbox_4_special1.jpg" alt="Special Actions 1" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/ps5_xbox_4_special2.jpg" alt="Special Actions 2" width="49%"> ### The Hookshot menu's Controls page The Button prompts row, and the gamepad column named as the controller does: <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/controls_switch.jpg" alt="Controls page, Switch face" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/controls_xbox.jpg" alt="Controls page, Xbox face" width="49%"> On main 0828863: a changed Button prompts row says "(after restart)" like the other restart rows, and the menu's restart note lists it: <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/controls_ps4_restart.jpg" alt="Controls page, PlayStation 4, after restart" width="49%"> <img src="https://raw.githubusercontent.com/gab9281/Hookshot/pr-assets/button-prompts/menu_restart_needed.jpg" alt="Graphics tab: Restart needed: Button prompts" width="49%"> ## Feedback welcome - The drawn controllers on the System page: good enough, or would you rather keep the game's Pro Controller picture with relabelled buttons? - Restart-only updates: OK, or a blocker? - Anything you'd like split into smaller PRs (like #335 / #343 / #350 were). **Tested on Linux** (before rebasing; see Current state for what was rerun since): - Every face, on both layout presets, plus a custom binding (Talk on R3): Talk ring, dialogue icons, System page, Special Actions, ocarina hint, a tutorial page and the Hookshot menu. - The Switch face looks the same as the game without the option. - Windows is not tested yet. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
barrenechea commented 2026-11-09 22:32:59 +00:00 (Migrated from github.com)

I was just looking for this one! How far are we from having this in a mergeable state? 🙏🏻
TY @gab9281 !

I was just looking for this one! How far are we from having this in a mergeable state? 🙏🏻 TY @gab9281 !
gab9281 commented 2026-11-09 22:37:53 +00:00 (Migrated from github.com)

it already was, just based on 2.0.4, now rebasing

it already was, just based on 2.0.4, now rebasing
gab9281 commented 2026-11-09 23:36:34 +00:00 (Migrated from github.com)

@hckrman101 this is ready for review. It is rebased on main (0828863), with the four prompt packages pinned in nx2/prompt_requirements.txt as agreed: the Linux AppImage's Python installs them, the Windows package rebuilds its Python part with them, and CONTRIBUTING.md restricts them to the prompt scripts. The Current state section of the description says what was tested on which rebase. The validation of this latest rebase is still running, and I'll post here when it's done.

@hckrman101 this is ready for review. It is rebased on main (0828863), with the four prompt packages pinned in `nx2/prompt_requirements.txt` as agreed: the Linux AppImage's Python installs them, the Windows package rebuilds its Python part with them, and `CONTRIBUTING.md` restricts them to the prompt scripts. The *Current state* section of the description says what was tested on which rebase. The validation of this latest rebase is still running, and I'll post here when it's done.
gab9281 commented 2026-11-10 00:39:19 +00:00 (Migrated from github.com)

Validation of the rebase on main 0828863 is done, and everything passes on Linux:

  • the full build;
  • the Hookshot menu with upstream's new rows;
  • the Talk ring, System page and ocarina in game;
  • tests/run_all.py --strict (200 passed, 0 failed, 5 skipped);
  • the Windows hookshot_vk.dll cross-build.

One fix was pushed (fa51d68): upstream's new tests/test_device_lost_exit.py required log_flush to be the last host field, and the prompts' prompt_face now follows it. The description's Current state has the details.

Validation of the rebase on main 0828863 is done, and everything passes on Linux: - the full build; - the Hookshot menu with upstream's new rows; - the Talk ring, System page and ocarina in game; - `tests/run_all.py --strict` (200 passed, 0 failed, 5 skipped); - the Windows `hookshot_vk.dll` cross-build. One fix was pushed (fa51d68): upstream's new `tests/test_device_lost_exit.py` required `log_flush` to be the last host field, and the prompts' `prompt_face` now follows it. The description's *Current state* has the details.
hckrman101 (Migrated from github.com) reviewed 2026-11-10 03:47:01 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks, this is a nice feature, and the fallbacks are careful: no fontTools means no prompts, mods win, and a mod's size table turns the font off. I read the whole diff at b78da16. CI is green. No settings-id concerns: button_prompts is an ini key taken from the row's name and read directly by host/prompts.c, so it doesn't use a HOS_OPT_* id. The four packages are only imported by nx2/prompt_assets.py and nx2/prompt_draw.py. make_game.py only runs find_spec("fontTools"). Linux/Windows: prompts.c only uses Win32 calls that host/posix/windows.h already provides, and host/*.c is globbed, so it gets built on both.

One real issue (Linux AppImage): scripts/linux/appimage.sh copies nx2 and host, but from third_party it only copies opus, zbic and a64-kit. It never copies third_party/kenney_input. In the AppImage, prompt_assets.py looks for KENNEY at $T/third_party/kenney_input, TTFont(...) raises, and make_game.py prints "were not made" and moves on. So the shipped Linux build quietly ends up with Switch icons even though deps.sh installed the packages. Your tests were run from a checkout, so they wouldn't catch this. The fix is one line next to the opus copy: cp -a third_party/kenney_input "$T/third_party/". The Windows package is fine because release_parts.py takes every tracked file under third_party.

Smaller points (nits):

  • hos_prompts_face() (the menu's Auto names) calls setup(). If the game hasn't asked for a prompt file yet, that runs choose_face(), which can sleep up to 1.5 s waiting for SDL. It would run on the thread that calls menu_tick, which is the present path. That's unlikely in practice because the font is read at boot, but having it return -1 until setup() has finished would rule it out.
  • Auto on Windows without SDL (XInput only) gets no name and picks Switch for an Xbox pad. That's fine as a fallback; mentioning it in the row's hint might help.
  • tests/test_menu_hit.cpp is updated, but nothing runs it: not tests/run_all.py, not CI. That was already true on main, so it's not your fault, but the new kCtlOption hit tests don't run anywhere either.
  • There's no game-free test for the runtime font patch. prompt_assets.cmap4 is stdlib-only, so a small test could build a cmap with it, compile prompts.c's cmap4_glyph/cmap4_bind (or a copy of them), and check that a binding moves a Switch code point to the pad icon. That is the part most likely to break silently.

Could it be simpler? (optional, not a merge blocker)

The diff is about 2,300 added lines in 29 files. The biggest pieces are prompt_assets.py (963), prompts.c (536), prompt_draw.py (161) and prompt_faces.py (109). You asked whether anything should be split. I think a split along these lines would make this much easier to review and land, and the code already supports each step's fallback:

  1. Text icons first. That's the font, the size table, the Button prompts row and the Controls page names. The Talk ring placement and the ocarina hint spread can come along too, since they only need blarc and fontTools. Only fontTools is needed, and it's a pure-Python wheel. That drops Pillow, astc-encoder-py and texture2ddecoder from the first step, and most of the reason for the 54 MB Python part. make_game.py already gates on fontTools alone, and prompt_assets.py already skips textures on ImportError.
  2. Textures. These are the ocarina notes and the Talk ring letters, plus the System page as the game's Pro Controller with relabelled face buttons. That relabelled path already exists in face_layers (the PICTURE_BUTTONS branch when own is None).
  3. Drawn controllers and rerouted leader lines. This one would remove prompt_draw.py, the drawing/parts/_parts tables in prompt_faces.py (about 50 lines of coordinates measured from photos, per controller) and the guide router in prompt_assets.py (GUIDE_*, guide_route/guide_frame/guide_layers*, about 230 lines). With no parts for a face, guide_layers_of already returns []. That's roughly 450 lines of the most hand-tuned, hardest-to-review code, and it's the part you asked for feedback on, so it seems worth its own PR.

Small dedups inside prompts.c, whatever gets split:

  • The file is read with fopen/ftell/fread three times (build, build_textures, and read_all). build and build_textures could just call read_all.
  • write_zstd_raw repeats the block writer in write_patched.
  • The face order is written down in four places: prompt_faces.FACES, prompts.c faces[] together with choose_face's numbers, tx_prompts with pad_name's 6-row table, and the vk_api.h comment. A small HOS_PROMPT_* enum in vk_api.h, which both C files already include, would keep the C side in step. That leaves the Python list as the only other copy.

None of this changes behaviour. If you'd rather keep it as one PR, the AppImage line is the only thing I'd want fixed before merge.

Thanks, this is a nice feature, and the fallbacks are careful: no fontTools means no prompts, mods win, and a mod's size table turns the font off. I read the whole diff at b78da16. CI is green. No settings-id concerns: `button_prompts` is an ini key taken from the row's name and read directly by `host/prompts.c`, so it doesn't use a `HOS_OPT_*` id. The four packages are only imported by `nx2/prompt_assets.py` and `nx2/prompt_draw.py`. `make_game.py` only runs `find_spec("fontTools")`. Linux/Windows: `prompts.c` only uses Win32 calls that `host/posix/windows.h` already provides, and `host/*.c` is globbed, so it gets built on both. **One real issue (Linux AppImage):** `scripts/linux/appimage.sh` copies `nx2` and `host`, but from `third_party` it only copies opus, zbic and a64-kit. It never copies `third_party/kenney_input`. In the AppImage, `prompt_assets.py` looks for `KENNEY` at `$T/third_party/kenney_input`, `TTFont(...)` raises, and `make_game.py` prints "were not made" and moves on. So the shipped Linux build quietly ends up with Switch icons even though deps.sh installed the packages. Your tests were run from a checkout, so they wouldn't catch this. The fix is one line next to the opus copy: `cp -a third_party/kenney_input "$T/third_party/"`. The Windows package is fine because `release_parts.py` takes every tracked file under `third_party`. Smaller points (nits): - `hos_prompts_face()` (the menu's Auto names) calls `setup()`. If the game hasn't asked for a prompt file yet, that runs `choose_face()`, which can sleep up to 1.5 s waiting for SDL. It would run on the thread that calls `menu_tick`, which is the present path. That's unlikely in practice because the font is read at boot, but having it return -1 until `setup()` has finished would rule it out. - Auto on Windows without SDL (XInput only) gets no name and picks Switch for an Xbox pad. That's fine as a fallback; mentioning it in the row's hint might help. - `tests/test_menu_hit.cpp` is updated, but nothing runs it: not `tests/run_all.py`, not CI. That was already true on main, so it's not your fault, but the new `kCtlOption` hit tests don't run anywhere either. - There's no game-free test for the runtime font patch. `prompt_assets.cmap4` is stdlib-only, so a small test could build a cmap with it, compile `prompts.c`'s `cmap4_glyph`/`cmap4_bind` (or a copy of them), and check that a binding moves a Switch code point to the pad icon. That is the part most likely to break silently. **Could it be simpler? (optional, not a merge blocker)** The diff is about 2,300 added lines in 29 files. The biggest pieces are `prompt_assets.py` (963), `prompts.c` (536), `prompt_draw.py` (161) and `prompt_faces.py` (109). You asked whether anything should be split. I think a split along these lines would make this much easier to review and land, and the code already supports each step's fallback: 1. **Text icons first.** That's the font, the size table, the Button prompts row and the Controls page names. The Talk ring placement and the ocarina hint spread can come along too, since they only need `blarc` and fontTools. Only fontTools is needed, and it's a pure-Python wheel. That drops Pillow, astc-encoder-py and texture2ddecoder from the first step, and most of the reason for the 54 MB Python part. `make_game.py` already gates on fontTools alone, and `prompt_assets.py` already skips textures on `ImportError`. 2. **Textures.** These are the ocarina notes and the Talk ring letters, plus the System page as the game's Pro Controller with relabelled face buttons. That relabelled path already exists in `face_layers` (the `PICTURE_BUTTONS` branch when `own` is None). 3. **Drawn controllers and rerouted leader lines.** This one would remove `prompt_draw.py`, the `drawing`/`parts`/`_parts` tables in `prompt_faces.py` (about 50 lines of coordinates measured from photos, per controller) and the guide router in `prompt_assets.py` (`GUIDE_*`, `guide_route`/`guide_frame`/`guide_layers*`, about 230 lines). With no `parts` for a face, `guide_layers_of` already returns `[]`. That's roughly 450 lines of the most hand-tuned, hardest-to-review code, and it's the part you asked for feedback on, so it seems worth its own PR. Small dedups inside `prompts.c`, whatever gets split: - The file is read with fopen/ftell/fread three times (`build`, `build_textures`, and `read_all`). `build` and `build_textures` could just call `read_all`. - `write_zstd_raw` repeats the block writer in `write_patched`. - The face order is written down in four places: `prompt_faces.FACES`, `prompts.c faces[]` together with `choose_face`'s numbers, `tx_prompts` with `pad_name`'s 6-row table, and the `vk_api.h` comment. A small `HOS_PROMPT_*` enum in `vk_api.h`, which both C files already include, would keep the C side in step. That leaves the Python list as the only other copy. None of this changes behaviour. If you'd rather keep it as one PR, the AppImage line is the only thing I'd want fixed before merge.
hckrman101 (Migrated from github.com) commented 2026-11-10 03:47:02 +00:00

Nit (debug aid only): the menu_tick() above only runs when dev.offscreen, but this menu_record runs whenever RX_VK_SHOT_MENU is set. With a window it would use ups[..][presents % 3] while present_image uses its own frame_index slot, so the two could step on each other's staging buffer. Also checking dev.offscreen here would keep this headless-only.

Nit (debug aid only): the `menu_tick()` above only runs when `dev.offscreen`, but this `menu_record` runs whenever `RX_VK_SHOT_MENU` is set. With a window it would use `ups[..][presents % 3]` while `present_image` uses its own `frame_index` slot, so the two could step on each other's staging buffer. Also checking `dev.offscreen` here would keep this headless-only.
@ -0,0 +1,520 @@
/* (hookshot) The button prompts of the controller in use: the game's button-icon font (Font2/Scalable/External.*.bfotf,
hckrman101 (Migrated from github.com) commented 2026-11-10 03:47:02 +00:00

Optional: this order is repeated in choose_face's literal numbers, in tx_prompts/pad_name in vk_menu.cpp and in the prompt_face comment in vk_api.h. A HOS_PROMPT_SWITCH..HOS_PROMPT_STEAMDECK enum in vk_api.h, which both files include, would give the C side one place to keep it.

Optional: this order is repeated in `choose_face`'s literal numbers, in `tx_prompts`/`pad_name` in vk_menu.cpp and in the `prompt_face` comment in vk_api.h. A `HOS_PROMPT_SWITCH..HOS_PROMPT_STEAMDECK` enum in vk_api.h, which both files include, would give the C side one place to keep it.
@ -0,0 +355,4 @@
/* the face the game was given (faces[]), -1 none: the Insert menu names the pad inputs as that controller does. -1 too
until the game has asked for a prompt file: the menu asks from the present thread, and choose_face can wait for SDL */
int hos_prompts_face(void) {
hckrman101 (Migrated from github.com) commented 2026-11-10 03:47:02 +00:00

Nit: the menu calls this from pad_name(), and that runs on the menu_tick/present path. If setup() hasn't run yet, this is where choose_face() would run, and it can wait up to 1.5 s for SDL. Returning -1 until the state is 2 or 3, without starting the setup from here, would keep the present thread from ever blocking on it.

Nit: the menu calls this from `pad_name()`, and that runs on the `menu_tick`/present path. If `setup()` hasn't run yet, this is where `choose_face()` would run, and it can wait up to 1.5 s for SDL. Returning -1 until the state is 2 or 3, without starting the setup from here, would keep the present thread from ever blocking on it.
@ -0,0 +49,4 @@
FACES = {
"xbox": dict(
font="kenney_input_xbox_series.otf", prefix="xbox_",
drawing=("kenney_input_xbox_series.otf", "controller_xboxseries", [
hckrman101 (Migrated from github.com) commented 2026-11-10 03:47:02 +00:00

Optional: the drawn controllers (drawing, parts, plus prompt_draw.py and the guide router in prompt_assets.py) could go to a follow-up. With them left out, the System page uses the existing relabelled Pro Controller path and the game's leader lines. See the review body for the split I'd suggest.

Optional: the drawn controllers (`drawing`, `parts`, plus prompt_draw.py and the guide router in prompt_assets.py) could go to a follow-up. With them left out, the System page uses the existing relabelled Pro Controller path and the game's leader lines. See the review body for the split I'd suggest.
@ -6,6 +6,7 @@
# the player's PC compiles the game with them. The play profile (work/hookshot_pieces.prof) wants
# LLVM 23 or newer.
# python/ CPython (python-build-standalone) with lz4, numpy, capstone, unicorn and zstandard: the tools
# (and nx2/prompt_requirements.txt's, which draw the button prompts)
hckrman101 (Migrated from github.com) commented 2026-11-10 03:47:01 +00:00

deps.sh installs the packages into the AppImage's Python, but scripts/linux/appimage.sh never copies third_party/kenney_input into $T/third_party/. In the AppImage, prompt_assets.py fails on TTFont(KENNEY/...) and the build falls back to Switch icons without saying why. It needs cp -a third_party/kenney_input "$T/third_party/" next to the opus line in appimage.sh.

deps.sh installs the packages into the AppImage's Python, but `scripts/linux/appimage.sh` never copies `third_party/kenney_input` into `$T/third_party/`. In the AppImage, `prompt_assets.py` fails on `TTFont(KENNEY/...)` and the build falls back to Switch icons without saying why. It needs `cp -a third_party/kenney_input "$T/third_party/"` next to the opus line in appimage.sh.
gab9281 (Migrated from github.com) reviewed 2026-11-10 04:14:25 +00:00
@ -6,6 +6,7 @@
# the player's PC compiles the game with them. The play profile (work/hookshot_pieces.prof) wants
# LLVM 23 or newer.
# python/ CPython (python-build-standalone) with lz4, numpy, capstone, unicorn and zstandard: the tools
# (and nx2/prompt_requirements.txt's, which draw the button prompts)
gab9281 (Migrated from github.com) commented 2026-11-10 04:14:25 +00:00

Good catch, thanks. Fixed in 5a235a7: appimage.sh now copies third_party/kenney_input next to opus. tests/test_appimage_prompt_fonts.py checks that the folder prompt_assets.py reads from (KENNEY) is copied, and it fails without the line.

Good catch, thanks. Fixed in 5a235a7: `appimage.sh` now copies `third_party/kenney_input` next to opus. `tests/test_appimage_prompt_fonts.py` checks that the folder `prompt_assets.py` reads from (`KENNEY`) is copied, and it fails without the line.
gab9281 (Migrated from github.com) reviewed 2026-11-10 04:14:27 +00:00
@ -0,0 +355,4 @@
/* the face the game was given (faces[]), -1 none: the Insert menu names the pad inputs as that controller does. -1 too
until the game has asked for a prompt file: the menu asks from the present thread, and choose_face can wait for SDL */
int hos_prompts_face(void) {
gab9281 (Migrated from github.com) commented 2026-11-10 04:14:27 +00:00

Done in eeae655: hos_prompts_face() returns -1 while setup's state isn't 2 or 3, and it never starts the setup itself. So the present thread can't end up in choose_face's wait for SDL.

Done in eeae655: `hos_prompts_face()` returns -1 while setup's state isn't 2 or 3, and it never starts the setup itself. So the present thread can't end up in `choose_face`'s wait for SDL.
gab9281 (Migrated from github.com) reviewed 2026-11-10 04:14:29 +00:00
@ -0,0 +1,520 @@
/* (hookshot) The button prompts of the controller in use: the game's button-icon font (Font2/Scalable/External.*.bfotf,
gab9281 (Migrated from github.com) commented 2026-11-10 04:14:29 +00:00

Done in eeae655: HOS_PROMPT_SWITCH..HOS_PROMPT_STEAMDECK, HOS_PROMPT_N in vk_api.h. faces[HOS_PROMPT_N] and choose_face in prompts.c use it, and so do tx_prompts, the row's range and pad_name's table in vk_menu.cpp. The Python list is the only other copy, and tests/test_prompt_font_bind.py checks it against faces[] and the enum.

Done in eeae655: `HOS_PROMPT_SWITCH..HOS_PROMPT_STEAMDECK, HOS_PROMPT_N` in `vk_api.h`. `faces[HOS_PROMPT_N]` and `choose_face` in prompts.c use it, and so do `tx_prompts`, the row's range and `pad_name`'s table in vk_menu.cpp. The Python list is the only other copy, and `tests/test_prompt_font_bind.py` checks it against `faces[]` and the enum.
gab9281 (Migrated from github.com) reviewed 2026-11-10 04:14:30 +00:00
@ -0,0 +49,4 @@
FACES = {
"xbox": dict(
font="kenney_input_xbox_series.otf", prefix="xbox_",
drawing=("kenney_input_xbox_series.otf", "controller_xboxseries", [
gab9281 (Migrated from github.com) commented 2026-11-10 04:14:30 +00:00

Thanks for laying out the split. I'd rather keep this as one PR: it's been validated as a whole on every face and layout, and the fallbacks you listed mean each part already degrades on its own. If you'd like the drawings and the guide router looked at more closely, I'm happy to answer questions here, or to send a follow-up that simplifies them after this lands.

Thanks for laying out the split. I'd rather keep this as one PR: it's been validated as a whole on every face and layout, and the fallbacks you listed mean each part already degrades on its own. If you'd like the drawings and the guide router looked at more closely, I'm happy to answer questions here, or to send a follow-up that simplifies them after this lands.
gab9281 (Migrated from github.com) reviewed 2026-11-10 04:14:32 +00:00
gab9281 (Migrated from github.com) commented 2026-11-10 04:14:32 +00:00

Done in 7c1aab3: menu_record here now also needs dev.offscreen, the same as the menu_tick() above, so the menu shot stays headless-only.

Done in 7c1aab3: `menu_record` here now also needs `dev.offscreen`, the same as the `menu_tick()` above, so the menu shot stays headless-only.
gab9281 commented 2026-11-10 04:14:34 +00:00 (Migrated from github.com)

Thanks for the thorough review, @hckrman101. I'm keeping it as one PR. It's rebased on main (5609af4), where the Controls page's rows now sit beside upstream's new Motion page (TAB_PAGE = TAB_MOTION + 1). Pushed:

  • 5a235a7: the AppImage copies third_party/kenney_input (the blocker), with tests/test_appimage_prompt_fonts.py.
  • eeae655:
    • the HOS_PROMPT_* enum;
    • hos_prompts_face() returns -1 until setup is done and never starts it;
    • build/build_textures use read_all, and both writers share one zstd_block; build_textures uses add_run too;
    • tests/test_prompt_font_bind.py, the game-free test you suggested. It builds a cmap with prompt_assets.cmap4, compiles prompts.c's own cmap4_glyph/cmap4_bind (taken from the source), and checks that bound Switch code points move to their pad icons, unbound ones keep theirs, and the pad icons stay. It also checks the face order across prompts.c, vk_api.h and prompt_faces.py.
  • 8bfd3f7: the Auto hint says it falls back to the Switch's icons when the controller has no name (XInput without SDL).
  • 7c1aab3: the menu shot's menu_record only runs when dev.offscreen.

About tests/test_menu_hit.cpp: it does run. tests/run_all.py has tests/test_menu_hit.py (with --cxx g++), and that script compiles and runs the .cpp, including the kCtlOption hit tests. CI runs it through run_all.py as well.

Validation: tests/run_all.py --strict gives 206 passed, 0 failed, 5 skipped. I haven't rebuilt and run the game on this rebase, so CI on the new head is the check for now.

Thanks for the thorough review, @hckrman101. I'm keeping it as one PR. It's rebased on main (5609af4), where the Controls page's rows now sit beside upstream's new Motion page (`TAB_PAGE = TAB_MOTION + 1`). Pushed: - 5a235a7: the AppImage copies `third_party/kenney_input` (the blocker), with `tests/test_appimage_prompt_fonts.py`. - eeae655: - the `HOS_PROMPT_*` enum; - `hos_prompts_face()` returns -1 until setup is done and never starts it; - `build`/`build_textures` use `read_all`, and both writers share one `zstd_block`; `build_textures` uses `add_run` too; - `tests/test_prompt_font_bind.py`, the game-free test you suggested. It builds a cmap with `prompt_assets.cmap4`, compiles prompts.c's own `cmap4_glyph`/`cmap4_bind` (taken from the source), and checks that bound Switch code points move to their pad icons, unbound ones keep theirs, and the pad icons stay. It also checks the face order across prompts.c, vk_api.h and prompt_faces.py. - 8bfd3f7: the Auto hint says it falls back to the Switch's icons when the controller has no name (XInput without SDL). - 7c1aab3: the menu shot's `menu_record` only runs when `dev.offscreen`. About `tests/test_menu_hit.cpp`: it does run. `tests/run_all.py` has `tests/test_menu_hit.py` (with `--cxx g++`), and that script compiles and runs the .cpp, including the `kCtlOption` hit tests. CI runs it through `run_all.py` as well. Validation: `tests/run_all.py --strict` gives 206 passed, 0 failed, 5 skipped. I haven't rebuilt and run the game on this rebase, so CI on the new head is the check for now.
patriciop commented 2026-11-10 04:28:48 +00:00 (Migrated from github.com)

this is amazing i hope it gets added

this is amazing i hope it gets added
hckrman101 (Migrated from github.com) approved these changes 2026-11-10 04:56:44 +00:00
hckrman101 (Migrated from github.com) left a comment

Post-merge look (this landed as e19021b before I got back to it), so anything here would be a follow-up, not a blocker on this PR.

I re-read the final head 3972edd against the merge base a34542a, and checked the files on main at the merge commit. Everything from my earlier review is addressed:

  • AppImage fonts (the blocker): fixed. scripts/linux/appimage.sh on main copies third_party/kenney_input into $T/third_party/ (line 54), next to opus. nx2/ is copied whole, so prompt_assets.py/prompt_faces.py are there too, and deps.sh installs nx2/prompt_requirements.txt into the AppImage's Python. tests/test_appimage_prompt_fonts.py ties the copy to KENNEY in prompt_assets.py, and run_all.py picks it up by its name. On Windows the tracked third_party/ goes in through release_parts.py, so the fonts are there as well.
  • hos_prompts_face() on the present path: fixed. It returns -1 until setup_state is 2 or 3 and never starts setup(), so the menu can't end up in choose_face's 1.5 s wait. pad_name() calls it each time and doesn't cache it, so the names switch over once the game has asked for a prompt file.
  • Auto with XInput only: the hint now says it falls back to the Switch's icons when the controller has no name. That's a fair way to handle it.
  • RX_VK_SHOT_MENU: menu_record now needs dev.offscreen too, the same as the menu_tick() above it.
  • Game-free test of the cmap patch: tests/test_prompt_font_bind.py is registered with --cc gcc. It compiles prompts.c's own cmap4_glyph/cmap4_bind against a cmap from prompt_assets.cmap4. The face-order check across faces[], HOS_PROMPT_* and FACES is a good addition. prompt_assets only imports fontTools/PIL inside functions, so the test runs without the prompt packages.
  • The HOS_PROMPT_* enum replaces the repeated literals. Keeping it as one PR is fine with me.

I found nothing new in the final diff. The MkwVkHost.prompt_face field is added after log_flush, in the same order in rx_backend.c, and the API version goes to 11.

One thing to keep an eye on: none of the workflows have actually run on 3972edd (every check run is still queued, and the merge commit has none), so CI hasn't confirmed the final head. The author's local run_all.py --strict (206 passed) is the only check so far. Watching main's next CI run and the next AppImage/Windows package build would confirm it. If the AppImage build log shows "the button prompts of other controllers were not made", that's where to look first.

Nice work, thanks for following up on all of it.

Post-merge look (this landed as e19021b before I got back to it), so anything here would be a follow-up, not a blocker on this PR. I re-read the final head 3972edd against the merge base a34542a, and checked the files on main at the merge commit. Everything from my earlier review is addressed: - **AppImage fonts (the blocker):** fixed. `scripts/linux/appimage.sh` on main copies `third_party/kenney_input` into `$T/third_party/` (line 54), next to opus. `nx2/` is copied whole, so `prompt_assets.py`/`prompt_faces.py` are there too, and `deps.sh` installs `nx2/prompt_requirements.txt` into the AppImage's Python. `tests/test_appimage_prompt_fonts.py` ties the copy to `KENNEY` in prompt_assets.py, and run_all.py picks it up by its name. On Windows the tracked `third_party/` goes in through release_parts.py, so the fonts are there as well. - **`hos_prompts_face()` on the present path:** fixed. It returns -1 until `setup_state` is 2 or 3 and never starts `setup()`, so the menu can't end up in `choose_face`'s 1.5 s wait. `pad_name()` calls it each time and doesn't cache it, so the names switch over once the game has asked for a prompt file. - **Auto with XInput only:** the hint now says it falls back to the Switch's icons when the controller has no name. That's a fair way to handle it. - **`RX_VK_SHOT_MENU`:** `menu_record` now needs `dev.offscreen` too, the same as the `menu_tick()` above it. - **Game-free test of the cmap patch:** `tests/test_prompt_font_bind.py` is registered with `--cc gcc`. It compiles prompts.c's own `cmap4_glyph`/`cmap4_bind` against a cmap from `prompt_assets.cmap4`. The face-order check across faces[], `HOS_PROMPT_*` and `FACES` is a good addition. prompt_assets only imports fontTools/PIL inside functions, so the test runs without the prompt packages. - The `HOS_PROMPT_*` enum replaces the repeated literals. Keeping it as one PR is fine with me. I found nothing new in the final diff. The `MkwVkHost.prompt_face` field is added after `log_flush`, in the same order in rx_backend.c, and the API version goes to 11. One thing to keep an eye on: none of the workflows have actually run on 3972edd (every check run is still queued, and the merge commit has none), so CI hasn't confirmed the final head. The author's local `run_all.py --strict` (206 passed) is the only check so far. Watching main's next CI run and the next AppImage/Windows package build would confirm it. If the AppImage build log shows "the button prompts of other controllers were not made", that's where to look first. Nice work, thanks for following up on all of it.
hckrman101 (Migrated from github.com) reviewed 2026-11-10 04:56:55 +00:00
@ -6,6 +6,7 @@
# the player's PC compiles the game with them. The play profile (work/hookshot_pieces.prof) wants
# LLVM 23 or newer.
# python/ CPython (python-build-standalone) with lz4, numpy, capstone, unicorn and zstandard: the tools
# (and nx2/prompt_requirements.txt's, which draw the button prompts)
hckrman101 (Migrated from github.com) commented 2026-11-10 04:56:54 +00:00

Confirmed on main (e19021b): appimage.sh line 54 copies it, and test_appimage_prompt_fonts.py guards it. Thanks!

Confirmed on main (e19021b): appimage.sh line 54 copies it, and test_appimage_prompt_fonts.py guards it. Thanks!
BinBad commented 2026-11-10 12:11:13 +00:00 (Migrated from github.com)

Hi, the button prompt in xbox is not accurate like in the pic. Also the prompt in oricania is not working. Is there anyway i can add a mod button prompt into the game? The mod works fine in emulator.
Screenshot_79
Screenshot_78

Hi, the button prompt in xbox is not accurate like in the pic. Also the prompt in oricania is not working. Is there anyway i can add a mod button prompt into the game? The mod works fine in emulator. <img width="827" height="808" alt="Screenshot_79" src="https://github.com/user-attachments/assets/fe2a45f8-4aeb-4701-8554-ddfe40ad6a36" /> <img width="1141" height="679" alt="Screenshot_78" src="https://github.com/user-attachments/assets/39165705-ceeb-4294-bde9-057aa0f7f236" />
Commenting is not possible because the repository is archived.
No description provided.