docs+build: hygiene pass — close leak risk, de-drift docs, fix build prereqs

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>
This commit is contained in:
Daniel LynchandClaude Opus 5 committed 2026-09-18 02:15:10 -04:00
1 parent 574f41a0e4
commit 1f3dc40c07
13 files changed
+304 -161

No files matched your search

+88 -47
View File
@@ -25,7 +25,7 @@ Idea: build the shim as an Android arm64 `.so`, drop it into the RE4 APK in plac
of the real `libOVRPlugin.so`, sideload, run. Our shim calls the Quest's own
`libopenxr_loader` -> Meta's OpenXR runtime.
Prereqs to do first (these are the currently-open work items):
Prereqs (all done — kept as the record of what Path A needed):
1. **NDK arm64 build** of the shim — DONE. `shim/build_android.sh` -> NDK r27c ->
build/arm64/libOVRPlugin.so (aarch64, 438/438 drop-in, NEEDED libopenxr_loader).
2. **Instance handshake** — DONE. src/android_init.c: JNI_OnLoad captures the JavaVM;
@@ -34,9 +34,9 @@ Prereqs to do first (these are the currently-open work items):
Application-context reflection fallback. [VERIFY-ON-HW] whether Meta's runtime
accepts the Application context vs requiring the real Activity, and the
PreInitialize3-creates-instance-before-activity ordering.
3. **Manifest** — add the OpenXR usage declarations Meta's runtime expects
(`<uses-feature android:name="android.hardware.vr.headtracking">` already there;
add OpenXR `<meta-data>`/intent bits per Meta's OpenXR mobile docs).
3. **Manifest** — no change needed in the end. RE4 VR is already a shipping Quest VR app
and declares `<uses-feature android:name="android.hardware.vr.headtracking">`;
`packaging/inspect_manifest.sh` reports any gaps.
4. **Entitlement** — on Quest you OWN RE4 and the Quest has the real Meta Horizon
platform service, so leave the ORIGINAL libovrplatformloader.so untouched and only
swap libOVRPlugin.so. Logged into the owning account, the real ovr_Entitlement check
@@ -55,61 +55,102 @@ cp shim/build/arm64/libOVRPlugin.so apk/lib/arm64-v8a/libOVRPlugin.so
adb install -r re4vr-shim.apk # or push OBB + sideload
adb logcat | grep -iE 'xrr|OVRPlugin|openxr' # watch the [xrr] logs
```
Expected first-run signal: instance/session create succeed in logcat; if the frame
loop spins and the projection layer submits, you get an image (even if poses/input
are rough). Known rough edges on first run: depth (Unsupported), input (controllers
return NotYetImplemented), the [VERIFY-ON-HW] swapchain index lockstep.
`packaging/repack.sh` now does the repack, align and re-sign in one step; the manual
sequence above is kept only to show what it does.
## Path B — Monado simulated (fast iteration, works on the Mac)
First-run signal to look for: instance and session create succeeding in logcat, then the
frame loop spinning and the projection layer submitting. (Historically the rough edges on a
first run were depth, input and the swapchain index lockstep; input and rendering are now
working — see "Current state" below.)
Monado has a simulated/headless HMD driver — no real headset. Run it in a Linux
arm64 VM (UTM/QEMU on Apple Silicon), point an OpenXR loader at it, and run the
harness (tests/harness.c) which drives the ovrp_* sequence:
## Path B — desktop OpenXR harness (fast iteration) — BUILT
`tools/desktop-harness/` drives the `ovrp_*` sequence against a real OpenXR runtime with no
headset and no RE4:
PreInitialize3 -> Initialize5 -> SetupLayer -> [WaitToBeginFrame -> BeginFrame4 ->
GetNodePoseState3 -> EndFrame4] xN -> Shutdown2
and asserts each returns ovrpSuccess. This exercises the real OpenXR calls without
RE4 or hardware. Build:
asserting each returns `ovrpSuccess`. Build and run:
```
# inside the Linux arm64 env, with Monado + openxr loader installed:
cc -std=c11 -Iinclude -Ithird_party/openxr tests/harness.c \
src/*.c -lopenxr_loader -lvulkan -o harness # needs a Vulkan device/headless
XR_RUNTIME_JSON=/path/to/monado/openxr_monado-dev.json ./harness
shim/build_host.sh # -> build/host/{libOVRPlugin.so,harness}
tools/desktop-harness/run.sh # headless against monado-service
```
Note: Initialize5 needs real Vulkan handles; for a pure-logic smoke test the harness
can pass a headless VkInstance/Device (or we add a "no-gfx" build flag that skips
xrCreateSession to test the non-rendering calls first).
Needs libvulkan and an OpenXR loader (Debian: `libvulkan-dev libopenxr-loader1
libopenxr-dev`); without the loader `build_host.sh` compiles the objects and stops.
Worth adding when chasing a portability bug: run it with `VK_LAYER_KHRONOS_validation` and
the OpenXR core-validation API layer enabled. The harness currently passes without them, so
it does not yet catch usage-flag or queue-family mistakes that a strict runtime would reject.
Note: `shim/tests/harness.c` is the original superseded smoke test; its build line is stale
and no script references it.
## Path C — Steam Frame (the target)
Same arm64 shim `.so`, but the APK runs under **Lepton** (Valve's Waydroid/AOSP) and
the OpenXR runtime is **Monado**. Once Frame ships: NDK build -> repack -> sideload
into Lepton -> the open question is whether Lepton exposes the OpenXR loader +
Vulkan swapchain sharing across the container (see HOST.md unknowns). Path A having
worked makes this mostly a packaging/runtime-plumbing exercise.
Same arm64 shim `.so`, but the APK runs under **Lepton** (Valve's Waydroid fork, now open
source) and the OpenXR runtime inside the container is **SteamVR** — Lepton's
`vendor/etc/openxr/1/active_runtime.json` names `steamvr` and points at a host-mounted
`vrclient.so`. (Earlier notes here and in `HOST.md` assumed Monado; that was wrong.)
What that buys us: SteamVR presents Frame controllers as emulating Oculus Touch by default,
so the shim's Touch bindings should bind; and it advertises the `XR_FB_foveation` family plus
`XR_META_foveation_eye_tracked`, so the existing foveation path survives. Lepton also mounts
the host's mesa/turnip/zink and gralloc into the container, which answers the old
`HOST.md` unknown about sharing swapchain images across the container boundary.
Steam Frame shipped 2026-09-14. Flow: NDK build -> `repack.sh` -> `steamframe_patches.sh`
(the Build spoof is required — Lepton reports `ro.product.manufacturer=Valve`) -> sideload
via adb into a Lepton container. Path A having worked makes this mostly packaging and
runtime-plumbing, but see `HOST.md` for the specific items to check first.
---
## Current state (what's ready to test vs not)
## Current state
WIRED & building (host x86-64, validation only):
- Session lifecycle: instance/system/session create, event-driven state machine.
- Frame loop: xrWaitFrame/Begin/EndFrame, predicted display time.
- Poses: xrLocateViews (eyes) + xrLocateSpace (head).
- Swapchains: xrCreateSwapchain from ovrpLayerDesc, enumerate VkImages, per-frame
acquire/wait/release, real XrCompositionLayerProjection submit.
- 20 OpenXR functions; 239/239 ovrp_ symbols.
**Path A is done and field-verified.** RE4 VR boots and is playable on a Quest 2 through the
shim on Meta's OpenXR runtime: stereo rendering, head and controller tracking, buttons,
grips, haptics, save loading. The frame-pacing "ghost" that dominated development is fixed
(game-thread pacing; see `docs/research/ghost-fix-2026-06-27.md`). Shipped and verified:
NOT yet (the to-do list before a meaningful Quest run):
- arm64/Android NDK build (host build only so far).
- [ANDROID-TODO] JavaVM/activity -> XrInstanceCreateInfoAndroidKHR + xrInitializeLoaderKHR.
- Input: controller/hand action sets (GetControllerState4 returns NotYetImplemented).
- Depth layer (SetupLayerDepth -> Unsupported).
- Map ovrp_GetInstance/DeviceExtensionsVk -> xrGetVulkan*ExtensionsKHR (so the app
creates its VkInstance/Device with the runtime's required extensions).
- [VERIFY-ON-HW] swapchain index lockstep assumption.
- Session lifecycle, event-driven state machine, Android instance handshake
(`src/android_init.c`).
- Frame loop with `xrWaitFrame` on the game thread and a FIFO frameState handoff to the
render thread.
- Poses via `xrLocateViews` and `xrLocateSpace`; `LOCAL_FLOOR` when the game asks for floor
level.
- Swapchains from `ovrpLayerDesc`, per-frame acquire/wait/release, real projection-layer
submit, layer z-order fix so splash quads composite above the eye layer.
- Input action sets for Touch controllers plus haptics (`src/xr_input.c`).
- Vulkan extension queries mapped to `xrGetVulkan*ExtensionsKHR` (`src/vk_session.c`).
- CPU/GPU perf levels forwarded via `XR_EXT_performance_settings`; game-driven foveation.
- 438/438 `ovrp_` symbols; around 48 OpenXR entry points.
## Pick-up-tomorrow shortlist
1. Install Android NDK; cross-build the shim to arm64 (proves it builds for target).
2. Stand up the Monado-sim Linux arm64 VM on the Mac + run tests/harness.c (Path B
smoke test of the non-gfx calls).
3. Then start the [ANDROID-TODO] instance handshake (gates the real Quest run).
**Open / known gaps:**
- Depth layer submission is built but gated off (`debug.re4vr.depth`); Meta's runtime accepts
but does not use plain KHR depth for reprojection.
- Swapchain index lockstep with UE's own `TextureStage` is assumed, not enforced — a
mismatch is logged but not corrected.
- Residual `XR_FRAME_DISCARDED` hiccups from the frameState ring dropping its oldest entry
under overflow instead of applying back-pressure.
- In-game black near load zones is a level-streaming / memory stall, mitigated rather than
fixed. See `docs/handoffs/HANDOFF-2026-06-27.md`.
- Several `debug.re4vr.*` paths are documented dead ends kept for reference.
## Next: Path C (Steam Frame)
Frame shipped 2026-09-14, so this is the live front. Highest-value items before a first boot
attempt, in order:
1. **Page alignment.** The shim's ELF segments and the repacked APK are 4 KB-aligned
(`zipalign -p 4`); Valve's Unreal docs reference a 16 KB page-alignment requirement. If
the Frame kernel uses 16 KB pages the library will not load. Check first: it is a
`-Wl,-z,max-page-size=16384` plus `zipalign -P 16` fix, but it would present as a
mystery launch failure.
2. **Swapchain usage flags.** `setup_layer` requests only COLOR_ATTACHMENT and SAMPLED.
Meta over-provisions mutable, broadly-usable images; a spec-following runtime gives you
exactly what you asked for, and UE 4.25 needs a mutable format for its linear UNORM view.
3. **Refresh rate.** 72 Hz is hardcoded in `ovrp_GetSystemDisplayFrequency2` and in the
frame-budget constant. Frame runs 72/90/120/144. Derive it from
`predictedDisplayPeriod`.
4. **Session events.** Only READY and STOPPING are handled, so focus loss and quit from the
Steam overlay never reach the game.
5. **The per-frame GPU flush-wait.** It exists because Meta's compositor does not sync
against our submit. A/B it on Frame with `debug.re4vr.noflushwait`.