feat: pictures of the destinations on the Teleport page, from your own game #2371

Open
samaBR85 wants to merge 2 commits from samaBR85/feat/teleport-pictures into main
samaBR85 commented 2026-11-11 16:19:02 +00:00 (Migrated from github.com)

The Teleport list only shows map names, and many players don't remember where a name like "Sacred Forest Meadow" or "Zora's Fountain" leads. A picture of each place makes the choice easy at a glance.

How it works now. The pictures come from the player's own game. Nothing is shipped and nothing leaves the PC. The first time Link stays a few seconds in a place (no map loading, no cutscene), Hookshot copies that frame once and keeps it in teleport_captures.bin in the game folder. The page shows the picture for Link's current age, or the other age's when only that one exists. A place not visited yet shows a "No picture yet" frame instead.

Retake picture. Debug › Travel gets a Retake picture row. The player chooses one key, mouse button or controller button, then presses it in play to take that place's picture again. A button the game or Hookshot already uses is refused, and the help says what uses it. Symbol keys show by the keyboard layout's own character with a name, for example "` (Backtick)". The Controls page uses the same names.

Tip: with Free camera on, a retake gets views Link can't reach.

What changed

  • host/hos_main.c: oot_capture_tick, run with the player's update, tells the menu when Link has settled in a map.
  • gpu/vk/vk_replay.cpp: teleport_capture copies the middle of the presented frame (480x270) to a host buffer when the menu asks, and hands it over a frame later.
  • gpu/vk/vk_menu.cpp: the picture above the help, the "No picture yet" frame, the Retake picture row, the "Teleport picture saved" notice, and the key names. One texture shows the chosen picture, with its pixels sent again when the choice changes, since the UI's descriptor pool holds 16.
  • gpu/vk/oot_teleport_pictures.h and oot_teleport_capture.h: each destination's child and adult maps, and the 47 maps that can be taken.
  • tests/test_teleport_pictures.py: the three lists (oot_destinations, the pictures, the maps that can be taken) agree. No Pillow needed.
  • readme.md: a paragraph in the debug tools section.
  • gpu/vk/menu_i18n.h: the new texts in the menu's 13 languages, the old Teleport help's entries replaced. Machine-made, as the tables' other texts; the reason a Retake picture button is refused is translated before the message is made of it. The "No picture yet" line breaks into two lines of about the same width when it is long, in any language (after a particle in Japanese and Chinese).
  • tests/test_menu_keys.py and tests/test_fps_overlay_order.py: their stubs know the new names. The notice is drawn by its own draw_notice, so menu_record stays simple to stub.

How it was tested

  • Built from this branch (current main) on Windows with an NVIDIA card, and played in game on that build: the page shows the screenshots taken in play. The menu screenshots below come from it.
  • Played longer on a v2.1.0 build with the same change: pictures taken on arrival, retaken with a chosen key, and the right one shown after switching age.
  • tests/run_all.py: 394 pass, 7 fail. Two of those were mine and are fixed (test_menu_keys, test_fps_overlay_order). The other 5 (test_env_on, test_vram_budget_row, test_lift_cache, test_fpx_sticky, test_bake_portability) fail on main without this change too, on this PC.
  • test_menu_i18n: the same problems as on main (texts from before this change), none from the new entries. The menu checked on screen in English, Portuguese, German, French, Japanese and Chinese.

Not tested: Linux and the Steam Deck (the game-folder path now takes / as well as \), and AMD or Intel cards.

Screenshots

Debug › Travel: the Retake picture row and its help
1-retake-picture-row-v2

The Teleport page with a picture
2-teleport-with-picture

The Teleport page before a place's picture is taken
3-teleport-no-picture-yet

In game: a picture saved
4-picture-saved-notice

The Teleport list only shows map names, and many players don't remember where a name like "Sacred Forest Meadow" or "Zora's Fountain" leads. A picture of each place makes the choice easy at a glance. **How it works now.** The pictures come from the player's own game. Nothing is shipped and nothing leaves the PC. The first time Link stays a few seconds in a place (no map loading, no cutscene), Hookshot copies that frame once and keeps it in `teleport_captures.bin` in the game folder. The page shows the picture for Link's current age, or the other age's when only that one exists. A place not visited yet shows a "No picture yet" frame instead. **Retake picture.** Debug › Travel gets a Retake picture row. The player chooses one key, mouse button or controller button, then presses it in play to take that place's picture again. A button the game or Hookshot already uses is refused, and the help says what uses it. Symbol keys show by the keyboard layout's own character with a name, for example "` (Backtick)". The Controls page uses the same names. Tip: with **Free camera** on, a retake gets views Link can't reach. **What changed** - `host/hos_main.c`: `oot_capture_tick`, run with the player's update, tells the menu when Link has settled in a map. - `gpu/vk/vk_replay.cpp`: `teleport_capture` copies the middle of the presented frame (480x270) to a host buffer when the menu asks, and hands it over a frame later. - `gpu/vk/vk_menu.cpp`: the picture above the help, the "No picture yet" frame, the Retake picture row, the "Teleport picture saved" notice, and the key names. One texture shows the chosen picture, with its pixels sent again when the choice changes, since the UI's descriptor pool holds 16. - `gpu/vk/oot_teleport_pictures.h` and `oot_teleport_capture.h`: each destination's child and adult maps, and the 47 maps that can be taken. - `tests/test_teleport_pictures.py`: the three lists (`oot_destinations`, the pictures, the maps that can be taken) agree. No Pillow needed. - `readme.md`: a paragraph in the debug tools section. - `gpu/vk/menu_i18n.h`: the new texts in the menu's 13 languages, the old Teleport help's entries replaced. Machine-made, as the tables' other texts; the reason a Retake picture button is refused is translated before the message is made of it. The "No picture yet" line breaks into two lines of about the same width when it is long, in any language (after a particle in Japanese and Chinese). - `tests/test_menu_keys.py` and `tests/test_fps_overlay_order.py`: their stubs know the new names. The notice is drawn by its own `draw_notice`, so `menu_record` stays simple to stub. **How it was tested** - Built from this branch (current main) on Windows with an NVIDIA card, and played in game on that build: the page shows the screenshots taken in play. The menu screenshots below come from it. - Played longer on a v2.1.0 build with the same change: pictures taken on arrival, retaken with a chosen key, and the right one shown after switching age. - `tests/run_all.py`: 394 pass, 7 fail. Two of those were mine and are fixed (`test_menu_keys`, `test_fps_overlay_order`). The other 5 (`test_env_on`, `test_vram_budget_row`, `test_lift_cache`, `test_fpx_sticky`, `test_bake_portability`) fail on main without this change too, on this PC. - `test_menu_i18n`: the same problems as on main (texts from before this change), none from the new entries. The menu checked on screen in English, Portuguese, German, French, Japanese and Chinese. **Not tested:** Linux and the Steam Deck (the game-folder path now takes `/` as well as `\`), and AMD or Intel cards. **Screenshots** Debug › Travel: the Retake picture row and its help <img width="1600" height="900" alt="1-retake-picture-row-v2" src="https://github.com/user-attachments/assets/79cd9cb4-8db7-49d1-af31-a165fcdcd8d9" /> The Teleport page with a picture <img width="1600" height="900" alt="2-teleport-with-picture" src="https://github.com/user-attachments/assets/03538aae-bc54-42c6-bce9-c33571cb0c20" /> The Teleport page before a place's picture is taken <img width="1600" height="900" alt="3-teleport-no-picture-yet" src="https://github.com/user-attachments/assets/1622b8de-6309-4582-8c70-586152b7c9ca" /> In game: a picture saved <img width="1602" height="938" alt="4-picture-saved-notice" src="https://github.com/user-attachments/assets/22358705-211b-49c2-ba1b-172079caff43" />
nullifyshot commented 2026-11-11 16:32:44 +00:00 (Migrated from github.com)

Thanks for this. A picture for each destination is a good idea, and the single-texture approach for the descriptor pool is a nice touch. I don't think AI-generated images are the right source for them, though. Shipping generated art in the repo isn't ideal: it only suggests each place, it doesn't look like what the player actually sees, and it's 2.5 MB of assets the project would carry from now on.

The game already has a picture for every one of these places: its own map-screen art. It can be used without anything from the game leaving anyone's PC, the same way the button prompts are built.

Where the textures are. They're in the player's RomFS under UI/CustomTex/, named almost exactly like your files:

  • Overworld: <Map>_<Child|Adult>_NN.bntx.zs, for example KokiriForest_Child_00, HyruleField_Adult_00 and HountedWasteland_Child_00. That spelling is the game's own.
  • Minimaps: Mini_<Map>_<Age>_NN, if a smaller one ever fits better.
  • Dungeons: a whole-dungeon overview, <Dungeon>_All (for example DodongoCavern_All and BottomOfTheWell_All), next to the per-floor maps (ForestTemple_2F_Main and so on).

Matched against oot_teleport_pictures.h, 45 of the 47 destinations have one under the same name. Gerudo Training Ground and Inside Ganon's Castle are the only two that don't by name, so they'd need the nearest map or no picture.

How to build them. This could happen at build time, from the player's own dump, as nx2/prompt_assets.py does with UI/Layout2Archive/Common for the button prompts:

  • read_zs (with the zs dictionary) decompresses the .bntx.zs files.
  • bntx_textures and decode read and deswizzle the Switch 2 BNTX, using texture2ddecoder, which is already in nx2/prompt_requirements.txt next to the Pillow this PR uses.
  • teleport_pictures.py then scales them to the box and writes teleport_pictures.bin into the game folder, as it does now.

Done that way, nothing made from the game is committed or released, which keeps it within CONTRIBUTING's rule that nothing made from the game leaves the PC. assets/teleport_pictures/ could go entirely. The player's own teleport_pictures/ override can stay as it is, and so can the rest of the PR (the menu drawing, the single reused texture, the test).

Thanks for this. A picture for each destination is a good idea, and the single-texture approach for the descriptor pool is a nice touch. I don't think AI-generated images are the right source for them, though. Shipping generated art in the repo isn't ideal: it only suggests each place, it doesn't look like what the player actually sees, and it's 2.5 MB of assets the project would carry from now on. The game already has a picture for every one of these places: its own map-screen art. It can be used without anything from the game leaving anyone's PC, the same way the button prompts are built. **Where the textures are.** They're in the player's RomFS under `UI/CustomTex/`, named almost exactly like your files: - **Overworld:** `<Map>_<Child|Adult>_NN.bntx.zs`, for example `KokiriForest_Child_00`, `HyruleField_Adult_00` and `HountedWasteland_Child_00`. That spelling is the game's own. - **Minimaps:** `Mini_<Map>_<Age>_NN`, if a smaller one ever fits better. - **Dungeons:** a whole-dungeon overview, `<Dungeon>_All` (for example `DodongoCavern_All` and `BottomOfTheWell_All`), next to the per-floor maps (`ForestTemple_2F_Main` and so on). Matched against `oot_teleport_pictures.h`, 45 of the 47 destinations have one under the same name. Gerudo Training Ground and Inside Ganon's Castle are the only two that don't by name, so they'd need the nearest map or no picture. **How to build them.** This could happen at build time, from the player's own dump, as `nx2/prompt_assets.py` does with `UI/Layout2Archive/Common` for the button prompts: - `read_zs` (with the zs dictionary) decompresses the `.bntx.zs` files. - `bntx_textures` and `decode` read and deswizzle the Switch 2 BNTX, using `texture2ddecoder`, which is already in `nx2/prompt_requirements.txt` next to the Pillow this PR uses. - `teleport_pictures.py` then scales them to the box and writes `teleport_pictures.bin` into the game folder, as it does now. Done that way, nothing made from the game is committed or released, which keeps it within CONTRIBUTING's rule that nothing made from the game leaves the PC. `assets/teleport_pictures/` could go entirely. The player's own `teleport_pictures/` override can stay as it is, and so can the rest of the PR (the menu drawing, the single reused texture, the test).
nullifyshot commented 2026-11-11 16:38:40 +00:00 (Migrated from github.com)

A follow-up, and one correction to my comment above. I decoded the map textures from my own dump to compare them with the pictures in assets/teleport_pictures. I'm not posting the images, since they're made from the game, but here's how to reproduce it locally.

What they are

  • Overworld: UI/CustomTex/.../<Map>_<Child|Adult>_00.bntx.zs is a 2000x1440 hand-drawn map of the area, in the map screen's parchment style. It's a top-down map, not a scenery view, so the Teleport page would show "where on the map" rather than "what it looks like".
  • Dungeons (correction): <Dungeon>_All is not usable. It decodes as a dark silhouette of the layout, a mask. The finished picture is the per-floor <Dungeon>_<Floor>_Main (for example DodongoCavern_1F_Main or ForestTemple_1F_Main). Some floors have one per state instead (ForestTemple_2F_State00_Main to State03). The other layers next to them are _Back (a flat backdrop), _Alpha and _Grad.
  • Formats: the overworld maps are ASTC 5x5 (format 0x2f06) and the dungeon floors ASTC 6x6 (0x3106), all 2000x1440.

How to reproduce it

  • The helpers: I used nx2/prompt_assets.py's zs_dictionary, read_zs, bntx_textures and gob_offset as they are. Its decode only handles ASTC 4x4 and BC4 (what the prompts need), so it needs one change: take the ASTC block size from the format's top byte (0x2D is 4x4, 0x2F 5x5, 0x31 6x6 and so on) and pass it to texture2ddecoder.decode_astc.
  • Where the files are: they sit in subfolders of UI/CustomTex, so find each by name rather than building the path.
  • Python: the Builder's Python already has zstandard, Pillow and texture2ddecoder.
The script I used (no game data in it)
"""Decode map-screen textures from your own RomFS to PNG, for viewing locally.
python -I decode_maps.py <romfs> <hookshot>/nx2 <out dir> KokiriForest_Child_00 DodongoCavern_1F_Main ..."""
import os, sys
romfs, nx2, out = sys.argv[1], sys.argv[2], sys.argv[3]
sys.path.insert(0, nx2)
import prompt_assets as pa   # zs_dictionary, read_zs, bntx_textures, gob_offset
import texture2ddecoder
from PIL import Image

ASTC = {0x2D: (4, 4), 0x2E: (5, 4), 0x2F: (5, 5), 0x30: (6, 5), 0x31: (6, 6), 0x32: (8, 5), 0x33: (8, 6), 0x34: (8, 8)}

def decode(b, t):
    _, fmt, w, h, gobs, data = t
    bx, by = ASTC[fmt >> 8]
    bw, bh = (w + bx - 1) // bx, (h + by - 1) // by
    pitch = (bw * 16 + 63) // 64
    lin = b"".join(b[data + o:data + o + 16] for o in (pa.gob_offset(x * 16, y, gobs, pitch) for y in range(bh) for x in range(bw)))
    return Image.frombytes("RGBA", (bw * bx, bh * by), texture2ddecoder.decode_astc(lin, bw * bx, bh * by, bx, by),
                           "raw", "BGRA").crop((0, 0, w, h))

os.makedirs(out, exist_ok=True)
dic = pa.zs_dictionary(romfs)
where = {f[:-len(".bntx.zs")]: os.path.relpath(os.path.join(d, f), romfs)
         for d, _, fs in os.walk(os.path.join(romfs, "UI", "CustomTex")) for f in fs if f.endswith(".bntx.zs")}
for name in sys.argv[4:]:
    b = pa.read_zs(romfs, where[name], dic)
    for tname, t in pa.bntx_textures(b).items():
        decode(b, t).save(os.path.join(out, name + ".png"))
        print(name, t[2], "x", t[3], hex(t[1]))
        break

The same calls would fit in teleport_pictures.py at build time: decode, scale to the box, and write teleport_pictures.bin. Nothing made from the game would need to be committed. For the dungeons I'd use the entrance floor's _Main (_State00_Main where a floor has states).

A follow-up, and one correction to my comment above. I decoded the map textures from my own dump to compare them with the pictures in `assets/teleport_pictures`. I'm not posting the images, since they're made from the game, but here's how to reproduce it locally. **What they are** - **Overworld:** `UI/CustomTex/.../<Map>_<Child|Adult>_00.bntx.zs` is a 2000x1440 hand-drawn map of the area, in the map screen's parchment style. It's a top-down map, not a scenery view, so the Teleport page would show "where on the map" rather than "what it looks like". - **Dungeons (correction):** `<Dungeon>_All` is not usable. It decodes as a dark silhouette of the layout, a mask. The finished picture is the per-floor `<Dungeon>_<Floor>_Main` (for example `DodongoCavern_1F_Main` or `ForestTemple_1F_Main`). Some floors have one per state instead (`ForestTemple_2F_State00_Main` to `State03`). The other layers next to them are `_Back` (a flat backdrop), `_Alpha` and `_Grad`. - **Formats:** the overworld maps are ASTC 5x5 (format `0x2f06`) and the dungeon floors ASTC 6x6 (`0x3106`), all 2000x1440. **How to reproduce it** - **The helpers:** I used `nx2/prompt_assets.py`'s `zs_dictionary`, `read_zs`, `bntx_textures` and `gob_offset` as they are. Its `decode` only handles ASTC 4x4 and BC4 (what the prompts need), so it needs one change: take the ASTC block size from the format's top byte (`0x2D` is 4x4, `0x2F` 5x5, `0x31` 6x6 and so on) and pass it to `texture2ddecoder.decode_astc`. - **Where the files are:** they sit in subfolders of `UI/CustomTex`, so find each by name rather than building the path. - **Python:** the Builder's Python already has `zstandard`, Pillow and `texture2ddecoder`. <details><summary>The script I used (no game data in it)</summary> ```python """Decode map-screen textures from your own RomFS to PNG, for viewing locally. python -I decode_maps.py <romfs> <hookshot>/nx2 <out dir> KokiriForest_Child_00 DodongoCavern_1F_Main ...""" import os, sys romfs, nx2, out = sys.argv[1], sys.argv[2], sys.argv[3] sys.path.insert(0, nx2) import prompt_assets as pa # zs_dictionary, read_zs, bntx_textures, gob_offset import texture2ddecoder from PIL import Image ASTC = {0x2D: (4, 4), 0x2E: (5, 4), 0x2F: (5, 5), 0x30: (6, 5), 0x31: (6, 6), 0x32: (8, 5), 0x33: (8, 6), 0x34: (8, 8)} def decode(b, t): _, fmt, w, h, gobs, data = t bx, by = ASTC[fmt >> 8] bw, bh = (w + bx - 1) // bx, (h + by - 1) // by pitch = (bw * 16 + 63) // 64 lin = b"".join(b[data + o:data + o + 16] for o in (pa.gob_offset(x * 16, y, gobs, pitch) for y in range(bh) for x in range(bw))) return Image.frombytes("RGBA", (bw * bx, bh * by), texture2ddecoder.decode_astc(lin, bw * bx, bh * by, bx, by), "raw", "BGRA").crop((0, 0, w, h)) os.makedirs(out, exist_ok=True) dic = pa.zs_dictionary(romfs) where = {f[:-len(".bntx.zs")]: os.path.relpath(os.path.join(d, f), romfs) for d, _, fs in os.walk(os.path.join(romfs, "UI", "CustomTex")) for f in fs if f.endswith(".bntx.zs")} for name in sys.argv[4:]: b = pa.read_zs(romfs, where[name], dic) for tname, t in pa.bntx_textures(b).items(): decode(b, t).save(os.path.join(out, name + ".png")) print(name, t[2], "x", t[3], hex(t[1])) break ``` </details> The same calls would fit in `teleport_pictures.py` at build time: decode, scale to the box, and write `teleport_pictures.bin`. Nothing made from the game would need to be committed. For the dungeons I'd use the entrance floor's `_Main` (`_State00_Main` where a floor has states).
Joey-Magnus (Migrated from github.com) requested changes 2026-11-11 20:08:19 +00:00
Joey-Magnus (Migrated from github.com) left a comment

Thanks for this. Pictures on the Teleport list are a good idea, and reusing one texture to stay inside the UI's 16-descriptor pool is the right call. There's one real bug, though, and a repo rule the build step breaks, so I'm asking for changes before merging.

blocking gpu/vk/vk_menu.cpp:2280: read_teleport_pictures finds the game folder with strrchr(path, '\\'). On Linux, GetModuleFileNameA (host/posix/win32_posix.c) returns a / path, so slash is null and the function returns before it even logs. The Teleport page then shows no pictures on Linux and the Steam Deck, with nothing in hookshot_log.txt to say why. ini_path() in the same file already does this portably: take the folder with find_last_of("\\/") (or loop back to the last \\ or /), and log on that early return too. The Linux build isn't in the PR's testing, which is how this slipped through.

blocking nx2/teleport_pictures.py:19 and nx2/make_game.py:1861: CONTRIBUTING accepts the button prompts' packages (Pillow among them) "for nx2/prompt_assets.py and nx2/prompt_draw.py alone, at build time", and asks reviewers to flag any other use. This PR adds a new build-time Pillow use in its own script. Either move the work into prompt_assets.py (which is where nullifyshot's suggestion of decoding the player's own map textures would naturally live anyway), or get an explicit OK from the maintainer to widen that rule, and update CONTRIBUTING in the same PR.

should fix The shipped pictures themselves. nullifyshot covered the alternative well (the game's own UI/CustomTex maps, decoded at build time from the player's dump, like the prompts), so I won't repeat it. Two things I'd add if the AI pictures stay:

  • The PR doesn't say which tool made them, under what terms, or whether any input was a screenshot of the game (image-to-image). All three decide whether they can be shipped under GPL-3.0-or-later. Please state their provenance (a note in assets/teleport_pictures/ or PROVENANCE.md).
  • 2.5 MB of binary assets in the repo for a page in the Debug tools is a lasting cost. That's a maintainer call, but it should be made on purpose.

should fix gpu/vk/oot_teleport_pictures.h and vk_menu.cpp:2306: the page always shows the child map's picture, so 16 of the 17 *_Adult.jpg files are never shown (only GerudoFortress_Adult is, and GanonCastle never is). They still go into teleport_pictures.bin: 47 × 480 × 270 × 4 bytes ≈ 24 MB in every game folder, all read into memory the first time the page opens. Either choose the picture by Link's current age (the menu already reads link_age for the Kid/Adult row), or drop the adult pictures that are never shown and remove them from the header. Storing the pictures compressed in the .bin (or loading only the chosen one) would also cut that 24 MB a lot.

minor tests/test_teleport_pictures.py:30: without Pillow the check prints SKIP and exits 0, which run_all.py counts as a pass. Declare RUN_ALL_MODULES = ["PIL"], as tests/test_prompt_guide_lines.py does, so the runner reports it as skipped instead.

CI is green and the branch merges cleanly onto current main. I didn't check the in-game look beyond the description's screenshot.

Thanks for this. Pictures on the Teleport list are a good idea, and reusing one texture to stay inside the UI's 16-descriptor pool is the right call. There's one real bug, though, and a repo rule the build step breaks, so I'm asking for changes before merging. **blocking** `gpu/vk/vk_menu.cpp:2280`: `read_teleport_pictures` finds the game folder with `strrchr(path, '\\')`. On Linux, `GetModuleFileNameA` (`host/posix/win32_posix.c`) returns a `/` path, so `slash` is null and the function returns before it even logs. The Teleport page then shows no pictures on Linux and the Steam Deck, with nothing in `hookshot_log.txt` to say why. `ini_path()` in the same file already does this portably: take the folder with `find_last_of("\\/")` (or loop back to the last `\\` or `/`), and log on that early return too. The Linux build isn't in the PR's testing, which is how this slipped through. **blocking** `nx2/teleport_pictures.py:19` and `nx2/make_game.py:1861`: CONTRIBUTING accepts the button prompts' packages (Pillow among them) "for `nx2/prompt_assets.py` and `nx2/prompt_draw.py` alone, at build time", and asks reviewers to flag any other use. This PR adds a new build-time Pillow use in its own script. Either move the work into `prompt_assets.py` (which is where nullifyshot's suggestion of decoding the player's own map textures would naturally live anyway), or get an explicit OK from the maintainer to widen that rule, and update CONTRIBUTING in the same PR. **should fix** The shipped pictures themselves. nullifyshot covered the alternative well (the game's own `UI/CustomTex` maps, decoded at build time from the player's dump, like the prompts), so I won't repeat it. Two things I'd add if the AI pictures stay: - The PR doesn't say which tool made them, under what terms, or whether any input was a screenshot of the game (image-to-image). All three decide whether they can be shipped under GPL-3.0-or-later. Please state their provenance (a note in `assets/teleport_pictures/` or `PROVENANCE.md`). - 2.5 MB of binary assets in the repo for a page in the Debug tools is a lasting cost. That's a maintainer call, but it should be made on purpose. **should fix** `gpu/vk/oot_teleport_pictures.h` and `vk_menu.cpp:2306`: the page always shows the child map's picture, so 16 of the 17 `*_Adult.jpg` files are never shown (only `GerudoFortress_Adult` is, and `GanonCastle` never is). They still go into `teleport_pictures.bin`: 47 × 480 × 270 × 4 bytes ≈ 24 MB in every game folder, all read into memory the first time the page opens. Either choose the picture by Link's current age (the menu already reads `link_age` for the Kid/Adult row), or drop the adult pictures that are never shown and remove them from the header. Storing the pictures compressed in the `.bin` (or loading only the chosen one) would also cut that 24 MB a lot. **minor** `tests/test_teleport_pictures.py:30`: without Pillow the check prints `SKIP` and exits 0, which `run_all.py` counts as a pass. Declare `RUN_ALL_MODULES = ["PIL"]`, as `tests/test_prompt_guide_lines.py` does, so the runner reports it as skipped instead. CI is green and the branch merges cleanly onto current main. I didn't check the in-game look beyond the description's screenshot.
Joey-Magnus (Migrated from github.com) reviewed 2026-11-11 21:25:01 +00:00
Joey-Magnus (Migrated from github.com) left a comment

Both earlier blockers still stand:

  • read_teleport_pictures uses strrchr(path, '\\'), so it returns early on Linux and Steam Deck.
  • nx2/teleport_pictures.py adds a Pillow use outside the two files CONTRIBUTING allows.

The 47 committed JPEGs (2.5 MB, AI-generated, no stated source or licence) are a maintainer call. Only 31 are shown, but all 47 expand to about 24 MB of RGBA in each game folder and load into memory when the page first opens. nullifyshot's suggestion of decoding the game's own UI/CustomTex map textures from the player's dump at build time, like the button prompts, would avoid shipping art.

Both earlier blockers still stand: - `read_teleport_pictures` uses `strrchr(path, '\\')`, so it returns early on Linux and Steam Deck. - `nx2/teleport_pictures.py` adds a Pillow use outside the two files CONTRIBUTING allows. The 47 committed JPEGs (2.5 MB, AI-generated, no stated source or licence) are a maintainer call. Only 31 are shown, but all 47 expand to about 24 MB of RGBA in each game folder and load into memory when the page first opens. nullifyshot's suggestion of decoding the game's own `UI/CustomTex` map textures from the player's dump at build time, like the button prompts, would avoid shipping art.
hckrman101 (Migrated from github.com) reviewed 2026-11-11 22:19:01 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks for this, the page is a nice idea and reusing one texture for the 16-descriptor pool is the right call. I read the code and the thread, and I agree with Joey-Magnus's two blockers, which are still open on this head: read_teleport_pictures uses strrchr(path, '\\') and so returns silently on Linux/Steam Deck (use the find_last_of("\\/") approach ini_path() has), and nx2/teleport_pictures.py is a Pillow use outside prompt_assets.py/prompt_draw.py, which CONTRIBUTING allows alone.

On the pictures, which is the question you asked me to look at: they are AI-generated JPEGs committed to the repo, so no game files are in the PR itself, and that part of CONTRIBUTING is not broken on its face. What I can't tell from the PR is the input: if any image was made from a game screenshot or art (image-to-image), the result is "made from the game" and can't be committed. The description says only "AI-generated illustrations", with no tool, terms or inputs, and the GPL-3.0-or-later licensing needs those too (Joey asked for a provenance note). Beyond that, nullifyshot's suggestion (decode the player's own UI/CustomTex map textures at build time, like the prompts) avoids committing any art at all, and also solves the Pillow-scope point if the work moves into prompt_assets.py. I'd go that way, but the shipped art, its 2.5 MB, and any widening of the Pillow rule are NotRexed's call.

Smaller things:

  • The description ends with a literal [screenshot] and no image is attached. CONTRIBUTING asks for screenshots when a change shows on screen; please attach it.
  • Not yet answered: the 16 adult pictures that are never shown (about 24 MB of RGBA per game folder, all loaded when the page opens) and the RUN_ALL_MODULES = ["PIL"] declaration for the test (Joey's points).
  • Nit: in teleport_picture, the lookup loop has no break, so the last match wins. Harmless, but a break says what it means.
  • Nit: if a picture's size differs from the texture's, teleport_picture returns null with no log line; fine for now since the builder makes them all one size, but a log would help if someone hand-edits the .bin.
  • Not verified by me (not built or run): the in-place pixel swap with WantUpdates while the previous frame is still in flight. It worked in the Windows screenshot test you describe, but it is the part I'd want checked on AMD/Intel and Linux.

CI: only the title check ran (pass); the branch is "unstable" per GitHub right now, so a rebase onto main and a full CI run are worth doing after the changes. This stays a COMMENT: the decision on the assets and the rule stays with NotRexed.

Thanks for this, the page is a nice idea and reusing one texture for the 16-descriptor pool is the right call. I read the code and the thread, and I agree with Joey-Magnus's two blockers, which are still open on this head: `read_teleport_pictures` uses `strrchr(path, '\\')` and so returns silently on Linux/Steam Deck (use the `find_last_of("\\/")` approach `ini_path()` has), and `nx2/teleport_pictures.py` is a Pillow use outside `prompt_assets.py`/`prompt_draw.py`, which CONTRIBUTING allows alone. On the pictures, which is the question you asked me to look at: they are AI-generated JPEGs committed to the repo, so no game files are in the PR itself, and that part of CONTRIBUTING is not broken on its face. What I can't tell from the PR is the input: if any image was made from a game screenshot or art (image-to-image), the result is "made from the game" and can't be committed. The description says only "AI-generated illustrations", with no tool, terms or inputs, and the GPL-3.0-or-later licensing needs those too (Joey asked for a provenance note). Beyond that, nullifyshot's suggestion (decode the player's own `UI/CustomTex` map textures at build time, like the prompts) avoids committing any art at all, and also solves the Pillow-scope point if the work moves into `prompt_assets.py`. I'd go that way, but the shipped art, its 2.5 MB, and any widening of the Pillow rule are NotRexed's call. Smaller things: - The description ends with a literal `[screenshot]` and no image is attached. CONTRIBUTING asks for screenshots when a change shows on screen; please attach it. - Not yet answered: the 16 adult pictures that are never shown (about 24 MB of RGBA per game folder, all loaded when the page opens) and the `RUN_ALL_MODULES = ["PIL"]` declaration for the test (Joey's points). - Nit: in `teleport_picture`, the lookup loop has no `break`, so the last match wins. Harmless, but a `break` says what it means. - Nit: if a picture's size differs from the texture's, `teleport_picture` returns null with no log line; fine for now since the builder makes them all one size, but a log would help if someone hand-edits the `.bin`. - Not verified by me (not built or run): the in-place pixel swap with `WantUpdates` while the previous frame is still in flight. It worked in the Windows screenshot test you describe, but it is the part I'd want checked on AMD/Intel and Linux. CI: only the title check ran (pass); the branch is "unstable" per GitHub right now, so a rebase onto main and a full CI run are worth doing after the changes. This stays a COMMENT: the decision on the assets and the rule stays with NotRexed.
samaBR85 commented 2026-11-11 23:35:35 +00:00 (Migrated from github.com)

Thanks, all three of you. I reworked the PR along your suggestions. The pictures now come from the player's own game, so the AI pictures are gone.

  • No shipped art: assets/teleport_pictures, nx2/teleport_pictures.py and the make_game.py step are removed. Nothing is decoded at build time either. The page takes its own screenshot the first time Link stays in a place, and keeps it in teleport_captures.bin in the game folder. That also answers the provenance and the 2.5 MB questions.
  • Pillow: no longer used. The new test reads only the source files.
  • Linux path: the game folder is found with find_last_of("\\/"), like ini_path(), and logged when it can't be found.
  • Adult pictures and memory: the page shows the picture for Link's current age (from link_age), else the other age's. Only places the player has visited are stored, at 480x270.
  • Smaller things: the [screenshot] placeholder is replaced by real screenshots, an unreadable picture or a size mismatch is logged, and the branch is rebased on current main.

@nullifyshot, thanks for digging into UI/CustomTex. Since those turned out to be top-down parchment maps rather than views of the place, screenshots from the player's own game fit the page better.

Thanks, all three of you. I reworked the PR along your suggestions. The pictures now come from the player's own game, so the AI pictures are gone. - **No shipped art:** `assets/teleport_pictures`, `nx2/teleport_pictures.py` and the `make_game.py` step are removed. Nothing is decoded at build time either. The page takes its own screenshot the first time Link stays in a place, and keeps it in `teleport_captures.bin` in the game folder. That also answers the provenance and the 2.5 MB questions. - **Pillow:** no longer used. The new test reads only the source files. - **Linux path:** the game folder is found with `find_last_of("\\/")`, like `ini_path()`, and logged when it can't be found. - **Adult pictures and memory:** the page shows the picture for Link's current age (from `link_age`), else the other age's. Only places the player has visited are stored, at 480x270. - **Smaller things:** the `[screenshot]` placeholder is replaced by real screenshots, an unreadable picture or a size mismatch is logged, and the branch is rebased on current main. @nullifyshot, thanks for digging into `UI/CustomTex`. Since those turned out to be top-down parchment maps rather than views of the place, screenshots from the player's own game fit the page better.
Owner

@Joey-Magnus requested changes on GitHub · 2026-11-12 13:38 UTC · original

Thanks for the rework. Taking the pictures from the player's own game is a much better fit, and both earlier blockers are fixed: game_folder_file takes the folder with find_last_of("\\/") and logs when it can't, and there's no Pillow and no shipped art. Merged into current main (51da0464), tests/run_all.py passes apart from a lift-cache check that fails on main here too. CI hasn't built this head yet (only the title check has run), so nothing has been compiled on Windows or Linux in CI.

Two things about when the pictures are taken, which I'd like fixed before merging:

blocking vk_menu.cpp:4072 (menu_store_capture), called from vk_replay.cpp:3386 inside do_present: each new picture rewrites the whole teleport_captures.bin on the replay thread, in the middle of a present. At 480x270 RGBA that's about 518 KB a map, so up to about 24 MB with all 47. The first capture also reads the whole file there (read_teleport_pictures). That's a hitch every time Link settles in a new map. Please hand the pixels to a worker (or the menu thread) for the file work, and consider writing them smaller: they're shown at a fraction of that size, and zstd is already linked.

should fix hos_main.c:1310: oot_capture_tick runs in oot_update_player_cheats for every player, Debug tools on or not. So everyone's game folder fills with screenshots, and "Teleport picture saved" shows over the game, for a page most players never open. Taking pictures only while the Debug tools are on, or from the first time the Teleport page is opened, would keep that to the people who use it.

Smaller points:

  • With HDR on, only present_native_pq is skipped. Please check that a picture taken with HDR Expanded doesn't come out washed out or clipped, since src is then not plain sRGB.
  • The help says the picture comes "the first time Link stays a few seconds". It might also say that the menu has to be closed then (menu_take_capture_request returns -1 while it's open), so someone waiting with the menu up isn't confused.
> [@Joey-Magnus](https://github.com/Joey-Magnus) requested changes on GitHub · 2026-11-12 13:38 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2371#pullrequestreview-5875382324) Thanks for the rework. Taking the pictures from the player's own game is a much better fit, and both earlier blockers are fixed: `game_folder_file` takes the folder with `find_last_of("\\/")` and logs when it can't, and there's no Pillow and no shipped art. Merged into current main (51da0464), `tests/run_all.py` passes apart from a lift-cache check that fails on main here too. CI hasn't built this head yet (only the title check has run), so nothing has been compiled on Windows or Linux in CI. Two things about when the pictures are taken, which I'd like fixed before merging: **blocking** `vk_menu.cpp:4072` (`menu_store_capture`), called from `vk_replay.cpp:3386` inside `do_present`: each new picture rewrites the whole `teleport_captures.bin` on the replay thread, in the middle of a present. At 480x270 RGBA that's about 518 KB a map, so up to about 24 MB with all 47. The first capture also reads the whole file there (`read_teleport_pictures`). That's a hitch every time Link settles in a new map. Please hand the pixels to a worker (or the menu thread) for the file work, and consider writing them smaller: they're shown at a fraction of that size, and zstd is already linked. **should fix** `hos_main.c:1310`: `oot_capture_tick` runs in `oot_update_player_cheats` for every player, Debug tools on or not. So everyone's game folder fills with screenshots, and "Teleport picture saved" shows over the game, for a page most players never open. Taking pictures only while the Debug tools are on, or from the first time the Teleport page is opened, would keep that to the people who use it. Smaller points: - With HDR on, only `present_native_pq` is skipped. Please check that a picture taken with HDR Expanded doesn't come out washed out or clipped, since `src` is then not plain sRGB. - The help says the picture comes "the first time Link stays a few seconds". It might also say that the menu has to be closed then (`menu_take_capture_request` returns -1 while it's open), so someone waiting with the menu up isn't confused. <!-- github-sync:r:5875382324 -->
Commenting is not possible because the repository is archived.
No description provided.