Skip to content

feat(libs): the Hmd2 reprojection session - #7658

Merged
oneandonlydean merged 9 commits into
boykopovar:mainfrom
primesoul-web:feat/export-leftovers-and-error-paths
Nov 2, 2026
Merged

oneandonlydean merged 9 commits into
boykopovar:mainfrom
primesoul-web:feat/export-leftovers-and-error-paths

Conversation

@primesoul-web

@primesoul-web primesoul-web commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

What

the Hmd2 reprojection session on its own, split from the earlier revision of this pull request per its review: libSceHmd2 16/153 -> 22/153 on main 926f7dd (26.33%, 9171/34833 -> 26.35%, 9177/34833, +6, no regressions; the denominator grew with the firmware-database declaration waves since the pull request was opened, the implemented delta is unchanged).

sceHmd2ReprojectionQueryBufferSizeAlign answers the console's requirement as measured in the discussion below (firmware 13.40 and 13.60): 0x100828 bytes at 0x100 while the process's positional setting is unset, and 0x1103000 + n * 0x1000000 at 0x1000 once sceHmd2ReprojectionSetAllowPositionalReprojection has recorded a first argument of n = 1 to 5; the setting is process-wide without an initialization, answers 0 for 0 to 5 and PARAMETER_INVALID for anything else with the previous setting kept, and the query never reads its own arguments.

sceHmd2ReprojectionInitialize answers PARAMETER_NULL for a null param or buffer, throws for an auxiliary-memory buffer and writes the 16-byte state (output mode, timing in microseconds) into the guest's work buffer with the timing the param carries or the 3000 us default; a second initialization answers REPROJECTION_ALREADY_INITIALIZED. The firmware first reads the process's app info and answers 0x8A72001F before its arguments when that fails or the info's nonzero attribute field lacks bit 0x400 (measured, firmware 13.40 — the answer MLB The Show 25 gets on #8645); the emulator's app info does not model the attribute fields, so every caller passes the gate, and the debt entry records the difference. The signatures follow the ABI restored on main (the firmware-database sweep of 2026-10-21): a two-argument Initialize whose reserved second parameter is ignored, a single-argument SetAllowPositionalReprojection, and the sweep's ReprojectionInitializeParameter field names at the same offsets this PR's 72-byte validation pinned — the console ignores the extra arguments, so the measured table is unchanged. sceHmd2ReprojectionSetTiming answers NOT_INITIALIZED without a session, PARAMETER_INVALID below 2000 us and above 7000 us and otherwise rounds the value up to the next hundred microseconds, answering 0 at once when the rounded value equals the current timing (measured, firmware 13.40 — the earlier revision threw out of range); the module forwards sceVideoOutSetDisplayEventPosition's result when the timing changes, which the debt entry holds until VideoOut grows that export. sceHmd2ReprojectionEnableVrMode answers NOT_INITIALIZED without a session, VIDEO_OUT_ERROR_INVALID_VALUE for an output mode other than 0x2000C and 0x2000F and otherwise VIDEO_OUT_ERROR_NO_DEVICE.

sceHmd2ReprojectionTerminate ends the session (the memory point in the review): it clears the record under the reprojection mutex, so the gated exports answer NOT_INITIALIZED again and a new initialization is accepted. The teardown follows the session model and is not a console measurement, and the positional setting survives it. The record is a borrow of the guest's work buffer: the guest keeps the buffer for the session's lifetime and frees it after terminating, every read and write of the record happens inside an export call under the mutex, and a guest that frees the buffer while the session is live corrupts its own heap exactly as it would on hardware. The debt entry carries the full wording.

T3cm0's #7690 (the console display and mirroring sizes) landed in the same four files and is carried unchanged in 59f2d3339's resolution: its four implementations, its size-query test and its debt bullet are main's, and the stale "display and see-through stay unimplemented" sentence is dropped from the Silent stubs bullet since the merged tree implements them. His #7798, #7805 and #7826 add other Hmd2 exports in the same files; the hunks do not meet, and whichever lands second rebases.

Tested

GuestHmd2 walks the measured table and the session on top of #7760's without-headset tests, #7690's console size-query session and the ABI-restored signatures carried in 4cf2a20 (the pre-session block and the session walk call the new forms; nikowolf123's two mutations rebuilt as negative controls still abort on the carried head): the rejected settings keep the previous answer, n = 1, 2 and 5 answer 0x2103000 / 0x3103000 / 0x6103000 at 0x1000, n = 0 restores 0x100828 / 0x100, the timing answers PARAMETER_INVALID below 2000 and above 7000 with the recorded setting kept, 2001 rounds to 2100, a repeat of 2100 answers 0 at once, the range ends 2000 and 7000 succeed, and the terminate teardown answers NOT_INITIALIZED for the gated exports and accepts a re-initialization with the default timing. The measured-timing rows and the app-info gate follow frankea's firmware 13.40 module read and GT7 session measurements (2026-10-26), carried in head 24d4edc together with main's canonical-headers refactor; the negative control against main's marker Export.cpp still aborts at the session exports. oneandonlydean's review asks are addressed in head 1f774a2: the guest test keeps to the session's real behaviour and argument validation (the auxiliary-memory throw is no longer pinned), and the debt bullets name the measured session's title id instead of its name. The branch carried current main's Hmd2 debt-file restructure in head 3226e99 with both sides kept, and GuestHmd2 plus the negative control were re-verified on the carried tree (the carry notice of 2026-11-01).

Checklist

  • AI-assisted: yes
  • Depends on: none

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Progress report (e0caf13 → 3226e99)

📈 System libraries: 26.27% (+0.02%, +6 implemented functions)

✅ 6 implemented
Library Name
libSceHmd2 sceHmd2ReprojectionEnableVrMode
libSceHmd2 sceHmd2ReprojectionInitialize
libSceHmd2 sceHmd2ReprojectionQueryBufferSizeAlign
libSceHmd2 sceHmd2ReprojectionSetAllowPositionalReprojection
libSceHmd2 sceHmd2ReprojectionSetTiming
libSceHmd2 sceHmd2ReprojectionTerminate

Changed libraries (implemented exports)

Library Before After Change
libSceHmd2 16/153 (10.46%) 22/153 (14.38%) +3.92%

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 8645 core/libs/prx/libSceHmd2/Export.cpp
core/libs/prx/libSceHmd2/Hmd2.hpp
core/libs/tests/GuestHmd2.cpp
docs/dev/debt/libSceHmd2.md
PR 12104 docs/dev/debt/libSceHmd2.md
PR 14212 docs/dev/debt/libSceHmd2.md

@LeandroLP23

Copy link
Copy Markdown
Contributor

Two existing tests fail with this PR, on Windows and probably elsewhere. Their expected messages still have the not implemented suffix that NotImplemented_nid_no_patch adds. Radeon 860M (RDNA 3.5, driver 32.0.13058.8001), Windows 11 26300, WinLibs GCC 15.2.0 r7, Release, BUILD_TESTING=ON.

With this PR merged alone onto main 8a456334f, guest_sysarch and guest_offline_imports abort (0xc0000409), 2 runs of 2. Without it they pass.

  • guest_offline_imports (GuestOfflineImports.cpp:43) expects "sceHttpSetCookieEnabled not implemented". sceHttpSetCookieEnabled now throws "sceHttpSetCookieEnabled: the offline backend has no cookie engine". That check isn't guarded by host, so it should fail on Linux too.
  • guest_sysarch: the two new messages for operations 129 and 7 match (I printed what Thrown returns). On hosts other than x86-64 Linux, though, the #else branch of the test still expects "sysarch: segment bases on this host not implemented", while sysarch now throws "sysarch: segment bases on this host". So it fails on Windows and macOS and passes on Linux.

Separately, AGENTS.md asks for NotImplemented_nid_no_patch(__func__) for exports that can't be implemented. Several of these changes replace it with a plain throw std::runtime_error, which a reviewer may want to keep as it was.

AI-assisted

@T3cm0

T3cm0 commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Two console datapoints for the Hmd2 part, measured on a retail PS5 (firmware 13.60, no headset) by loading libSceHmd2.sprx in a homebrew payload and calling the exports directly:

  • sceHmd2ReprojectionQueryBufferSizeAlign returns size 0x100828 and alignment 0x100 (RAX:RDX), the same for every argument tried: the (2, 0xAF, 1) that Horizon Call of the Mountain (PPSA05666) passes, and changes to each of the three. The {16, 8} from KytyPS5's state struct is not what the console answers, so a title allocates a 16-byte reprojection buffer here where the console asks for ~1 MiB. The other size queries are also fixed values: QueryDisplayBufferSizeAlign 0x6000000/0x10000, QuerySeeThroughBufferSizeAlign 0x1106000/0x100, GetMirroringDisplayBufferSizeAlign 0x5FA0000/0x10000, GetMirroringWorkMemorySizeAlign 0x4000/0x100.
  • Your ReprojectionInitializeParam layout matches what PPSA05666 passes, captured under AnyPS5: reprojection and display buffer pointers at +0 and +8, 0x100 at +16, 0x1FFF at +20, 4 and 4 at +24/+28, 2 at +32, a null see-through buffer at +40 and 0 at +48 (default timing). It then calls sceHmd2ReprojectionEnableVrMode(0x2000F), so the two output modes are right too.

I had the five size queries ready as a follow-up to #7463 with those console values; happy to drop QueryBufferSizeAlign from it if you take the measured value here, or to leave it to you entirely. Whatever is easier.

@frankea

frankea commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Datapoint from Gran Turismo 7 (PPSA01317, 01.000.001 base package) on main 324e0e4 + this PR: the sceHmd2ReprojectionQueryBufferSizeAlign not implemented stop that every GT7 run on main has hit since #7323 is gone, 3 of 3. The title takes the {16, 8} answer and asks the next size query 0x48 bytes later in the same function, which this PR leaves throwing; with #7690 on top it passes all four and goes on to sceVrTracker2Initialize (#7463's remaining throw):

Tree Stop Where Wall-clock (3 runs)
main 48d61f5 (10/18 afternoon, reference) sceHmd2ReprojectionQueryBufferSizeAlign not implemented, SIGABRT libSceHmd2.prx+0x1ba0 under app.exe+0x952eff 12 s, 5 s, 5 s
main 324e0e4 + this PR (head b2f28a7) sceHmd2ReprojectionQueryDisplayBufferSizeAlign not implemented, SIGABRT libSceHmd2.prx+0x1c90 under app.exe+0x952f47 6.7 s, 4.8 s, 4.8 s
main 324e0e4 + this PR + #7690 (head e191a13, which carries #7463) sceVrTracker2Initialize not implemented, SIGABRT libSceVrTracker2.prx+0x1650 under app.exe+0x95310a 9.5 s (cold shader cache), 4.9 s, 4.9 s

All three stops are on the same module-load thread (a libSceSysmodule.prx+0x347e frame is on every stack), before the first frame: the window opens and the process aborts 5 to 10 s in. stdout is 72 lines with this PR and 73 with #7690 added (the extra line is sceSysmoduleLoadModule unknown id: 299, loaded between the Hmd2 queries and the tracker). Nothing in the title's behaviour distinguishes {16, 8} from the console's {0x100828, 0x100} yet; it never reaches sceHmd2ReprojectionInitialize with that buffer because the tracker throws first.

Horizon Call of the Mountain (PPSA05666, v01.004.011) on the same main + this PR + #7690 tree: 3 of 3 stop on sceVrTracker2Initialize not implemented (108 s cold cache, 10 s, 11 s), exactly as with #7463 alone; no sceHmd2Reprojection* export is called before that abort, so this PR's Hmd2 session is not exercised by that title yet.

Two notes on the tree:

stderr, run 2 with this PR alone (abort block)
[libc] unhandled exception 'St13runtime_error' thrown from 00007fff70a2756c
terminate called after throwing an instance of 'std::runtime_error'
  what():  sceHmd2ReprojectionQueryDisplayBufferSizeAlign not implemented
FATAL: abort() (SIGABRT) on thread 8292
    #0 0x00007fff70962c98 libc.prx+0x12c98
    #1 0x00007fff70a43b31 libc.prx+0xf3b31
    #2 0x000000000060f750
  stack return address candidates:
    [rsp+0x8] 0x00007fff70962eb5 libc.prx+0x12eb5
    [rsp+0x38] 0x00007fffd0d62139 ucrtbase.dll+0xc2139
    [rsp+0x98] 0x00007fff70a2756c libc.prx+0xd756c
    [rsp+0xb8] 0x00007fffd0d45261 ucrtbase.dll+0xa5261
    [rsp+0xe0] 0x00007fff70a2756c libc.prx+0xd756c
    [rsp+0xe8] 0x00007fff709bebf6 libc.prx+0x6ebf6
    [rsp+0x138] 0x00007fff709b9c7b libc.prx+0x69c7b
    [rsp+0x140] 0x00007fff70a2756c libc.prx+0xd756c
    [rsp+0x158] 0x00007fff70a2756c libc.prx+0xd756c
    [rsp+0x168] 0x00007fff709c8203 libc.prx+0x78203
    [rsp+0x180] 0x00007fff70a2756c libc.prx+0xd756c
    [rsp+0x268] 0x00007fff709c8c1e libc.prx+0x78c1e
    [rsp+0x298] 0x00007fff70a2756c libc.prx+0xd756c
    [rsp+0x328] 0x00007fff980c1c90 libSceHmd2.prx+0x1c90
    [rsp+0x358] 0x0000000140952f47 app.exe+0x952f47
    [rsp+0x428] 0x000000014094e714 app.exe+0x94e714
    [rsp+0x488] 0x00007fff70bff1bc libkernel.prx+0x4f1bc
    [rsp+0x498] 0x0000000140a86552 app.exe+0xa86552
    [rsp+0x4e8] 0x00007fff70c0c8cc libkernel.prx+0x5c8cc
    [rsp+0x548] 0x0000000140bde714 app.exe+0xbde714
    [rsp+0x578] 0x00007fffa2886aca libc.prx.guest.prx+0x56aca
    [rsp+0x588] 0x0000000140c54a0f app.exe+0xc54a0f
    [rsp+0x5b8] 0x0000000140c54b87 app.exe+0xc54b87
    [rsp+0x608] 0x0000000140c54623 app.exe+0xc54623
    [rsp+0x668] 0x00007fffa476347e libSceSysmodule.prx+0x347e
    [rsp+0x6b8] 0x000000014092b706 app.exe+0x92b706
    [rsp+0x6e8] 0x000000014092bf61 app.exe+0x92bf61
    [rsp+0x728] 0x00000001416d34af app.exe+0x16d34af
    [rsp+0x730] 0x0000000149072da4 app.exe+0x9072da4
    [rsp+0x748] 0x0000000140d12d7f app.exe+0xd12d7f
stderr, run 3 with this PR + #7690 (abort block)
[libc] unhandled exception 'St13runtime_error' thrown from 00007fff6d06756c
terminate called after throwing an instance of 'std::runtime_error'
  what():  sceVrTracker2Initialize not implemented
FATAL: abort() (SIGABRT) on thread 15624
    #0 0x00007fff6cfa2c98 libc.prx+0x12c98
    #1 0x00007fff6d083b31 libc.prx+0xf3b31
    #2 0x000000000060f750
  stack return address candidates:
    [rsp+0x8] 0x00007fff6cfa2eb5 libc.prx+0x12eb5
    [rsp+0x38] 0x00007fffd0d62139 ucrtbase.dll+0xc2139
    [rsp+0x98] 0x00007fff6d06756c libc.prx+0xd756c
    [rsp+0xb8] 0x00007fffd0d45261 ucrtbase.dll+0xa5261
    [rsp+0xe0] 0x00007fff6d06756c libc.prx+0xd756c
    [rsp+0xe8] 0x00007fff6cffebf6 libc.prx+0x6ebf6
    [rsp+0x138] 0x00007fff6cff9c7b libc.prx+0x69c7b
    [rsp+0x140] 0x00007fff6d06756c libc.prx+0xd756c
    [rsp+0x158] 0x00007fff6d06756c libc.prx+0xd756c
    [rsp+0x168] 0x00007fff6d008203 libc.prx+0x78203
    [rsp+0x180] 0x00007fff6d06756c libc.prx+0xd756c
    [rsp+0x268] 0x00007fff6d008c1e libc.prx+0x78c1e
    [rsp+0x298] 0x00007fff6d06756c libc.prx+0xd756c
    [rsp+0x328] 0x00007fff6a1e1650 libSceVrTracker2.prx+0x1650
    [rsp+0x358] 0x000000014095310a app.exe+0x95310a
    [rsp+0x428] 0x000000014094e714 app.exe+0x94e714
    [rsp+0x488] 0x00007fff6f3ff1bc libkernel.prx+0x4f1bc
    [rsp+0x498] 0x0000000140a86552 app.exe+0xa86552
    [rsp+0x4e8] 0x00007fff6f40c8cc libkernel.prx+0x5c8cc
    [rsp+0x548] 0x0000000140bde714 app.exe+0xbde714
    [rsp+0x578] 0x00007fff70ce6aca libc.prx.guest.prx+0x56aca
    [rsp+0x588] 0x0000000140c54a0f app.exe+0xc54a0f
    [rsp+0x5b8] 0x0000000140c54b87 app.exe+0xc54b87
    [rsp+0x608] 0x0000000140c54623 app.exe+0xc54623
    [rsp+0x668] 0x00007fffa1fc347e libSceSysmodule.prx+0x347e
    [rsp+0x6b8] 0x000000014092b706 app.exe+0x92b706
    [rsp+0x6e8] 0x000000014092bf61 app.exe+0x92bf61
    [rsp+0x728] 0x00000001416d34af app.exe+0x16d34af
    [rsp+0x730] 0x0000000149072da4 app.exe+0x9072da4
    [rsp+0x748] 0x0000000140d12d7f app.exe+0xd12d7f

Tested. Windows 11 (26200), RTX 4090 (NVIDIA 616.92), WinLibs GCC 15.2.0 r7 (UCRT), Release libs target, ccache, built in one worktree; app.exe is the relink from main 31cbb55, only libs/ is swapped. Desktop session, no input, three runs per tree in a row, 600 s cap, box otherwise idle. Not a review of the console values.

AI-assisted: yes (Claude Code).

@omar-akermi

Copy link
Copy Markdown
Contributor

@primesoul-web We are preparing a libSceHttp transport contribution in omar-akermi#1 and are coordinating the shared sceHttpSetCookieEnabled change before opening it upstream.

Your change preserves the offline backend's rejection of cookie enabling. Our branch replaces that offline backend with a per-context curl cookie engine, including inherited enable/disable settings and cookie receive callbacks, so it supersedes this particular error path. We are currently integrating and retesting against upstream ca13a56; Windows, native Linux and ASAN results from the previous integrated revision are linked in the fork draft.

Proposed merge order: #7658 first, then the HTTP transport. If #7658 lands first, we will merge its resulting main revision and retain the real cookie implementation when resolving this hunk. If the transport lands first, #7658 should drop its cookie-only hunk rather than restore the offline throw. The transport has no functional dependency on the other exports in this PR.

Does that order work for you? No agreement is assumed until you reply; we will record the agreed order and any required dependency in the upstream PR checklist.

@primesoul-web

Copy link
Copy Markdown
Contributor Author

Thank you for the Windows run and the precise reproductions — both failures are fixed in 93bbeb3.

  • guest_offline_imports now expects the offline backend's message: sceHttpSetCookieEnabled: the offline backend has no cookie engine.
  • guest_sysarch's #else branch now expects sysarch: segment bases on this host, matching the reworded throw; the x86-64 Linux branch was already updated by the first revision and the other-host branch was missed, exactly as you found. Both binaries were rebuilt and run from the branch merged into current main and pass here (Linux); the other-host branch is a text match against the same string the throw builds.

On the AGENTS.md point: every conversion in "The other documented error paths" is an export whose states a title reaches were already implemented and kept the marker only on a side state that the library's debt file already describes as a throw. The marker's own behaviour is the same std::runtime_error; the wording keeps the debt file's words and only drops the " not implemented" suffix the marker appends, and nothing returns a made-up success. That is the reading the PR body argues, but it is a policy call, not a technical one: if you or a maintainer prefer the NotImplemented_nid_no_patch form kept on any of these side states, say which and I will revert those conversions here. symlink already went the other way — 93bbeb3 restores the markers #6200 kept and extended.

@primesoul-web

Copy link
Copy Markdown
Contributor Author

Thank you for the retail measurement — adopted. 93bbeb3 makes sceHmd2ReprojectionQueryBufferSizeAlign answer the measured 0x100828 / 0x100 for every argument (constants Hmd2::ReprojectionBufferSize / ReprojectionBufferAlign), with the debt file now naming the console as the source and noting that KytyPS5's 16-byte state is not what the console answers; the session's 16-byte state sits at the start of that buffer. GuestHmd2 asserts the measured pair and allocates the full size.

Please keep all four remaining size queries in your #7463 follow-up — this PR deliberately leaves them throwing so your measured QueryDisplayBufferSizeAlign 0x6000000/0x10000, QuerySeeThroughBufferSizeAlign 0x1106000/0x100, GetMirroringDisplayBufferSizeAlign 0x5FA0000/0x10000 and GetMirroringWorkMemorySizeAlign 0x4000/0x100 land there in one place (frankea's GT7 runs below show the title walking into exactly that throw next, and past all four once #7690 carries your values). Nothing needs to be dropped from your branch.

The layout confirmation for ReprojectionInitializeParam and the 0x2000F mode is great to have — both match what GuestHmd2 now pins.

@primesoul-web

Copy link
Copy Markdown
Contributor Author

Thank you for the three-tree measurement — the stop-walk table and the stderr blocks make the remaining throw chain unambiguous, and the "same module-load thread, before the first frame" observation is a nice confirmation that this is the set-up path and nothing else.

Both tree notes are addressed in 93bbeb3:

On the predicted Hmd2::SizeAlign duplicate with #7690: this branch keeps the struct block as in your resolution, and #7690's declarations ride on top when it rebases.

@primesoul-web

Copy link
Copy Markdown
Contributor Author

That order works for us: #7658 first, then the HTTP transport. The branch keeps the offline backend's rejection hunk, and 93bbeb3 updates guest_offline_imports' expectation to the offline message (sceHttpSetCookieEnabled: the offline backend has no cookie engine), so your resolution on top replaces the hunk and that expectation together with the real cookie engine in one pass.

No dependency from this PR's other exports on the transport. If the maintainer happens to land the transport first, we will drop the cookie-only hunk here as you propose rather than restore the offline throw — please link the upstream PR here when it opens and we will record the agreed order either way.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
@LeandroLP23

Copy link
Copy Markdown
Contributor

Rechecked 93bbeb3 on Windows (AMD Radeon 860M, WinLibs GCC 15.2.0, Release), merged into main 6b8228b: guest_sysarch and guest_offline_imports now pass, 3 of 3 each. Both aborted on the previous head, so the fix covers the two failures I reported.

AI-assisted: yes (Claude Code)

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 21, 2026
…jection session

Adopt main's restored reprojection ABI (two-argument Initialize with the
reserved parameter, single-argument SetAllowPositionalReprojection,
ReprojectionInitializeParameter layouts, typed mirroring/parameter structs)
over the session implementations; the measured table is unaffected because
the console ignores the extra arguments. ReprojectionInitializeParameter
moves to Hmd2.hpp so GuestHmd2 can construct it; GuestHmd2's main drops
the QueryBufferSizeAlign throw expectation the implementation supersedes
and keeps the wave's before-initialization checks.
@primesoul-web

Copy link
Copy Markdown
Contributor Author

The ABI sweep landed on main, and this branch carries it over the session in head 4cf2a20 — per the coordination note on the sweep, the restored signatures are adopted whole: the two-argument sceHmd2ReprojectionInitialize (the reserved second parameter ignored, the console's extra arguments unrecorded), the single-argument sceHmd2ReprojectionSetAllowPositionalReprojection, and the ReprojectionInitializeParameter layouts with the sweep's field names (workMemory, displayMemory, auxiliaryMemory, timingInterval — the same offsets this PR's 72-byte validation pinned). The struct moves to Hmd2.hpp so GuestHmd2 can construct it, following DeviceInformation/FieldOfView's placement; the mirroring/parameter structs the sweep added stay in Export.cpp with their markers.

The measured table is untouched — frankea's 13.40 rows and T3cm0's 13.60 datapoint answer exactly as before, because the console ignores the arguments the restored ABI drops. GuestHmd2 adapts: the pre-session block and the session walk call the new signatures (SetAllowPositionalReprojection(2), Initialize(&param, nullptr), the 0xFF truncation covering the console's 0xFFFFFFFF reject row), main()'s QueryBufferSizeAlign throw expectation drops (the implementation supersedes it) while the wave's before-initialization checks stay, and both of nikowolf123's mutations were rebuilt as negative controls against the new head — the session gate aborts at "the positional setting without a session did not succeed", the reject-path reset aborts at "a rejected positional setting did not keep the previous non-zero setting". The debt entry moves with the sweep's restructure (recovered contracts vs remaining gaps), keeps the session bullet with the no-session origin note, and the PPSA import bullets stay.

Re-measured on the merged tree against main d6eafa5: 4834/11181 (43.23%) -> 4840/11181 (43.29%), libSceHmd2 13/104 -> 19/104, +6, no regressions. git merge-tree against main is clean; all conventions checks pass (the pre-existing session-bullet length warning is unchanged from the approved head).

@primesoul-web

Copy link
Copy Markdown
Contributor Author

A re-verification against today's main c9b7582, since the tree has moved 2000+ commits since the ABI carry: git merge-tree against the current head is clean, and the re-measured merged tree still lands exactly the six session exports — libSceHmd2 13/126 → 19/126 (+6, no regressions); only the denominator moved (104 → 126, the continuing declaration imports).

The newest neighbour in this file, hypermist's df2e2760c (the gaze results without an open headset), is complementary the same way #8645 is: it answers the two sceHmd2GazeGetResult* exports on the no-session path and meets none of the session hunks — the measured table, the session walk and the debt entry are untouched by the merge.

Nothing is needed on our side: the branch is unchanged since 4cf2a20 and merges as-is.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 24, 2026
@frankea

frankea commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

@primesoul-web Some data for the session, from libSceHmd2.sprx on firmware 13.40 and from inside Gran Turismo 7 (PPSA01317) on the same console, with no headset connected. The module points are read from its code; the GT7 points are measured with a payload that calls the game's own exports, using only calls that the code shows store nothing.

  • GT7 gets a session without a headset. In GT7, sceHmd2ReprojectionGetStatus(&s) returned 0 and wrote 0x68 bytes: zeros up to 0x28, four timestamps at 0x28, 0x30, 0x38 and 0x40 that advanced between calls, then zeros. GetStatus(NULL) returned 0x8a720008. Every gated export tests the session before its arguments, so this is a live session, not an argument check. So a title can reach the in-session paths on a console with no PS VR2 attached.
  • Which titles get one. sceHmd2ReprojectionInitialize first reads the process's app info (sceKernelGetAppInfo(getpid())). If that call fails, or the info has a nonzero field without attribute bit 0x400, it returns 0x8a72001f before it looks at its arguments. That is the 0x8A72001F MLB The Show 25 got on feat(libSceHmd2): answer five reprojection calls without a session as the console does #8645. A second initialization returns 0x8a72000c, as this PR has it.
  • sceHmd2ReprojectionSetTiming. The module returns 0x8a720009 (PARAMETER_INVALID) for a value outside 2000 to 7000 us, where this PR throws. Inside the range it rounds up to the next hundred, as here. If that equals the current timing it returns 0 at once; otherwise it returns sceVideoOutSetDisplayEventPosition's result.
  • sceHmd2ReprojectionEnableVrMode. The module matches this PR. A mode other than 0x2000C and 0x2000F returns 0x80290016; otherwise the VideoOut pipeline-status and layout calls decide, which without a headset is the NO_DEVICE you measured.
  • Measured in GT7's session.
    • GetPredictedDisplayTime returned 0x8a720019 for frame ids 0, 1 and 0x123456789, and 0x8a720008 for a null output.
    • SetRenderConfig(0, 1, 1) and SetRenderConfig(2, 0, 0) returned 0x8a720009. The code accepts only bits 0 and 32 in the first argument and zero in the other two.
    • SetParam(16, 0, buffer, 0) returned 0x8029000a, and SetParam(0, 0, NULL, 0) returned 0x80290002.

AI-assisted: yes (Claude Code)

@primesoul-web

Copy link
Copy Markdown
Contributor Author

Thank you — this settles both open questions about the session with the module itself, and the adoption is in head 24d4edc.

SetTiming: the out-of-range throw is gone; the setting now answers PARAMETER_INVALID (0x8a720009) below 2000 and above 7000 us, rounds up to the next hundred inside the range, and answers 0 at once when the rounded value equals the current timing — exactly the module's order. The one piece held back is the change case: the module returns sceVideoOutSetDisplayEventPosition's result there, and VideoOut does not carry that export yet, so the branch keeps answering 0 and the debt entry records the forwarding for whenever it grows. GuestHmd2 walks the new rows (1999 and 7001 rejected with the previous setting kept, 2001 rounding to 2100, a repeat of 2100 answering 0 at once, 2000 and 7000 succeeding).

Initialize: the app-info gate is recorded in the debt entry rather than implemented — the module's 0x8a72001f for a failed sceKernelGetAppInfo or a nonzero attribute field without bit 0x400 (the MLB The Show 25 answer on #8645) needs the attribute fields modelled, and the emulator's app info currently writes only the app id and title id, so every caller would pass a faithful check either way. When the kernel grows the attributes, the gate drops in as the first thing Initialize does, before its arguments — the order your read shows. The second initialization answering 0x8a72000c was already in place.

The in-session table: GetStatus's 0x68-byte record (zeros to 0x28, the four advancing timestamps, then zeros), GetPredictedDisplayTime's 0x8a720019 for every frame id and 0x8a720008 for a null output, SetRenderConfig's bit rules answering 0x8a720009 for (0, 1, 1) and (2, 0, 0), and SetParam's 0x8029000a / 0x80290002 are now in the debt entry as measured reference for whoever implements them — none of them is in this PR's six, so the scope stays the session.

The head also carries the new main (the canonical-headers refactor moved the reprojection structs into include/Api.hpp; the branch drops its own copies and adopts the canonical ones, VrTracker2's initialization pass-through, and the Open/Initialize archaeology note). Re-measured on main c547dea: 6568/13463 (48.79%) -> 6574/13463 (48.83%), libSceHmd2 13/126 -> 19/126, +6, no regressions; git merge-tree against main is clean and the conventions checks pass (the pre-existing session-bullet length warning is unchanged).

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 27, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

@primesoul-web thanks for this. Two small things. First, please don't have the test assert that Initialize with auxiliary memory throws. We keep tests to real behaviour and argument validation, because a test that pins a not-yet-implemented path breaks the moment someone implements it. Second, the new Hmd2 debt bullets say "GT7" three times ("inside a GT7 session on firmware 13.40" and the like). We keep game names out of the debt docs; could you describe it as a session on firmware 13.40, or give the title's PPSA id instead? The PPSA ids already in the bullets can stay.

The initialization-with-auxiliary-memory assertion pinned a
not-yet-implemented path and would break the moment auxiliary memory
grows an implementation, so the test goes back to the session's real
behaviour and argument validation. The debt bullets name the measured
session's title id (PPSA01317) instead of its name, per the review.
@primesoul-web

Copy link
Copy Markdown
Contributor Author

@oneandonlydean both done in head 1f774a2.

The test: the initialization-with-auxiliary-memory assertion is gone, together with its buffer and the try/catch that carried it — GuestHmd2 keeps to the session's real behaviour and argument validation: the null-parameter and buffer checks, the initialization writing the default timing into the work memory, the second initialization answering REPROJECTION_ALREADY_INITIALIZED, and the timing table (rounding, the equal-timing zero, the rejected 1999/7001 keeping the previous setting). tools/check_conventions.py --base upstream/main passes with the same single pre-existing warning, and the merged tree re-measures exactly the six session exports on main 3850cc8: libSceHmd2 13/134 → 19/134 (+6, no regressions).

The debt bullets: the three game names are now the title's id — "inside a session on firmware 13.40 (PPSA01317)", "In a session on firmware 13.40 (PPSA01317) GetPredictedDisplayTime answered…" and the SetRenderConfig/SetParam bullet likewise, matching the PPSA ids the bullets already carry.

The branch still merges clean against current main (git merge-tree against 3850cc8 is without conflicts).

…ction session

Main restructured the libSceHmd2 debt file since c547dea: the
Additional callable exports section with the inventory table, the
Mirroring/PredictedDisplayTime/SetRenderConfig functional bullet
(carrying the session measurements from this pull request's
discussion), the Older callable NIDs bullet, and the Unknown
CBkVZi9WhVo entry. The parameter-submission bullet conflicted with
this branch's measured rows; the resolution keeps the measured rows
and main's ending.
@primesoul-web

Copy link
Copy Markdown
Contributor Author

@oneandonlydean @adriadam10 the branch carried current main in head 3226e99: the debt-file restructure had reached this branch's four files, and main's side of docs/dev/debt/libSceHmd2.md conflicted with the measured rows this branch added to the parameter-submission bullet.

The resolution keeps both: the measured rows stay (in a session on firmware 13.40 (PPSA01317), (0, 1, 1) and (2, 0, 0) answered 0x8A720009; SetParam answered 0x8029000A ... 0x80290002), and main's side is adopted whole — the SetParam and SetParamWithBuffer remain unimplemented ending, the new Older-callable-NIDs bullet, and everything else main grew since c547dea (the Additional callable exports section, the inventory table, and the new functional bullet carrying the session measurements from this pull request's discussion).

Re-verified on the carried tree: GuestHmd2 passes (session behaviour and argument validation), the negative control against main's marker Export.cpp still aborts at the session exports, conventions pass with the same single pre-existing warning, and git merge-tree against main e0caf13 is clean. Re-measured: 9138/34808 (26.25%) -> 9144/34808 (26.27%) on main e0caf13, exactly the six session exports (libSceHmd2 16/153 -> 22/153), no regressions.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Nov 1, 2026
@oneandonlydean
oneandonlydean merged commit a7c4549 into boykopovar:main Nov 2, 2026
13 checks passed
@primesoul-web
primesoul-web deleted the feat/export-leftovers-and-error-paths branch November 2, 2026 08:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants