WIP: feat(runtime): one list of the game's switches, host/switches.def, kept in step with the reads #62

Draft
dyst wants to merge 2 commits from dyst/Longshot:feat/switch-list into main
First-time contributor

Why

The game reads 935 switches and nothing lists them: a new one goes unnoticed, a removed one stays in the docs, and longshot_env.txt's safety rules keep their own hand-written table of the switches a shared file must not set. This adds one list that a check keeps in step with the code.

What changed

  • host: host/switches.def (new): one X-macro line per switch that host/, gpu/vk, gpu/render and gpu/compat read, SWITCH(NAME, TYPE, SCOPE, "area", "what").
    • TYPE: FLAG, INT, FLOAT, TEXT, PATH, or ANY where the reads don't make it certain yet.
    • SCOPE: GAME (may be set in longshot_env.txt), LOCAL (from the environment only: it names a file or presses inputs on a timer), or SYSTEM (the OS's or a library's variable).
    • area: a file that reads it. what: a one-line description; the docs' tables gave 91, and the rest are "" until someone who knows the switch writes one (auto-drafting them from nearby comments was tried, and was wrong too often to keep).
  • host: env_line_allowed in host/hos_main.c builds its table from the list (listed, through SWITCH_LOCAL_GAME/LOCAL/SYSTEM) and refuses its LOCAL switches, in place of its own by_name table and the two names it checked apart (RX_VK_FRAME_LOG, HOS_PRESENT_TRACE). Otherwise unchanged: the names it refused before are LOCAL; a _FILE or _DIR name not listed yet is still refused; RX_VK_DLSS_DIR, paths in values and the system's names behave as before.
  • scripts: scripts/switch_list.py (new, standard library): reads, listed and compare list what's read and not listed, and what's listed and read nowhere; --update (update, certain_type, line_for) writes the missing lines (the type where the reads make it certain), drops the dead ones, and moves an area when that file no longer reads the switch. A switch is read through getenv, env_on, env_int, env_float, menu_env_on or env_override with its name written out, or named in a table as a string literal of its own.
  • build: nx2/build.py counts host/*.def among the host's dependencies (host_deps), so an edit to the list compiles hos_main.c again. Host objects compile once after this change; the lifted game does not.
  • tests: tests/test_switch_list.py (new); tests/test_env_file.py's fixture compiles env_line_allowed with host/ on the include path.

The cost for everyone: a new switch needs its line (python -I scripts/switch_list.py --update, then the description), or test_switch_list fails and names it.

Flags and settings

No switch is added or changed. scripts/check_pr_description.py finds the nine names below because host/switches.def names them in backquotes in other switches' descriptions; each is an existing switch with its own line in the list.

Flag New or changed What it does Default now Goal: on by default? Helps on Doesn't help on Untested on Untested, but should help on
HOS_PROFILE Existing; listed (GAME) The busiest threads in the log every interval; starts the samplers unchanged unchanged (this PR only lists it) - - - -
RX_VK_DUMP_DIR Existing; listed (LOCAL) The folder RX_VK_DUMP writes to unchanged unchanged (this PR only lists it) - - - -
RX_VK_HANG_SEC Existing; listed (GAME) The seconds without a frame before RX_VK_HANG_DUMP writes unchanged unchanged (this PR only lists it) - - - -
RX_VK_PRESENTS_AHEAD Existing; listed (GAME) How many presents the game may hand over ahead of the replay unchanged unchanged (this PR only lists it) - - - -
RX_VK_PRESENT_AHEAD Existing; listed (GAME) Frame generation's copies into the window submitted each at its own time unchanged unchanged (this PR only lists it) - - - -
RX_VK_SHOT_EVERY Existing; listed (GAME) How many presents between RX_VK_SHOTS' PNGs unchanged unchanged (this PR only lists it) - - - -
RX_VK_SHOT_W Existing; listed (GAME) RX_VK_SHOTS' width unchanged unchanged (this PR only lists it) - - - -
RX_VK_SHOT_H Existing; listed (GAME) RX_VK_SHOTS' height unchanged unchanged (this PR only lists it) - - - -
RX_VK_TRANSLATE_WAIT_MS Existing; listed (GAME) Set: a draw whose targets hold an earlier frame waits for shader translation at most that long a frame in all, then is left out unchanged unchanged (this PR only lists it) - - - -
HOS_GONE, HOS_KEPT, HOS_MOVED, HOS_NEW_LEVEL, RX_NEW_DUMP_DIR, RX_VK_FROM_A_TABLE, RX_NOT_LISTED_YET_DIR, RX_NOT_LISTED_YET_FILE, RX_VK_SOMETHING New, test only Test only: made-up names in tests/test_switch_list.py's fixtures; the game reads none of them. The test's cases also name existing switches (RX_VK_NOOPT, RX_VK_PACE, HOS_WINDOW_SIZE, HOS_SAMPLE_FILE, RX_VK_DLSS_DIR, HOS_ENV_FILE) - - - - - -
scripts/switch_list.py [--update] New command The switches read and not listed, and the listed ones read nowhere; --update fixes the list - no (a developer tool) - - Linux -

Measurements

Run on a Longshot build of this branch (this PR was made on Longshot; there is no GitHub original). There is no main-side measurement: the game run is a behaviour check of longshot_env.txt's allow-list, with nothing to time.

main: 82de567a (2026-11-12) (this branch's base; nothing was run on it in the game)
this PR: feat/switch-list @ 5255a001
Hardware: RTX 3080 (NVIDIA 621.14), Ryzen 9 3900X, 64 GB, Windows 11
Scene: one launch, from a save into Kokiri Forest
Method: one launch with the longshot_env.txt below; what the log names as left out, and whether the files were written
How to reproduce: put these five lines in longshot_env.txt beside the game, start it, and read longshot_log.txt

HOS_AUDIO_DUMP=test.wav
HOS_INPUT_SCRIPT=0:A
RX_VK_FRAME_LOG=frames.csv
RX_E1_NOT_LISTED_FILE=x.txt
RX_VK_PACE=1
Set in longshot_env.txt What happened (this PR, Longshot build)
HOS_AUDIO_DUMP=test.wav left out (named in the log); test.wav not written
HOS_INPUT_SCRIPT=0:A left out (named in the log)
RX_VK_FRAME_LOG=frames.csv left out (named in the log); frames.csv not written
RX_E1_NOT_LISTED_FILE=x.txt left out (named in the log): a _FILE name not listed
RX_VK_PACE=1 taken (not named as left out)

The log names exactly the first four as left out. The game reached play from a save, Kokiri Forest at 23-28 fps, with no errors in the log.

Testing

Tested: the runs under Measurements (on this branch) and the game-free checks this description names. Checked but not measured: the game on Windows (above), a behaviour check.

Not tested: Linux (the same C; the check runs there).

Needs other testers:

  • Linux: longshot_env.txt setting a LOCAL switch (HOS_AUDIO_DUMP), expecting it logged as left out and no file written (request to open)

Screenshots

Nothing on screen.

Risk and rollback

  • A switch marked LOCAL by mistake is refused from longshot_env.txt, so a shared file loses that setting; one marked GAME by mistake that writes a file would be let through. The names refused before are held LOCAL by test_switch_list, and an unlisted _FILE or _DIR name is still refused.
  • Every new switch now needs its line in host/switches.def, or test_switch_list fails.
  • Host objects compile once after this change (host/*.def in host_deps).
  • No switch: reverting the commit is enough.

Reviewer notes

  • Made on Longshot directly (no GitHub original). The dev-loop proposal's E1 (NotRexed/Hookshot#2636), in a lean first version; its plan was posted in the team room for objections before this was opened.
  • Checks run (on Longshot, not the whole run_all.py): every check that reads the changed files (56), through run_all.py with the Builder's tools, Windows: 51 pass. The 5 that fail (nx2/test_dll.py, test_driver_cache_folder, test_fpx_sticky, test_linux_update, test_udmabuf_warning) fail the same way on Longshot's main on this PC.
  • tests/test_switch_list.py: the list against the reads, both ways; its lines well formed and in order; each area a file that reads it; build.py counting it among the host's dependencies; env_line_allowed cut out, compiled with the list and run (every LOCAL switch refused, the names refused before still LOCAL, the backstop for unlisted _FILE/_DIR, and the earlier cases); --update on a copy (a missing line added, a dead one dropped, an area moved, the file's head kept). Two deliberate breaks fail it: a read switch removed from the list, and an old refused name made GAME.

Issues

None.

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Flags table filled in; README / docs/development.md switch reference updated.
  • Screenshots for a change that shows on screen.
  • A row in docs/tested-configurations.md for each measured run.
  • MKWVK_API_VERSION bumped (gpu/vk/vk_api.h's MkwVkHost or MkwVkRenderer changed).
  • The SPIR-V headers regenerated with glslang (a shader changed).
  • A game-free test added for the fix, failing on main and passing here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths.
  • Docs updated.

🤖 Generated with Claude Code

## Why The game reads 935 switches and nothing lists them: a new one goes unnoticed, a removed one stays in the docs, and `longshot_env.txt`'s safety rules keep their own hand-written table of the switches a shared file must not set. This adds one list that a check keeps in step with the code. ## What changed - host: `host/switches.def` (new): one X-macro line per switch that `host/`, `gpu/vk`, `gpu/render` and `gpu/compat` read, `SWITCH(NAME, TYPE, SCOPE, "area", "what")`. - TYPE: `FLAG`, `INT`, `FLOAT`, `TEXT`, `PATH`, or `ANY` where the reads don't make it certain yet. - SCOPE: `GAME` (may be set in `longshot_env.txt`), `LOCAL` (from the environment only: it names a file or presses inputs on a timer), or `SYSTEM` (the OS's or a library's variable). - area: a file that reads it. what: a one-line description; the docs' tables gave 91, and the rest are `""` until someone who knows the switch writes one (auto-drafting them from nearby comments was tried, and was wrong too often to keep). - host: `env_line_allowed` in `host/hos_main.c` builds its table from the list (`listed`, through `SWITCH_LOCAL_GAME`/`LOCAL`/`SYSTEM`) and refuses its `LOCAL` switches, in place of its own `by_name` table and the two names it checked apart (`RX_VK_FRAME_LOG`, `HOS_PRESENT_TRACE`). Otherwise unchanged: the names it refused before are `LOCAL`; a `_FILE` or `_DIR` name not listed yet is still refused; `RX_VK_DLSS_DIR`, paths in values and the system's names behave as before. - scripts: `scripts/switch_list.py` (new, standard library): `reads`, `listed` and `compare` list what's read and not listed, and what's listed and read nowhere; `--update` (`update`, `certain_type`, `line_for`) writes the missing lines (the type where the reads make it certain), drops the dead ones, and moves an area when that file no longer reads the switch. A switch is read through `getenv`, `env_on`, `env_int`, `env_float`, `menu_env_on` or `env_override` with its name written out, or named in a table as a string literal of its own. - build: `nx2/build.py` counts `host/*.def` among the host's dependencies (`host_deps`), so an edit to the list compiles `hos_main.c` again. Host objects compile once after this change; the lifted game does not. - tests: `tests/test_switch_list.py` (new); `tests/test_env_file.py`'s fixture compiles `env_line_allowed` with `host/` on the include path. The cost for everyone: a new switch needs its line (`python -I scripts/switch_list.py --update`, then the description), or `test_switch_list` fails and names it. ## Flags and settings No switch is added or changed. `scripts/check_pr_description.py` finds the nine names below because `host/switches.def` names them in backquotes in other switches' descriptions; each is an existing switch with its own line in the list. | Flag | New or changed | What it does | Default now | Goal: on by default? | Helps on | Doesn't help on | Untested on | Untested, but should help on | |---|---|---|---|---|---|---|---|---| | `HOS_PROFILE` | Existing; listed (`GAME`) | The busiest threads in the log every interval; starts the samplers | unchanged | unchanged (this PR only lists it) | - | - | - | - | | `RX_VK_DUMP_DIR` | Existing; listed (`LOCAL`) | The folder `RX_VK_DUMP` writes to | unchanged | unchanged (this PR only lists it) | - | - | - | - | | `RX_VK_HANG_SEC` | Existing; listed (`GAME`) | The seconds without a frame before `RX_VK_HANG_DUMP` writes | unchanged | unchanged (this PR only lists it) | - | - | - | - | | `RX_VK_PRESENTS_AHEAD` | Existing; listed (`GAME`) | How many presents the game may hand over ahead of the replay | unchanged | unchanged (this PR only lists it) | - | - | - | - | | `RX_VK_PRESENT_AHEAD` | Existing; listed (`GAME`) | Frame generation's copies into the window submitted each at its own time | unchanged | unchanged (this PR only lists it) | - | - | - | - | | `RX_VK_SHOT_EVERY` | Existing; listed (`GAME`) | How many presents between `RX_VK_SHOTS`' PNGs | unchanged | unchanged (this PR only lists it) | - | - | - | - | | `RX_VK_SHOT_W` | Existing; listed (`GAME`) | `RX_VK_SHOTS`' width | unchanged | unchanged (this PR only lists it) | - | - | - | - | | `RX_VK_SHOT_H` | Existing; listed (`GAME`) | `RX_VK_SHOTS`' height | unchanged | unchanged (this PR only lists it) | - | - | - | - | | `RX_VK_TRANSLATE_WAIT_MS` | Existing; listed (`GAME`) | Set: a draw whose targets hold an earlier frame waits for shader translation at most that long a frame in all, then is left out | unchanged | unchanged (this PR only lists it) | - | - | - | - | | `HOS_GONE`, `HOS_KEPT`, `HOS_MOVED`, `HOS_NEW_LEVEL`, `RX_NEW_DUMP_DIR`, `RX_VK_FROM_A_TABLE`, `RX_NOT_LISTED_YET_DIR`, `RX_NOT_LISTED_YET_FILE`, `RX_VK_SOMETHING` | New, test only | Test only: made-up names in `tests/test_switch_list.py`'s fixtures; the game reads none of them. The test's cases also name existing switches (`RX_VK_NOOPT`, `RX_VK_PACE`, `HOS_WINDOW_SIZE`, `HOS_SAMPLE_FILE`, `RX_VK_DLSS_DIR`, `HOS_ENV_FILE`) | - | - | - | - | - | - | | `scripts/switch_list.py [--update]` | New command | The switches read and not listed, and the listed ones read nowhere; `--update` fixes the list | - | no (a developer tool) | - | - | Linux | - | ## Measurements Run on a Longshot build of this branch (this PR was made on Longshot; there is no GitHub original). There is no main-side measurement: the game run is a behaviour check of `longshot_env.txt`'s allow-list, with nothing to time. main: 82de567a (2026-11-12) (this branch's base; nothing was run on it in the game) this PR: feat/switch-list @ 5255a001 Hardware: RTX 3080 (NVIDIA 621.14), Ryzen 9 3900X, 64 GB, Windows 11 Scene: one launch, from a save into Kokiri Forest Method: one launch with the `longshot_env.txt` below; what the log names as left out, and whether the files were written How to reproduce: put these five lines in `longshot_env.txt` beside the game, start it, and read `longshot_log.txt` ``` HOS_AUDIO_DUMP=test.wav HOS_INPUT_SCRIPT=0:A RX_VK_FRAME_LOG=frames.csv RX_E1_NOT_LISTED_FILE=x.txt RX_VK_PACE=1 ``` | Set in `longshot_env.txt` | What happened (this PR, Longshot build) | |---|---| | `HOS_AUDIO_DUMP=test.wav` | left out (named in the log); `test.wav` not written | | `HOS_INPUT_SCRIPT=0:A` | left out (named in the log) | | `RX_VK_FRAME_LOG=frames.csv` | left out (named in the log); `frames.csv` not written | | `RX_E1_NOT_LISTED_FILE=x.txt` | left out (named in the log): a `_FILE` name not listed | | `RX_VK_PACE=1` | taken (not named as left out) | The log names exactly the first four as left out. The game reached play from a save, Kokiri Forest at 23-28 fps, with no errors in the log. ## Testing **Tested:** the runs under Measurements (on this branch) and the game-free checks this description names. Checked but not measured: the game on Windows (above), a behaviour check. **Not tested:** Linux (the same C; the check runs there). **Needs other testers:** - Linux: longshot_env.txt setting a LOCAL switch (`HOS_AUDIO_DUMP`), expecting it logged as left out and no file written (request to open) ## Screenshots Nothing on screen. ## Risk and rollback - A switch marked `LOCAL` by mistake is refused from `longshot_env.txt`, so a shared file loses that setting; one marked `GAME` by mistake that writes a file would be let through. The names refused before are held `LOCAL` by `test_switch_list`, and an unlisted `_FILE` or `_DIR` name is still refused. - Every new switch now needs its line in `host/switches.def`, or `test_switch_list` fails. - Host objects compile once after this change (`host/*.def` in `host_deps`). - No switch: reverting the commit is enough. ## Reviewer notes - Made on Longshot directly (no GitHub original). The dev-loop proposal's E1 ([NotRexed/Hookshot#2636](https://github.com/NotRexed/Hookshot/pull/2636)), in a lean first version; its plan was posted in the team room for objections before this was opened. - Checks run (on Longshot, not the whole `run_all.py`): every check that reads the changed files (56), through `run_all.py` with the Builder's tools, Windows: 51 pass. The 5 that fail (`nx2/test_dll.py`, `test_driver_cache_folder`, `test_fpx_sticky`, `test_linux_update`, `test_udmabuf_warning`) fail the same way on Longshot's `main` on this PC. - `tests/test_switch_list.py`: the list against the reads, both ways; its lines well formed and in order; each area a file that reads it; `build.py` counting it among the host's dependencies; `env_line_allowed` cut out, compiled with the list and run (every `LOCAL` switch refused, the names refused before still `LOCAL`, the backstop for unlisted `_FILE`/`_DIR`, and the earlier cases); `--update` on a copy (a missing line added, a dead one dropped, an area moved, the file's head kept). Two deliberate breaks fail it: a read switch removed from the list, and an old refused name made `GAME`. ## Issues None. ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [ ] Rebased on current `main`. - [ ] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [ ] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [ ] Flags table filled in; README / docs/development.md switch reference updated. - [ ] Screenshots for a change that shows on screen. - [ ] A row in `docs/tested-configurations.md` for each measured run. - [ ] `MKWVK_API_VERSION` bumped (`gpu/vk/vk_api.h`'s `MkwVkHost` or `MkwVkRenderer` changed). - [ ] The SPIR-V headers regenerated with glslang (a shader changed). - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. - [ ] Docs updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The game reads 935 switches (host/, gpu/vk, gpu/render, gpu/compat) and nothing listed them: a new one could go
unnoticed, a dead one stay documented, and longshot_env.txt's allow-list kept its own hand table of the switches that
must not come from a shared file. host/switches.def lists each (type, scope, area, what; the docs' tables gave 91
descriptions, the rest say "" until someone does). tests/test_switch_list.py fails when a read switch is missing or a
listed one is read nowhere; scripts/switch_list.py --update writes the line. env_line_allowed refuses the LOCAL ones
from the list (the names it refused before stay LOCAL, and a _FILE or _DIR name not listed yet is refused as before);
build.py counts the list among the host's headers. The dev-loop proposal's E1 (NotRexed/Hookshot#2636).

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
dyst changed title from feat(runtime): one list of the game's switches, host/switches.def, kept in step with the reads to WIP: feat(runtime): one list of the game's switches, host/switches.def, kept in step with the reads 2026-11-13 01:14:03 +00:00
Author
First-time contributor

Back to WIP under #48's process: this was carried over before the new template. Its description will be brought to the template and measured on Longshot (main against this PR, at named commits) when it's wanted for review; ask here if you'd like it sooner.

Back to WIP under #48's process: this was carried over before the new template. Its description will be brought to the template and measured on Longshot (main against this PR, at named commits) when it's wanted for review; ask here if you'd like it sooner.
Author
First-time contributor

Description brought to Longshot's PR template (#48). Still WIP: it has a game run on this branch but no main-against-this-PR measurement yet, and it isn't rebased on current main.

Description brought to Longshot's PR template (#48). Still WIP: it has a game run on this branch but no main-against-this-PR measurement yet, and it isn't rebased on current main.
Author
First-time contributor

Description moved to the template's Testing section and whole checklist (#72): what was tested, what wasn't, and what needs other testers (their test requests to open while this is WIP).

Description moved to the template's Testing section and whole checklist (#72): what was tested, what wasn't, and what needs other testers (their test requests to open while this is WIP).
This pull request is marked as a work in progress.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'feat/switch-list:dyst-feat/switch-list'
git switch 'dyst-feat/switch-list'
Sign in to join this conversation.
No description provided.