Repository navigation
fix(prompts): find the prompts' font on native Windows, kept from its stream's start - #2583
Merged
NotRexed merged 1 commit intoNov 12, 2026
Conversation
… stream's start On native Windows the face's stream has the served file's size but holds the font from its start, with no 8-byte header in front. hos_prompts_glyph read from base + 8, never matched the font's head, and the live button prompts (NotRexed#2366) waited for a restart. Look for the head at both places. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NotRexed
pushed a commit
that referenced
this pull request
Nov 12, 2026
…texts, hints and menus (#2435) 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 #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.  Keyboard with A rebound to F: Select reads F.  Xbox, for comparison (a pad face, as before this PR).  The field: the HUD's item buttons on R and X.  🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Needicla <226049938+Needicla@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_glyphlooked 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 isfont.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:
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 ownentrySelector(3) andrangeShift(48): the font starts atbase, 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:
prompts: the font at ...prompts: the font at 0x7fd37800e8: the icons in texts follow at oncenow the xbox icons in texts; the pause menu reopened shows LB/RB, A, Bnow the keyboard icons in texts; Q/R, E, Shift againTests:
test_prompts_live,test_prompt_font_bind,test_menu_hookspass. The fix was tested on top of #2435's branch, the same function as on main.🤖 Generated with Claude Code