YouTube for Quest starts as a 2D panel and only opens its VR part (360 videos, full
screen) when the system has oculus.software.vr.app.hybrid. Opt-in patch
device.hybrid_app (offered when the manifest names the feature): the launcher exports
FRAMEPORT_FEATURES and puts FramePort's Podman wrapper on PATH, which mounts a
permissions file into the container's /system/etc/permissions. Agent v125; triage
hybrid-feature-missing. Checked headless on the Frame: pm list features shows it.
Get-ChildItem lists a short path like C:\Users\RUNNER~1\... under its long name, so the
relative paths cut with the short path's length were wrong: the backup, the removal of
old-only files and the rollback went to wrong folders. CI's update smoke test caught it.
OVRPort's patch_remove_uses_library drops every <uses-library>, also ones
Lepton's Android 11 provides: YouTube VR (org.apache.http.legacy) then dies
at start with ClassNotFoundException org.apache.http.protocol.HttpRequestHandler.
New patch frame.keep_system_libraries (default on where the original
manifest names such a library) puts those entries back after OVRPort,
always android:required="false". Lepton's libraries are one list
(system_libraries.LEPTON_LIBRARIES); analysis extra.uses_libraries reads the
original manifest (ANALYSIS_VERSION 27). Builds of apps without such an entry
are unchanged. Triage system-library-missing, PLAYBOOK row, tests.
While a now-mode library update waited for FramePort/Desktop Mode/a game to close it held the shortcut lock;
the next install's library_mode call blocked on it until the 600 s agent timeout. library_mode now tries the
lock once, shortcuts journals changes into the waiting worker (merged after Steam closes, one restart), the
later queue waits at most 30 s, and runs only drop the journal entries they applied. Agent v124.
Crisis Brigade 2 reloaded's office/alleys/challenges/mansion/ta1-3 .222.<package>.obb
copies with a lower-case package part reached the Frame under names the game can't
open. obb_names renames such <kind>.<vc>.<package>.obb files (case or copy suffix,
kind spelling kept) without an "another app" warning; NAMED_OBB kinds may contain
digits (ta1 was skipped by the scan) and copy-suffixed files of any kind are found.
A failed copy was rolled back without deleting the files the new version had added: the old version then started next to a new patch module and crashed at start (cannot import name 'ORG_MARK'). The swap script now changes nothing while FramePort or a program file is in use, moves the old version's files the new one doesn't have into 'previous' (only inside the bundle's own folders), and a rollback deletes what the update added. Bundles carry frameport/_bundle_files.txt; at start the app removes or ignores leftover package files and tells the user to reinstall when compiled modules don't match. update_smoke.py runs a locked-file rollback and a leftover-module update on Windows.
- Home, meta descriptions: Linux apps alongside Quest, PC VR and Android; shorter notice, matching the README.
- Setup page: the app's labels ('Already set up?', 'Connect by address…'); Pair new host asks by itself.
- Tour: the install video is about 90 s.
- Docs: CONTROLLERS.md is a site page now, and the image sync copies docs/images subfolders (its diagram lives in
controllers/); the 'game won't start' path starts with the FAQ.
- 'it'/'this'/'that' replaced by the noun where the referent was unclear (walkthrough, setup, download, install
pages, compare, PC VR panel, demo app).
Every page this branch touches: sentences that started with 'It'/'This'/'That' after naming two things, and short
answers ('report it', 'click it'), now say what they mean. Two FAQ headings start with 'The game' (no page links to
them).
Diagnostics starts with what a user sends and moves the maintainer workflow into one paragraph; contributing folds
the tested-game section into the quick contributions.
Same instructions in fewer words: setup alternatives as a list, moving from a PC in three short steps, room editing
as numbered steps, save backups once in the FAQ. The FAQ's 32-bit answer was outdated: the 32-bit bridge is
suggested for such games now.
'Hundreds of games' instead of a number that goes stale (README, COMPATIBILITY, the site's comparison). Shorter
notice, features, quick start and comparison bullets; one-line docs index rows.
The page named report files under a personal Windows home folder; the measurements stay, the paths go. A short
current-state section (OpenSSH SFTP for big files, 3 parallel uploads) heads the page, so the older 'production
still uses Paramiko, default 2' statements read as history.
The title pinned SteamOS 0.3.0 although sections cover up to 0.5.x; PC VR under Proton isn't 'unverified' any more,
and Revive's hooks don't work there (the old line said they did). sshd comes with Developer Mode and FramePort's key,
no password. The headset-view webcam does show games. Foveation preferences moved next to the other foveation
notes (they sat under Monitoring sources), the scan preview below the room overview. Room tab paths.
The startup table had a row above its header separator (it rendered as plain text). PC VR games are installed
unchanged by default, Revive only for LibOVR games; the two duplicate -3001 rows are one now. Upload/SSH, install
link and store-detail rows moved out of the startup and PC VR tables into their own section, the OutRun rows into
the startup table. No agent version numbers; room scanning in the headset exists now; Add from a link.
CONTRIBUTING: Share working recipe / Report a problem under their menu groups, Language lives in Appearance,
patch_docs and showcase pointers. ARCHITECTURE: bundles and backups as sources, session triage, remote catalog,
PC VR games installed unchanged with Revive only for LibOVR games, a Linux apps section. FRAME_SETUP: menu entries,
services and FEX that FramePort adds later. DIAGNOSTICS: no agent version numbers. 'for example' instead of e.g.
INSTALL: menu paths through the game menu's groups, one section on when games appear in Steam (was buried under
running FramePort on the Frame), save backup folder/automatic snapshots, shorter overlay and first-launch notes,
rename/session/settings commands. FAQ: foveation lives in Game settings now, Exit game pauses games first, save
backups as a short list, controller buttons.
vk_shader_fix (vkshim) and zink_shader_fix (shader-fix layer) match a shader by size + SHA-256, so a game update that changes the shader made the fix silently do nothing. Once per session, when fixes are configured and none matched 5 minutes after the first shader module (checked only at module creation), the shim/layer logs '<prefix>: N shader fix(es) configured, none matched after M modules in S s (game updated?)'. Triage shader-fix-unmatched (info, report) + PLAYBOOK row. Artifacts rebuilt; no revision bumps.
The verified 3.4.3-aa54c3f layout keeps its exact guards. Any other dispatcher (the older 3.4.3-23204ea, later
runtimes) gets Quest 3 when Quest 2's name and UUID each occur once and each is loaded by exactly one adr /
adrp+add / adrp+ldr q sequence; otherwise nothing changes and the build keeps Quest 2 with a WARN, as before.
Checked against both runtimes in the OVRPort workspace.
The patch finds its code by name, leaves a build that already saves unchanged and refuses when the code isn't
there, so it applies to every Just Dance VR build without parsing version names.
Just Dance VR (0.5.4 and later) loads its progress from a local save at
start, but LocalSaveEvents.RequestSave is empty (a single ret in
libil2cpp.so), so the SaveValueRequested event that LocalSaveManager
listens to is never raised and the OfflineSave file is never written.
On a Quest the progress lives in PlayFab after a Meta sign-in, which the
Frame doesn't have.
The patch gives RequestSave its declared body (raise SaveValueRequested
with the key and value). Everything is found per build: Cpp2IL (the
shared IL2CPP run) gives the methods and field offsets, capstone decodes
SaveSystemReady for the TypeInfo slot, the metadata init and the
delegate layout, and the new code goes into a new R+X segment
(elf.append_segment); RequestSave's ret branches there. A build whose
RequestSave has a body is left alone; anything unexpected is refused
with the reason. Nothing online is touched.
Triage signature justdance-local-save-off, gated with the new
unless_patch key (not when the fix is in the build); PLAYBOOK row,
DEVICE_NOTES.
refresh_remote also caches catalog/triage.yaml from the same tree listing
(triage-gh-<sha>.yaml); triage.database() merges it per signature id over
the bundled file, dropping remote signatures this app can't use (malformed,
unknown field, unknown patch/adapter key, min_app too new). Bundled-only
signatures stay; a bundled one dated later wins.
QuestZDoom leaves GL_INVALID_ENUM pending every frame; the runtime's GL check fails on it at the first
swapchain and its GL interop then leaks ~105 GPU objects a second (debugfs gem, headless), until the
game slows to a crawl and the GPU driver oopses. gl_error_clear keeps the count flat. Suggested for
every Team Beef port; QuestZDoom catalog recipe with it; PLAYBOOK row and device note.
e5921ccb kept libVkLayer_fdm_darken.so out of The Climb 1's libAndroidLauncher.so but also stopped shipping the
layer file, while frame.adapter links every arm64 FrameBridge build against it: FrameBridge didn't load, the VrApi
bridge logged 'xrInitializeLoaderKHR failed: OpenXR result -7' and CryEngine aborted (SIGTRAP; dev build zip, static
check warned the NEEDED was missing). diagnostic_support now always ships the layer; frame.adapter validate fails
without it.
The 1.3.0 start failure itself: the layer exports vkEnumerate{Instance,Device}{Extension,Layer}Properties for the
Vulkan loader and elf.add_needed linked it first, so a renderer importing those from libvulkan.so (libCryRenderVulkan.so
does, The Climb 1's monolithic launcher imports Vulkan directly too) got the layer's answers (no extensions /
VK_ERROR_LAYER_NOT_PRESENT). elf.add_needed(first=False) links it after the existing entries for every other engine.
frame.adapter package revision 5 for com.crytek.climb; triage vrapi-bridge-no-loader.
Xelee opens MeleeActivity (a 2D SDL launcher that picks the game) and starts MeleeXrActivity from there. Lepton
runs VR apps headless, so the launcher was never seen and OpenXR never started. device.text_input_window is now
suggested for apps with an analysis vr_activity, the deployment recipe records flat_launcher (unless
frame.start_activity starts the VR part directly), and agent v122 keeps SteamVR's theater mode on for those, so the
launcher is in front of the player instead of a dashboard page. Catalog: Xelee gets the window.
- ui/popmenu.PopMenu draws menus in page.overlay: panels size to their labels (no 280 px cap, wrap past a
maximum, scale with the UI), open at the click or under the button, flip and clamp into the window; submenus
open to the side with room (hover with intent delays, click, or the arrow keys); Enter/Esc; closes on an outside
click, a choice or a view change; portal edge in dual themes, no fade with Reduce motion.
- The game menu (app.game_actions) is one structure for the Library right-click and the page's "…": Play,
Install/Update, Game settings | Open game page, Select | Update, On the Frame, Saves, Name and artwork, Recipe,
Troubleshoot submenus | Uninstall, Remove from library. Launch tests are in Troubleshoot.
- Files, Screenshots and Monitor right-click menus use it too; the multi-select menus are unchanged.
- ui_smoke: menu steps open a submenu and use the keyboard.
- Menu rows put the label next to the icon themselves so a label wider than Flutter's menu limit wraps (Flet's own
icon row overflowed); menu buttons scale the width limit with the UI scale.
- Library right-click: Play, Install/Update, Game settings | Open game page, Select, More actions. Rename, the
newer-version updates and Remove from library stay in the game's menu; uninstalls on the game page and Frame page.
- Right-click on one of several selected games: Install them on Frame / this PC, Select all shown, Clear selection.
- Game page: the recipe's actions (reset, save as known-good, share) are buttons on its recipe card; Back up saves
is only the button next to Play.
Agent v121. Verified headless on the dev Frame with AC Nexus launched through Steam: OnApplicationPause(true)
0.15-0.29 s after TerminateApp, container gone 1.5-1.7 s after it (0.65-0.8 s before); a game that doesn't
answer is capped (3.2 s). The Monitor's End game calls Steam's TerminateApp past the hook (no second pause).
Scan a folder, Add an APK, drag and drop, install links and the CLI take them. The arm64-v8a split's libraries and assets are merged in Python (split attributes and Play's splits.required flag removed); language/density/feature splits go through REAndroid's APKEditor (downloaded into tools/). OBBs from the bundle or next to it become the game's data. Old encrypted .apkm files are explained, not opened. Headless device check: Escaping Wonderland split into base + arm64 split, merged, built, installed and launch-tested at 72 fps.
main.203.com.Armature.VR4-003.obb is sent as main.203.com.Armature.VR4.obb (#450); the files on the PC keep their
names and the game page shows an info note instead of a warning. Another app's name (#129), a copy next to the
right-named file, two copies for one name and a copy suffix without a versionCode stay warnings. plan_update compares
the Frame's names (no repeated Update on Frame), a file installed earlier under the copy name is renamed on the Frame
instead of uploaded again, and on-Frame hardlinks (#433) keep the PC file as their source. Loose copy-named OBBs next
to an APK are found by name. Unreal chunk files and split parts are no longer flagged as named for another app.
Killing the container (launch.sh podman kill, Lepton's reboot -p + podman stop) never paused the app: no onPause, no Unity OnApplicationPause(true). am start of the HOME intent pauses it in Lepton's Android 11 (input keyevent does nothing headless); the script waits for STOPPED + 0.4 s, capped at 3 s. upgrade_launchers adds pause_game to existing Lepton launchers. Steam's own Exit game signals the whole process tree (container included) at once and isn't covered yet. Agent v120.
device.text_input_window makes the game's Android window a gamescope "desktop game" overlay. SteamVR put it into
theater mode before the game's OpenXR session existed and opened the dashboard on it when the session started: the
Android screen with the controllers as a mouse pointer. The dashboard worker now turns dashboard.autoShowGameTheater
off while such a game runs (holds per worker, restored after the last one, by the next start or ensure_host_fixes
after a killed worker, and by purge). It also recognises SteamOS 0.5.6's open_dashboard_action as the player's
dashboard button instead of closing every press again. Agent 119.
Patch frame.browser_handoff (opt-in; offered for 2D apps, suggested-off when the manifest's <queries> ask for a
browser: analysis browser_query, ANALYSIS_VERSION 26) adds an http/https VIEW + BROWSABLE activity (no host) and its
class (native/java-handoff -> artifacts/dex/browser-handoff.dex) to the app. Lepton's ActivityStarter.execute takes
every VIEW intent before resolution and writes steam://openurl to /lepton/steam.pipe, so the stand-in normally never
runs; started directly it writes the same line itself. axml: queries_browser, browser_activities,
add_browser_activity (missing android: attribute names go into the resource-mapped part of the pool).
A 2D app with an APK patch turned on is now built instead of installed unchanged (engine.flat_as_is in set_recipe):
hand-enabled APK patches on 2D apps were never applied. Verified headless on the Frame with a test app.
OutRun quits with "AppSW recommended buffers unavailable" whether XR_FB_space_warp is offered or hidden.
New per-game adapter setting space_warp_emul (off by default, native/adapter/space_warp.c): offers the
extension (emulated when the runtime lacks it, overrides hide_space_warp), reports a motion-vector size of
eye/4 (the Frame's runtime answers the full 1728x1728 eye), lists RGBA16F and depth formats after the
runtime's, serves motion-vector swapchains (RGBA16F/RG16F at the reported size or at most half the eye) and
their depth swapchains as FrameBridge-owned GL textures / Vulkan images (3 images, round robin, wait/release
in front of every other hook), and strips XrCompositionLayerSpaceWarpInfoFB at xrEndFrame (eye depth info is
now kept when space warp info isn't first in the chain, also for hide_space_warp).
Headless on the dev Frame with Pinball FX VR (UE5 + OVRPlugin, Vulkan): both 376x376 pairs served,
600/599/598 acquire/wait/release by FrameBridge, space warp info removed, 0 xrEndFrame failures, 72.2 fps like
its own recipe. Triage appsw-required, catalog entry for OutRun (fb_color_space + space_warp_emul),
PLAYBOOK row, DEVICE_NOTES section, host harness for the GL/Vulkan paths.
The paste field (expand) shrank to a few pixels in narrow windows while the buttons kept their size. The view now
measures the buttons and the row (on_size_change) and stacks the buttons, right-aligned and wrapping, below the field
when it would get less than 240 px (scaled).
a8e01c01 skipped only libCryRender*; The Climb 1 has no separate renderer, so engine_lib() picked
libAndroidLauncher.so and the layer was still linked into it. Both reporters' zips (1.3.0 and a dev build with
a8e01c01) crash identically: SIGSEGV fault addr 0xf0 under CreateSystemInterface+320 right after Vulkan instance
setup. frame.adapter package revision 4 for com.crytek.climb marks existing builds for Update on Frame.
Valve's VALVE_fdm_injection layer gives every eye swapchain a density map covering the whole swapchain with its
FOVE_LEVEL rectangle at the swapchain centre; it never uses the submitted imageRect or the eye's optical axis, so games
with smaller or shifted eye rects (dynamic resolution, AC Nexus 0,0 / 287,0 1726x1728 in 2016x1728) got the sharp area
~14% of the view to the side. VK_LAYER_FRAMEPORT_fdm_centre (native/foveation/centre.h, second layer in
libVkLayer_fdm_darken.so, right below Valve's layer when FDM_DEBUG has "enable"; not for The Climb 2;
FRAMEPORT_FDM_CENTRE=0 turns it off) replaces the per-eye density-map offsets: the eye's optical centre (Fixed) or
Valve's gaze re-based on the eye's rect (Eye-tracked). FrameBridge reports the views (fdm_views.c).
Changes after the first version (b2be40af, reverted 2ce1c505) failed in the headset:
- Eye-tracked black: Valve declares fragmentDensityOffsetCount 1 for two-view passes while Turnip reads one offset
per view regardless, so the right eye's offset came from uninitialised stack memory. Every offset Turnip reads is
now written (view count from the pass's view mask, eye from the map view's layer, each eye's gaze from Valve's array).
- Fixed crash: the layer no longer flags images (it had flagged external-memory images Valve leaves alone). Valve's
own disable_offsets path also crashed AC Nexus without this layer (Turnip under fdm_injection_CmdBeginRendering), so
Fixed now drops disable_offsets in-process once the layer is in the chain and overrides Valve's gaze offsets with the
optical centre; if Valve read the option first its maps carry no offset flag and passes are left alone.
Headless on the dev Frame, AC Nexus, 130 s per run, darkening test, snapshots every 15 s: centring Fixed (2 runs) and
Eye-tracked ran without crash at 72 fps, both eyes' sharp area within 2% of the optical centre (Valve only: +14% Fixed,
+25% left eye Eye-tracked; Valve-only Fixed crashed in 1 of 2 runs). FrameBridge snapshot=N reads Vulkan eye images
back; FRAMEPORT_FOVEATION_TEST=2 paints 1/fragment area. Triage valve-fdm-render-crash. Host harness
tests/test_fdm_centre.py. The sharp area's size still snaps to Turnip's GMEM bins (driver design).
Valve's VALVE_fdm_injection layer gives every eye swapchain a density map that covers the whole swapchain and centres
its FOVE_LEVEL rectangle there; it never uses the submitted imageRect or the eye's optical axis. Games that submit a
smaller or shifted rect (dynamic resolution, asymmetric eye rects such as AC Nexus 0,0 / 287,0 1726x1728 in a
2016x1728 swapchain) got the sharp area ~14% of the view off to the side, towards the bottom for corner-anchored rects.
VK_LAYER_FRAMEPORT_fdm_centre (native/foveation/centre.h), a second layer in libVkLayer_fdm_darken.so inserted right
below Valve's layer when FDM_DEBUG has "enable" (not for The Climb 2; FRAMEPORT_FDM_CENTRE=0 turns it off), rewrites
the per-eye density-map offsets at the end of each pass: Fixed puts the sharp area on the eye's optical centre (it adds
the offsets and the offset image flag Valve leaves out with disable_offsets), Eye-tracked re-bases Valve's gaze offset
on the eye's rect. FrameBridge reports the views (fdm_views.c: swapchain size, layer, imageRect, optical centre).
FrameBridge snapshot=N now also reads Vulkan eye images back (both eyes, half size, rect + FOV logged), and
FRAMEPORT_FOVEATION_TEST=2 paints 1/fragment area. Headless on the dev Frame (AC Nexus, Fixed, darkening test):
sharp-area centre vs optical centre +203,+43 px -> +26,+11 at 1x, +276,+65 -> +12,+17 at 1.5x, +407,+87 -> +47,+31
at 2x. The size still snaps to Turnip's GMEM bins (density is chosen per bin), so Small equals Large at 1x and the
shape changes between render scales; that part is the driver's.
Docs: DEVICE_NOTES "Foveation: sharp area centre and size", FRAME_RUNTIME, PLAYBOOK row, native/README; triage
dynamic-resolution-foveation is quiet once the centring layer ran. Host harness tests/test_fdm_centre.py.
Without a worn headset the session stays SYNCHRONIZED: the VrApi bridge ended every frame empty and the a32 bridge dropped every projection with an untracked default pose, so FrameBridge's snapshot=N had nothing to read. Under OVP_HEADLESS_TEST (tests only) the VrApi bridge submits anyway and keeps GetTrackingTransform(3)'s floor 1.6 m below the eye origin (the runtime's headless STAGE sat 0.865 m below it, and Ghost Giant hid everything behind "Return to play area"), and vrapi.c keeps the game's pose. Harness: headless submit keeps the layer. Checked on the Frame: Ghost Giant's intro videos and main menu in the snapshots.
The in-place flip raced Unity, which finishes some panels after xrEndFrame without acquiring them again, and its
blits/readbacks waited for the GPU inside xrEndFrame. swapchain_pool.c now owns flips (also without swapchain_pool):
a flagged pooled panel's atlas copy is a blit with reversed rows, a flagged runtime swapchain's quad gets a per-frame
atlas area with an upside-down blit of its released image. The app's images are only read; no CPU waits. flip_dump
reads back what the runtime is given (async); layer_debug logs copies and waits per 5 s. The late-drawing
fingerprints, scratch image and in-place flip are gone. Headless: every intro panel upright as submitted, 72 fps.
OVRPort's platform loader exports ovr_User_GetOrgScopedID, ovr_Message_GetOrgScopedID and ovr_OrgScopedID_GetID as
`mov x0, xzr; ret` (runtimes 3.4.3-23204ea and -aa54c3f): request id 0, no message ever, so Meta's C# wrapper logs
"An unknown error occurred. Request failed." and the callback never runs (Eleven Table Tennis waits in
<BuildIdentity>b__0; Cortopia's Escaping Wonderland #360 / TMNT #422 too).
libfp_langpack.so (native/langpack) now answers it when its @FPORGID@ marker is set (or FRAMEPORT_ORG_SCOPED_ID=1):
a fresh request id and a successful ovrMessage_User_GetOrgScopedID (0x18F0B01B) whose ovr_OrgScopedID_GetID is an
ovrID hashed from the (already stable, frame.ovr_stable_user) user id: FNV-1a 64, top bit cleared, never 0. A new
@FPLANGS@ marker lets the library be linked for that alone with the language-pack calls passed through to the loader
(FRAMEPORT_LANGPACK_SERVE overrides); builds with frame.langpacks/asset_files behave as before.
ovr_User_GetUserProof (Eleven's next request) is answered by OVRPort itself (queued message with a nonce), so it is
left alone.
New patch frame.ovr_org_scoped_id (order 35, default on, suggested for games known to wait: Cortopia's analysis flag,
Eleven Table Tennis, TMNT), triage signature meta-org-scoped-id-wait, PLAYBOOK row, host tests against the stand-in
loader. frame.cortopia_org_id stays until the generic answer is headset-verified.
The first device run (AC Nexus, Quest 3) pooled nothing and said nothing
about why. The pool declined any chained create info and any mipCount
other than 1; on Vulkan it now accepts a XrSwapchainCreateInfoFoveationFB
(irrelevant to a private image) and honours XrVulkanSwapchainCreateInfoMETA's
extra create flags and usage, and keeps the app's mip levels (barriers
cover every level, level 0 goes into the atlas). GLES keeps its rules.
Every decline is logged once per reason ("swapchain_pool: not pooled:
..."): session mismatch (also from the proactive check), array/faces,
samples, mips, usage, flags, size, format, chained struct type, Vulkan
device, GL context, slots, atlas refused, image allocation, atlas full;
plus one line when the 8-swapchain trigger starts pooling. The
xrCreateSwapchain log gains mips= and the first chained struct type.
Harness: the device sequence (1728 eye set, 2x2 and 1024x1024 usage 0x39,
2016 eye set, twelve 0x39 panels with foveation/META chains and a 4-mip
panel, flip-tracked runtime creates) pools every panel and the 2592x2256
set then fits under a 15-swapchain cap; decline reasons are checked.
swapchain_pool now also covers Vulkan sessions (XR_KHR_vulkan_enable/2):
from eight live app swapchains, single-layer format-43 colour panels get
private device-local VkImages (app usage + transfer, returned as
XrSwapchainImageVulkanKHR, UNDEFINED -> COLOR_ATTACHMENT_OPTIMAL at the
first acquire) packed into up to two 4096x4096 sRGB runtime atlases.
xrEndFrame records one command buffer per frame on the binding's queue
(3-deep fence ring, submits under flip_lock): vkCmdCopyImage from each
referenced panel into the waited atlas image, which returns to
COLOR_ATTACHMENT_OPTIMAL before its release. Image ownership, crops and
layer ordering follow the GLES pool; the whole layer rewrite holds the
pool lock so a destroy cannot free an image with recorded copies.
Pooled Vulkan panels stay tracked by flip_vk.c (in-place flips keep
working). Other formats and arraySize-2 eye images keep runtime
swapchains. GLES behaviour is unchanged.
AC Nexus: Quest 3 identity again with swapchain_pool=1 (min_app 1.3.1),
frame.adapter revision 3 for it only. Settings text, triage diagnosis,
PLAYBOOK, DEVICE_NOTES, native README, PATCHES.md and the i18n template
updated. Host harness gains a recording Vulkan double (layouts, copy
regions, ring, failures, cleanup, AC Nexus swapchain count: 12 live with
the new eye set). Not verified on the device.
On the device Unity got past AMediaFormat_new and stopped at AMEDIAFORMAT_KEY_AAC_PROFILE, falling back to its JNI path again. The guest runtime now defines the 25 keys libunity.so references (AOSP's values; the host's libmediandk compares key names by content) and exports them; the harness checks each one via dlsym and uses them as format names. Ghost Giant: package revision 3.
TMNT: Empire City awaits Users.GetOrgScopedID in Cortopia.Scripts.Platform.OculusPlatform.<Login>d__17 (after the
licence check and GetLoggedInUser, behind the same internetReachability == LAN check as Escaping Wonderland), which
OVRPort's platform library never answers. The patch now also looks there (parse_methods: "<Login>d__.MoveNext" =
that nested type's MoveNext; a plain MoveNext would be FetchEntitlements'), the analysis flag also covers
OculusPlatform directly in Cortopia.Scripts.Platform (ANALYSIS_VERSION 26), the triage signature matches the
<Login>d__ stack. The entitlement request is untouched.
Unity 2017.4 (Ghost Giant) looks up 43 libmediandk functions and fell back to its JNI path when AMediaFormat_new was missing; that path reads MediaCodec ByteBuffers above 4 GiB (GetDirectBufferAddress returned 0), so the intro video never played. Format strings/buffers get guest copies freed with the format; input buffers are mirrored and copied to the codec at queueInputBuffer, output buffers copied in at getOutputBuffer (the range dequeueOutputBuffer reported). Harness: fake libmediandk with buffers above 4 GiB + guest test library. Triage a32-video-ndk; Ghost Giant gets Update on Frame.
The game picks Meta/PICO from SystemInfo.deviceModel and throws for Lepton's
Valve Lepton. frame.gazzlers_quest_platform (catalog recipe) makes
PlatformDeterminer.GetAndroidPlatform return Platform.Meta (Cpp2IL lookup);
triage signature, PLAYBOOK row, catalog entry, test.
Iris takes an 8K session only while no other is open. Checked in Batman's container on the dev Frame (SteamOS 0.5.6):
the small video now stays off Iris, but DRM2.mp4 (8192x4096 HEVC) is still refused because Steam's web helper keeps
the Iris decoder open, so Batman's 8K cutscenes stay in software. hold_probe.cpp holds a decoder for such tests.
pojlib's API.hasConnection() only counts Wi-Fi; Lepton's Android reports Ethernet, so the JRE download was skipped
("libjvm.so not found", GitHub #18). hasConnection() returns true (in-place classes.dex edit). Catalog recipe +
QuestCraft's package (no ANALYSIS_VERSION bump for one game family; new scans record pojlib_connection), triage
questcraft-no-java-runtime, PLAYBOOK row. Checked on the dev Frame: the installed APK carries the edit and starts
(72 fps headless); the download itself runs only after Play in QuestCraft's menu, which needs the headset.
1.3.0 linked libVkLayer_fdm_darken.so into every arm64 game's engine library; in libCryRenderVulkan.so CrySystem
initialization then fails right after the renderer lists its Vulkan device extensions, and the shutdown crashes
(SIGSEGV in CSystem::ShutDown). Bisected headless on the dev Frame by swapping libraries between the September
known-good APK and a current build: only the modified renderer breaks it. Climb 2's private VrApi bridge still
loads the layer itself (72 fps headless with this change). frame.adapter package revision 3 for both games;
triage cryengine-init-failed.
With Quest 3 the game switches to a 2592x2256 eye buffer after the intro; by then its UI panels have used up the
runtime's ~15 swapchains per session, so the new eye buffer is refused (XR_ERROR_LIMIT_REACHED) and it stops
drawing. Headless on the dev Frame, same build: Quest 3 frames stop, Quest 2 holds 72 fps.
From issues #409#410#412#418#421#435#441#447#448#453#456#461#462#464#472 via
scripts/catalog_from_issue.py. GoldenEye VR (no sound), JKXR (stutters after minutes) and Umurangi Generation
(freezes when saving a photo) are "issues"; Simpsons and Astro VR Host setup steps point to the Files tab, the
long instructions moved to details.
The software Main 10 path narrowed HDR10 pictures to 8 bits as they were, so
PQ shown as SDR looked grey and BT.2020 colours dull. hdr_tonemap.h maps
brightness with BT.2390's EETF (203 cd/m2 reference white = SDR white,
highlights rolled off up to MaxCLL / the mastering peak / 1000 cd/m2) and
converts colour per chroma sample through linear light into BT.709, moving
out-of-gamut colours toward grey of the same luminance. The component then
prefers its bitstream colour aspects, so surfaces and its RGBA conversion see
BT.709. Bands run in the colour helper threads (about 10 ms per 1080p picture
on one big core). Decoder revision 10; artifacts rebuilt with build.py.
Iris on the Frame's kernel (6.18) offers HEVC Main 10 only into its
compressed Q10C capture format. The component always asks for NV12, and a
Main 10 stream in such a session returned no pictures: playback stayed at
0:00, the flush on leaving timed out (ACodec -110) and the next video hit
MediaCodec::reclaim, so every later decoder start in the app failed until it
restarted (seen in Bigscreen Beta with local files over DLNA).
- hevc_profile.h reads general_profile_idc from the parameter sets
(CODECCONFIG or the first access unit, Annex B or hvcC); streams beyond
Main never open Iris and decode in FFmpeg's software HEVC decoder, which
the component already contains.
- Its 10-bit planar pictures are rounded to 8-bit planar before the existing
outputs (no HDR tone mapping).
- The HEVC component lists Main 10 (level 5.1), so format-aware players
select it instead of reporting NoSupport and using it anyway.
- Decoder revision 9; artifacts rebuilt with native/hevc/build.py (the same
toolchain rebuilds revision 8 byte for byte).
Tested on a Steam Frame in Bigscreen Beta: 1080p and 4K Main 10 clips and a
real 1080p Main 10 episode play; 8-bit HEVC and H.264 afterwards play too.
FFmpeg's software HEVC decoder on the Frame's CPU, 4 threads as the
component uses: 1080p Main 10 episode 232 fps, synthetic 4K Main 10 72 fps.
The owner's tuned room-scan cleanup (2026-10-18) ported to C, pass for pass: plane offsets re-estimated from the
scan, one-sided structural bands, flat wall/floor/ceiling panels (or snap), near clearing, erase spheres, the walked
volume from the recording's head path, furniture tops, clipping with the unseen-edge allowance, islands, thin
floating pieces, Taubin smoothing; label rules on the scanner's volumes (merged/nested/outside boxes removed, shape
relabelling, SCREEN flatness from the raw mesh, door/closet suggestions in scan.json).
room.txt + room-mesh.bin are now refined (ceiling re-estimated); the unrefined output stays as room-raw.txt +
room-mesh-raw.bin with the same UUIDs; scan.json holds heads, settings, report, labels, suggestions. The scan path
refines its raw files as read back, so --refine-only on them reproduces the scan byte for byte; --refine 0 writes
the old output. The fusion grid is freed before the refinement (peak memory unchanged).
- detect: many Meta XR SDK games carry the Depth API without using it; until a headset check shows more games
with the symptom (or OVRPort is found advertising XR_META_environment_depth, which a FrameBridge hide setting
would fix generally), the patch comes from Laser Dance's catalog recipe or the user's choice.
- ApkWorkspace.size/contains: entries are searched in 4 MiB chunks instead of read whole.
- Laser Dance: the fixes' original values at all seven places together identify the build (no versionName was
recorded with them); another build gets none of them.
- tests: lz4 is skipped when missing, the known/unknown Laser Dance build, chunked search.
The PR turned it on for every Unity game with USE_SCENE or MRUK's library: a game-logic change keyed to one
game's symptom (MRUK games mostly don't use OVRSceneManager at all). A scanned room around the player usually
passes IsUserInRoom by itself.
- Limit _patch_shader_proximity_fade to known builds (Laser Dance) and known shader signatures/keywords to avoid modifying unrelated shaders
- Rename HARD_OCCLUSION and SOFT_OCCLUSION keywords in .assets files only, leaving global-metadata.dat intact so managed code cannot enable renamed occlusion variants
- Defer meta_depth_occlusion library migration for entries analysed before the field existed until re-analysis, and update user recipes on re-analysis
- Bump ANALYSIS_VERSION to 23 so older library entries are re-analysed for meta_depth_occlusion
- Implement frame.meta_depth_occlusion to zero out Meta EnvironmentDepthManager methods in libil2cpp.so and neutralize close-proximity depth shader occlusion in Unity assets
- Implement frame.unity_scene_active_rooms to bypass Meta OVRSceneManager active room position evaluations in libil2cpp.so
- Recommend frame.meta_permissions when spatial data or scene permissions are declared
- Remove redundant hand-tracking heuristic forcing controller_fix=0
- Add unit tests for depth occlusion, active room bypass, and manifest heuristics
- Emulate XR_META_spatial_entity_discovery (xrDiscoverSpacesMETA, xrRetrieveSpaceDiscoveryResultsMETA) and XR_META_boundary_visibility
- Add system properties for space discovery and boundary visibility
- Fix semantic labels length and null termination
- Allow xrGetInstanceProcAddr with null handle for emulated procs in xrshim
- Trim composition layers exceeding runtime maxLayerCount in xrEndFrame and validate quad extents
- Hook xrshim when scene_emul is enabled in adapter settings
- Update prebuilt native artifacts and checksums
Adapter rebuilt from the combined source (fixed_resolution + swapchain_pool), libfp_xrruntime.so back in SHA256SUMS, ANALYSIS_VERSION 24 for static OpenXR loaders (23 is frame.main_process), agent v108 for the static-loader launcher line (above the MR branches' 104-107 so a Frame with one of those still updates), patch docs and translations template regenerated, Ruff line length.
Add opt-in swapchain_pool with private GLES images and up to two packed
runtime atlases. Start at eight live app runtime swapchains to reserve
headroom before the session cap. Preserve image ownership, GL state,
source fences, crops and the existing cylinder/equirect layer ordering.
Enable for 4XVR, add count-limit triage without unknown suggestions for
older apps, and document the pending headset menu check. Leave default
recipes and adapter revisions unchanged. Rebuild arm64/arm32 artifacts.
Validation: 132 targeted tests passed (2 skipped), including the native
runtime/GL harness; Ruff and generated docs pass. Full host suite:
2095 passed, 137 skipped, 12 failures and 16 setup errors, all denied
socket creation in this sandbox. No headset validation or push.
Detect and redirect static loader manifests and Android brokers, ship a runtime negotiation shim and the inner loader chain, and maintain per-game manifests on install and launch. Add the PrimedGun recipe, triage and host coverage.
Route OVRPlugin recommendations through the existing extension shim, retain native loader hooks, and log projection rect changes once per second per eye. Enable the fix for Batman, document verification, and rebuild both adapter architectures.
2026-10-19 00:03:20 -04:00
387 changed files with 36005 additions and 2252 deletions
metadata (agent `ROOM_REFINE_DEFAULTS` must match, a test checks), saved non-defaults in the Frame's
`rooms/refine.json`, used by every build; Apply = job → agent `room_refine` (`--refine-only` on a copy of the room
folder from `room-raw.txt`/`room-mesh-raw.bin`, scan.json → status `report`/`labels`/`suggestions`); Labels =
the label rules' decisions + Add as door / Add as closet; Furniture & screens = box editor (click a box in the
canvas: `view3d.pick`) + doors/windows on a wall (`mr.room.plane_on_wall`), Save = `rooms.put_room` → `room_put
edited` (keeps raw files, `edits.txt` survives a new clean-up; "Back to the scan's boxes" = keep_edits false); no
eraser in the GUI (`rooms.refine(erase=)` only)); settings; welcome; activity panel). Files, Screenshots and the Library share right-click menus
(`app.popmenu`, filled on right-click; on one of several selected items they act on the whole
selection, `C.menu_targets`) and click-and-drag multi-select (`C.DragSelect`: pan start/end on the area + item
hover events, which Flutter also sends with the button held; Flet can't report item positions, so no rubber band).
Selection bars sit below the list: above it, their appearing shifted the items mid-drag. Async handlers (they
@@ -130,10 +144,19 @@ Detailed device findings per game/issue: docs/DEVICE_NOTES.md (read before debug
in Containers with `ft.ScrollKey`), the header stays; `app.settings_view.show_section(key)` (ui_smoke
`settings-index`).
Help hints: wording for non-obvious terms lives in `ui/help.py` (`HELP`); show it with `C.help_icon(key)` or the
`help=` argument of `section`/`kv` (`C.Check` help key), tooltips via `C.tip()` (wraps). Game actions for the Library
right-click menu (one `ft.ContextMenu` around the grid, filled on right-click; short: `menus.quick_menu`, key actions
+ "More actions…" = the game page with its full "…" menuopened from code,`app.open_game_menu`) and the game
page's "…" menu (`menus.menu_sections` quick=False, sectioned) come from `app.game_actions()`. Picking art (`sources.apply_choice`) downloads into a staging dir and keeps the old
`help=` argument of `section`/`kv` (`C.Check` help key), tooltips via `C.tip()` (wraps). Context menus
(Library, Files, Screenshots, Monitor) and the game page's "…" are FramePort's own (GitHub #414):
`ui/popmenu.PopMenu` =`app.popmenu.show(entries, at=(x, y) | below=box)`, drawn in page.overlay (outside-click
barrier + panels; hover submenus with open/close intent delays, ↑ ↓ → ← Enter Esc via `app._on_key` first, closes
on outside click/Esc/choice/`app.go`; panels size to the estimated label width between MIN_W and MAX_W, longer
labels wrap, flip/clamp into the window, submenus on the side with room; a solid grey left edge (owner: no portal gradient) and a solid soft-accent highlight; fade off with
Reduce motion; hovering only repaints rows). Flet's stock ContextMenu/PopupMenu can't nest and cap at 280 px, so
they're only used for button menus (`C.menu_button`, width scaled; `C.menu_label` wraps). Game menus come from
`app.game_actions()` = `menus.menu_sections`: right-click = Play, Install/Update, Game settings | Open game page,
Select | Update ▸ On the Frame ▸ Saves ▸ Name and artwork ▸ Recipe ▸ Troubleshoot ▸ | Uninstall…, Remove from
library… (`menus.group`: a one-item group stays inline); the page's "…" (quick=False) is the same without what
its hero has; on one of several selected games `menus.selection_menu` (`library.menu_actions`); the recipe card's
buttons = `game_actions(recipe=True)`. Picking art (`sources.apply_choice`) downloads into a staging dir and keeps the old
art if nothing came back; "Update Steam art on Frame" re-sends the art set to the game's anchor (agent ≥ 12).
Patch descriptions/reasons describe the general case, naming games only as "e.g. …".
then 6 h), sidebar card (hidden control, toggled), Library bar (`library_bar`), changelog dialog, job
`app-update` that restarts only when no other job is active, `apply_pending_at_start()` in `main()` for
@@ -455,7 +487,9 @@ Repo is on an NTFS drive (`core.fileMode=false`); line endings are LF (`.gitattr
- No XR_FB_display_refresh_rate (`xrGetDisplayRefreshRateFB` -7), yet OVRPort's dispatcher offers it and answered 0 Hz
→ FrameBridge always answers with the measured rate (1/predictedDisplayPeriod, else 72). PreyVR (#306, idTech 4:
`USERCMD_MSEC = 1000 / GetRefresh()`) had 0 ms tics: async thread spinning, `com_frameTime` stuck at 0, menu black.
- Guardian STAGE bounds report 1×1 m (scene emulation uses ≥1.5 m).
- Guardian STAGE bounds report 1×1 m (scene emulation uses ≥1.5 m). OVRPlugin treats that rect as a configured
boundary but can't outline it ("ovrpBoundary_Outer is not supported"): Demeter's guardian check indexed an empty
array → with `scene_emul` the app's STAGE `xrGetReferenceSpaceBoundsRect` returns XR_SPACE_BOUNDS_UNAVAILABLE.
- GL goes through **Zink** (Mesa GL on Vulkan). Mesa GLSL is strict: `#pragma` before `#extension` fails, implicit
int/float conversions fail (enable `GL_EXT_shader_implicit_conversions`), num_views=2 shaders on single-view FBOs
give GL_INVALID_OPERATION. Some GLES games hit `zink: DEVICE LOST` (Unity MSAA RTT; Sniper Elite VR even without).
@@ -568,20 +602,21 @@ Toy Master doesn't request a model after that (it uses its own), so the glb load
**Crash backtraces:** Lepton writes tombstones only to `lepton-logcats/steamlaunch-<appid>/logcat-crash.log` (launch tests return `crash_log`). Fossilize loads into every app (userdebug guest) → `frame.vk_sanitize` (`native/vkshim`); overport's dispatcher aborts on swapchains > 4096 px → `frame.swapchain_limit`; cylinder layers → quad strips (`cylinder_strips`). Drawing equirect layers ourselves and a focus debounce were tried and removed: don't retry without a new approach. → docs/DEVICE_NOTES.md#crash-backtraces-fossilize-swapchain-limit-cylinder-and-equirect-layers
**360° layers / 4XVR:** the Frame's Android runtime composites only quad + projection layers (quads always above projection layers). Per-game adapter settings (off by default): `equirect_emul` (GLES), `equirect_flip/face/res/fps/stereo`, `stable_local`, `focus_hold` (default on), `aim_pitch/aim_yaw/aim_forward`, `refresh_rate`, `layer_debug`; agent `link_media` / `app_media_dirs` put sent files into a player's own folder. → docs/DEVICE_NOTES.md#360-layers--4xvr
**Companion apps / intents:** a second APK can run inside a game's Lepton container, but Lepton turns every `android.intent.action.VIEW` with data into `steam://openurl` → "open in external player" hand-offs can't work; Lepton installs exactly one `*.apk` per app folder. → docs/DEVICE_NOTES.md#companion-apps--intents-stremio--4xvr
**Text input:** Lepton's Android has no IME and VR apps run headless (no focused window); `lepton-show-flatscreen` keeps VR and gives focus + Steam's keyboard (`device.text_input_window`); `frame.unity_text_input` patches Unity's TMP_InputField/InputField at Cpp2IL offsets; agent `_keyboard` = uinput "Type on Frame". `pm install` of a second APK corrupts the main app (`cmd_real package install` skips the hook). → docs/DEVICE_NOTES.md#text-input-stremio-vr
**Browser stand-in (#466):** Lepton's `ActivityStarter.execute` takes every VIEW intent before resolution → `steam://openurl` into `/lepton/steam.pipe`; apps that first query for a browser (Plex) find none → opt-in `frame.browser_handoff` (activity + `dex/browser-handoff.dex` in the app's own APK, analysis `browser_query`); a 2D app with an APK patch on is built, not as_is (`engine.flat_as_is`). → docs/DEVICE_NOTES.md#browser-stand-in-for-sign-ins-plex-github-466
**Text input:** Lepton's Android has no IME and VR apps run headless (no focused window); `lepton-show-flatscreen` keeps VR and gives focus + Steam's keyboard (`device.text_input_window`); `frame.unity_text_input` patches Unity's TMP_InputField/InputField at Cpp2IL offsets; agent `_keyboard` = uinput "Type on Frame". `pm install` of a second APK corrupts the main app (`cmd_real package install` skips the hook). Its window is a gamescope "desktop game" overlay: SteamVR auto-theaters it before the OpenXR session and opens the dashboard on it at session start (controllers = mouse on the Android screen, AC Nexus/#476/#477) → the launcher (`_theater_hold`, first thing) and dashboard worker keep `dashboard.autoShowGameTheater` off while such a game runs (agent v119, holds per launcher pid in `theater-holds/`); SteamOS 0.5.6 logs the dashboard button as `open_dashboard_action`. → docs/DEVICE_NOTES.md#text-input-stremio-vr, #typing-window-takes-the-controllers-steamvr-theater-mode-476-477-ac-nexus
**User reports #4-#10:** shortcuts go to the signed-in account (agent `library_users`), `launch` errors `NOT_IN_LIBRARY`; shared Cpp2IL run (`unity_text_input.Il2cppReturnPatch`) for `frame.unity_runtime_msaa_off` and `frame.unity_multipass`; VR4 → catalog `use_alt`. **Catalog changes reach existing games**: recipes store `catalog_rev`, `library._follow_catalog` re-derives non-user recipes. → docs/DEVICE_NOTES.md#user-reports-2026-10-04-issues-4-10
**Round 2 of reports / headset rounds 2-3 (2026-10-04):** vkshim `vk_shader_fix` (VR4); `Il2cppReturnPatch.field_loads` (I Am Cat); builds record `recipe_fp` (changed recipe/revised patch → "Update on Frame"); `Frame.relocate`; adapter `profile_remap` (Touch Plus/Pro); `sync_guard` (Myst); `focus_hold` tuning; ovrpshim for Unity 2017 (Accounting+); Steam library to every account, `shortcuts_lost` / `add_devkit_entry`; no clipboard service → `frame.sdl_clipboard`, `frame.godot_clipboard`; 2D apps keep non-VR patches (`needs_vr = False`); `frame/local.py` (FramePort on the Frame); after every app update `library._follow_catalog` re-derives non-user recipes once (`recipes.app_version`, `library.REFRESH_ON_UPDATE`). Analysis fields added later: **bump `analysis/detect.ANALYSIS_VERSION`** (re-analysis at GUI start + `pipeline.refresh_analyses`; `analysis_failed`). Roblox and BONELAB crash (unsupported). → docs/DEVICE_NOTES.md#round-2-of-reports-headset-rounds-2-3-2026-10-04
**Round 4:** XR_KHR_android_surface_swapchain is listed but unsupported → adapter `surface_emul` (default on, `native/adapter/surface_swapchain.c`); save folders without u/g+rwx → launch.sh `fix_perms`; Lucky's Tale OOM (no fix); `perf` works as steamos (gdb blocked); QuestCraft JRE, WiiCompiled import by hand (Lepton has no document picker). → docs/DEVICE_NOTES.md#round-4-2026-10-04-surface-swapchains-save-folder-permissions
**Steam library failures (GitHub #21/#30/#41/#42):** on some Frames Steam never loads FramePort's shortcuts.vdf entries (Play → AppError_9) → fallback: register through Steam's devkit interface (`devkit-1 … create-shortcut`, `~/devkit-game/<id>/`; `devkit_appid`, `devkit_appid_from_log`, `devkit_unregister`, `copy_grid_art`; ids: letter first, letters/digits/_ only); `collect_diag` adds `steam_library`. Cause still unknown. → docs/DEVICE_NOTES.md#steam-library-failures-and-the-devkit-fallback-github-21304142
**Exit game (GitHub #36):** `Apps.TerminateApp` (Steam CEF devtools on 127.0.0.1:8080) closes games; launch.sh ends the game when its parent reaper is gone (`upgrade_launchers` adds it). → docs/DEVICE_NOTES.md#exit-game-github-36
**SteamVR dashboard at game start:** launch.sh's `_dashboard_worker` hides the dashboard via `SteamClient.OpenVR.VROverlay` (agent `steam_js`) from the first FrameBridge `new layer:` line (120 s, ≤10 times, stops after `toggle_dashboard_action`); log `<base>/dashboard.log`, opt out `FRAMEPORT_KEEP_DASHBOARD=1`. → docs/DEVICE_NOTES.md#steamvr-dashboard-at-game-start
**Heuristics eval (2026-10-04):** 19/44 recipes exact (superseded by the 2026-10-15 numbers under Heuristics). → docs/DEVICE_NOTES.md#heuristics-eval-2026-10-04
**Power (0.9, agent v47):** sidebar bar below the Frame card: Sleep / Restart / Shut down, each confirmed (`app.frame_power`, a running game asks again). logind answers CanSuspend/CanReboot/CanPowerOff = "challenge" for an SSH session but "yes" inside a user unit, so agent `power` runs `systemctl suspend|reboot|poweroff` from a transient `systemd-run --user --on-active=3` timer (the command answers first). Restart verified on the device (back in ~84 s, new boot id).
**USB cable link:** `usb0` is a USB NCM gadget (Developer Mode only); Frame 10.86.200.233, PC **10.86.200.234** (`frame/usb.py`); ~37 MB/s; `scripts/usb_autotest.py`, `frameport frame usb-check`; discovery prefers USB > hotspot > network (`discovery.dedupe`). → docs/DEVICE_NOTES.md#usb-cable-link
**Catalog updates without a release (2026-10-05):** apps fetch `catalog/games/*.yaml` from GitHub `main` (`recommend/catalog.refresh_remote`: one API call `git/trees/main?recursive=1` + raw.githubusercontent downloads of changed blobs, cached as `catalog-gh-<sha>.yaml`, every 6 h; GUI at start + Settings → Data "Check now"; CLI background thread + `frameport catalog-update`). `catalog.load()` only reads the cache. A remote entry is skipped (`unusable_reason`) when it names a patch/adapter key this app doesn't register, has a field `CatalogEntry` doesn't know, or `min_app` > this version: add `min_app` (or rely on new patch ids) when a config needs an unreleased FramePort. Remote beats bundled unless the bundled entry has a later updated/verified date; the user's own entries still win as before. Off: setting `catalog.auto_update`, env FRAMEPORT_NO_CATALOG_UPDATE; FRAMEPORT_CATALOG_URL still overrides the source. So: **pushing a catalog change to main reaches users within 6 h** (their recipes follow via catalog_rev → Update on Frame).
**Catalog updates without a release (2026-10-05):** apps fetch `catalog/games/*.yaml` from GitHub `main` (`recommend/catalog.refresh_remote`: one API call `git/trees/main?recursive=1` + raw.githubusercontent downloads of changed blobs, cached as `catalog-gh-<sha>.yaml`, every 6 h; GUI at start + Settings → Data "Check now"; CLI background thread + `frameport catalog-update`). `catalog.load()` only reads the cache. A remote entry is skipped (`unusable_reason`) when it names a patch/adapter key this app doesn't register, has a field `CatalogEntry` doesn't know, or `min_app` > this version: add `min_app` (or rely on new patch ids) when a config needs an unreleased FramePort. Remote beats bundled unless the bundled entry has a later updated/verified date; the user's own entries still win as before. Off: setting `catalog.auto_update`, env FRAMEPORT_NO_CATALOG_UPDATE; FRAMEPORT_CATALOG_URL still overrides the source. So: **pushing a catalog change to main reaches users within 6 h** (their recipes follow via catalog_rev → Update on Frame). `catalog/triage.yaml` comes from the same tree listing (cached `triage-gh-<sha>.yaml`, index key `triage`; a new copy → `triage.reset()`; with FRAMEPORT_CATALOG_URL: that folder's optional `triage.yaml`): `validate/triage.database()` = bundled merged with remote per signature id (remote wins unless the bundled one has a later `updated:`; bundled-only ids stay), each remote signature dropped (bundled one kept) when malformed, it has an unknown field, suggests a patch/adapter key this app lacks, or `min_app` (per signature or top-level = whole file) > this version; `triage.skipped()`, `frameport catalog-update` lists them.
**Start activity / QuestCraft / ITR2 pop-in:** `frame.start_activity` (analysis `extra.vr_activity`, `axml.set_start_activity`; OVRPort gives every MAIN activity LAUNCHER, so take the VR activity from the original APK); Unreal reads user config from **internal** storage; Lepton passes `LEPTON_ENV_<NAME>` to the app as `<NAME>` (QuestCraft `LIBGL_NOERROR`); vkshim `vk_query_slots` left off. → docs/DEVICE_NOTES.md#start-activity--questcraft--itr2-pop-in
**Vulkan validation / ITR2 solved:** bundle Khronos' `libVkLayer_khronos_validation.so` into a test APK + vkshim `vk_validation=1` (logcat tag VALIDATION); `vk_spec_fixes=1` drops ITR2's invalid depth resolve; VALVE_rpo removed its fog (catalog `lepton_env: VK_INSTANCE_LAYERS`). Agent `not_started` waits 30 s for "Boot complete!". → docs/DEVICE_NOTES.md#vulkan-validation--itr2-solved
**GitHub #38/#39:** SteamVR rejects image rects past their swapchain (-25) → FrameBridge `rect_clamp` (default on), triage `swapchain-rect-invalid`; The Room VR MSAA crash in libgallium. → docs/DEVICE_NOTES.md#github-3839-powerwash-simulator-the-room-vr
@@ -590,9 +625,11 @@ Toy Master doesn't request a model after that (it uses its own), so the glb load
**Vader Immortal (GitHub #49):** stuck on its loading card and leaked GPU mappings (solved below: `gl_error_clear`); diagnostics FrameBridge `layer_debug`, `strip_color_bias`, `frame_balance`, `snapshot=N` (`snapshot_gl.c`: saves the submitted left-eye image as `files/fb_snap_*.ppm`). → docs/DEVICE_NOTES.md#vader-immortal-headless-investigation
**SteamVR overlay apps:** host OpenVR overlays composite over Lepton games; detection `analysis/vroverlay.py`, patch `pcvr.vr_overlay`, Linux fields `vr_overlay`/`vr_overlay_autostart`, agent `register_vr_overlay`/`unregister_vr_overlay`/`launch_vr_overlay`; SteamVR on the Frame reads only `binary_path_linux_arm`; FramePort's own autostart (`ensure_overlay_service`, `frameport-vr-overlays.service`). See also docs/FRAME_RUNTIME.md "Overlay apps". → docs/DEVICE_NOTES.md#steamvr-overlay-apps
**Vader's leak solved / AC Nexus flips:** stale GL errors broke SteamVR's GL interop → adapter `gl_error_clear` (GLES, catalog for Vader ep1-3); read `xrclient_<pkg>.txt` in Steam's logs for runtime errors; the runtime caps swapchains per session (~15) → flip emulation in place; adapter `flip_dump`. → docs/DEVICE_NOTES.md#vaders-leak-solved--ac-nexus-flips
**Mixed reality: scanned rooms (branch mr-scene):** agent `room_capture` records Valve's RoomView 3D depth mesh -> `fp_roomscan` -> `room.txt` + `room-mesh.bin` (docs/mr/ROOM_FORMAT.md; refined by default: the owner's cleanup passes + label rules in `native/roomscan/refine.c`/`rules.c`, unrefined output kept as `room-raw.txt` + `room-mesh-raw.bin`, head path/settings/report/labels/door suggestions in `scan.json`, `--refine-only <dir>` re-runs it, `--refine 0` = old output; docs/mr/scan.md "Refinement") in `~/.local/share/frameport/rooms/<universe>/`, copied next to settings.conf (`_room_sync`); FrameBridge (`scene_room.c/scene_meta.c/scene_anchors.c`, all through xrshim) serves walls, volumes, planes, the room mesh, META discovery/persistence, saved anchors and `XR_META_boundary_visibility`; `scene_emul` + `frame.meta_permissions` for every USE_SCENE/USE_ANCHOR_API game (migrations `scene_emul_all`, `scene_permissions`); per-game `scene_hide_bounds` (Demeter). → docs/DEVICE_NOTES.md#mixed-reality-scanned-rooms
**Unreal's self-restart:** UE 4.21+ restarts itself after the first shader precompile and Lepton ends the container → launch.sh RESTART_BLOCK starts Lepton once more (`upgrade_launchers`; `FRAMEPORT_NO_RESTART=1` off). → docs/DEVICE_NOTES.md#unreals-self-restart
**xrWaitFrame runs ahead:** the runtime sometimes returns a display time <1 refresh after the last submit and rejects the second submission (`VRCompositorError_AlreadySubmitted`) → the VrApi bridge re-waits (`pacing 2s:` counters). → docs/DEVICE_NOTES.md#xrwaitframe-runs-ahead-journey-of-the-gods
**Graceful stop (#480, agent v120):** killing the container never paused the app (saves in `OnApplicationPause` lost: Just Dance VR) → launch.sh `pause_game` / agent `stop` / End game first `am start` the HOME intent in the container (`input keyevent` does nothing headless) and wait ≤3 s for STOPPED (`PAUSE_SCRIPT`, `<base>/stop-pause.log`). Steam's own Exit game signals the whole process tree (conmon is re-parented to the reaper) at once → agent v121: launch.sh's `_exit_hook` worker (`exit_hook_worker`, `EXIT_HOOK_JS`) wraps `SteamClient.Apps.TerminateApp` in SharedJSContext with a CDP binding of its own: Exit game for that game's ids waits for the worker's pause (cap 3 s, 0.8 s without an answer, lease 10 s), other ids pass straight through; End game calls `terminate_app_js` (past the hook). → docs/DEVICE_NOTES.md#graceful-stop-the-game-is-paused-before-it-ends-github-480-agent-v120
**Double launch:** launch.sh takes `flock` on `<base>/.launch.lock` (fd 9); a second launch exits 0 and logs to `<base>/launch-dup.log` (`upgrade_launchers` adds it). → docs/DEVICE_NOTES.md#double-launch
**Issue triage round 2026-10-06:** FrameBridge turns haptic amplitude 0 into xrStopHapticFeedback, per-game `haptic_scale`; Lambda1VR → `frame.tbxr_vendor`; Jurassic World → `frame.vrapi_stub`; Metro Awakening / Pinball FX → `hide_space_warp`; `frame.ovr_trace` can't trace Unity games; the VrApi bridge's 30 s head-pose deadline ends headless VR mode. → docs/DEVICE_NOTES.md#issue-triage-round-2026-10-06
**Lepton storage:** each app's /sdcard has `Movies`/`Download`/`Documents` symlinked to the Frame's `~/Videos`/`~/Downloads`/`~/Documents` (agent `storage_targets`); the media index never works; Lepton grants all runtime permissions. → docs/DEVICE_NOTES.md#lepton-storage
@@ -601,6 +638,8 @@ Toy Master doesn't request a model after that (it uses its own), so the glb load
**Cube swapchains:** the runtime refuses `faceCount=6` → FrameBridge `cube_standin` (default on, GLES only, `native/adapter/cube_standin.c`). → docs/DEVICE_NOTES.md#cube-swapchains
**Hardware video decoding:** `native/hevc` OMX plugin (`OMX.frameport.{avc,hevc,vp9}.decoder`), installed once per Frame (`ensure_video_codec`, agent `video_codec_status`/`install_video_codec`/`video_codec_switch`); **bump `revision` in native/hevc/build.py with every artifact change**; per game `frame.hw_video_decode` (analysis `media_codec`), Settings `video.hw_decode`, `FRAMEPORT_NO_HW_VIDEO=1`; Batman migration `batman_video_patches` + `surface_native`. → docs/DEVICE_NOTES.md#hardware-video-decoding
**Foveation centre (#394):** Valve's FDM layer centres its density map on the whole swapchain (not the eye's rect or optical axis) and Turnip picks density per GMEM bin (the sharp area snaps to bins, so its size shifts with render resolution: driver design); `VK_LAYER_FRAMEPORT_fdm_centre` (native/foveation/centre.h, below Valve's layer, views from FrameBridge `fdm_views.c`) rewrites the per-eye density-map offsets; FrameBridge `snapshot=N` reads Vulkan eyes back. → docs/DEVICE_NOTES.md#foveation-sharp-area-centre-and-size-394
**Expansion file names (#450/#481):** OBBs with a download's copy suffix (`main.203.com.Armature.VR4-003.obb`) or another case are installed under the game's name (`quest_dump.obb_names`/`frame_manifest`, PC files untouched), other kinds too (#415: Crisis Brigade 2's `office.222.<pkg>.obb`, kind spelling kept, never an "another app" warning); another app's name (#129), duplicates and unclear cases stay warnings. → docs/DEVICE_NOTES.md#expansion-file-names-450-481
**Issue round 2026-10-12:** Lepton breaks on unquoted paths with spaces → launchers give Lepton a `~/.local/share/frameport/lepton-paths/<pkg>` link (`app_dir_lines`/`set_app_dir`/`launcher_base`); `frame.eos_keychain_loop`, `frame.unity_tls_key_pool` (`libfp_tkeys.so`, `elf.rename_imports`), `frame.vermillion_scene_canvas`; `tools/cpp2il.py` falls back to Cpp2IL's dev build for metadata v107; Linux `desktop_icon`; `scripts/upload_bench.py`. → docs/DEVICE_NOTES.md#issue-round-2026-10-12
**OpenSSH uploads (#324/#242):** `native_upload.quote` escapes only backslash and `"`, Win32 local paths go as `C:/…`; `Frame.auth` records the login that worked (app_key / user_key from `ssh.extra_keys`, `frame ssh-keys`, agent, password); strict IdentitiesOnly first, relaxed retry. → docs/DEVICE_NOTES.md#openssh-uploads
@@ -704,7 +743,8 @@ short recap of the thread so far; read **all** comments on a thread first (a lat
- **How a change reaches users** (decide this before promising a fix):
- *Catalog / recipe / triage* changes reach **installed** apps within ~6 h with no build — apps fetch
`catalog/games/*.yaml` + `triage.yaml` from `main` (`catalog.refresh_remote`), recipes follow via `catalog_rev` →
"Update on Frame". So accepting a recipe or adding a triage signature is "live" once pushed.
"Update on Frame". So accepting a recipe or adding a triage signature is "live" once pushed (a signature that
suggests a patch/setting the installed app doesn't have yet is skipped there: needs a release; `min_app` gates it).
- *Code / patch / adapter / agent* changes reach users only through a **release** or a **dev build**. For a per-game
fix a reporter should try now: push to `main`, cut a dev build (Actions → build → `dev` ticked, with `notes`), tell
them to install it via **Settings → Updates → "Install the latest dev build…"** and then rebuild the game on the
@@ -778,3 +818,9 @@ asks before ending a running game, and closes local FramePort/Desktop Mode. Purg
Hook hardening: both lines `-` + `/usr/bin/timeout -k 5 20`; ExecStartPost only `systemd-run --no-block`s the
verify (unit frameport-library-verify) so Steam's start never waits; drop-in removed once mode != later and nothing
pending (`drop_library_hooks_if_idle`). Steam = static user unit steam.service (Type=simple, Restart=always, 90 s).
Agent v124: a waiting `now` worker holds the shortcut lock (`shortcut_lock`) for up to 12 h, so nothing an SSH command
reaches blocks on it (an install's `library_mode` hung until the 600 s agent timeout): `library_mode` tries once and
skips the hook cleanup; `cmd_shortcuts` journals into the waiting worker (`pending_lock`, a short separate lock;
the worker writes "running" under it before merging the journal after stop_steam, one restart for all; the PC
shows the same "waiting" reason); `queue_library` waits ≤30 s, else journal only; runs drop what they applied
uv sync --extra dev # Python 3.11+, uv (https://docs.astral.sh/uv/)
uv run frameport-gui # the app
@@ -15,39 +20,32 @@ uv run frameport --help # the command line
uv run pytest # tests (no headset or game files needed)
uv run ruff check . # lint
```
- Code and architecture: `CLAUDE.md`, `docs/ARCHITECTURE.md`; per-game symptoms and fixes: `docs/PLAYBOOK.md`.
- Interface wording: `docs/STYLE.md`. GUI texts go through `tr()`; after changing them run
`python scripts/i18n_extract.py`.
- Translations: see [Translating FramePort](#translating-frameport) below.
- New fixes are patch modules (`src/frameport/patches/`), with a triage signature (`catalog/triage.yaml`), a PLAYBOOK
row and a unit test.
- Native binaries in `artifacts/` are rebuilt with `python native/build.py` (downloads the NDK).
## Adding a tested game
Tested a game on the Steam Frame? The easiest way is in FramePort: open the game → **…** → **Share working config…**.
It opens a prefilled GitHub issue with the exact recipe; after review a workflow turns it into
`catalog/games/<package>.yaml`. Without the app: [working config form](https://github.com/spoopyghosty0/frameport/issues/new?template=working-config.yml). Something doesn't run: the game's
**Report a problem…** (diagnostics zip, personal data removed) or the [problem report form](https://github.com/spoopyghosty0/frameport/issues/new?template=bug-report.yml).
After catalog changes, regenerate the games list: `python scripts/compat_list.py`.
- Where things are: `docs/ARCHITECTURE.md` (module notes in `CLAUDE.md`), `docs/PLAYBOOK.md` (symptoms and fixes),
`docs/FRAME_RUNTIME.md` (runtime facts).
- A new fix = a patch module (`src/frameport/patches/`) + a triage signature (`catalog/triage.yaml`) + a PLAYBOOK row
+ a unit test.
- After changing a patch, game setting, recipe or triage signature: `python scripts/patch_docs.py`. After catalog
changes also `python scripts/compat_list.py`.
- GUI texts go through `tr()`; after changing them run `python scripts/i18n_extract.py`. Wording rules:
`docs/STYLE.md`.
- Docs screenshots and videos are rendered, never taken by hand: `docs/SHOWCASE.md`.
- Native binaries in `artifacts/`: `python native/build.py` (downloads the NDK).
## Translating FramePort
The app's texts (screens, help, patch and setting descriptions) can be translated; the command line, logs and
diagnostics stay English. No programming needed:
1. Copy `src/frameport/locales/template.json` to `src/frameport/locales/<code>.json`, where `<code>` is the language
code (`de`, `fr`, `pt`, `ja`, …).
2. Add `"_language": "<the language's own name>"` (e.g. `"Deutsch"`); it's shown in Settings → Language.
3. Fill in the values. Each key is the English text; leave a value empty to keep the English text. Plural entries
have a list value: `["<one>", "<other>"]`. Keep `{placeholders}` such as `{title}` or `{n}` exactly as they are,
and keep `…`, `·` and line breaks where the English has them.
4. Try it: `uv run frameport-gui`, choose the language in Settings → Language, restart FramePort.
5. Open a pull request with the file. When the English texts change, `python scripts/i18n_extract.py` refreshes the
template; new texts then show in English until they're translated.
The app's texts can be translated, no programming needed. The command line, logs and diagnostics stay English.
Wording rules for English (and a guide for tone) are in `docs/STYLE.md`.
1. Copy `src/frameport/locales/template.json` to `src/frameport/locales/<code>.json` (`de`, `fr`, `pt`, `ja`, …).
2. Add `"_language": "<the language's own name>"`, for example `"Deutsch"`.
3. Fill in the values. Each key is the English text; an empty value keeps the English. Plural entries take a list:
`["<one>", "<other>"]`. Keep `{placeholders}` such as `{title}` exactly, and `…`, `·` and line breaks where the
English has them.
4. Try the translation: `uv run frameport-gui`, pick the language in Settings → Appearance → Language, restart FramePort.
5. Open a pull request with the file. New English texts show in English until they're translated.
## Ground rules
- Use only games you own. Contributions that help bypass DRM, licence or entitlement checks, or that add game files,
are not accepted.
- Never commit personal data (IP addresses, user names, home paths, Steam IDs).
Tested games get their recipe (the patches and settings that work) automatically. Other games get suggested
patches. See the [tested games](docs/GAMES.md). Got a game working?
[Share its recipe](docs/INSTALL.md#share-a-recipe-or-report-a-problem).
## Compared with other tools
FrameDrop and Valve's own tools install an app as it is. FramePort differs in four ways:
FrameDrop and Valve's own tools install an app as it is. FramePort also:
- **Free and open source** (GPL-3.0). FrameDrop is free (donationware) without published source.
- **Quest games that don't run on the Frame as they are** get converted and patched, with a tested recipe for 120+
games. The others install the game unchanged.
- **Windows, macOS and Linux.** FrameDrop is for Windows.
- **Wi-Fi or a USB cable**, and no **Pair new host** step.
- is **free and open source** (GPL-3.0). FrameDrop is free (donationware), without published source.
- **converts and patches Quest games** that don't run as they are, with tested recipes for hundreds of games.
- runs on **Windows, macOS and Linux**. FrameDrop is for Windows.
- connects over **Wi-Fi or a USB cable**, without a **Pair new host** step.
<details>
<summary>All differences</summary>
@@ -88,12 +83,12 @@ FrameDrop and Valve's own tools install an app as it is. FramePort differs in fo
|---|---|---|---|
| Cost and source code | Free, open source (GPL-3.0) | Free (donationware), source not published | Free, from Valve |
| Runs on | Windows, macOS, Linux | Windows | Depends on the tool |
| First connection | One line in the Frame's terminal; it turns on Developer Mode itself. No password | Turn on Developer Mode, then **Pair new host** | Turn on Developer Mode and pair, or use Android's debug tool (adb) |
| First connection | One line in the Frame's terminal; turns on Developer Mode itself. No password | Turn on Developer Mode, then **Pair new host** | Turn on Developer Mode and pair, or use Android's debug tool (adb) |
| Wireless or cable | Wi-Fi, the Frame's hotspot or a USB cable | Same Wi-Fi network | Wi-Fi, or adb |
| Quest games that don't run as they are | Converted and patched for the Frame | Installed as they are | Installed as they are |
| Knows which patches a game needs | Tested recipes for 120+ games, updated without an app update | Not stated | No |
| Knows which patches a game needs | Tested recipes for hundreds of games, updated without an app update | Not stated | No |
| In the Steam library | Yes, with artwork and tags | As a "Devkit Game" shortcut | As "Devkit Game: <title>"; not with adb |
| Android apps, Linux apps, Windows programs | All three; Proton (runs Windows programs) is installed for you | All three; install Proton first | Yes; 2D Android apps need an extra file |
| Android apps, Linux apps, Windows programs | All three; Proton is installed for you | All three; install Proton first | Yes; 2D Android apps need an extra file |
| PC VR games | On your PC; some also on the Frame | Not supported | Not supported |
| "Install with …" buttons on websites | Yes ([frameport.app](https://frameport.app) links) | Its own | No |
| A game doesn't start | A launch test reads the logs and suggests a patch | A log viewer | No help |
@@ -101,10 +96,10 @@ FrameDrop and Valve's own tools install an app as it is. FramePort differs in fo
</details>
Other tools as described on their own pages, checked 2026-10-09:
Based on each tool's own pages, checked 2026-10-09:
notes:Runs your own Astro Bot Rescue Mission files (the unpacked CUSA12392 folder, not the .pkg). With the Files
tab, copy the folder to Android/data/com.astrobotquest.vrhost/files/games/ in the game's storage and add Android/data/com.astrobotquest.vrhost/files/vrhost.txt
with the lines language=<your language, e.g. en-US> and refresh_rate=80. Set FoV to 100% in the game.
details:'Tested with AstroQuest v0.23 (https://github.com/bigmak94/AstroQuest). The port caps the resolution at
1440 px per eye; vrhost.txt can''t change that. Other vrhost.txt options:https://github.com/bigmak94/AstroQuest/blob/main/README-QUEST-VR.md#installing-and-running'
notes:Mixed reality laser obstacle course; requires spatial data and room layout emulation (scene_emul=1). frame.meta_depth_occlusion keeps close lasers visible (its asset fixes only for the build they were made for); tested version not recorded (PR#246).
details:Mixed reality game that projects lasers across your room; needs Meta scene emulation and spatial data permission.
diagnosis:'The game is using a simple fallback room. Scan your room for mixed reality, or use Frame > Room > Use my play area for walls without furniture. A recorded scan can be imported until headset capture is available.'
diagnosis:'The game cannot discover a Meta room. Enable room layout (adapter.scene_emul), rebuild and reinstall for the updated extension shim, then save a room under Frame > Room.'
diagnosis:The game submits an offset projection viewport. With Valve foveation, dynamic resolution can move the sharp centre or expose a white edge. A rebuilt APK (Update on Frame) puts the sharp area back on each eye; keeping full resolution may also help for the white edge, increases GPU load and needs the updated adapter and OVRPlugin shim in a rebuilt APK.
question:With foveated rendering enabled, was the sharp centre misplaced or was there a white line at the picture edge?
suggest:[adapter.fixed_resolution]
- id:static-openxr-loader-bypass
pattern:'RuntimeManifestFile::FindManifestFiles - using global runtime file /[^\r\n]*etc/openxr/1/active_runtime(?:\.[A-Za-z0-9_-]+)?\.json'
unless:'FrameBridge[:]|fp_xrruntime[:]'
severity: warning
diagnosis: A Khronos loader loaded the system runtime without FrameBridge. A statically linked loader may bypass the bundled loader chain; analyze again, enable the embedded OpenXR loader patch when detected, then rebuild and reinstall to write its per-game manifest.
diagnosis:CryEngine's start-up failed (CrySystem initialization), and the crash that follows comes from its shutdown. FramePort 1.3.0 builds of The Climb 1/2 failed like this because a dormant diagnostic layer was linked into CryEngine's renderer; a newer FramePort leaves it out, so update FramePort and press Update on Frame. Otherwise attach the diagnostics zip to a report.
report:true
suggest:[]
- id:vrapi-bridge-no-loader
pattern:'OVRPortVrApi:xrInitializeLoaderKHR failed:OpenXR result -7'
severity:fatal
diagnosis:The VrApi bridge found no OpenXR loader because FrameBridge didn't load (a library it needs is missing from the APK). Dev builds of FramePort before this was fixed left that library out of The Climb 1; update FramePort and press Update on Frame. Otherwise attach the diagnostics zip to a report.
report:true
suggest:[]
- id:questcraft-no-java-runtime
pattern:'libjvm\.so[^\r\n]*(?:not found|No such file)'
severity:fatal
diagnosis:QuestCraft's Java runtime isn't there. QuestCraft downloads it only on Wi-Fi, and the Frame's Android reports its network as Ethernet, so the download was skipped. Rebuild with the pojlib connection fix (frame.pojlib_connection), or unpack QuestCraft's JRE.zip into the game's files/runtimes/JRE with the Files tab.
suggest:[frame.pojlib_connection]
- id:video-decoder-held-by-steam
pattern:'FramePort:hardware video decoder \S+ in use by:[^\r\n]*steamwebhelper'
severity:warning
diagnosis:Steam's own interface had the Frame's hardware video decoder open when the game started. Large videos (8K cutscenes, e.g. Batman) then decode in software and play choppy. Restarting Steam, or the Frame, before playing frees it.
diagnosis:The Frame runtime reached its swapchain count limit (about 15 live swapchains per session), not necessarily a memory limit. Extra menus or video panels may close or stay gray. A game may also stop drawing when a new set of eye images is refused (e.g. AC Nexus with the Quest 3 identity). Updated FramePort builds provide Share panel images (swapchain limit), for GLES and Vulkan apps (panels share one or two runtime images, leaving room for the rest).
diagnosis:'Nothing starts:Lepton found no entry point in the app (an activity with category LAUNCHER).'
diagnosis:'Nothing starts:Lepton found no entry point in the app (an activity with category LAUNCHER; one on an activity-alias alone does not count). Rebuild with the launcher fix (frame.launcher) and reinstall.'
suggest:[frame.launcher]
- id:linux-x86-no-fex
pattern:'(cannot execute binary file:Exec format error|exec format error)'
@@ -87,6 +145,13 @@ signatures:
severity:fatal
diagnosis:32-bit-only APK (e.g. a Gear VR or Oculus Go game). The Steam Frame has no 32-bit ARM support; FramePort's 32-bit bridge translates the game (experimental).
suggest:[frame.a32_bridge]
- id:missing-split
pattern:'INSTALL_FAILED_MISSING_SPLIT'
severity:fatal
diagnosis:Only the base APK of a split app was installed (the base requires its split APKs). Add the whole app
bundle (.xapk, .apks, .apkm or .apkx) or the folder with all its split APKs instead; FramePort merges them into
one APK. Then install the game again.
suggest:[]
- id:a32-jit-overflow
pattern:'a32:block at 0x[0-9a-f]+ overflowed the code buffer'
severity:fatal
@@ -94,11 +159,40 @@ signatures:
interpreter, for example OpenSSL's ARMv8 SHA-1 code in Pinball FX2 VR. Fixed in newer FramePort versions;
update FramePort and rebuild the game ("Update on Frame").
suggest:[frame.a32_bridge]
- id:a32-video-ndk
pattern:'AndroidMediaNDK could not load symbol AMedia\w+|GetDirectBufferAddress:buffer at 0x[0-9a-f]+ is outside the guest''s reach'
severity:error
diagnosis:A 32-bit game on the bridge can't play its videos through Unity's video player (Ghost Giant's intro
stays black). Older bridges lacked the NDK media functions (libmediandk), and Unity's Java fallback hands the 32-bit
code decoder buffers it can't reach. Fixed in newer FramePort versions; update FramePort and rebuild the game
("Update on Frame").
suggest:[frame.a32_bridge]
- id:android-too-new
pattern:'(NoClassDefFoundError|ClassNotFoundException|NoSuchMethodError)\b.*(Landroid/window/|android\.window\.|(storeStoreFence|loadLoadFence)\(\)V in class Ljava/lang/invoke/VarHandle)'
severity:fatal
diagnosis:The game needs a newer Android than the Frame has, so it can't run yet. It calls Android 12/13+ classes (android.window.*, VarHandle fences) that the Frame's Android 11 (API 30) container lacks (for example minSdk 34 apps); nothing to patch until Valve updates Lepton's Android.
suggest:[]
- id:system-library-missing
# GitHub #484 (YouTube VR): OVRPort's patch_remove_uses_library also drops <uses-library> entries for libraries
# Lepton has; the classes of patches/frame/system_libraries.LEPTON_LIBRARIES then can't be found
diagnosis:The app closed at start because it can't find classes from one of Android's optional libraries (for example org.apache.http.legacy in YouTube VR). The Frame's Android has the library, but OVRPort removed the app's request for it from the manifest; "Keep Android's shared libraries the app uses" puts it back. Turn it on and rebuild the game ("Update on Frame").
suggest:[frame.keep_system_libraries]
- id:refresh-rate-refused
# GitHub #336 (OutRun): the game quits when its xrRequestDisplayRefreshRateFB is refused; FrameBridge now answers
# success, so the game's own error line only shows in older builds
pattern:'request display Hz:-1000101000'
severity: error
diagnosis: The game asked for a refresh rate the Frame's runtime refuses and may quit on that error. A newer FramePort keeps the current rate instead; press Update on Frame. Setting Game settings → Refresh rate to 90 Hz also avoids the error.
suggest:[adapter.refresh_rate=90]
- id:hybrid-feature-missing
# GitHub #484 (YouTube VR): a Horizon OS hybrid app checks for the system feature oculus.software.vr.app.hybrid
# before it leaves its 2D panel for VR
pattern:'Missing hybrid app system feature'
severity:warning
diagnosis:'The app stays in its 2D panel because it can''t find a Quest system feature it checks before switching into VR (360° videos, full screen). "Hybrid app: allow the switch into VR" declares that feature for the app. Turn it on and press Update on Frame; whether the VR part then works depends on the app.'
suggest:[device.hybrid_app]
- id:web-wrapper
pattern:'(Creating TwaLauncher for com\.oculus\.browser|NameNotFoundException:? com\.oculus\.browser)'
severity:fatal
@@ -202,6 +296,16 @@ signatures:
GraphicsEnv functions it uses were not found or behaved differently, for example after a Lepton update), so the game's
OpenGL ES shader fixes (zink_shader_fix) don't apply.
suggest:[]
- id:shader-fix-unmatched
# vkshim (vk_shader_fix) / the shader-fix layer (zink_shader_fix), once per session, 5 minutes after the first shader
diagnosis:The game waits forever on its loading screen for an answer from Meta's platform (an org-scoped user id, used as its analytics id) that OVRPort's platform library doesn't give. The patch makes the game skip that one request, as it does without Wi-Fi.
suggest:[frame.cortopia_org_id]
- id:meta-org-scoped-id-wait
# OVRPort's ovr_User_GetOrgScopedID returns request id 0 and Meta's wrapper logs "Request failed." with a stack
# that names no game code. Eleven Table Tennis (GitHub #452) logs its platform start just before: its
# <BuildIdentity>b__0 asks for the id ~0.3 s after "[META.FUN] in meta platform HandlePlatformInitialization"
diagnosis:The game waits on its loading screen for Meta's org-scoped user id, which OVRPort's platform library
never answers. The patch answers that request with a fixed id derived from the game's user id.
suggest:[frame.ovr_org_scoped_id]
supersedes:[meta-request-failed]
- id:gazzlers-platform-unsupported
# GAZZLERS (GitHub #404): picks Meta/PICO from SystemInfo.deviceModel and throws for Lepton's "Valve Lepton"
pattern:'Exception:Current Platform is not supported[\s\S]{0,300}?PlatformDeterminer\.GetAndroidPlatform'
severity:fatal
diagnosis:The game decides whether it runs on a Meta Quest or a PICO headset from Android's device name and stops for any other name; the Frame's Android calls itself Valve Lepton. The patch makes the game take its Meta Quest path.
suggest:[frame.gazzlers_quest_platform]
- id:ovr-microphone-crash
pattern:'ovr_Microphone_GetOutputBufferMaxSize\+'
severity:fatal
@@ -507,6 +639,13 @@ signatures:
severity:error
diagnosis:Epic Online Services can't save its login keychain and retries without end (a start that never finishes, 100 % CPU). The usual cause is missing internal storage (see lepton-data-link-failed); the patch stops the loop, and EOS then reports the failure to the game once.
suggest:[frame.eos_keychain_loop]
- id:justdance-local-save-off
# Just Dance VR's own log line at start (Ubisoft's JdLog): its offline save opens, but this build never writes it
unless_patch:frame.justdance_local_save # the line is the same with the fix in the build
severity:info
diagnosis:Just Dance VR (0.5.4 and later) opens its local save at every start but never writes it (the save request method is empty in this build; on a Quest the progress is kept by Ubisoft's online backend after a Meta sign-in, which the Frame doesn't have), so progress, unlocks and the finished tutorial are lost at every restart. The patch makes the game write its local save; nothing online is touched.
suggest:[frame.justdance_local_save]
- id:pthread-keys-exhausted
pattern:'cannot create thread specific key'
severity:fatal
@@ -535,6 +674,12 @@ signatures:
diagnosis:The game crashed while leaving VR, inside Valve's foveation layer when it destroys a swapchain (for example Lambda1VR). This can happen after manually quitting; it is not a startup crash and does not explain distorted geometry seen before quitting.
suggest:[]
supersedes:[native-crash]
- id:valve-fdm-render-crash
pattern:'(?s)\A(?=.*(?:Fatal signal 11|SIGSEGV))(?=.*vulkan\.freedreno\.so)(?=.*fdm_injection_CmdBeginRendering)'
severity:error
diagnosis:The game crashed in the graphics driver while Valve's foveation layer started a render pass. Valve's Fixed foveation path crashed AC Nexus like this (#394); builds with FramePort's foveation centring run Fixed on Valve's eye-tracked path instead, so rebuild the game (Update on Frame), or switch Foveated rendering to Eye-tracked or Off.
suggest:[]
supersedes:[native-crash]
- id:batman-audio-geometry-allocation
pattern:'(?s)\A(?=.*com\.camouflaj\.manta)(?=.*Trying to allocate:[1-9][0-9]{10,}B)(?=.*MetaXRAudioUnityMemoryAllocator\.cpp)'

## Data flow for one game
1.**Source** (`sources/quest_dump.py`): folder with an APK and optional `<package>/` or `obb/` data.
1.**Source** (`sources/quest_dump.py`): folder with an APK and optional `<package>/` or `obb/` data; SideQuest
backups and AXRB downloads are recognised, app bundles (`.xapk`/`.apks`/`.apkm`/`.apkx`, split APKs) are merged
`triage.py` (milestones + signatures → suggested patches) → UI offers "apply suggestions and rebuild". Real play
sessions are triaged the same way afterwards (`validate/session.py`, game page "Last session").
## Extending
- **New patch**: `patches/frame/<name>.py` with a `Patch` subclass (`detect`, `apply`, optional `validate`), plus a
signature in `catalog/triage.yaml` and a PLAYBOOK row. Stages: `overport` | `apk` | `install`.
- **Upstream fixed a bug we work around**: register an `UpstreamFix` (`patches/upstream.py`) in the workaround's
- **Upstream fixed a bug FramePort works around**: register an `UpstreamFix` (`patches/upstream.py`) in the workaround's
module: a probe that finds the fix in OVRPort's output (True / False / None = can't tell). Each build runs the probes
on the converted APK and leaves out the workarounds whose fix is there (log "not needed: …", `build.superseded`,
left out of settings.conf at install; game page and `frameport show` say so). The recipe keeps asking for the fix,
so builds made with an older runtime keep the workaround. `FRAMEPORT_KEEP_WORKAROUNDS=1` turns it off.
- **New heuristic**: put it in the patch's `detect()` (and `applies()` for visibility), with the evidence in the
so builds made with an older runtime keep the workaround. `FRAMEPORT_KEEP_WORKAROUNDS=1` turns the probes off.
- **New heuristic**: put the rule in the patch's `detect()` (and `applies()` for visibility), with the evidence in the
reason text; check `scripts/eval_heuristics.py` still reproduces the catalog and add a test in
`tests/test_heuristics.py`.
- **New game recipe**: `catalog/games/<package>.yaml` (or "Save as known-good" in the GUI, which writes to the user
catalog). Publish recipes by serving a folder with `index.json` and pointing `FRAMEPORT_CATALOG_URL` at it.
catalog). Installed apps fetch `catalog/games/` from GitHub `main` every 6 hours (`recommend/catalog.refresh_remote`),
so a merged recipe reaches users without a release; `catalog/triage.yaml` follows the same way. `FRAMEPORT_CATALOG_URL` points the apps at another folder that
serves `index.json` + `<package>.yaml` instead.
- **New target**: implement `targets/base.Target`; the pipeline and UI only use that interface.
## Oculus Rift (PC VR) games
`sources/rift_dump` finds Windows game folders; `analysis/rift` (own PE reader) detects exe, bitness, engine, LibOVR vs
OpenXR, D3D version and Oculus Platform SDK use; ids are `rift.<slug>`. Their only patches are the `pcvr` category
API), apksigner (Google repository index), Cpp2IL and APKEditor (GitHub releases, on first use), store artwork,
titles and details (overport image API, OculusDB, Steam), catalog recipes and triage signatures (GitHub `main`),
announcements, Lepton location/appid (Frame appmanifests), Steam user (Frame userdata).
## Built on
Loaded 100 of 387 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.