Add an MCP server for scripted in-game testing #1025
Open
nicholasgower wants to merge 16 commits from
nicholasgower/feature/mcp-ingame-testing into main
pull from: nicholasgower/feature/mcp-ingame-testing
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!1025
Loading…
Reference in a new issue
No description provided.
Delete branch "nicholasgower/feature/mcp-ingame-testing"
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?
Why: the project's only in-game test today is a person playing. This adds an MCP server so an agent or a script can start a build, drive controller input and look at the frame, which makes in-game regressions repeatable.
What this adds
scripts/hookshot_mcp.py, a Model Context Protocol server that turns a built Hookshot intosomething a test can drive. It is a separate Python process: nothing is linked into
hookshot.exe, and it needs no gamefiles of its own.
folder, save folder and shader/pipeline cache.
HOS_INPUT_LIVE) and wait for the game to logthat it read the line.
RX_VK_SHOT_FILE, WebGPURX_SCREENSHOT_FILE) and returns it inline as MCP image content plus a path on disk, with the present number.!quitlive command, then terminates if it does not.hookshot://log,hookshot://last-screenshot,hookshot://status.Game-side seams added for this:
HOS_INPUT_SCRIPT_ONLY, the analog stick tokensLX=,LY=,RX=,RY=, the!quitlive command,
HOOKSHOT_NO_ENV_FILE, and the one-shot screenshot request files.Docs:
docs/mcp.md(install, host configuration, tools, launch options, what the server sets, limits). The tool andresource tables in it are checked against the live server by
tests/test_docs.py.Why this matters for testing
Today the project has two testing speeds: game-free unit tests that run in seconds, and a person playing the game. Anything
in between — start the game, reach the title, open a menu, look at the frame — is done by hand, one run at a time, and
cannot be repeated after a change.
That is the gap this closes:
can be checked by the same scripted sequence: it either produces the expected frames or it does not. The log, the input
lines and the PNGs are left in a run folder to compare.
cutscenes, the pause and file menus, resolution and frame-generation settings, controller mappings and save paths
without a human at the keyboard.
same protocol; the tools are plain JSON calls and the frame comes back as an image, so "is the title screen drawn?" is a
question a model can actually answer about a real build.
this lands.
Rebased, and the previous PR
This is the same branch as #764, which was closed to keep the queue clear with "please rebase on current main and reopen".
It is rebased onto
mainas it stands at the last push, and the game has been rebuilt from this branchand driven end to end.
What I tested
Not only the unit tests: the server was run against a real build of this branch, over its real stdio transport, and every
tool was exercised. Build: a normal builder layout (
hookshot.exelauncher,bin\,romfs), Vulkan back end, Windows,13 min 47 s, 0 failed modules, 100.0000% of 17,646,501 instructions translated.
Game-free suite (what CI runs):
tests/test_mcp_server.pygame_dir, fake-game end to endtests/test_docs.pytests/test_mcp_scenario.pytests/test_links_case.pytests/test_pr_check.pyscripts/pr_check.py --base mainThe full game-free suite gave 301 passed, 5 failed, 20 skipped (run on Windows with llvm-mingw and the Vulkan
and SPIR-V headers, at
--jobs 6so the fixture builds are not starved). None of the five touches a file in thisPR:
tests/test_antilag.pyandnx2/test_nup_out_of_space.pyfail identically in a cleanorigin/mainworktreeat the base commit (the first is main's present-ahead refactor moving the Anti-Lag call out of
present_image),nx2/test_nup_paths.pyneeds symlink privileges this PC does not grant,tests/test_wayland_libdecor_hint.pytrips a clang-only
-Wunused-but-set-global, andtests/test_precompile_compute_guard.pyneedswindows.hforits
--target=x86_64-pc-windows-msvcfixture. The four checks this PR adds, and the capture checks it adapts,all pass.
Every tool against the real game, over stdio (34 checks):
launchwait_readyworksstatusVulkan (hookshot_vk.dll), uptime, last screenshot) and after stopscreenshotpresentparsed (e.g. "present 116"); the picture is the presented framepressAacknowledged in the game log, and it started New Game — the frame changed from the title screen to the intro cutscenestickLX=,LY=,RX=in the log; the frame changedsequence0:DOWN/120,400:DOWN/120,800:A/150); the frame changedwaitlogshid: live) and unfiltered; every injected line appearsstop!quit, exit code 0, process gone; a secondstopis safeextra_env, after stop — all clear messagesThe visual half of that run was checked too: title screen, then the cutscene after
A, then further changes afterstickandsequence— 12/12 visual checks. One detail worth knowing (now in the docs): the very firstwait_readyscreenshot of a coldrun is a black boot frame, because it returns on the first present while the shaders are still being prepared; the run above
waited for the title before asserting on the picture. The screenshots stayed on the development disk; no game-derived file is
in this PR.
Finally the server was installed as a project MCP server in OpenCode (V2
mcp.serversconfiguration) and the nine tools werecalled through OpenCode itself —
status,launch(withwait_ready),wait,press,stick,sequence,screenshot,logs,stop— all returned the expected results, and the run ended with exit code 0.Bug found and fixed by this testing
--game-dirandHOOKSHOT_MCP_GAME_DIRselected the folder'shookshot.exe. In a folder made by the builder ormake_game.pythat is the setup launcher, which opens the installed builder (builder/setup/setup.c), not the game inbin\;launchwould have started the builder setup and no screenshot would ever have arrived.resolve_pathsnow prefersbin\hookshot.exeand falls back to the top of the folder for a development build, andtests/test_mcp_server.pycoversboth layouts.
docs/mcp.mdalso now gives the V2 OpenCode configuration (mcp.servers, noenabledfield) and notes thatwait_readyreturns on the first present, which on a cold cache is a boot frame while the shaders are still being prepared.
On the CLI-first feedback in #764
The points raised there — a CLI, files instead of inline images, state in run folders, waits on game state, a GPU lock — are
reasonable, and this keeps the door open for them. Every tool is a thin call into
GameSession; the run state already livesin a run folder (
log.txt,input.txt,shot.request,shots/);screenshot(inline=false)returns a path instead of imagecontent; and input waits for the game's acknowledgement line rather than a fixed sleep. A CLI front end over the same
functions, a wait-on-log-state tool and a GPU lock are the natural next steps, and none of them need this PR to change shape.
Settings and the cheat menu (added since this PR opened)
settings()reads every row of the Insert menu — Graphics, Gameplay and Controls — with its key, label, value, rangeand the text the player sees. When the cheat menu is on it also returns the Cheats page's eleven rows, the pouch's
items, the 31 teleport destinations and the inventory's 85 items with their counts.
set_setting(name, value)goes through the menu's own setter: it clamps, applies a quality preset or a control layout,marks the settings Custom and writes
hookshot_options.ini. A row that says it needs a restart still does.cheat(name, value, item)runs a cheat row directly: heart protection, no-clip flight and the free camera; mark andreturn; the clock; teleports by name or index; the age; the pouch; the inventory (a wanted count, or -1 to
unlock/relock).
launch(cheats=true)turns the cheat menu on withHOS_OOT_CHEATS=1, which is otherwise a line inhookshot_env.txtbefore start.
On the game side these are three live-input commands —
!settings,!setting <key> <value>,!cheat <name> <value> <extra>— answered in the game log, which the server reads back. The rows cross the renderer table (API 8) fromgpu/vk/vk_menu.cppthroughhost/rx_backend.ctohost/nn_hid.c. The WebGPU back end has no Insert menu and says so.Testing found and fixed one real defect: two cheat actions in quick succession overwrote each other (the menu kept a single
atomic), so an inventory edit sent right after a pouch edit was lost. They are a queue now, and both are taken.
Real-game verification against a build of this branch (Vulkan, a copied save): 23 settings listed with ranges and text;
set_setting("frame_pacing", 1),set_setting("text_speed", 3)andset_setting("frame_generation", 0)applied, shown bysettings()and written tohookshot_options.ini; an unknown key refused; heart protection and the free camera toggledand read back;
cheat("set_time", 18)made the game log "cheats: requested 18:00";cheat("pouch", item="Bombs", value=5)logged "cheats: pouch Item_Bomb +5";
cheat("teleport", item="Kakariko Village")moved Link from Death Mountain Trail toKakariko Village (screenshot, and "cheats: teleport child to KakarikoVillage_Child ... request sent");
mark_positionlogged the position; two actions sent back to back both ran.
The game-free suite: 301 passed, 5 failed, 20 skipped — the five are main's own or this PC's toolchain, as
described above.
Not included
No game files, dumps, builds, saves, logs or screenshots. Test runs stayed under
work/, which is ignored.Hi! This PR has merge conflicts with
mainnow (a lot has landed today). Could you mergemainin or rebase and resolve them? Happy to give it a proper review once it's up to date.As of now, all conflicts have been resolved.
Thanks, this is a well-scoped change, and the split is clean: the MCP server is a separate stdio process (no network port, no auth surface), and the game-side hooks are all env-gated, so a normal run is unaffected.
One thing to fix before merge: the Linux
Python source checksjob fails on the newtests/test_docs.py(docs/development.md:14: link does not resolve: DESIGN.md). The file isdocs/design.md, so the link only resolves on case-insensitive filesystems (Windows passes). That link is already on main, but your new test is what trips on it. Changing the link todesign.md(line 14, and theDESIGN.mdtable entry around line 55) should fix it. The two known main failures (test_output_resolution / test_program_cache_cleanup) are not the cause.Things I checked and am fine with:
HOS_INPUT_LIVE/RX_VK_SHOT_FILE/RX_SCREENSHOT_FILE/HOS_INPUT_SCRIPT_ONLY/HOOKSHOT_NO_ENV_FILEgated; unset means no behaviour change.!cheat <number>keeps its old meaning.tests/test_mcp_server.py,test_docs.pyandtest_shot_request.pyare registered intests/run_all.py;mcpis only needed for those two tests.Notes, not blockers:
launchtakes arbitraryexeandextra_env(only the control vars are reserved, so e.g.LD_PRELOADorPATHpass through), andscreenshot(path=...)unlinks and then writes whatever path it is given. Fine for a local dev tool, but worth one line indocs/mcp.mdso nobody exposes it to an untrusted host.!quitcallsexit(0)from the live-input thread. You flush the log first, which is good; just be aware it skips any other shutdown work.MKWVK_API_VERSIONgoes 9 to 10, so the exe andhookshot_vk.dllmust be rebuilt together (expected, just flagging).mcp==2.3.0is a new pip dependency in the CI workflow; I did not verify that pin.Thanks for the review. All addressed, plus the branch is rebased again onto current
main(581afaf) — it had fallen behind again after the last comment, and the conflicts are gone.Python source checksfailure:docs/development.mdnow links[design notes](design.md)and its table entry saysdesign.md, so the link resolves on a case-sensitive filesystem.python -I -B tests/test_docs.pypasses here (Windows), and the path now matches exactly.docs/mcp.mdhas a new line under Safety and limits: the server runs as the user and trusts its host,launchstarts any executable and passesextra_envthrough (only the control switches are reserved, soPATH/LD_PRELOADincluded), andscreenshotunlinks and writes any path it is given — use it with a trusted host, not an untrusted client.!quit: intentional for a test run (flush, thenexit(0); the server falls back to terminate/kill if the game does not go). It is not used outside test runs.MKWVK_API_VERSION9 → 10: yes, the executable andhookshot_vk.dllare always built together; the table gainedsettings_dump,setting_setandcheat.mcp==2.3.0: verified here — the server and the new tests run on 2.3.0 (importlib.metadata.version("mcp")reports 2.3.0).The 11 failures here are environment gaps (Vulkan/SPIR-V and Windows SDK headers, symlink privileges, and clang fixture quirks) and fail the same way on a clean
origin/mainworktree.Follow-up: what the latest commits on the branch are, after the rebase onto current
main(9a2e80e; heada7ce62d, 11 commits, 28 files).Since the review
feat(scripts): add a scenario runner for scripted game checks-scripts/mcp_scenario.pyruns a JSON scenario through the server's own session code (press,stick,sequence,wait,screenshot,settings,set_setting,cheat,expect_log), fails on the first unacknowledged input or unmatched expectation, writes a transcript and screenshots underwork/mcp/scenarios/<name>/, and--buildrebuilds withHOOKSHOT_KEEP_BUILD_FILES=1. The fake game moved totests/fake_game.py;tests/test_mcp_scenario.pycovers a passing run and an impossible expectation. This commit also adds the docs: a "Driving the game" guide indocs/mcp.md(start-up timing, the remake's control map, the pause menu's rules, what persists, the build/run loop) and a "Scenario file" section, plus the command inCONTRIBUTING.md.tests/test_links_case.py(same commit) - fails a relative link whose path case differs from the file on disk. That is theDESIGN.mdvsdesign.mdfailure the Linux job hit; the check is stdlib-only and does not need themcppackage, so it runs wheretest_docs.pyskips.docs: wait for the automatic checks after opening a pull request- the contributing rule to wait for Actions and push fixes for failures.docs: fix the design link's case and note the MCP trust model- thedocs/development.mdlink fix (the CI blocker) and the trust-model line indocs/mcp.md.Before those
docs: map the in-game actions behind the MCP tools(docs/actions.md) - the research map for equipment, the held item and the ocarina: the action classes and entry functions, the pause-menu selection chain, the item grid, the player equipment words and the ocarina manager. The three tools themselves are not in this PR yet; this is the write-up of what was found and what is still open.docs: require an MCP tool for every player action- the design rule indocs/design.mdsection 8 andCONTRIBUTING.md.feat(mcp): drive the Insert menu's settings and the cheat menu from MCP-settings,set_setting,cheat, andlaunch(cheats=true).--game-dirfix for builder-layout folders.Checks: the previous head passed every job, including
Python source checks (ubuntu-24.04); the run for this head is in progress, and I will push fixes if anything fails.A control bridge is out of scope of this PR.
Check status on this head (
837a54b, rebased ontomainatb2e1c5a6).Passing: Windows Vulkan back end, AppImage, Hookshot Builder package, Native F2I lowering, workflow lint. The earlier Windows Vulkan failure was damage from the
mainmerge (twotx_deadzonedefinitions,menu_take_cheat_actionon the oldcheat_actioninstead of the queue, andcheats_enabledwheremainnow sayscheats_on()); fixed in this head and compile-checked with the job's own compiler and Vulkan headers.Failing, in the Python source checks: none of the tests this PR adds. The six failures (
test_bc_raw_targets,test_display_startup_failure,test_parallel_init_order,test_parallel_rb_refresh,test_present_aspect,test_present_semaphores) reproduce identically on a cleanorigin/mainworktree atb2e1c5a6, so they are main's fixture/source mismatches. Ubuntu addstest_dlss_actor(the checked-inshaders/dlss_actor_frag_spv.hno longer matchesdlss_actor.frag) andtest_menu_font_latin(fontconfig asked for a different font), both unrelated to this branch.The PR's own tests pass on both platforms:
test_mcp_server,test_mcp_scenario,test_docs,test_links_case.Rebased with upstream main.
I've used this PR to test game mechanics with raw user input based on deterministic input. As far as I can tell, this MCP server is ready for others to use and add features to. Let me know if you've tested this server and found something lacking.
I'm not planning on rebasing this any more. If a project maintainer would like to merge this, they will need to fix merge conflicts themselves.
The author has said they won't rebase this again, so whoever picks it up will need to resolve conflicts in vk_api.h, vk_menu.cpp, vk_menu.h, vk_replay.cpp, nn_hid.c, rx_backend.c, CONTRIBUTING.md and the source-regressions workflow. Before that happens, a few things in the current head need fixing:
settings_dump,setting_setandcheattoMkwVkRenderer, but the diff doesn't touchMKWVK_API_VERSION, which is 17 on main. The earlier 9-to-10 bump was lost in a rebase. It needs a bump, and 18 is already claimed by several open PRs, so check for a collision.HOS_INPUT_SCRIPT_ONLYwithgetenv(...) != NULL, so setting it to=0turns it on. Since #2042 this should use env_on.!quitcallsexit(0)from the live-input thread (nn_hid.c:411). That's fine for a throwaway save folder, but it can interrupt a save write that's in progress.The PR also bundles process changes that deserve their own decision: a design.md rule that every player action needs an MCP tool, a CONTRIBUTING rule, and
scripts/pr_check.py. I'd split those out from the server itself.@nicholasgower, @Joey-Magnus: I would like to pick this up, since it is waiting for someone to rebase it. Nicholas, your commits keep your name, and if you would rather keep the pull request yourself, say so and I stand down.
What I plan, following the two reviews here and hckrman101's note on #764:
docs/control.md,scripts/hookshot_ctl.py). The bridge already gives timed presses, sticks and sequences with replies, screenshots, the menu's options, teleport and age, waits and a clean quit. So the server becomes a thin layer over that client, and the game-side changes here are no longer needed: the three renderer-table entries and their API version bump, the!quitthat exits from the input thread, the one-shot screenshot files. That removes every conflict Joey listed invk_api.h,vk_menu.cpp,vk_menu.h,vk_replay.cpp,nn_hid.candrx_backend.c. Where a switch is still read, it goes throughenv_on.design.mdrule, the CONTRIBUTING rule andscripts/pr_check.pyare not part of it.It will come as a new pull request that names this one and carries its history, with a real-game run on Linux as evidence. I will say here when it is up.
A correction to my note above, before anyone relies on it: I called the bridge's quit "clean". It is cleaner than
!quitin that it does not exit from the input thread, but it does not wait for a save in progress either: it asks the renderer to close and then exits the process (host/control.c, thequitcommand). So Joey's third point holds for a server on the bridge too. The new pull request will deal with it there, not claim it solved.Also for scope: the bridge's
option_getleaves out the Debug tab's cheat rows, so of this pull request's cheats only teleport and age have a bridge equivalent today. Whether the others belong on the bridge or stay out is a question I will put in the new pull request rather than decide quietly.