- ovrplugin_shim.h: runtime on Steam Frame is SteamVR (HOST.md), not Monado; drop the "Status: scaffold" line and the note that SetupLayer / GetLayerTexture2 await a 128-byte desc (implemented against the 108-byte ovrpLayerDesc); point RE-NOTES/SHIM-SCOPE at docs/research/. - lever2-detect-skip-black-handoff.md lives in docs/research/, not analysis/. - References to unpublished RE notes now say so instead of naming files that are not in the repo. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
5.4 KiB
RE4 VR Shim — Handoff (2026-06-26, skipblack instrument session)
Continuation of HANDOFF-2026-06-26.md (morning). Branch latency-perffix-copyring.
This session built + ran Lever 2 step 1 (instrument-only black-frame detector) and
validated the signal is real but coarse. Detailed finding in auto-memory
skipblack-posthitch-signal-validated. Design: docs/research/lever2-detect-skip-black-handoff.md.
TL;DR — the post-hitch black signal is REAL but needs a tighter gate + frame-accurate proof
Added debug.re4vr.skipblack 1 (instrument-only): after a HITCH (dt>20ms), flag the next
skipblackn frames as LIKELY-TRUNCATED and log them. NO behavior change at level 1. Ran on
device (standing, walk off bridge into house). 47 HITCHes / 89 flagged frames. Result:
- The truncated/black frame has a signature: a SHORT (<12ms) frame right after a big hitch — it finishes fast because UE rendered almost nothing into it (matches RenderDoc 28-vs-198 draws → pure black).
- Big hitches (≥40ms): 25, of which 52% have that <12ms truncated recovery frame.
- Mild hitches (20–40ms): 22, only 5% — these recover to a full ~14ms frame = a 1-frame judder, NOT black. The current heuristic OVER-flags these.
- HITCH magnitudes span 20ms → 3.8s. The multi-hundred-ms / multi-second ones are load/streaming stalls (level transitions) — a different event from per-frame gameplay black drops. Must be separated from the gameplay black.
- Ground truth is still COARSE: user confirmed "black" happened, but not frame-accurate; the short-dt=black link rests on the single RenderDoc capture, not per-frame verified.
Raw frame/flag sequence persisted: analysis/skipblack-2026-06-26/frame-sequence.log.
NEXT TASK — do BOTH (user decision this session):
(3) Tighten the detector, then re-instrument
- Gate the arm on hitch ≥40ms (drops the 22 harmless mild hitches; ~halves false flags).
- Add short-dt confirmation: only treat a post-hitch frame as truncated if its own dt < ~12ms. Combines the two corroborating signals; cuts the ~48% of big hitches whose recovery wasn't short (those may be a different failure mode — investigate separately).
- Handle sustained-overload clusters (e.g. f=3162–3173: ~10 consecutive hitches). The fixed "next 2 frames" model is wrong there — the whole burst is bad. Consider: stay armed while consecutive frames keep hitching, not a fixed count.
- Re-run instrument-only and re-inspect (reuse the awk recipes from this session's analysis).
- Code is all in
shim/src/xr_runtime.c:skipblack_level()/skipblack_count()(~205) and the flag block inside the FRAME/HITCH trace (~1186, look forSKIPBLACK: LIKELY-TRUNCATED).
(2) Frame-accurate ground truth via Meta Cast (do alongside / before reproject)
- In-game recording is broken (empty mp4); display 0 is FLAG_SECURE so scrcpy/screenrecord can't see VR. Use Meta Cast → desktop screen-record while walking off the bridge.
- Run with
skipblack=1 skipblackn=2 trace=1, capture logcat with timestamps in parallel. - Align the video's visible black flashes to the
SKIPBLACK: LIKELY-TRUNCATEDlog timestamps to PROVE (or refute) flag == perceived black, per frame. This is theverify-before-theorydiscipline — don't build the reproject on the coarse signal alone.
THEN — Lever 2 step 2 (reproject, debug.re4vr.skipblack 2)
Only after (2)+(3). Re-present the last-good eye image with the current pose so the OpenXR compositor timewarps it instead of showing black.
- Use the COPY-ring, NOT a cross-frame swapchain hold — holding across frames is what
sank
pipeline=1(see memorydeferred-flush-unstable-abandoned). Copy last-good eye → shim VkImage (xrr_vk_alloc_images/shimImages[], copy path in end_frame ~922), blit it into the acquired image on a flagged frame, submit the composition with the CURRENT pose. - Low harm on false positives: reprojecting a stale frame ≈ what the compositor already does during the hitch, so over-flagging mild hitches is tolerable but wasteful — the ≥40ms gate keeps it clean.
- Success = black flashes replaced by (at worst) brief reprojection judder, not black.
Build / deploy / test (unchanged)
./shim/build_android.sh && ./packaging/repack.sh # repack bundles LAST build — check
# shim/build/arm64/libOVRPlugin.so timestamp
adb -s <redacted-serial> install -r packaging/out/re4vr-shim.apk
adb -s <redacted-serial> logcat | grep -iE "xrr|SKIPBLACK"
Launch is BLOCKED by the controllers-asleep system dialog
(app_launch_blocked_controller_required) — wake controllers + headset on before
am start -n com.Armature.VR4/com.epicgames.ue4.GameActivity.
Logcat is dominated by PerfMetrics polling — filter to FRAME f=|HITCH|SKIPBLACK for pacing.
Props
debug.re4vr.skipblack 0|1|2— 0 off / 1 instrument-only (built) / 2 reproject (TODO).debug.re4vr.skipblackn N— post-hitch frames to flag (default 2). Will change with the cluster-aware rewrite in (3).- Reset to baseline now: skipblack=0, skipblackn=0, trace=0, ffr=2, resscale=100.
State
- Code committed? NO —
skipblackdetector is UNCOMMITTED in the working tree (shim/src/xr_runtime.c). Commit it (instrument-only, gated off, safe) before further work. - Branch
latency-perffix-copyring, clean except the skipblack edit + the new analysis dir.