Repository navigation
Conversation
… a channel and a test on the Audio tab
The ocarina's microphone mode (humming or playing an instrument) heard nothing through a Focusrite Scarlett Solo 4th
Gen: Windows' processed stream, which SDL's recording opens whatever SDL_AUDIO_DEVICE_RAW_STREAM says (SDL 3.4.20),
gave apps only the interface's first channel, silent, while the XLR microphone was on its second, and SDL's mono mix
of the 4 channels would have cut it to a quarter anyway. There was also no way to make a quiet microphone louder or
to see what the game hears.
- host/hos_sdl.c: on Windows the microphone is opened through WASAPI's raw stream (AUDCLNT_STREAMOPTIONS_RAW: every
channel, no capture effects), found by the name SDL lists it under, read by a thread of its own into an SDL audio
stream that converts it to the format asked for. One that cannot be opened this way, HOS_MIC_RAW=0, or SDL2: SDL's
own recording, as before. hos_sdl_mic_channels: the device's channel count.
- host/nn_audio_in.c: a mono stream's microphone is opened with the device's own channels and one is given to the
game: the Microphone channel row's (HOS_OPT_MIC_CHANNEL), or on Auto the louder of the first two (an interface's
inputs; its loopbacks of what the PC plays come after them, so the ocarina does not hear the game's music). The
Microphone volume (HOS_OPT_MIC_GAIN, 0 to 400 %) scales it, clipped at full scale; 100 % leaves it untouched.
- Test microphone (MkwVkHost mic_test, API version bump): the chosen microphone opened on its own while the Audio
tab shows the test, a level meter at the volume, the steady note it hears (McLeod pitch, 60 to 1500 Hz) and the
channel ("ch 2/4"). Let go when the menu closes or another tab shows.
- The Microphone row moves from Gameplay to Audio, beside Microphone volume, Microphone channel and Test microphone.
- tests/test_audio_in.py: the volume (every format, clipping, through a stream), a 4-channel device (Auto's second
channel, a loud loopback left out, the menu's channel, one past the device's), the test's states, level and channel,
the pitch (tones, a hum's harmonics, noise, silence), the notes and meter, the option ids on both sides, and that
Windows opens raw before SDL.
Measured on the Scarlett while humming: SDL's processed stream -84 dB on every channel; the raw path, through
hos_sdl.c itself, -13 to -39 dB on channel 2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Joey-Magnus
reviewed
Nov 11, 2026
This was referenced Nov 11, 2026
Draft
hckrman101
pushed a commit
that referenced
this pull request
Nov 12, 2026
…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>
This was referenced Nov 12, 2026
hckrman101
pushed a commit
that referenced
this pull request
Nov 12, 2026
**What this is for:** make controller-mapping reports such as #2546 answerable from `hookshot_log.txt`, without changing the player's working bindings. **What is measured:** Linux game-free checks of the real SDL2/SDL3 adapter and connection-log call, with GCC 16.2.1 and Clang 23.1.3: 64 checks pass with each compiler; the same fixture fails against the original connection log. Both changed host C files also pass Windows-target syntax checks without warnings or errors. **Not measured:** a real controller on Windows, a game run, or a fix for the reporter's physical button mapping. Refs #2546 and #2504. ## Problem The new attachment in #2546 identifies `Core (Plus) Wired Controller` through SDL3 and one connected SDL controller (lines 171, 216, 221), but does not record its GUID or active mapping. The reporter has since confirmed a basic PowerA wired Switch-licensed pad with no mode switch. A device name alone cannot distinguish the mapping routes involved ([reporter's comment](#2546 (comment))). There is a concrete reason to collect the actual mapping rather than guess: SDL associates `20d6:a711` with both the PowerA Wired Controller Plus and its GameCube-style variant ([SDL device list](https://github.com/libsdl-org/SDL/blob/f00dc919ca35b9756316e86c4777392de442f9b2/src/joystick/controller_list.h#L608-L614)). Its GameCube-classified HIDAPI route can account for the exact reported permutation. This PR does **not** assume that ID/backend for the reporter and does not install a mapping workaround. ## What changed - `host/hos_sdl.c`: optional diagnostic exports for SDL2 and SDL3. At a successful connection, obtain the borrowed joystick's hardware GUID and the active gamepad mapping, then release the mapping string through the same loaded library's `SDL_free`. - `host/hos_sdl.h` and `host/pad_sdl.c`: call one connection-log helper at the existing successful-connect site. Keep the controller name, gyroscope suffix and Steam Input suffix. - `tests/test_sdl_pad_diagnostics.py`: compile the actual loader, adapter and connection call with fake library exports. Cover a known mapping, no returned mapping, zero GUID, missing optional functions, missing SDL2 allocator, SDL3 missing joystick accessor, correct allocation/free ownership, and bounded output through the production-sized log sink. `--source` compares the same assertions with another checkout's source. - `docs/development.md`: describe the fields, bounds, status meanings and privacy qualification. Bindings, input polling, prompt selection, controller enumeration, rumble and mapping-file loading are unchanged. There is no renderer API change and no new setting. Diagnostic queries run on connection, not every controller poll. ### What the statuses mean - `guid=<32 hex digits>`: SDL's hardware GUID; a zero result is `unknown`, unavailable accessors are `unavailable`. - `mapping=available`: SDL returned an active mapping string. - `mapping=none`: no string was returned; this is **not** proof that the device is unmapped. - `mapping=unavailable`: the query or required allocator export is absent. Neither success nor the mapping name identifies where the mapping originated. The [SDL3 API](https://wiki.libsdl.org/SDL3/SDL_GetGamepadMapping) and [SDL2 API](https://wiki.libsdl.org/SDL2/SDL_GameControllerMapping) return a mapping or NULL, not a provenance flag. Names are limited to 128 bytes. Mapping text is capped at 1024 bytes total, with a truncation marker; it follows the connection line in GUID-labelled parts of at most 512 bytes. That keeps the captured mapping intact inside `hos_log`'s existing 1024-byte per-message buffer rather than enlarging the global logger. ### Privacy The GUID and the mapping describe a device model's button layout; no serial, path, user or account call is made. A mapping the player supplied himself, by environment or file, is logged as he wrote it, within the length limits. SDL constructs these hardware GUIDs from bus/vendor/product/version, manufacturer/product names and driver bytes, with serial information stored separately ([SDL3 GUID construction](https://github.com/libsdl-org/SDL/blob/f5e5f6588921eed3d7d048ce43d9eb1ff0da0ffc/src/joystick/SDL_joystick.c#L2693-L2734), [HIDAPI serial separation](https://github.com/libsdl-org/SDL/blob/f5e5f6588921eed3d7d048ce43d9eb1ff0da0ffc/src/joystick/hidapi/SDL_hidapijoystick.c#L855-L916)). The active mapping serializer writes the GUID, supplied mapping name/body and platform metadata ([SDL3](https://github.com/libsdl-org/SDL/blob/f5e5f6588921eed3d7d048ce43d9eb1ff0da0ffc/src/joystick/SDL_gamepad.c#L2182-L2231), [SDL2](https://github.com/libsdl-org/SDL/blob/5d249570393f7a37e037abf22cd6012a4cc56a71/src/joystick/SDL_gamecontroller.c#L1720-L1768)). Custom mapping text can therefore contain whatever the player put in it; clipping is not privacy redaction. ## How it was tested Arch Linux, kernel `7.2.15-1-omarchy`, GCC `16.2.1 20260810`, Clang `23.1.3`, Python 3.12. No GPU, window, controller or performance measurement was used. Initial base `edaef2a99`; final commit `e0864483` on `87991bc8b`. The final rebase adds only an upstream team-chat script/test change; all five changed file blobs are identical to the tested pre-rebase version. | Check | GCC | Clang | |---|---|---| | Final SDL2/SDL3 diagnostic fixture | `PASS: 64 checks, 0 failures` | Same | | Same fixture against original main source | `FAIL: 60 checks, 31 failures` | Same | | Same fixture against the earlier single-line implementation | `FAIL: 60 checks, 4 failures` | Same | The failures are executable assertions, not missing-symbol compile failures. The original source still emits only the old connection message; the earlier implementation loses the tail and truncation note through the real-sized logger. Dependent chunk-byte assertions are entered only when both chunks exist, hence the different reached-check totals on the failing versions. Example fixture output (authored device data, not the reporter's controller): ```text hid: controller "Test controller" connected (SDL), with a gyroscope, Steam Input's; guid=03000000341200007856000001000000; mapping=available; mapping-name="Test mapping" hid: guid=03000000341200007856000001000000; mapping-string[1/1]="03000000341200007856000001000000,Test mapping,a:b1,b:b2,x:b0,y:b3,platform:Windows," hid: controller "Test controller" connected (SDL); guid=unknown; mapping=none; mapping-name="?" hid: controller "Test controller" connected (SDL); guid=unavailable; mapping=unavailable; mapping-name="?" ``` Reproduction: ```sh python -I -B tests/test_sdl_pad_diagnostics.py --cc gcc python -I -B tests/test_sdl_pad_diagnostics.py --cc clang python -I -B tests/test_sdl_pad_diagnostics.py --cc gcc --source /path/to/unmodified/main ``` Both changed host C files, `hos_sdl.c` and `pad_sdl.c`, pass Clang's `x86_64-w64-mingw32` syntax-only check using llvm-mingw headers, `-Wall -Wextra`, with **zero warnings/errors**. This is a syntax check, not a Windows link or runtime test. Existing Steam-pad checks pass with both compilers, and the SDL library regression fixture passes. One full suite ran before the final log-chunk correction, on intermediate `aba3eb1a`: ```text 470 passed, 1 failed, 20 skipped in 157 s (linux, 32 at once) ``` The sole failure was `nx2/test_texture_phi_dominance.py`: `clang++` compiling unchanged `emit_spirv_image.cpp` was terminated by `SIGKILL`. Its cause was not established; it was not a failed shader assertion, and no baseline/OOM explanation is claimed. The failed result is retained, not rerun away. Load average was 16.98 at entry and 37.45 at exit. The final chunk correction was then tested with both compilers and both Windows syntax passes, **not another full suite**; final focused load was 23.94. The final branch passes the mechanical PR checks on current main: author/committer identity, assistance trailers, conventional title, test included, and no game data/build output. No core dumps were found at the recorded check. ## Not tested - A real controller on Windows, including the reporter's PowerA pad. - Actual SDL2/SDL3 DLL calls, a Windows executable link, or game startup/play. - Physical hotplug or user-supplied mapping overrides in a game. - A green full-suite result on the final head; the retained suite interruption above remains outstanding. ## Risks and overlaps The new exports are optional and cannot make controller support fail to load. The mapping getter is not called if its matching allocator is unavailable. The GUID ABI mirrors SDL2/SDL3's 16-byte byte-array struct returned by value; the borrowed joystick is not closed. The overlap scan found 44 open PRs sharing a changed file, mostly documentation, and no new textual conflicts. Shared-code hunks were read for #1543, #1600, #1872, #2252, #2459 and #2566. The scan preceded the small logger-boundary correction; the final branch's current-main merge check also passes. **#2252 needs semantic integration even though the text merges:** it introduces non-SDL `pad_sw2` handles and guards the existing SDL getters. If both changes land, the new diagnostic helper must likewise avoid passing those handles to SDL. That path is absent from this PR's main base; no speculative compatibility shim is included here. This is diagnostics for #2546, not a controller-map fix. The next useful reporter evidence is one startup log from a build containing the change, with the pad connected. Assisted-by: GPT-6 Astra Co-Authored-By: GPT-6 Astra <noreply@openai.com> --------- Co-authored-by: GPT-6 Astra <noreply@openai.com> Co-authored-by: Claude Fable 5.2 <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.
The ocarina's microphone mode (humming or playing an instrument) heard nothing through an audio interface such as a Focusrite Scarlett Solo 4th Gen, and there was no way to make a quiet microphone louder or to see what the game hears. This PR makes the microphone work on such interfaces and adds a volume, a channel choice and a test to the Audio tab.
Why the ocarina heard nothing
The Scarlett Solo lists 4 channels: its two inputs, then two loopbacks of what the PC plays. Windows' processed capture stream, the one SDL's recording opens, gave apps only the first channel. On this setup that channel is silent, while the XLR microphone arrives on the second. The device opened with no error, and the game read silence.
Measured on the Scarlett while humming:
SDL_AUDIO_DEVICE_RAW_STREAM=1(SDL 3.4.20)AUDCLNT_STREAMOPTIONS_RAW), through this PR'shos_sdl.cSDL's mono mix of the 4 channels would also have cut a single-input microphone to a quarter (−12 dB).
What changed
host/hos_sdl.c: on Windows, the microphone is opened through WASAPI's raw stream: every channel, no capture effects. It's found by the name SDL lists it under, and read by a thread of its own into an SDL audio stream that converts it to the format the game asks for. It falls back to SDL's own recording, as before, when the device can't be opened this way, withHOS_MIC_RAW=0, or on SDL2 (Linux).hos_sdl_mic_channelsgives the device's channel count.host/nn_audio_in.c:Test microphone (new
MkwVkHost::mic_test,MKWVK_API_VERSION18): opens the chosen microphone on its own while the Audio tab shows the test, and shows:The microphone is released when the menu closes or another tab shows.
Menu: the Microphone row moves from Gameplay to Audio, next to Microphone volume, Microphone channel and Test microphone. New options in the ini:
microphone_volume(HOS_OPT_MIC_GAIN29) andmicrophone_channel(HOS_OPT_MIC_CHANNEL30).Docs: the README's menu table and Microphone notes, and the development guide's options table.
How it was tested
tests/test_audio_in.py(76 cases, no game, no hardware):python -I -B tests/run_all.py. 3 checks fail, and all 3 also fail on unmodifiedmainon this PC:test_fpx_stickyandnx2/test_nup_paths(toolchain/encoding environment) andtest_vram_budget_row(an unused-constant warning in its fixture with this clang).hos_sdl.c's microphone path, compiled into a small harness on Windows 11 (build 26300) with a Scarlett Solo 4th Gen. The raw path opened 4 channels and the microphone came through on channel 2 (the table above).the default microphone open (48000 Hz, its 4 channels, one given to the game: Auto, ...), and the microphone works in game (reporter confirmed). Screenshots to follow.Not tested / open questions
🤖 Generated with Claude Code