Commit Graph
110 Commits
Author SHA1 Message Date
spoopyghosty0 922f7f31d4 MR: scan the room in the headset - RoomView 3D capture with headset prompts, wall labels, agent 92
The agent records Valve's passthrough depth mesh (IVRBlockQueue + IVRPaths, Background/Overlay OpenVR app), converts
poses with the final raw->standing transform, shows pre-rendered prompts as a SteamVR overlay, then builds the room
with fp_roomscan (room_capture / room_capture_status). PC: rooms.scan, Frame page progress, frameport frame room scan.
FrameBridge reads the optional wall label (INVISIBLE_WALL_FACE). ROOM_FORMAT: outline orientation stated exactly.
2026-10-18 19:56:12 -04:00
spoopyghosty0 8df7fe68a7 Rebuild FrameBridge scene emulation artifacts deterministically 2026-10-18 19:48:26 -04:00
spoopyghosty0 030c0f676b MR: optional wall label in room.txt; in-headset scan prompts (render_prompts.py) 2026-10-18 19:47:13 -04:00
spoopyghosty0 d801ff6bc8 Controller buttons: second button up and toward the other hand like Quest (Quest pair: Y = d-pad right, B = X; Mirror follows); d-pad touch is one sensor 2026-10-18 09:23:52 -04:00
spoopyghosty0 33bb14e034 FrameBridge: cube-only frames as quads (#385); CI: lint, foveation test
HardBullet's loading frames submit only a cube layer, which the Frame's runtime doesn't composite, so the loading
screen stayed black. Adapter setting cube_quads (default on) draws a cube-only frame's six faces as quad layers
around the viewer; frames with a projection layer are unchanged. Adapter rebuilt on current main.

Also: the foveation test expected a numeric FOVE_LEVEL to pass through (Valve's layer ignores numbers since #394),
a long description line and an import order in adapter_dialog.py failed lint.
2026-10-17 21:52:39 -04:00
spoopyghosty0 358db1360f quest_buttons: rebuild the adapter on current main, regenerate patch docs and translations 2026-10-17 21:44:01 -04:00
spoopyghosty0 d02ff3c9aa Vulkan shim: leave out Lepton's Fossilize layer, blank shaders for refused pipelines (#349)
Transformers Beyond Reality: Redux ended its first frames with Unreal's "Shader compilation failures are
Fatal." With Lepton's Fossilize layer loaded every graphics pipeline failed with
VK_ERROR_OUT_OF_HOST_MEMORY, even a constant-colour one; the pipeline cache, the app's CPU allocator and
Valve's FDM/RPO layers were ruled out on the device. Without Fossilize a few multiview vertex shaders
still fail in Turnip.

- adapter.vk_no_fossilize: the shim removes VK_LAYER_fossilize from GraphicsEnv's debug layer list when
  the engine loads it (library constructor, same GraphicsEnv calls as zinkfix)
- adapter.vk_pipeline_fallback: a refused graphics pipeline is created again with a blank fragment
  shader, then blank vertex + fragment shaders
- catalog entry (status unknown, headless only), triage vulkan-pipeline-refused, PLAYBOOK, DEVICE_NOTES

Headless on the dev Frame: RUNNING after 78 s at 72 fps (was: exit within 21 s). Headset check pending.
2026-10-17 21:34:00 -04:00
spoopyghosty0 5e668f33d3 Batman audio hooks: place stubs in verified free gaps, say which step failed
On the Frame the atomic-branch detours never installed ("installation failed"): an mmap hint isn't honoured,
so the stub landed out of B range. Stubs now go into free gaps from /proc/self/maps within +-128 MiB of the
entry, mapped with MAP_FIXED_NOREPLACE (a relocated result is rejected); no gap = no hook. Every failure logs
the target and the failing step.
2026-10-17 16:47:06 -04:00
spoopyghosty0 feb4658dde Batman audio hooks: publish detours with one atomic branch (#389, #34)
The detour helper from PR #34 (used by the metadata hook and PR #389's two geometry hooks) wrote a 16-byte
branch + literal in two steps while game threads may run the function (installation is retried from
xrPollEvent). Each hook now publishes a single aligned release-atomic AArch64 B to a stub within +-128 MiB;
the trampoline replays the one verified instruction and returns to entry+4. Every mprotect is checked;
failures before publication free the stub and trampoline and leave the function untouched; no stub in range =
no hook. Adapter artifacts rebuilt by us (the PR's binary isn't shipped).
2026-10-17 16:47:06 -04:00
spoopyghosty0 1e1c435191 Headset model per game, Quest 3 by default (#373)
Games saw a Quest 2: OVRPort's dispatcher hardcodes the system name "Oculus Quest2" and a Quest 2
headset UUID (XR_META_headset_id), and OVRPlugin decides the headset type from the UUID first, then the
name. device.headset_model (Customize; Quest 3 default for every Quest game, also Quest 3S, Quest Pro,
Quest 2) rewrites both in the dispatcher after checking the original bytes; Quest 2 leaves the build
byte-identical. FrameBridge reports the same name to games that call the runtime directly. A library
migration adds the default to existing recipes (installed games show Update on Frame).
Lepton's ro.product.model ("Lepton") can't be overridden by boot properties, so Build.MODEL is unchanged.

Verified on the Frame: OVRPlugin logs "systemName Meta Quest 3" (was "Oculus Quest2").
2026-10-17 15:18:45 -04:00
spoopyghosty0 7d4e20844f FrameBridge: XR_FB_color_space for games that require it (#336)
OutRun Quest VR quits before its VR session with "Rec.709 color space unavailable": the Frame's runtime
has no XR_FB_color_space. Adapter setting fb_color_space (off by default; triage color-space-missing
suggests it) offers the extension when the runtime lacks it, reports Rec.709 and accepts any colour
space without converting. OVRPort's dispatcher can't forward the new functions, so it isn't on for every
game. Not yet tried on the device.
2026-10-17 14:56:59 -04:00
spoopyghosty0 6423739ae6 Rebuild artifacts for #293, #329, #303, #327/#328, #323, #306, #349 and the snapshot fallback
FrameBridge (libopenxr_loader_generic.so, arm64-v8a + armeabi-v7a) rebuilt from the merged adapter sources: rect_clamp empty rects (#293), alpha_blend_emul (#329), foveation storage under Valve's FDM (#303), profile_remap profile report + detached-controller paths (#327/#328), action_dedup (#323), quad/cylinder snapshots, refresh-rate answer (#306), hide_fb_foveation (#349). The other targets rebuild to the committed bytes.
2026-10-17 02:19:10 -04:00
spoopyghosty0 9772b9a17f Vulkan shim: vk_pipeline_log diagnostics (failing pipeline creations, Y'CbCr conversions; GitHub #349) 2026-10-17 02:19:08 -04:00
spoopyghosty0 542e54882d Add opt-in dispatcher-level Quest Vulkan foveation suppression 2026-10-17 02:19:06 -04:00
spoopyghosty0 8986aa7040 FrameBridge: answer xrGetDisplayRefreshRateFB with the real rate (PreyVR black menu, #306)
The Frame's runtime has no XR_FB_display_refresh_rate (-7); OVRPort's dispatcher still
offers it and answered 0 Hz. Team Beef's idTech 4 ports run USERCMD_MSEC = 1000 / refresh,
so PreyVR had 0 ms tics: the async thread spun (~1.9M tics/s), com_frameTime stayed 0 and
the main menu's timeline never got past its first step (black backdrop + cursor only).
FrameBridge now always answers with 1 / predictedDisplayPeriod (else the refresh_rate
setting, else 72 Hz). Headless: menu drawn (snapshot mean 68 vs 0.1 before), clocks advance.

- glmv: gl_mv_debug=2 traces draws (state, GL errors per framebuffer), failed texture
  uploads/copies/compiles and idTech 4 clocks; how this was found
- triage: a single xrEndFrame -16 is info, not the black-menu error it was blamed for
- PLAYBOOK row, CLAUDE.md fact, host test for the fallback rate
2026-10-17 02:19:00 -04:00
spoopyghosty0 9cacc0eacc Unity IL2CPP fixes warn when they find nothing; FrameBridge fills empty image rects (#293)
- Il2cppReturnPatch (unity_runtime_msaa_off, unity_multipass, unity_text_input, ...): a patch that is turned on
  but whose methods Cpp2IL doesn't find in the game now logs it and adds a WARN build check naming the patch
  ("not applied (<id>): code not found in this game ...") instead of skipping silently. ApkContext.checks =
  checks a patch reports while applying; apply_frame_fixes adds them to the build's checks (job/CLI output,
  build record). WARN only: the build still succeeds.
- FrameBridge rect_clamp: an empty projection/quad image rect (BattleTalent's first frames: 0,0 0x0 ->
  xrEndFrame -25 twice, then OVRPlugin stops submitting) becomes the whole swapchain image when its size is
  known; counted as swapped, logged ("rect_clamp: view N empty image rect -> full swapchain WxH"). Adapter
  revision unchanged; artifacts rebuilt.
- triage swapchain-rect-invalid, PLAYBOOK row, setting texts, PATCHES.md, i18n template.
2026-10-17 01:52:16 -04:00
spoopyghosty0 728a009f22 a32 translator: end JIT blocks before the exit-stub table is full (GitHub #342)
Pinball FX2 VR (32-bit, through the a32 bridge) died ~2 s after its loading
screen with "a32: block at 0xf68587a0 overflowed the code buffer" (exit 127):
OpenSSL's sha1_block_data_order_armv8 (reached from RAND_poll) has ~60 SHA-1
crypto instructions in a row, each translated through the interpreter with
its own exit stub, and the block loop only stopped at 96 instructions or half
the code buffer, so the 64-entry stub table overflowed and fatal() ended the
process. The loop now also ends a block while 8 stubs are left.

Headless on the dev Frame the game now runs on past that point (menu-size
swapchains, recenter, its save files written) instead of exiting.
tests/programs/sha1ce.c (72 crypto instructions in a row + an unrolled SHA-1)
failed with the old a32run and passes with the new one, JIT = interpreter.
Triage signature a32-jit-overflow, PLAYBOOK row; frame.a32_bridge marks only
Pinball FX2 VR "Update on Frame" (package_revisions).
2026-10-17 01:33:03 -04:00
spoopyghosty0 6cd1eaaf23 adapter: show a per-eye cylinder menu to both eyes (fix black curved menus)
Doom3Quest-based ports (PreyVR, #306) draw their menu on a curved
cylinder layer submitted once per eye. The Frame's runtime has no
cylinder layers, so FrameBridge shows each as ~7 flat quad strips; a
per-eye pair is ~14 quad layers in one frame, which the runtime rejects
whole (xrEndFrame -> XR_ERROR_LAYER_INVALID, -16). The menu then stays
black while the game runs at 72 fps.

Collapse the pair: drop the right-eye cylinder and show the left eye's
strips to both eyes (the runtime composites a quad to both eyes anyway).
That halves the layer count and drops the per-eye eyeVisibility, and the
runtime accepts the frame. Only frames with per-eye cylinders change --
exactly the ones that were being rejected; mono (BOTH) cylinder games
such as 4XVR are untouched.

Verified on the dev Frame (headless): PreyVR v1.2.4 rebuilt + installed,
the menu frame now submits (one transient -16 on the first frame before
the space is locatable, then clean), 72 fps. Visual confirmation of the
menu needs the headset. Triage signature cylinder-menu-layers-rejected.

No adapter revision bump (installed games are not marked outdated).
2026-10-15 15:54:29 -04:00
spoopyghosty0 741af0651e adapter: log the created swapchain handle in xrCreateSwapchain
Diagnostics only (no behaviour change): the "new layer ... usable=0"
line prints a layer's swapchain pointer, but creation logged only the
result, so a dropped layer couldn't be matched to the xrCreateSwapchain
that made (or failed to make) its swapchain. Needed to pin why a quad
"screen layer" is dropped (#306 PreyVR: menu rendered to a quad whose
swapchain FrameBridge reports as not usable).
2026-10-15 15:13:37 -04:00
spoopyghosty0 9eef555bf2 cube_standin: serve a refused cube swapchain on Vulkan too (#296 UNDERDOGS)
The GLES-only stand-in left Vulkan games (UNDERDOGS: VkFormat 43, faces=6) with the runtime's refusal, so OVRPlugin
crashed in ovrp_EndFrame4. When there's no GLES context, the adapter now makes a cube-compatible VkImage (6 layers,
the requested format/size/mips, usage from the XR flags) with the app's device/queue and a private transient pool,
transitions it to COLOR_ATTACHMENT_OPTIMAL, and enumerates it as a Vulkan image; its cube layers are still dropped.
GLES behaviour is unchanged. Adapter rebuilt deterministically (both ABIs).
2026-10-15 01:43:29 -04:00
spoopyghosty0 927dae5ff8 Merge main into aarch32testing; maintainer fixes for the 32-bit bridge
Merged main (agent 90, ANALYSIS_VERSION 17/18 after main's 15/16) and rebuilt the bridge, foveation and a32
libraries from source (deterministic, no local paths).

Fixes on top:
- a32 loader: apply DT_ANDROID_RELR relocations (Journey of the Gods crashed at start)
- VrApi bridge: vrapi_Get/SetTrackingTransform (floor-level tracking; Shadow Point's player was below the floor)
- a32: never submit an untracked default head pose (hold up to 100 ms, repair or drop); pose consistency per display
  time; submit/tracking counters
- VrApi bridge: wait again when xrWaitFrame gives a display time in the refresh just submitted (the runtime rejected
  the second frame with AlreadySubmitted; Journey of the Gods ran at ~120 fps on a 90 Hz display and the scene slid
  and snapped back); pacing counters
- VrApi bridge: GLES re-entry polls events before reusing the kept session (a lost or exiting session is replaced, a
  stopped one waits for READY)
- VrApi bridge: STAGE is the runtime's STAGE space (play-area centre and orientation, unchanged by recentering);
  LOCAL_FLOOR keeps the head-based origin
- signer certificate: pick the certificate the SignerInfo names, not the first in the PKCS#7 block
- flat 32-bit apps keep the bridge (it isn't a VR-only fix)
- Frida gadget loads are stripped before translation
- launcher restart test fixed; native/.cache is no longer committed
- catalog: Shadow Point and Journey of the Gods work (headset, 2026-10-14; min_app 1.1.2)
2026-10-14 20:10:10 -04:00
spoopyghosty0 57bb69f79c VrApi bridge: save the headset's FOV only when it really changes (it was rewritten every frame)
Rebuilt libvrapi.so and libvrapi_foveation.so without the local NDK paths a worktree build left in their debug info; a test checks that no artifact contains a local path.
2026-10-14 19:45:34 -04:00
spoopyghosty0 a2515f13a7 FrameBridge: turn upside-down quads upright in place (#138); gl_error_clear stops Vader's runtime memory leak (#285) 2026-10-14 13:40:15 -04:00
spoopyghosty0 7c066b5fea Opt-in frame.asset_files_stub: answer Meta asset-file requests for DLC managers that stall without the service (#203) 2026-10-14 12:22:13 -04:00
spoopyghosty0 f6ae3f7724 Fix xLua startup on Frame with full-pointer Lua engine 2026-10-14 09:19:19 -04:00
UnicronNLandClaude Opus 5.5 a37c75aec8 VrApi bridge: recenter from the head after the runtime's recenter
Holding the Frame's menu button turned 32-bit (and other VrApi bridge)
games 90-120 degrees to the side. The bridge's app space is LOCAL plus
the head's yaw at the start; the runtime's recenter moves LOCAL to the
head, so that old yaw was applied again. On a LOCAL reference space
change (and on vrapi_RecenterPose, ignored before) the origin is taken
from the head again in the first frame after the change, and VrApi's
recenter count goes up. Owner-verified with Gunjack.

The upstream patches are regenerated against OVRPort 5e7df52 (they
were missing the FOV cache changes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:58:03 +02:00
UnicronNLandClaude Opus 5.5 2acfd64f7f Sports Scramble runs: Quest builds on the a32 bridge, Unreal's self-restart
Sports Scramble (a 32-bit Santa Cruz build: UE 4.21, OVRPlugin on VrApi 1.21)
now plays in the headset with both controllers.

a32 bridge:
- AAsset_getBuffer: the host buffer lies above 4 GiB, so the guest gets a
  copy in its own memory, freed in AAsset_close (Unreal read its
  UE4CommandLine.txt this way and aborted on the NULL it got before)
- AKeyEvent_getFlags and the remaining AConfiguration getters
- Touch controllers for Quest builds: apps that require 6DoF head tracking
  (uses-feature android.hardware.vr.headtracking version 1) get
  assets/fp_a32_headset.txt = "quest" at conversion; the bridge then passes
  both Frame controllers through (input devices, caps, state, tracking,
  vibration; struct tails per VrApi minor) and sends no synthetic touchpad
  events. Gear VR / Go games keep the remote emulation. A32_TOUCH=1/0
  overrides.

Analysis: detect.requires_6dof, headset "quest" for such 32-bit apps
(ANALYSIS_VERSION 13).

Agent 82: Unreal restarts the app once after its first-run shader precompile
(the "optimizing" screen): it starts its own launcher activity and exits, and
Lepton ends the container with the first process. Skipping the restart or
turning it off (r.ProgramBinaryCache.RestartAndroidAfterPrecompile=0) left
the game on that screen for good. launch.sh now starts Lepton once more when
the system log shows the app's own LAUNCHER start right before its
System.exit(0) (_restart_requested; first run kept as
launch-before-restart.log; FRAMEPORT_NO_RESTART=1 turns it off).
upgrade_launchers adds it to existing launchers. Crashes, quitting from the
game and Steam's Exit game don't match.

Catalog: Sports Scramble works (owner, headset, 2026-10-13).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:48:53 +02:00
UnicronNLandClaude Opus 5.5 324b3401f7 a32translator: lock-free dispatch and per-page invalidation (profiled on the Frame)
Profiled Gunjack and Spunky Dungeon on the Frame with perf and a new
A32_PERFMAP option (a perf map of the translated code: block bodies, exit
stubs, FPCR writes). FPCR switches turned out negligible (8 of 86k samples,
none in Gunjack). The cost was elsewhere:

- The run loop took the translation mutex on every dispatch and walked a hash
  chain under it (Gunjack: jit_run 23.6 % of all samples, pthread_mutex_*
  about 7 %, plus futex time in the kernel).
- The inline lookup table for indirect branches was only written at
  translation, so a hot return target lost its slot for good to any block
  translated later into the same slot and went through the run loop ever after.
- Every dispatch did an atomic add/sub on a counter all threads share (4.7 %).
- jit_invalidate scanned every block on each cacheflush that touched a code
  page: Unity's Mono JIT does that constantly (72 % of Spunky Dungeon's main
  thread in one run).

Now:
- the run loop looks blocks up without the lock (inline table first, then
  the hash chains, published with release stores); the lock is only taken to
  translate or to chain the exit it came from
- it refills the inline table with every block it dispatches; the refill and
  invalidate_block are fenced against each other, so an invalidated block
  never stays reachable
- HASH_BITS / LTAB_BITS 16 -> 18 (Gunjack translates well over 100k blocks)
- a per-thread in_jit_section flag + fence instead of the shared counter;
  flush_cache sets flush_pending and waits for the flags (threads_any_in_jit)
- per-guest-page block lists: an invalidation visits only the blocks on the
  affected pages and drops invalid ones from the lists on the way

Results on the Frame (same build otherwise, same launch conditions):
- Gunjack, 25-45 s after start with OVP_HEADLESS_TEST: 100,462 -> about
  45,000 samples in 20 s (about 55 % less CPU), jit_run 23.6 % -> 1.6 %,
  pthread_mutex_lock about 7 % -> 0.4 %
- Gunjack, 12-27 s: 72 fps in both, 11,306 -> 9,801 samples, jit_run
  10.4 % -> 1.0 %
- Spunky Dungeon, 12-27 s: 72 fps in both; translated code is only 2-3 %
  there, so no change. The jit_invalidate phase wasn't reproducible headless
  under identical conditions: not yet measured in a played session.
- bench.c (single thread, few dispatches): unchanged.

Tests: run_programs.py (jit + interp, and A32_CACHE_KB=300), bridge harness,
fuzz.py arm-dp / t32 / arm-vfp / arm-neon (0 mismatches), pytest. Both games
pass their launch tests at 72 fps with the final build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:46:01 +02:00
UnicronNLandClaude Opus 5.5 9df1d35003 a32 bridge: right stick up/down = swipe forward/back for NativeActivity games
Gunjack (UE 4.10, Gear VR) has no separate reload or special-weapon input on
the touchpad: every swipe goes to UE4's Gesture_Flick, and
ATDPlayerController::OnFlick reads the flick angle. Below 45 or above 315
degrees (+X, a swipe towards the back of the Gear VR touchpad) it reloads;
135 to 225 degrees (-X, a swipe forward) fires the special weapon. Up and down
swipes do nothing. The bridge sent the stick's up/down as Y swipes, so in
Gunjack those directions did nothing.

For games that read their AInputQueue (NativeActivity), the right stick's
up/down now swipe along the touchpad's own X axis: up = swipe forward
(special weapon), down = swipe back (reload). Left/right are unchanged.
Unity games (AFFECTED, Spunky Dungeon) keep their mapping.

Rebuilt liba32bridge.so. Gunjack was rebuilt and installed on the dev Frame;
the headless launch test passes. Not yet tried in the headset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:45:43 +02:00
UnicronNL a86a86df3d a32 bridge: controller input for NativeActivity games (Gunjack)
Gunjack (UE 4.10, Gear VR) imports no vrapi input functions: it reads the
Gear VR touchpad and back button as Android input events from its
AInputQueue (native_app_glue). The bridge delivered its controller events
through Activity.dispatchKeyEvent, which never reaches native code, so no
control worked at all.

input.c now queues synthetic events in front of the game's AInputQueue:
- AInputQueue_attachLooper (without a callback) registers the queue;
  ALooper_pollAll/pollOnce return its ident while a synthetic event waits
  (ALooper_wake ends a blocking poll), AInputQueue_getEvent hands it out,
  finishEvent frees it.
- The AInputQueue/AInputEvent/AKeyEvent/AMotionEvent imports are written
  by hand ("!" in imports.txt) and answer for synthetic events; real
  events go to libandroid as before.
- Touch positions are scaled to the activity's window size
  (native_activity.c records it).

Mapping (right controller, as for AFFECTED: The Manor): trigger = hold
the touchpad, A = tap, right stick = swipe, B / left menu = back. Unity
and other Java-delivered games are unchanged. A new input_start (VR
re-entry) ends the previous poll thread.

Also: the libandroid functions the host side calls directly are weak
references again, so the PC test harness links (it broke with the
AAsset calls of the signature loader); bridge/test/run.py passes.

Headless on the Frame: "controllers: the game reads its AInputQueue",
72 fps. Not yet verified in the headset.
2026-10-13 14:45:43 +02:00
UnicronNLandClaude Opus 5.5 626950e52e Gunjack runs: original APK signature for UE4's check, GLES session kept across VR re-entry
Gunjack (CCP, UE 4.10, Gear VR) quit after loading because AndroidMain compares
GameActivity.AndroidThunkJava_GetSignature() with the original signing
certificate, and then crashed in the Frame's runtime when it entered VR again.

a32 bridge:
- frame.a32_bridge: original_signature() = Base64.DEFAULT(SHA-1(signing
  certificate DER)) of the original APK (META-INF/*.RSA|DSA|EC), stored as
  assets/fp_a32_signature.txt for libUE4.so games (convert(), shared with
  bridge/convert.py)
- the bridge loads it at activity creation and answers guest calls on the
  method ID of AndroidThunkJava_GetSignature()Ljava/lang/String; with it
  (replaces the unused hex/OpenSSL prototype)

VrApi bridge: after loading, Unreal leaves VR mode and enters it again on the
same GL context; the first frame of a second OpenXR session there aborted in
vrclient (CSharedSemaphoreLinux::WaitFromGPU from the GL binding's
PreBeginFrame). vrapi_LeaveVrMode now keeps a running GLES session and
vrapi_EnterVrMode reuses it for the same context (another context still gets
a new session; Vulkan unchanged). upstream-patches/runtime.cpp.patch updated,
libvrapi.so and liba32bridge.so rebuilt.

Verified on the Frame: headless launch test passes (72 fps after re-entry);
owner-verified in the headset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:45:43 +02:00
UnicronNLandClaude Opus 5.5 4be8e3be37 a32 bridge in FramePort: patch frame.a32_bridge, Spunky Dungeon, Gunjack findings
32-bit-only APKs (Gear VR / Oculus Go games) are no longer "unsupported": the
recipe engine suggests frame.a32_bridge, which converts the APK with the
translator bridge (convert() shared with bridge/convert.py) and ships
liba32bridge.so / liba32rt.so / liba32stub.so (native/build.py --only a32).

FramePort:
- engine._a32: overport off (OVRPort is 64-bit only; the patch ships OVRPort's
  loader chain + its 32-bit platform dispatcher, overport.ensure_runtime_lib),
  only frame.a32_bridge/adapter/launcher of the frame patches, 64-bit-only
  adapter settings dropped, device.text_input_window for Unity; such patches
  are hidden in the UI
- analysis extra.headset (ANALYSIS_VERSION 9): quest / go / gearvr ->
  "Gear VR" / "Oculus Go" platform + Steam tag; catalog `headset:` field
- expects_obb ignores Unreal's OBB-inside-the-APK (assets/main.obb.png)
- catalog: com.fallen.affected, net.mobilemischief.spunkydungeon

Bridge / translator:
- VrApi 1.31 + Unity 2019 (no-config EGL contexts, LOCAL_FLOOR/STAGE tracking
  space), GL buffer mappings per buffer object (fixes a ~10 MB/s guest leak and
  garbled vertices), Go controller mapped to the right Frame controller
- native_app_glue imports (ALooper/AInputQueue/AConfiguration/AAsset*) for
  UE4 NativeActivity games (Gunjack: runs, then quits on its own APK signature
  check, which FramePort doesn't defeat)
- debugging aids: A32_BREAK, A32_STRACE to logcat, lifecycle/LeaveVrMode logs

vrapi-bridge: honours LOCAL_FLOOR/STAGE, picks a window/RGBA8 ES3 config for
no-config contexts; upstream patch updated, libvrapi.so rebuilt.

Verified on the Frame: Spunky Dungeon (Unity 2019.2, OVRPlugin 1.46) plays at
72 fps; AFFECTED The Manor still runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:45:24 +02:00
spoopyghosty0 43994d1c93 Artifacts: adapter, xrshim, VrApi bridges and the foveation darken layer rebuilt from #247's sources (merged with main) 2026-10-12 22:02:42 -04:00
spoopyghosty0 8fead8387d Merge PR #247 (experimental shared eye-tracked foveation, Batman video)
# Conflicts:
#	artifacts/SHA256SUMS
#	artifacts/arm64-v8a/libopenxr_loader_generic.so
#	artifacts/armeabi-v7a/libopenxr_loader_generic.so
#	catalog/triage.yaml
#	tests/test_triage.py
2026-10-12 21:56:42 -04:00
spoopyghosty0 c29cef5f97 FrameBridge: fade_quad draws colour-scale fades as a black quad over the picture (#197)
Opt-in game setting (Picture -> Draw fades over the picture). Batman: Arkham Shadow fades by dimming its projection
layer with XR_KHR_composition_layer_color_scale_bias; with fade_quad=1 FrameBridge adds a black, alpha-blended quad
right after the dimmed layer (VIEW space, 1 m ahead), opacity = 1 - scale in 16 steps. One 8x8 swapchain, refilled
only when the step changes (Vulkan clear on the app's queue / GLES clear in its context); one swapchain per step hit
XR_ERROR_LIMIT_REACHED. fade_quad=2 also removes the layer's own scale. Device (headless, Batman): swapchain created,
level 16/16 on the first frame, no xrEndFrame errors, 72 fps.
2026-10-12 21:40:01 -04:00
spoopyghosty0 b678ce8967 Artifacts: libvrapi.so rebuilt from #244's sources 2026-10-12 21:28:05 -04:00
Lucas-Mathieu 697e96c35f Add experimental shared foveation and Batman video/loading fixes 2026-10-13 01:50:29 +02:00
Nick Leach 34d00efe0a Fix The Climb CryEngine content detection and VrApi rendering 2026-10-12 16:28:22 -06:00
spoopyghosty0 375a9d9fe2 Wander 0.3570: Unity's thread keys share one real key (frame.unity_tls_key_pool, #198)
Bionic allows 128 pthread keys per process. Unity 6 takes about 40, and with Gecko, Firebase and the Frame's own
libraries Wander 0.3570 ran out: Firestore aborted at start ("cannot create thread specific key for
__cxa_get_globals()", #198, #202). Opt-in patch, suggested for Unity 6 with Firebase C++ or Gecko: libunity.so's
pthread_key_create/_delete and pthread_get/setspecific imports are renamed in place to fp_tkey_* (new
elf.rename_imports: undefined symbols only, shared names refused, versions set global) and libfp_tkeys.so
(native/tlskeys) is added to its DT_NEEDED; keys without a destructor become generation-tagged slots behind one
real key, everything else keeps real keys. Approach and the first helper by Klownicle (#198), reimplemented here.
Triage signature pthread-keys-exhausted, PLAYBOOK row, Wander's recipe updated to 0.3570.
2026-10-12 18:06:59 -04:00
spoopyghosty0 aff939a6d8 BlazeRush native fixes (GitHub #57, Klownicle's guide): GL shim gl_int_attribs (integer vertex inputs fed as floats re-set with glVertexAttribIPointer per draw, per-context reflection dropped on link/binary/delete) and gl_highp_markers (matching vertex shaders compute in highp); VrApi bridge answers FOV properties 7/8 before any session with the live FOV saved by an earlier session (also located before tracking); FrameBridge logs the runtime's unscaled eye size 2026-10-10 23:18:41 -04:00
spoopyghosty0 6fb4a52051 Vulkan shim: vk_shader_dump writes the game's SPIR-V modules with a creation index (GitHub #140)
Adapter setting vk_shader_dump=1: every distinct module once to files/fp_vk_shaders/<size>_<sha256>.spv, each
vkCreateShaderModule as one index.txt line (order, time), to find the shader behind a GPU hang for a vk_shader_fix.
Agent v72 collect_diag returns the newest dumped modules (Vulkan and OpenGL ES dumps); diagnostics bundle them.
Triage gpu-hang suggests the dump for the session's graphics API (FrameBridge's swapchain formats).
2026-10-10 15:56:01 -04:00
spoopyghosty0 44bc689348 Shared video decoder (codec revision 8): hidden VP9 frames no longer end the EOS drain early
Iris returns an empty capture buffer (bytesused 0, no LAST flag, time 0) for every VP9 frame that isn't shown
(alt-ref frames split from superframes). FFmpeg's V4L2 wrapper takes any empty capture buffer during a drain
for the end of the stream, so the first hidden frame decoded after EOS ended it and the pictures still in the
driver were lost (two-pass VP9 4K: 573/600 and 617/640, always the last frames); outside a drain the empty
buffers were returned as pictures, which the component then guessed away by counting hidden frames.
build.py now patches v4l2_context.c to requeue empty capture buffers without LAST/ERROR; the drain ends at the
LAST buffer, or after a skipped empty buffer at one silent second, so it can't block. The component's
hidden-frame counting is removed. Dev Frame (Batman's container): two-pass VP9 4K 600/600 and 640/640 in
hardware with the same Y hashes as OMX.google.vp9.decoder (seek phase too), VP9 without hidden frames,
H.264 4K and HEVC 4K 600/600, VP9 surface output 600/600. Two builds (different dirs and NDK copies) give the
same libstagefrighthw.so 141de01f.
2026-10-10 02:22:48 -04:00
spoopyghosty0 3fb2621030 Shared video decoder: per game (frame.hw_video_decode) with a global off switch; 8K retry at start and VP9 fixes from the hw-video-decode branch
- Opt-in per game: only launchers of games whose recipe has frame.hw_video_decode (install stage, no APK change;
  suggested from analysis media_codec, ANALYSIS_VERSION 8) get the codec line (deployment.json hw_video_decode,
  finalize + upgrade_launchers). Games that had agent 70's per-game codec (Batman) keep it; their old
  <base>/frameport-codec folders are removed afterwards. Batman: catalog + migration batman_video_patches
  (frame.hw_video_decode + the hidden adapter setting surface_native, no more package check); 4XVR catalog.
- Off switch: Settings -> Installing "Hardware video decoding" (library video.hw_decode, default on) -> agent
  video_codec_switch (video-codec/disabled, checked by the launcher line and the wrapper); FRAMEPORT_NO_HW_VIDEO=1
  for one game.
- Connection: the codec is checked once per connection, also after a failed install; a Frame keeps the same or a
  newer revision built elsewhere (logged) instead of flip-flopping between PCs.
- Wrapper: the merged media_codecs.xml goes to $XDG_RUNTIME_DIR/frameport-video, never into the version directory.
- Decoder: refused (busy) Iris sessions retry until 20 s after the plugin loaded (SteamVR's link holds a session
  ~12 s at game start), else 2 s, before the software fallback; VP9 capped at 4096x2304 (XML + component, larger
  decodes in software); hidden VP9 frames' pictures dropped. Manifest revision 7.
- build.py builds from paths with spaces and from freshly extracted FFmpeg sources. Artifacts rebuilt here, identical
  from two trees (ext4 and the NTFS repo path, two NDK copies); PR #128's own sources rebuild to its binary.
- AGENT_VERSION 71 (matches the connection's gate); triage hw-video-decoder-busy, PLAYBOOK row, docs.
2026-10-10 00:58:24 -04:00
Lucas-Mathieu 26cee1551e Deploy shared hardware video decoding for compatible Lepton apps 2026-10-10 03:36:56 +02:00
spoopyghosty0 f812ea8716 Codec wrapper (PR #96): find the host's podman when Lepton calls it with the Android guest's PATH (its boot-wait, app-pid and logcat-mirror podman exec calls failed, so launch.log lost logcat and the container could be stopped early) 2026-10-09 17:54:44 -04:00
spoopyghosty0 af9c207ddf FrameBridge: always log the runtime's focus losses and returns with their length (focus: lost / back after N ms), before focus_hold hides a dip; for session triage 2026-10-09 14:03:23 -04:00
spoopyghosty0 5147710fad Merge Batman cutscene playback (hardware HEVC decoding, stereo composition) by Lucas-Mathieu (GitHub #96)
Adapter rebuilt here; the codec plugin rebuilt here with native/hevc/build.py (NDK r27c, Lepton SoftOMX fingerprint 456e912c) is byte-identical to the contributed one (c1c2a73e). Agent version 70 -> 69 (main was at 68).
2026-10-09 13:43:22 -04:00
Lucas-Mathieu b4848c0424 Harden codec deployment and isolate native video hooks 2026-10-09 19:24:51 +02:00
Lucas-Mathieu da638aea99 Merge remote-tracking branch 'origin/main' into fix/batman-cutscene-playback
# Conflicts:
#	agent/frameport_agent.py
#	artifacts/SHA256SUMS
#	artifacts/arm64-v8a/libopenxr_loader_generic.so
#	artifacts/armeabi-v7a/libopenxr_loader_generic.so
#	native/adapter/frame_adapter.c
2026-10-09 18:29:12 +02:00
spoopyghosty0 1e6ce04e4f pose_time_fix: never move a recent predicted display time; monotonic times only told apart when the clocks are more than 4 display periods apart and the time is clearly nearer the monotonic now (GitHub #49: on a Frame with XrTime only ~68 ms ahead of CLOCK_MONOTONIC, a request at the frame's display time after a hitch was taken for a monotonic time and moved +67.7 ms). Found by Klownicle, GitHub #49 2026-10-09 11:35:35 -04:00