Repository navigation
feat: the Insert menu in 13 languages, with Game language and Menu la… - #1864
Conversation
…nguage rows The game itself ships in 16 languages, but its settings menu was English only: a player who plays in Portuguese, Japanese or German gets every option and help line in English. - gpu/vk/menu_i18n.h: one table a language (Portuguese (Brazil), Spanish (Latin America and Spain), French, German, Italian, Dutch, Polish, Russian, Japanese, Korean, Chinese (Simplified and Traditional)), each English text of vk_menu.cpp and its translation, about 400 a language, the Audio and Profile rows of NotRexed#1825 too. Place and item names stay English for now. - vk_menu_i18n.cpp looks a text up whole; a text the menu made with snprintf is matched against the table's printf formats ("%d of %d") and put together from the translated one. ui::text, ui::para, ui::size_of and ui::spaced (vk_ui.h) look every text up, so no row needs a call of its own; spaced and para step by whole UTF-8 characters. - Menu language row (Gameplay): Auto follows the game's language (language.txt; fr-CA and en-GB take French and English), or a language by its own name. Saved as menu_language. Game language row: the game's 16 languages, written to language.txt for the next start (a restart row); not kept in the options file. - Japanese, Chinese and Korean letters: the game's own fonts for them (FZLTHB, DFPT_C8, AsiaKKRST, from the player's files as the menu font is) merged into the menu's fonts, then the system's (Yu Gothic, Microsoft YaHei/JhengHei, Malgun Gothic; Noto CJK on Linux) for what they lack. Japanese takes the system's alone: the game's is a subset, and its kanji mixed with the system's in one word. Other languages get Segoe UI / DejaVu Sans behind the menu fonts for letters they lack. Merged once a language. - tests/test_menu_i18n.py: every table text is one of vk_menu.cpp's, every translation keeps its printf conversions in order and its leading and trailing spaces. test_rumble_menu, test_framegen_preset and test_stick_dead_zone cut set_row and menu_init's loop out of vk_menu.cpp: their stubs gain the Game language row's hook. The translations are machine-made and want checking by speakers of each language: each is one line in menu_i18n.h. One English text is two things: "Switch" is the console's name in Button prompts and the Switch age tool's verb, so it stays English for both. Tested on an RTX 3060 Laptop, Windows 11: the menu in Portuguese, Japanese, German and Chinese (in play and in RX_VK_SHOT_MENU shots), the Game language row writing language.txt and offering the restart. The suite run on Windows here fails the same tests with and without this change (local tools); the menu's own tests pass. Played on 52ac35c; rebased on 51de681, where the renderer does not link yet with or without this change (present_commit and present_defer_into are declared, not defined: NotRexed#1702?). Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hckrman101
left a comment
There was a problem hiding this comment.
Nice piece of work: the lookup lives in ui::text/para/size_of/spaced, so no row needs its own call, and falling back to the English text when a key is missing is the right default. I read the plumbing, fonts, layout and tests and spot-checked translations (pt-BR, de, fr, ru, ja, zh-Hant); I did not build or run it.
Blocking
- The branch conflicts with main (
mergeStateStatus: DIRTY), so no CI has run on it. The description also says the renderer does not link on the base it was rebased on (present_commit/present_defer_into). Please rebase and let CI run before this is judged. - #2078 renames the Ultrawide label.
test_texts_are_the_menuswill then fail for the 9 languages that translate "Ultrawide", which is the intended tripwire, but whoever merges second has to re-translate the new text. Worth merging #2078 first, or coordinating. The test only checks table keys against the menu, so a menu text that has no table entry (or loses it after a rename) silently falls back to English. Fine for a first version.
Checked and fine
- Option ids: the two new rows are menu-only (
opts.game_lang,opts.menu_lang) and are not sent to the host, so no HOS_OPT id is needed and nothing collides with 10/44 or the ids in use.menu_languageis the generated ini key (fits the 32-byte buffer),game_langis skipped on save and load, and both rows arepreset_exempt. - Fonts: ImGui is 1.92.x (dynamic atlas), so merging fonts before
NewFrameon first use is fine, andload_fontsonly runs once, so thedonelist cannot go stale. Missing system fonts only log "may show as boxes".expand()returns "" for%WINDIR%off Windows, so the Linux path list is safe. tr()pointer lifetime (two generations of 4096) is sound; no lock is held acrosstr_text/table()in a way that can deadlock.spaced/paranow step by whole UTF-8 characters. Clip rects around the rows keep overlong German or Russian text from drawing outside its column, and the Off/On halves cell picks its layout from the translated widths.- The tests that cut
set_rowout ofvk_menu.cppgot the stub they need.
Non-blocking
- The menu ignores
HOS_LANGUAGE(the host prefers it overlanguage.txt,host/nn_applet.c) and compares the code exactly, while the host acceptspt-brin any case. In both cases Auto and the Game language row would disagree with the language the game actually runs in. See the inline notes. - With Menu language on Auto, stepping through the Game language row changes the whole menu under the player on every step (and reads a 10-20 MB CJK font the first time it reaches ja/ko/zh).
language.txtis also rewritten on every step, and "Later" on the restart question does not restore it. A write that fails (read-only folder) still updates the in-memory value, so the row looks changed. Consider keeping Auto on the language the game started in until restart, and writing only when the row is left. - Plural forms: the restart line builds "N more settings" from one singular and one plural. Russian ("5 параметра") and Polish ("5 ustawienia") need a third form for 5+. Small, as N is a handful of rows.
- Perf nit:
tr()builds astd::stringkey for each lookup, and takes a global mutex once any format exists (always), every frame for every text. Hundreds of lookups are fine; astring_viewkey and a lock-free exact path would avoid the allocations. - Nit: Linux font paths cover Debian/Arch only; Fedora has
/usr/share/fonts/google-noto-cjk/...and/usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf. - Translations look natural in the ones I sampled (Restart needed, Menu language, Game language, setting/more). The README note that they want native review is the right call.
| std::string path(exe, n); | ||
| return path.substr(0, path.find_last_of("\\/") + 1) + "language.txt"; | ||
| } | ||
| std::string& game_language_now() { |
There was a problem hiding this comment.
Only language.txt is read here. HOS_LANGUAGE overrides it in the host (docs/development.md, host/nn_applet.c), and the host also accepts the code in any case (pt-br). With either, this returns a code that is not the game's, game_lang_index() falls to 0 and Auto picks English. Reading HOS_LANGUAGE first and comparing case-insensitively would match the host.
| if (game_language_now() == kGameLangs[i].code) return i; | ||
| return 0; | ||
| } | ||
| void set_game_language(int index) { |
There was a problem hiding this comment.
This runs on every Left/Right step of the row, and the result of a failed fopen(..., "w") is ignored while game_language_now() is updated anyway, so the row can show a language that was not saved. With Menu language on Auto the menu also switches language on each step. Maybe write when the row is left, or only report success after the write.
With Ultrawide off, the Graphics tab showed "Ultrawide: 16:9". On a 3440x1440 monitor that read as if ultrawide were already on, so the game stayed at a 16:9 2560x1440 with bars at the sides, and 100% render resolution stayed well below the screen's width. The off value now reads "Off (16:9)". "Monitor's shape" is unchanged, so the translations in #1864 still match it. The readme's settings table follows. **Testing:** `tests/test_menu_restart.py`, `tests/test_output_resolution.py` and `tests/test_widescreen_shapes.py` pass on Linux. It is a label change only; I have not seen it in the game yet.
Conflicts: gpu/vk/vk_menu.cpp (the menu's includes; the options file writes Output as its size and leaves the Game language row out), gpu/vk/vk_ui.h (size_of looks its text up, and main's fit_px), tests/test_stick_dead_zone.py (main's Options stub with game_lang). The translation tables follow in the next commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ved when left, and main's new texts translated From NotRexed#1864's review: - The menu reads the game's language as host/nn_applet.c does: HOS_LANGUAGE over language.txt, any case (pt-br is pt-BR), a language.txt line the game does not know (or a BOM) passed over, English (US) when neither names one. - Menu language Auto stays on the language the game started in until the restart, so stepping through Game language no longer changes the whole menu under the player (nor loads a CJK font at each step). - Game language writes language.txt once the row is left (another row, page or tab, or the menu closed), not at every step. A write that fails (a read-only folder) is logged and the row goes back to what language.txt says, so it never shows a language that was not saved. set_game_language now says whether it wrote. - "N more settings" takes Russian's and Polish's third form for 5 and up (i18n::plural; " settings (many)"). - tr()'s table lookup makes no std::string (string_view keys of the tables' own texts) and takes no lock once the table is made (call_once); only a text made from a format still goes through the cache's lock. - Linux: Fedora's Noto CJK (both package layouts) and DejaVu Sans paths. Translations: the 15 texts main renamed or dropped are gone from the tables, and main's 64 new texts (the Mesh shaders, V-Sync, Anisotropic filtering, Text size, FPS counter position and Movement speed rows, the FPS counter's breakdown, the reworded Upscaler and Render resolution hints, Off (16:9) and the others) are translated in all 13 languages, reusing each table's own words for the rows they name. Machine-made, as the rest: to be checked by speakers. Tests: test_menu_i18n passes on the merged menu and checks the plural forms; test_rumble_menu, test_framegen_preset and test_stick_dead_zone pass (clang); vk_menu.cpp, vk_menu_i18n.cpp and vk_imgui.cpp compile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Joey-Magnus
left a comment
There was a problem hiding this comment.
This and #2417 can't both land. Both add gpu/vk/vk_menu_i18n.h and tests/test_menu_i18n.py, and both send the same vk_menu.cpp texts through a lookup. #2417 has 16 languages, covers the startup screen and is mergeable with green CI, apart from the points still open in its review. This one has things #2417 lacks. It has the Game language row that writes language.txt, and the game's own CJK fonts (FZLTHB, DFPT_C8, AsiaKKRST) with system fallbacks. It also follows host/nn_applet.c's HOS_LANGUAGE rule and has plural forms for Russian and Polish. a8911f1 addressed every point from hckrman101's review, and CI was green on it. It conflicts with main in vk_menu.cpp again. I'd like NotRexed to pick one as the base. Whichever loses, its extras would make a good follow-up on top of the winner, not a second table. The translations in both are machine-made and still need native review.
|
Thanks for the thorough review. Done in a8911f1, with main merged in d910595 (conflicts in vk_menu.cpp, vk_ui.h and test_stick_dead_zone.py; no force push). |
… first one takes the system's (#2184) (#2284) A game built through RecompLauncher is always in English, and a language the player set in `language.txt` goes back to English the next time the launcher builds the folder (#2184). ## Why The launcher builds with `--cli --prepare --game <copy>` (`recomplauncher.json`): no window, and no `--language`. `make_game.py` took `en-US` for that and wrote it to `language.txt` at the end of every build. Only `--update` read the folder's own language first. So the one way a launcher's player has to change the language, the code in `language.txt` (the file says so itself, and the readme's "wrong language" row), lasted until the next build. Anything else that writes the player's choice to that file is undone the same way; the "Game language" row of #1864 would be. ## The change With no `--language`, a build now takes, in this order: 1. the language the folder has: the build before, or the player's own change to `language.txt`; 2. with `--prepare` and no folder yet: the game language closest to the system's (on Linux `LANGUAGE`'s first entry unless the locale is C, else `LC_ALL`, `LC_MESSAGES`, `LANG`, as gettext reads them; on Windows the display language, the first of `GetUserPreferredUILanguages`, as the builder's `CurrentUICulture`), by the rule the Windows builder's window uses for its first choice (the exact one, then the family; `es-MX` is Spanish (Latin America), `zh-TW` Chinese (Traditional)); 3. `en-US`, as before. A `--language` given is still the one built, so both builder windows, which always pass theirs, are unchanged. `--update` keeps the folder's as before. A plain command line build without `--language` is still English into a new folder; into a folder built before it now keeps that folder's language rather than turning it English. ## Tested - Linux: `python -I -B tests/test_make_game_prepare.py`: 17 pass, 8 of them new. One runs `main` itself up to its first step with `--prepare` over a folder whose `language.txt` says `ru`: on `main` it fails with `'en-US' != 'ru'`, here it is `ru`. The others cover the order above, the system names (`de_DE.UTF-8`, `pt_PT`, `es_MX`, `zh_TW`, `zh-Hant-HK`, `C`, ...) and which of the three variables is read. - Linux: `python -I -B tests/run_all.py`: nothing new fails. - Windows 11 (Python 3.13.7): `python -I -B tests\test_make_game_prepare.py`, 16 pass and the Linux environment one skips. On that PC the display language is en-US and the regional format en-CA; `system_language()` reads `en-US`, where `GetUserDefaultLocaleName` (the rule before review) read `en-CA`. Not tested: a whole build through RecompLauncher (no launcher here), and a Windows locale the game has a language for. Part of #2184
Conflicts in gpu/vk/vk_menu.cpp: the includes (vk_menu_i18n.h and main's hookshot_env.h), and the options file, which writes main's saved_value() and still leaves the Game language row out (language.txt's). The tables follow next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The VRAM budget, Camera speed, HDR contrast, Frame generation by, Compact menu key and ASTC as RGBA8 rows, the Controls page's two gamepad inputs (Input 1, Input 2, the stick presses, the binding prompts and its help), the compact bar's prompts and the right Shift and Ctrl keys, in all 13 languages, with each table's own words for the rows and keys they name. The 5 texts main reworded (the Custom preset, Reflex, Controls help and binding prompts) are gone from the tables. Machine-made, as the rest: to be checked by speakers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ape by default (#2119) A player on an ultrawide, 16:10 or 4:3 screen starts with the game's 16:9 picture until they find the Graphics tab's **Ultrawide** row, change it and restart. The name also hides that the same row shapes the picture on screens narrower than 16:9. PC games usually follow the monitor's shape from the first start, with 16:9 as a choice. **What changed** - The **Ultrawide** row is now **Aspect ratio**, and **Monitor's shape** is its default (`gpu/vk/vk_menu.h`; `host/hos_main.c` reads an options file with no `ultrawide=` line as Monitor's shape). 16:9 stays a choice. On a 16:9 screen nothing changes. - The saved key stays `ultrawide=` (`ini_key`), so a saved `ultrawide=0` still means 16:9. - **Older options files on a screen narrower than 16:9.** The game saves every row, so a file from before this PR holds `ultrawide=0`, the old default, whether chosen or not. On a screen narrower than 16:9 (16:10 such as the Steam Deck's 1280x800, or 4:3), where 16:9 only adds bars above and below, that file's 16:9 moves to Monitor's shape; an ultrawide keeps its 16:9 (often a real choice there). The rule is one function, `hos_aspect_on` in `host/hos_wide_size.h`: `host/hos_main.c` starts the game with it, and the menu's row follows what the host started with (`host->aspect_ratio()`, in `menu_init` before `restart_init`), so the two agree on Linux too, where the menu has no screen size of its own. `save()` writes `options_version=6`, so a 16:9 chosen after the update stays. - The other value reads **16:9** again instead of #2078's "Off (16:9)". #2078 added "Off" because "Ultrawide: 16:9" read as if ultrawide were already on; under the name Aspect ratio the value is a shape, and "Aspect ratio: Off (16:9)" would not read well. Happy to keep "Off (16:9)" if you prefer it. - `recomplauncher.json`: the same label and help, and its default is `1`, so the launcher does not write 16:9 over the new default. - New help text for the row, and for **Ultrawide menus**, whose text named the old row. The readme's settings table and Output resolution notes, `menu_restart.h` and the comments that named the row follow (#2052's wording kept, with the new name). - Tests: `test_menu_restart.py` and `test_menu_restart.cpp` use the new name; `test_audio_output_menu.py` and `test_stick_dead_zone.py` give their option stubs the `ultrawide` field that `ini_key` now reads. New `test_aspect_default.py`: `hos_aspect_on` over 16:10, 4:3, 5:4, 16:9, 1366x768, 21:9, 32:9, portrait and no screen size, plus the host and menu source checks. **Tested** - In game on Windows 11, RTX 5090 (driver 621.14), 5120x2160 (21:9) monitor, in one build with the widescreen cap change from #2120: the picture takes the monitor's shape, and Kokiri Forest and Castle Town load. That run was before the merge with current main (#2052, #2041, #2078). This PR only moves a default and renames a row; the code that sizes the picture is unchanged. - `tests/run_all.py` on Windows (llvm-mingw) after merging main at 06c30bd (with #2197): 392 passed, 31 skipped, 1 failed: `test_vram_budget_row`, which fails the same way on main's own sources on this PC (clang stops on the unused `kVramMinimumOnly`). - A build of this branch compiled (`hos_main.c`, `vk_menu.cpp`) before that merge; the merge itself only adds the version 6 move next to #2197's. The move for older files was not run in game: this PC has a 21:9 screen, where an older 16:9 stays. The tests this PR touches pass, and so do the others that build `ini_key` or the row: `test_framegen_preset`, `test_menu_env_on`, `test_recomplauncher_json`, `test_rumble_menu`, `test_upscaler_rows`, `test_vsync_row`, `test_output_resolution`, `test_widescreen_shapes`. **Not tested** - 16:10, 4:3, 32:9 and 48:9 screens in game (`test_widescreen_shapes.py` covers their sizes). - Linux and the Steam Deck: SDL gives the screen's size there too, so a Steam Deck (16:10) now starts with a 16:10 picture instead of 16:9. - If #1864's translations name the row, they would need the new name. **Screenshots** From a headless run of this branch (head 2a74ec4) on Windows 11, RTX 5090, with a 5120x2160 (21:9) monitor and no options file, so a first start (`HOS_HEADLESS=1`, `RX_VK_SHOTS` at 2560x1080; the save loaded with `HOS_INPUT_SCRIPT` presses). The log: `widescreen: picture 3416x1440, scene 2560x1080, camera ratio 2.3722`.   _The menu shot is the middle 16:9 of the frame: behind it the title movie stays 16:9 with black sides. Its build had one extra line so the list scrolls headless (`ease_step = 1.0f` in `menu_record_image`), not part of this PR._ The widescreen cap PR (#2120) edited the same readme paragraph; it has merged, and the paragraph is merged with it here. **`options_version`:** #2197 (merged) took 5 for the Esc default, so this writes 6 and moves an older file's 16:9 from files before 6 (`version < 6` in `hos_aspect_on` and `menu_init`). #2110 also writes 5 today; whichever of the two lands later takes the next number. #2197's `test_menu_keys.py` compiles `menu_init`'s options read, which now also reads `host->aspect_ratio`, so its `host` stub gains that member. Its check of older files expected every version below the one `save()` writes to move Insert or F1 to Insert, F1 or Esc, which would move a version 5 file's chosen Insert or F1 again; it now reads the move's version from `menu_init` and checks that files from it up to `save()`'s version keep their choice. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: hckrman101 <239247847+hckrman101@users.noreply.github.com>
The Graphics tab is one list of 24 settings, with the window, HDR, upscaling, frame generation and GPU options mixed together, and about ten more are on their way in open PRs. This puts its rows under section titles, so the upscaler, frame generation or HDR can be found without scrolling the whole list. It is the first step of a menu organization laid out below for discussion. **What changed** - The Graphics rows in six sections, under heading rows like the Debug tab's Developer (a gold title, never selected, nothing to click): - Quality preset (first: it sets rows in every section) - **Resolution and upscaling:** Output resolution, Profile, Upscaler, DLSS model, Render resolution, Sharpness - **Frame generation and latency:** Frame generation, Frame pacing, Game frame rate cap, NVIDIA Reflex, AMD Anti-Lag 2 - **Screen:** Fullscreen, V-Sync, Window scaling filter, Aspect ratio (#2119), Ultrawide menus - **HDR:** HDR, HDR paper white, HDR peak brightness, HDR contrast (#2342), HDR tone mapper (#2481) - **Detail:** Anisotropic filtering - **GPU and memory:** GPU device, VRAM budget (#1859), Mesh shaders (Robust buffers is on Debug ▸ Developer since #2042) - Moves only: every row's definition is unchanged, word for word; no row is renamed and no value or saved key changes, so options files carry over, and #1864's tables (keyed by the English text) still find every row. They need the six headings. - No new drawing or input code: the menu already draws heading rows on any tab, and the keys, the pad and the mouse step over them. - `readme.md`: a sentence on the sections, and the option table in the new order. (Ultrawide menus and Mesh shaders have no line yet; #2105 adds them.) The **Frame generation by** row from #2118 gets its line in the table here too: main's readme has none yet. So do #1859's **VRAM budget** (under GPU and memory) and #2342's **HDR contrast** (under HDR). - `tests/test_upscaler_rows.py`: its order list follows the picture's path (output, upscaler, render size, sharpening, then the frame rows, then the window). New `tests/test_menu_sections.py`: the Graphics sections and which rows they hold, and on every tab no section that `row_shown` could leave empty (a title over nothing on a GPU without Reflex, say). **The plan this is step 1 of (for discussion)** Each tab answers one question; a section groups the rows you set together; a row that depends on another follows it; vendor rows sit beside their counterpart and hide where they can't run (no vendor sections, so nothing is left empty on AMD); rows to touch only when something is wrong go last; moves only, no renames. | Tab | Holds | Sections | |---|---|---| | **Game** (was Gameplay; first, where the menu opens) | how the game behaves and reads, and Restart and Quit, as a pause menu | flat; Restart and Quit last | | Graphics | how the picture is made, and what it costs | Quality preset · Resolution and upscaling · Frame generation and latency · Detail · GPU and memory | | **Display** (new) | how it gets to your screen, and what is drawn over it | Screen · HDR · Overlays (FPS counter, Menu size, from Game) | | Audio | sound out and in | + Microphone (from Game: it picks the input device) | | Controls | how you play | Camera · Controller · Buttons (optional) | | Debug | tools and developer switches | Robust buffers in Developer (#2042, merged) | Next PRs, one topic each: (2) a Display tab, taking the Screen and HDR sections over whole and adding Overlays; (3) the Gameplay tab titled Game, first in the strip, where the menu opens; (4) Microphone to Audio; (5) optional, sections on Controls. If a sixth tab is a no, this PR stands on its own. Open questions: 1. A sixth tab (Display)? 2. V-Sync on Display (the PC convention) or with frame generation (it changes with it)? 3. Sharpness with the upscaling rows (where players look for it) or with the window filter (where it runs)? 4. DLSS 5 (#1590) and #1834's Neural rows as a page opened from a Graphics row, like Motion, rather than a seventh tab? <details><summary>Where open PRs' rows would go</summary> - Resolution and upscaling: Sharpening method (#1979), DLSS 5 page (#1590), Native 3D resolution (#2017) - Frame generation and latency: Frame generation by (#2118, merged: in this section now), Frame generation method (#1834) - Detail: Shadow resolution (#1892), Screen filter and Filter strength (#1488) - GPU and memory: Texture streaming cache (#2017), Refresh memory (#888); VRAM budget (#1859, merged) is in this section now - Game (was Gameplay): Game language and Menu language (#1864), Skip title screen (#1560), Amiibo (#1521) - Controls: Camera speed (#1945) - #2042 (merged) moved Robust buffers to Debug ▸ Developer. - #2105: its readme check would read the heading rows as rows; it needs to skip `{TAB_..., "...", nullptr, 0, 0, 0, ..., true}` headings, or this PR adds that if it lands first. Whichever PR lands second moves one row on rebase; happy to do it for anyone. </details> **Testing** - `tests/run_all.py` on Windows 11 (llvm-mingw clang, 8 at once) after merging main at 2dfe901 (with #2119's Aspect ratio, #2481's HDR tone mapper, #2118's Frame generation by, #2342's HDR contrast and #1859's VRAM budget): 422 passed, 33 skipped, 2 failed, both outside this PR: `test_vram_budget_row.py` (fails the same on main's own sources here) and `test_portable_builder_startup.py` (its `dotnet publish` needs packages this PC does not have). A build of the branch compiles. All 81 of main's rows keep main's definitions word for word; the only additions are the six headings. `test_menu_sections.py` fails on main as it stands (no sections) and passes here; a layout with a section of Reflex and Anti-Lag 2 alone fails it. - A build of this branch (Windows 11, RTX 5090), headless with scripted menu keys at Small, Medium and Large: 12 Downs from the Quality preset land on V-Sync and 13 Ups come back past it to the last row, so every heading is stepped over both ways and Up from the first row wraps to the last; the help box always shows the selected row (at the first head, when Robust buffers was the last row). Again at this head (25feb16, after #2042, #1859 and #2119): Up from the Quality preset wraps to Mesh shaders, the last row now, under VRAM budget (the bottom screenshot). **Not tested**: in a window with a pad, the mouse wheel and a click just under a heading (the headless runs drive the keys); values kept across a restart (no row's saved key changes); AMD and Intel (their rows hide through `row_shown` as before; the new check makes sure no section can end up empty), Linux. **Screenshots** The Graphics tab at 1920x1080, Medium menu size: the top, then scrolled with the keys to V-Sync and to the last row (the selection steps over every heading; Up from the first row wraps to the last row). The bottom one is retaken at this head (25feb16, a 16:9 picture: `HOS_ASPECT=1.7778`, as on a 16:9 monitor); the others are from the first head (634ef65).    Small and Large:   _Taken from headless runs of this branch (`HOS_HEADLESS=1`, `RX_VK_SHOT_MENU=1`, the keys from `RX_VK_MENU_SCRIPT`), RTX 5090. Headless, the list never scrolls on main either: `menu_record_image` leaves `ease_step` at 0, so the build behind the scrolled shots had one more line there (`ease_step = 1.0f`, hence "-dirty" in the first head's build label), not part of this PR. The windowed menu eases as before._ 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
This now conflicts with main in |
|
I went through the Simplified Chinese entries in menu_i18n.h as a native speaker. The translation holds up well beyond what I expected — there is nothing in it I would actually change. I would like to see it in a release. |
…ess (#2347) **What this is for:** a player who meets a problem presses one row in the Insert menu, **Gameplay → Bug report**, and gets a folder with everything the game problem form asks for: the form's answers in `report.txt` (version, the map he is in, graphics card and driver version, CPU, RAM and system, the options changed and the switches set), this run's log and the run before's (the crashed one after a restart) with his home folder and user name taken out, and a picture of the game. Maintainers get reports with the log and the hardware from the start instead of asking for them. **What is measured:** on Linux (Omarchy, an Arch Linux; RTX 4090, NVIDIA 615.78.08), on `main` `ba6eef70`: the new test passes with g++ 16 and clang 23 and fails on `main`; its cases for the review's points (NUL bytes in a log, a picture that comes late, which switch values are written, the reason a folder cannot be made); `tests/run_all.py`; headless runs of this head that pressed the row (one pressed it in Kokiri Forest, one after a `kill -9` and a restart: both reports written, read back by a script against the logs they came from (no file holds the home folder or the user name; each log copy is the original line for line apart from those two), the after-crash report's `hookshot_log.1.txt` is the killed start's whole log, and its picture came through #926's `shot_ask`); the length GitHub takes for the link, signed out. **Not measured:** Windows at run time (compiled and linked only), a windowed run, the link opened in a browser (signed out or in), AMD and Intel drivers' strings, a Steam Deck. Refs #2346 (closed by the maintainer: tracked in this pull request). ## Problem - The game problem form (`.github/ISSUE_TEMPLATE/1-game-problem.yml`) requires the version, where in the game, the graphics card **and driver version**, CPU, RAM and operating system, and `hookshot_log.txt` (and `hookshot_log.1.txt` "if you started the game again after the problem"). The player has to find each one by hand. - `hookshot_log.txt` answers only part of it: its first line has the version, but the driver is only Vulkan's packed number (`vk: device 0: NVIDIA GeForce RTX 4090, Vulkan 1.4, driver 0x99d38200, type 2`), there is no line for the system, the CPU or the options changed, and nothing logged where the player was (the host reads `Sequence_CurrentBanc` only for the debug tools). - After a crash a player starts the game again, and the crashed run's log becomes `hookshot_log.1.txt` (`host/hos_main.c`, the rotation added because players sent the new run's log and the crash was gone). It still has to be found and attached. - The logs carry the home folder and the user name in their paths (`fs: mounted content: -> <home>/...`, `vk: pipeline cache <home>/...`), which a player may not want to post. - So reports arrive without the log or the hardware, and `docs/workflow.md` scores such a report at confidence 0.4 and asks for the log first. This gathers what CONTRIBUTING.md already asks ("Include the version, your hardware, where you were and `hookshot_log.txt`"; "Report the version you actually ran: ... the first line of `hookshot_log.txt`"). It changes nothing about where reports go. ## What changed - `gpu/vk/vk_bug_report.h`, `vk_bug_report.cpp` (new, 59 + 602 lines): the packer, without Vulkan and without the menu, so the test compiles it alone. It writes `bug-reports/<YYYY-MM-DD_HH-MM-SS>/` in the game folder on a thread of its own: - `report.txt`: the form's questions under the form's own `### ` headings, so `scripts/hardware_report.py`'s `section()` reads it as an issue: **Hookshot version** (`HOOKSHOT_VERSION`, as the log's first line); **Where in the game** (the last `map:` line of this run's log, and the run before's, and a request to add what you were doing); **Graphics card and driver version** (the device and `VkPhysicalDeviceDriverProperties`' `driverName` + `driverInfo`, e.g. `NVIDIA GeForce RTX 4090, NVIDIA 615.78.08`); **CPU, RAM and operating system** (the CPU's brand string and threads, RAM, `os-release`, desktop and session type on Linux; `RtlGetVersion` and Wine on Windows); **Changes from the defaults** (each Insert-menu row not at its default as the menu shows it; a microphone chosen by its name is not named; then the switches set, below); **hookshot_log.txt** (what is attached, the run before's version and date, each log's first `FATAL`, `CRASH` or `device lost` line); **Screenshots or video**; then a link that opens the form with version, where, GPU, system and settings filled in (GitHub fills issue-form fields from query parameters named by the fields' ids). - **The switches.** CONTRIBUTING.md lists `hookshot_env.txt` among the files never to share, so the report never copies it, and of the switches set (the names `hookshot_env.txt` takes: `RX_`, `HOS_`, `HOOKSHOT_`, `EDEN_`, `NVN_`, `SDL_`; and what hooks other code in: `VK_INSTANCE_LAYERS`, `MANGOHUD`, `ENABLE_VKBASALT`, `LD_PRELOAD` ...) it writes each name, and the value only when it cannot hold anything private: a number of at most 12 characters, a size such as `1920x1080`, or on/off, true/false, yes/no, and never for a name with `TOKEN`, `KEY`, `ROOM`, `SECRET`, `PASS`, `SERVER` or `USER` in it. Every other value reads `(value left out)`: paths, addresses, scripts. Other variables are not listed at all. - **The link** stays within 2000 characters and carries no switch value: the options changed and the switches' names, or, when the names do not fit, how many switches are set. A player who is not signed in is sent to GitHub's sign-in page with the whole link inside it, and that page failed for links from about 3500 characters of escaped text (table below). - `hookshot_log.txt` (this run's log up to the press) and `hookshot_log.1.txt` (the run before's; when this run logs elsewhere, e.g. from a terminal, the game folder's `hookshot_log.txt`). Read by length (`fread`, split on newlines), so NUL bytes, which a run ended by a power cut or a hard kill can leave, are copied as they are. Each line: the home folder (a whole folder; on Windows either slash and any case) becomes `~` or `%USERPROFILE%`, the user's name as a folder of any path `<user>`. A log over 10 MB keeps its first MB and its last 9 MB (GitHub takes files up to 25 MB). - `picture.png`: 1280x720, the game's next frame, without the menu (below). If none comes within 3 s, the report goes without it and says so; a picture that comes later is deleted again. - A folder that cannot be made is reported with the system's reason (`GetLastError()` on Windows, `errno` elsewhere). - `gpu/vk/vk_menu.cpp` (+89): the row before Restart and Quit (`kBugReportRow = kRestartRow - 1`), its help (what goes in, that no saves or game files go in and nothing is sent), its value (Press to save / Saving... / Saved / Not saved), Save in both prompt lines (the full menu's and the compact bar's), the press, `changed_rows_text()` (rows against a default-constructed `Options`), `bug_report_save()`, and the picture's ask, `bug_report_picture()`. It follows Save my pipeline list's pattern (#2447): its value in `row_value_cell`, its line under the help in `draw_options` once pressed (where the folder is and how to send it: File Explorer on Windows, a file manager on Linux; on a Steam Deck, Desktop Mode), and room for that line's longest form in `measure_help`. The game opens no browser or file manager: Game Mode on a Deck has neither. - `gpu/vk/vk_replay.cpp` (+2): `shot_ask_next()`, which calls #926's `shot_ask(path, -1, done, ctx)` from outside its anonymous namespace (declared in `vk_internal.h`). The report's picture is the control bridge's kind of shot: an image and a buffer of its own (1280x720, made at the first ask), copied from `src`, the game's frame, which the menu is never drawn into (`menu_record_image` draws only into the screenshots' image), read back once the GPU is past it and written on a thread. No other line of `do_present` changes. - `gpu/vk/vk_main.cpp` (+11), `vk_internal.h` (+6): `device_report_text()`, and the declaration above. - `host/hos_main.c` (+12): `map: <Sequence_CurrentBanc> (child|adult)` when the map or the age changes, in `oot_inventory_refresh` (on Link's update, at most every 500 ms, where the age is already read for the Debug tab). It is where the report's place comes from, and it puts the place into every log. The names are the game's own map names, which the debug tools' teleport lines already print. - `readme.md`: the row in the menu table, a paragraph on what it saves and which values it leaves out, a pointer from "If the problem continues". - `tests/test_bug_report.py` (new), and `tests/test_menu_tune.py`: its check of the compact bar's prompt line now reads the line with the Bug report in it (the check is otherwise unchanged). - Unchanged: nothing is sent anywhere; no saves, no game files, not `hookshot_options.ini` or `hookshot_env.txt` themselves; no new option or switch; the logs' own rotation; the screenshot path. ## How to try it Insert menu (Insert, F1 or Esc, or the menu button) → **Gameplay** → **Bug report** → Enter or A. The row reads Saved and its help names the folder: `bug-reports/<date>_<time>` in the game folder, with `report.txt`, `hookshot_log.txt`, `hookshot_log.1.txt` and `picture.png`. ## How it was tested **2026-11-11, after the two reviews, head `a75f3ca5` on `main` `ba6eef70`.** Rebased twice today: onto #926, then onto #2042, #2378, #2447 and the rest. The conflicts, each kept on both sides: #926's `shot_ask_record` beside the old readback (which is now gone); #2042's Debug rows where the Bug report row goes before Restart; #2378's `row_value_cell()` and #2447's share row, where the row's value cell and prompt went (the row now follows the share row's pattern, above); a forward declaration in `host/hos_main.c`. #2042's rules hold for the row: it has no value, so `preset_exempt` does not apply (`tests/test_preset_exempt.py` reads only rows with one), and the branch reads no on/off switch. #2042 also took `TOTK_` out of the names `hookshot_env.txt` takes; the report follows (the test compares the two lists). Environment: Omarchy (an Arch Linux), kernel 7.2.15-1-omarchy, RTX 4090 with NVIDIA 615.78.08, i9-13900K, 94 GB, g++ 16.2.1 and clang 23.1.3. - The new test on the branch (`python -I -B tests/test_bug_report.py`, with `--cxx g++` and with `--cxx clang++`; the same line from both): ``` bug report: ok (report.txt as the issue form, both logs redacted, the picture, the long log, the terminal start, switch values, NUL bytes, the folder's reason) ``` Against `main`'s source (the test copied into an export of `ba6eef70`; with both compilers): it fails at its first step, the menu row: ``` at = menu.index('{TAB_GAMEPLAY, "Bug report", nullptr') ValueError: substring not found ``` And the review's NUL case against the old log copy (`fgets` and `line.back()`, put back on its own): ``` bug report: the copy of a log with NUL bytes differs: b'[hos host] hookshot v2.1.0-rc.3-39-gbdfe09c (source 763271ad)\n[hos Main] fs: mounted content: -> ~/Games\n[hos Main] map: KokiriForest_Child (child)\n[hos Main] half a line' ``` It compiles `vk_bug_report.cpp` alone with a driver and writes reports from a made-up game folder inside a made-up home folder (`.../home/alice/Games/Hookshot/`), with paths of the home and of `/run/media/alice/...` in the logs, map lines and a `CRASH:` line in the run before's. It checks: the folder holds exactly the report, both logs and the picture; no file names the home folder, the user as a folder or the game folder's path (`alice123` and `Malice` stay); `hardware_report.py`'s `section`, `classify` and `os_of` read the version, `NVIDIA` / `RTX 40` and Linux from `report.txt`; the place is the last map, and the run before's; the switches: values kept (`HOS_HEADLESS=1`, `60`, `off`, `3840x2160`, `-1`, `1.5`) and left out (a path, a drive path, a menu script, an IP address, a `host:port`, a 13-digit number, and numbers under ROOM, KEY, SERVER, USER and PASSWORD names), none of those values anywhere in `report.txt`, `PATH` and a token not listed, no `%3D` in the link; the run before's crash line; the link's fields and its length (with 98 switches: within 2000, the options and the count in it, every switch in `report.txt`); the menu's way (a thread, one report at a time); no frame means no picture, said so; a picture that comes after the report went without it is deleted; a start from a terminal; two reports in one second; a 22 MB log cut to its first and last part with its middle's map still found; a log with a NUL in a line and 4 KB of NULs at its end copied byte for byte, the map after the NUL found; a folder that cannot be made, with the system's reason; the redaction's Windows cases (either slash, any case); this PC's system line. And the wiring by text: the row and what its help promises, Save in both prompt lines, the press, the picture asked of `shot_ask_next` and copied from the game's frame without the menu, the host's `map:` line, that the switches it lists are the names `hookshot_env.txt` takes (`env_line_allowed`'s), the form's labels and ids. Eight runs at once at load 280 all pass (the first version of the menu's case read the report's state too late under load in the full suite; it now holds the report until it has looked). - The checks the review named and those that read the menu's rows, on this head (with g++ and clang++ where they compile C++): `test_capture_dimensions.py`, `test_capture_slot_ownership.py`, `test_menu_env_overrides.py`, `test_menu_keys.py`, `test_pipeline_share.py`, `test_menu_tune.py`, `test_stick_dead_zone.py`, `test_preset_exempt.py` (62 rows), `test_menu_env_on.py`, `test_env_on.py`, `test_fps_stats.py`, `test_gpu_choice.py`, `test_menu_restart_slots.py`, `test_mouse_settings.py`: all pass. `vk_bug_report.cpp` alone with `-Wall -Wextra -Wpedantic -Werror`: no warning from either compiler. - `python -I -B tests/run_all.py` on Linux, on this head: ``` 440 passed, 1 failed, 20 skipped in 600 s (linux, 32 at once) ``` The one failure is `test_recorder_sync.py`, which ran out of its 600 s at load 250-320 (other work ran beside it) and touches nothing of this branch. An earlier run of the whole suite on this branch failed `test_bug_report.py` once under that load: its menu case read the report's state too late (fixed in the test, above). - Merged into the newest `main` (`fce53453`, nine commits later; it merges cleanly): `test_bug_report.py` (both compilers), `test_menu_tune.py`, `test_pipeline_share.py`, `test_menu_keys.py`, `test_menu_env_overrides.py`, `test_preset_exempt.py` (63 rows), `test_menu_restart_slots.py`, both capture tests: pass. `test_env_on.py` fails there and on `fce53453` alone (`HOS_FPS_LOG (host/oot_high_fps.c:15)`, from #2470), not from this branch. - Windows: `vk_bug_report.cpp` with a small driver compiles and links for x86_64-w64-mingw32 (clang 23 with llvm-mingw 20261104's headers and libraries, `-Wall -Wextra -Werror`: a PE32+ console executable). Not run. - In the game, on a build of this head's code (`v2.1.0-rc.3-177-g9c538c91 (source c4f2afbc)`; the head differs only in two test files), headless, RTX 4090, `RX_VK_PRECOMPILE=0` (shaders compiled when first used), load 200 to 320 (other work ran beside it), the save loaded into Kokiri Forest and the menu script pressing **Gameplay → Bug report**: | Run | What | Harness result | The report | |---|---|---|---| | br-menu2 | the row pressed 55-63 s after the first frame | exit 124 (its 120 s), fatal 0, crash 0, device lost 0, kernel driver lines 0 | `bug-reports/2026-11-11_16-58-06`: report.txt and both logs, **no picture**, and it says so. Rightly: the game showed no frame from 39 s to 107 s (the save loading, shaders compiled as they came, at load 300), took the scripted keys when frames came again, and stopped once more at 109 s | | br-crash3 | a first start killed with `kill -9` after 110 s, then a second start; the row pressed 63 s after its first frame | exit 124 (its 260 s), fatal 0, crash 0, device lost 0, kernel driver lines 0 | `bug-reports/2026-11-11_17-13-48`: report.txt, both logs (`hookshot_log.1.txt` is the killed start's whole log), picture.png (present 1772) | | br-menu3 | the row pressed 130-138 s after the first frame, in play | exit 124 (its 200 s), fatal 0, crash 0, device lost 0, kernel driver lines 0 | `bug-reports/2026-11-11_17-25-32`: report.txt, both logs, picture.png (present 3063, 138 s) | The after-crash run's log lines (the picture's path shortened): ``` [hos BaseProcExecutor5] map: KokiriForest_Child (child) [hos Presentation Thread] menu: bug report asked for [hos host] vk: present 1772 written to ~/Games/.../bug-reports/2026-11-11_17-13-48/picture.png (the control bridge) [hos host] menu: bug report written to bug-reports/2026-11-11_17-13-48: report.txt, hookshot_log.txt (245 KB), hookshot_log.1.txt (117 KB), picture.png ``` Its folder read back against the game folder's logs (`check_report.py`: each copy compared line by line with its original, with the home folder and the user name replaced as the game does): ``` files: hookshot_log.1.txt (119514 bytes), hookshot_log.txt (250199 bytes), picture.png (2765798 bytes), report.txt (3212 bytes) no file holds the home folder or the user name (any case, anywhere) [the checker's line, with the two values left out here] hookshot_log.txt: 1827 of the original's 2991 lines, 12 of them with the home or the user replaced, nothing else changed hookshot_log.1.txt: 991 of the original's 991 lines, 11 of them with the home or the user replaced, nothing else changed hardware_report.py: vendor and class ('NVIDIA', 'RTX 40'), OS Linux / SteamOS ok ``` br-menu2's and br-menu3's folders pass the same check (1387 of 1571 and 3230 of 3230 lines; 2861 of 3679 and 3230 of 3230 lines; only the home folder and the user name replaced). A picture's pixel data is not read as text: br-menu3's picture.png has the bytes `4d 4f 4f 4b 49` among its pixels (row 176), which an any-case byte search reads as a name; the PNG holds only IHDR, IDAT and IEND chunks. The host name is in no text file of either. - Pictures (headless, 1280x720; the first two are the harness's shots with the menu drawn in, the third the report's own file):  1. br-menu3, the row before the press: **Bug report** selected, **Press to save**, its help (what goes in; no saves or game files; nothing is sent), the prompt bar **Save Enter**. 2. After Enter: **Saved**, and the paragraph under the help names the folder (`bug-reports/2026-11-11_17-25-32`) and how to send it. 3. That report's `picture.png`, taken through `shot_ask` at present 3063: child Link and Navi on the path in Kokiri Forest, hearts and minimap; no menu, not dimmed.  4. br-crash3, after the `kill -9` and the restart: **Saved**, the folder `bug-reports/2026-11-11_17-13-48` under the help. 5. That report's `picture.png` (present 1772): Kokiri Forest from low down, facing the bright sky, hearts and minimap; no menu. - The `report.txt` the after-crash run wrote, as a player would attach it (the switches are the test harness's: see which values are written and which are left out): <details><summary>The report.txt that run wrote</summary> ```text Hookshot bug report, made by the Insert menu's Bug report on 2026-11-11 at 17:13. To send it: open https://github.com/NotRexed/Hookshot/issues/new/choose and choose Game problem (or the link at the end of this file, which fills in the answers below), copy the answers below into the fields of the same names, say what happened, and drag the files of this folder, this one too, into the issue. ### Hookshot version v2.1.0-rc.3-177-g9c538c91 (source c4f2afbc) ### Where in the game KokiriForest_Child (child) (The map this run's log names last: add what you were doing there.) ### Graphics card and driver version NVIDIA GeForce RTX 4090, NVIDIA 615.78.08 ### CPU, RAM and operating system 13th Gen Intel(R) Core(TM) i9-13900K (32 threads), 94 GB, Omarchy (Linux 7.2.15-1-omarchy), Hyprland (wayland) ### Changes from the defaults Every Insert menu option is at its default. Switches set (the environment, hookshot_env.txt; a value only when it is a number or on/off, and never for a name with TOKEN, KEY, ROOM, SECRET, PASS, SERVER or USER in it): HOOKSHOT_NO_UPDATE_CHECK=1 HOS_AUDIO=0 HOS_DISCORD=0 HOS_ENV_FILE=(value left out) HOS_HEADLESS=1 HOS_INPUT_BACKGROUND=0 HOS_INPUT_EXCLUSIVE=1 HOS_INPUT_FRAME_CLOCK=1 HOS_INPUT_SCRIPT=(value left out) HOS_PRESENT_TRACE=(value left out) RX_CPM=(value left out) RX_VK_DEVICE=0 RX_VK_FRAME_LOG=(value left out) RX_VK_MENU_SCRIPT=(value left out) RX_VK_PRECOMPILE=0 RX_VK_SHOTS=(value left out) RX_VK_SHOT_EVERY=300 RX_VK_SHOT_MENU=1 SDL_AUDIODRIVER=(value left out) SDL_AUDIO_DRIVER=(value left out) VK_DRIVER_FILES=(value left out) ### hookshot_log.txt hookshot_log.txt: this run's log as it was when the report was made, 245 KB. hookshot_log.1.txt: the run before's log (hookshot v2.1.0-rc.3-177-g9c538c91 (source c4f2afbc), last written 2026-11-11 17:12), 117 KB. In both, the home folder is written ~ and the user's name <user>. ### Screenshots or video picture.png: the game's picture when the report was made, without the menu. ### What this folder holds report.txt (this file), hookshot_log.txt, hookshot_log.1.txt, picture.png. Nothing else from this PC: no saves, no game files, not the options file itself. The link with the answers filled in: https://github.com/NotRexed/Hookshot/issues/new?template=1-game-problem.yml&version=v2.1.0-rc.3-177-g9c538c91%20%28source%20c4f2afbc%29&where=KokiriForest_Child%20%28child%29&gpu=NVIDIA%20GeForce%20RTX%204090%2C%20NVIDIA%20615.78.08&system=13th%20Gen%20Intel%28R%29%20Core%28TM%29%20i9-13900K%20%2832%20threads%29%2C%2094%20GB%2C%20Omarchy%20%28Linux%207.2.15-1-omarchy%29%2C%20Hyprland%20%28wayland%29&settings=Every%20Insert%20menu%20option%20is%20at%20its%20default.%0ASwitches%20set%20%28their%20values%20are%20in%20the%20attached%20report.txt%29%3A%20HOOKSHOT_NO_UPDATE_CHECK%2C%20HOS_AUDIO%2C%20HOS_DISCORD%2C%20HOS_ENV_FILE%2C%20HOS_HEADLESS%2C%20HOS_INPUT_BACKGROUND%2C%20HOS_INPUT_EXCLUSIVE%2C%20HOS_INPUT_FRAME_CLOCK%2C%20HOS_INPUT_SCRIPT%2C%20HOS_PRESENT_TRACE%2C%20RX_CPM%2C%20RX_VK_DEVICE%2C%20RX_VK_FRAME_LOG%2C%20RX_VK_MENU_SCRIPT%2C%20RX_VK_PRECOMPILE%2C%20RX_VK_SHOTS%2C%20RX_VK_SHOT_EVERY%2C%20RX_VK_SHOT_MENU%2C%20SDL_AUDIODRIVER%2C%20SDL_AUDIO_DRIVER%2C%20VK_DRIVER_FILES.%0A ``` </details> - The link's length, signed out (`curl`, dummy links to the form, 2026-11-10; nothing of this PC in them): | Link length | Padding | GitHub's answer | |---|---|---| | 1238 | the report's own link (then) | sign-in page, the link kept in `return_to` | | 1999, 2500, 2998, 3199 | `%20` | sign-in page, the link kept in `return_to` | | 3500 | `%20` | 400 | | 4000, 4500 | `%20` | 500 after the redirect | | 6000 | `%20` | sign-in page, but `return_to` dropped (the answers lost) | | 6000, 6400 | `x` | sign-in page, the link kept | | 6700, 7000, 7500, 7900 | `x` | 500 | | 8192 and more | `x` | 414 | ## Not tested - **Windows, at run time.** The report's GPU line there (`VkPhysicalDeviceDriverProperties` on a Windows driver), its CPU, RAM and system line (`__cpuid`, `GlobalMemoryStatusEx`, `RtlGetVersion`, Wine), the path of the log stderr writes to (`GetFinalPathNameByHandleA`), `GetLastError`'s reason, the redaction of real `C:\Users\...` logs, File Explorer: compiled and linked only. - **A windowed run.** Only headless runs: the picture taken with a window, HDR (a native HDR frame is copied to 8-bit as the control bridge's shots are) and frame generation were not seen. - **The link in a browser.** Not opened in a browser, signed out or signed in, and no issue was opened from it; only `curl` signed out (table above). That GitHub fills the form's fields from it is its documented behaviour, not seen here. - **A real crash's last lines in the game.** The after-crash run ended its first start with `kill -9`, which leaves no `CRASH:` line, and under that load the first start had not reached a map yet; the test covers the `CRASH:` line and the run before's map with made-up logs. - AMD and Intel drivers' `driverName`/`driverInfo` text (only NVIDIA's was read; `hardware_report.py` classifies typical RADV, AMD and Intel strings correctly when written by hand), a Steam Deck, the AppImage, and the Linux Builder's Play button (since #2279 it gives the game its `hookshot_log.txt` again, so the report finds this run's log there; not run here). ## Risks - **Something private in a report.** The home folder (as a whole folder; on Windows either slash and any case) and the user's name as a folder of any path are replaced in both logs; a switch's value is written only when it is a number, a size or on/off and its name holds none of the words above; other environment variables are not listed; a microphone chosen by name is not named; no saves, no options or env file, no game files go in. Guarded by the test (made-up home and user, Windows cases, `alice123` kept, values kept and left out) and, on this machine's real logs, by the read-back above. Not guarded: something private a log line carries in another form (a game folder outside the home named after a person, a machine name in a network path), or a future switch whose secret is a short number under a name without those words. - **A report written while the game is busy.** The press does only cheap work on the menu's thread (flushing the log, reading the environment, `/etc/os-release`, the device's properties, the menu's rows); copying the logs and writing the folder run on a thread of their own, one report at a time (a second press while it saves does nothing). This run's log is copied as far as it was when the copy began. The picture is waited for at most 3 s. - **The picture.** It shares #926's queue (16 asks at most) and its one readback: a control-bridge shot being read back delays the report's by a present or two. Without a report nothing changes on the present path. The control bridge's log line for a written shot also appears for the report's picture (`vk: present N written to ... (the control bridge)`). - **The `map:` line in every log.** It is written only when the map or Link's age changes, from `oot_inventory_refresh` (at most every 500 ms, where the age is already read). A log that a player posts now says where he was; the names are the game's own map names, which the debug tools' teleport lines already print. - A crash that leaves no line (a kill, a freeze, a power cut): the run before's log is attached, but nothing in it says how it ended. A game that stops before its menu can be used cannot make a report; the readme says to attach `hookshot_log.1.txt` by hand then. ## Overlaps `pr_overlap.py --branch bug-report` (run on this head, 2026-11-11): 79 open pull requests touch one of these files; 73 merge cleanly with it. The row stores nothing (no `Options` field, no ini key, no option id), so no option id can clash. Six conflict in this branch's files, each hunk read: - #2417 (the menu in 16 languages): it wraps the menu's texts in `i18n::tr(...)`, among them the lines of `measure_help`, `row_value_cell`'s branch for rows without a value, the help line in `draw_options` and the compact bar's prompt (and the same line in `tests/test_menu_tune.py`), where this branch adds the Bug report beside the share row. Whichever lands second wraps the Bug report's texts the same way; both stay. - #1521, #1864, #2105, #2459, #2479 (the readme): a row each after Microphone in the menu table (#2459 moves the Microphone row), where this branch adds the Bug report's row; both stay. - #2317 (saves and settings in a per-user data folder, logs in `logs/`) now merges cleanly, but overlaps in meaning: this branch finds the game folder through `ini_path()` and the logs in the game folder; after #2317 the settings are in the data folder and the logs in `logs/`. Whichever lands second moves those two reads to `game_folder()` and `log_path()`. ## Questions for you 1. Is the Insert menu the place you want this, and the game folder the place for the reports (with #2317, the data folder or `logs/` could be)? 2. The report takes the strict reading of CONTRIBUTING.md on `hookshot_env.txt` (names, and only harmless values). Should CONTRIBUTING.md say so, next to the line that lists the file? 3. The link carries the form's one-line answers and, when short enough, the options and the switches' names. Do you want it at all, or only `report.txt` to copy from? Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # readme.md # tests/test_stick_dead_zone.py
…s sections, Field of view, Aspect ratio, Save my pipeline list, HDR tone mapper, Bug report) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Your note says the tables are machine-made and want a native check. I have ~520 |
hckrman101
left a comment
There was a problem hiding this comment.
Re-review at e44b9fc. Both points from my first review are fixed:
HOS_LANGUAGEand case: the menu now reads the language the wayhost/nn_applet.cdoes (env overlanguage.txt, any case, unknown line or BOM skipped, en-US fallback).- Game language row:
language.txtis written once when the row is left, a failed write is reported and the row goes back, and Auto stays on the language the game started in until the restart, so nothing reloads fonts on each step.
The conflict in tests/test_stick_dead_zone.py that I commented on is resolved: the branch merges cleanly with main now. Main's newer menu texts are translated in the 13 tables, and test_menu_i18n passes in CI.
CI: the only red check is test_env_on (HOS_FPS_LOG in host/oot_high_fps.c), a file this PR does not touch and main already fixed. The branch is 38 commits behind, so please merge main once more and it should go green.
The rest, checked:
- Option ids: Game language and Menu language are menu-only (not sent to the host),
game_langis skipped on save and load,menu_languageis the only new ini key. No id is taken, so nothing collides with 29, 30, 44, 49, 50, 52-54, 60 or 8192+. - UTF-8: texts stay whole characters (
spacedandparastep by UTF-8 sequences);language.txtis opened throughgame_module_pathandfopenlike the other game-folder files, which the UTF-8 manifest covers.test_menu_utf8andtest_utf8_pathspass. - Game text:
test_texts_are_the_menusmakes every key one ofvk_menu.cpp's own texts, so the tables hold the menu's wording only. Place and item names stay English and the game's fonts are read from the player's files at run time, nothing is shipped.
On #2417 (same menu, 16 languages, plus the startup screen and overlays): the two add gpu/vk/vk_menu_i18n.h and tests/test_menu_i18n.py and route the same texts through a lookup, so only one can be the base. I'd take this one: it has the Game language row, the game's CJK fonts with system fallbacks, the HOS_LANGUAGE rule, the Russian/Polish plurals, and a native Simplified Chinese check (EverChangAn above), and it merges cleanly while #2417 conflicts now. #2417 should then be rebased onto it as a follow-up: its startup-screen and overlay strings go into the menu_i18n.h tables as more entries, and its extra pieces (RX_VK_UI_LANG, RX_VK_UI_FONT, a per-language fallback when no font covers the script) are worth adding on top, not a second catalog.
Nits, not blocking:
set_game_languagetruncateslanguage.txtwithfopen("w"), so the comment "whole or not at all" is a little strong (a crash mid-write loses the file), and a trailing comment on the code line is dropped. Write to a temp file and rename if you want it exact.- A text the menu gets later without a table entry falls back to English silently;
test_texts_are_the_menusonly checks the other direction. Fine for now. - The translations are still machine-made; gx0803 offered a reviewed zh-Hans set above, worth diffing against yours.
Good to go once main is merged.
…u's 13 languages NotRexed#1864's tables had this branch's earlier texts (the Aspect ratio's help, the Output resolution's "The sharpest on a ... screen" lines), which the rows no longer show. Those seven are gone from each table; the rows' texts now are there: the Aspect ratio's help and its four shapes' lines, the Output resolution's own-size, larger and smaller lines, and "%s (monitor's)". tx_ultrawide prints the mark from that format, so vk_menu_i18n.cpp's match_format translates "21:9 (monitor's)" with the shape kept as it is. Machine-made like the rest of the tables, with each table's own words for the Output resolution, the HUD and the render targets. test_menu_i18n still fails on main's three texts the menu no longer has (the Controls page's gamepad line, the mesh shader line, the button prompts' line); this branch adds none. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…S made again after a failure, NotRexed#1864) Conflicts: gpu/vk/vk_menu.cpp (the fork's Neural row hint beside upstream's language rows), host/oot_high_fps.c (upstream's spelling of the same env_on fix), tests/test_stick_dead_zone.py (the fixture's Options carries game_lang and the fork's nr_* keys). Upstream's main is red at this commit (15 source checks, and the game does not link without hos_disable_camera_centering): the commits after this one deal with that. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
**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>
…u's 13 languages NotRexed#1864's tables had this branch's earlier texts (the Aspect ratio's help, the Output resolution's "The sharpest on a ... screen" lines), which the rows no longer show. Those seven are gone from each table; the rows' texts now are there: the Aspect ratio's help and its four shapes' lines, the Output resolution's own-size, larger and smaller lines, and "%s (monitor's)". tx_ultrawide prints the mark from that format, so vk_menu_i18n.cpp's match_format translates "21:9 (monitor's)" with the shape kept as it is. Machine-made like the rest of the tables, with each table's own words for the Output resolution, the HUD and the render targets. test_menu_i18n still fails on main's three texts the menu no longer has (the Controls page's gamepad line, the mesh shader line, the button prompts' line); this branch adds none. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…u's 13 languages NotRexed#1864's tables had this branch's earlier texts (the Aspect ratio's help, the Output resolution's "The sharpest on a ... screen" lines), which the rows no longer show. Those seven are gone from each table; the rows' texts now are there: the Aspect ratio's help and its four shapes' lines, the Output resolution's own-size, larger and smaller lines, and "%s (monitor's)". tx_ultrawide prints the mark from that format, so vk_menu_i18n.cpp's match_format translates "21:9 (monitor's)" with the shape kept as it is. Machine-made like the rest of the tables, with each table's own words for the Output resolution, the HUD and the render targets. test_menu_i18n still fails on main's three texts the menu no longer has (the Controls page's gamepad line, the mesh shader line, the button prompts' line); this branch adds none. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…u's 13 languages NotRexed#1864's tables had this branch's earlier texts (the Aspect ratio's help, the Output resolution's "The sharpest on a ... screen" lines), which the rows no longer show. Those seven are gone from each table; the rows' texts now are there: the Aspect ratio's help and its four shapes' lines, the Output resolution's own-size, larger and smaller lines, and "%s (monitor's)". tx_ultrawide prints the mark from that format, so vk_menu_i18n.cpp's match_format translates "21:9 (monitor's)" with the shape kept as it is. Machine-made like the rest of the tables, with each table's own words for the Output resolution, the HUD and the render targets. test_menu_i18n still fails on main's three texts the menu no longer has (the Controls page's gamepad line, the mesh shader line, the button prompts' line); this branch adds none. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…u's 13 languages NotRexed#1864's tables had this branch's earlier texts (the Aspect ratio's help, the Output resolution's "The sharpest on a ... screen" lines), which the rows no longer show. Those seven are gone from each table; the rows' texts now are there: the Aspect ratio's help and its four shapes' lines, the Output resolution's own-size, larger and smaller lines, and "%s (monitor's)". tx_ultrawide prints the mark from that format, so vk_menu_i18n.cpp's match_format translates "21:9 (monitor's)" with the shape kept as it is. Machine-made like the rest of the tables, with each table's own words for the Output resolution, the HUD and the render targets. test_menu_i18n still fails on main's three texts the menu no longer has (the Controls page's gamepad line, the mesh shader line, the button prompts' line); this branch adds none. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@EverChangAn thank you for going through the Simplified Chinese table as a native speaker, that is exactly the check it needed. @gx0803 yes, that would help, thank you. A plain en -> zh-Hans export is perfect: I'll diff it against the zh-Hans table and open a follow-up PR with the differences, crediting you. Where your wording and the current one differ I'll take yours, since it was reviewed by hand. |
…u's 13 languages NotRexed#1864's tables had this branch's earlier texts (the Aspect ratio's help, the Output resolution's "The sharpest on a ... screen" lines), which the rows no longer show. Those seven are gone from each table; the rows' texts now are there: the Aspect ratio's help and its four shapes' lines, the Output resolution's own-size, larger and smaller lines, and "%s (monitor's)". tx_ultrawide prints the mark from that format, so vk_menu_i18n.cpp's match_format translates "21:9 (monitor's)" with the shape kept as it is. Machine-made like the rest of the tables, with each table's own words for the Output resolution, the HUD and the render targets. test_menu_i18n still fails on main's three texts the menu no longer has (the Controls page's gamepad line, the mesh shader line, the button prompts' line); this branch adds none. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…u's 13 languages NotRexed#1864's tables had this branch's earlier texts (the Aspect ratio's help, the Output resolution's "The sharpest on a ... screen" lines), which the rows no longer show. Those seven are gone from each table; the rows' texts now are there: the Aspect ratio's help and its four shapes' lines, the Output resolution's own-size, larger and smaller lines, and "%s (monitor's)". tx_ultrawide prints the mark from that format, so vk_menu_i18n.cpp's match_format translates "21:9 (monitor's)" with the shape kept as it is. Machine-made like the rest of the tables, with each table's own words for the Output resolution, the HUD and the render targets. test_menu_i18n still fails on main's three texts the menu no longer has (the Controls page's gamepad line, the mesh shader line, the button prompts' line); this branch adds none. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nguage rows
The game itself ships in 16 languages, but its settings menu was English only: a player who plays in Portuguese, Japanese or German gets every option and help line in English.
The translations are machine-made and want checking by speakers of each language: each is one line in menu_i18n.h. One English text is two things: "Switch" is the console's name in Button prompts and the Switch age tool's verb, so it stays English for both.
Tested on an RTX 3060 Laptop, Windows 11: the menu in Portuguese, Japanese, German and Chinese (in play and in RX_VK_SHOT_MENU shots), the Game language row writing language.txt and offering the restart. The suite run on Windows here fails the same tests with and without this change (local tools); the menu's own tests pass. Played on 52ac35c; rebased on 51de681, where the renderer does not link yet with or without this change (present_commit and present_defer_into are declared, not defined: #1702?).
Assisted-by: Claude Opus 5.5


