Skip to content

feat(prompts): a Keyboard face: the keys and mouse buttons bound, in texts, hints and menus - #2435

Merged
NotRexed merged 3 commits into
NotRexed:mainfrom
Needicla:feat/keyboard-prompts
Nov 12, 2026
Merged

NotRexed merged 3 commits into
NotRexed:mainfrom
Needicla:feat/keyboard-prompts

Conversation

@Needicla

@Needicla Needicla commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Players on the keyboard and mouse see a controller's buttons in every prompt (the Switch's when no controller is connected), so they have to work out which key each one means. This adds a Keyboard face to the Button prompts system: the game's texts, hints and menus show the key or mouse button each action is bound to.

Part of #1007 (keyboard button prompts, from #2028).

What it does

  • Button prompts → Keyboard (a seventh value). Auto picks it when no controller is connected, through SDL or XInput; with one connected, Auto picks that controller's face as before.
  • Each Switch button shows the first key bound to it on the Controls page, or the second when the first slot is clear. Mouse buttons show as a mouse with that button lit, and keys named by words (Ctrl, Alt, Esc, Home, PgUp, Num1...) as wider keys set in the game's own bold font. Kenney's versions of those are too small to read at prompt size.
  • The sticks' icons (Move, Change View and their direction variants) show WASD (or W/A/S/D, AD, WS) and the mouse moving.
  • It follows the live system from feat(prompts): a new face or new controls show while the game runs, no restart #2366: changing a key, or switching to or from Keyboard, shows at once, like a pad rebind.
  • Kept as the Switch's: the textures (ocarina notes, the letters on black, the pause menu's controller picture) and the Talk ring's places. The ocarina hint is spaced by the keys' icon widths.

How

  • nx2/prompt_keys.py (new, data only, standard library): which icon each key and stick shows, and the code points.
  • nx2/prompt_draw.py: the keyboard face's glyphs. They are Kenney's Input Prompts Keyboard & Mouse font (CC0, the same 1.5 release as the controller faces), in its own folder third_party/kenney_input_keyboard with a PROVENANCE.md and the release's License.txt; the AppImage copies it. The word keys and the mice are drawn as non-crossing contours, so they fill the same under either winding rule.
  • nx2/prompt_assets.py: faces_font builds the keyboard face (the Switch's pad icons, plus each key's icon at KEY_BASE + key code, past every face's code points), and writes its hint.bin per key code.
  • host/prompts.c:
    • The bind arrays carry each button's first key after its pad input (BINDS), so key changes go through the same live path.
    • cmap4_bind points a button at its key's icon on the keyboard face.
    • text_code gives a text a code of its own for each (button, key) pair as it first appears (key_slot), and points it at the key's icon where the game keeps the font.
    • Code points: the per-pair codes come from 320 codes at U+EEC0–U+EFFF, and the sticks' codes are at U+F8C0–U+F8DF. I checked every font in the romfs: none uses either range. The CJK fonts use most of the private use area, including parts of U+F700–U+F7BF.
  • host/nn_hid.c: hos_xinput_pads(), so Auto doesn't pick Keyboard for an XInput-only controller.
  • gpu/vk/vk_menu.cpp: the Keyboard value and its hint; pad_name names pad inputs by place on the keyboard face.

Testing

In game, a build of this branch (81a5445, the same code before the rebases onto #2448 and #1990) on Windows 11 (native, RTX 4060), keyboard and mouse, no controller. Driven with the control bridge (HOS_CONTROL, headless, --user on a copy of the saves), into Hyrule Field from a save, then the pause menu:

Case Result
Auto, no controller ✅ prompts: auto: controller "(none)": keyboard, keyboard icons, 16 buttons on these keys. The pause menu shows Q/R on the tabs, the mouse for Rotate, E for Select, Shift for Back; the HUD's item buttons show R and X.
Keyboard picked, a key rebound (A from E to F) ✅ Select reads F.
Xbox picked (a pad face, unchanged by this PR) ✅ LB/RB, Select on B and Back on A (the pad layout in use); the ocarina hint spread for Xbox's icons.
A change while the game runs (Keyboard ↔ Xbox) ✅ with #2583: on native Windows #2366's live path never found the font (it read it 8 bytes past its start), with or without the keyboard face. @polackangry found it and fixed it in #2583, and with that on top of this branch, Keyboard → Xbox → Keyboard while the game runs applies at once (now the xbox icons in texts / now the keyboard icons in texts), the pause menu reopened showing each face's icons. Without #2583 the row says "(restart)" on native Windows, as for every face.
A pad plugged in and out, a key rebound with a hint on screen ⚠️ Not tested: no controller here, and both go through the live path.

Also:

  • tests/test_prompt_font_bind.py is extended. On the keyboard face it checks: keys bound in the first or second slot, a key with no icon, the sticks, and text codes for each (button, key) pair. The other faces are unchanged. It passes, as do prompts_live, appimage_prompt_fonts and menu_hooks.
  • tests/run_all.py: the same results with and without the PR on this machine (its other failures are missing .NET and MSVC-target C++ headers).

Screenshots

From the build of this branch, 1280×720 (the bridge's shots: the game's own picture).

Auto with no controller, the pause menu: Q/R tabs, the mouse for Rotate, E for Select, Shift for Back.

Auto, no controller: the pause menu with keyboard prompts

Keyboard with A rebound to F: Select reads F.

Keyboard face, A on F: Select reads F

Xbox, for comparison (a pad face, as before this PR).

Xbox face: LB/RB, B and A

The field: the HUD's item buttons on R and X.

Hyrule Field: the item buttons on R and X

🤖 Generated with Claude Code

Needicla added a commit to Needicla/Hookshot that referenced this pull request Nov 11, 2026
Needicla added a commit to Needicla/Hookshot that referenced this pull request Nov 11, 2026

@Joey-Magnus Joey-Magnus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is a well-built addition. The keyboard face sits on top of the live prompt system from #2366 without disturbing the controller faces. The code-point ranges are checked against each other in tests/test_prompt_font_bind.py: KEY_BASE past every face, slots and sticks clear of BIND_BASE. Auto only falls back to Keyboard when the build actually made prompts/keyboard/, and the textures and the Talk ring stay on the Switch's, which is the safe choice. At the PR's head I ran prompt, bound_keys and menu_hit locally (7 checks), and all pass. On GitHub only the title check has run so far; the other workflows need a maintainer to approve them.

should fix, nx2/prompt_keys.py:61,69-70,120-121,144,191-194: CONTRIBUTING (Code and data conventions) accepts the prompt packages (fontTools, Pillow, astc-encoder-py, texture2ddecoder) "for nx2/prompt_assets.py and nx2/prompt_draw.py alone", and asks reviewers to flag any other use. This new module imports fontTools.pens.* and fontTools.ttLib. The imports are inside functions, so the tests still import it on the standard library alone, which is good. Either move the drawing into nx2/prompt_draw.py, which is where drawing lives, or have the maintainer agree to add prompt_keys.py to that list and update the sentence in CONTRIBUTING in this PR.

should fix (testing): the in-game test installed the new icons into a v2.1.0 build as the Switch face's font. That shows the artwork, but not this PR's host code: auto_face (host/prompts.c:119), the keyboard branch of cmap4_bind, key_slot and the slots kept across live_font (host/prompts.c:967 on), and the stick rewrites in text_code. Those are the parts that write into the game's font in memory while it runs. Please run a build of this branch once: with Keyboard picked, with Auto and no controller, then plug a pad in and out, and rebind a key while a hint is on screen. Note the result in the description. Screenshots from that build would be better than the transplanted ones.

question, third_party/kenney_input/: CONTRIBUTING says not to modify third_party/, and to add a new folder with its own provenance note rather than edit one. This adds an unmodified font from the same Kenney release to the existing folder and adds a line to its PROVENANCE.md. That seems within the spirit of the rule, but it's the maintainer's call. If not, third_party/kenney_input_keyboard/ with its own PROVENANCE.md and License.txt is the literal reading.

minor, host/prompts.c:53,967: the 320 slots are given out once per (button, key) pair for the whole run. Someone who rebinds a lot can use them up, and then key_slot silently falls back to the Switch's icon. A single hos_log the first time it runs out would make that easy to diagnose.

One heads-up: #1990, also open, raises HOS_PAD_N, which is the stride of the BIND_BASE codes this PR builds on. Whichever of the two lands second needs to re-check text_code and the slot/BIND_BASE overlap test.

@Needicla

Copy link
Copy Markdown
Contributor Author

Thanks for the careful review, @Joey-Magnus. Addressed in 81a5445:

Prompt packages outside their files: nx2/prompt_keys.py is now data only (key → icon, the stick table, the code points) and imports nothing but the standard library. All the drawing (word keys, mice, keyboard_glyphs) moved into nx2/prompt_draw.py, so CONTRIBUTING's list stays as it is. The font built is glyph-for-glyph the same as before the move (checked on a v2.1.0 romfs).

third_party/: I went with the literal reading. The Keyboard & Mouse font is in third_party/kenney_input_keyboard/ with its own PROVENANCE.md and the release's License.txt; it's byte-identical to the one in the same 1.5A release as the controller fonts. third_party/kenney_input/ is back as it was. scripts/linux/appimage.sh copies the new folder, and tests/test_appimage_prompt_fonts.py now checks both.

Slots running out: key_slot logs once the first time all 320 are given out, saying a key bound from then on shows the Switch's icon in texts until the next start.

#1990: noted. Whichever lands second re-checks text_code and the slot/BIND_BASE range checks in test_prompt_font_bind.py. I'll do that here if #1990 goes in first.

Testing: agreed, the in-game run so far covered the artwork, not this branch's host code. I'll run a build of this branch through the four cases you list (Keyboard picked, Auto with no controller, a pad plugged in and out, a key rebound with a hint on screen), then update the description with the results and screenshots from that build.

run_all.py shows the same results with and without the PR on this machine; the prompt checks (prompt_font_bind, prompts_live, appimage_prompt_fonts) pass.

Needicla added a commit to Needicla/Hookshot that referenced this pull request Nov 11, 2026
Needicla added a commit to Needicla/Hookshot that referenced this pull request Nov 11, 2026
Needicla added a commit to Needicla/Hookshot that referenced this pull request Nov 11, 2026
Needicla added a commit to Needicla/Hookshot that referenced this pull request Nov 11, 2026
@Needicla

Copy link
Copy Markdown
Contributor Author

@Joey-Magnus I ran a build of this branch (81a5445) in game; the results and screenshots from that build are now in the description. Auto with no controller, Keyboard with a rebound key, and Xbox all show what they should at the start.

The live cases didn't run, for a reason outside this PR. @gab9281, this one may interest you (#2366): on native Windows 11 (RTX 4060, the v131072 game) the font part of the live prompts is never found. The textures' pool and the layouts are found:

prompts: the textures' pool at 0x7fc3005000 (+0xe86c000): a new face or new controls show at once
prompts: the layouts at 0x7fe1a31000: the Talk ring and the ocarina hint follow at once

But prompts: the font at ... never comes, even after the pause menu and its pages have drawn their button icons. So hos_prompts_live_parts() lacks HOS_PROMPTS_LIVE_TEXTS, the Button prompts row says "(restart)", and no change is applied live. The build logged the glyph hook as installed (oot_patches: the glyphs' hook in code_017.c).

To rule this PR out, I served a font built without the keyboard face (--faces switch,xbox,ps4,ps5,steamdeck, 33460 bytes, the size #2366 was tested at) to the same build: same result. Your Windows runs were under GE-Proton; this is native. My guess is that the face's stream there isn't the served file's size (base[1] != font.n && base[1] != font.n - 8 in hos_prompts_glyph), but I haven't confirmed it. I can run a build with a log line there if that helps.

@gab9281

gab9281 commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

I don't have any windows PC anymore, sadly 😅

@Joey-Magnus Joey-Magnus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This will turn main red on merge. #2448 landed today with tests/test_prompt_auto_face.py:51, which requires choose_face to contain face_of_name(name). This PR changes that line in host/prompts.c to auto_face(name). Fix the test to look for auto_face(name) in choose_face and face_of_name(name) in auto_face.

Everything else looks right: all five bind[HOS_SW_N] arrays are widened to BINDS, the enum-only vk_api.h change needs no version bump, and the earlier points are addressed. Still pending: the promised in-game run (Auto with no pad, plugging a pad in and out, rebinding with a hint on screen) and a CI run, which hasn't happened on 81a5445.

@Needicla

Needicla commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor Author

I don't have any windows PC anymore, sadly 😅

No worries! I can't dig further on my side right now either. The log lines above are what a native Windows run shows. If someone with a Windows build wants to look, a log line in hos_prompts_glyph comparing the face's stream size (base[1]) with font.n should show whether it's the size check.

@Needicla

Copy link
Copy Markdown
Contributor Author

@Joey-Magnus thanks, good catch. Rebased onto main (with #2448), and in b3bb874 tests/test_prompt_auto_face.py now looks for auto_face(name) in choose_face (and in live_face) and face_of_name(name) in auto_face. It also checks auto_face itself: with no controller, the keyboard's face when the build made it and XInput has no pad either, else the Switch's; with a controller's name, that controller's face. It passes, as do prompt_font_bind, prompts_live, appimage_prompt_fonts and menu_hooks. On this machine run_all.py shows the same results as main, apart from test_prepare_at_build's timing case, which failed once under the parallel run and passes on its own.

On the in-game run: it's in the description, from a build of 81a5445, which is the same code before this rebase. Auto with no controller, Keyboard with a rebound key, and Xbox all show what they should at the start. The live cases (rebinding with a hint on screen, plugging a pad in and out) didn't run: on native Windows the font part of #2366's live path is never found, with or without the keyboard face (see the comment above), and I have no controller here. CI still needs a maintainer's approval to run.

@Joey-Magnus Joey-Magnus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

b3bb874 fixes what would have turned main red. The branch is rebased on #2448. tests/test_prompt_auto_face.py now looks for auto_face(name) in choose_face and live_face and for face_of_name(name) in auto_face, and it tests auto_face's keyboard cases. At the new head, test_prompt_auto_face, test_prompt_font_bind, test_prompts_live, test_appimage_prompt_fonts and test_menu_hooks all pass for me, and the branch merges cleanly with main.

The earlier points are done: the drawing is in prompt_draw.py, the font is in its own third_party/kenney_input_keyboard/ with provenance and license, and key_slot logs once when its codes run out. The in-game run covers Auto with no pad, a rebound key and a pad face. Plugging a pad in and out, and rebinding with a hint on screen, still weren't tested. Your comparison points to #2366's font lookup on native Windows rather than this PR; that deserves its own issue. What's left is a full CI run on b3bb874. The #1990 HOS_PAD_N heads-up still applies.

@hckrman101

Copy link
Copy Markdown
Collaborator

This now conflicts with main in gpu/vk/vk_menu.cpp, host/prompts.c, readme.md, tests/test_prompt_font_bind.py. 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.

@polackangry

Copy link
Copy Markdown
Contributor

Tested this branch (b3bb874) on native Windows 11 (RTX 5060 Ti, v131072), keyboard + mouse, through the control bridge:

  • Auto/Keyboard at the start: ✅ the load screen's Back shows Shift; the pause menu shows Q/R on the tabs, the mouse for Rotate, E for Select, Shift for Back.

The live part not finding the font on native Windows: found it. Your guess about the size check was close: the face's stream has the served file's size (55088 = font.n), so hos_prompts_glyph read from base + 8. But the font starts at base: the bytes at +8 were 00 03 00 30, the OpenType header's entrySelector/rangeShift, against the head 4f54544f ("OTTO"). It's in main's code from #2366, not this PR, so the fix is a separate PR: #2583.

With that fix on top of this branch, Keyboard → Xbox → Keyboard while the game runs applies at once (now the xbox icons in texts / now the keyboard icons in texts), and the pause menu reopened shows each face's icons.

Needicla and others added 3 commits November 12, 2026 04:19
…texts, hints and menus

The button prompts showed a controller's buttons even to players on the keyboard and mouse. A Keyboard face (the Button
prompts row's seventh value, and Auto's choice with no controller connected) shows the key or mouse button each action is
bound to, follows the Controls page while the game runs as the pad faces do, and shows WASD and the mouse for the sticks.

- nx2/prompt_keys.py: the keys from Kenney's Input Prompts Keyboard & Mouse font (CC0); the keys whose names are words in
  the game's own bold font, and the mouse's buttons, drawn; the code points (past every face's, and a range for texts no
  font of the game uses).
- nx2/prompt_assets.py: the keyboard face in the font (the Switch's pad icons, its keys and sticks), its ocarina hint by
  key.
- host/prompts.c: the keys bound followed with the pad inputs; the keyboard face's icons at the start and after a change
  (a text's code per button and key given out as they come); the Switch's textures and Talk ring places; Auto's keyboard
  face only with no controller through SDL or XInput (host/nn_hid.c hos_xinput_pads).
- gpu/vk/vk_menu.cpp: the Keyboard value and its hint.

Part of NotRexed#1007

Assisted-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er of its own, a log when the slots run out

- nx2/prompt_keys.py is data alone (the standard library imports it); the drawing (the word keys, the mice, the keyboard face's
  glyphs) is in nx2/prompt_draw.py, where CONTRIBUTING allows the prompt packages. The font built is the same.
- The Keyboard & Mouse font in third_party/kenney_input_keyboard with its own PROVENANCE.md and the release's License.txt;
  third_party/kenney_input as it was. The AppImage copies it (tests/test_appimage_prompt_fonts.py checks both).
- host/prompts.c key_slot: a log line the first time the codes for buttons on keys run out.

Assisted-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…keyboard face

tests/test_prompt_auto_face.py looks for auto_face(name) in choose_face and live_face and face_of_name(name) in auto_face, and
checks auto_face: with no controller the keyboard's face when the build made it and XInput has no controller either, else the
Switch's; a controller's name its face.

Assisted-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Needicla
Needicla force-pushed the feat/keyboard-prompts branch from b3bb874 to 0a76662 Compare November 12, 2026 02:22
@Needicla

Copy link
Copy Markdown
Contributor Author

@hckrman101 rebased onto main (55b976c); the branch is at 0a76662 and merges cleanly. The conflicts were all with #1990's second gamepad binding:

prompt_font_bind, prompt_auto_face, prompts_live, appimage_prompt_fonts, menu_hooks and bound_keys pass. run_all.py matches main here, apart from three .NET builder checks that fail under the parallel run on this machine and pass on their own.

@polackangry thanks for testing it and for finding the font offset! I've added your live result with #2583 to the description.

@NotRexed
NotRexed merged commit b2456c6 into NotRexed:main Nov 12, 2026
2 checks passed
NotRexed pushed a commit that referenced this pull request Nov 12, 2026
… stream's start (#2583)

On native Windows the live button prompts (#2366) never find the font,
so a change of face or of controls waits for a restart (seen in #2435's
testing). The game keeps the font there from its stream's start with the
stream's size the file's, and `hos_prompts_glyph` looked 8 bytes in, for
a header that isn't there.

## What it does

`hos_prompts_glyph` (host/prompts.c) takes a stream of the served font's
size and looks for the font's head where it looked before (behind the
8-byte header when the size is the file's), then at the stream's start.
Whichever holds the head is `font.at`. A face whose bytes aren't
readable yet is looked at again, as before; one with neither head is
another font, as before.

## Why

A diagnostic build on native Windows 11 (RTX 5060 Ti, the v131072 game)
logged each face FreeType draws from, its stream's base and size:

```
prompts-diag: face 0x107e504c00 stream 0x107e4f9370 base 0x7fd37800e8 size 55088 (want 55088 or 55080)
prompts-diag: face 0x107e504c00 has the size but not the head (00030030 vs 4f54544f)
```

The size is the served file's (55088), so the code read from `base + 8`.
The bytes there, `00 03 00 30`, are the OpenType header's own
`entrySelector` (3) and `rangeShift` (48): the font starts at `base`,
with no header in front. #2435's description and comment guessed a size
mismatch; the size matches and the start is the difference.

## Testing

In game, native Windows 11, RTX 5060 Ti, keyboard and mouse, driven with
the control bridge (headless), a save in the Fire Temple:

| Case | Before | After |
|---|---|---|
| `prompts: the font at ...` | never logged | `prompts: the font at
0x7fd37800e8: the icons in texts follow at once` |
| Keyboard → Xbox while the game runs (#2435's branch) | held for a
restart | `now the xbox icons in texts`; the pause menu reopened shows
LB/RB, A, B |
| Xbox → Keyboard while the game runs | held for a restart | `now the
keyboard icons in texts`; Q/R, E, Shift again |

Tests: `test_prompts_live`, `test_prompt_font_bind`, `test_menu_hooks`
pass. The fix was tested on top of #2435's branch, the same function as
on main.


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

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

Upstream's main fails 15 of its own source checks at c356bef (PRs merged without a
rebase). These are the eleven outside the upscalers:

- vk_memory.cpp: RX_VK_CAPTURE_KEEP_OLD and RX_VK_CAP_BLOCKS are on/off switches and
  were read as set-means-on, so =0 turned them on (NotRexed#2437). env_on, as the rest.
- mouse_native.c: the mouse's kept angles are let go when the camera leaves states 3
  and 4 (first-person aim, Z-targeting): they were written back on return, and the
  view snapped to where it was before (NotRexed#2617; test_mouse_native's yaw).
- menu_i18n.h: three English texts changed in vk_menu.cpp (NotRexed#1930, NotRexed#2362, NotRexed#2435), so
  their 13 translations are keyed and translated again (machine-made, as the file's
  others).
- Fixtures that cut source by its text and missed what the source gained:
  window_title (NotRexed#2116), dead_import_bytes (NotRexed#1906), env_on in reverb_frame,
  hos_xinput_pads (NotRexed#2435), wait_recorded's forward declaration (NotRexed#2458); and the
  Dockerfile check's parser, which took --no-update for a PPA.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
hckrman101 pushed a commit that referenced this pull request Nov 12, 2026
**What this is for:** restore ten game-free checks whose extracted
fixtures or expectations no longer match intentional changes on main;
[main's CI
log](https://github.com/NotRexed/Hookshot/actions/runs/43151254256)
shows these failures. **What is measured:** those ten checks on Linux
with GCC 16.2.1 and Clang 23.1.3, plus the strict full suite and
deliberate regression mutations, as detailed below. **Not measured:** a
game run, Windows execution, audio output or GPU behavior; no production
file changes in this diff.

## Cause and changes

Each row is a separate commit, naming the change that made the fixture
stale; the quoted diagnostics come from [main's failed
jobs](https://github.com/NotRexed/Hookshot/actions/runs/43149508038).

| Introduced by | Checks / CI diagnostic | Repair |
|---|---|---|
| #1544, after #2042's switch helper | `audio_aux`, `audio_biquad`:
`implicit declaration of function ‘env_on’` | Include the real helper
before the extracted DSP. |
| #2116 | `display_startup_failure`, `wayland_libdecor_hint`: `use of
undeclared identifier 'window_title'` | Extract the real title helper
along with its callers. |
| #1906 | `texel_buffer_ownership`: `use of undeclared identifier
'dead_import_bytes'` | Model the counter and check that it stays charged
until completion. |
| #2458 | `readback_part`: `extraneous closing brace ('}')` | Select the
complete `wait_recorded` definition, not its new forward declaration. |
| #2574 | `dlss_checks`: `FAIL under a third wide at line 40`;
`framegen_preset` / `rumble_menu`: undeclared `temporal_reset_failure` |
Check both sides of the intended two-pixel tolerance; supply a counted
reset hook and exercise the real scaling rows. |
| #2435 | `mods_size_table`: `undefined reference to hos_xinput_pads` |
Supply the fixture's no-controller answer; retain real merge and routing
checks. |

The diff retains the existing assertions: no check is deleted or
skipped, and it does not revert the production behavior named above.

## How it was tested

Recorded on 2026-11-11, Linux kernel `7.2.15-1-omarchy`; base
`de866017`, head `eeb09c09`, GCC 16.2.1 and Clang 23.1.3 (compiler
version output and test records).

- Untouched main `a016b7a6`: `455 passed, 15 failed, 20 skipped`; each
of these ten failures also reproduced alone with both compilers,
matching the linked CI diagnostics.
- Branch: all ten repaired checks pass independently with each compiler
(`python -I -B tests/run_all.py --strict --jobs 1 <test>` records).
- Negative controls: 14 deliberate source regressions were each rejected
with both compilers, without a compile failure—28 behavioral rejections
(mutation records). These remove Aux sizing, biquad filtering, startup
failure handling, the libdecor hint, view ownership release,
part-specific waits, preset preservation, temporal reset or mod-table
routing; the DLSS controls independently make each axis's tolerance too
narrow and too broad.
- One full `python -I -B tests/run_all.py --strict` run: **`464 passed,
6 failed, 20 skipped in 141 s`**, 32 workers, load 7.24 → 28.38
(complete suite output).

### Remaining failures and the path to green

The complete branch run names these six failures; the first five are
outside this fixture-only diff:

| Remaining check | Separate fix |
|---|---|
| `test_env_on.py` | `fix/capture-switch-values`: the three
presence-only capture switch reads from #2437. |
| `test_linux_docker_packages.py` | `test/docker-ppa-options`:
distinguish `--no-update` from its PPA operand. |
| `test_mouse_native.py` | `fix/mouse-camera-handover`: discard retained
angles while another camera owns the view. |
| `test_menu_i18n.py` | `fix/menu-hint-translations`: update three
complete hint keys and their translations. |
| `test_hdr_encode.py` | Already repaired in #2614; deliberately not
duplicated here. |
| `test_release_scripts.py` | An intermittent, unchanged release-script
concurrency failure: `SHA256SUMS still differs from the files after 5
uploads`. It passed untouched main and current main CI; isolated reruns
passed once and failed once. Neither run invokes a C/C++ compiler. |

## Not tested / risks

No Windows, game, display, controller, listening or GPU run was
performed; the evidence above is game-free only. The fixture extraction
still follows source structure, so future helper/signature changes must
update its inputs rather than remove its behavioral guards.

## Overlaps

The inspected merge hunks overlap #1834's frame-generation fixture
helpers and #2622's older readback extraction; neither contains these
repairs. #2614 owns the separate HDR initializer check. Other same-file
changes merge cleanly in the recorded overlap comparison.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <noreply@openai.com>

---------

Co-authored-by: GPT-6 Astra <noreply@openai.com>
hckrman101 pushed a commit that referenced this pull request Nov 12, 2026
**What this is for:** make the current controls, automatic mesh-shader
and automatic button-prompt hints find their translations ([current main
CI](https://github.com/NotRexed/Hookshot/actions/runs/43151254256)).
**What is measured:** the affected game-free check before and after,
with GCC 16.2.1 and Clang 23.1.3 selected, plus one strict full-suite
run, as recorded below. This check is Python-only; the two environment
runs do not claim compiler coverage. **Not measured:** a game run or
Windows execution.

## Cause

#1864 imported all thirteen catalogs after #1930, #2362 and #2435 had
changed those three complete English hints; runtime lookup is exact, so
their old keys no longer match (`gpu/vk/menu_i18n.h`, `vk_menu.cpp`,
`vk_menu_i18n.cpp`).

The relevant failure in [main's CI
log](https://github.com/NotRexed/Hookshot/actions/runs/43151254256) is:

```text
'Gamepad: the D-pad moves, A binds, B goes back.' is no text of vk_menu.cpp (renamed? then translate it again)
```

## What changed

Update all 39 keys and their full translations; leave English help and
runtime lookup unchanged. Retain exact membership, order, count,
formatting and whitespace checks, and require all three current hints in
every language so deleting an entry cannot hide the mismatch.

## How it was tested

Recorded on 2026-11-11, Linux kernel `7.2.15-1-omarchy`, base
`de866017`, head `f9a7e70f` (version and test records).

- Untouched main `a016b7a6`: the affected check fails independently in
both compiler environments with the diagnostic above; the branch passes
independently in both (`python -I -B tests/run_all.py --strict --jobs 1
tests/test_menu_i18n.py` records).
- Each old key was restored independently, and each replacement was
separately removed with its table count reduced: all six mutations fail
in both environments (12 records), including the new completeness check.
- One full `python -I -B tests/run_all.py --strict`: **`456 passed, 14
failed, 20 skipped in 162 s (linux, 32 at once)`**, load 81.38 → 42.70
(complete suite output).
- That run's remaining failures are: `test_wayland_libdecor_hint.py`,
`test_display_startup_failure.py`, `test_texel_buffer_ownership.py`,
`test_audio_aux.py`, `test_audio_biquad.py`, `test_dlss_checks.py`,
`test_env_on.py`, `test_framegen_preset.py`, `test_hdr_encode.py`,
`test_linux_docker_packages.py`, `test_mods_size_table.py`,
`test_mouse_native.py`, `test_readback_part.py`, `test_rumble_menu.py`
(complete suite output). The companion fixture, capture-switch,
PPA-parser and camera-handover branches address their separate causes;
#2614 owns the HDR check and is not duplicated here.

## About the translations

The added clauses in the 39 entries were written with a language model,
in the wording of each catalogue's existing entry (the part that was
already translated is kept word for word). Nobody who speaks these
languages natively has read them; corrections are welcome and will not
break the check, which compares only the English keys.

## Not tested / risks

No game or layout screenshot was taken, and no native-speaker review was
performed; the checks establish key coverage and structural consistency,
not linguistic or visual polish.

## Overlaps

#2417 is an alternative localization implementation, not a repair of
these catalogs; it and #2566 merge cleanly with this change in the
recorded overlap comparison.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <noreply@openai.com>

Co-authored-by: GPT-6 Astra <noreply@openai.com>
LNDF added a commit to LNDF/Hookshot that referenced this pull request Nov 12, 2026
- main's 19 is NotRexed#1930's ocarina note bindings (a changed meaning of the pad
  binding calls, no new field); MkwVkHost.data_dir, still the last field and
  last in the host's initializer, is 20.
- host/prompts.c: NotRexed#2435's keyboard face; choose_face takes the game folder
  again for prompts/keyboard (a build input), the options stay the data
  folder's. NotRexed#2435 adds no generated file of its own: all still in
  cache/prompts.
- host/nn_hid.c, docs/development.md: main's text (the ocarina's note
  controls, the RX_VK_FALLBACK_PHI_LIMIT row) with the data folder's places.
- tests/test_ocarina_note_bindings.py, tests/test_ocarina_notes.py (NotRexed#1930):
  stub hos_data_path as tests/test_bound_keys.py does.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants