Repo hygiene round following a full review. No shim behaviour changes. Leak risk: - .gitignore: ignore CLAUDE.md (personal assistant-lane config, was one `git add -A` away from a public commit) and scratch_obj/. Docs vs. reality: - shim/README.md: rewritten. It described a pre-implementation skeleton with "core fns are TODO stubs returning -1005", three mutually inconsistent stub counts, and four completed milestones listed as open. Now carries the verified breakdown: 438/438 exports = 371 generated stubs + 46 core + 7 layers + 2 Vulkan queries + 12 passthru trampolines. - TESTING.md: dropped the self-contradicting "NOT yet" block (5 of 6 items were done or misstated, and contradicted the same file 45 lines above). Path B now points at tools/desktop-harness, which exists, instead of the orphaned shim/tests/harness.c. Path A prereqs marked as the record they are. - HOST.md: corrected the runtime assumption. The OpenXR runtime inside Lepton is SteamVR (vendor/etc/openxr/1/active_runtime.json -> vrclient.so), not Monado. Favourable: SteamVR emulates Oculus Touch by default and advertises the XR_FB_foveation family, so the existing input and foveation paths should carry over. The old "remaining unknowns" are resolved by Lepton's published source and replaced with the items to check before a first Frame boot. - README.md: same runtime correction. - docs/research/RECON.md: the four passages prescribing an entitlement NOP/stub/bypass are corrected in place rather than merely disclaimed by the top banner, which they contradicted. Build correctness: - shim/build_android.sh: missing patchelf is now fatal. It warned and exited 0, producing a .so that cannot resolve the OpenXR loader at runtime. - scripts/fetch_deps.sh + packaging/build_openxr_loader.sh: pin the OpenXR and Vulkan header versions (were tracking `main`), overridable via OPENXR_TAG / VULKAN_HEADERS_TAG; require cmake for the loader build. - packaging/steamframe_patches.sh: use the apktool.jar that fetch_deps.sh downloads. Its prereq check demanded an `apktool` binary on PATH that the documented setup never provides, so it could not run after a clean setup. - shim/gen_stubs.sh: it reads all_exports.txt, not shim_surface.txt; comment and emitted banner corrected. stubs.c regenerated (banner line only). - shim/src/core.c: split seven `if (out) ...; return ...;` one-liners. Host build now compiles with zero warnings, down from seven. Verified: host build 0 warnings; gen_stubs.sh output identical on regeneration; bash -n clean on all edited scripts; pinned header/tarball URLs return 200 and the tag tarball extracts to the expected directory name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
RE4 VR shim — docs
Research trail and session handoffs for the OVRPlugin→OpenXR shim that runs RE4 VR on non-Meta OpenXR runtimes (Quest 2 today; Steam Frame the goal).
handoffs/— chronological session handoffs (the day-by-day journey).research/— reverse-engineering notes, design docs, the ghost-fix writeup, andrelated-work.md(how Overport/others run Quest games elsewhere; why this shim is novel).../analysis/— raw RE dumps (*.txt, mostly gitignored;all_exports.txt+shim_surface.txtfeedshim/gen_stubs.sh).- Operational docs live at repo root:
README.md,TESTING.md,HOST.md.
The "ghost" — how it was actually solved (the short version)
For weeks the headline bug was a VR "ghost": doubled/tripled hands and watches, seated-mode deform, standing-mode black flashes. It survived every theory it looked like — stereo geometry, FOV/IPD, depth reprojection, render-submit ordering, GPU load.
The break came from building P4 passthru (research/ghost-fix-2026-06-27.md):
running the real Meta libOVRPlugin inside our app proved native is flawless, so the
bug was in our path. Then always-on anomaly logging caught the real signature —
dropped frames — and a recording + frame-blending showed it was whole-frame
temporal judder, not a stereo/geometry artifact. Localizing the stall showed UE's
render thread blocking ~85–150ms during motion at only ~14ms of GPU work.
Root cause: the shim left ovrp_WaitToBeginFrame a no-op and ran xrWaitFrame on
the render thread, so the game thread was unpaced and UE's pipelined renderer
desynced → render-thread stalls → dropped frames → the compositor's timewarp filled the
gaps → judder. Fix: pace the game thread like vrapi (xrWaitFrame in
xrr_wait_frame, frameState handed to the render thread via a 1:1 FIFO ring). Result:
render stall 150→3ms, drops 0.79% (~native 0.34%), steady 72Hz — ghost gone.
Commits 79476c3 (fix) + c0ecff3 (XR_FRAME_DISCARDED guard).
Secondary wins kept as defaults: game-driven FFR (apply the game's foveation), and the per-frame luma readback off.
Reading order if you're new
research/ghost-fix-2026-06-27.md— the full diagnosis chain (start here).handoffs/newest → oldest — the journey, including the dead ends (reproject, submit-ordering, depth) that were ruled out.research/RECON.md,RE-NOTES.md,SHIM-SCOPE.md— how the shim was reverse-engineered.research/render-submit-sync-design.md— the render/submit sync design.