feat(scripts): the pipeline tour, driven through the control bridge #111
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!111
Loading…
Reference in a new issue
No description provided.
Delete branch "HumanBean/Longshot:scripts/pipeline-tour"
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?
Type: feat · Risk: low · Tested on: not run yet (this head,
d41a45bd) · Needs: Linux, any GPU, a counting renderer pack for--vk-profile(the maintainer's local run)Why
Every release ships a pipeline list so a new install builds its pipelines before the title, not in play. The tour's part of that list (
assets/pipeline_states/tour.bin.xz) comes from a script that was never merged (Hookshot's draft https://github.com/NotRexed/Hookshot/pull/2048), so Longshot has no way to make it again. The renderer's committed play profile (assets/profiles/longshot_vk.profdata.xz, merged in #569) was recorded on one short route (Kokiri Forest and the Market) and should come from the same tour. This adds that script, driven through the control bridge, and lets it record the profile too. See #14 (players' pipeline lists, which the tour's list complements) and #505 (ship play profiles).What changed
A new script plays the game unattended from a new save through every Teleport destination at both ages and writes the tour's pipeline list. With
--vk-profileit also records the renderer's play profile on that route, andmerge-profileturns the recording into the profile builds use.scripts/pipeline_tour.py, ported from https://github.com/NotRexed/Hookshot/pull/2048 (HumanBean's). It plays from a new save through every Teleport destination at both ages, with the items, a dive, the pause menu, and the sword, shield and an item once an age and at the stops with passes of their own. Then it writestour.bin.xz+tour.mdthroughnx2/pipeline_states.py tour(--publish), which main already reads. The stops, the left-out stops, the presses, the memory guard (MEM, systemd scope), the games of 8 stops,merge,reportandvalueare the same as in the draft. What changed from it:HOS_INPUT_LIVEand log polling (Game,map_change,new_game,start_game). It usesteleportby the destination's index andage, and asks again onrefused,timeoutorcancelled. It gives up when the refusal says asking again can't help (PERMANENT, the game's own reasons fromoot_teleport), or after five sends that don't move Link (the Haunted Wasteland). It waits for arrival withwait map(loading_endafter the age switch), then callsstatusuntil the arrival's event is over. Presses aresequencewithunits: hos(whichscripts/longshot_ctl.pynow sends as frame steps), and waits arewait present. Frames come from the replies, and the GPU and build fortour.mdfromstatus. The game is asked toquit, so its lists are flushed.HOS_INPUT_LIVE's!cheatlines, because the bridge doesn't do it.!cheat 998state line is replaced by the bridge'sstatus.game. Its!teleport <map> <start>live command and theextraspart (boss rooms) are left out.HOS_FPS_MAX=1000,frame_rate_cap=6) where the draft used 240, still with every frame a 60th of game time (HOS_FPS_STEP=0, Game speed Per frame). V-Sync is off. The game skips the opening itself (HOS_SKIP_OPENING=1) in every part but the one that watches it. The draft's other settings stay (headless, 640x360, game upscaler at 50%, no pacing, frame generation, sharpening or HDR, instant text). None of these change a pipeline's key.ACT_AT, at both ages). A pipeline is a program plus fixed draw state, and a scene's lighting and fog are inputs to the same pipelines, so they're now used once per age (ITEMS_ONCE: Kokiri Forest as the child, Hyrule Field as the adult). They're also used at the stops with passes of their own that an item's draws could combine with (ITEMS_PASSES, each with its reason: Zora's Domain and the Water Temple for water, the Shadow Temple for the Lens of Truth (child only, since the adult's visit is left out), Death Mountain Crater (adult only) and the Fire Temple for heat haze). That makes 10 uses. Each one gets its own pause-menu pick, numbered across the whole route (item_stops), so a pick no longer repeats in every game of 8 stops. The every-item inventory, the dive and the pause menu stay as they were.wait present). It's now 60 frames (--settle-quiet, onrunandtour), and 600 at most as before (--settle-max: those are frames that are still bringing pipelines). The tour passes both to its runs, andreport.txtsays which were used (--settle-quiet 150is the old wait).!cheatlines were 70 ms of wall time apart. They're now 32 ms apart (the game reads the live file once 30 ms have passed,host/nn_hid.clive_input), followed by three presents (the render thread keeps the game a frame ahead of its presents). A frame slowed by a pipeline build is now waited for, which the wall-clock pause alone didn't do. The bridge's start poll is 0.1 s instead of 0.25 s. Nothing else waits on the game in wall time.HOS_FPS_MAX=1000,HOS_FPS_STEP=0) were already set in every part's game. They're unchanged, and so are the graphics settings, since those decide which pipelines exist.compare <new list> <old list>andtour --compare-to <old list>count the pipelines the old list has that the new one lacks (lost), and the other way round (gained), for checking that a faster tour loses nothing.trythat restores it, after what can fail (--cpusis checked first).put_backmoves the run's files withshutil.move(so--outcan be on another drive), one file at a time, and names any that didn't go back. An options file the folder didn't have is removed again (restore_ini). A stopped tour (Ctrl+C or SIGTERM) gives its run two minutes to end its game, dump the profile and put the folder back (run_child); before,subprocess.runkilled the run a quarter of a second in and left the headless game running. A run holds further stops while it puts things back (hold_stops), and writes its report as "stopped". A game whose bridge never answers is ended (start_game). The bridge client's ownFailed(a crash during a wait) ends the run with its report instead of a traceback. A run clears an earlier tour'sstates.binand report. A leftover<game>-tourcopy is refused, and a copy that can't reflink says so.cache/longshot_computes.binis put aside too, since the game writes earlier play's compute programs into a new list. The game gets none of the shell'sHOS_*/RX_*/A64_*/OOT_*switches (run_environ), an emptyHOS_ENV_FILEinstead of the folder'slongshot_env.txt, and its own--userfolder for the saves.game_exeisscripts/longshot_ctl.py's:bin/longshotin a builder's folder, whose top-levellongshotstarts the launcher.reload).calmtakes only a fresh snapshot (game.framewithin 10 presents of the reply's, asdocs/control.mdasks).--publish(this head):tour.mdnames no path of the run's (RX_VK_SHOTS), and its settings are the options the runs had (their--ini), notBASE_INIalone.cache/now (host/game_layout.c,vk_draw.cppstate_list_path): the tour putscache/longshot_pipelines.bin,cache/longshot_pipeline_states.bin(and_v1), the shipped list beside the game, and any loose ones an older build left, aside for a run, and readscache/longshot_pipeline_states.bin.longshot_*files,longshot_options.ini, thelongshotbinary. The Sway window-moving code is dropped (--window WxHjust shows a window).--cpushas no PC-specific default.--vk-profile <dir>onrunandtour(Profile,profile_end,counting_renderer,merge_profile):RX_VK_PGO=gen: itslongshot_vk.sonamesRX_VK_PGO_FILE); otherwiserunandtourrefuse before touching anything.RX_VK_PGO_FILE=<run dir>/vk.profraw. Oncenew_gamehas control of Link, the tour creates<file>.resetand waits for the renderer to take it (host/pgo_rt.c, every 500 ms), so the loading, the prepare and the title are left out. A renderer that doesn't take it within 10 s fails the run.<file>.dump, waits up to 10 s for the counters and moves them to<dir>/<game>.profraw, with<game>.jsonbeside it (part, GPU and driver, build, seconds). A dump that writes nothing fails the run, so a recording never ends empty. A game that never reached control keeps nothing.merge-profile <dir>runs the toolchain'sllvm-profdata merge -o work/longshot_vk.profdata <dir>/*.profraw(third_party/a64-kit/tools/toolchain.pyfind("llvm-profdata"), or--llvm-profdata;--outelsewhere). It refuses a folder with no.profraw, a file that isn't LLVM's raw format, one with no counted functions, and a merge that writes nothing.merge-profile --publishalso writesassets/profiles/longshot_vk.profdata.xz(Python'slzma, preset9 | PRESET_EXTREME, asxz -9e) and the lines forassets/profiles/README.md(date, parts, games, build, GPU and driver, OS and CPU, the LLVM version) to<dir>/README-note.md.RX_VK_PGO_FILEpath is kept out oftour.md's settings.tests/test_pipeline_tour.py, game-free.host/hos_main.c,gpu/vk/vk_menu.cppandgpu/vk/oot_cheat_items.h(the Hookshot is checked again: main fixed its name). The bridge commands and status fields are checked againsthost/control.candhost/oot_state_tap.c(status'sgamenow comes from the sampled state), and theunits: hossteps againstscripts/longshot_ctl.py. The lists' places are checked againstvk_draw.cpp,vk_paths.handgame_layout.c.RX_VK_PGO_FILE,.reset,.dumpand the raw header are checked againsthost/pgo_rt.candnx2/build.py. Each of the game's teleport refusals must be either retried or given up on. EveryBASE_ENVswitch must exist in the source, and everyBASE_INIkey must be one an Insert menu row writes.ITEMS_PASSES(each one visited and with a reason). The picks must all differ, and a game-freeplaymust use the items at exactly those stops and settle by the run's--settle-quiet/--settle-max.--vk-profileoff), refusals,--dry-run(which marks the item stops), the switches and the tour's per-game command (with and without--vk-profile, now with the settle's frames). Also checked: the speed settings in every part's game, the settle's defaults (in frames, under the old 150),--settle-quiet 150passed on bytour,--compare-to, and that the only wall-clock sleeps left are the live lines' gap and the bridge's start poll.merge,compareandlist_diff(lost and gained both ways, nothing lost against a merge's own input),set_ini, the report (now with its settle line) and--publish(noRX_VK_PGO_FILEin its note).tests/test_longshot_ctl.py) behind the realscripts/longshot_ctl.pyclient, taking frame steps ashost/control.cdoes. It covers a new game to control, a teleport refused twice and retried, a refusal that is final, a teleport that never moves, the age switch,settle(the old 150 frames and the new default), and the inventory through the live file (three frames per line).runandtourrefusing a renderer that doesn't count, before touching the game folder; a stand-in forpgo_rt.c's writer, against which stale files are cleared, the reset comes right after control (and not at all without the option), and the dump is moved to<dir>/<name>.profraw. A writer that takes neither request fails the run.merge-profileis checked with a stand-inllvm-profdata: the command,--out,--publish's xz and note, and its four refusals.value.docs/development.mdgets a new section, "The pipeline tour", and "The renderer's play profile" now gives the tour's recording andmerge-profilein steps 2 and 3.assets/pipeline_states/README.mdnow names the tour command.Measurements
Verdict: not measured yet. The tour has not been run on this branch, with or without
--vk-profile(WIP until it is).Hardware: to come (the maintainer's local run, Linux)
Scene: the whole tour route, from a new save
Method:
tourwall time andreport.txtper part, against the draft's numbers. For this head's faster route: one tour atc23b44f3(items at 16 stops, 150-frame settle) and one at this head on the same PC and game folder, thencompare(expected: 0 lost). For the profile, the recording merged and published, then frame time as inassets/profiles/README.md(the Market save, standing and running, 3 sessions each), main's profile vs the tour'sHow to reproduce: a game folder built from main (or a counting runtime for the profile), on Linux, with the commands below
Baseline: main has no tour script, so the before column is the Hookshot draft's own runs (https://github.com/NotRexed/Hookshot/pull/2048), on main's game otherwise.
fe5c60d0(no tour; the Hookshot draft's runs, RTX 3090, Linux, partial)d41a45bdThe estimate assumes the stops cost about what they did in the draft. Most of the saving is per game: the game skips its own opening instead of being pressed through it, and there are no 10 s resend waits. This head saves about 8,000 game frames on top of that, estimated from the code: 48 settles × 90 frames, plus 6 fewer item uses × ~630 frames (the pick plus the sword, shield and item, ~10.5 s of game time). That's ~135 s of game time, or ~45-75 s of wall time at 107-180 fps. It also saves ~25 s of
!cheatgaps (12 inventory steps × ~60 lines × ~37 ms), so ~1-2 minutes in all. Each stop also waits out the game's 8 s between teleports (wall time), so a stop costs at least 8 s however fast the game runs: only a run can say how much of the estimate is left.Flags and settings
None added to the game: the
HOS_*andRX_VK_*names the script sets for its games (BASE_ENV,HOS_ENV_FILE,HOS_CONTROL,RX_VK_PGO_FILE) are existing switches, each checked against the source bytests/test_pipeline_tour.py. The script's own options (--vk-profile,merge-profile) are documented indocs/development.md, "The pipeline tour" and "The renderer's play profile".Testing
Tested:
e182b7e8, on mainb9cdc432):python -I -B tests/run_all.pyon Linux (CachyOS, clang 23), 493 passed, 2 failed (the same on main:tests/test_bake_portability.py,tests/test_builder_log_read_only.py), 36 skipped.run --dry-runprinted the route.c23b44f3: the rebase onto main5d3ecc78had no conflicts. Every source text the checks read was looked up by hand in main's files.84421c99: read, not run.d41a45bd, rebased onto mainfe5c60d0with no conflicts): a code review of the whole PR (in a separate session) found 20 points; the fixes for the confirmed and plausible ones are in this head, with checks intests/test_pipeline_tour.py(folder_kept: the set-aside and put-back, the options file,game_exewithbin/,--cpus,run_environ,published_settings,run_childfor Ctrl+C and SIGTERM; the fake bridge: a stale snapshot, the 8 s refusal). Read and traced by hand, not run.Not tested:
python -I -B tests/run_all.py,tests/test_pipeline_tour.pyincluded: no checks are run on the author's PC./proc, the bridge is a Unix socket, the copy iscp --reflink), as its docs say;tests/run_all.pyskips its check there.--vk-profilerecording, and no A/B of this head's route against c23b44f3's.Must be tested:
python -I -B tests/run_all.py, expectingtests/test_pipeline_tour.pyto pass.c23b44f3and one at this head on the same game folder, thencompare <new>/play.bin <old>/play.bin. Expected: 0 lost, and the new tour's wall time (its last line) shorter.tour --vk-profile, thenmerge-profile --publish. Expected: one.profrawper game in<tour dir>/vk, "renderer profile: " in eachreport.txt, and the.xzand its note written. The pipeline lists should come out as without the option.Risks
What could break: only the script. A teleport refusal the tour should retry but treats as final (or the other way round), or a stop that misses: a run says so in its
report.txtandsteps.txt, and three misses in a row end the run. With--vk-profile, a renderer that is slow to take.resetor.dump(more than 10 s) fails its run rather than recording too little.Turning it off: leave
--vk-profileout; revert the commits for the rest.Compatibility: Nothing changes. The game is unchanged, and the committed profile changes only when someone runs
merge-profile --publishand commits it.Issues
See #14 and #505. Also https://github.com/NotRexed/Hookshot/pull/2048, the draft this ports; #569, the renderer profile it records again.
Reviewer notes
run'stry/finally,put_backandrun_childare what keeps a player's game folder as it was: worth the closest read.map_changeandnew_gameare the parts that replace the draft's log polling.PERMANENTis checked against the game's own refusal texts.ITEMS_ONCE/ITEMS_PASSESanditem_stopsdecide where the items are used. Ifcompareshows pipelines lost, the place to look is the item picks: 10 differ now, where before each game started again from the first (2-4 item uses a game).Profile.resetandprofile_endare the profile's two moments. The reset waits for the renderer to delete.reset, which only a counting renderer does, so a wrong game folder fails at its first control and not after a whole tour.cache/folder, the sampledstatus.game, the client's frame steps) together with--vk-profile.hot_funcs.txt,hot_order.txt,longshot_pieces.prof, from anRX_PGO=genbuild) is still parked.extraspart (boss rooms through a teleport-to-map command). GummiNaomi's comment on this PR offers a host change for it.HOS_INPUT_LIVEuse.Checklist
Everyone
type(scope): description) of the status line's type.main.python -I -B tests/run_all.pypasses (any failure that also fails onmainis named). (not run on this head: no tests are run on this PC)third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.docs/tested/for each new measured run (format indocs/tested-configurations.md). (to come with the run)tests/that reads changed source updated in this PR, not weakened.🤖 Generated with Claude Code
Some data for this PR's route, from a tour I ran with my own script over the last two days (Windows, RTX 4070 SUPER, driver 617.42; a build of af15c4a5 plus the host change below). I won't open a competing tour PR. What I'd send instead is below.
What a wider route finds. I recorded cold (
RX_VK_PRECOMPILE=0, no notes file) and compared against the shipped sources at514591a3(42,072 records, 233 compute programs):Banc/BancBasedResource/StartPos(370), child and adult, a camera turn eachActorInfo), made 16 at a time beside LinkDm_*event flows)The menus add almost nothing because play already covers them.
What the wider route needs from the host. It's one opt-in change in
host/hos_main.c, unused unless the variable is set:HOS_OOT_TELEPORT_EXTRA=<file>: more destinations after the table's, which the bridge'steleportreaches by index. Each line is either<child banc> <child start> <adult banc> <adult start> [boot event],event <name>oractor <name>. An actor line makes up to 16 actors on a ring around Link through the actor manager and deletes them after a few seconds.HOS_OOT_TOUR_SKIP_EVENTS=1: in play only, while an event plays, takes each event's skip edge, so a song lesson doesn't wait for the ocarina.That is the "extras" follow-up this PR names. If you want it, I'll send it as its own PR for this script to use, or adapt it to whatever bridge command you'd rather have.
Things the run hit that this PR's route may hit too:
pipeline_states.py checkstops atCOMPUTE_MOST(1024).What I'll send: a contributed list (
contrib/gumminaomi-…), so it doesn't touchtour.bin.xz, withCOMPUTE_MOSTraised. It will include a main vs PR measurement of the first start, the memory the listed compute programs hold, and later starts, as CONTRIBUTING asks. That is WIP until the measurement is in.Review of the head commit
e182b7e84b79(full hashe182b7e84b7969de8ba955ab5d3184ca9d69bca4), read against its own baseb9cdc4326f3band against current main7ff5256e0006, which is 125 commits ahead of that base.I did not run the game (no GPU run on this side yet). What I did: read the script, the test, the docs and the code they depend on at both mains; ran the PR's test on the head and on the head merged onto main
7ff5256e0; broke the script and the game source on purpose, one thing at a time, and ran the test again; and ran the realscripts/pipeline_tour.pyend to end against a stand-in for the game (a small program that serves the bridge with the test's own fake and writes a pipeline list where I tell it to). Links to the PR's files are pinned to the head commit; links to main are pinned to7ff5256e0.The route logic, the chunking and the resume after a memory stop are careful, and the source checks that exist do break when the game's strings change (I tried renaming a refusal text, the
!cheatprefix and the teleport action number in the game source, and changing the five-send rule and thePERMANENTlist in the script: each fails the test). Two things stop it from merging as it is, both because main moved after the PR was written, and there are several things I would fix before the first real run.Blocking
1. Main's game writes the pipeline list and notes in
cache/now; the script still uses the old place.Main
76699aad3(#321, 14 November) movedlongshot_pipeline_states.bin,longshot_pipelines.binandlongshot_computes.bininto<game>/cache/(state_list_path,game_layout.c); only the shipped list stays beside the game. The script reads, counts, moves aside and copies the names at the game folder's root (STATE_FILESandLIST, used bylist_size,settle, the move aside and the put back).With the stand-in writing the list in
cache/as main's game does,runfinished with exit 0 and0 pipelines, 0 programsat every step, nostates.bin, the oldcache/list left in the game folder and appended to, and the notes not moved aside.settlethen waits exactly its 150 quiet frames at every stop, andtour --publishprints "not published (a run failed or made no list)" but returns 0. With the list beside the game (the PR's own base) the same stand-in gave 3 pipelines. A real run would show it in a minute; a short real run is the thing I would do next (see the end).Fix: use
cache/for the notes, the list andlongshot_computes.bin, the game folder for the shipped list, and fail loudly when the list is missing or empty after the first stop.2.
tests/test_pipeline_tour.pyfails once this is on main. It passes at the PR's base. Merged onto7ff5256e0(no text conflict) it fails twice:"game":{"event":%s,"loading":%s,"age":"%s"}inhost/control.c; #58 (1ea02558e) moved that intostatus_game. The fields are all still there.bad_request: unitsto asequencewithoutunits: "hos". Since #47 (8e8f04c85) the real client turns that script intostepsbefore sending (Bridge.request). The real bridge takessteps, so the script is fine and the fake is stale.So "Rebased on current
main" and "run_all.pyrun and passing" were true forb9cdc432and are not now. A rebase and these two edits fix it.Should fix
3. The test cannot fail for most of what the script does. Twenty-one breaks, one at a time: it fails for 7 (the
PERMANENTlist, the five-send rule, the refusal texts, the action number, the!cheatprefix,--settings). It still passes with: a wrong list file name (the test makes its own list undertour.LIST), the game never asked toquit, the memory guard never stopping a running game (only the textraise MemoryStop(why)is searched, line 589), the soft limit never firing, a game that ended never noticed, atimeoutreply treated as final, the age switch waiting formapinstead ofloading_end, the bridge losingwait loading_end, or losing thesentandmapfields of its replies. The bridge half of the test is the test's own fake, so the description's "the bridge commands and status fields againsthost/control.c" holds for command names and one status string only. Six of the 14BASE_INIkeys are in a hard-coded set and would pass if the reader dropped them. The fake also invents a renderer string with a driver in it (point 5). A stand-in game drivingrunonce, as I did, would catch most of these.4. A game that exits during a request ends the run with a traceback and no report.
Game.askletslongshot_ctl.Failedthrough ("the game closed the connection");runcatches only the script's ownFailed, a different class. With the stand-in ending the process during the second teleport: a traceback, exit 1,states.binkept, but noreport.txtorreport.json(they are written after thefinally), and intourpart_runsreturns, so the part's remaining games never run, although a memory stop already resumes in a fresh game. A game that dies between requests is counted as three missed stops instead. A hung game (device lost, no exit) costs, by my reading ofGame.frames, about 5 minutes a stop;status.stalledis not used. Fix: catch the client'sFailedinask, write the report in thefinally, and resume from the stop where the game ended, with the "twice at the same stop: left out" rule.5.
tour --publishwould write a local path intotour.md, and the GPU line has no driver.publishkeeps everyHOS_OUTPUT*,HOS_FPS*andRX_VK*variable of the run, andrunsetsRX_VK_SHOTSto the run folder's absolute path. The note I got from the stand-in hasRX_VK_SHOTS=with the absolute path of the run folder'sshotsin its Settings line, plus the screenshot variables. That file is committed.status.rendereris<device name> (Vulkan, native)(describe), with no driver.publishuses it whenever it is not empty, and its fallback regex does not match main's device log line (driver %#x). The currenttour.mdsays "driver 620.70.06 (Linux)" and the README asks for it. The test's fake saysVulkan: Fake GPU, driver 1, which hid this.Fix: pass only the settings that matter (output size, the fps variables, the ini), and give
tour--gpuand--buildoptions that win, or read the driver from the log line as main prints it.6. The pause menu is driven with ZL and ZR; this repo says L and R turn its pages.
pause_pick,UNDERWATER_GEAR,BOOTS_OFF,PAUSE_TOURuse ZL and ZR.host/oot_menu.c: "0 Collection, 1 Equipment, 2 Items, 3 System; L and R step through them", and the mouse tabs send0:L/120and0:R/120(558-575); the README names "the L and R signs". GummiNaomi's run says the same (PLUS opens the items page, R and L step to each end) and adds that the game ignores menu buttons for about 6 seconds after an arrival; the script presses PLUS 3.7 seconds after the arrival event ends (LOOK). If ZL and ZR do not turn pages, the item pick at ten stops per age and the iron boots and Zora tunic do not happen, and nothing in the script notices (a ZR press in the world uses the item button, anAmay start a talk).Question: what did #2048's runs show, a picture of the Items page after the pick? If it is L and R, the three scripts need changing, the 6 seconds need respecting, and a bridge
shotor the state should confirm that the menu opened.7. The forced settings can be undone at start, and the environment is not pinned.
BASE_INIhas noquality_presetand nohandheld_profile. With an options file present andquality_preset=6(Auto),menu_auto_for_gpurunsapply_autowhen the saved fingerprint differs, and the fingerprint includes the screen size (auto_settings.h); a headless Linux run has no screen.apply_autothen sets the upscaler, render resolution, sharpness, pacing and Game speed and releases the environment overrides, includingHOS_REALTIME_STEP=0. A copy of a game folder that was ever played in a window has exactly that file.handheld_profile=1makes bothoutput_resandHOS_OUTPUT_RESbe ignored (nn_applet.c). The game also reads the copy'slongshot_env.txtand the caller's environment; its own comment saysHOS_ENV_FILEis for "a scripted or A/B test run" (hos_main.c). I read this, I did not run it. Fix: putquality_preset(Custom) andhandheld_profile=0inBASE_INI, setHOS_ENV_FILEto an empty file, and copy the game's own settings lines intoreport.txt.8. "None of these change a pipeline's key" is true for what a record holds, not for which records are made. A record has code hashes, texture answers, mesh flag, topology, colour count, samples, formats and draw state: no size, rate, filter or upscaler (
StateRec2), and frame rate, V-Sync, frame generation, sharpness and the window filter are present-only. But the game runs a different upscale compute network for each render share (vk_temporal.h) and compute programs are recorded. The tour at 50% with the game's own upscaler records the 50% network. Auto sets 100% for Balanced, High and Ultra and 75% for Low (kTierResolution), so most players on the game's own upscaler run a network the tour never records. (DLSS and FSR replace those dispatches, sodlss=0is the right base.) Native HDR changes formats and cannot be reached headless; the docstring's--ini hdr=1(line 32) is the host-only mode. Question: should the tour also run at 100% and 75% (--ini resolution=1and2), or say which tier it covers?valuewould show what each pass adds.9. The tour's list also takes the copy's own earlier compute programs.
state_open_lockedadds a compute record for every key inlongshot_computes.bin, "what this install's own play dispatched". That file is not inSTATE_FILES(line 63); at the PR's base it is beside the game and equally kept. So "only what this run meets counts" and the README's "a function of the build and the tour's route" do not hold for compute programs; they also count againstCOMPUTE_MOST. Move it aside too, or tour from a folder with nocache/.10. Unstick presses start during the host's 8-second lockout, which is not an event. In
map_change, after 240 frames of refusals every round presses A, B, stick+A and so on. The host refuses a teleport asked less than 8 seconds of wall clock after the last one, with no event playing (hos_main.c). 240 frames is 4 seconds at 60 fps and under 1 second at 300 fps, so A and B go out in the first half of a lockout, earlier the faster the run. Against the test's fake: 3 refusals gave no press, 10 refusals gave0:A/100and0:B/100. The script's own comment says A next to someone starts a conversation. Whether real stops come within 8 seconds of each other is open. Fix: recognise "one every 8 s" and just wait on the wall clock; keep unstick for an event that lasts.11. Items. Question. The unlock the script sends is a toggle: for an item already held it takes all of it away (
hos_main.c, "unlock / relock"). The route sends the whole set again after the age switch. Do the items the child pass gave survive the switch, and does the second pass then remove them? Also!cheatwrites one slot (nn_hid.c); a later line replaces one the game has not taken yet, and it takes one per Link update (not during a load or the pause menu). The script's 70 ms gap (cheat) has no margin for a hitch, and a tour with no pipelines prebuilt is full of them. The game logscheats: inventory <name> <delta> (had N, now M ...)for each edit that lands; waiting for that line before the next one (the bridge'ssubscribetakes a log filter) would answer both questions and make "every item" true. This one I could not settle without the game.12. A wedged event manager (GummiNaomi's data) is retried for minutes and ends the part. Questions. She sees the event manager refuse every teleport until a restart after the Haunted Wasteland, the Colossus' Spirit Temple entrance and the collapsing Ganon's Castle. The refusal then is "an event is playing" (
hos_main.c), whichPERMANENTtreats as passing: 7200 frames of retries with unstick presses per stop, three misses, the run ends, and (point 4) so does the part. The PR describes it as "sent, the map does not change" and a five-send rule that the default route never reaches; she sees refusals. The route puts the Haunted Wasteland last in its age, which protects the defaulttourparts, butrun --part all(the default ofrun) puts the age switch right after it (the dry run showsvisit Haunted Wasteland (child), thenage). Which destinations in the table wedge it? "The collapsing Ganon's Castle" could be Hyrule Castle as the adult (index 4, bancGanonCastle), which is the 4th of 15 stops ofadult-overworldwith 11 after it, or Ganon's Castle (index 30), the last stop of the dungeons part. Either way, a refusal that lasts more than some seconds could be taken as a wedge: stop the game and go on from that stop in a fresh one, as the memory path does.Smaller
Item_Hookshotbecoming a secondItem_Longshot(Reviewer notes) was fixed on main by #117 (3cbf12e8f, 13 November): rows 37 and 38 are Hookshot and Longshot again. The test still checksItem_Longshotonly (line 96).nn_vi.c);HOS_FPS_STEP=0is what gives a 60th of a second a frame, and that is a code comment, not something a test or a run shows.vsync=0is Auto (0 Auto, 1 On, 2 Off); harmless headless. "The game's own upscaler" forupscaling=0: that row is the window scaling filter; the Upscaler isdlss.Baseline:line and no line on why main was not the comparison (CONTRIBUTING, Measurements). The estimates are marked as estimates, and I could not check them; leavingMeasuredunticked is right.set_inirestores only an options file that existed; with none, the forced one stays in the game folder (the docs say "restored after").cp -a --reflink=alwaysfails with a traceback on a file system without reflinks; say so in--helpor use--reflink=auto.--publishpublishes it; the misses are only inreport.txt.touralso replacestour.bin.xzwhole with no count of the pipelines of the current one (11,227) that the new run lacks, and does not runnx2/pipeline_states.py check(GummiNaomi: her tour reaches 2,199 compute programs with the shipped ones andcheckstops at 1024).want = lambda st: True(line 888);wait mapis met at once when the map is already that one, so the arrival is not confirmed, only 300 frames waited.tour.mddo not say so, andfloor_gbcounts the whole machine, so another program can drop a stop.LEFT_OUTsays "Lost Woods (child): 20 pipelines no other stop met" and the next comment says under 10 is not worth a stop (lines 153-167). Which is it?featis "something new for players or modders" and makes the next minor release. This adds a maintainer script and docs and ships no list. Ischore(scripts)meant?GummiNaomi's data against the route
The route is 47 stops (14 of the 61 destination and age pairs are left out), no actors and no cutscenes; her wider run finds 21,848 records (+52%) more than the shipped list, of which maps at every start position are 9,390, actors 2,798, cutscenes 9,476, menus 94. Her "menus add almost nothing" fits the PR's
PAUSE_PAGES = False. The wider route is the PR's own follow-up, and her host change (HOS_OOT_TELEPORT_EXTRA, by index) would fit the bridge'steleport; that is for the maintainers. For this PR the useful parts of her data are points 6 (the menu buttons and the 6 seconds) and 12 (the wedge), and theCOMPUTE_MOSTnote under Smaller. The tour list is replaced whole, so everything the old tour met and this route does not is lost; that is worth a count in the report.Short runs that would settle what is open (not done: no game run on my side)
One game, windowless, a copy of a game folder with its own
cache/:run --part child-overworld --stops 1 --skip-opening: doescache/longshot_pipeline_states.bingrow, what does the report say, and arecache/longshot_computes.binrecords in the list (points 1 and 9).HOS_FPS_LOG=1: thevi: game clock ... (speed S) at F fpsline should give S about F/60.ACT_AT(Kokiri Forest) with a bridgeshotafter the pause presses, once with ZL/ZR and once with L/R; and a copy whose options file hasquality_preset=6and theauto_forof a windowed run, to seevk: Auto (this PC): the hardware changed(points 6 and 7).--part adult-overworld --stops 1, thengrep 'cheats: inventory' log.txt: how many of the 56 landed, and anynow 0after the age switch (point 11).--part adult-overworldthrough Hyrule Castle (adult) and one stop after it (point 12).e182b7e84bc23b44f39fRebased onto main
on run and tour (RX_VK_PGO_FILE set, .reset once the game has control of Link, .dump at each game's end to /.profraw, refused or failed when the renderer does not count), and merge-profile (llvm-profdata merge into work/longshot_vk.profdata; --publish writes assets/profiles/longshot_vk.profdata.xz as xz -9e and the README note). The description follows main's new template. Not run: neither tests/run_all.py nor the tour on this head; it stays WIP until the local run.5d3ecc78(no conflicts) and added the renderer's play-profile recording. New headc23b44f3. The rebase follow-ups: the lists are in the game folder's cache/ now, so the tour puts cache/'s and the loose ones aside and reads cache/longshot_pipeline_states.bin; status.game comes from the sampled state; the client sends units:hos as frame steps; the source checks and the fake bridge follow main's text, and the Hookshot item is checked again. New: --vk-profile3917b2777784421c993eFaster tour, as asked on 2026-11-14 (
84421c99, rebased onto main82886e89with no conflicts). The sword, shield and an item were used at 16 stops. They're now used once per age (ITEMS_ONCE: Kokiri Forest as the child, Hyrule Field as the adult) and at the stops with their own passes (ITEMS_PASSES: Zora's Domain, the Water Temple, the Shadow Temple, Death Mountain Crater, the Fire Temple), 10 uses in all, each with its own pause-menu pick across the whole route. The per-stop settle already counted frames; it now waits 60 quiet frames instead of 150 (600 at most, as before), set with --settle-quiet and --settle-max on run and tour and shown in report.txt. The inventory's !cheat lines are now 32 ms plus three presents apart instead of 70 ms of wall time. New: compare and tour --compare-to , which count the pipelines lost and gained. Estimated from the code: ~8,000 game frames and ~25 s of cheat gaps saved, ~1-2 min in all. Not run yet: the plan is a tour atc23b44f3and one at this head on the lab, then compare (expected: 0 lost). Tests were updated and not run on this PC. Still WIP.84421c993ed41a45bd18Self-review and fixes (
d41a45bd, rebased onto mainfe5c60d0with no conflicts). A code review of the whole PR found 20 points; this head fixes the confirmed and plausible ones. A stopped tour now gives its run two minutes to end its game and put the game folder back (subprocess.run killed it at once and left the game running). The folder is changed only inside the try that restores it, and each file goes back on its own, across drives too. A folder with no options file gets none back. bin/longshot is started in a builder's folder (not the launcher). longshot_computes.bin is put aside, and the shell's and longshot_env.txt's switches are kept out. tour.md names no local path and uses the runs' --ini. The 8 s teleport refusal is waited out, not pressed at. Checks for each are in tests/test_pipeline_tour.py. The description now links issues 14 and 505 and has the Measurements table the template asks for. Not run: neither the checks nor the tour; still WIP until the lab comparison.Description only: the Measurements' How to reproduce line, and Flags and settings names the existing switches the script sets for its games.
WIP: feat(scripts): the pipeline tour, driven through the control bridgeto feat(scripts): the pipeline tour, driven through the control bridgemooki referenced this pull request2026-11-15 00:35:08 +00:00
mooki referenced this pull request2026-11-15 00:35:09 +00:00
mooki referenced this pull request2026-11-15 00:35:10 +00:00
mooki referenced this pull request2026-11-15 00:35:11 +00:00
mooki referenced this pull request2026-11-15 00:35:12 +00:00
mooki referenced this pull request2026-11-15 00:35:13 +00:00
mooki referenced this pull request2026-11-15 00:35:14 +00:00
mooki referenced this pull request2026-11-15 00:35:15 +00:00
mooki referenced this pull request2026-11-15 00:35:16 +00:00
mooki referenced this pull request2026-11-15 00:35:17 +00:00
mooki referenced this pull request2026-11-15 00:35:18 +00:00
mooki referenced this pull request2026-11-15 00:35:20 +00:00
mooki referenced this pull request2026-11-15 00:35:21 +00:00
mooki referenced this pull request2026-11-15 00:35:23 +00:00
mooki referenced this pull request2026-11-15 00:35:23 +00:00