feat(options): a Shadow resolution setting keeps thin shadows near Link smooth #773

Open
Jiro113 wants to merge 4 commits from Jiro113/Longshot:feat/sun-shadow-resolution into main
First-time contributor

Type: feat · Risk: low · Tested on: Windows 11, RTX 4090; Linux build and checks (Ubuntu 22.04) · Needs: an 8 GB card (Balanced preset), any maker

Why

The shadows the sun casts from thin things near Link, such as the drawbridge's chains, look like stair steps. The game draws the sun's shadow maps 1600 pixels a side, sized for the console's 720p and 1080p;
at a PC's resolutions each shadow-map pixel covers several screen pixels, so the edges of thin shadows break up. The game
already has larger sizes in its own table (2200 and 3072) that it never uses at the console's settings. This PR lets the
player choose them, and makes the presets for cards with 8 GB or more choose the largest.

Searched the tracker for shadow issues: none matches the stair-stepped shadows of thin things. Two other shadow problems
are open and this PR does not touch them: shadows on Link that break with the camera angle (#601) and blocky artifacts
around Link in Zora River's water on AMD/Linux (#603).

What changed

A Shadow resolution row in Graphics: Game's (1600), High (2200), Highest (3072). The lifter adds one hook where the
game rounds the size of the sun's shadow cascades; the host raises that size when the row asks for more, never lowers it,
and reads the row every frame, so a change applies at once. The Quality presets set it: Game's on Minimum and Low,
Highest on Balanced, High and Ultra.

  • lifter: nx2/oot_shadow.py, installed from nx2/lift.py in standalone builds. It checks the build id and the exact
    instruction words at the site (and w23, w8, #0xfffffff8 after MAIN+0x4729f0) and installs nothing when they differ.
  • host: host/oot_shadow.c, hos_oot_shadow(). Only the object whose vtable is the sun shadow's is changed, and only
    w23. Game's, or a value out of range, reads no game memory and leaves every register as it was.
  • renderer and menu: HOS_OPT_SHADOW_RES = 56 in gpu/vk/vk_api.h, answered in gpu/vk/vk_replay.cpp. In
    gpu/vk/vk_menu.cpp: the row, apply_preset()'s values, and the reading of an options file from before the row in
    menu_init() and menu_auto_for_gpu() (shadow_res_unsaved). The bug report counts the row among the defaults that
    depend on the PC (report_default_depends_on_pc()).
  • translations: the row, its help and its value hints in the menu's 13 languages, and the Quality preset's help and
    tier hints, which now name the shadows (gpu/vk/menu_i18n.h).
  • docs: the README's option table and its Shadow resolution paragraph; the shadow_resolution line in
    docs/development.md; the measured run in docs/tested/.
  • tests: tests/test_oot_shadow.py checks the install against the build id and each instruction word, and compiles the
    real host helper against an authored shadow object. The checks cover the sun's object only, raise-only, live changes,
    only x23 changing, the menu wiring, and each preset's value. The fixtures that cut menu_init()'s load block
    (test_menu_keys.py, test_upscaler_round_trip.py) declare the new flag. test_bug_report_defaults.py counts the
    row with the preset-set rows. test_menu_sections.py counts the new row.

Measurements

Verdict: High costs about 0.02 ms of GPU time a frame and Highest about 0.05 ms on an RTX 4090 (medians; 0.03 and 0.07 ms by the means). That is under 1 %
of the frame, and the frame rate did not move: the run is CPU-bound there. In video memory, by the maps' size, Highest
takes about 113 MB against the game's 31 MB.

Hardware: Windows 11 Home 10.0.26300, RTX 4090 24 GB, NVIDIA 621.24, Core i9-13900K, 64 GB RAM.
Scene: Hyrule Field at the castle's drawbridge as a child, standing still, camera untouched. Output 2560x1440 with
ultrawide on (presented 5120x1440 SDR); DLSS 2560x720 to 5120x1440, preset K; frame generation Off; V-Sync Auto
(mailbox); Quality preset Auto (this PC), on its Ultra tier (memory budget 5).
Method: an in-session A/B with RX_VK_PASS_TIMING=1. Shadow resolution was switched live through the control pipe in 3
rounds of Game's, High, Highest. Each setting kept 2 blocks of 600 presents, after leaving out the block that straddles
the switch, so there are 6 blocks for each. The table gives the median, with the mean and the standard deviation beside it.
Baseline: Game's is this PR's row at 0, which leaves the game's size as main does: the hook reads no game memory and
changes no register there (tests/test_oot_shadow.py). An in-session A/B on the same spot was steadier than separate
runs of main and this PR.
How to reproduce: build a pack of this branch over a base, stand at the drawbridge, set RX_VK_PASS_TIMING=1, and switch
the row (or longshot_ctl.py option set shadow_resolution <0|1|2>), reading the GPU pass timing blocks.

A frame, median of 6 blocks of 600 presents (mean, sd) Before: Game's 1600, this PR @ e257da53 After: High 2200, this PR @ e257da53 After: Highest 3072, this PR @ e257da53
GPU time, all passes 9.04 ms (9.03, 0.03) 9.06 ms (9.06, 0.01) 9.09 ms (9.10, 0.02)
GPU time, the main scene pass 3.567 ms (3.558, 0.022) 3.587 ms (3.587, 0.016) 3.620 ms (3.626, 0.016)
Render thread (CPU) 12.66 ms (12.65) 12.60 ms (12.61) 12.68 ms (12.70)
Sun shadow maps, video memory (by their size) about 31 MB about 58 MB about 113 MB
round 1 setting 0 [(9.04, 3.568, 0.06, 12.64), (9.04, 3.564, 0.06, 12.71)]
round 1 setting 1 [(9.06, 3.584, 0.06, 12.74), (9.05, 3.585, 0.06, 12.61)]
round 1 setting 2 [(9.1, 3.619, 0.06, 12.78), (9.09, 3.617, 0.06, 12.64)]
round 2 setting 0 [(9.05, 3.57, 0.06, 12.95), (8.97, 3.509, 0.06, 12.45)]
round 2 setting 1 [(9.03, 3.559, 0.06, 12.59), (9.08, 3.615, 0.06, 12.42)]
round 2 setting 2 [(9.15, 3.661, 0.06, 12.78), (9.1, 3.62, 0.06, 12.7)]
round 3 setting 0 [(9.03, 3.565, 0.06, 12.5), (9.04, 3.571, 0.06, 12.68)]
round 3 setting 1 [(9.06, 3.59, 0.06, 12.55), (9.06, 3.59, 0.06, 12.73)]
round 3 setting 2 [(9.09, 3.619, 0.06, 12.63), (9.09, 3.62, 0.06, 12.65)]
(each pair: GPU ms in all, main scene pass ms, cascade passes ms, render thread ms a frame)

Two earlier runs with the periodic GPU time line showed no difference in fps either: Hyrule Field 88.0, 89.4 and 89.7
fps, and the drawbridge 74.7, 74.6 and 74.1 fps for Game's, High and Highest.

e257da53 is this branch's second commit before the rebase onto aa95d35f (now 463c23ff). The commits after it change only
the menu, so the hook and what it draws are the same: the presets set the row, and an old options file is read.

Screenshots

The same spot, by day, switched live in one session: Game's, High, Highest, then Game's again, each shot 12 frames after
the switch. A 400x560 crop of the 5120x1440 frame, enlarged 2x without smoothing: the gate pillar's shadow on the
left-hand wall. Its edge's stair steps are coarse on Game's, finer on High, finest on Highest, and coarse again back on
Game's. The whole view shifts by a few pixels between shots, so compare the edges rather than where the shadow lies.

Before: Game's 1600 / After: High 2200 / After: Highest 3072 / Game's 1600 again, this PR @ 608cd6ab

A second run with RX_VK_PASS_TIMING=1 checked the sizes at each switch: 1600x1600 shadow passes after Game's, 2200x2200
after High, and the 3072x3072 map used every frame on Highest only
(the comment). The chains' shadow on the
planks, and the whole view, are in the first screenshots' comment.

Flags and settings

One menu row and its options-file key; no switch.

Flag What it is Before this PR After this PR Effect Helps / doesn't help / untested Recommendation
Shadow resolution (shadow_resolution in longshot_options.ini, 0 to 2) The size of the sun's shadow maps: Game's 1600, High 2200, Highest 3072 none: always the game's 1600 Highest (2) on Balanced, High and Ultra; Game's (0) on Minimum and Low. A file from before the row follows the preset its settings came from Smooth shadows of thin things near Link; about 82 MB more video memory and about 0.05 ms more GPU time a frame at Highest (RTX 4090) Helps: the drawbridge chains' shadows near Link on an RTX 4090. Untested on 4 to 8 GB cards and other makers Highest on 8 GB cards and above, as the presets set it; Game's on Minimum and Low, where video memory is short

Testing

Tested:

  • Rebased on main 69ff5beb with no change to the commits (git range-diff): 608cd6ab, named below where it was tested,
    is now ab099100. python -I -B tests/run_all.py on Windows 11 (llvm-mingw) at ab099100: 501 passed, 20 failed, 74
    skipped, none in a file this PR touches: the 18 below, and fpx_sticky and spawn_emit, which can't find the C++
    standard headers to build a64emit on this PC ('algorithm' file not found).
  • python -I -B tests/run_all.py on Windows 11 (llvm-mingw), at df342aab (608cd6ab, the head, only corrects the
    settings in the docs/tested/ file; test_tested_configurations and test_oot_shadow ran again there): 539 passed,
    18 failed, 37 skipped. All 18
    also fail on main (a00c485c) on this PC; they are Windows environment failures in files this PR doesn't touch:
    appimage_player_build, bug_report, cpm_discovery, cpp_preflight, crash_dialog, env_on, lift_cleanup_error,
    linux_preflight, make_game_cache, make_game_prepare, missing_nup, oot_state_tap, options_budget_once, readback_late,
    tested_configurations (CRLF in one pipe check), vk_pgo, walk_key, work_lock (the venv's Python launcher keeps the lock
    after a kill; it passes with the base interpreter). tests/test_oot_shadow.py, test_preset_exempt, test_bug_report_defaults,
    test_menu_keys, test_upscaler_round_trip, test_menu_i18n, test_menu_sections and test_tested_configurations's
    format checks pass.
  • On Linux (Ubuntu 22.04 in WSL 2, at 608cd6ab): tests/run_all.py with the system Python 3.10: 487 passed, 22 failed,
    85 skipped; main (aa95d35f) in the same place: 484 passed, 24 failed. Every failure on the branch also fails on main
    there, and none is in a file this PR touches; several are the same as on Windows (env_on, options_budget_once). test_oot_shadow passes with gcc 11 and with tools.lock's
    clang 23.
  • Linux build: the renderer (RX_VK_ONLY=1 nx2/build.py --standalone, tools.lock's clang and sysroot) builds and links
    longshot_vk.so with the new option. host/oot_shadow.c compiles with the host's Linux flags and
    -Wall -Wextra -Werror, with clang 23 and gcc 11, against the generated runtime headers. A whole Linux player build
    needs the game, so it was not run.
  • The presets and the options file in the game (Windows, RTX 4090, at 608cd6ab), one start each with its own user
    folder, reading the row through the control pipe once the game presents:
    • no options file, Auto on this PC (tier 5): Highest; the same with RX_VK_AUTO_PRESET_VRAM_MB=4096 (tier 1): Game's;
    • a file from before the row: on Low, Game's; on Balanced, Highest; on Custom that came from Minimum, Game's;
    • a file from before the row and memory_budget, on Auto for this PC: Highest; the same file with the card taken
      as 6 GB (RX_VK_AUTO_PRESET_VRAM_MB=6144, "the hardware changed", tier 2): Game's;
    • a file that has the row (Game's) on Balanced keeps Game's;
    • the Quality preset row set through the control pipe (the menu row's own path) to Minimum, Low, Balanced, High, Ultra, Auto, Minimum, Custom: Game's, Game's,
      Highest, Highest, Highest, Highest, Game's, Game's (Custom keeps what was there). Balanced, then Shadow resolution
      set to High by hand: High, and the preset turns to Custom.
    • In all 9 starts DLSS's NGX started, and the logs have no device-lost or validation lines.
  • In the game on Windows 11 with an RTX 4090 (NVIDIA 621.24), at e257da53, looking at the drawbridge's chains and the
    ivy near Link:
    • High and Highest smooth the chains' shadows;
    • Game's gives back the game's shadows at once;
    • switching back and forth while playing changes the shadows at once;
    • the pass timing lists the larger cascade passes on High and Highest.
      Measured as above.

Not tested:

  • Whether the shadows of thin things still shimmer while Link or the camera moves: they were looked at standing still,
    and while switching the row.
  • Other thin things (fences, the ivy up close) than the drawbridge's chains.
  • A run in the game on Linux, AMD, Intel or a Steam Deck. The hook runs on the CPU and the renderer draws whatever size the game asks for, so
    nothing in it depends on the maker, but the video memory and the GPU time were measured on an RTX 4090 only.

Must be tested:

  • An 8 GB card on the Balanced preset, any maker, Windows or Linux (by hand, test request T-TUR5): stand still at Hyrule Field's drawbridge as a child and
    switch Shadow resolution between Game's and Highest. Expect smooth chain shadows on Highest, no new stutter, and no
    texture-budget warning in longshot_log.txt; report the GPU time line for each.
  • One AMD or Intel GPU, Windows or Linux (by hand, test request T-WSWW): the same steps, expecting the same shadows
    as on NVIDIA, and the same row values after picking each Quality preset.

Risks

What could break:

  • A changed game build: the hook checks the build id and the instruction words and installs nothing when they differ,
    so the row then does nothing.
  • Cards with 8 GB on Balanced: Highest takes about 82 MB more video memory than the game's size.
  • Switching down during play does not give all of it back at once: in the size-check run, the 3072x3072 map in use left
    the renderer's list of large textures after the switch to Game's, but a second one (54 MB), idle since before the
    switch, stayed listed through Game's and High for over 6000 presents. Starting the game on Game's never makes them.

Turning it off: set Shadow resolution to Game's, which makes the hook a no-op.

Compatibility:

  • longshot_options.ini gains shadow_resolution. A file from before it takes the value of the preset it came from:
    Game's on Minimum and Low, Highest otherwise, and Auto's tier once the GPU is known. A player who had picked Balanced
    or above sees sharper shadows after updating.
  • The game is translated again at the next build, as for any lifter change, since the lifter adds the hook.
  • No change to saves, the shader or pipeline caches, or MKWVK_API_VERSION: MkwVkHost and MkwVkRenderer are
    unchanged; only a new option id is added.

Checklist

Everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description) of the status line's type.
  • Why gives the reason plainly and links the issues that fit (the tracker searched).
  • Measurements has a verdict, or one line saying why there is nothing to measure.
  • Testing names what was tested, what wasn't, and what must still be tested, where and how.
  • Rebased on current main.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named).
  • 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.

When it applies

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Numbers from the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value.
  • Screenshots of the same spot, captioned like the Measurements columns, for a change that shows on screen.
  • A file in docs/tested/ for each new measured run (format in docs/tested-configurations.md).
  • 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

**Type:** feat · **Risk:** low · **Tested on:** Windows 11, RTX 4090; Linux build and checks (Ubuntu 22.04) · **Needs:** an 8 GB card (Balanced preset), any maker ## Why The shadows the sun casts from thin things near Link, such as the drawbridge's chains, look like stair steps. The game draws the sun's shadow maps 1600 pixels a side, sized for the console's 720p and 1080p; at a PC's resolutions each shadow-map pixel covers several screen pixels, so the edges of thin shadows break up. The game already has larger sizes in its own table (2200 and 3072) that it never uses at the console's settings. This PR lets the player choose them, and makes the presets for cards with 8 GB or more choose the largest. Searched the tracker for shadow issues: none matches the stair-stepped shadows of thin things. Two other shadow problems are open and this PR does not touch them: shadows on Link that break with the camera angle (#601) and blocky artifacts around Link in Zora River's water on AMD/Linux (#603). ## What changed A **Shadow resolution** row in Graphics: Game's (1600), High (2200), Highest (3072). The lifter adds one hook where the game rounds the size of the sun's shadow cascades; the host raises that size when the row asks for more, never lowers it, and reads the row every frame, so a change applies at once. The Quality presets set it: Game's on Minimum and Low, Highest on Balanced, High and Ultra. - lifter: `nx2/oot_shadow.py`, installed from `nx2/lift.py` in standalone builds. It checks the build id and the exact instruction words at the site (`and w23, w8, #0xfffffff8` after MAIN+0x4729f0) and installs nothing when they differ. - host: `host/oot_shadow.c`, `hos_oot_shadow()`. Only the object whose vtable is the sun shadow's is changed, and only w23. Game's, or a value out of range, reads no game memory and leaves every register as it was. - renderer and menu: `HOS_OPT_SHADOW_RES = 56` in `gpu/vk/vk_api.h`, answered in `gpu/vk/vk_replay.cpp`. In `gpu/vk/vk_menu.cpp`: the row, `apply_preset()`'s values, and the reading of an options file from before the row in `menu_init()` and `menu_auto_for_gpu()` (`shadow_res_unsaved`). The bug report counts the row among the defaults that depend on the PC (`report_default_depends_on_pc()`). - translations: the row, its help and its value hints in the menu's 13 languages, and the Quality preset's help and tier hints, which now name the shadows (`gpu/vk/menu_i18n.h`). - docs: the README's option table and its Shadow resolution paragraph; the `shadow_resolution` line in `docs/development.md`; the measured run in `docs/tested/`. - tests: `tests/test_oot_shadow.py` checks the install against the build id and each instruction word, and compiles the real host helper against an authored shadow object. The checks cover the sun's object only, raise-only, live changes, only x23 changing, the menu wiring, and each preset's value. The fixtures that cut `menu_init()`'s load block (`test_menu_keys.py`, `test_upscaler_round_trip.py`) declare the new flag. `test_bug_report_defaults.py` counts the row with the preset-set rows. `test_menu_sections.py` counts the new row. ## Measurements **Verdict:** High costs about 0.02 ms of GPU time a frame and Highest about 0.05 ms on an RTX 4090 (medians; 0.03 and 0.07 ms by the means). That is under 1 % of the frame, and the frame rate did not move: the run is CPU-bound there. In video memory, by the maps' size, Highest takes about 113 MB against the game's 31 MB. Hardware: Windows 11 Home 10.0.26300, RTX 4090 24 GB, NVIDIA 621.24, Core i9-13900K, 64 GB RAM. Scene: Hyrule Field at the castle's drawbridge as a child, standing still, camera untouched. Output 2560x1440 with ultrawide on (presented 5120x1440 SDR); DLSS 2560x720 to 5120x1440, preset K; frame generation Off; V-Sync Auto (mailbox); Quality preset Auto (this PC), on its Ultra tier (memory budget 5). Method: an in-session A/B with `RX_VK_PASS_TIMING=1`. Shadow resolution was switched live through the control pipe in 3 rounds of Game's, High, Highest. Each setting kept 2 blocks of 600 presents, after leaving out the block that straddles the switch, so there are 6 blocks for each. The table gives the median, with the mean and the standard deviation beside it. Baseline: Game's is this PR's row at 0, which leaves the game's size as main does: the hook reads no game memory and changes no register there (`tests/test_oot_shadow.py`). An in-session A/B on the same spot was steadier than separate runs of main and this PR. How to reproduce: build a pack of this branch over a base, stand at the drawbridge, set `RX_VK_PASS_TIMING=1`, and switch the row (or `longshot_ctl.py option set shadow_resolution <0|1|2>`), reading the `GPU pass timing` blocks. | A frame, median of 6 blocks of 600 presents (mean, sd) | Before: Game's 1600, this PR @ e257da53 | After: High 2200, this PR @ e257da53 | After: Highest 3072, this PR @ e257da53 | |---|---|---|---| | GPU time, all passes | 9.04 ms (9.03, 0.03) | 9.06 ms (9.06, 0.01) | 9.09 ms (9.10, 0.02) | | GPU time, the main scene pass | 3.567 ms (3.558, 0.022) | 3.587 ms (3.587, 0.016) | 3.620 ms (3.626, 0.016) | | Render thread (CPU) | 12.66 ms (12.65) | 12.60 ms (12.61) | 12.68 ms (12.70) | | Sun shadow maps, video memory (by their size) | about 31 MB | about 58 MB | about 113 MB | ``` round 1 setting 0 [(9.04, 3.568, 0.06, 12.64), (9.04, 3.564, 0.06, 12.71)] round 1 setting 1 [(9.06, 3.584, 0.06, 12.74), (9.05, 3.585, 0.06, 12.61)] round 1 setting 2 [(9.1, 3.619, 0.06, 12.78), (9.09, 3.617, 0.06, 12.64)] round 2 setting 0 [(9.05, 3.57, 0.06, 12.95), (8.97, 3.509, 0.06, 12.45)] round 2 setting 1 [(9.03, 3.559, 0.06, 12.59), (9.08, 3.615, 0.06, 12.42)] round 2 setting 2 [(9.15, 3.661, 0.06, 12.78), (9.1, 3.62, 0.06, 12.7)] round 3 setting 0 [(9.03, 3.565, 0.06, 12.5), (9.04, 3.571, 0.06, 12.68)] round 3 setting 1 [(9.06, 3.59, 0.06, 12.55), (9.06, 3.59, 0.06, 12.73)] round 3 setting 2 [(9.09, 3.619, 0.06, 12.63), (9.09, 3.62, 0.06, 12.65)] (each pair: GPU ms in all, main scene pass ms, cascade passes ms, render thread ms a frame) ``` Two earlier runs with the periodic `GPU time` line showed no difference in fps either: Hyrule Field 88.0, 89.4 and 89.7 fps, and the drawbridge 74.7, 74.6 and 74.1 fps for Game's, High and Highest. e257da53 is this branch's second commit before the rebase onto aa95d35f (now 463c23ff). The commits after it change only the menu, so the hook and what it draws are the same: the presets set the row, and an old options file is read. ### Screenshots The same spot, by day, switched live in one session: Game's, High, Highest, then Game's again, each shot 12 frames after the switch. A 400x560 crop of the 5120x1440 frame, enlarged 2x without smoothing: the gate pillar's shadow on the left-hand wall. Its edge's stair steps are coarse on Game's, finer on High, finest on Highest, and coarse again back on Game's. The whole view shifts by a few pixels between shots, so compare the edges rather than where the shadow lies. ![Before: Game's 1600 / After: High 2200 / After: Highest 3072 / Game's 1600 again, this PR @ 608cd6ab](https://git.magicbean.dev/attachments/856e1489-2ca7-4777-be8f-cbf3f48e958f) A second run with `RX_VK_PASS_TIMING=1` checked the sizes at each switch: 1600x1600 shadow passes after Game's, 2200x2200 after High, and the 3072x3072 map used every frame on Highest only ([the comment](https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/773#issuecomment-24829)). The chains' shadow on the planks, and the whole view, are in [the first screenshots' comment](https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/773#issuecomment-24657). ### Flags and settings One menu row and its options-file key; no switch. | Flag | What it is | Before this PR | After this PR | Effect | Helps / doesn't help / untested | Recommendation | |---|---|---|---|---|---|---| | Shadow resolution (`shadow_resolution` in `longshot_options.ini`, 0 to 2) | The size of the sun's shadow maps: Game's 1600, High 2200, Highest 3072 | none: always the game's 1600 | Highest (2) on Balanced, High and Ultra; Game's (0) on Minimum and Low. A file from before the row follows the preset its settings came from | Smooth shadows of thin things near Link; about 82 MB more video memory and about 0.05 ms more GPU time a frame at Highest (RTX 4090) | Helps: the drawbridge chains' shadows near Link on an RTX 4090. Untested on 4 to 8 GB cards and other makers | Highest on 8 GB cards and above, as the presets set it; Game's on Minimum and Low, where video memory is short | ## Testing **Tested:** - Rebased on main 69ff5beb with no change to the commits (`git range-diff`): 608cd6ab, named below where it was tested, is now ab099100. `python -I -B tests/run_all.py` on Windows 11 (llvm-mingw) at ab099100: 501 passed, 20 failed, 74 skipped, none in a file this PR touches: the 18 below, and fpx_sticky and spawn_emit, which can't find the C++ standard headers to build a64emit on this PC (`'algorithm' file not found`). - `python -I -B tests/run_all.py` on Windows 11 (llvm-mingw), at df342aab (608cd6ab, the head, only corrects the settings in the `docs/tested/` file; `test_tested_configurations` and `test_oot_shadow` ran again there): 539 passed, 18 failed, 37 skipped. All 18 also fail on main (a00c485c) on this PC; they are Windows environment failures in files this PR doesn't touch: appimage_player_build, bug_report, cpm_discovery, cpp_preflight, crash_dialog, env_on, lift_cleanup_error, linux_preflight, make_game_cache, make_game_prepare, missing_nup, oot_state_tap, options_budget_once, readback_late, tested_configurations (CRLF in one pipe check), vk_pgo, walk_key, work_lock (the venv's Python launcher keeps the lock after a kill; it passes with the base interpreter). `tests/test_oot_shadow.py`, `test_preset_exempt`, `test_bug_report_defaults`, `test_menu_keys`, `test_upscaler_round_trip`, `test_menu_i18n`, `test_menu_sections` and `test_tested_configurations`'s format checks pass. - On Linux (Ubuntu 22.04 in WSL 2, at 608cd6ab): `tests/run_all.py` with the system Python 3.10: 487 passed, 22 failed, 85 skipped; main (aa95d35f) in the same place: 484 passed, 24 failed. Every failure on the branch also fails on main there, and none is in a file this PR touches; several are the same as on Windows (env_on, options_budget_once). `test_oot_shadow` passes with gcc 11 and with tools.lock's clang 23. - Linux build: the renderer (`RX_VK_ONLY=1 nx2/build.py --standalone`, tools.lock's clang and sysroot) builds and links `longshot_vk.so` with the new option. `host/oot_shadow.c` compiles with the host's Linux flags and `-Wall -Wextra -Werror`, with clang 23 and gcc 11, against the generated runtime headers. A whole Linux player build needs the game, so it was not run. - The presets and the options file in the game (Windows, RTX 4090, at 608cd6ab), one start each with its own user folder, reading the row through the control pipe once the game presents: - no options file, Auto on this PC (tier 5): Highest; the same with `RX_VK_AUTO_PRESET_VRAM_MB=4096` (tier 1): Game's; - a file from before the row: on Low, Game's; on Balanced, Highest; on Custom that came from Minimum, Game's; - a file from before the row and `memory_budget`, on Auto for this PC: Highest; the same file with the card taken as 6 GB (`RX_VK_AUTO_PRESET_VRAM_MB=6144`, "the hardware changed", tier 2): Game's; - a file that has the row (Game's) on Balanced keeps Game's; - the Quality preset row set through the control pipe (the menu row's own path) to Minimum, Low, Balanced, High, Ultra, Auto, Minimum, Custom: Game's, Game's, Highest, Highest, Highest, Highest, Game's, Game's (Custom keeps what was there). Balanced, then Shadow resolution set to High by hand: High, and the preset turns to Custom. - In all 9 starts DLSS's NGX started, and the logs have no device-lost or validation lines. - In the game on Windows 11 with an RTX 4090 (NVIDIA 621.24), at e257da53, looking at the drawbridge's chains and the ivy near Link: - High and Highest smooth the chains' shadows; - Game's gives back the game's shadows at once; - switching back and forth while playing changes the shadows at once; - the pass timing lists the larger cascade passes on High and Highest. Measured as above. **Not tested:** - Whether the shadows of thin things still shimmer while Link or the camera moves: they were looked at standing still, and while switching the row. - Other thin things (fences, the ivy up close) than the drawbridge's chains. - A run in the game on Linux, AMD, Intel or a Steam Deck. The hook runs on the CPU and the renderer draws whatever size the game asks for, so nothing in it depends on the maker, but the video memory and the GPU time were measured on an RTX 4090 only. **Must be tested:** - An 8 GB card on the Balanced preset, any maker, Windows or Linux (by hand, test request T-TUR5): stand still at Hyrule Field's drawbridge as a child and switch Shadow resolution between Game's and Highest. Expect smooth chain shadows on Highest, no new stutter, and no texture-budget warning in `longshot_log.txt`; report the GPU time line for each. - One AMD or Intel GPU, Windows or Linux (by hand, test request T-WSWW): the same steps, expecting the same shadows as on NVIDIA, and the same row values after picking each Quality preset. ## Risks **What could break:** - A changed game build: the hook checks the build id and the instruction words and installs nothing when they differ, so the row then does nothing. - Cards with 8 GB on Balanced: Highest takes about 82 MB more video memory than the game's size. - Switching down during play does not give all of it back at once: in the size-check run, the 3072x3072 map in use left the renderer's list of large textures after the switch to Game's, but a second one (54 MB), idle since before the switch, stayed listed through Game's and High for over 6000 presents. Starting the game on Game's never makes them. **Turning it off:** set Shadow resolution to Game's, which makes the hook a no-op. **Compatibility:** - `longshot_options.ini` gains `shadow_resolution`. A file from before it takes the value of the preset it came from: Game's on Minimum and Low, Highest otherwise, and Auto's tier once the GPU is known. A player who had picked Balanced or above sees sharper shadows after updating. - The game is translated again at the next build, as for any lifter change, since the lifter adds the hook. - No change to saves, the shader or pipeline caches, or `MKWVK_API_VERSION`: `MkwVkHost` and `MkwVkRenderer` are unchanged; only a new option id is added. ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. - [x] Testing names what was tested, what wasn't, and what must still be tested, where and how. - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). - [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. ### When it applies - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Numbers from the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value. - [x] Screenshots of the same spot, captioned like the Measurements columns, for a change that shows on screen. - [x] A file in `docs/tested/` for each new measured run (format in `docs/tested-configurations.md`). - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. - [x] Docs updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The sun's shadow maps are 1600 pixels a side in the game's normal path, so
the shadows of chains, fences and leaves near Link come out stair-stepped.
The game has larger sizes in its own table (2200, 3072); the new Graphics
row (Detail) raises the size the game rounds each frame to one of them:
Game's (1600), High (2200) or Highest (3072). It is read every frame, so a
change applies at once, and it only ever raises the game's own size.

The hook (nx2/oot_shadow.py) checks the build and the five instructions it
follows, and is left out otherwise. The default is Game's until measured.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Machine-made like the rest of menu_i18n.h, to be checked by speakers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Minimum and Low keep the game's 1600 (the 4 and 6 GB cards they are for),
Balanced, High and Ultra choose Highest. An options file from before the
row follows the preset its settings came from (memory_budget), Auto's tier
once the GPU is known; the bug report counts the row among the defaults
that depend on the PC. The run measured on an RTX 4090 is in docs/tested.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
fix(options): review: Shadow resolution's old-file reading runs once, the docs say what was computed
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 4s
Website / Website on Cloudflare (pull_request_target) Has been skipped
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Guide screenshots / Guide images and local UI source review (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
Website / Website on Cloudflare (pull_request) Has been cancelled
608cd6abd5
The flag for an options file from before the row is cleared once Auto has
used it, so a later call can't overwrite what the player chose; the option
id sits in order in vk_api.h; the README's preset paragraph is re-flowed and
says the video memory figures come from the maps' size; the tested run says
what the pass timing and the picture showed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Author
First-time contributor

Screenshots of the same spot: Hyrule Field at the castle's drawbridge as a child, by day, standing still with the camera reset (ZL). Windows 11, RTX 4090, NVIDIA 621.24, presented at 5120x1440 SDR (DLSS 2560x720 to 5120x1440, preset K). Shadow resolution was switched live through the control pipe in one session, each shot taken 12 frames after the switch.

The crops below are 400x360 pixels of the 5120x1440 frame (from x 2600, y 740), enlarged 2x without smoothing. They show the right-hand chain's shadow on the planks beside Link. The shadow sits a little to one side or the other from shot to shot because the idle camera sways: the chain and the gate pillar move by the same amount, and a second Game's shot taken last is as far from the first as High is.

Before: Game's 1600, this PR @ 608cd6ab

Before: Game's 1600, this PR @ 608cd6ab

After: High 2200, this PR @ 608cd6ab

After: High 2200, this PR @ 608cd6ab

After: Highest 3072, this PR @ 608cd6ab

After: Highest 3072, this PR @ 608cd6ab

The whole view, After: Highest 3072, this PR @ 608cd6ab (scaled down to 2560x720)

The whole view, After: Highest 3072, this PR @ 608cd6ab

🤖 Generated with Claude Code

Screenshots of the same spot: Hyrule Field at the castle's drawbridge as a child, by day, standing still with the camera reset (ZL). Windows 11, RTX 4090, NVIDIA 621.24, presented at 5120x1440 SDR (DLSS 2560x720 to 5120x1440, preset K). Shadow resolution was switched live through the control pipe in one session, each shot taken 12 frames after the switch. The crops below are 400x360 pixels of the 5120x1440 frame (from x 2600, y 740), enlarged 2x without smoothing. They show the right-hand chain's shadow on the planks beside Link. The shadow sits a little to one side or the other from shot to shot because the idle camera sways: the chain and the gate pillar move by the same amount, and a second Game's shot taken last is as far from the first as High is. **Before: Game's 1600, this PR @ 608cd6ab** ![Before: Game's 1600, this PR @ 608cd6ab](https://git.magicbean.dev/attachments/151e783f-c9e7-4db2-bed0-720445ca74cc) **After: High 2200, this PR @ 608cd6ab** ![After: High 2200, this PR @ 608cd6ab](https://git.magicbean.dev/attachments/262d9bc2-62a4-43b8-81ea-901d9c31d362) **After: Highest 3072, this PR @ 608cd6ab** ![After: Highest 3072, this PR @ 608cd6ab](https://git.magicbean.dev/attachments/42b44ee3-134f-4a30-a060-9dff66f8e46c) **The whole view, After: Highest 3072, this PR @ 608cd6ab** (scaled down to 2560x720) ![The whole view, After: Highest 3072, this PR @ 608cd6ab](https://git.magicbean.dev/attachments/e42f7eff-6d44-4005-86ed-78be038ac550) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
First-time contributor

Settled the last two boxes and tested the presets in the game, at 608cd6ab (no new commit):

  • Linux: the renderer builds and links with tools.lock's clang; host/oot_shadow.c compiles with the host's Linux flags under clang 23 and gcc 11 with -Wall -Wextra -Werror; run_all fails nothing that main doesn't fail in the same place.
  • The presets and old options files, run in the game (9 starts, each with its own user folder): every case gives the expected row value (Testing lists them).
  • Screenshots of the drawbridge chains at Game's, High and Highest are in the description and in the screenshots' comment.
  • Must be tested now says who runs it: by hand, through test requests.
Settled the last two boxes and tested the presets in the game, at 608cd6ab (no new commit): - Linux: the renderer builds and links with tools.lock's clang; `host/oot_shadow.c` compiles with the host's Linux flags under clang 23 and gcc 11 with `-Wall -Wextra -Werror`; run_all fails nothing that main doesn't fail in the same place. - The presets and old options files, run in the game (9 starts, each with its own user folder): every case gives the expected row value (Testing lists them). - Screenshots of the drawbridge chains at Game's, High and Highest are in the description and in [the screenshots' comment](https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/773#issuecomment-24657). - Must be tested now says who runs it: by hand, through test requests.
Author
First-time contributor

Must be tested now names its test requests: T-TUR5 (an 8 GB card on Balanced) and T-WSWW (AMD or Intel). Both wait for the testing wanted label.

Must be tested now names its test requests: T-TUR5 (an 8 GB card on Balanced) and T-WSWW (AMD or Intel). Both wait for the testing wanted label.
Author
First-time contributor

A clearer view of the same four shots (same run as the earlier screenshots, this PR @ 608cd6ab): the gate pillar's shadow on the left-hand wall, a 400x560 crop of the 5120x1440 frame (from x 560, y 200), enlarged 2x without smoothing. The setting went Game's, High, Highest, then Game's again, each shot 12 frames after the switch.

The edge's stair steps are coarse on Game's, finer on High, finest on Highest, and coarse again when it goes back to Game's.

Before: Game's 1600 / After: High 2200 / After: Highest 3072 / Game's 1600 again, this PR @ 608cd6ab

The cascade sizes at each setting were checked in a second run at the same save's start in Hyrule Field, with RX_VK_PASS_TIMING=1 and the row switched through the control pipe, 1300 presents per setting:

  • before any switch (Highest): the shadow passes are 3072x3072, and the 3072x3072 map is used every frame;
  • after Game's: the passes are 1600x1600, and the 3072x3072 map is no longer used ("last used 1241 presents ago" and counting);
  • after High: the passes are 2200x2200, and the 3072x3072 map is still unused;
  • after Highest: the 3072x3072 map is used every frame again ("last used 1 presents ago");
  • after Game's again: the 3072x3072 map is no longer used.

🤖 Generated with Claude Code

A clearer view of the same four shots (same run as the earlier screenshots, this PR @ 608cd6ab): the gate pillar's shadow on the left-hand wall, a 400x560 crop of the 5120x1440 frame (from x 560, y 200), enlarged 2x without smoothing. The setting went Game's, High, Highest, then Game's again, each shot 12 frames after the switch. The edge's stair steps are coarse on Game's, finer on High, finest on Highest, and coarse again when it goes back to Game's. ![Before: Game's 1600 / After: High 2200 / After: Highest 3072 / Game's 1600 again, this PR @ 608cd6ab](https://git.magicbean.dev/attachments/856e1489-2ca7-4777-be8f-cbf3f48e958f) The cascade sizes at each setting were checked in a second run at the same save's start in Hyrule Field, with `RX_VK_PASS_TIMING=1` and the row switched through the control pipe, 1300 presents per setting: - before any switch (Highest): the shadow passes are 3072x3072, and the 3072x3072 map is used every frame; - after Game's: the passes are 1600x1600, and the 3072x3072 map is no longer used ("last used 1241 presents ago" and counting); - after High: the passes are 2200x2200, and the 3072x3072 map is still unused; - after Highest: the 3072x3072 map is used every frame again ("last used 1 presents ago"); - after Game's again: the 3072x3072 map is no longer used. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
First-time contributor

Screenshots: the description now shows the gate pillar's shadow edge for Game's, High, Highest and Game's again, where the difference is plainest; the chain crops stay in the earlier comment. The cascade sizes at each setting are checked in a second run (comment 24829). The note on the view shifting between shots is reworded as what was seen.

Screenshots: the description now shows the gate pillar's shadow edge for Game's, High, Highest and Game's again, where the difference is plainest; the chain crops stay in the earlier comment. The cascade sizes at each setting are checked in a second run (comment 24829). The note on the view shifting between shots is reworded as what was seen.
Author
First-time contributor

A correction to the size check in the comment above (#773 (comment)): the "last used 1241 presents ago" I quoted belongs to the wrong map. The run has two 3072x3072 maps, and that one (0x10004579a00) was already idle before any switch (last used 41, then 641 presents ago, on Highest).

The evidence is the other one, 0x10004c2e400: it is listed as "last used 1 presents ago" in every listing taken on Highest (before the first switch and after the switch back to Highest), and it is absent from every listing taken on Game's or High. The shadow passes are 1600x1600 after Game's and 2200x2200 after High, as said.

The idle map stays listed through Game's and High (54 MB), so switching down during play doesn't give all the video memory back at once; the description's Risks now say so.

🤖 Generated with Claude Code

A correction to the size check in the comment above (https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/773#issuecomment-24829): the "last used 1241 presents ago" I quoted belongs to the wrong map. The run has two 3072x3072 maps, and that one (0x10004579a00) was already idle before any switch (last used 41, then 641 presents ago, on Highest). The evidence is the other one, 0x10004c2e400: it is listed as "last used 1 presents ago" in every listing taken on Highest (before the first switch and after the switch back to Highest), and it is absent from every listing taken on Game's or High. The shadow passes are 1600x1600 after Game's and 2200x2200 after High, as said. The idle map stays listed through Game's and High (54 MB), so switching down during play doesn't give all the video memory back at once; the description's Risks now say so. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Jiro113 changed title from WIP: feat(options): a Shadow resolution setting keeps thin shadows near Link smooth to feat(options): a Shadow resolution setting keeps thin shadows near Link smooth 2026-11-15 13:36:00 +00:00
Author
First-time contributor

Testing needed. This PR is ready for review, but it was run in the game on an RTX 4090 (Windows 11) only. Two runs by hand are needed before merge, posted as test requests:

  • T-TUR5: an 8 GB card on the Balanced preset, any maker, Windows or Linux.
  • T-WSWW: one AMD or Intel GPU, Windows or Linux.

Steps for both: stand still at Hyrule Field's drawbridge as a child, by day, and switch Graphics > Shadow resolution between Game's and Highest. Expect the shadows' edges (the gate pillar's on the left wall, the chains' on the planks) to turn smooth on Highest and coarse again on Game's, with no new stutter. On the 8 GB card, also look for a texture-budget warning in longshot_log.txt and give the GPU time line for each setting. On AMD or Intel, also pick each Quality preset and check that the row reads Game's on Minimum and Low, and Highest on Balanced, High and Ultra.

A maintainer: the requests are waiting for the testing wanted label before testers can see them.

🤖 Generated with Claude Code

**Testing needed.** This PR is ready for review, but it was run in the game on an RTX 4090 (Windows 11) only. Two runs by hand are needed before merge, posted as test requests: - **T-TUR5: an 8 GB card on the Balanced preset**, any maker, Windows or Linux. - **T-WSWW: one AMD or Intel GPU**, Windows or Linux. Steps for both: stand still at Hyrule Field's drawbridge as a child, by day, and switch Graphics > Shadow resolution between Game's and Highest. Expect the shadows' edges (the gate pillar's on the left wall, the chains' on the planks) to turn smooth on Highest and coarse again on Game's, with no new stutter. On the 8 GB card, also look for a texture-budget warning in `longshot_log.txt` and give the `GPU time` line for each setting. On AMD or Intel, also pick each Quality preset and check that the row reads Game's on Minimum and Low, and Highest on Balanced, High and Ultra. A maintainer: the requests are waiting for the `testing wanted` label before testers can see them. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Jiro113 force-pushed feat/sun-shadow-resolution from 608cd6abd5
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 4s
Website / Website on Cloudflare (pull_request_target) Has been skipped
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Guide screenshots / Guide images and local UI source review (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
Website / Website on Cloudflare (pull_request) Has been cancelled
to ab09910038
Some checks are pending
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Guide screenshots / Guide images and local UI source review (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 5s
2026-11-15 17:53:56 +00:00
Compare
Author
First-time contributor

Rebased on main 69ff5beb, with no change to the commits (git range-diff): 608cd6ab is now ab099100. run_all rerun at the new head, added to Testing; the measurements and in-game runs stand, the code they ran is unchanged. #836, stacked on this PR, was rebased with it.

🤖 Generated with Claude Code

Rebased on main 69ff5beb, with no change to the commits (`git range-diff`): 608cd6ab is now ab099100. `run_all` rerun at the new head, added to Testing; the measurements and in-game runs stand, the code they ran is unchanged. #836, stacked on this PR, was rebased with it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Some checks are pending
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Guide screenshots / Guide images and local UI source review (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Website / Website on Cloudflare (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 5s
This pull request doesn't have enough approvals yet. 0 of 1 approvals granted.
Some workflows are waiting to be reviewed.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'feat/sun-shadow-resolution:Jiro113-feat/sun-shadow-resolution'
git switch 'Jiro113-feat/sun-shadow-resolution'
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
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/Longshot!773
No description provided.