Skip to content

game: Nintendo Switch 2 Welcome Tour plays on the Mac - #1425

Open
JakePre wants to merge 2 commits into
seedds:mainfrom
JakePre:welcome-tour-040057C02159A000
Open

JakePre wants to merge 2 commits into
seedds:mainfrom
JakePre:welcome-tour-040057C02159A000

Conversation

@JakePre

@JakePre JakePre commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Two commits, in the commit-message form of docs/agents/06-dev-loop.md § 8: host: answer two Switch 2 SDK calls Welcome Tour stops on (the two hooks, with their reference entries naming the game as "no profile yet"), then game: Nintendo Switch 2 Welcome Tour plays on the Mac (the profile, its note, the lists, and the links to its note). Each passes the host tests and test_docs on its own.

Nintendo Switch 2 Welcome Tour (040057C02159A000, update v262144, SDK 20.5.29, NintendoWare's Bezel Engine) is a new game. It now plays on the Mac past its intro video, through the tour, its menus and exhibits, at about 60 fps. It stopped, in turn, on these:

  • vi::InitializeWithVrrLfcEnabled at boot: main answers it since 11825d2 (Resident Evil 4), so this PR no longer adds it; the game commit adds Welcome Tour to that entry's Found on.
  • Bezel's allocator at boot, out of room in profile_guess.py's 5 GB block (a 4.4 GB alias region): the profile takes the Switch 2's 9 GB layout from Splatoon Raiders and Star Fox (mem_reserve 0x550000000, mem_alias 0x400000000, app_pool 0x240000000). The game takes Bezel's virtual-memory path (Bezel Engine's two memory setups).
  • hid::GetJcmsState each frame, and GetMousePointerSpeedScale: main has both since the Joy-Con 2 mouse sensor (413e880), so this PR no longer adds them.
  • hid::SetSceneIdForPrivateApp as each scene module starts: the SDK's own connects to the hid service. Nothing reads it, so it's logged.
  • oe::QueryApplicationGpuTimeSpan after the first frame (hle_oe.c, main+0x30a83c): the SDK's own locks the gs graphics server's context, as SetGraphicsFrameLengthMode does. It now returns success and a fixed 8 ms, under a 60 fps frame: the host has no GPU time per application, so a game that sizes its picture by the span stays at full size, and a span that is not 0 takes no divide or "not measured" path in some other game's controller (DeathKhan's suggestion below). The game reads the span only on success. Welcome Tour ran the same with 0 and with 8 ms on the Mac: title screen, 16.67 ms frames once loaded, no crash.

Files added:

  • the profile (dm/games/040057C02159A000): from profile_guess.py's, with the memory above, the game's name, and v262144 as a placeholder display version (no control data gave one)
  • its note, a games-table row and a line in README's Switch 2 list
  • reference entries for the new host calls (SetSceneIdForPrivateApp now has an entry of its own); docs_symptoms.py was run

Shaders: 6,766 (the tables were split again on 7 November: main's mesh reader now finds 7 mesh shaders and a task shader of forward_plus.bfsha, with 14 more pairs). On chain 3df44a5a4e0c (9 November), 29 fail to translate (28 on Ampere instruction 0x3ad, one AST form), and 9 lose bindless accesses (429 deleted). The note lists them as open, with the ray tracing NVN calls (unimplemented, [RED] once each at boot).

Controllers. The exhibits use the Joy-Con 2's mouse sensor and HD rumble, which are on main now (#1346, #1349, #1363 and #1426). The note says so.

Checked

  • Tests (rebased on main 7479adf, 9 November):

    • make -C dm/host/tests GAME=040057C02159A000 run: all pass.
    • dm/tools/tests (test_docs.py, test_game_profile.py, test_settings.py, test_launcher_settings.py): pass.
    • lint_c.py tidy (LLVM 23.1.5): 0 findings above the baseline.
    • make -k -C dm/gfx/tests GAME=040057C02159A000 run: everything passes but t_render_counter_overlap (1 or 2 of 42 checks, CopyBuffer0) and t_render_enable_queue (9 of 46). Both fail the same way on plain main 7479adf on this M1 Pro, and this PR touches no NVN code.
  • Games run: Nintendo Switch 2 Welcome Tour, v262144: about 10 minutes from boot through the intro video to the tour and its exhibits.

    • Frame time p50 16.7 ms, p95 20 ms over 300 frames.
    • The exhibits ran with the controller PRs on top, with a left and a right Joy-Con 2.
  • Devices and OS: M1 Pro, macOS 27.2.

  • regress.py: I didn't run check.

    • impact names Hades, Breath of the Wild, Pokémon Scarlet and Tears of the Kingdom, through vi::Initialize. That's because the new vi hook sits next to it. vi::Initialize itself is unchanged.
    • The other host changes are hooks no other game's manifest calls.
    • This Mac has no game set up for the harness: regress.py list finds none, since my games are archived off it.

    If you want check on any of these, tell me which.

  • Not checked: an IPA (the 16 GB alias region is over iOS's limit, as for Splatoon Raiders).

Docs

  • The game note, its games-table row and README's Switch 2 list; reference entries for the new calls, and docs_symptoms.py was run
  • No chain tool changed
  • No game files, keys or extracted game data. The profile's tables are shader keys and pairs, as other profiles have.

🤖 Generated with Claude Code

@github-actions github-actions Bot added host The Horizon host: SVCs, services, threads, memory (dm/host) docs Documentation game: 040057C02159A000 A game profile new game Adds a game profile that main does not have yet labels Nov 6, 2026
@JakePre
JakePre force-pushed the welcome-tour-040057C02159A000 branch 8 times, most recently from a93402e to c9370c9 Compare November 9, 2026 03:29
@JakePre JakePre changed the title Nintendo Switch 2 Welcome Tour: its profile and note, and the four host calls it stopped on Nintendo Switch 2 Welcome Tour: its profile and note, and the three host calls it stopped on Nov 9, 2026
@JakePre
JakePre force-pushed the welcome-tour-040057C02159A000 branch 2 times, most recently from 8024ba8 to 2a770ed Compare November 10, 2026 16:14
DeathKhan added a commit to DeathKhan/droidplay-core that referenced this pull request Nov 10, 2026
…plicationGpuTimeSpan: seedds/nxnative#1425 adds them

From the second review of enjoyerofslop#158: seedds/nxnative#1425 (Welcome Tour's
profile, open) takes over the same three calls with the same C names,
so once it lands they would stop the next merge from upstream, and the
profile comes with that PR anyway. Their code, comments and docs go:
the services.md entry keeps the refresh rates, the 120 Hz capability,
GL / GR and native memory (with its never-called note) and names #1425
for the rest; the frame-rate.md line and the DM_DYNAMIC_RESOLUTION row
of env_switches.tsv are main's again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DeathKhan added a commit to DeathKhan/nxnative that referenced this pull request Nov 10, 2026
…tionGpuTimeSpan are left to seedds#1425, which takes them over with the same C names: with both, the second to land would not build (a redefinition, or a duplicate in the forced-takeover list). Their code, the GPU time's weak stand-ins, the frame-rate.md line and the DM_DYNAMIC_RESOLUTION row's addition go, and t_oe keeps only its native memory check; the services.md entry keeps the refresh rates, the 120 Hz capability, GL / GR and native memory (only seen as imports, never called) and names seedds#1425 for the rest. As DroidPlay Core's review asked of enjoyerofslop/droidplay-core#158

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DeathKhan

Copy link
Copy Markdown

A thought on QueryApplicationGpuTimeSpan, from bringing Welcome Tour up on an Android port (DeathKhan/nx2android, also offered to DroidPlay Core as enjoyerofslop/droidplay-core#158):

  • What we answer: a fixed time, 8 ms, by default. The host's measured GPU time is answered only when the player turns Dynamic resolution on (DM_DYNAMIC_RESOLUTION=1, as for the games with the DYNAMIC_RESOLUTION capability). There we take the newest completed frame up to 8 back from mtl_gpu_ms, else 8 ms.
  • Why: the game reads the span to drive its dynamic resolution. The host's GPU time is what held Ocarina of Time's controller at its floor (frame-rate.md, "Dynamic resolution at its floor while the GPU has time to spare"). A fixed answer under the frame keeps the game at full size, as Dynamic resolution off does elsewhere.
  • Compared with 0: 0 reads as "free" too, but a fixed non-zero span is a little less likely to trip a divide or an "unmeasured" path in some other game's controller.
  • What we ran: Welcome Tour ran to its title screen on the Odin 3 with what amounted to a fixed 8 ms. The measured reading itself hasn't run with a game.

We've dropped our own takeover of this call, InitializeWithVrrLfcEnabled and SetSceneIdForPrivateApp from #2246, so this PR keeps them. One more small point from DroidPlay Core's review: returning a Result of 0 from InitializeWithVrrLfcEnabled and SetSceneIdForPrivateApp is safe whether the real functions return void or a Result.

@JakePre
JakePre force-pushed the welcome-tour-040057C02159A000 branch from 2a770ed to 542ef26 Compare November 10, 2026 23:43
@JakePre

JakePre commented Nov 10, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, switched: the PR now answers a fixed 8 ms (542ef26), for the reason you gave, that a span that isn't 0 is less likely to send another game's controller down a divide or "not measured" path.

What we ran on the Mac (M1 Pro), the same Welcome Tour build with only that line changed: both 0 and 8 ms reach the title screen and settle at 16.67 ms frames (p95 about 17.1 ms), with no crash and the same warnings in the log. The game asks about 5 times a frame, with spans of roughly 0.3–1 M ticks once it's running. Neither run went past the title screen, so whether the game resizes anything in the tour itself is untested.

The measured reading behind Dynamic resolution sounds right as a follow-up of its own; this PR keeps the fixed answer only.

enjoyerofslop pushed a commit to enjoyerofslop/droidplay-core that referenced this pull request Nov 11, 2026
…: VRR init, refresh rates, GL / GR buttons, a frame's GPU time, native memory

Nintendo Switch 2 Welcome Tour stops at start-up on SDK calls that talk
to a system service (vi, hid, gs) whose session the host never opens:
a null read in nn::os::SdkMutexType::Lock under nn::vi::sdk::Initialize
and under nn::gs::sdk::QueryApplicationGpuTimeSpan, an sf abort (0xe15)
under the hid calls.

- hle_vi.c: InitializeWithVrrLfcEnabled is Initialize's no-op (no VRR on
  the host); QueryDisplayCurrentRefreshRate answers 60 Hz, the display's
  own, as one 32-bit Hz word and a bool (two 64-bit words overwrote the
  caller's saved frame pointer); QueryDisplayAvailableRefreshRates lists
  60 Hz alone; SetDisplay120HzRefreshRateCapability is logged.
- hle_hid.c: GetGlGrButtonConfigState answers no GL / GR assigned;
  SetSceneIdForPrivateApp does nothing.
- hle_oe.c: QueryApplicationGpuTimeSpan answers the GPU time of the
  newest frame whose GPU work has completed (mtl_gpu_ms, up to 8 frames
  back, as the frame step reads it), else 8 ms; gs::sdk::CreateNativeMemory
  gives the range's address as its handle, DestroyNativeMemory nothing.
  Weak stand-ins of mtl_gpu_ms and dm_nvn_frame_count keep host unit
  tests without a graphics backend linking.

From DeathKhan/nx2android cbeb5ccd6 (its hle_vi.c and hle_hid.c hunks)
and eea4efef9 (its hle_oe.c hunk). The GPU time is written anew: the
fork read mtl_gpu_ms(0), frame 0's, so it answered 8 ms nearly always.
A reference entry in services.md.

Also in this pull request:
- QueryApplicationGpuTimeSpan answers a fixed 8 ms unless DM_DYNAMIC_RESOLUTION=1; VRR init and the scene id return a Result of 0; CreateNativeMemory is noted as never called
- Drop InitializeWithVrrLfcEnabled, SetSceneIdForPrivateApp and QueryApplicationGpuTimeSpan: seedds/nxnative#1425 adds them

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JakePre
JakePre force-pushed the welcome-tour-040057C02159A000 branch from 542ef26 to 3ed1f33 Compare November 12, 2026 23:18
@JakePre JakePre changed the title Nintendo Switch 2 Welcome Tour: its profile and note, and the three host calls it stopped on game: Nintendo Switch 2 Welcome Tour plays on the Mac Nov 12, 2026
Nintendo Switch 2 Welcome Tour (040057C02159A000, update v262144, SDK 20.5.29, NintendoWare's Bezel Engine; its profile is the next commit) stopped on two SDK calls whose SDK version reaches a service the host does not have:
- hid::SetSceneIdForPrivateApp as each scene module starts: the SDK's own connects to the hid service. Nothing reads it, so it is logged (hle_hid.c).
- oe::QueryApplicationGpuTimeSpan after the first frame (main+0x30a83c): the SDK's own locks the gs graphics server's context, as SetGraphicsFrameLengthMode does. Success and a fixed 8 ms, under a 60 fps frame: the host has no GPU time per application, a game that sizes its picture by the span stays at full size, and a span that is not 0 takes no divide or "not measured" path; the game reads the span only on success (hle_oe.c). Welcome Tour runs the same with 0 and with 8 ms.

Each has its entry in docs/reference/services.md and a line in docs/agents/symptoms.md. It also stopped at boot on vi::InitializeWithVrrLfcEnabled, which main answers since 11825d2 (Resident Evil 4); the next commit adds Welcome Tour to that entry.

Checked on an M1 Pro, macOS 27.2, with the next commit's profile: the game plays past its intro video to the tour, its menus and exhibits, at about 60 fps. The host tests and test_docs pass at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Switch 2 game (040057C02159A000, update v262144, SDK 20.5.29, NintendoWare's Bezel Engine). With the previous commit's two host calls and main's vi::InitializeWithVrrLfcEnabled (11825d2) it plays on the Mac past its intro video to the tour, its menus and exhibits, at about 60 fps.

Besides those calls, it stopped on:
- Bezel's allocator at boot, out of room in the guessed 5 GB block (a 4.4 GB alias region): the profile takes the Switch 2's 9 GB layout of Splatoon Raiders and Star Fox (mem_reserve 0x550000000, mem_alias 0x400000000, app_pool 0x240000000). The game takes Bezel's virtual-memory path (docs/reference/memory.md, Bezel Engine's two memory setups).
- hid::GetJcmsState each frame, and GetMousePointerSpeedScale: main has them since the Joy-Con 2 mouse sensor (413e880).

Profile: dm/games/040057C02159A000 (from profile_guess.py's, with the memory above, its name and the build's display version as the placeholder v262144), its note, a games-table row and a line in README's Switch 2 list; the two services.md entries of the previous commit link its note, and it is added to the vi::InitializeWithVrrLfcEnabled entry's Found on. Shaders: 6,766 (6,758 at first; split again on 7 November, when main's mesh reader found 7 mesh shaders and a task shader of forward_plus.bfsha); on chain 3df44a5a4e0c, 29 fail to translate (28 on Ampere instruction 0x3ad, one AST form) and 9 lose bindless accesses; on chain 8d9afacc48e2, 28. Open: the ray tracing NVN calls are unimplemented ([RED] once each at boot), a few H.264 packets are refused in the videos.

Checked on an M1 Pro, macOS 27.2: about 10 minutes from boot to the tour and its exhibits, frame time p50 16.7 ms and p95 20 ms over 300 frames. Its exhibits use the Joy-Con 2's mouse sensor and HD rumble, which main has with the Switch 2 controllers (issue seedds#1267).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JakePre
JakePre force-pushed the welcome-tour-040057C02159A000 branch from 3ed1f33 to b647af8 Compare November 14, 2026 00:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation game: 040057C02159A000 A game profile host The Horizon host: SVCs, services, threads, memory (dm/host) new game Adds a game profile that main does not have yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants