Skip to content

pso_offline.py: metal-tt fails on a specialized main0_z entry ("cannot find private metadata at offset '8'"): a near = 0 game whose vertex shaders declare dm_border_off gets no pipeline archive (511a0a2c) #2337

Closed

Description

@Amost-

What happened

On main 8fb5ffeb, and again on f75ac06e, the NSP script's pipeline archive step fails for Bayonetta Origins: Cereza and the Lost Demon, in all three builds I ran, and the Mac app is packed without an archive:

pso_offline: 4772 pairs: 4772 bases, 0 left to the device (function constants), left out as the host leaves them: reflection 0, mesh 0, stage inputs 0, library 0, layer 0, primitive ID 0, entry 0 (0.7 s)
pso_offline: 77 compute shaders: 77 compute pipelines, left out: reflection 0, library 0, entry 0, constants 0
pso_offline: applegpu_g14s: metal-tt, 4772 bases, 77 compute pipelines (minutes) ...
pso_offline: applegpu_g14s: batch 1/10: 1000 bases, compiling or checking cache
[RED] pso_offline: metal-tt failed for applegpu_g14s (exit 1): applegpu-nt: error: cannot find private metadata at offset '8' in '…/pso/mac-work/batches.applegpu_g14s/00001/libraries/000c64be106f9a28_bp.metallib'
air-tt: applegpu-nt command failed
air-tt: air-tt command failed

The second build (8fb5ffeb) had the pso/mac-work folder removed first and failed the same way; so did the third, on f75ac06e with its new chain 1fa63cf7c7ae (all 7,661 shaders built again). The app still runs: in a headless run (the bundle's host started directly, DM_AUDIO=0, input through DM_CTL) the host's own prebuild made the 4,772 bases in the background on the first start (80.4 s, none failed).

What fails. The game sets near = 0 (depth_mode zero), so every base takes the vertex entry main0_z. Of the 4,772 bases in the script, 492 name a specialized vertex function, all of them the main0_z entry of a pulling library (246 distinct <key>_bp), specialized with dm_border_off (constant 216 = true) as pso_offline.py does since 511a0a2. The fragment function of every one of the 4,772 bases is specialized the same way (the game's two cover pixel shaders).

One-pipeline scripts cut from the failing group, each run alone through xcrun -sdk macosx metal-tt -arch applegpu_g14s -macos-version-min 27.0 (AIR-TT 32023.922):

Script Result
the group's first base as written: vertex 000c64be106f9a28_bp main0_z specialized, fragment 0ec195a93c4d3496_b main0 specialized fails, cannot find private metadata at offset '8'
a base whose vertex function declares no constant (0079d8ba80bda973_bp main0_z), fragment specialized compiles
the first base with its vertex function named unspecialized fails, cannot lower module with unresolved function constants (as the tool's header says)
the first base with the vertex function taken from the same library's main0 entry, same specialization compiles
the first base with the vertex library cut down to main0_z alone (dmsh_side.cut(lib, {'main0'})), same specialization compiles
the same library with its two functions swapped (dmsh_side._take with keep = [1, 0]: main0_z first, main0 second), vertex main0_z specialized compiles
that swapped library, vertex main0 specialized (now the second function) fails, cannot find private metadata at offset '8'
the first base of five other libraries as written (00f8d7ff25b31345, 013e59a5d89c7323, 0289a194998883ca, 03754b2141cedb50, 04598b27e093203c, each _bp main0_z specialized) all five fail the same way
the whole first group without its 100 bases that name a specialized vertex function (900 bases) compiles, 36 s

The library reads as well-formed with the layout dmsh_side.py reads: two functions, main0 with OFFT (0, 0, 0) and main0_z with OFFT (34, 8, 17456); the private metadata section is 16 bytes, two 8-byte entries (size 4, ENDT). Both functions declare dm_border_off (pso_offline.constants). So 8 is main0_z's private-metadata offset in its OFFT, and an entry is there.

My reading of these runs: this metal-tt fails to specialize a function that is not the first one of its library. The swapped library shows it is the position and not the main0_z entry. The runs do not say which of the three things that come with being second is the cause (the function's index, its private-metadata offset or its code offset), and I tried no entry other than main0 and main0_z (main0_gs, main0_l). It would fit the tool's header, which says specializing a <key>_bp function was checked on Ocarina of Time's libraries: that profile sets no depth_mode, so its bases take main0, the first function. In this game every vertex library has main0 first and main0_z second.

If that reading holds, a game with depth_mode zero loses its archive as soon as one of its vertex shaders declares the border constant: one function metal-tt fails fails its group, and a failed group ends the whole step (sys.exit), so there is no archive at all, the compute pipelines included.

The comparison with an older main is not a clean before / after: an app built on 3b6b9803 (chain 39417042cfe8, 2026-11-08, the same AIR-TT 32023.922) got an archive of all 4,772 bases with 0 pairs left to the device, but on that chain the same libraries declared no function constant, so there was nothing to specialize. 511a0a2 is the only commit in between that touches pso_offline.py; I did not bisect.

Not checked: any other game (55 of the 159 profiles on main set depth_mode zero; whether their vertex shaders declare the constant I have not looked), the iOS architectures, another Metal toolchain.

What you expected

An archive. Two ways that might give one here, neither tried in the tool:

  • leave a pair whose specialized entry is not its library's first function to the device (rest_pairs.tsv), as pairs with other constants are: 4,280 of this game's 4,772 bases name an unspecialized vertex function and should stay in the archive (the first group's 900 such bases compile, the last row of the table; the other nine groups and the compute pipelines I have not run);
  • hand metal-tt a library in which that entry comes first (the cut and swapped rows of the table). I have not checked whether the host then finds that base, since it matches libraries by content.

I can open a PR for the first one with a test if that is the direction you want.

Game, title ID and version

Bayonetta Origins: Cereza and the Lost Demon, 0100CF5010FEC000, base game 1.0.0 (v0). Main has no profile for it yet; the build used the profile I am about to open a PR for (the NSP script's provisional profile plus depth_mode zero).

nxnative commit

8fb5ffeb plus that profile's files, uncommitted at build time (the host log says 8fb5ffeb1f09+dirty); they touch no tool. Then plain f75ac06e with the NSP script's provisional profile plus depth_mode zero.

Shader chain version

5ee7c6407a57 (on 8fb5ffeb) and 1fa63cf7c7ae (on f75ac06e)

How it was built and run

python3 scripts/build_ipa_from_nsp.py <NSP folder> -k <prod.keys> --platform mac-app --progress lines, three times (the second after removing <title ID>_build/pso/mac-work, the third on f75ac06e). All 7,661 shaders built each time, none failed.

Device and OS

MacBook Pro M2 Pro (19-core GPU, 32 GB), macOS 27.2 (26B123); Metal toolchain 27.2.47.0, metal-tt -version: AIR-TT 32023.922; archive architecture applegpu_g14s.

Settings and environment overrides

None for the build. The run that timed the host's prebuild was headless, as said above.

Logs

The failing lines of pso_offline_mac.log are quoted above (the path shortened). The one-pipeline .mtl4-json scripts hold no game data (labels, entry names, the constant and unspecialized attachment state); I can attach them. The .metallib files they name are built from the game's shaders, so I am not attaching those.

No game files

  • No game files, keys or extracted data are attached.

Activity

  1. added
    macThe Mac front end and app (dm/mac, front_mac.m)
    toolsTools and scripts (dm/tools, scripts)
    bugSomething isn't working
    on Nov 11, 2026
  2. QID-Rob commented on Nov 11, 2026

    @QID-Rob
    Contributor

    The same failure here on another Mac, GPU architecture and macOS version, with two games that have profiles on main. Both set depth_mode zero, which fits the reading above.

    Setup: MacBook Pro M4 Pro, macOS 27.0.1 (26A434), Xcode 27.2, Metal toolchain 27.2.47.0 (AIR-TT 32023.922), archive architecture applegpu_g16s. python3 scripts/build_ipa_from_nsp.py <NSP folder> -k <prod.keys> --platform mac-app --progress lines, no overrides.

    Game main Plan Result
    Pokémon Legends: Z-A 2.0.2 (0100F43008C44000, v393216) b149a1e4, chain 2afd39090b76 26,051 pairs: 26,030 bases, 0 left to the device; 125 compute pipelines fails in group 1, cannot find private metadata at offset '8' in 004a2c9a18b6e0c6_bp.metallib; app packed without an archive
    the same, pso_offline.py run by hand with --os-min 27.2 b149a1e4 the same fails the same way
    Castlevania: Belmont's Curse 1.1.0 (0100BA4026E94000, v65536) 477b4f85 9,522 pairs: 9,507 bases, 0 left to the device fails in group 1, the same message, 00424a05bd2121e9_bp.metallib; no archive
    Hollow Knight 1.5.12620 (0100633007D48000, v589824; also depth_mode zero) b149a1e4 858 bases, 0 left to the device archive built
    Mario Kart World 1.8.0 (0400C3F00006E000, v917504; no depth_mode) b149a1e4 9,591 bases, 0 left to the device; 728 compute pipelines archive built, 744 MB

    The first direction proposed above, on Z-A's libraries. With every pair whose pulling vertex function declares a constant left to the device (one line in plan): 21,685 bases and 4,345 pairs in rest_pairs.tsv; all 30 groups and the 125 compute pipelines compile and merge, 1,637 MB. So for this game the other groups and the compute pipelines are fine once those pairs are out.

    The narrower rule the issue describes, a pair left out only when an entry it would specialize is not the first function of its library (vf[ventry] and next(iter(vf)) != ventry, the same for the pixel function, and for plan_compute), plans 21,684 bases and 4,346 left-out pairs for Z-A (--script-only); I have not compiled that plan separately.

    Not checked: that the host finds these bases and builds only the rest_pairs.tsv pairs on its first start (none of these apps has been run yet), the iOS architectures, another Metal toolchain.

    No game files, keys or extracted data are attached.

  3. seedds commented on Nov 12, 2026

    @seedds
    Owner

    Fixed on main in c761e6a: a specialized function that is not its library's first (main0_z of a _bp for depth_mode zero) is now compiled from a library of its own, so the archive is built and the host finds those bases through the whole library. Thanks for the clear bisection to the function's position, and to QID-Rob for the Z-A and Belmont's Curse confirmation. Not yet checked on main: a full app build of Cereza, Z-A or Belmont's Curse; a rebuild there should give the archive with no pairs lost.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggame: 0100CF5010FEC000A gamemacThe Mac front end and app (dm/mac, front_mac.m)toolsTools and scripts (dm/tools, scripts)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions