feat(menu): text fields in the Insert menu, and a Custom output size typed in it #2594
Open
mooki000 wants to merge 2 commits from
mooki000/menu-text-fields into main
pull from: mooki000/menu-text-fields
merge into: MagicBeanDev:main
MagicBeanDev:main
MagicBeanDev:github/pr/2893
MagicBeanDev:github/pr/2890
MagicBeanDev:github/pr/2887
MagicBeanDev:github/pr/2878
MagicBeanDev:github/pr/2875
MagicBeanDev:github/pr/2874
MagicBeanDev:github/pr/2873
MagicBeanDev:github/pr/2871
MagicBeanDev:github/pr/2756
MagicBeanDev:github/pr/2897
MagicBeanDev:github/pr/2896
MagicBeanDev:github/pr/2718
MagicBeanDev:github/pr/2842
MagicBeanDev:github/pr/2691
MagicBeanDev:github/pr/2895
MagicBeanDev:github/pr/2743
MagicBeanDev:github/pr/2765
MagicBeanDev:github/pr/2708
MagicBeanDev:github/pr/2732
MagicBeanDev:github/pr/2755
MagicBeanDev:github/pr/2817
MagicBeanDev:github/pr/2876
MagicBeanDev:release-please--branches--main
MagicBeanDev:release-please--branches--main--release-notes
MagicBeanDev:github/pr/2778
MagicBeanDev:github/pr/2888
MagicBeanDev:github/pr/2866
MagicBeanDev:github/pr/2877
MagicBeanDev:github/pr/2800
MagicBeanDev:github/pr/2829
MagicBeanDev:github/pr/2850
MagicBeanDev:github/pr/2889
MagicBeanDev:github/pr/2882
MagicBeanDev:github/pr/2860
MagicBeanDev:github/pr/2849
MagicBeanDev:github/pr/2886
MagicBeanDev:github/pr/2794
MagicBeanDev:github/pr/2840
MagicBeanDev:github/pr/2830
MagicBeanDev:github/pr/2834
MagicBeanDev:github/pr/2719
MagicBeanDev:github/pr/2742
MagicBeanDev:github/pr/2741
MagicBeanDev:github/pr/2820
MagicBeanDev:github/pr/2771
MagicBeanDev:github/pr/2859
MagicBeanDev:github/pr/2858
MagicBeanDev:github/pr/2823
MagicBeanDev:github/pr/2757
MagicBeanDev:github/pr/2769
MagicBeanDev:github/pr/2844
MagicBeanDev:github/pr/2843
MagicBeanDev:github/pr/2673
MagicBeanDev:github/pr/2855
MagicBeanDev:github/pr/2739
MagicBeanDev:github/pr/2807
MagicBeanDev:github/pr/2851
MagicBeanDev:github/pr/2789
MagicBeanDev:github/pr/2814
MagicBeanDev:github/pr/2818
MagicBeanDev:github/pr/2774
MagicBeanDev:github/pr/2772
MagicBeanDev:github/pr/2750
MagicBeanDev:github/pr/2786
MagicBeanDev:github/pr/2785
MagicBeanDev:github/pr/2782
MagicBeanDev:github/pr/2779
MagicBeanDev:github/pr/2775
MagicBeanDev:github/pr/2706
MagicBeanDev:github/pr/2766
MagicBeanDev:github/pr/2747
MagicBeanDev:github/pr/2759
MagicBeanDev:github/pr/2745
MagicBeanDev:github/pr/2740
MagicBeanDev:github/pr/2733
MagicBeanDev:github/pr/2725
MagicBeanDev:github/pr/2727
MagicBeanDev:github/pr/2728
MagicBeanDev:github/pr/2729
MagicBeanDev:github/pr/2721
MagicBeanDev:github/pr/2722
MagicBeanDev:github/pr/2715
MagicBeanDev:github/pr/2714
MagicBeanDev:github/pr/2707
MagicBeanDev:github/pr/2703
MagicBeanDev:github/pr/2705
MagicBeanDev:github/pr/2697
MagicBeanDev:github/pr/2679
MagicBeanDev:github/pr/2676
MagicBeanDev:github/pr/2669
MagicBeanDev:docs/what-hookshot-connects-to
MagicBeanDev:fix/hdr-encode-test
MagicBeanDev:fix/main-tests
MagicBeanDev:fix/play-cache-files
MagicBeanDev:perf/game-barrier-order
MagicBeanDev:feat/heap-lazy
MagicBeanDev:feat/texture-detail
MagicBeanDev:perf/astc-opaque-bc1
MagicBeanDev:fix/fs-dir-lock
MagicBeanDev:fix/framegen-native-kept
MagicBeanDev:fix/update-no-downgrade
MagicBeanDev:port/k10-gcn-f16-mirrors
MagicBeanDev:fix/framegen-mult-cap
MagicBeanDev:chore/release-as-2.1.1
MagicBeanDev:perf/pcache-load-switch
MagicBeanDev:fix/prepare-progress-log
MagicBeanDev:fix/update-closes-stale-game
MagicBeanDev:fix/hdr-scaled-sampled
MagicBeanDev:perf/first-start-preset
MagicBeanDev:perf/zero-upload
MagicBeanDev:fix/vcore-escape-2s
MagicBeanDev:test/fake-gh-atomic
MagicBeanDev:perf/batch-idle-flush
MagicBeanDev:test/finish-stub-a
MagicBeanDev:chore/record-2.1.0
MagicBeanDev:fix/main-red-tests
MagicBeanDev:fix/test-display-startup-driver-cache
MagicBeanDev:feat/mesh-shader-mode
MagicBeanDev:fix/menu-probes-at-startup
MagicBeanDev:fix/fps-counter-classic
MagicBeanDev:fix/test-log-batch-flake
MagicBeanDev:fix/narrow-screen-hud
MagicBeanDev:ci/prereleases
MagicBeanDev:release/v2.0.15
MagicBeanDev:hotfix/v2.0.15
MagicBeanDev:fix/present-ahead-replay
MagicBeanDev:release/v2.0.13
MagicBeanDev:fix/bracket-paths-2013
MagicBeanDev:fix/hdr-overlays-2013
MagicBeanDev:fix/fps-counter-scale-2013
MagicBeanDev:fix/stick-seen-at-rest
MagicBeanDev:hotfix/menu-fits-screen
MagicBeanDev:fix/f1-always-opens-menu
MagicBeanDev:fix/ini-keep-lines
MagicBeanDev:claude/cranky-pike-7cf247
MagicBeanDev:perf/texture-answer-flat
MagicBeanDev:fix/stamp-test-modules
MagicBeanDev:fix/appimage-fonts
MagicBeanDev:fix/display-awake
MagicBeanDev:release/v2.0.12
MagicBeanDev:fix/discord-windows
MagicBeanDev:rc/v2.0.12-tests
MagicBeanDev:fix/dlss-keep-feature
MagicBeanDev:rc/v2.0.12
MagicBeanDev:button-icons
No reviewers
Labels
Clear labels
accessibility
Barrier affecting people with disabilities
autorelease: pending
autorelease: tagged
bug
Something isn't working
dependencies
Pull requests that update a dependency file
documentation
Improvements or additions to documentation
duplicate
This issue or pull request already exists
enhancement
New feature or request
github_actions
Pull requests that update GitHub Actions code
good first issue
Good for newcomers
help wanted
Extra attention is needed
invalid
This doesn't seem right
question
Further information is requested
testing wanted
wontfix
This will not be worked on
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
Milestone
Clear milestone
No items
No milestone
Assignees
Clear assignees
No assignees
2 participants
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2594
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki000/menu-text-fields"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What this is for: a player with a monitor the menu has no size for (3440x1440, 2560x1080, 2560x1600) can type that
size in the Insert menu instead of editing
hookshot_options.iniby hand, and every later setting that is text (aco-op name, a relay address, a room secret: #2269, #2274) has a field to live in. What is measured: on Linux (Arch,
RTX 4090, NVIDIA 615.78.08), base
mainfce53453, head cefdafbb: a game-free test of the field, the host's queue and thewiring (3 parts pass with g++ and clang++; the same test fails on main's source); the whole of
tests/run_all.py(450 passed, 1 failed, 20 skipped; the failure is also present on main alone); one headless run, tf6, showing typing
3440x1440, caret movement, committing, and that size in both the row and options file. Sixran without a corelines were counted, not investigated. Not measured: refusal, Esc cancelling or a completed edit after erasing
in the game on this head; Windows execution, real keyboard/clipboard/controller input, an input method, SDL2 or Deck.
Updated 2026-11-12: rebased and the Windows check repaired. Head
85e3df35on main6f7ab2a7(it merges with55b976c0), two commits:gpu/vk/vk_menu.cppwhere they met keep both, main's Save prompt and dispatch and the field's Edit, Paste, Done, Cancel and its dispatch;tests/test_menu_text_field.py's generated sources (and the screensaver test's) callgetenvandsscanf, which Microsoft's headers mark deprecated, and with warnings as errors the fixture did not build there. They now begin with_CRT_SECURE_NO_WARNINGS, as main's other fixtures do. No runtime code, check or warning flag changed.What ran on the new head: the menu's checks with g++ and clang++ (the nine named below plus #2347's
test_bug_report.pyandtest_menu_tune.py: pass with both), the three new or updated tests on main6f7ab2a7's tree (fail), and a Windows-target syntax compile of the generated sources with a declaration marked deprecated the way Microsoft's is (fails before the guard, passes after). What is from before the rebase, oncefdafbb: the whole suite's totals and the game run tf6 below. Nothing was run on Windows; the project's Windows job is the check.Refs #2269. This is the text-fields commit of the co-op draft #2274 (
b4ec89b7there), alone onmainso that it can be reviewed first; since the rebase below it also carries main's Bug report row beside the field.Problem
gpu/vk/vk_menu.cppRow:value,lo,hi,step,text). Nothing in the menu takes typed text: the window's thread forwards key presses (host/display.cWM_KEYDOWN,host/posix/display_sdl.ckey events), never characters, and the renderer's interface(
gpu/vk/vk_api.hMkwVkHost) has no call for them.hookshot_options.ini. Today that is the Output resolution of theplayer's own: the readme sends players to the file for
output_res=<width>x<height>, with rules to get right (eachside a multiple of 8, at least 640x360, at most 5120x2880's pixels, between 5:4 and 48:9), and a size the file gets
wrong becomes the game's own 2048x1152 with only a line in the log (
host/nn_applet.c: "is not a size the game canuse").
What changed
The field, host text queue, platform input paths, documentation and their game-free checks change together.
gpu/vk/vk_menu.cppTextField,text_edit,field_rows, drawn by main's value cell astext_cell): a row whose value is typed. Left, Right, Home, End, Backspace and Delete edit; Ctrl+V and Shift+Insertpaste the clipboard's first line; Enter sets the text (the field's own check can refuse it and says why in the help
box), Esc puts it back. A controller: confirm and back are Enter and Esc, the D-pad moves the caret, a free face button
(the top one with either layout) pastes (
gpu/vk/vk_menu_keys.h;gpu/vk/vk_ui.h: its prompt, the glyphkPasteafter main's
kCompact). A click elsewhere sets, a right click puts back; F1, Home or closing the menu put back. While afield has the keyboard no key does anything else, and the game gets none (as for the whole menu:
host/nn_hid.ckey()). The prompts bar shows Paste, Done, Cancel while a field is edited, and Edit on a field's row, inside main'scompact-bar layout.
vk_imgui.cppbake_some), so a fieldkeeps printable ASCII; any other character typed or pasted (é, 中, an emoji) is left out rather than drawn as a
missing glyph.
WM_CHAR(Windows,host/display.c; the window is ANSI, a double-byte character's pair isleft out) and SDL's text input (Linux,
host/posix/display_sdl.c; SDL3's and SDL2'sSDL_TEXTINPUTread at theiroffsets in
host/hos_sdl.c), turned on only while a field has the keyboard, so no input method's window opens overthe game. A small queue in
host/rx_backend.ccarries characters and editing keys to the menu. The window's threadreads the clipboard (
WM_APP + 12on Windows, the SDL loop on Linux).MKWVK_API_VERSIONgoes from 18 to 20. One newMkwVkHostmember,take_text,appended last (after main's
prompts_live, version 17), filled byrx_backend.c'svk_hostin the same place; nothingelse in the interface moves. Why 20 and not 19: the open #2317 (data folder) already takes 19 for its own
MkwVkHostmember, and this keeps the co-op draft #2274's current text-fields interface number (its Co-op tab is 21). This does not
make two independent interface additions interchangeable: whichever lands later must append after the merged call and
take a fresh version. The executable and
hookshot_vk.somust come from the same build.resolution): typed as
<width>x<height>, checked by the rulesoutput_res=already has (host/hos_wide_size.h); asize the row offers is chosen there; the Output resolution row then shows it, and the restart question counts a new
size of your own as a change. Like the Output resolution row itself it is
preset_exempt(the Quality preset leavesit alone, #2042's rule), and a size typed calls
env_release, so it is the player's own from then on also in a runwhose
HOS_OUTPUT_RESheld the row.RX_VK_MENU_SCRIPTgainsTYPE:<text>and, while a field has the keyboard,HOME,END,BACK,DEL, so aheadless run can type (
docs/development.md). The readme's Graphics table gets the row, and the paragraph on sizes ofyour own says to type it there.
tests/test_menu_keys.pykeeps all its existing keyboard checks. Its source extraction now ends the Esc block atthe text-editing branch, rather than looking for the old first page's
if, which is now anelse if.switched on and the queue takes nothing.
How to try it
Insert (or F1), Graphics, down to Custom output size, Enter, type
3440x1440, Enter; the Output resolution rowthen shows it and the menu asks for a restart on closing. Headless:
RX_VK_MENU_SCRIPT='25:INS,26:DOWN,26.5:DOWN, 32:ENTER,33:TYPE:3440x1440,37.5:ENTER'withRX_VK_SHOTSandRX_VK_SHOT_MENU=1.How it was tested
Environment: Arch Linux, kernel 7.2.15, i9-13900K, RTX 4090 with NVIDIA 615.78.08, clang 23.1.3 and g++ 16.2.1; base
mainfce53453, head cefdafbb; 2026-11-11; the game run headless (no window), pictures 1280x720.The new test, both ways.
tests/test_menu_text_field.pyon the branch, with g++ and again with clang++(
-Wall -Wextra -Werror):The same file on main fce53453's source:
FAIL: vk_api.h: no HOS_TEXT_KEY and HOS_TEXT_OFF/ON/PASTE(bothcompilers). It cuts the field's edit function out of
vk_menu.cppand drives it with key and text events (typing, thecaret, limits, ASCII only, Enter and Esc), Custom output size's Enter for good, rounded, offered, refused and emptied
sizes; the host's queue cut out of
rx_backend.cand compiled as C; and the wiring, including thatvk_hostgiveshost_take_textintake_text's place.tests/screensaver_test.c(the real window procedure under mocks) andtests/test_menu_restart_slots.pyupdated forthe new lines; on main's source they fail as they should (the new mocks unused under
-Werror;FAIL: restart_changed does not skip a row with no slot). #2042's menu tests (test_menu_env_overrides.pyandtest_menu_env_on.pywithboth compilers,
test_preset_exempt.py) and #2177'stest_menu_sections.pyandtest_upscaler_rows.pypass on thebranch.
The whole suite,
python -I -B tests/run_all.py(32 at once, the test gate's idle class):Final head cefdafbb, in the idle scheduling class with the pinned Python, 2026-11-11 17:31:30–17:35:36 CST;
load average 92.91 / 115.60 / 148.65 → 94.12 / 106.14 / 137.48. The sole failure is
tests/test_env_on.py:HOS_FPS_LOG (host/oot_high_fps.c:15)still uses set-means-on semantics. The same check fails at that exact site onmain
fce53453alone; this patch changes neither the switch nor its check. No core dumps were found.The earlier suite found a real extraction failure in
test_menu_keys.py; it was repaired without dropping anychecks. The final run above includes that repair and passes the test. The earlier run also timed out
test_recorder_sync.pyandtest_fpx_sticky.pyunder heavy load; both pass in this final suite. No performanceconclusion is drawn from test duration or machine load.
In the game on cefdafbb: tf6, started 2026-11-11 at 18:24:39 CST, headless (
HOS_HEADLESS=1), no shaderpreparation (
RX_VK_PRECOMPILE=0). The script reached the correct Custom output size row, typed3440x14,appended
40, moved the caret and committed with Enter. Both the Output resolution row and the saved file show3440x1440. The game itself started at 2048x1152; the new size requires a restart, which this run did not perform.fce53453output_res=3440x1440saved; second edit left unfinishedThe wrapper's frame-source label is quoted unchanged; the recorded environment confirms a headless run. This was
not a timing measurement: no FPS or frame-time conclusion is drawn. The six
ran without a corelines arelog lines 79, 81, 82, 83, 86, 91: four
PreActorModelCompose, oneAudioFrameworkThread, oneSceneLowPriority. They are counted and not looked into here.All six frames and the labelled panel were inspected. Panels 1–5 show None →
3440x14→3440x1440→ thecaret moved left → Enter commits, with “Restart needed: Output resolution”. Panel 6 is a later unfinished
edit: its field is empty while the committed size remains in the row above.
Not shown in the game on this head: a refused size, Esc cancelling, or a completed edit after erasing.
The 62 s run ended during that second edit, before those remaining scripted steps. The current game-free field
test covers refusal and cancellation. Earlier run tf4, build cf925b67 (the same tree as 34a634a3, on main
49f75dc8) showed100x100refused with its reason and Esc keeping 3440x1440; that is evidence on the older head,not a repeat of those steps on this head. No retry was run.
Windows, syntax only:
host/display.c,host/hos_sdl.candhost/rx_backend.ccompiled with clang--target=x86_64-w64-mingw32and llvm-mingw's headers,-Wall -Wextra:display.candhos_sdl.cno warning,rx_backend.cthe same six as main's. Nothing was linked or run for Windows.Before pushing: the commit identity, trailers and title checks pass.
mainhad moved toef81aee7(past the tested basefce53453) and the branch merges with it without a conflict; nothing was rerun on the merge. Rerun on the head by me: the seven menu checks named above pass with g++ and with clang++, and the three new or updated ones (test_menu_text_field,test_screensaver,test_menu_restart_slots) fail on main's tree.Not tested
WM_CHAR, the clipboard message, Ctrl+V and Shift+Insert exist only as compiled text.RX_VK_MENU_SCRIPT'sTYPE, which enters the same queue the keyboard's characters do, after SDL.already,
host/display.cImmDisableIME; on Linux SDL's text input is on only while a field has the keyboard),SDL2 (only SDL3 on this machine), the Steam Deck's on-screen keyboard, AMD and Intel GPUs (the menu is drawn the
same way on all of them, but none was run).
output_res=3440x1440is honoured at thenext start is main's existing path.
Risks
Whichever merges later renumbers and appends after; a mixed pair of executable and
hookshot_vk.sois refused atstart, as for every bump.
were ever left on by a path this change missed, the keyboard would seem dead in the menu until Esc; F1, Home, closing
the menu, a click elsewhere and a right click all end it, and the test drives each.
WM_CHARhandling would show as a field that takes no characters, or takesthem twice; nothing else of the game reads that message.
Overlaps
From
pr_overlap.py --branch menu-text-fields(2026-11-11, onfce53453: 73 open pull requests touch one of its 15 files,10 conflict), each conflict hunk read:
host/hos_sdl.c,host/posix/display_sdl.cgpu/vk/vk_ui.hpaste_dothere,prompt_facethere): keep bothgpu/vk/vk_api.hgpu/vk/vk_api.h,host/rx_backend.chost/rx_backend.c(andgpu/vk/vk_api.hfor #2317, version 19)host_take_texthere,hos_wheel_state/hos_data_dirthere: keep both, in merge ordergpu/vk/vk_menu.cppgpu/vk/vk_menu.cpp,readme.mdQuestions for you
MkwVkHostwith a version bump the way you want new host calls to arrive, or would you ratherhave text reach the menu another way?
addresses are ASCII; a save folder with an é in its path could not be typed)?
Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 [email protected]
Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra [email protected]
Rebased onto
main(85e3df35on6f7ab2a7) and the Windows check repaired; the description's first block says what changed and what ran on the new head.The Windows failure of the first run was in the test, not in the feature:
tests/test_menu_text_field.pybuilds a fixture from the menu's source, which callsgetenv(throughhost/hos_wide_size.h) andsscanf. Microsoft's headers mark both deprecated, and with warnings as errors the fixture did not compile on the Windows runner. The generated sources now start with_CRT_SECURE_NO_WARNINGS, like main's other fixtures; the screensaver test's template had the same trap and got the same line. I could only check this with a Windows-target syntax compile here, so the Windows job on this head is the proof.The rebase met the merged Bug report row (#2347) in two places in
gpu/vk/vk_menu.cpp; both sides are kept. The game run in the description (typing a size, the caret, committing) was made before the rebase, oncefdafbb, and is labelled so.Rebased onto current main (
edaef2a9) asfaf1c9b1; nothing in what it does has changed.gpu/vk/vk_api.h: #1930 took API version 19 for the ocarina note bindings, so this one is 20 (it addstake_textat the end of the host table; all of main's ids are kept).gpu/vk/vk_menu.cpp: merged without conflict beside #2574's reset hook, #2435's keyboard prompts and #2347's Bug report row.gpu/vk/menu_i18n.h: this change rewords one hint of the Output resolution row ("Your own size, from Custom output size below."), and since #1864 every catalogue has that hint. Its entry is replaced in all 13 catalogues, none removed. The reworded translations were written with a language model on top of the existing ones; corrections are welcome.The text-field, menu-key, restart-slot, translation and switch checks pass on the new head with GCC 16.2.1 and Clang 23.1.3; the Windows-target syntax pass of the changed host files has no error. The whole suite: 470 passed, 1 failed, 20 skipped; the one is
test_input_background.pyunder Clang, which fails the same way on untouched main (a variable set and not used in a fixture this pull request does not touch).I merged this into current main. tests/test_menu_text_field.py (all three parts), test_menu_keys.py, test_menu_restart_slots.py, test_menu_i18n.py, test_output_resolution.py, test_menu_setting_walk.py, test_utf8_paths.py and test_env_on.py pass.
I checked the SDL event offsets by hand against SDL2's and SDL3's structs:
All match. The SDL3 text pointer is copied right away in
event_text. The queue in host/rx_backend.c:216 is locked and drops input while no field is focused. Text input is only on while a field has focus, so no input method window opens over the game.The one thing in the way is #2566. Both claim MKWVK_API_VERSION 20 for a new MkwVkHost member (
take_texthere,screen_sizethere). Merging #2566 on top of this conflicts in vk_api.h, vk_menu.cpp, menu_i18n.h, rx_backend.c and readme.md. You've already offered to rebase if #2566 goes first. In that casetake_textgoes afterscreen_sizeat version 21, and the Custom output size has to fit #2566's per-screen list (custom_output/output_extras).Thanks for checking the event offsets by hand. Agreed on the order: #2566 first. I am testing #2566 on Linux now (its head
ea303801), and I am preparing this branch on top of it so it can follow straight after:take_textafterscreen_sizeat API version 21, and the custom output size fitted to #2566's per-screen list (custom_output/output_extras). I will push that rebase when #2566 lands, with the tests you ran repeated on it.Rebased onto
15d4ae8c6now that #2566 is merged; final headc102f6f5.API 22: #2566 took 20 for
screen_size, and #2760 took 21 fornvn_program_created;take_textfollowsscreen_size.A typed listed size reuses its choice. An unlisted size joins its shape's choices; #2566's modes and monitor/native marks stay unchanged.
All 22 final checks pass with GCC 16.2.1 and Clang 23.1.3: the 19 requested checks, both changed #2788 drivers, and the README check. A full run of the project's checks in my fork, Windows included, is green on this exact head.
Earlier full suite: 496 passed, 4 failed, 20 skipped. Two compiler-invocation failures passed after repair; the two main failures were subsequently fixed by #2861 and #2883.
The two game runs were on
4914fdec(main8cf9a524), Linux/RTX4090, windowless.The menu run typed
1920x800, moved the caret, committed, selected listed2560x1080and3440x1440, then returned to the custom size; rows and saved sizes agree.The next start logs
picture 1920x800andcamera ratio 2.4000: #2566's rule gives this non-16:9 size its own picture/camera shape; the row shows 21:9.Both games exited 124 with zero fatal/crash/device-loss/kernel/NVRM/vcore-escape counts. A closing-pipe error in the first observer was recovered from existing receipts, without repeating that game.
The final head adds a clean rebase over five merges, one touching the menu file, and a docs-only commit. That combination was checked game-free only, not run in the game.
Not tested: Windows gameplay, a real window, HDR, physical keyboard/clipboard/controller input, IME or SDL2. No performance claim.
Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra [email protected]