Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fd95635d6 | ||
|
|
8d2cb30da8 | ||
|
|
d16ff4e53f | ||
|
|
c5014bf249 | ||
|
|
ce155af69d | ||
|
|
173137ed33 | ||
|
|
370d3ae247 | ||
|
|
e8b746ec9a | ||
|
|
2f617d708a | ||
|
|
cf9da9a6d0 | ||
|
|
fdd3df2714 | ||
|
|
9ef2c952b0 | ||
|
|
404beb7786 | ||
|
|
46863a7c61 | ||
|
|
79e329afe5 | ||
|
|
9973833585 | ||
|
|
c239a8cf32 | ||
|
|
9e69242001 | ||
|
|
6e0958b03d | ||
|
|
4c1cbc9f50 | ||
|
|
f393884606 | ||
|
|
984c1ab4f3 | ||
|
|
53980029bb | ||
|
|
c95efb92d7 | ||
|
|
a4d71c529d | ||
|
|
192bf14f9d | ||
|
|
e30361bc9e | ||
|
|
a09fdd7ae3 | ||
|
|
f53b25686f | ||
|
|
e542fd1b67 | ||
|
|
5c9848c0ba | ||
|
|
0a59ebd7d3 | ||
|
|
5d6f598c31 | ||
|
|
4f5b19e48b | ||
|
|
ad295e12eb | ||
|
|
2be68ca37f | ||
|
|
d13381db7f | ||
|
|
79bd23e728 | ||
|
|
1262880f14 | ||
|
|
a8f6c7f1e4 | ||
|
|
48805ff810 | ||
|
|
c66d364451 | ||
|
|
6e701ae33a | ||
|
|
7daf9aa103 | ||
|
|
d70e540482 | ||
|
|
5dee4bac51 | ||
|
|
7eec8fdf57 | ||
|
|
25198bc11c | ||
|
|
e5921ccb4e | ||
|
|
a4546e4ee4 | ||
|
|
2ce1c50544 | ||
|
|
b2be40af35 | ||
|
|
3dc7d823e4 | ||
|
|
33f076e66c | ||
|
|
aff56927ec | ||
|
|
8a89f821e0 | ||
|
|
02eb17dd91 | ||
|
|
2c9a1add30 | ||
|
|
33a29a51d5 | ||
|
|
9e15706068 | ||
|
|
379bd161cb | ||
|
|
398081b22f | ||
|
|
fdaba13f51 | ||
|
|
7fd9e20e21 | ||
|
|
595d12a038 | ||
|
|
9793e6dbae | ||
|
|
94783f564e | ||
|
|
4ab6b670d2 | ||
|
|
e3e8fc4388 | ||
|
|
e0638d6b6d | ||
|
|
09a329d54a | ||
|
|
4eff40ca64 | ||
|
|
7a194ccaa0 | ||
|
|
697b774248 | ||
|
|
c01257847b | ||
|
|
cb954b6fd0 | ||
|
|
e4ffc4eb8a | ||
|
|
bccd916fab | ||
|
|
e76366728c | ||
|
|
fffe6c088c | ||
|
|
dcbcce0f66 | ||
|
|
0578954826 | ||
|
|
69c61ac914 | ||
|
|
0c4493af44 | ||
|
|
9612812609 | ||
|
|
b1ba609e79 | ||
|
|
9e2fce433f | ||
|
|
ae2846f852 | ||
|
|
913d06afa9 | ||
|
|
fee75576cf | ||
|
|
2cca7d6012 | ||
|
|
12ed206a85 | ||
|
|
4fd46ba023 | ||
|
|
a6a28aaf91 | ||
|
|
8fedb37860 | ||
|
|
2ecaa56ead | ||
|
|
890b2bc615 | ||
|
|
a8e01c01dc | ||
|
|
a08d46a1b7 | ||
|
|
2fefb1e6d1 | ||
|
|
043955983f | ||
|
|
ffe57c5c89 | ||
|
|
dd9f20049e | ||
|
|
26975686f9 | ||
|
|
459c62ba2d | ||
|
|
0df0bd16b5 | ||
|
|
7128432e39 | ||
|
|
3268c3c412 | ||
|
|
11dd195688 | ||
|
|
5b86843430 | ||
|
|
3e5044ce4a | ||
|
|
5345434ba7 | ||
|
|
0ca7bc45f0 | ||
|
|
a4702d2a13 | ||
|
|
455bc987b2 | ||
|
|
4b0b0daa6d | ||
|
|
91f73fbd03 | ||
|
|
77b527f129 | ||
|
|
6ab6c11f06 | ||
|
|
92e84a1a4b | ||
|
|
a0b71fdfb7 | ||
|
|
3e8f0ce369 | ||
|
|
b65abb4b95 | ||
|
|
0478588886 | ||
|
|
1a5892ee40 | ||
|
|
e3788c0b2b | ||
|
|
feda5f4a03 | ||
|
|
6d9fb0b1e6 | ||
|
|
b78160ca6f | ||
|
|
20c36b112e | ||
|
|
fcfba77f31 | ||
|
|
8870afe18a | ||
|
|
4be04b8a34 | ||
|
|
e231c5a6fe | ||
|
|
7585acd0a8 | ||
|
|
b9fb5791b0 | ||
|
|
a5199495fd | ||
|
|
28a5795323 | ||
|
|
d6cf9a3503 | ||
|
|
2a769dddba | ||
|
|
7a65e86cdc | ||
|
|
2be8873fec | ||
|
|
7d5ed42d18 | ||
|
|
90bf8bcd67 | ||
|
|
d028390da5 | ||
|
|
0a542d849e | ||
|
|
09358fb65e | ||
|
|
ae49d886da | ||
|
|
7afaa2629b | ||
|
|
5e27333f74 | ||
|
|
68f278a744 | ||
|
|
a0c26c9646 | ||
|
|
45900ef294 | ||
|
|
95eed63460 | ||
|
|
04d03b8f87 | ||
|
|
6617941d47 | ||
|
|
05344906ef | ||
|
|
94ece1a232 | ||
|
|
38bd178aa3 | ||
|
|
56c821cce3 | ||
|
|
3298c5f64d | ||
|
|
087ad75f47 | ||
|
|
460c503e43 | ||
|
|
3687a6eb0d | ||
|
|
e07441fb09 | ||
|
|
212801ead2 | ||
|
|
7971354675 | ||
|
|
c3144f65dd | ||
|
|
210d6e80c5 | ||
|
|
f8ee87145f | ||
|
|
52df211266 | ||
|
|
976260ed14 | ||
|
|
893db92985 | ||
|
|
a690d0ee3f | ||
|
|
d48dcaba4e | ||
|
|
f68c7f4232 | ||
|
|
fc5933bb2a | ||
|
|
0e41e1fcca | ||
|
|
bbb05cfc0b | ||
|
|
71a427ac0f | ||
|
|
e8eea8548c | ||
|
|
ab4bb5b68d | ||
|
|
ee902e4c40 | ||
|
|
e1fd4fd244 | ||
|
|
a5172c1e2f | ||
|
|
5c9d2e5c62 | ||
|
|
79c5a210f8 | ||
|
|
4864703e11 | ||
|
|
b3e5b2401e | ||
|
|
301f734dbf | ||
|
|
577b72969a | ||
|
|
11ccdb7fac | ||
|
|
7fe604dbec | ||
|
|
2306b2e788 | ||
|
|
65b39a51ca | ||
|
|
8473fe429e | ||
|
|
3586d520d3 | ||
|
|
79f60057fa | ||
|
|
77b01cbadb | ||
|
|
b9cb73a6c7 | ||
|
|
11a677ed1c | ||
|
|
d3a0846775 | ||
|
|
25db57f468 | ||
|
|
bd2a742356 | ||
|
|
d0a2d89c92 |
No files matched your search
@@ -59,8 +59,22 @@ Detailed device findings per game/issue: docs/DEVICE_NOTES.md (read before debug
|
||||
agent ends at EOF); sources and costs in docs/FRAME_RUNTIME.md "Monitoring sources"; game card (fps from
|
||||
FrameBridge pacing; End game = Steam's Exit game, then cmd_stop), tiles with `C.Sparkline` (Flet canvas, no charts
|
||||
extension), details, a pooled process table (Game / Steam & SteamVR / All; right-click: end / force kill / end
|
||||
game; MON_CRITICAL needs force, MON_NEVER is refused)); settings; welcome; activity panel). Files, Screenshots and the Library share right-click menus
|
||||
(one `ft.ContextMenu` per view, filled on right-click; on one of several selected items they act on the whole
|
||||
game; MON_CRITICAL needs force, MON_NEVER is refused)); room (`views/room.py`, persistent, "experimental" pill:
|
||||
scan with progress + Cancel = agent v109 `room_capture_stop`, play area, delete; cache `ui/room_state.py` shared
|
||||
with the Frame page's short card, mesh cached in `<data>/rooms-cache/<frame>/`; in-window 3D `ui/room3d.py`
|
||||
RoomCanvas over `mr/view3d.py` (pure-Python projection, mesh reduced to ≤4k/≤1.5k edges at rest/moving, ≤18 fps
|
||||
worker; each redraw sends NEW shape controls: changing a Points list made Flet diff it item by item, 0.3-1 s);
|
||||
"Open full 3D view" = `mr/room_viewer.py` token server on 127.0.0.1 + hand-written WebGL `mr/viewer_page.py`,
|
||||
stopped on disconnect/close; Test Quest MR tip = `mr.TESTER_*`; editing cards `views/room_edit.py` (agent v109):
|
||||
Clean-up = fp_roomscan's refinement settings, `install/rooms.REFINE_DEFAULTS` = the owner's defaults + slider
|
||||
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 "…" menu opened 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. …".
|
||||
- Installs: queued/cancelled/failed ones are remembered (library setting `ui.installs`) → Library "Resume" bar;
|
||||
@@ -218,6 +241,11 @@ Rick and Morty runs on the Frame via its catalog recipe (OpenVR, no Revive),
|
||||
`FRAMEPORT_DESKTOP=1` skips the Steam-parent watchdog and Steam's display; ensure_host_fixes refreshes entries
|
||||
(older installs, stale ones removed), uninstall/purge remove them. Per app: library entry field `desktop_entry`
|
||||
(default on; patches don't apply to Linux apps) → game page switch → agent `desktop_entry`. Untested on device.
|
||||
- App bundles (discussion #482, `sources/bundles.py`, docs/DEVICE_NOTES.md#app-bundles-xapk--apks--apkm--apkx-discussion-482):
|
||||
.xapk/.apks/.apkm/.apkx files and folders of split APKs are unpacked + merged into ONE APK (Lepton: one APK per app;
|
||||
OVRPort: one APK) in `<data>/bundles/<pkg>/<key>/` (lib/assets splits in Python, resource/feature splits via
|
||||
APKEditor `tools/apkeditor.py`); arm64-v8a split only; library field `bundle`; merged APKs are unsigned (builds sign).
|
||||
FrameDrop itself takes no bundles; `.apkx` is no defined format (read as a zip of APKs); old encrypted .apkm: refused.
|
||||
- Scanning: SideQuest backups (`<package>/<time>/apk|obb|data`) and AXRB downloads (`AXRB/<app id>/<binary id>/base.apk`
|
||||
+ OBBs/assets; its `patched/`/`*-axrb.apk` PC builds are skipped) are recognised (`sources/quest_dump.py`, FAQ).
|
||||
- Quest/Rift twins stay separate entries, shown and named in Steam "Title (Quest)"/"(Rift)" (`core/titles.py`).
|
||||
@@ -455,7 +483,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 +598,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
|
||||
**Space warp / ITR2:** OVRPort's dispatcher offers XR_FB_space_warp itself (`patch_disable_space_warp` doesn't reach UE5) → FrameBridge `hide_space_warp` (per game, off; suggested for UE5 + OVRPlugin). `equirect_emul` switches itself off on Vulkan. → docs/DEVICE_NOTES.md#space-warp--itr2
|
||||
**Space warp / ITR2:** OVRPort's dispatcher offers XR_FB_space_warp itself (`patch_disable_space_warp` doesn't reach UE5) → FrameBridge `hide_space_warp` (per game, off; suggested for UE5 + OVRPlugin). Games that *require* space warp (OutRun, #336: "AppSW recommended buffers unavailable") → `space_warp_emul` (`native/adapter/space_warp.c`: MV size, FrameBridge-owned MV/depth images, info stripped; overrides hide_space_warp). `equirect_emul` switches itself off on Vulkan. → docs/DEVICE_NOTES.md#space-warp--itr2
|
||||
**PR #34 (Lucas-Mathieu):** `native/adapter/audio_metadata.h` keeps Meta XR Audio Wwise metadata alive (Batman: Arkham Shadow 1.4.1 smoke-bomb crash). Outside PRs: review source, rebuild `artifacts/` ourselves, merge locally. → docs/DEVICE_NOTES.md#pr-34-meta-xr-audio-metadata-batman
|
||||
**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).
|
||||
**Proton default:** the newest **stable** ARM64 Proton (`pick_proton`; owner's headset A/B); `pcvr.proton_tool` dropdown (Experimental = alias `proton-experimental`), `ensure_proton`, `proton_alternative_worth_trying`. → docs/DEVICE_NOTES.md#proton-default-stable-vs-experimental
|
||||
**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 +621,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
|
||||
**Lepton's logcat mirror dies** (EOF, read failure, or silent) → launch.sh `_logcat_keeper` reads `podman exec <container> logcat` itself into launch.log (log `<base>/logcat-keeper.log`). → docs/DEVICE_NOTES.md#leptons-logcat-mirror-dies
|
||||
**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 +634,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
|
||||
**Vulkan shader dump:** adapter `vk_shader_dump=1` (`native/vkshim/shader_dump.h`) writes SPIR-V modules + `index.txt` to `files/fp_vk_shaders/`; `collect_diag` returns the newest session's modules (`shader_dumps`); triage `gpu-hang`, `triage.graphics_api`. → docs/DEVICE_NOTES.md#vulkan-shader-dump
|
||||
**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); another app's name (#129), duplicates and unclear cases stay warnings. → docs/DEVICE_NOTES.md#expansion-file-names-450-481
|
||||
**Unreal OBB check (#159):** `frame.unreal_skip_obb_check` (analysis `unreal_verify_obb`) turns off Epic's OBB CRC pass; triage `unreal-obb-check-stuck`. → docs/DEVICE_NOTES.md#unreal-obb-check
|
||||
**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 +739,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
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
|
||||
Thanks for helping. The quickest contributions:
|
||||
|
||||
- **A game works on the Frame?** In FramePort: the game's menu → **Share working config…** opens a prefilled issue
|
||||
with its recipe. Accepted configs become built-in recipes.
|
||||
- **Something doesn't work?** The game's menu → **Report a problem…** saves a diagnostics zip (personal data
|
||||
removed) and opens a prefilled issue to attach it to.
|
||||
- **A game works on the Frame?** Game menu → **Recipe → Share working recipe…** opens a prefilled issue. After
|
||||
review the recipe becomes a built-in recipe (`catalog/games/<package>.yaml`), and installed copies of FramePort get
|
||||
the recipe within 6 hours.
|
||||
- **Something doesn't work?** Game menu → **Troubleshoot → Report a problem…** saves a diagnostics zip (personal data
|
||||
removed) and opens a prefilled issue to attach the zip to.
|
||||
|
||||
Without the app: [working recipe form](https://github.com/spoopyghosty0/frameport/issues/new?template=working-config.yml)
|
||||
· [problem report form](https://github.com/spoopyghosty0/frameport/issues/new?template=bug-report.yml).
|
||||
|
||||
## Development
|
||||
|
||||
```
|
||||
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).
|
||||
|
||||
@@ -8,78 +8,73 @@
|
||||

|
||||
|
||||
Install Quest games, Android apps, Linux apps and PC VR games on the **Valve Steam Frame**. FramePort sets up the
|
||||
Frame, patches each game so it runs there, uploads it and adds it to your Steam library with artwork.
|
||||
Frame, patches each game to run there, uploads the game and adds the game to your Steam library with artwork.
|
||||
|
||||
[](docs/media/frameport-tour.mp4)
|
||||
|
||||
▶ [Watch the full tour](docs/media/frameport-tour.mp4) · New to FramePort? [Watch the install tutorial](docs/media/frameport-install.mp4)
|
||||
▶ [Watch the tour](docs/media/frameport-tour.mp4) · [Watch the install tutorial](docs/media/frameport-install.mp4)
|
||||
(about 90 seconds each)
|
||||
|
||||
> **Notice:** FramePort doesn't download, share or unlock games. Use games you got legally, for example from
|
||||
> [SideQuest](https://sidequestvr.com/). FramePort downloads published open-source tools (see [Built on](#built-on))
|
||||
> and adds its own patches so games run on the Frame.
|
||||
> [SideQuest](https://sidequestvr.com/). FramePort downloads published open-source tools ([Built on](#built-on))
|
||||
> and adds its own patches.
|
||||
>
|
||||
> Not every game runs on the Frame: recipes are tested by the community, and some games need Meta's services or
|
||||
> hardware the Frame lacks.
|
||||
> Not every game runs on the Frame. Some need Meta's services or hardware the Frame lacks.
|
||||
|
||||
## Features
|
||||
|
||||

|
||||
|
||||
- **Easy setup:** one line in the Frame's terminal. No root, no password. [What it changes](docs/FRAME_SETUP.md).
|
||||
- **One click per game:** patch, upload, add to Steam with artwork and test that it starts.
|
||||
- **Recipes:** tested patches and settings for 120+ games; other games get suggested patches, each explained in
|
||||
plain words.
|
||||
- **One click per game:** patch, upload, add to Steam with artwork, test that the game starts.
|
||||
- **Recipes:** tested patches and settings for hundreds of games. Other games get suggested patches, each explained.
|
||||
- **Game settings:** sharpness, refresh rate, controllers, 360° video and mixed reality as simple switches.
|
||||
- **More than Quest games:** Android apps in a window, Linux apps, PC VR games and Windows programs.
|
||||
- **Install links:** "Install with FramePort" buttons on websites ([frameport.app](https://frameport.app) links) open
|
||||
in FramePort.
|
||||
- **Your Frame from your PC:** Type on Frame (your keyboard on the Frame), Files, Screenshots, Live view (what the
|
||||
headset shows, in your browser) and Monitor (frame rate, temperatures, battery, processes).
|
||||
- **Install links:** "Install with FramePort" buttons on websites open in FramePort.
|
||||
- **Your Frame from your PC:** Type on Frame, Files, Screenshots, Live view (what the headset shows) and Monitor
|
||||
(frame rate, temperatures, battery, processes).
|
||||
- **Your room for mixed reality (experimental):** scan your room in the headset and fix the scan up in 3D. Try the room with
|
||||
[Test Quest MR](https://github.com/spoopyghosty0/TestQuestMR).
|
||||
- **Updates itself**, and reports problems without personal data.
|
||||
|
||||
Details for each: [Install and first steps](docs/INSTALL.md).
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Quick start
|
||||
|
||||
1. [Download](https://github.com/spoopyghosty0/frameport/releases/latest) the build for Windows, macOS (Apple
|
||||
Silicon) or Linux, unpack it and start FramePort.
|
||||
2. In FramePort click **Steam Frame → Start setup**. The Frame and your PC must be on the same Wi-Fi (or use a USB
|
||||
cable).
|
||||
3. On the Frame open the **SteamVR dashboard → Launch a program → Desktop**, then the app menu → **System →
|
||||
Konsole**, and run:
|
||||
1. [Download](https://github.com/spoopyghosty0/frameport/releases/latest) FramePort for Windows, macOS (Apple
|
||||
Silicon) or Linux, unpack the download and start FramePort.
|
||||
2. Click **Steam Frame → Start setup**. The Frame and your PC must be on the same Wi-Fi (or use a USB cable).
|
||||
3. On the Frame open **SteamVR dashboard → Launch a program → Desktop**, then **app menu → System → Konsole**, and
|
||||
run:
|
||||
|
||||
```
|
||||
curl -fsSL https://frameport.app/s | bash
|
||||
```
|
||||
|
||||
4. Click **Allow** in FramePort when it shows the same 4-digit code as the Frame. Steam restarts once; if it asks to
|
||||
install **Lepton** (Valve's Android runtime), confirm it.
|
||||
5. **Add games → Scan a folder…** with your games, open one and click **Install on Frame**. Play it from the Frame's
|
||||
Steam library.
|
||||
4. Click **Allow** when FramePort shows the same 4-digit code as the Frame. Steam restarts once. If Steam asks to install
|
||||
**Lepton** (Valve's Android runtime), confirm.
|
||||
5. **Add games → Scan a folder…**, open a game and click **Install on Frame**. Play the game from the Frame's Steam library.
|
||||
|
||||
Full guide, firewalls and troubleshooting: [Install and first steps](docs/INSTALL.md). How to lay out game folders:
|
||||
[FAQ](docs/FAQ.md).
|
||||
Full guide: [Install and first steps](docs/INSTALL.md). Game folders: [FAQ](docs/FAQ.md).
|
||||
|
||||
## Compatibility
|
||||
|
||||
Tested games use their recipe (the patches and settings that work for them) automatically; for other games FramePort
|
||||
suggests patches. See the [list of tested games](docs/GAMES.md). Got a game working? Share its recipe from the game's
|
||||
**…** menu: [how](docs/INSTALL.md#share-a-recipe-or-report-a-problem).
|
||||
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:
|
||||
[FrameDrop about](https://framedropvr.com/about) · [how-to](https://framedropvr.com/how-to) ·
|
||||
[Valve: loading games on Steam Frame](https://partner.steamgames.com/doc/steamhardware/steamframe/loadgames).
|
||||
Out of date? [Report a problem](https://github.com/spoopyghosty0/frameport/issues/new).
|
||||
Out of date? [Report the problem](https://github.com/spoopyghosty0/frameport/issues/new).
|
||||
|
||||
## Built on
|
||||
|
||||
@@ -119,15 +114,17 @@ What FramePort adds: [Architecture](docs/ARCHITECTURE.md).
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| [Install and first steps](docs/INSTALL.md) | Install, connect, update, PC VR games, files, problem reports |
|
||||
| [What the setup changes](docs/FRAME_SETUP.md) | What setup changes on the Frame, networks and firewalls, undoing it |
|
||||
| [Compatibility](docs/COMPATIBILITY.md) | What runs and how well |
|
||||
| [Tested games](docs/GAMES.md) | Tested games and how well they run |
|
||||
| [FAQ](docs/FAQ.md) | Game folders and common questions |
|
||||
| [Controller buttons](docs/CONTROLLERS.md) | Quest Touch buttons on the Frame: SteamVR default, Mirror and Quest pair |
|
||||
| [Porting playbook](docs/PLAYBOOK.md) | Symptoms and fixes per game |
|
||||
| [Steam Frame runtime reference](docs/FRAME_RUNTIME.md) | Facts about the Frame's runtime |
|
||||
| [Architecture](docs/ARCHITECTURE.md) | How the code is organized |
|
||||
| [Install and first steps](docs/INSTALL.md) | Install, connect, install games, update |
|
||||
| [What the setup changes](docs/FRAME_SETUP.md) | Changes on the Frame, firewalls, undoing it |
|
||||
| [FAQ](docs/FAQ.md) | Game folders and fixing problems |
|
||||
| [Compatibility](docs/COMPATIBILITY.md) · [Tested games](docs/GAMES.md) | What runs, and how well |
|
||||
| [Controller buttons](docs/CONTROLLERS.md) | Quest buttons on the Frame's controllers |
|
||||
| [Themes](docs/THEMES.md) | Color themes and making your own |
|
||||
| [Install button](docs/INSTALL_BUTTON.md) | An "Install with FramePort" button for your download page |
|
||||
| [Diagnostics](docs/DIAGNOSTICS.md) | What a problem report contains |
|
||||
| [Patch reference](docs/PATCHES.md) | Every patch and game setting |
|
||||
| [Porting playbook](docs/PLAYBOOK.md) · [Runtime reference](docs/FRAME_RUNTIME.md) | Symptoms and fixes; facts about the Frame |
|
||||
| [Architecture](docs/ARCHITECTURE.md) · [Screenshots and videos](docs/SHOWCASE.md) | How the code and the docs' pictures are made |
|
||||
| [Contributing](CONTRIBUTING.md) | Code, recipes, translations |
|
||||
|
||||
## Development
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Third-party notices
|
||||
|
||||
FramePort is GPL-3.0-only (see `LICENSE`). It includes or downloads the following third-party work.
|
||||
FramePort is GPL-3.0-only (see `LICENSE`). FramePort includes or downloads the following third-party work.
|
||||
|
||||
## Included in this repository / the release bundles
|
||||
| Component | Where | License |
|
||||
@@ -25,6 +25,8 @@ FramePort's own native code (the FrameBridge adapter, GL/Vulkan/OpenXR shims, th
|
||||
| Eclipse Temurin JRE | api.adoptium.net | GPL-2.0 with Classpath Exception |
|
||||
| apksigner (Android build-tools) | dl.google.com Android repository | Apache-2.0 |
|
||||
| Revive (portable copy, only if Revive isn't installed) | github.com/LibreVR/Revive releases | GPL-3.0 |
|
||||
| Cpp2IL (only for Unity IL2CPP patches) | github.com/SamboyCoding/Cpp2IL releases | MIT |
|
||||
| APKEditor (only for app bundles with resource splits) | github.com/REAndroid/APKEditor releases | Apache-2.0 |
|
||||
|
||||
Valve's Lepton, Proton and SteamVR, and Meta's software, are used on your devices as installed by Steam / Meta;
|
||||
FramePort doesn't distribute them.
|
||||
@@ -1,16 +1,18 @@
|
||||
# Announcements
|
||||
|
||||
`current.md` is the one announcement FramePort shows as a pop-up in the app. Edit it on `main`: the app reads it
|
||||
from GitHub `main` (raw.githubusercontent.com) at most every 6 hours, so a push reaches everyone without a release.
|
||||
`current.md` is the one announcement FramePort shows as a pop-up in the app. Edit `current.md` on `main`: the app
|
||||
reads the file from GitHub `main` (raw.githubusercontent.com) at most every 6 hours, so a push reaches everyone
|
||||
without a release.
|
||||
|
||||
## Who sees it
|
||||
|
||||
- **Only people who already used FramePort before the announcement's `date`.** A new install never shows one, and
|
||||
nor does an announcement published before FramePort was installed.
|
||||
- **Once.** Closing it in any way (the X, a click outside it, Escape, or its button) dismisses it for good. The app
|
||||
remembers dismissals in its settings (`announce.dismissed`).
|
||||
- **A new announcement = a new `id`.** Fixing a typo with the same `id` doesn't show it again to anyone who closed it.
|
||||
A new `id` shows to everyone who used FramePort before its `date`, whether they closed the earlier one or not.
|
||||
- **Only people who used FramePort before the announcement's `date`.** A new install never shows an announcement,
|
||||
and neither does an announcement published before FramePort was installed.
|
||||
- **Once.** Closing the pop-up in any way (the X, a click outside, Escape, or the pop-up's button) dismisses the
|
||||
announcement for good. The app remembers dismissals in its settings (`announce.dismissed`).
|
||||
- **A new announcement = a new `id`.** A typo fix with the same `id` doesn't show the announcement again to anyone
|
||||
who closed it. A new `id` shows to everyone who used FramePort before its `date`, whether they closed the earlier
|
||||
announcement or not.
|
||||
|
||||
## The file
|
||||
|
||||
@@ -31,7 +33,7 @@ The body, in Markdown: **bold**, *italic*, `code`, [links](https://frameport.app
|
||||
|
||||
The body is Markdown (no HTML). A `date` in the future works as a schedule: nobody sees it before then.
|
||||
|
||||
Images look best at about 2:1 (e.g. 1200×600). The pop-up is 600 px wide and shows between 140 and 320 px of the
|
||||
Images look best at about 2:1 (for example 1200×600). The pop-up is 600 px wide and shows between 140 and 320 px of the
|
||||
picture's height.
|
||||
|
||||
`pytest tests/test_announcements.py` checks this file, so a broken announcement fails CI instead of silently showing
|
||||
|
||||
@@ -1,32 +1,66 @@
|
||||
1a4dce5c4a6d0dfc5f20e55ac29e70c73cc2cc124c21ca6a7e1b316e6afb1c79 ./arm64-v8a/libVkLayer_fdm_darken.so
|
||||
5becb96ee86fbda0ce98c0e0fd064f8ba092314aceaa400e3dcc785705dc3c23 ./arm64-v8a/libVkLayer_fp_shaderfix.so
|
||||
222727ddabf7eaee08c92e86c64f02edc6639667e9041ab2629032b885c9ae3a ./arm64-v8a/liba32bridge.so
|
||||
c9c3da5cac224c38e2210b781bd12b7f97e02bd6b0ebb0ef85543e4ea4b76671 ./arm64-v8a/liba32rt.so
|
||||
dfeee15b52a530aef0fc90f79140249522adf888d1c88fd57935dd8ce5257fa5 ./arm64-v8a/libVkLayer_fdm_darken.so
|
||||
86974202436e9209e260a690ae3eb0082d7d0f88e17c621dd77c0788ea66bc6d ./arm64-v8a/libVkLayer_fp_shaderfix.so
|
||||
e63d93e086df3b7b29501188b80fb113879e2fd325d7659f68a31ac5bc87b561 ./arm64-v8a/liba32bridge.so
|
||||
81b073b0cdf11fdb09d22dff682884c5046fe13801f18db9aed232e86f3bf173 ./arm64-v8a/liba32rt.so
|
||||
18d8d917e83a6d87008f41908eb478dff635e798844e125abd1e8a5cf31805e2 ./arm64-v8a/liba32stub.so
|
||||
d6337034fb0bdcca7ef9b7063d85b7586b8508b63b435959e8fffaba0691091c ./arm64-v8a/libfp_langpack.so
|
||||
845905e8c2da617dddcb9625f4ed38e06f1d85f9c729b55dacb2c59488074c3e ./arm64-v8a/libfp_langpack.so
|
||||
69102b128e00fa1809489a7308e09c1505f563bf7091bbdd3fe5a58e4cc96c2c ./arm64-v8a/libfp_luajit.so
|
||||
321da502e0f8f46f0880aad39fe6b84bdef88f925164568814cf704a1b31a577 ./arm64-v8a/libfp_ovrp.so
|
||||
40defdaddcda53bd2649eb48076fae1622bfc2bfc88e9e03c94fc8b45af2a4f2 ./arm64-v8a/libfp_ovrtrace.so
|
||||
0167c2fde5dfde62897f9d85afc4fc8e97784e988b5b8df6cea8c407240b3424 ./arm64-v8a/libfp_tkeys.so
|
||||
e32f9b3222bfe2e58c193db809ac2cf54086df36fd2ec6651aa2e58308f6b39d ./arm64-v8a/libfp_vk.so
|
||||
0055308856070aa7e09d80bcf66ea52dc0a495829b9da64383e38f4350706226 ./arm64-v8a/libfp_vk.so
|
||||
3c548b567c6984c91bf3f4a2b0bae1336f569f3e9fa9c3204872fd9876f88284 ./arm64-v8a/libfp_xrruntime.so
|
||||
383054f8b3b41dde76d062c71856cd3163655e50009c08454bc1793f393ead65 ./arm64-v8a/libfpg.so
|
||||
4a13a33e112c9e4e3c5ff4337ed6e988a9e5b3602f48d654df148ef5fafbf426 ./arm64-v8a/libfpglmv.so
|
||||
36400eae4ba3807bcfcd54463daa217347032b07218792faa448d8cbbd086a0e ./arm64-v8a/libframe_xrshim.so
|
||||
19ba5ed3c4b780f5ae46251f6958f552acfc5f13c8bb39f995579593fa8990c6 ./arm64-v8a/libglshim.so
|
||||
f0894d9cc08aed4c0f6468134e76909d583a208c3f5e528bc3ee795f51b19fcb ./arm64-v8a/libopenxr_loader_generic.so
|
||||
dc8b8dd017934077ad181da0cd08b03581561822b189b580faa2b495e20eec7d ./arm64-v8a/libopenxr_loader_generic.so
|
||||
1feaeafad467c4cafdf2b018a4d84b0bee200c3f711697b4ce97e66a3ba256ca ./arm64-v8a/libovrplatformcompat.so
|
||||
a4ed80ae45ca90758239f152ea5a2e8c127d491e1e697ea5680db6fd7f72db07 ./arm64-v8a/libvrapi.so
|
||||
a4ed80ae45ca90758239f152ea5a2e8c127d491e1e697ea5680db6fd7f72db07 ./arm64-v8a/libvrapi_foveation.so
|
||||
4e03a8389981ed5a27b4c159cc83168c4f93d0d75479a445b02e5669aa26a71b ./armeabi-v7a/libopenxr_loader_generic.so
|
||||
c7a14d34e4ddf20ec943641f6431525867d6f030cd01160df1b24afd441af39f ./arm64-v8a/libvrapi.so
|
||||
c7a14d34e4ddf20ec943641f6431525867d6f030cd01160df1b24afd441af39f ./arm64-v8a/libvrapi_foveation.so
|
||||
200525569cd37551e606be93fc76e8ac41e9516b3ffa922a3d4ebe40ba6a3824 ./armeabi-v7a/libopenxr_loader_generic.so
|
||||
6fd59e6c6e533c0a17204e4f12adaf992ffa6cf58d6280aa9b37ca7ba20299d4 ./dex/browser-handoff.dex
|
||||
1871eae093432d277da4bc751bf5f3269dfcf11f9168260b0c3caadb0dedb19d ./dex/oculusos-stubs.dex
|
||||
b634ab5640e258563c536e658cad87080553df6f34f62269a21d554844e58bfe ./hevc/COPYING.FFmpeg
|
||||
141de01f0d40b97d4f9a720db5ab8ea442f6aa35db5272048899292884994a1c ./hevc/libstagefrighthw.so
|
||||
4516971bbb2b635e19b14d2d37b9353962876f7de02043e7457327052c1fcbc0 ./hevc/manifest.json
|
||||
55aae57d292e9536701f2f8a9dbaf1edcfdd766c5748d07a34da2f5ef197f212 ./hevc/libstagefrighthw.so
|
||||
389352bf10c03ab54e784a43c9b217451dd814cacec1d7178777e337c9d6f7de ./hevc/manifest.json
|
||||
46c3dcad2c43cc30bea3b3680b362ed84a99c15d60714bbbbb422c80ecf90405 ./hevc/media_codecs_frameport.xml
|
||||
529ea35d5d82474afe9e09ad1216d269317f56dd24a05fb100fced52b7b4dbca ./hevc/podman.py.txt
|
||||
6cda3076a8c09b31bb1f90da614ea89e7ee194589626eeac613e522849604e47 ./licenses/LuaJIT.txt
|
||||
2cc16b90abcd3d359f1658c581172bd650017bf2725e24268915f50904c000ec ./linux-arm64-bin/fp_roomscan
|
||||
b10b3a5c10c3339fc63fd9f6cb4d01ea29be11a863d0ebad1389ea69fd471940 ./linux-arm64-bin/fp_venc
|
||||
1aa733117cf57ccff7cf23f0425dbfd73b0332a3ef1c905ceeca0ccd0449c4e0 ./linux-arm64/XR_APILAYER_FRAMEPORT_timefix.json
|
||||
28c2430a02bbd8902c5bfb9562c6fd0e318654f9e05c095bfeb94b0450ab1b07 ./linux-arm64/libxr_frameport_timefix.so
|
||||
b10b3a5c10c3339fc63fd9f6cb4d01ea29be11a863d0ebad1389ea69fd471940 ./linux-arm64-bin/fp_venc
|
||||
00c2dead0fd7497929833467f0e55699d57d26be1b85dce35402993852f7b111 ./roomscan/ceiling.png
|
||||
c1962de31ed2810d31c760cf9ee63316ac2b8c9528ccbcd9299ebf5ba39b2995 ./roomscan/done.png
|
||||
0dfaea4c68979b0a45a9fef06fdf1a92468ffc8f46f80a3a014fce1d805972d5 ./roomscan/failed.png
|
||||
f2bae84f1d1512f87b25e688acdddb29b3f360f3f0d0cd4fdeeb741a79e7b19a ./roomscan/finish_00.png
|
||||
9be7720c45f162e99813cb9a7a03e026fed53b41f06637b30ded309eeac91759 ./roomscan/finish_01.png
|
||||
220d699f879b3a8669cfaf4dc714496e8c1db7e7878e7cd12b1a41aec9b62faa ./roomscan/finish_02.png
|
||||
42871b440b77449d4fe41be1c809a930148a874caa8bc92b13b5d25bb6effa83 ./roomscan/finish_03.png
|
||||
a470f62624c94ed6791cc7ee03066e1ffb0a676d520658a0a8b02a6af1605b3b ./roomscan/finish_04.png
|
||||
195ff1c649fd9b42bfa6eea29c43a8b67116fd6ffe9b0fe53dcbdb1074bd34fa ./roomscan/finish_05.png
|
||||
a45accab86b02d7b78327a17b84a6fea543ecc549282055dd3182c686fc2b1b8 ./roomscan/finish_06.png
|
||||
b88d6c590ea839a9d11b3a4de8c3effb39752f7114318966fddae09995e0fd37 ./roomscan/finish_07.png
|
||||
4a3615db26cf765b855a997be3e067aed58464894dab4a6700fdec5c2b854501 ./roomscan/finish_08.png
|
||||
f532a83044e707c66dbc74787ce7b844890bc05829d3e9c8f7e33dae9db246a5 ./roomscan/finish_09.png
|
||||
ffce61a9f13bbb250fad3582396a5bec82a1bad1d585b7ef9b8f65d343806b26 ./roomscan/finish_10.png
|
||||
4a63772adacb31004749ab04b592076bd36cda61eef52f59037d5abdc9e527db ./roomscan/finish_11.png
|
||||
f373e7021cfce0d1078227f473425a10035ee426102b78f13e0fea4833b736d5 ./roomscan/finish_12.png
|
||||
1147391822be3f1aa5351374b8fd9ec85f8b5ce40e3cb9f3146d778e6fbc652a ./roomscan/floor.png
|
||||
eb3c8b55a9dbafa030797ca9f06a22c3ee3bbef96cb7adea26d433ebad43d43f ./roomscan/look_00.png
|
||||
6ba965ff6d22a469c558f2a68229bd2ff48fc28badb95889ca245310d1dd3e96 ./roomscan/look_01.png
|
||||
0a11cec25ebc76fe11d505d1f2ec162c9b4a6754393065800e73b1f81e2d94c8 ./roomscan/look_02.png
|
||||
fa5a4c94d20af56797b9fcc111148767847a6633ade9217cab373de232efad90 ./roomscan/look_03.png
|
||||
e80fa493e380fba39084be2ee62859adcff4e39aa66566abee10a4d6339c52e9 ./roomscan/look_04.png
|
||||
133617f0d8d1e69c37408aac80033f71856b592b62e3b780cc12b446bd974680 ./roomscan/look_05.png
|
||||
d5dd296ff23641c6973c57ac139147e99c7b783316ab60537adb2155dd196880 ./roomscan/look_06.png
|
||||
c08570aa78f966ae937b2e21d0bad887c5352fbca56aa579a2a7ef8541fb96ad ./roomscan/look_07.png
|
||||
3767e7db5fb6babc872fa62c42604f749c65936717d332101745b201cd450cd5 ./roomscan/look_08.png
|
||||
2924dc65e5c497545340209edc16060e12e1834d11af6871d1c1baa50b7b2562 ./roomscan/look_09.png
|
||||
cd0150dfc10b2aea39403c74eff72fd05fb0111178ef39b4c98657a6d16d105e ./roomscan/look_10.png
|
||||
011355e412f73ade3896e9037490e607c4365d0cf8ceb807d236bf68b414d3ed ./roomscan/look_11.png
|
||||
1b8b47ffb47568d540921f065b5958eeb44a6489498d58e0f33f5d1f535154e2 ./roomscan/look_12.png
|
||||
926c7bc6a103adee5b9bf938f5d4e39095ab77fd6270b5301be3b0428edd9ae6 ./roomscan/start.png
|
||||
c2c70f4af1a5c3e6089dff1130161dd417f8044f99ec639eeecf1083930aab16 ./win-x64/fp_oculushmd.exe
|
||||
60ebc5fea05b8945082e2ca8b9f2eb74616012a26eb50133af273dff0fcddc42 ./win-x64/fp_vrsettings.exe
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"revision": 8,
|
||||
"revision": 11,
|
||||
"runtime_sha256": "456e912c75cd389abcf6a63bc80e2a53bdc334371d00b200c93680388ae955e2",
|
||||
"files": {
|
||||
"libstagefrighthw.so": "141de01f0d40b97d4f9a720db5ab8ea442f6aa35db5272048899292884994a1c",
|
||||
"libstagefrighthw.so": "55aae57d292e9536701f2f8a9dbaf1edcfdd766c5748d07a34da2f5ef197f212",
|
||||
"podman.py": "529ea35d5d82474afe9e09ad1216d269317f56dd24a05fb100fced52b7b4dbca",
|
||||
"media_codecs_frameport.xml": "46c3dcad2c43cc30bea3b3680b362ed84a99c15d60714bbbbb422c80ecf90405",
|
||||
"COPYING.FFmpeg": "b634ab5640e258563c536e658cad87080553df6f34f62269a21d554844e58bfe"
|
||||
|
||||
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -8,19 +8,21 @@ notes: 'Native OpenXR GLES video player. 360 theatres/videos (equirect2 layers,
|
||||
details: 'Settings: equirect_emul (GLES worker draws 360 layers into a projection layer), stable_local, focus_hold,
|
||||
scale 0.75; frame.swapchain_limit for its 7680x3840 theatre swapchains. Its hardware decoding mode (Java MediaCodec,
|
||||
Vr4pMediaPlayer; its own FFmpeg is the software path) gets the Frame''s video hardware with frame.hw_video_decode
|
||||
(added 2026-10-09, not yet checked in the headset).'
|
||||
(added 2026-10-09, not yet checked in the headset). swapchain_pool shares extra GLES panel images to avoid the
|
||||
runtime swapchain count cap (#387); host-tested only, menu interaction needs a headset check.'
|
||||
tested_version: 2.0.22
|
||||
engine: Other
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.hw_video_decode
|
||||
adapter:
|
||||
swapchain_pool: 1
|
||||
equirect_emul: 1
|
||||
stable_local: 1
|
||||
focus_hold: 1
|
||||
scale: 0.75
|
||||
verified:
|
||||
date: '2026-10-01'
|
||||
updated: '2026-10-10'
|
||||
updated: '2026-10-18'
|
||||
min_app: 0.12.1
|
||||
source_hint: 4XVR Video Player
|
||||
@@ -0,0 +1,16 @@
|
||||
package: com.And7Limited.Hitstream
|
||||
title: Hitstream
|
||||
status: works
|
||||
tested_version: 2.06bQuest
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.unity_text_input
|
||||
adapter:
|
||||
controller_models: 1
|
||||
haptic_fix: 1
|
||||
notes: Fitness rhythm game; uses controller models and haptics fix.
|
||||
verified:
|
||||
date: '2026-10-12'
|
||||
known_good_sha256: ecc9e2eee79073ae97ad5d3f70c21e8f049f899f21b51dfd541430bb7dd04cac
|
||||
source_hint: Hitstream
|
||||
@@ -0,0 +1,29 @@
|
||||
package: com.BaggyG.JKXR_Companion_App
|
||||
title: JKXR_Companion_App
|
||||
status: unknown
|
||||
notes: 'JKXR''s companion app: it manages JKXR''s mods and start options in /sdcard/JKXR. On the Frame each game
|
||||
has its own /sdcard, so this recipe shares JKXR''s folder with it (device.shared_folder). Install JKXR first.
|
||||
Starting JKXR from the companion app can''t work (each game runs in its own container): start JKXR from Steam.
|
||||
Shared folder not yet confirmed by a player (GitHub 440).'
|
||||
tested_version: '1.0'
|
||||
engine: Unity
|
||||
xr: VrApi
|
||||
frame:
|
||||
- frame.unity_text_input
|
||||
device:
|
||||
- device.text_input_window
|
||||
adapter:
|
||||
haptic_fix: 1
|
||||
verified:
|
||||
date: '2026-10-20'
|
||||
known_good_sha256: 75ee0487ed6253a682b388df7ff06c6f7f35b2ce1b5a71341aed815bf6d60b34
|
||||
app: 1.3.0
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261007.6125817'
|
||||
agent: 109
|
||||
issue: 440
|
||||
source_hint: jkxrcompanion_115meta
|
||||
shared_folder:
|
||||
game: com.drbeef.jkxr
|
||||
folder: JKXR
|
||||
min_app: 1.3.1
|
||||
@@ -0,0 +1,23 @@
|
||||
package: com.BanzaiStudios.GAZZLERS
|
||||
title: GAZZLERS
|
||||
status: unknown
|
||||
notes: Stopped at start with "Current Platform is not supported" (GitHub issue 404); the recipe makes the game take
|
||||
its Meta Quest path. The fix builds and applies offline; not yet checked on the Frame.
|
||||
details: The game chooses between its Meta Quest and PICO code from SystemInfo.deviceModel and throws for any other
|
||||
device; Lepton's Android reports Valve Lepton (write-once ro.product properties). frame.gazzlers_quest_platform
|
||||
makes PlatformDeterminer.GetAndroidPlatform return Platform.Meta.
|
||||
tested_version: 1.4.1.7857
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.unity_text_input
|
||||
- frame.gazzlers_quest_platform
|
||||
device:
|
||||
- device.text_input_window
|
||||
adapter:
|
||||
haptic_fix: 1
|
||||
verified:
|
||||
date: '2026-10-23'
|
||||
issue: 404
|
||||
updated: '2026-10-23'
|
||||
source_hint: Gazzlers
|
||||
@@ -0,0 +1,26 @@
|
||||
package: com.EgerterSoftware.MovieDeckforPlex
|
||||
title: Movie Deck for Plex
|
||||
status: works
|
||||
notes: Works out of the box and absolutely fantastically so.
|
||||
tested_version: '6.4'
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.meta_permissions
|
||||
- frame.unity_text_input
|
||||
- frame.hw_video_decode
|
||||
device:
|
||||
- device.text_input_window
|
||||
adapter:
|
||||
controller_models: 1
|
||||
haptic_fix: 1
|
||||
scene_emul: 1
|
||||
verified:
|
||||
date: '2026-10-20'
|
||||
known_good_sha256: 397912f98ded90df0dabeb51d097c56de9b70ef99674303fa0efba84d43c4df0
|
||||
app: 1.3.0
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261015.6141207'
|
||||
agent: 109
|
||||
issue: 435
|
||||
source_hint: Movie Deck For Plex
|
||||
@@ -0,0 +1,20 @@
|
||||
package: com.Iconik.LittleNightmaresVR
|
||||
title: Little Nightmares VR
|
||||
status: works
|
||||
tested_version: 1.0.31
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.unity_text_input
|
||||
- frame.hw_video_decode
|
||||
device:
|
||||
- device.text_input_window
|
||||
verified:
|
||||
date: '2026-10-18'
|
||||
known_good_sha256: 171e586bf35ee4494874c6f2c37fb734f718e5b81744b2df9095919398d70839
|
||||
app: 1.2.1
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261015.6141207'
|
||||
agent: 96
|
||||
issue: 421
|
||||
source_hint: Little Nightmares VR- Altered Echoes
|
||||
@@ -0,0 +1,19 @@
|
||||
package: com.MoonMonsterStudios.SpaceControl
|
||||
title: SpaceControl
|
||||
status: works
|
||||
tested_version: 1.1.2
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.unity_text_input
|
||||
device:
|
||||
- device.text_input_window
|
||||
verified:
|
||||
date: '2026-10-20'
|
||||
known_good_sha256: 60b900a0a73a693426b85d81a489c63fab6cc959939f80efd29632945e0ce559
|
||||
app: 1.3.0
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261015.6141207'
|
||||
agent: 109
|
||||
issue: 447
|
||||
source_hint: Space Control
|
||||
@@ -0,0 +1,23 @@
|
||||
package: com.MyronSoftware.Deisim
|
||||
title: Deisim
|
||||
status: works
|
||||
tested_version: 1.58.543
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.bhaptics_permission
|
||||
- frame.photon_voice_audio_route
|
||||
- frame.unity_text_input
|
||||
device:
|
||||
- device.text_input_window
|
||||
adapter:
|
||||
haptic_fix: 1
|
||||
verified:
|
||||
date: '2026-10-21'
|
||||
known_good_sha256: 91bc0a28ff7ca22a877f4f920984c3a6a2c91828a2bccff0c5eb4affb4dc13ae
|
||||
app: 1.3.0
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261015.6141207'
|
||||
agent: 109
|
||||
issue: 462
|
||||
source_hint: Deisim
|
||||
@@ -12,6 +12,7 @@ overport_extra:
|
||||
- patch_force_passthrough
|
||||
adapter:
|
||||
scene_emul: 1
|
||||
scene_hide_bounds: 1
|
||||
device:
|
||||
- device.text_input_window
|
||||
verified:
|
||||
@@ -22,4 +23,4 @@ verified:
|
||||
frame:
|
||||
- frame.meta_permissions
|
||||
- frame.unity_text_input
|
||||
updated: '2026-10-15'
|
||||
updated: '2026-10-17'
|
||||
@@ -0,0 +1,20 @@
|
||||
package: com.ORIGAMEDIGITAL.UmurangiGenerationVR
|
||||
title: Umurangi Generation VR
|
||||
status: issues
|
||||
notes: The game freezes after taking a photo, while it saves it, which makes it hard to play.
|
||||
tested_version: '1.163'
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.unity_text_input
|
||||
device:
|
||||
- device.text_input_window
|
||||
adapter:
|
||||
haptic_fix: 1
|
||||
verified:
|
||||
date: '2026-10-22'
|
||||
known_good_sha256: c4dfe227c3e33e611f9a4b8ee542912c0891fe600a7d32e56cbe84b0524ec8d9
|
||||
app: 1.3.0
|
||||
overport_cli: 1.2.5
|
||||
issue: 472
|
||||
source_hint: com.ORIGAMEDIGITAL.UmurangiGenerationVR.output
|
||||
@@ -6,7 +6,10 @@ engine: Unreal
|
||||
xr: OpenXR
|
||||
status: works
|
||||
notes: Very large data set (~31 GiB). Built with disable_space_warp.
|
||||
details: Reported working on Pico 4 with overport runtime 3.0.x. 83 OBB/pakchunk files (~32 GB).
|
||||
details: 'Reported working on Pico 4 with overport runtime 3.0.x. 83 OBB/pakchunk files (~32 GB): besides main and
|
||||
patch.0, 81 patch.pakchunk<N>-Android_ASTC.com.Sanzaru.Wrath2.obb files (~27 GB). Without them the game hangs at
|
||||
engine start-up (GitHub #471); up to FramePort 1.3.0 they are only sent from a com.Sanzaru.Wrath2 folder next to
|
||||
the APK, not loose next to it.'
|
||||
overport_extra:
|
||||
- patch_disable_space_warp
|
||||
alt_overport:
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package: com.TeravisionGames.OrcsMustDie
|
||||
title: 'Orcs Must Die: By The Blade'
|
||||
status: works
|
||||
notes: Perfect at v151
|
||||
tested_version: 1.0.164237
|
||||
engine: Unreal
|
||||
xr: OpenXR
|
||||
alt_overport:
|
||||
- patch_remove_unreal_force_quit
|
||||
frame:
|
||||
- frame.unreal_ovrp_entrypoints
|
||||
- frame.hw_video_decode
|
||||
adapter:
|
||||
equirect_emul: 1
|
||||
haptic_fix: 1
|
||||
verified:
|
||||
date: '2026-10-21'
|
||||
known_good_sha256: 11e5d45587277a5edea8be4862c7af19aa5c38445676607c541bc1e471f473ff
|
||||
app: 1.3.0
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261015.6141207'
|
||||
agent: 109
|
||||
issue: 453
|
||||
source_hint: OrcsMustDie
|
||||
@@ -5,16 +5,21 @@ source_hint: Assassins Creed Nexus VR
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
status: works
|
||||
notes: FrameBridge turns its upside-down UI panels upright (flip emulation, in place since FramePort 1.2.0). Ubisoft
|
||||
login may not work.
|
||||
frame.unity_text_input and device.text_input_window added from detection (not headset-verified).
|
||||
notes: 'FrameBridge turns its upside-down UI panels upright (flip emulation, in place since FramePort 1.2.0). Ubisoft
|
||||
login may not work. frame.unity_text_input added from detection (not headset-verified); no device.text_input_window:
|
||||
its Android window took the controllers'' input (owner, 2026-10-23).
|
||||
|
||||
Keeps the Quest 2 headset identity for now: as a Quest 3 it switches to larger eye images after the intro, which
|
||||
the Frame''s swapchain limit refused (GitHub #424). With the Vulkan panel pool (swapchain_pool) it runs as a Quest
|
||||
3 (checked headless 2026-10-23); the Quest 3 identity comes back with the FramePort release that has it.'
|
||||
details: Ubisoft online login may not work. Data folder holds raw assets (start0..2, .climb, bundles), not .obb
|
||||
files; install to obb as-is.
|
||||
overport_extra:
|
||||
- patch_ac_nexus_no_appsw_90
|
||||
frame:
|
||||
- frame.unity_text_input
|
||||
device:
|
||||
frame_remove:
|
||||
- device.headset_model
|
||||
- device.text_input_window
|
||||
verified:
|
||||
date: '2026-09-28'
|
||||
@@ -23,5 +28,7 @@ verified:
|
||||
known_good_sha256: 89644a12bf8df956fa3d3a9a40540ba9e1fb62f63494714a05c9f05984383ac8
|
||||
no_appsw_90: '2026-10-02, OVRPort 1.2.5 (owner: 90 Hz felt better than 72 Hz; same known issues)'
|
||||
flip_in_place: '2026-10-14, owner in the headset: launch warnings and tutorial pop-ups upright (GitHub #138)'
|
||||
updated: '2026-10-15'
|
||||
min_app: 1.2.0
|
||||
updated: '2026-10-23'
|
||||
min_app: 1.3.0
|
||||
adapter:
|
||||
swapchain_pool: 1
|
||||
@@ -0,0 +1,11 @@
|
||||
package: com.ZarApps.PianoVision
|
||||
title: PianoVision
|
||||
status: unsupported
|
||||
notes: Starts, but needs camera hand tracking (Android games on the Frame only get hands built from the controllers)
|
||||
and USB MIDI from a piano, which the Frame's Android container doesn't pass through.
|
||||
tested_version: 3.0.8
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
verified:
|
||||
date: '2026-10-20'
|
||||
issue: 297
|
||||
@@ -0,0 +1,22 @@
|
||||
package: com.astrobotquest.vrhost
|
||||
title: Astro VR Host
|
||||
status: works
|
||||
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'
|
||||
tested_version: '0.23'
|
||||
engine: Other
|
||||
xr: OpenXR
|
||||
adapter:
|
||||
refresh_rate: 80.0
|
||||
verified:
|
||||
date: '2026-10-22'
|
||||
known_good_sha256: c2e13ab155b947c0037362635bd34112c16b69b804af82b347021f2261ef5bfb
|
||||
app: 1.3.0
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261015.6141207'
|
||||
agent: 109
|
||||
issue: 464
|
||||
source_hint: AstroQuest
|
||||
@@ -16,6 +16,7 @@ frame:
|
||||
- frame.hw_video_decode
|
||||
- frame.unity_text_input
|
||||
adapter:
|
||||
fixed_resolution: 1
|
||||
sync_guard: 1
|
||||
surface_native: 1
|
||||
fade_quad: 1
|
||||
@@ -26,6 +27,6 @@ verified:
|
||||
overport_cli: 1.2.3
|
||||
overport_runtime: 3.4.3-23204ea
|
||||
known_good_sha256: 796d1cab08f614d72c50d9702974c719c0908f5de91183cfe02d1fd5eb09551a
|
||||
updated: '2026-10-15'
|
||||
min_app: 1.1.0
|
||||
updated: '2026-10-18'
|
||||
min_app: 1.2.2
|
||||
source_hint: Batman- Arkham Shadow
|
||||
@@ -0,0 +1,26 @@
|
||||
package: com.com2usroca.Darksword
|
||||
title: Darksword
|
||||
status: works
|
||||
tested_version: 1.6.2.11
|
||||
engine: Unreal
|
||||
xr: OpenXR
|
||||
alt_overport:
|
||||
- patch_remove_unreal_force_quit
|
||||
frame:
|
||||
- frame.unreal_skip_obb_check
|
||||
- frame.bhaptics_permission
|
||||
- frame.unreal_ovrp_entrypoints
|
||||
- frame.hw_video_decode
|
||||
adapter:
|
||||
haptic_fix: 1
|
||||
scale: 1.5
|
||||
foveation: tracked
|
||||
verified:
|
||||
date: '2026-10-18'
|
||||
known_good_sha256: 47f9968e5fac70a5873b996a4d7b9c01d42d2f0cc1d9dc24013eb2e3b08a8a8e
|
||||
app: 1.2.1
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261007.6125817'
|
||||
agent: 96
|
||||
issue: 410
|
||||
source_hint: darksword
|
||||
@@ -0,0 +1,19 @@
|
||||
package: com.drbeef.jkxr
|
||||
title: JKXR
|
||||
status: issues
|
||||
notes: Tested in the early part of Jedi Outcast. Two players report stutters and freezes after 3-15 minutes of play
|
||||
(under investigation in issue 441).
|
||||
tested_version: 1.1.28
|
||||
engine: Other
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.tbxr_vendor
|
||||
verified:
|
||||
date: '2026-10-20'
|
||||
known_good_sha256: 771e48b22d3a558ea11869aa1e9070b11908b17ab47a9e8866769bb67efc30a5
|
||||
app: 1.3.0
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261007.6125817'
|
||||
agent: 109
|
||||
issue: 441
|
||||
source_hint: QuestAPKs
|
||||
@@ -0,0 +1,23 @@
|
||||
package: com.drbeef.questzdoom
|
||||
title: QuestZDoom
|
||||
status: issues
|
||||
notes: 'Needs a WAD: put it into QuestZDoom/wads in the game''s storage (Files tab) and set QuestZDoom/commandline.txt
|
||||
to "qzdoom -iwad /sdcard/QuestZDoom/wads/<name>.wad". Slowed to a crawl after 5-10 minutes before
|
||||
gl_error_clear (issue 237); not yet confirmed in the headset with it.'
|
||||
details: GZDoom on OpenGL ES with Team Beef's OpenXR code. The game leaves GL_INVALID_ENUM pending every frame; the
|
||||
runtime's GL check failed on it at the first swapchain and its GL interop then created about 100 GPU objects a
|
||||
second that were never freed (8,000 after 75 s headless). Frames slowed down over the minutes, and in a player's
|
||||
session the GPU driver crashed after 6 minutes and the Frame restarted. gl_error_clear clears the errors before
|
||||
FrameBridge calls the runtime; GPU objects then stay flat (headless, 2026-10-24).
|
||||
tested_version: 1.6.2
|
||||
engine: Other
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.tbxr_vendor
|
||||
adapter:
|
||||
gl_error_clear: 1
|
||||
verified:
|
||||
date: '2026-10-24'
|
||||
issue: 237
|
||||
updated: '2026-10-24'
|
||||
source_hint: questzdoom-1.6.2
|
||||
@@ -0,0 +1,24 @@
|
||||
package: com.fasttravelgames.solero
|
||||
title: Solero
|
||||
status: works
|
||||
tested_version: 1.00.109160
|
||||
engine: Unreal
|
||||
xr: OpenXR
|
||||
alt_overport:
|
||||
- patch_remove_unreal_force_quit
|
||||
frame:
|
||||
- frame.gms_platform_version
|
||||
- frame.unreal_ovrp_entrypoints
|
||||
- frame.hw_video_decode
|
||||
adapter:
|
||||
equirect_emul: 1
|
||||
haptic_fix: 1
|
||||
verified:
|
||||
date: '2026-10-20'
|
||||
known_good_sha256: 2d19ac1ed61cc12d0976155fbb6697fb87ec5d60c6601fe1598c965bc3e48377
|
||||
app: 1.3.0
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261015.6141207'
|
||||
agent: 109
|
||||
issue: 448
|
||||
source_hint: Action Hero
|
||||
@@ -0,0 +1,36 @@
|
||||
package: com.fgol.HungrySharkEvolution
|
||||
title: Hungry Shark Evolution
|
||||
status: works
|
||||
notes: Phone game (2D). Tested with 14.6.0 installed from an .xapk bundle; cloud sync and Google services don't work.
|
||||
tested_version: 14.6.0
|
||||
engine: Unity
|
||||
xr: '?'
|
||||
overport_remove:
|
||||
- patch_copy_libraries
|
||||
- patch_copy_ovrplugin_vrapi
|
||||
- patch_replace_icon_label
|
||||
- patch_fix_min_android_sdk
|
||||
- patch_generate_config
|
||||
- patch_remove_localized_names
|
||||
- patch_clean_up_frida
|
||||
- patch_oculus_unity
|
||||
- patch_oculus_unreal
|
||||
- patch_vr_metadata
|
||||
- patch_launcher_entry
|
||||
- patch_remove_uses_library
|
||||
- patch_fix_unreal_crash
|
||||
- patch_meta_xr_audio
|
||||
- patch_mark_as_debuggable
|
||||
- patch_mark_allow_backup
|
||||
frame:
|
||||
- frame.unity_text_input
|
||||
- frame.hw_video_decode
|
||||
verified:
|
||||
date: '2026-10-25'
|
||||
known_good_sha256: d912bc914ea396f19e51d1b35777232cb0a4225a5f288870916744fcaacba9b1
|
||||
app: 1.3.1.dev675
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261022.6153909'
|
||||
agent: 123
|
||||
issue: 486
|
||||
source_hint: Hungry+Shark+Evolution_14.6.0_APKPure
|
||||
@@ -0,0 +1,24 @@
|
||||
package: com.garage.drumsrock
|
||||
title: Drums Rock
|
||||
status: works
|
||||
notes: Let's Rock!
|
||||
tested_version: '1.401'
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.unity_text_input
|
||||
- frame.oculusos
|
||||
device:
|
||||
- device.text_input_window
|
||||
adapter:
|
||||
haptic_fix: 1
|
||||
scale: 2.0
|
||||
verified:
|
||||
date: '2026-10-18'
|
||||
known_good_sha256: 61d724ce51cc5bc17c7f942803922e8606fc5d60bc4ec5134f06754c5172c7eb
|
||||
app: 1.2.1
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261007.6125817'
|
||||
agent: 96
|
||||
issue: 409
|
||||
source_hint: drums rock
|
||||
@@ -0,0 +1,16 @@
|
||||
package: com.gevr.port
|
||||
title: GoldenEye VR
|
||||
status: issues
|
||||
notes: No sound; otherwise works great.
|
||||
tested_version: 0.5.8
|
||||
engine: Other
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.sdl_clipboard
|
||||
verified:
|
||||
date: '2026-10-18'
|
||||
known_good_sha256: 5bd0d5ad71368178f8478193c215459a670d22ca6828bb155d76c5255908759b
|
||||
app: 1.2.1
|
||||
overport_cli: 1.2.5
|
||||
issue: 412
|
||||
source_hint: GoldenEye-VR-v0.5.8
|
||||
@@ -5,8 +5,10 @@ source_hint: NOPE CHALLENGE
|
||||
engine: Unreal
|
||||
xr: OpenXR
|
||||
status: works
|
||||
notes: Meta XR Audio telemetry patch + nodebug. MR "Reality Breach" mode unavailable.
|
||||
details: The MR 'Reality Breach' mode needs passthrough; standard VR mode used.
|
||||
notes: "Meta XR Audio telemetry patch + nodebug. MR mode: needs a scanned room."
|
||||
details: The MR 'Reality Breach' mode needs passthrough and a room; standard VR mode was verified.
|
||||
adapter:
|
||||
scene_emul: 1
|
||||
alt_overport:
|
||||
- patch_remove_unreal_force_quit
|
||||
frame:
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package: com.mrf.tvb.laser_dance
|
||||
title: Laser Dance
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
status: works
|
||||
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.
|
||||
adapter:
|
||||
controller_fix: 1
|
||||
haptic_fix: 1
|
||||
scene_emul: 1
|
||||
passthrough_emul: 1
|
||||
frame:
|
||||
- frame.meta_permissions
|
||||
- frame.meta_depth_occlusion
|
||||
- frame.unity_scene_active_rooms
|
||||
- frame.unity_text_input
|
||||
min_app: 1.2.2
|
||||
updated: '2026-10-18'
|
||||
@@ -0,0 +1,25 @@
|
||||
package: com.onehamsa.underdogs
|
||||
title: UNDERDOGS
|
||||
status: works
|
||||
tested_version: 1.5.4.12573
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.bhaptics_permission
|
||||
- frame.photon_voice_audio_route
|
||||
- frame.unity_text_input
|
||||
- frame.hw_video_decode
|
||||
device:
|
||||
- device.text_input_window
|
||||
adapter:
|
||||
controller_models: 1
|
||||
haptic_fix: 1
|
||||
verified:
|
||||
date: '2026-10-23'
|
||||
known_good_sha256: 0b98b5c71855341abc2ad942dbbdd920a6d362f7fee25a598b1a6ca1248fb27a
|
||||
app: 1.3.0
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261022.6150929'
|
||||
agent: 109
|
||||
issue: 474
|
||||
source_hint: com.onehamsa.underdogs
|
||||
@@ -18,11 +18,12 @@ engine: Unity
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.unity_text_input
|
||||
- frame.pojlib_connection
|
||||
device:
|
||||
- device.text_input_window
|
||||
lepton_env:
|
||||
LEPTON_ENV_LIBGL_NOERROR: '1'
|
||||
verified:
|
||||
date: '2026-10-05'
|
||||
updated: '2026-10-05'
|
||||
updated: '2026-10-23'
|
||||
source_hint: QuestCraft
|
||||
@@ -0,0 +1,27 @@
|
||||
package: com.simpsonsHitAndRun.vr
|
||||
title: Simpsons Hit and Run VR
|
||||
status: works
|
||||
notes: 'Needs the port''s game data: after installing, copy the "SimpsonsHitRun" folder (see the port''s GitHub
|
||||
instructions) into the game''s storage with FramePort''s Files tab. In-game settings: Vehicle lights off, Enhanced
|
||||
materials off, CSM shadows off (otherwise it crashes when starting a new game), Render scale 150% at 120 Hz.'
|
||||
details: 'Tested with v1.2 of https://github.com/kote2345/The-Simpsons-Hit-and-Run-VR. Vehicle lights break entities
|
||||
and Enhanced materials break textures. Render scale above 150% can''t keep up at 120 Hz (200% runs at about 100
|
||||
fps). Optional: set the in-game VR tab''s mode to VR to make the menus more comfortable. While paused, a long
|
||||
black texture can appear from the head when looking up; it goes away when the pause menu closes. Contributed in
|
||||
issues 443 and 461.'
|
||||
tested_version: '0.93'
|
||||
engine: Other
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.sdl_clipboard
|
||||
adapter:
|
||||
refresh_rate: 120.0
|
||||
verified:
|
||||
date: '2026-10-22'
|
||||
known_good_sha256: e406a5da1b2f99fc3a9f95af44f458598ad52d1296a8c577bf1f8e2343738ff2
|
||||
app: 1.3.0
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261015.6141207'
|
||||
agent: 109
|
||||
issue: 461
|
||||
source_hint: SimpsonsHitRun_1.2
|
||||
@@ -0,0 +1,22 @@
|
||||
package: com.stalwart.barbaria
|
||||
title: Barbaria
|
||||
status: works
|
||||
tested_version: 1.10.1.22383.158.OBERON.Quest.release
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.unity_text_input
|
||||
device:
|
||||
- device.text_input_window
|
||||
adapter:
|
||||
haptic_fix: 1
|
||||
scale: 2.0
|
||||
verified:
|
||||
date: '2026-10-18'
|
||||
known_good_sha256: e0cd063fc14a2bea5503bf63cf83246527b286915f05186ca5a2c42ce6e7d75a
|
||||
app: 1.2.1
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261007.6125817'
|
||||
agent: 96
|
||||
issue: 418
|
||||
source_hint: barbaria
|
||||
@@ -0,0 +1,23 @@
|
||||
package: com.wnas.eternalstarlight
|
||||
title: Eternal Starlight
|
||||
status: issues
|
||||
notes: Voice commands do not appear to work.
|
||||
tested_version: 1.7.404
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.bhaptics_permission
|
||||
- frame.unity_text_input
|
||||
device:
|
||||
- device.text_input_window
|
||||
adapter:
|
||||
haptic_fix: 1
|
||||
verified:
|
||||
date: '2026-10-24'
|
||||
known_good_sha256: 37a79332a89c974971df30a38adbeafdc2466c3163348ab4215ff93014d5be12
|
||||
app: 1.3.0
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261022.6153909'
|
||||
agent: 109
|
||||
issue: 479
|
||||
source_hint: Eternal Starlight
|
||||
@@ -1,13 +1,14 @@
|
||||
package: com.zenstudios.PFXVRQuest
|
||||
title: Pinball FX VR
|
||||
status: issues
|
||||
notes: Plays; mixed reality mode not working yet. Space warp off (fewer stutters).
|
||||
notes: "Plays; space warp off (fewer stutters). MR mode: needs a scanned room."
|
||||
tested_version: '1.9'
|
||||
engine: Unreal
|
||||
xr: OpenXR
|
||||
alt_overport:
|
||||
- patch_remove_unreal_force_quit
|
||||
adapter:
|
||||
scene_emul: 1
|
||||
equirect_emul: 1
|
||||
hide_space_warp: 1
|
||||
verified:
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package: dev.melee.game
|
||||
title: Xelee
|
||||
status: unknown
|
||||
notes: 'The app opens a flat launcher (MeleeActivity) that starts the VR part (MeleeXrActivity): "Show the app''s
|
||||
Android window" is on so that launcher can be seen in the headset; without it Lepton runs the app headless, the
|
||||
launcher waits invisibly and OpenXR never starts. Keep the Vulkan library rename disabled: the engine (Aurora/Dawn
|
||||
WebGPU) builds the name libvulkan.so from two pieces, so the rename made it ask for "libfp_vkn.so" (GitHub #475).
|
||||
The VR part is not yet checked on the Frame.'
|
||||
details: 'Vulkan (Dawn) loads and precompiles all 6080 pipelines in about 34 s with the rename off; the launch then
|
||||
stays in the 2D launcher, so no OpenXR call is made (GitHub #475).'
|
||||
tested_version: 0.1.0-beta
|
||||
engine: Other
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.meta_permissions
|
||||
- frame.sdl_clipboard
|
||||
frame_remove:
|
||||
- frame.vk_sanitize
|
||||
device:
|
||||
- device.text_input_window
|
||||
adapter:
|
||||
scene_emul: 1
|
||||
updated: '2026-10-24'
|
||||
source_hint: Xelee-Quest-arm64
|
||||
@@ -0,0 +1,26 @@
|
||||
package: games.b4t.epicrollercoasters.oculus
|
||||
title: Epic Roller Coasters
|
||||
status: works
|
||||
notes: You may need to restart the game for the camera to be placed in front of the menu.
|
||||
tested_version: 8.36.04
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
overport_extra:
|
||||
- patch_disable_space_warp
|
||||
frame:
|
||||
- frame.bhaptics_permission
|
||||
- frame.vivox_audio_route
|
||||
- frame.unity_text_input
|
||||
device:
|
||||
- device.text_input_window
|
||||
adapter:
|
||||
haptic_fix: 1
|
||||
verified:
|
||||
date: '2026-10-21'
|
||||
known_good_sha256: 5760a135be2e2df634c78eae4f9d177b2a95a9da26a4739fdd6e1f5159ed9320
|
||||
app: 1.3.0
|
||||
overport_cli: 1.2.5
|
||||
frame_build: '20261015.6141207'
|
||||
agent: 109
|
||||
issue: 456
|
||||
source_hint: Epic Roller Coasters
|
||||
@@ -0,0 +1,21 @@
|
||||
package: jp.strippers.punchfit
|
||||
title: Punch Fit
|
||||
status: works
|
||||
tested_version: 1.7.2
|
||||
engine: Unity
|
||||
xr: OpenXR
|
||||
frame:
|
||||
- frame.meta_permissions
|
||||
- frame.unity_text_input
|
||||
adapter:
|
||||
haptic_fix: 1
|
||||
pose_time_fix: 1
|
||||
scene_emul: 1
|
||||
passthrough_emul: 1
|
||||
notes: Mixed reality workout arena; composition layer trimming prevents 21+ layer overflow.
|
||||
verified:
|
||||
date: '2026-10-12'
|
||||
known_good_sha256: 1a0050272526360542d62e7e6d1fc2daecb765c34378a6fe4ad51568ec550c0d
|
||||
source_hint: Punch Fit
|
||||
min_app: 1.2.2
|
||||
updated: '2026-10-18'
|
||||
@@ -0,0 +1,20 @@
|
||||
package: org.outrun.quest.vr
|
||||
title: OutRun Quest VR
|
||||
status: unknown
|
||||
notes: Needs colour-space support and FrameBridge's space warp (it quits at start without them). Not yet checked on
|
||||
the Frame with both (GitHub #336).
|
||||
details: Own engine with OpenGL ES and its own OpenXR loader. It requires XR_FB_color_space (Rec.709; quit with "Rec.709
|
||||
color space unavailable") and Meta's Application SpaceWarp (quit with "AppSW recommended buffers unavailable"); the
|
||||
Frame's runtime offers neither in a usable form. fb_color_space offers the colour-space extension; space_warp_emul
|
||||
offers XR_FB_space_warp with a motion-vector size, serves the motion-vector and depth images as GL textures and
|
||||
removes the space warp info from every frame. Turning space warp off can't help, the game refuses to start without it.
|
||||
tested_version: 0.4.25-music-indicators
|
||||
engine: Other
|
||||
xr: OpenXR
|
||||
adapter:
|
||||
fb_color_space: 1
|
||||
space_warp_emul: 1
|
||||
verified:
|
||||
issue: 336
|
||||
updated: '2026-10-24'
|
||||
source_hint: OutRunQuest-VR-v0.4.25-music-indicators-arm64
|
||||
@@ -1,9 +1,10 @@
|
||||
package: org.primedgun.v2
|
||||
title: PrimedGun v2
|
||||
status: unknown
|
||||
notes: 'Without the Vulkan library rename: the game builds the library name from two pieces, so the renamed name didn''t
|
||||
exist ("libfp_vkn.so"), its renderer fell back to OpenGL and VR couldn''t start (GitHub #254). Not yet confirmed in
|
||||
the headset.'
|
||||
status: issues
|
||||
notes: 'Vulkan via a statically linked Khronos OpenXR loader, which bypasses FrameBridge (the picture jumps).
|
||||
The embedded-loader patch is off for now: with it, the game''s loader and the one FramePort adds call each other
|
||||
until the game crashes at Play (GitHub #254). Keep the Vulkan library rename disabled: this game assembles its
|
||||
library name from two pieces, so renaming it prevented Vulkan from loading and VR could not start.'
|
||||
tested_version: 2.0.0-alpha.4
|
||||
engine: Other
|
||||
xr: OpenXR
|
||||
@@ -11,5 +12,6 @@ frame:
|
||||
- frame.sdl_clipboard
|
||||
frame_remove:
|
||||
- frame.vk_sanitize
|
||||
updated: '2026-10-18'
|
||||
- frame.static_openxr_loader
|
||||
updated: '2026-10-23'
|
||||
source_hint: PrimedGun v2 Quest APK (github.com/iChris4/MetroidPrimePortVR)
|
||||
@@ -3,9 +3,67 @@
|
||||
# "apply and rebuild"; an entry `adapter.<key>=<value>` sets that value (else 1). `severity`: fatal (the game can't
|
||||
# run) | error | warning | info. `question`: a symptom only the player sees (play sessions, GUI "Last session"): its
|
||||
# fix is offered as that question, never applied on its own. `report: true` = the useful next step is a problem report
|
||||
# with diagnostics. Launch tests and real play sessions (agent session_log, `frameport session`) are both matched.
|
||||
# with diagnostics. `unless_patch: <patch id>` = not when that patch is in the game's build (a log line that stays
|
||||
# the same with the fix in place). Launch tests and real play sessions (agent session_log, `frameport session`) are both matched.
|
||||
# Sources: every failure we hit porting 34 games to the Steam Frame (see docs/PLAYBOOK.md).
|
||||
# Installed apps fetch this file from GitHub main (every 6 h, like catalog/games) and use each remote signature in
|
||||
# place of their bundled one with the same id, unless it names a patch/adapter setting or a field the app doesn't know,
|
||||
# or `min_app` (per signature, or top-level for the whole file) is above its version. Optional `updated: 'YYYY-MM-DD'`
|
||||
# lets a bundled copy that was changed later win over an older remote one.
|
||||
signatures:
|
||||
- id: mr-no-room
|
||||
pattern: 'FrameBridge[^\r\n]*scene: (?:capture requested -> (?:guardian box|using guardian room)|room from guardian|room [^\r\n]*(?:from guardian|configured/default size|box))'
|
||||
severity: info
|
||||
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.'
|
||||
suggest: []
|
||||
- id: mr-discovery-unsupported
|
||||
pattern: '(?:Unknown proc addr:\s*xrDiscoverSpacesMETA|(?:MRUK[^\r\n]*)?NoRoomsFound)'
|
||||
# discovery reached FrameBridge and found the room (OVRPort's "Unknown proc addr" comes before the shim takes over)
|
||||
unless: 'FrameBridge[^\r\n]*scene: discovery request=\d+ results=[1-9]'
|
||||
severity: warning
|
||||
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.'
|
||||
suggest: [adapter.scene_emul]
|
||||
- id: dynamic-resolution-foveation
|
||||
pattern: 'projection view [01](?: rect change)?:[^\r\n]*rect=(?:[1-9]\d*,\d+|0,[1-9]\d*) '
|
||||
# a rebuilt APK's foveation centring layer re-centres Valve's density map on the submitted rect (#394)
|
||||
unless: 'fixed_resolution: recommended|fdm_centre: pass'
|
||||
severity: info
|
||||
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.
|
||||
suggest: [frame.static_openxr_loader]
|
||||
- id: cryengine-init-failed
|
||||
pattern: 'Cry\s*:[^\r\n]*Engine initialization failed![^\r\n]*CrySystem initialization failed'
|
||||
severity: fatal
|
||||
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.
|
||||
suggest: []
|
||||
- id: swapchain-limit-reached
|
||||
pattern: 'FrameBridge:\s+xrCreateSwapchain [^\r\n]*\bresult=-10\b'
|
||||
severity: error
|
||||
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).
|
||||
suggest: []
|
||||
- id: quest2-headset-identity
|
||||
pattern: 'venderId[^\r\n]*systemName Oculus Quest2'
|
||||
severity: info
|
||||
@@ -75,7 +133,7 @@ signatures:
|
||||
- id: no-launcher
|
||||
pattern: 'APP_ACTIVITY is empty'
|
||||
severity: fatal
|
||||
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,6 +159,14 @@ 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
|
||||
@@ -202,6 +275,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
|
||||
pattern: '(?:vk shim|shader fix layer): \d+ shader fix\(es\) configured, none matched'
|
||||
severity: info
|
||||
diagnosis: This game's recipe has a shader fix (for a GPU hang at one effect or scene), but none of the shaders it
|
||||
fixes showed up in the first minutes of play. If you haven't reached that part of the game yet, that's expected.
|
||||
Otherwise a game update may have changed the shader, so the fix no longer applies and the old hang can come back.
|
||||
If the game freezes or closes there, please send a problem report with diagnostics so the fix can be updated.
|
||||
suggest: []
|
||||
report: true
|
||||
- id: zink-device-lost
|
||||
pattern: 'zink.*DEVICE[_ ]LOST|VK_ERROR_DEVICE_LOST'
|
||||
severity: fatal
|
||||
@@ -313,6 +396,16 @@ signatures:
|
||||
diagnosis: The game needs Meta's colour-space extension, which the Frame's runtime doesn't offer, and quits before
|
||||
it starts VR (for example OutRun Quest VR). FrameBridge can offer it.
|
||||
suggest: [adapter.fb_color_space]
|
||||
- id: appsw-required
|
||||
# GitHub #336: OutRun Quest VR requires Application SpaceWarp: without a motion-vector size (the Frame's runtime
|
||||
# reports none) or without XR_FB_space_warp it logs this line and quits before its first frame
|
||||
pattern: 'AppSW recommended buffers unavailable'
|
||||
unless: 'per-game: space_warp_emul=1'
|
||||
severity: fatal
|
||||
diagnosis: The game requires Meta's space warp (Application SpaceWarp) and quits because the Frame reports no
|
||||
buffer size for it (for example OutRun Quest VR). Turning space warp off can't help; FrameBridge can provide
|
||||
space warp itself (the game renders every frame normally, its motion vectors are not used).
|
||||
suggest: [adapter.space_warp_emul]
|
||||
- id: vulkan-pipeline-refused
|
||||
# GitHub #349: with Lepton's Fossilize layer loaded every Transformers pipeline failed with -1; without it a few
|
||||
# multiview vertex shaders still fail in Turnip. Unreal ends at the first one.
|
||||
@@ -387,6 +480,8 @@ signatures:
|
||||
# "Name=XR_FB_scene SpecVersion=1", OVRPort's "overportOXR: - XR_FB_scene 1", Unity's 'Meta XR Feature: ...
|
||||
# Extensions="..."' (GitHub #404); XR_FB_scene_capture alone doesn't count either
|
||||
pattern: '(?m)^(?!.*(?:Unavailable OpenXR extension: |Name=|overportOXR: - |Extensions=")).*XR_FB_scene\b|XR_FB_spatial_entity.*(not supported|UNSUPPORTED)|xrQuerySpacesFB'
|
||||
# FrameBridge serves the room (OVRPort still lists XR_FB_scene and the shim names xrQuerySpacesFB): no finding
|
||||
unless: 'FrameBridge[^\r\n]*scene: emulation active'
|
||||
severity: warning
|
||||
diagnosis: The game wants Meta's room data (scene API), which the Frame lacks. The adapter can emulate a room from the guardian bounds.
|
||||
suggest: [adapter.scene_emul]
|
||||
@@ -438,11 +533,27 @@ signatures:
|
||||
supersedes: [native-crash]
|
||||
- id: cortopia-org-id-wait
|
||||
# Meta's C# wrapper logs this when the native call returned request id 0 (no callback ever follows); Cortopia's
|
||||
# start-up awaits it forever (GitHub #360)
|
||||
pattern: 'An unknown error occurred\. Request failed\.[\s\S]{0,600}?Cortopia\.Scripts\.Platform\.Meta'
|
||||
# start-up awaits it forever (GitHub #360: Meta.<Initialize>d__13; #422 TMNT: Empire City: <Login>d__17)
|
||||
pattern: 'An unknown error occurred\. Request failed\.[\s\S]{0,600}?Cortopia\.Scripts\.Platform\.(?:Meta|<Login>d__)'
|
||||
severity: error
|
||||
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"
|
||||
pattern: 'meta platform HandlePlatformInitialization[\s\S]{0,3000}?An unknown error occurred\. Request failed\.'
|
||||
severity: error
|
||||
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 +618,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
|
||||
pattern: '\|LocalSave +\|OfflineSave_Init succeeded'
|
||||
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 +653,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)'
|
||||
severity: fatal
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||

|
||||
|
||||
## 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
|
||||
into one APK first (`sources/bundles.py`).
|
||||
2. **Analysis** (`analysis/detect.py`): package/label/version (pyaxmlparser), ABIs, engine, XR API, graphics API,
|
||||
direct-VrApi, GLAD/eglGetProcAddress, Unity MSAA levels, Meta permissions, telemetry references.
|
||||
3. **Recipe** (`recommend/engine.py`): every patch's `detect()` suggests itself with a reason; a catalog entry (exact
|
||||
@@ -16,37 +18,50 @@
|
||||
deployment.json, artwork) → `shortcuts` (detached systemd unit stops Steam, writes shortcuts.vdf + grid art,
|
||||
restarts Steam).
|
||||
6. **Test** (`validate/device.py`): agent `launch_test` (systemd-run launch.sh, wait, stop) → fetch launch.log →
|
||||
`triage.py` (milestones + signatures → suggested patches) → UI offers "apply suggestions and rebuild".
|
||||
`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
|
||||
(`patches/pcvr.py`: Revive, OpenVR backend, crash reporter, Oculus detection, OpenXR layer, Proton
|
||||
log/version/env); `base.for_game` keeps Quest and PC VR patches
|
||||
apart. "Build" = `pipeline.prepare_rift` (checks + Revive). Revive is a portable tool (`tools/revive.py` unpacks
|
||||
`ReviveInstaller.exe` in pure Python: NSIS header + deflate blocks). Targets:
|
||||
- `targets/pc_revive.PcReviveTarget`: Windows/WSL (`core/winhost.py`), non-Steam shortcut running
|
||||
`ReviveInjector.exe /openxr <exe>` via the agent's VDF code, grid art, local records in `<user data>/pc/`.
|
||||
## PC VR (Oculus Rift and SteamVR) games
|
||||
`sources/rift_dump` finds Windows game folders (one game per folder); `analysis/rift` (own PE reader) ranks the
|
||||
programs, detects bitness, engine, the VR APIs (LibOVR, OpenVR, OpenXR), D3D version and Oculus Platform SDK use; ids
|
||||
are `rift.<slug>`. Their only patches are the `pcvr` category (`patches/pcvr.py`: Revive, OpenVR backend, crash
|
||||
reporter, Oculus detection, OpenXR layer, launch arguments, Proton version/env); `base.for_game` keeps Quest and PC
|
||||
VR patches apart. Repacks are usually pre-patched, so a game is installed unchanged and its program started
|
||||
directly; Revive is only suggested for games with LibOVR code, and those run only on the PC (Revive's hooks don't
|
||||
work under Proton on ARM64). "Build" = `pipeline.prepare_rift` (checks, plus Revive when the recipe has it). Revive
|
||||
is a portable tool (`tools/revive.py` unpacks `ReviveInstaller.exe` in pure Python: NSIS header + deflate blocks).
|
||||
Targets:
|
||||
- `targets/pc_revive.PcReviveTarget`: Windows/WSL (`core/winhost.py`), non-Steam shortcut (the game's program, or
|
||||
`ReviveInjector.exe [/openxr] <exe>`; Revive's OpenVR backend by default) via the agent's VDF code, grid art, local
|
||||
records in `<user data>/pc/`.
|
||||
- `FrameLeptonTarget.install_pcvr` → `installer.install_pcvr` → agent `prepare_pcvr`/`finalize_pcvr`: upload
|
||||
`game/` + `revive/` (+ `xrlayer/` for `pcvr.xr_timefix`, `helpers/` for `pcvr.oculus_unreal`; size-manifest dedupe,
|
||||
`game/` (+ `revive/`, `xrlayer/` for `pcvr.xr_timefix`, `helpers/` for `pcvr.oculus_unreal`; size-manifest dedupe,
|
||||
stale files removed), `launch.sh` running the ARM64 Proton chain (built from toolmanifest.vdf; with
|
||||
`pcvr.oculus_unreal` the injector runs through `helpers/fp_oculushmd.exe`, which provides Unreal's
|
||||
`pcvr.oculus_unreal` the program runs through `helpers/fp_oculushmd.exe`, which provides Unreal's
|
||||
`OculusHMDConnected` event); `proton_status`/`install_proton` manage Proton from Valve's ARM64 compat list.
|
||||
|
||||
## Linux apps and Windows programs
|
||||
`pipeline.add_linux_app` (AppImage, archive or folder; library kind `linux`) and `pipeline.add_windows_exe` add
|
||||
programs that are installed unchanged. The agent's `finalize_linux` unpacks them, writes the launcher (arm64 builds
|
||||
directly, x86_64 builds through Valve's FEX), a Desktop Mode menu entry and, for SteamVR overlay apps, their
|
||||
`.vrmanifest` registration; Windows programs run through Proton like PC VR games.
|
||||
- **Native binaries** (`artifacts/`): edit `native/…`, run `python native/build.py`, commit the new artifacts + SHA256SUMS,
|
||||
run `frameport parity` to see which games change.
|
||||
|
||||
@@ -60,19 +75,20 @@ apart. "Build" = `pipeline.prepare_rift` (checks + Revive). Revive is a portable
|
||||
release's wheel).
|
||||
- `prepare()` (bundle): `<data>/updates/<ver>/`: download → SHA256SUMS.txt check → extract to `staged/` (ditto on
|
||||
macOS) → layout check → on Windows the same Authenticode signer as the running exe → `ready.json`.
|
||||
- `apply()`: writes `<data>/updates/apply.{ps1,sh}` and starts it detached; FramePort quits. The script waits for the
|
||||
- `apply()`: writes `<data>/updates/apply.{ps1,sh}` and starts the script detached; FramePort quits. The script waits for the
|
||||
pid. Windows: backs up the files it replaces (`updates/<ver>/previous`) and copies over the folder (the zip has no
|
||||
folder of its own). macOS/Linux: `mv` target → `.old`, staged → target, rollback on failure, clears the quarantine.
|
||||
Then it relaunches FramePort. Log: `<data>/logs/update.log`.
|
||||
Then the script relaunches FramePort. Log: `<data>/logs/update.log`.
|
||||
GUI: background check 10 s after start + every 6 h → sidebar card + Library bar → dialog with notes → job
|
||||
`app-update` (waits for other jobs) → restart. "Install updates automatically": prepare in the background, apply in
|
||||
`main()` before the window opens. CLI: once-a-day hint from the cached result (a daemon thread refreshes it), `frameport
|
||||
update [--check] [--yes]`. CI runs `scripts/update_smoke.py` on every OS with the archive it just built.
|
||||
|
||||
## Dynamic data (fetched live, cached, bundled fallback)
|
||||
overport CLI release (Android-XR-Bridge/OVRPort, fallback ovrport/app) + patch list + titles, Temurin JRE (Adoptium API), apksigner (Google repository index),
|
||||
store artwork/titles (overport image API), catalog (optional remote), Lepton location/appid (Frame appmanifests),
|
||||
Steam user (Frame userdata).
|
||||
overport CLI release (Android-XR-Bridge/OVRPort, fallback ovrport/app) + patch list + titles, Temurin JRE (Adoptium
|
||||
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
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Compatibility
|
||||
|
||||
FramePort's catalog has a tested recipe (the patches and settings that work) for many games, each marked as working,
|
||||
working with issues or not running: see the [list of tested games](GAMES.md). Other games get suggested patches,
|
||||
each with its reason. Every patch can be switched on or off under **Customize** on the game's page.
|
||||
FramePort has tested recipes (the patches and settings that work) for hundreds of games, each marked as working,
|
||||
working with issues or not running: see the [tested games](GAMES.md). Other games get suggested patches, each with
|
||||
its reason. **Customize** on a game's page turns any patch on or off.
|
||||
|
||||
Got a game working? [Share its recipe](INSTALL.md#share-a-recipe-or-report-a-problem).
|
||||
|
||||
@@ -10,13 +10,14 @@ Got a game working? [Share its recipe](INSTALL.md#share-a-recipe-or-report-a-pro
|
||||
|
||||
| Kind of game | On the Steam Frame |
|
||||
|---|---|
|
||||
| Quest games | Converted to OpenXR (the VR standard the Frame uses) by OVRPort and patched; they run in Lepton, Valve's Android runtime |
|
||||
| 32-bit Gear VR, Oculus Go and early Quest games | Run through FramePort's 32-bit bridge (experimental): it translates the game's 32-bit code inside the app; the Frame's right controller acts as the Gear VR / Go controller |
|
||||
| Other Android VR apps using OpenXR (for example Pico builds) | Converted the same way; the other headset's own features and store services aren't available |
|
||||
| Android apps without VR | Installed unchanged and shown as a window in the headset |
|
||||
| PC VR games | On the Frame through Proton (experimental), or on your PC and streamed to the Frame: see [PC VR games](INSTALL.md#pc-vr-games) |
|
||||
| Quest games | Converted to OpenXR (the Frame's VR standard) by OVRPort, patched, and run in Lepton, Valve's Android runtime |
|
||||
| 32-bit Gear VR, Oculus Go and early Quest games | Run through FramePort's 32-bit bridge (experimental). The right controller acts as the Gear VR or Go controller |
|
||||
| Other Android VR apps using OpenXR (for example Pico builds) | Converted the same way, without the other headset's own features and store services |
|
||||
| Android apps without VR | Installed unchanged and shown as a window |
|
||||
| PC VR games | On the Frame through Proton (experimental), or on your PC and streamed: see [PC VR games](INSTALL.md#pc-vr-games) |
|
||||
| Linux apps | Installed unchanged. x86_64 builds run through FEX, slower than arm64 builds: see [Linux apps](INSTALL.md#linux-apps) |
|
||||
| Can't run | x86-only Android apps, apps for Pico's or HTC's own VR system, Android XR apps, and games that check their license through the Oculus app |
|
||||
|
||||
Automatic launch tests show that a game starts; only the headset shows whether it looks right.
|
||||
Launch tests show that a game starts. Only the headset shows whether the game looks right.
|
||||
|
||||

|
||||
@@ -1,25 +1,24 @@
|
||||
# Controller buttons
|
||||
|
||||
> **Experimental.** The Frame's runtime accepts these bindings, but the modes haven't been tried in the headset on
|
||||
> many games yet. If buttons stop working in a game, set it back to **SteamVR default** (Game settings →
|
||||
> Controllers) and report it.
|
||||
> **Experimental.** The Frame's runtime accepts these bindings, but few games have been tried in the headset. If
|
||||
> buttons stop working in a game, set the game back to **SteamVR default** (Game settings → Controllers) and report the problem.
|
||||
|
||||
Quest games usually bind Quest Touch controllers. Steam Frame has a d-pad and View button on the left controller,
|
||||
and an A/B/X/Y diamond and Menu button on the right. Without a game's own Frame bindings, SteamVR maps Touch X/A
|
||||
to the bottom button and Touch Y/B to all three top buttons. View sends Touch Menu. The right X and Y therefore
|
||||
send Touch B, rather than the left-hand X and Y of a Quest controller.
|
||||
Quest games expect Quest Touch controllers. The Frame's left controller has a d-pad and a View button; the right one
|
||||
has an A/B/X/Y diamond and a Menu button. Without a game's own Frame bindings, SteamVR sends Touch X/A from the bottom
|
||||
button and Touch Y/B from all three top buttons, so the right X and Y both send Touch B. View sends Touch Menu.
|
||||
|
||||
**Controller buttons** chooses an alternative for Quest games running through FrameBridge:
|
||||
**Controller buttons** offers other mappings for Quest games:
|
||||
|
||||
- **SteamVR default (0)** leaves SteamVR's Touch remapping in place. This is the initial default.
|
||||
- **Mirror (1)** uses every face button. On each controller, bottom and outer send X/A; top and inner send Y/B, as on Quest controllers, whose second button sits up and toward the other hand.
|
||||
- **Quest pair (2)** uses two face buttons per controller, placed like Touch's: left X = d-pad down, Y = d-pad
|
||||
right; right A = A, B = X. The other face buttons send no Touch input.
|
||||
- **SteamVR default (0):** SteamVR's mapping above. The initial default.
|
||||
- **Mirror (1):** uses every face button. Bottom and outer send X/A; top and inner send Y/B, as on a Quest
|
||||
controller, whose second button sits up and toward the other hand.
|
||||
- **Quest pair (2):** two buttons per controller, placed like Touch's: left X = d-pad down, Y = d-pad right; right
|
||||
A = A, B = X. The other face buttons send nothing.
|
||||
|
||||

|
||||
|
||||
The small label identifies the physical button; the larger label is the Touch input the game receives. Blue is
|
||||
X/A, orange is Y/B or Menu, and a dash means no Touch input. Left and right refer to the hand holding the controller.
|
||||
The small label names the physical button, the large one the Touch input the game gets. Blue is X/A, orange is Y/B
|
||||
or Menu, a dash is no input.
|
||||
|
||||
<!-- quest-buttons-table:start -->
|
||||
| Frame input | SteamVR default (0) | Mirror (1) | Quest pair (2) |
|
||||
@@ -38,32 +37,25 @@ X/A, orange is Y/B or Menu, and a dash means no Touch input. Left and right refe
|
||||
| Right bumper | — | — | — |
|
||||
<!-- quest-buttons-table:end -->
|
||||
|
||||
The table describes button clicks and face-button touches. View sends Menu click only. Triggers, grip pressure,
|
||||
thumbsticks, tracked poses and vibration keep their corresponding Touch bindings.
|
||||
Triggers, grips, thumbsticks, tracking and vibration keep their Touch bindings. View sends Menu click only.
|
||||
|
||||
## Choose a mode
|
||||
|
||||
For one game, open its **Game settings → Controllers → Controller buttons**. Choose **Use global default** to
|
||||
follow Settings, or select a specific mode to keep an override for that game. An explicit **SteamVR default**
|
||||
keeps mode 0 even when the global choice is Mirror or Quest pair.
|
||||
- **One game:** **Game settings → Controllers → Controller buttons**. **Use global default** follows Settings; any
|
||||
other choice stays for that game, including an explicit **SteamVR default**.
|
||||
- **All games:** **Settings → Controllers**. New installs and updates use the default for games without their own
|
||||
choice.
|
||||
**Apply to installed games now** sends the default to installed Quest games that follow it.
|
||||
|
||||
For the library default, open **Settings → Controllers**. The diagram follows the selected mode. New installs
|
||||
and configuration updates use this default for games without an override. With a Frame connected, choose
|
||||
**Apply to installed games now** to send it to installed Quest games that follow the global default. Overrides
|
||||
are kept. Changing the default does not mark every game as needing an update.
|
||||
Changes apply at the **next launch**. Games installed before this feature need **Update on Frame** once.
|
||||
|
||||
Changes take effect at the **next launch**. Close and relaunch a running game.
|
||||
Games installed before this feature need **Update on Frame** once for the mode to work.
|
||||
|
||||
The CLI can change an installed game's runtime setting:
|
||||
Command line, for an installed game (writes the Frame's settings only):
|
||||
|
||||
```sh
|
||||
frameport settings <pkg> quest_buttons=2
|
||||
```
|
||||
|
||||
Use `0` for SteamVR default or `1` for Mirror. This command writes the installed settings directly; it does not
|
||||
save a per-game recipe override. To keep the value through later installs and global applies, also save it in
|
||||
the recipe, or use Game settings:
|
||||
To keep the value through later installs, also save the value in the recipe (or use Game settings):
|
||||
|
||||
```sh
|
||||
frameport recipe <pkg> --set quest_buttons=2
|
||||
@@ -71,23 +63,18 @@ frameport recipe <pkg> --set quest_buttons=2
|
||||
|
||||
## Limits
|
||||
|
||||
- SteamVR's binding UI can still override these suggested bindings. Check custom bindings if the result differs
|
||||
from the diagram.
|
||||
- The d-pad has a single touch sensor: a thumb resting on it touches X and Y together. The right controller's
|
||||
face buttons sense touch one by one.
|
||||
- Bumpers have no Touch counterpart and are unbound. Thumbrest touch is dropped; the Frame profile has no
|
||||
corresponding input. System buttons keep their runtime role.
|
||||
- Mirror and Quest pair require the Frame runtime's `XR_VALVE_frame_controller_interaction` extension. If it is
|
||||
absent, FrameBridge logs that fact and leaves SteamVR's remap in place.
|
||||
- Games suggesting their own Frame Controller bindings keep those bindings and continue seeing the Frame
|
||||
Controller profile, even if they also suggest Touch bindings. These modes do not replace the game's own mapping.
|
||||
- Touch suggestions using bare pose, trigger, squeeze or button paths are translated too; explicit components
|
||||
such as `/pose`, `/value`, `/click` and `/touch` are also supported where the mapping provides them.
|
||||
- Finger proximity emulation (`proximity_emul`) continues to work with Mirror and Quest pair. Dropping the
|
||||
unsupported thumbrest path does not disable proximity emulation from the other touch inputs.
|
||||
- The setting applies to Quest VR games, including 32-bit games through the bridge. It does not change PC VR or
|
||||
flat Android Steam Input layouts.
|
||||
- Custom bindings in SteamVR's binding UI override these. Check them if the result differs from the diagram.
|
||||
- The d-pad has one touch sensor: a thumb resting on the d-pad touches X and Y together. The right face buttons sense touch
|
||||
one by one.
|
||||
- Bumpers and thumbrest touch have no Touch counterpart and do nothing. System buttons keep their role.
|
||||
- Mirror and Quest pair need the runtime's `XR_VALVE_frame_controller_interaction` extension. Without the extension,
|
||||
FrameBridge logs the fact and keeps SteamVR's mapping.
|
||||
- Games with their own Frame Controller bindings keep them; these modes don't replace a game's own mapping.
|
||||
- Touch bindings on bare pose, trigger, squeeze or button paths are translated too, as are `/pose`, `/value`,
|
||||
`/click` and `/touch` where the mapping has them.
|
||||
- Finger proximity emulation (`proximity_emul`) keeps working with Mirror and Quest pair.
|
||||
- Applies to Quest VR games, including 32-bit games on the bridge. PC VR games and flat Android apps aren't changed.
|
||||
|
||||
The mapping follows [Valve's Frame Controller input reference](https://partner.steamgames.com/doc/steamhardware/steamframe/input)
|
||||
and the [shared mapping data](input/quest_buttons.json). To regenerate the diagram, app illustrations and table,
|
||||
run `uv run python scripts/controller_diagram.py`; add `--check` to verify them without writing files.
|
||||
and the [shared mapping data](input/quest_buttons.json). `uv run python scripts/controller_diagram.py` regenerates
|
||||
the diagram, app illustrations and table (`--check` verifies them).
|
||||
@@ -59,6 +59,25 @@ tested), translator speed. Profiled on the Frame
|
||||
main thread). Now lock-free lookups, ltab refill, per-thread flags, per-page block lists: Gunjack's dispatcher
|
||||
23.6 % -> 1.6 %, CPU in the same phase about -55 %, still 72 fps.
|
||||
|
||||
**Ghost Giant (GitHub #439, Unity 2017.4 il2cpp + OVRPlugin 1.29 on VrApi 1.16, headless 2026-10-24):** the logo,
|
||||
then black forever was its intro videos (Thunderful/Zoink logos, Unity VideoPlayer): without libmediandk in the
|
||||
bridge Unity took its JNI path, `GetDirectBufferAddress` of the codec's ByteBuffers (host heap, above 4 GiB) gave 0
|
||||
→ `AndroidVideoMedia: Error while getting codec input buffers`, and the Splash scene waits for the video for ever
|
||||
(never reaches `Scene SplashApp`). With the NDK media functions + `AMEDIAFORMAT_KEY_*` symbols (dcbcce0f, bccd916f)
|
||||
both videos play (decoded by `OMX.google.h264.decoder` into a SurfaceTexture), then SplashApp → PersistentObjects →
|
||||
Loading → MainMenu, which renders (left-eye snapshots every 3 s: before = pure black from +9 s, mean 0.0; after = the
|
||||
two videos, then the island menu from +46 s). Still seen: `VideoPlayback audio buffer overflow. N sample frames
|
||||
discarded` during the intros (the videos' sound may crackle; unchecked in the headset). The game hides everything
|
||||
behind "Return to play area" when the head is outside `PlayAreaManager`'s height limits (`PlayAreaSettings.heightData`
|
||||
min/max above the floor from GetTrackingTransform(3)).
|
||||
**Headless snapshots of VrApi games** (`device.lepton_env` / launch.sh `LEPTON_ENV_OVP_HEADLESS_TEST=1` + adapter
|
||||
`snapshot=N`): without a worn headset the session stays SYNCHRONIZED (`shouldRender` false), the VrApi bridge ended
|
||||
every frame empty (`pacing 2s: … ends=145 (empty 145 …)`) and the a32 bridge dropped every projection with an
|
||||
untracked default pose (`a32 submit anomaly … dropped=1`), so there was nothing to snapshot. Under OVP_HEADLESS_TEST
|
||||
the VrApi bridge now submits the images anyway, puts the floor (GetTrackingTransform(3)) 1.6 m below the eye origin
|
||||
instead of the runtime's headless STAGE (0.865 m: Ghost Giant then showed only "Return to play area"), and the a32
|
||||
bridge keeps the game's pose. Never set outside tests.
|
||||
|
||||
## Upload speed
|
||||
|
||||
**Upload speed (measured 2026-09-29):** via the home router (PC Wi-Fi → router → Frame wlan0) only 15-18 MB/s, the
|
||||
@@ -146,11 +165,34 @@ pose (images drawn only after head turns, or ahead from xrWaitFrame and sometime
|
||||
lists its own `/sdcard/4XPlayer`, not Movies: agent v25 `link_media` hard-links sent files into an app's own top-level
|
||||
folder (`app_media_dirs`), `frameport frame send --app <pkg>`, GUI game page "Add videos" (players) / "Add videos &
|
||||
files…". A 4XVR webm stereo swapchain was refused with XR_ERROR_LIMIT_REACHED (-10) → right eye grey; our projection
|
||||
swapchain was halved (1536/eye) in case memory is the limit (unverified). Frame data snapshot
|
||||
swapchain was originally halved (1536/eye) while investigating memory. Issue #387 below confirms a swapchain
|
||||
count cap; reducing image sizes does not avoid it. Frame data snapshot
|
||||
(SteamVR runtimes, Lepton scripts, logs; never commit Valve binaries): `~/frameport-research/frame-data-2026-09-30/`.
|
||||
Not yet verified in the headset.
|
||||
|
||||
|
||||
**Issue #387 (2026-10-18, host-tested only):** 4XVR's extra sub-panels hit the session cap at roughly 15–16 live
|
||||
swapchains: the same 256×128 create succeeds then fails in the same millisecond, so shrinking images is not a fix.
|
||||
Default-off GLES `swapchain_pool` (catalog enabled for 4XVR) starts at eight live app runtime swapchains, reserving runtime
|
||||
headroom for up to two 4096² SRGB atlases. Single-face/layer/sample/mip colour images use private app-context textures;
|
||||
submitted panels are blitted into packed atlas rectangles after cylinder strips and rect_clamp. Destroy frees rectangles.
|
||||
Vulkan is untouched. No adapter revision bump: this opt-in fix should not mark every installed game for update.
|
||||
The new catalog key already makes older apps reject the recipe via `unusable_reason`; triage suggests no unknown setting.
|
||||
Headset check: repeatedly open/close all 4XVR submenus while playing stereo and 360 video, verify both eyes, panel crops,
|
||||
curved strips, alpha and frame pacing; confirm no new -10 errors or menu closures and reuse after closing panels.
|
||||
|
||||
**Issue #424 (2026-10-23, host-tested only): Vulkan panel pool.** AC Nexus (Unity, Vulkan) creates ~12 single-layer
|
||||
format-43 panels (1200x1020, 768x153, 1459x1228, 855x1500, 1875x1500, 2457x1152, 512x512, 460x102, 691x153, 360x360,
|
||||
…) plus a 3-swapchain eye set (arraySize 2: colour 43, 384x384 formats 97/129). With the Quest 3 identity it creates a
|
||||
2592x2256 eye set while the old one lives: the runtime refuses it (-10) and the game stops drawing (the catalog kept
|
||||
the Quest 2 identity until now). `swapchain_pool` now also covers Vulkan sessions: from eight live app swapchains,
|
||||
format-43 panels get private VkImages copied into one or two 4096² atlases at xrEndFrame (no CPU wait on this frame's
|
||||
GPU work; a 3-deep fence ring). Host harness: 8 app runtime swapchains + 1 atlas + the new eye set = 12 live, 7 panels
|
||||
pooled. Catalog: AC Nexus back to Quest 3 with `swapchain_pool: 1` (`min_app` 1.3.1). Device check (headless first):
|
||||
the 2592x2256 create succeeds, `pacing` continues, no `atlas update failed`; then the headset: panels upright, crisp and
|
||||
in place, menus, frame time.
|
||||
|
||||
|
||||
## Companion apps / intents (Stremio + 4XVR)
|
||||
|
||||
**Companion apps / intents (2026-10-03, tested with Stremio + 4XVR):** a second APK can be installed into a running
|
||||
@@ -162,6 +204,36 @@ scheme, explicit component or not, no property to disable): it writes `steam://o
|
||||
→ 4XVR) can't work inside Lepton; don't retry without Valve changing it. Lepton installs exactly one `*.apk` per app
|
||||
folder (two break `get_apk_path`).
|
||||
|
||||
## Browser stand-in for sign-ins (Plex, GitHub #466)
|
||||
|
||||
**Browser hand-off (2026-10-24, GitHub #466 Plex):** Lepton's hand-off is in services.jar
|
||||
`com.android.server.wm.ActivityStarter.execute()`, before anything is resolved: when the request's intent action is
|
||||
`android.intent.action.VIEW` it logs `ActivityTaskManager: Intercepted VIEW: <data>`, appends
|
||||
`steam://openurl/<data>\n` to `/lepton/steam.pipe` (mounted rw from the host's `~/.steam/steam.pipe`, a 0600 FIFO;
|
||||
Steam's console_log.txt then shows `ExecCommandLine: "steam://openurl/…"`) and returns START_SUCCESS (0) — also for a
|
||||
VIEW without data (nothing written) and for an explicit component. Other actions start normally. Apps that check for a
|
||||
browser first (Plex: `<queries>` for VIEW + BROWSABLE http/https, then "no browser installed") never get there: the
|
||||
container has no browser package. Patch `frame.browser_handoff` (opt-in; offered for 2D apps and suggested-off when
|
||||
the manifest's `<queries>` ask for a browser or a Custom Tabs service, analysis `browser_query`) adds
|
||||
`org.frameport.handoff.BrowserHandoff` to the app's own manifest (exported, Theme.NoDisplay, excludeFromRecents,
|
||||
VIEW + DEFAULT + BROWSABLE, schemes http/https without a host, so the app's own host-specific deep links are
|
||||
unchanged; missing android: attribute names are inserted into the resource-mapped part of the string pool,
|
||||
`axml._attr_name`) and its class as the next `classesN.dex` (`native/java-handoff`, `dex/browser-handoff.dex`). The
|
||||
app's query now finds its own stand-in; its startActivity(VIEW) is still taken by Lepton before resolution, so the stand-in
|
||||
normally never runs. If it is started directly (another action, explicit component), it writes the same pipe line on a
|
||||
background thread and finishes. No second package (see Companion apps above). A 2D app turns from "installed
|
||||
unchanged" into a FramePort build signed with FramePort's key once an APK patch is on (`engine.flat_as_is` in
|
||||
`pipeline.set_recipe`; before, a hand-enabled APK patch on a 2D app stayed `as_is` and was never applied, e.g.
|
||||
Plex's `frame.sdl_clipboard`). Whether the app then notices the finished sign-in depends on its flow (PIN/code polling
|
||||
works from any browser; a redirect back into the app through a link can't, since Lepton sends every VIEW to Steam).
|
||||
Verified headless on the dev Frame (2026-10-24) with a scratch 2D test app (targetSdk 33, Plex's `<queries>`,
|
||||
`queryIntentActivities`/`resolveActivity` with MATCH_DEFAULT_ONLY, then startActivity): unpatched `query: 0`,
|
||||
`resolve: null`; with the patch `query: 1 … org.frameport.handoff.BrowserHandoff`, the VIEW start returned normally
|
||||
and Steam's console_log had `ExecCommandLine: "steam://openurl/https://example.com/?…=view"`; an explicit start of the
|
||||
stand-in wrote `…=explicit` itself (logcat `FramePort: browser hand-off: page sent to Steam`): the app uid can open
|
||||
`/lepton/steam.pipe` for writing. aapt2 reads the edited manifest of 70 real APKs with the original tree unchanged.
|
||||
Plex's own sign-in flow is unverified.
|
||||
|
||||
## Text input (Stremio VR)
|
||||
|
||||
**Text input (2026-10-03, verified in the headset with Stremio VR):** Lepton's Android has no IME (`ime list` empty)
|
||||
@@ -376,7 +448,11 @@ bar) that never advances; the async loader thread sleeps and OBB reads stop (~16
|
||||
|
||||
## Vader's leak solved / AC Nexus flips
|
||||
|
||||
**Vader's leak solved / AC Nexus flips (2026-10-14):** Vader Immortal leaves GL errors pending on its context; SteamVR's GL interop (`openxr_graphics_gl_openvr.cpp`, `xrclient_<pkg>.txt` in Steam's logs: `SXR_GL_CHECK: error:0x502 ... glGenTextures`) takes the stale error as its own failure once, and from then on `CVRCompositorSharedTextures::BeginCommandBuffer` allocates a command buffer every frame (perf page-fault stacks; ~430 renderD128 mappings + 22 MB/s). Adapter `gl_error_clear` (GLES, catalog on for Vader ep1-3) clears them before xrCreateSwapchain/xrBeginFrame/xrEndFrame: flat memory, no SXR_GL_CHECK (headless). The per-game `xrclient_<pkg>.txt` logs are worth reading for runtime errors. The Frame's runtime caps swapchains per session (XR_ERROR_LIMIT_REACHED from ~15; AC Nexus asks for more): flip emulation now turns quads upright in place (scratch VkImage, once per acquire; transfer usage added to single-layer colour swapchains) instead of a shadow swapchain per quad, which ran into the cap (#138). Headless sessions don't draw AC Nexus's panels (adapter `flip_dump` saves before/after images of flipped quads, for headset runs).
|
||||
**Vader's leak solved / AC Nexus flips (2026-10-14):** Vader Immortal leaves GL errors pending on its context; SteamVR's GL interop (`openxr_graphics_gl_openvr.cpp`, `xrclient_<pkg>.txt` in Steam's logs: `SXR_GL_CHECK: error:0x502 ... glGenTextures`) takes the stale error as its own failure once, and from then on `CVRCompositorSharedTextures::BeginCommandBuffer` allocates a command buffer every frame (perf page-fault stacks; ~430 renderD128 mappings + 22 MB/s). Adapter `gl_error_clear` (GLES, catalog on for Vader ep1-3) clears them before xrCreateSwapchain/xrBeginFrame/xrEndFrame: flat memory, no SXR_GL_CHECK (headless). The per-game `xrclient_<pkg>.txt` logs are worth reading for runtime errors. The Frame's runtime caps swapchains per session (XR_ERROR_LIMIT_REACHED from ~15; AC Nexus asks for more): flip emulation now turns quads upright in place (scratch VkImage, once per acquire; transfer usage added to single-layer colour swapchains) instead of a shadow swapchain per quad, which ran into the cap (#138). Headless sessions don't draw AC Nexus's panels (adapter `flip_dump` saves before/after images of flipped quads, for headset runs). 2026-10-23: panels that stayed upside down at random were blits that took longer than the 100 ms wait while the GPU was loading; they were counted as failed and flipped again next frame, so both blits ran. A submitted blit now counts as done (log `flip: blit … still running after 100 ms`), and the shared command buffer waits for it before reuse (`vk_cmd_idle`, also fade_quad/cube_standin). Equirect2 layers (type 1000091000, `flip_tag=1 usable=0`) are dropped, not shown upside down. Same day, headset dumps (`flip_dump`, now also a `shown`/`late` image later): the "calibrate arms" panel was finished by the game after xrEndFrame (raw dump half drawn) and never acquired again, so its drawing landed upside down after the flip. Each flipped image is now sampled (16x16 texels) for ~2 s; a settled change without a new acquire flips it again (log `drawn after its flip`). Flips also use the last released image (release tracking). AC Nexus' recipe lost `device.text_input_window`: its Android window took the controllers' input.
|
||||
|
||||
**Flips as atlas copies (2026-10-24, replaces the in-place flip):** every in-place variant raced the game: Unity draws some panels after xrEndFrame (or on the GPU after FrameBridge's blit) without acquiring them again, so a one-shot flip of the app's own image either ran before the drawing (content upside down for good) or, when "repaired" by fingerprints, was applied again to an animated panel (AC Nexus' pooled "Loading" panel was re-flipped over and over). The blits also waited for the GPU inside xrEndFrame (up to 100 ms per flip, 1 s per dump), so the compositor missed frames: the likely source of the white start flash, which came and went with the diagnostics' extra waits/readbacks (owner: gone with heavy diagnostics off, back with fdaba13f's readbacks). GLES never had a flip path; what made the GLES pool robust is that it never touches the app's texture and copies it into the atlas every frame. Vulkan now does the same: `swapchain_pool.c` owns flips (also when `swapchain_pool` is off: the Vulkan session gets the atlas machinery, nothing is pooled). A flagged pooled panel's per-frame atlas copy becomes a vkCmdBlitImage with reversed rows (same cost); a flagged runtime swapchain's quad gets an atlas area for that frame (re-packed every frame around the pooled rectangles, shared by both eye quads) with an upside-down blit of its last released image. The app's images are only read, so late drawing shows upright one frame later and no flip can be applied twice; one command buffer per frame on the binding's queue after the app's submits, a 3-deep fence ring, no CPU waits (headless: 0.06 ms avg, 0.2 ms max in xrEndFrame). `flip_dump=N` now reads back the atlas region the runtime is given (async, written when the fence of that frame has signalled, a file per content change: `fb_flip_<n>.ppm`); each file is written on the render thread (45-60 ms hitches while panels change), so leave it off for play. `layer_debug` logs copies/frame and every wait (`swapchain_pool: … copies/frame`). Headless (two runs): "WARNING BEFORE PLAYING", "Please carefully read…", the "work of fiction" notice (pooled), "The game saves automatically…" (animated icon) and both "Loading" panels upright as submitted, through their fades; 72.2 fps after the game's own ~14 s loading stall (same as with flips off). Not reachable headless: the calibrate-arms screen; the headset check is that screen, the start (no white flash) and the menus.
|
||||
|
||||
**QuestZDoom slowdown, same leak (GitHub #237, 2026-10-24):** a player's session slowed over ~6 minutes (vrcompositor `WaitForPendingPresent` failures more and more often), then the kernel oopsed in `drm_gpuvm_validate_deferred_collect` (`msm_ioctl_gem_submit` from the game's `AppThreadFunction`, a write past an array at index 0x1600) and the Frame restarted. `xrclient_com.drbeef.questzdoom.txt` had `SXR_GL_CHECK: error:0x500 ... glGenTextures`: the game leaves GL_INVALID_ENUM pending every frame. Headless with freedoom2.wad, `/sys/kernel/debug/dri/0/gem` (readable as steamos; `gemstat`, objects per `vm=`) showed the game's VM gaining ~105 GPU objects a second (8,027 after 75 s), freed when it quit; with `gl_error_clear=1` the total stayed at ~1,915 for two minutes and no SXR_GL_CHECK was logged. Now suggested for every Team Beef port (Lambda1VR's log on the dev Frame has no SXR_GL_CHECK, so not all of them hit it; JKXR #441 has the same compositor stalls, unconfirmed). That debugfs listing is a quick leak check for any game.
|
||||
|
||||
## Unreal's self-restart
|
||||
|
||||
@@ -680,6 +756,51 @@ per game; add_from_link clears the offer and keeps a name the user gave). CLI `f
|
||||
|
||||
OutRun Quest VR (`org.outrun.quest.vr`, its own Khronos loader, no OVRPlugin) logs "Rec.709 color space unavailable" right after xrCreateInstance and quits ~20 s later without a session: the Frame's runtime (and OVRPort's dispatcher) list no XR_FB_color_space. Adapter setting `fb_color_space` (off by default, `native/adapter/color_space.c`) offers the extension when the runtime lacks it, strips it from the runtime's create info, fills XrSystemColorSpacePropertiesFB with Rec.709 and answers xrEnumerateColorSpacesFB / xrSetColorSpaceFB (no conversion: the runtime has no API for it). The functions are reachable through FrameBridge's xrGetInstanceProcAddr and `framebridge_extension_proc` (xrshim); OVRPort's fixed dispatcher table can't forward them, hence off by default. Not yet tried on the device (the build was not downloaded: it ships Sega's game data). Log: `color_space: emulating XR_FB_color_space`.
|
||||
|
||||
## XR_FB_space_warp emulation (#336)
|
||||
|
||||
**OutRun Quest VR (2026-10-24):** with colour space solved, OutRun logs `E OutRunQuest: AppSW recommended buffers
|
||||
unavailable` right after its session, reference spaces and view sizes, then quits (ANR). Same line with
|
||||
XR_FB_space_warp enabled (10-19) and hidden (`hide_space_warp`, 10-24): the game requires Application SpaceWarp, so
|
||||
hiding can't help. OutRun's binary isn't available (Sega content), so the exact check is unknown; the emulation covers
|
||||
the two plausible ones: the motion-vector size and the formats.
|
||||
|
||||
**What the Frame reports (dev Frame, 2026-10-24):** SteamVR's Android runtime lists XR_FB_space_warp (spec 2) and
|
||||
answers XrSystemSpaceWarpPropertiesFB with the **full eye size, 1728x1728** (Meta: about a quarter). It accepts
|
||||
Vulkan motion-vector/depth swapchains (`376x376 format=97` / `format=129`, Pinball FX VR, ITR2); its GLES swapchain
|
||||
formats are the sRGB colour ones. OVRPlugin/Unreal ignore the reported size and make 376x376 themselves.
|
||||
|
||||
**`space_warp_emul` (adapter setting, off by default; `native/adapter/space_warp.c`):** offers XR_FB_space_warp
|
||||
(passed to the runtime when it has it, else emulated; overrides `hide_space_warp`); reports eye/4 (432x432 on the
|
||||
Frame) unless the runtime's answer is already below half the eye; adds RGBA16F + D24S8/D24/D32F/D16 (GLES) or 97 +
|
||||
129/126/125/124 (Vulkan) after the runtime's formats in xrEnumerateSwapchainFormats (exported too: OVRPort's
|
||||
dispatcher can call core functions by symbol); serves motion-vector swapchains (RGBA16F/RG16F at the reported size
|
||||
or at most half the eye) and their depth (depth usage at the reported size, or the size of a served motion-vector
|
||||
swapchain of the session) as FrameBridge-owned GL textures (2D arrays for multiview) / Vulkan images (attachment
|
||||
layout, META extra usage honoured), three images round robin; wait/release succeed at once (a gipa wrapper sits in
|
||||
front of whichever wait/release hook the other settings chose); xrEndFrame drops XrCompositionLayerSpaceWarpInfoFB and
|
||||
depth info showing a served image (`sw_strip_chain` keeps eye depth info: depth infos are copied past a removed node;
|
||||
the old hide_space_warp strip dropped the whole chain when space warp info wasn't first). The game renders every
|
||||
frame: SteamVR doesn't halve the frame rate the way Meta's runtime does. No new runtime functions, so OVRPort's
|
||||
dispatcher table needs nothing (xrshim not involved). Logs: `space warp: runtime motion-vector size`, `space_warp_emul:`.
|
||||
Triage `appsw-required` suggests it; catalog `org.outrun.quest.vr` = fb_color_space + space_warp_emul. The
|
||||
reporter's recipe is a user recipe: the setting has to be switched on by hand (Customize → Game settings → Provide
|
||||
space warp).
|
||||
|
||||
**Headless test (Pinball FX VR, Unreal 5 + OVRPlugin, Vulkan, owner's build re-signed with its own key, only
|
||||
FrameBridge replaced; original APK + settings restored after):** `space_warp_emul=1 force_render=1 layer_debug=1`,
|
||||
`hide_space_warp=0`: `the app enabled XR_FB_space_warp (the runtime has it too)`, `runtime motion-vector size
|
||||
1728x1728` → `432x432 reported`, both 376x376 pairs (format 97 + 129, after each eye swapchain) `served by FrameBridge
|
||||
(Vulkan images, 3 images)`, `space warp images: 600 acquired, 599 waited, 598 released (all by FrameBridge)`, `removed space warp info from the eye images`, `600 frames submitted with their space warp
|
||||
info removed`, 0 xrEndFrame failures, 72.2 fps steady after the loading phase (the same series as the game's own
|
||||
recipe: 62/71/56/46/70 then 72.2), RUNNING after 117 s, the same 12 OVRPlugin errors as the baseline (interaction
|
||||
profiles, perf settings, refresh rate). No GLES space-warp game was available: the GL path is covered by the host
|
||||
harness (`tests/native/space_warp_harness.c`) only. OutRun itself is unverified.
|
||||
|
||||
**Space-warp games that flicker (ITR2, Metro Awakening, Pinball FX, The Boys VR, Lawn Mowing Simulator):** keep
|
||||
`hide_space_warp`. With the emulation the runtime never sees space warp either (same picture as hidden), but the game
|
||||
renders motion vectors into images nobody reads; it doesn't make space warp work (no frame synthesis). It is only for
|
||||
games that refuse to start without the extension.
|
||||
|
||||
## Per-game headset identity (#373, 2026-10-17)
|
||||
|
||||
Owner decision: all Quest / OVRPort conversions default to Quest 3; Customize → Headset model offers Quest 3,
|
||||
@@ -741,6 +862,63 @@ can be enabled and persist after restart. Check actual runtime refresh/pacing se
|
||||
identity alone does not implement refresh-rate support. Rebuild with Quest 2 for the control: original `Oculus Quest2`,
|
||||
original Quest 2 UUID, type 9 if logged, and previous option visibility. Also smoke-test a Unity OVRPlugin game
|
||||
with the default Quest 3 choice for regressions. No device access was used during implementation.
|
||||
|
||||
## Mixed reality: scanned rooms
|
||||
|
||||
Live floor coverage (agent v104) is enabled by default for manual and game-requested scans; the capture
|
||||
payload accepts `preview: false`, and `frameport frame room scan --no-preview` disables it. A world-locked,
|
||||
upward-facing 6 m square at floor height follows the current raw-to-STAGE calibration and stays centred on the
|
||||
scan's first recorded camera position. Every 16th depth vertex contributes to raw-space 10 cm voxels; at most once
|
||||
per second, newly occupied cells redraw a 256×256 RGBA map in one colour (FramePort's secondary blue), each cell a
|
||||
filled 10 cm square, only the opacity telling floor (<0.3 m, faint), raised surfaces (0.3–1.5 m) and tall surfaces
|
||||
(>1.5 m, strongest, the room's outline) apart; unseen transparent. Overlays have no depth (they draw over hands and
|
||||
passthrough), so the map stays faint; three colours in 3x3 blotches clashed in the owner's first run. It shows
|
||||
coverage, not a validated room boundary. IVROverlay_028 slots 1/3/20/22/33/43/62 (OpenVR 2.15.6 header)
|
||||
create/destroy/sort order (1, below the scan prompts' 10)/set width/set absolute STAGE transform/show/upload raw
|
||||
pixels. Each upload is 256 KiB (at most 256 KiB/s); timing for the first
|
||||
20 uploads and every tenth thereafter, plus failures, goes to `rooms/scans/scan-*.fprs.log` and the built room’s `scan.log`. Overlay errors or an
|
||||
upload taking over 50 ms disable the preview while capture continues; headset appearance and upload cost still
|
||||
need device validation.
|
||||
|
||||
When an MR game asks for a room scan, FramePort scans in the headset and FrameBridge loads the new room before
|
||||
answering the game. If no scanner answers within five seconds, the game gets the stored room. Set
|
||||
`scene_scan_request=0` to always use the stored room. A scan that starts but fails keeps the old room and reports
|
||||
a capture failure to the game.
|
||||
|
||||
**Mixed reality: scanned rooms (agent v92, 2026-10-16, branch mr-scene; docs/mr/):** Quest MR games read the room
|
||||
through Meta's scene APIs; the Frame offers none, and Valve's chaperone is no room (2D outline raised to 2.43 m, often
|
||||
the default 1 m circle -> the Android runtime's STAGE rect falls back to 1x1 m; XRService's plane detection is
|
||||
horizontal-only, its persistent map sparse ORB, its XRIPC single-client; cameras are XRService's alone,
|
||||
IVRTrackedCamera gives no frames). **The passthrough's RoomView 3D depth mesh is readable** (docs/mr/PHASE0.md:
|
||||
IVRBlockQueue_005 `/xrservice/roomview3d/meshData|meshLayout`, metadata through the C++ `IVRPaths_002` on the block
|
||||
handle, `meshIsUpToDate` on the queue handle; 132x128 fan, ~30/s; raw->standing moves during a session -> convert
|
||||
with the final one), so FramePort scans rooms itself: agent `room_capture` (Background/Overlay OpenVR app, prompts =
|
||||
artifacts/roomscan/*.png via IVROverlay_028 slots 1/3/20/22/35/43/44/63, `scripts/mr/render_prompts.py`) -> `.fprs`
|
||||
dump -> `fp_roomscan` (native/roomscan, freestanding, offline) -> `room.txt` + `room-mesh.bin`
|
||||
(docs/mr/ROOM_FORMAT.md; outline = positive shoelace in x-z, wall inward normal (-dz, dx)) in
|
||||
`~/.local/share/frameport/rooms/<universe>/`; launch.sh copies it next to settings.conf (`_room_sync`, games with
|
||||
`scene_emul`); FrameBridge (`scene_room.c/scene_meta.c/scene_anchors.c`) serves N walls, volumes (BOUNDED_3D),
|
||||
planes, GLOBAL_MESH (XR_META_spatial_entity_mesh), XR_META discovery/persistence (newer Meta SDKs and MRUK), anchors
|
||||
saved in `files/framebridge-anchors.txt`; all through xrshim (OVRPort's dispatcher drops unknown META calls).
|
||||
`scene_emul` is now suggested for every USE_SCENE/USE_ANCHOR_API game (migration `scene_emul_all`; NOPE and Pinball
|
||||
FX had none). PC: Frame page "Room" card, `frameport frame room scan|status|from-boundary|import|export|build|show`.
|
||||
Real data (owner's room, 2026-10-16): floor at 0, table surfaces, orthogonal walls found; the ceiling needs "look up".
|
||||
|
||||
Scan on request (agent v103): scene-emulation launchers run `_scan_watch` alongside the game. FrameBridge's
|
||||
`framebridge-scan-request` in the game's external files directory gets an ack, then the existing prompted headset
|
||||
capture/build flow saves and copies the new room before an `ok` result; errors, cancellation and timeout return
|
||||
`failed`. Captures share a global lock, stale protocol files are cleared on launch, and the watcher stops with the
|
||||
launcher. Logs: `<base>/scan-watch.log`. Device validation is still required.
|
||||
|
||||
PR #246 (@alicecatpaw, merged into mr-scene 2026-10-18): FrameBridge trims frames to maxLayerCount (Punch Fit sent
|
||||
21+ layers; projection layers and FrameBridge's fade quad always stay), drops quads with zero size or an empty image
|
||||
rect, and logs up to 10 quads with pose and swapchain. Two Unity patches, both only from a catalog recipe or the
|
||||
user's choice: `frame.meta_depth_occlusion` (Depth API occlusion off; Laser Dance's lasers vanished up close) and
|
||||
`frame.unity_scene_active_rooms` (OVRSceneManager's player-in-room check). The PR's own META discovery and boundary
|
||||
visibility code was replaced by this branch's (`scene_meta.c`). The PR's recipes were checked on the author's Frame
|
||||
only; open question: does OVRPort's dispatcher advertise XR_META_environment_depth (then a FrameBridge setting that
|
||||
hides it would fix depth occlusion for every engine).
|
||||
|
||||
## Vulkan pipelines refused: Fossilize and Turnip (Transformers, #349)
|
||||
|
||||
Transformers Beyond Reality: Redux (Unreal, Vulkan) ended its first frames with Unreal's "Shader compilation failures
|
||||
@@ -785,3 +963,466 @@ SteamVR's own Touch remap (`/opt/steamvr/drivers/frame_controller/resources/inpu
|
||||
layout `oculus_touch`) is the diagram's "SteamVR default": X -> d-pad down, Y -> d-pad left/right/up, A -> A,
|
||||
B -> B/X/Y, Menu -> View, plus trigger click -> bumper. Games with their own Valve binding file in that folder
|
||||
(Beat Saber, Batman, Accounting+, In Death, ...) use that file instead.
|
||||
|
||||
|
||||
## Fixed layer resolution / Batman (#406, #394)
|
||||
|
||||
Batman: Arkham Shadow (`com.camouflaj.manta`, Unity + OVRPlugin, OpenXR/Vulkan) submits
|
||||
`2768x2768`, then corner-anchored `rect=0,32 2736x2736`, `rect=0,64 2704x2704` inside the
|
||||
same swapchain. On SteamOS 0.5.6 Valve's FDM injection covers the full swapchain: shrinking
|
||||
viewports can move the sharp centre towards the top right and expose a coarse white right edge.
|
||||
The runtime recommendation driving OVRPlugin's dynamic resolution remains unverified.
|
||||
|
||||
`adapter.fixed_resolution=1` (default off, Batman recipe on) overrides
|
||||
`xrGetRecommendedLayerResolutionMETA`: tracked full swapchain dimensions, `isValid=XR_TRUE`.
|
||||
The existing rect_clamp size table already records successful xrCreateSwapchain calls and
|
||||
retires entries at destruction. Projection views must have matching sizes; quads are supported;
|
||||
unknown swapchains, mixed sizes and other layer types retain the runtime behavior. Submitted
|
||||
rectangles are never enlarged after rendering. Full resolution may increase GPU load.
|
||||
|
||||
The cached OVRPort 3.4.3-aa54c3f dispatcher contains both the extension and function name
|
||||
(and a runtime-missing diagnostic). Its fixed routing table is not a guarantee that every
|
||||
version forwards the call unchanged. Enable the existing arm64 xrshim for this setting:
|
||||
OVRPlugin's loader lookup reaches `framebridge_extension_proc` directly, while native OpenXR
|
||||
loaders use the adapter's xrGetInstanceProcAddr hook. No extension hiding: keeping the
|
||||
recommendation API available avoids relying on an unverified OVRPlugin fallback. The adapter
|
||||
revision stays unchanged because the new setting is opt-in; the catalog requires 1.2.2 so older
|
||||
remote clients keep their bundled recipe (`recommend.catalog.unusable_reason`). Rebuild and
|
||||
reinstall Batman to receive the binary and shim routing; this is not a settings-only fix for
|
||||
an old APK. Device/headset verification is pending.
|
||||
|
||||
Headless check: expect `extension shim: xrGetRecommendedLayerResolutionMETA -> FrameBridge`
|
||||
for OVRPlugin, then up to five `fixed_resolution: recommended WxH -> 2768x2768 (runtime=...)`
|
||||
lines. Compare the size to the actual eye swapchain creation log; scaling can change it.
|
||||
`projection view 0 rect change: ... rect=0,0 2768x2768` (and view 1) should remain full size
|
||||
through gameplay/loading transitions. Rect changes continue to log at most once per second
|
||||
per eye, comparing against the last logged state so a throttled final change is still shown.
|
||||
No recommendation logs means this API path was not exercised: do not claim the fix worked.
|
||||
Headset check: sharp region centred in both eyes, no white right edge, correct menus/loading
|
||||
fades, and acceptable pacing with full-resolution GPU load.
|
||||
|
||||
This is separate from #303's `foveation_storage_fix` (clearing SUBSAMPLED allocation flags;
|
||||
see PLAYBOOK and FRAME_RUNTIME), which fixes black/coarse storage corruption. It also leaves
|
||||
#394's `FOVE_LEVEL=lo/med/hi` presets intact (commits 01ad5e6b, d7f0d514): those select the
|
||||
full-swapchain density pattern, not the game's dynamic viewport.
|
||||
## Embedded Khronos OpenXR loaders (GitHub #254)
|
||||
|
||||
PrimedGun v2 (`org.primedgun.v2`, 2.0.0-alpha.4) embeds the Khronos loader in
|
||||
`libmetroid_prime_port.so` (no loader DT_NEEDED). Its diagnostics show both Android
|
||||
broker queries failing, then `/vendor/etc/openxr/1/active_runtime.json`, runtime
|
||||
`SteamVR/OpenXR`, and no FrameBridge messages. The supplied `openxr_android.cpp`
|
||||
initializes the loader with the VM and SDL activity. This is Vulkan OpenXR; retain
|
||||
the catalog's removal of `frame.vk_sanitize` (the game assembles the Vulkan soname).
|
||||
|
||||
`frame.static_openxr_loader` is general: analysis v23 detects arm64 game libraries
|
||||
with Khronos manifest strings and negotiation/manifest markers. Complete manifest
|
||||
paths (including ABI variants) are replaced in `.rodata`, NUL padded, with
|
||||
`/sdcard/fp_openxr_runtime.json`. Khronos also builds paths from fragments: the
|
||||
verified `platform_utils.hpp` layout uses five partition prefixes, `/etc/openxr/`,
|
||||
the major API version and `/active_runtime[.aarch64].json`. For this complete table,
|
||||
prefixes become `/sd` and the subdir becomes `card/fp/`; all candidates then resolve
|
||||
to `/sdcard/fp/1/active_runtime[.aarch64].json`. The undecorated manifest is written;
|
||||
a missing ABI-specific file falls back to it. No offsets or sizes change. **Pooled
|
||||
partition strings can also be used by game code**; this cannot be ruled out from
|
||||
synthetic tests. Only the complete known fragment table is accepted. Other layouts
|
||||
need analysis/rewrite support before they can be patched.
|
||||
|
||||
Both broker authorities become `org.fp_null.openxr.*`, causing provider lookup to
|
||||
fail and the loader to fall back to files even if a broker is later installed.
|
||||
Only the game's copy is changed. Agent v108 writes both manifest spellings into
|
||||
`<base>/lepton-data/external` (the game's private `/sdcard`), records
|
||||
`static_openxr_loader` in deployment.json, and repairs them before every launch.
|
||||
Launcher upgrades preserve that repair. A configuration-only finalize also writes
|
||||
them. The payload is `{"file_format_version":"1.0.0","runtime":{"library_path":"libfp_xrruntime.so","name":"FramePort"}}`.
|
||||
|
||||
The shim exports negotiation and initialization, loads FrameBridge by soname,
|
||||
forwards Android loader initialization through its GIPA, and returns that GIPA to
|
||||
the outer loader. FrameBridge then loads `libopenxr_loader_original.so`, an
|
||||
unmodified Khronos loader reused from OVRPort's downloaded generic runtime.
|
||||
This works for recipes with `overport: False`: the patch supplies the original
|
||||
loader before `frame.adapter` installs FrameBridge, its settings payload and
|
||||
foveation support. Settings still use `settings.conf` through the normal launcher.
|
||||
No Meta dispatcher is needed. The loader's unmodified system paths keep it from
|
||||
recursing through the game manifest. Android linker namespaces, symbol binding
|
||||
between the two loaders, runtime handles across the chain, and FrameBridge's
|
||||
behavior without the OVRPort dispatcher still require a device check.
|
||||
|
||||
Source verification: Khronos OpenXR-SDK-Source
|
||||
[`release-1.0.34/src/common/platform_utils.hpp`](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.0.34/src/common/platform_utils.hpp)
|
||||
and the same fragment layout in release-1.1.53;
|
||||
[`manifest_file.cpp`](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.0.34/src/loader/manifest_file.cpp)
|
||||
leaves a bare library filename unchanged for dlopen and tries brokers before files;
|
||||
[`runtime_interface.cpp`](https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/release-1.0.34/src/loader/runtime_interface.cpp)
|
||||
forwards loader initialization to the runtime export before negotiation (or through
|
||||
GIPA afterwards). The reporter's diagnostic ELF summary contains no embedded
|
||||
loader version or raw string table; the exact game's loader revision and binary
|
||||
rewrite remain unverified. Host tests cover complete and assembled synthetic ELF
|
||||
layouts, idempotence, unchanged other bytes, APK apply/validate with no OVRPort,
|
||||
negotiation rejection, Android init forwarding and missing-chain failure. No Frame
|
||||
connection or device test was performed.
|
||||
|
||||
**Crash at Play (GitHub #254, 2026-10-23):** the reporter's 1.3.0 build crashed right after `xrCreateInstance`
|
||||
(stack overflow in SDLThread: FrameBridge GIPA → `libopenxr_loader_original.so` → the game's library → FrameBridge
|
||||
…). Lepton mounts Valve's implicit layer manifest at `/vendor/etc/openxr/1/api_layers/implicit.d/`, and the log shows
|
||||
`XR_APILAYER_VALVE_fdm_injection` loaded by BOTH loaders: one shared layer library stacked into two chains, so the
|
||||
inner chain's next call landed in the game loader's code. The rewrite now also turns the game loader's implicit-layer
|
||||
suffix `/api_layers/implicit.d` into `/api_layers/fp_none.d` (Khronos builds `<partition>/etc` + `openxr/1` + suffix),
|
||||
so only FrameBridge's inner loader loads implicit layers, as in every other FramePort game (patch revision 2). Not
|
||||
device-verified: headless, PrimedGun stays in its own launcher without the game's disc image, so the catalog keeps
|
||||
the patch off until a headset check.
|
||||
|
||||
Headless acceptance after rebuilding and installing with this development build:
|
||||
`uv run frameport test org.primedgun.v2`. Expect `fp_xrruntime: chain resolved:
|
||||
FrameBridge`, `fp_xrruntime: xrInitializeLoaderKHR forwarded to FrameBridge: 0`,
|
||||
`fp_xrruntime: negotiated runtime interface 1: FrameBridge`, then FrameBridge's
|
||||
`scale=... loader=...libopenxr_loader_original.so`, `xrCreateInstance` and pacing /
|
||||
`new layer:` lines. The outer loader should log the private sdcard manifest, the
|
||||
inner loader the real vendor manifest. Require RUNNING and submitted frames;
|
||||
headset rendering and controls need separate confirmation.
|
||||
|
||||
## Foveation: sharp area centre and size (#394)
|
||||
|
||||
Reports: Valve foveation's sharp area sat towards the bottom right in both eyes (In Death: Unchained, The 7th Guest
|
||||
VR), top right in Batman (dynamic resolution), and its size changed with FramePort's render resolution (RE4,
|
||||
Asgard's Wrath 2: Large at 1x bigger than at 1.5x/2x; Small "different sizes and shapes").
|
||||
|
||||
**Mechanism** (Valve's `libVkLayer_VALVE_fdm_injection.so` disassembled 2026-10-24, Mesa Turnip source, AC Nexus on the
|
||||
dev Frame): one library is both an implicit OpenXR API layer and a Vulkan layer. Its `xrCreateSwapchain` makes a
|
||||
density map per eye swapchain (`detected FDM image 63x54[2]` for 2016x1728 array 2: `ceil(w/32) x ceil(h/32)`, one layer
|
||||
per eye) and fills the `FOVE_LEVEL` preset rectangles in map-normalized coordinates, so every preset is centred at
|
||||
about (0.51, 0.51) of the **whole swapchain**. Its `xrEndFrame` stores each view's imageRect, but nothing uses it for
|
||||
the map. Eye-tracked offsets (`calc_offset`) are `foveationCenter NDC x 0.5 x render area`, also from the swapchain
|
||||
centre; Fixed (`disable_offsets`) passes none and creates images without the density-map-offset flag. Turnip then
|
||||
picks one density per GMEM **bin**, from the map texel under the bin's centre (`tu_calc_frag_area`); bins have a fixed
|
||||
pixel size (GMEM / bytes per pixel), roughly 250-300 px for AC Nexus.
|
||||
|
||||
**Root causes.** Off-centre: games often submit a rect that isn't the swapchain (AC Nexus rect=0,0 / 287,0 1726x1728
|
||||
inside 2016x1728 for its asymmetric eyes, then dynamic resolution rect=0,259 1467x1469; Asgard's Wrath 2 0,0 / 347,0
|
||||
2237x2256 inside 2592x2256), and the eye's optical axis is off the rect centre anyway (FOV left/right -1.03/+0.88 rad,
|
||||
up/down +0.86/-1.05). The swapchain-centred map then sits ~14% of the view width to one side (and towards the bottom
|
||||
for corner-anchored dynamic-resolution rects). Size: the sharp area is the set of bins whose centre lies in Valve's
|
||||
rectangle, so it snaps to whole bins: at 1x the bins are large relative to the eye, at 1.5x/2x smaller. At 1x Small
|
||||
(hi) and Large (lo) gave the **same** 572x444 px area; at 1.5x they differ (764x444 vs 764x892, also in shape). The
|
||||
rectangle is also a fraction of the swapchain, not of a smaller dynamic-resolution view.
|
||||
|
||||
**Fix (centre):** `VK_LAYER_FRAMEPORT_fdm_centre` (`native/foveation/centre.h`, a second layer in
|
||||
`libVkLayer_fdm_darken.so`; its constructor inserts it right after Valve's layer in GraphicsEnv's debug-layer list when
|
||||
`FDM_DEBUG` has `enable`, never for The Climb 2). Below Valve it sees the passes with Valve's map attached (render pass
|
||||
+ framebuffer, or dynamic rendering) and the offsets at their end, and replaces each eye layer's offset so that Valve's
|
||||
sharp centre (computed with Turnip's texel size and the preset) lands on the eye's optical centre (Fixed, or
|
||||
Eye-tracked without a gaze: all-zero offsets) or on the gaze re-based on the eye's rect (Eye-tracked). It never changes
|
||||
images: for Fixed it drops `disable_offsets` from `FDM_DEBUG` in the process (only once it is in the layer chain, and
|
||||
before Valve reads the option), so Valve runs exactly as in Eye-tracked (flags its own images, passes offsets) and the
|
||||
layer replaces Valve's gaze offsets with the optical centre. If Valve read the option first, its maps carry no offset
|
||||
flag and every pass is left alone (Valve's own Fixed). It writes every offset Turnip reads: Turnip takes one per view
|
||||
(or per-layer map layer) whatever `fragmentDensityOffsetCount` says, and one-eye passes get their eye's map layer
|
||||
(the map view's base layer picks the eye). FrameBridge
|
||||
(`adapter/fdm_views.c`) reports per view: swapchain size, array layer, imageRect and optical centre (GLES: rows from
|
||||
the bottom, Zink's memory order), through a weak `fp_fdm_set_views`. Double-wide single-layer eyes and passes of other
|
||||
sizes are left alone. `FRAMEPORT_FDM_CENTRE=0` (as `LEPTON_ENV_`) turns it off. Logs: `FrameBridge: fdm views: …`,
|
||||
`FoveationDarken: fdm_centre: pass 2016x1728 map 63x54 layer 0+2 (preset 1, Valve centre 1024,880, Fixed): 2
|
||||
view(s), 2 offset(s) from Valve 0,0 -> -24,-184 (+ more views)`. Not changed: the bin quantization of the size (driver design) and the preset being a fraction of
|
||||
the swapchain. Ships in the APK (FrameBridge + layer library): a game needs a rebuild (Update on Frame) to get it.
|
||||
|
||||
**Headless evidence, first version** (AC Nexus, Fixed, darkening test, `snapshot=2`; its white start flash shows full density white,
|
||||
coarse grey): FrameBridge now also reads Vulkan eye images back (`snapshot_vk.c`: both eyes, half size, the whole
|
||||
swapchain layer, rect + FOV in the log). Sharp-area centre minus the left eye's optical centre, before -> after:
|
||||
|
||||
| scale | size | swapchain / left-eye rect | before | after | sharp area (of the rect) |
|
||||
|---|---|---|---|---|---|
|
||||
| 1.0 | Large (lo) | 2016x1728 / 0,259 1467x1469 | +203,+43 px (+14%,+3%) | +26,+11 (+2%,+1%) | 572x444 (39%x30%) |
|
||||
| 1.0 | Small (hi) | same | +202,+43 | +26,+11 | 572x444 (same as Large) |
|
||||
| 1.5 | Large (lo) | 2976x2592 / 0,389 2166x2203 | +276,+65 (+13%,+3%) | +12,+17 (+1%,+1%) | 764x892 (35%x40%) |
|
||||
| 1.5 | Small (hi) | same | +276,+65 | +13,+17 | 764x444 (35%x20%) |
|
||||
| 2.0 | Large (lo) | 4032x3456 / 0,519 2933x2937 | +407,+87 (+14%,+3%) | +47,+31 (+2%,+1%) | 1148x892 (39%x30%) |
|
||||
| 2.0 | Small (hi) | same | (no flash frame caught) | -49,-81 (-2%,-3%) | 956x668 (33%x23%) |
|
||||
|
||||
Right eyes match (mirrored rects 244,259 / 360,389 / 488,519). The residual 1-2% is the bin grid. Not checked: GLES/Zink
|
||||
games (the dynamic-rendering path is implemented, the GL row order is unverified), Eye-tracked with a real gaze
|
||||
(headless has none), and the picture in the headset. Twice AC Nexus died in a freedreno thread right after a Vulkan
|
||||
snapshot of its loading frame (not without snapshots): keep `snapshot` for diagnostics.
|
||||
|
||||
**Headset round 1 failed (2026-10-24), second version.** The first version (b2be40af, reverted) crashed AC Nexus in
|
||||
Fixed (SIGSEGV in Turnip's `CmdBeginRendering` called from Valve's `fdm_injection_CmdBeginRendering`, ~33 s) and showed
|
||||
black in Eye-tracked. Causes, confirmed headless:
|
||||
- Eye-tracked: Valve declares `fragmentDensityOffsetCount` 1 for a two-view (multiview) pass but computes both eyes'
|
||||
offsets; Turnip reads one offset per view whatever the count says (`tu_fdm_num_layers`). The first version handed
|
||||
down an array of `count` entries, so Turnip read the right eye's offset from uninitialised stack memory. Now every
|
||||
offset Turnip reads is written: the view count comes from the render pass / rendering view mask (else framebuffer
|
||||
layers), each eye's gaze from Valve's array (the same memory Turnip reads with Valve alone), one-eye passes take the
|
||||
eye from the map view's base layer.
|
||||
- Fixed: the first version flagged every 2D render target with the density-map-offset flag, including
|
||||
external-memory images Valve deliberately leaves alone (`fdm_injection_CreateImage` skips images with
|
||||
`VkExternalMemoryImageCreateInfo`: Zink/AHardwareBuffer imports such as surface_emul's video). The layer no longer
|
||||
touches images. Valve's own Fixed path (`disable_offsets`) also crashed AC Nexus without this layer (1 of 2 headless
|
||||
130 s runs, same `fdm_injection_CmdBeginRendering` -> freedreno stack), so Fixed now runs Valve's offset mode (the
|
||||
Eye-tracked path, 0 crashes in 6 runs) with the layer replacing the gaze offsets by the optical centre.
|
||||
|
||||
Headless, 130 s each, AC Nexus 1x, Large, darkening test, `snapshot=15` (start-flash centre vs optical centre):
|
||||
|
||||
| run | result | left eye | right eye |
|
||||
|---|---|---|---|
|
||||
| foveation not enabled (`FDM_DEBUG` empty) | runs | whole view sharp | whole view sharp |
|
||||
| Valve only, Fixed (1) | **crash** after ~20 s, frames stopped | - | - |
|
||||
| Valve only, Fixed (2) | runs | +14%, +3% | +14%, +2% |
|
||||
| Valve only, Eye-tracked (headless default gaze) | runs | +25%, -9% | +2%, -8% |
|
||||
| centring, Fixed (2 runs) | runs, 72 fps | +2%, +1% | +2%, +1% |
|
||||
| centring, Eye-tracked | runs, 72 fps | +2%, +1% | +2%, +1% |
|
||||
|
||||
Later snapshots (dark scene) stay non-black in every surviving run (mean ~20/255 with the darkening test, ~39 without
|
||||
it): the darkening test dims everything outside the sharp area to 20%, which in AC Nexus' dark start area looks almost
|
||||
black, so headset checks of the picture should be done with the test off. Headless gaze isn't zero (Valve's
|
||||
straight-ahead gaze lands on the optical centre, which confirms the NDC re-basing). Not done: Khronos validation, a
|
||||
GLES game, a real gaze.
|
||||
|
||||
|
||||
## Typing window takes the controllers: SteamVR theater mode (#476, #477, AC Nexus)
|
||||
|
||||
**2026-10-24 (SteamOS 0.5.6, gamescope 2.18.2, Lepton 3.0.5).** With `device.text_input_window` (Lepton's
|
||||
`lepton-show-flatscreen`) some VR games render but ignore the controllers; the owner saw "the Android screen with a
|
||||
mouse pointer" in AC Nexus (catalog dropped the window, 94783f56), users in PadelVRTraining (#476) and AltspaceVR
|
||||
(#477); UNDERDOGS (#474) and many others were reported fine with it.
|
||||
|
||||
How the window reaches the headset: Lepton then sets `waydroid.wayland_display=wayland-0`, `lepton.headless=false`
|
||||
(`liblepton/properties.sh`), so Waydroid's window is a client of gamescope. gamescope runs with `--backend openvr
|
||||
--vr-overlay-show-immediately --virtual-connector-strategy PerAppId --vr-app-overlay-key valve.steam.desktopgame`:
|
||||
each Steam app's windows become a SteamVR dashboard overlay `valve.steam.desktopgame.<appid>` ("Gamescope", input
|
||||
method mouse) plus a cursor overlay `gamescope.gamescope-0.cursor.<appid>`. SteamVR's dashboard
|
||||
(`/opt/steamvr/resources/webinterface/dashboard/chunk~*.js`) does, when that game's frame comes alive:
|
||||
`!bVRSceneApp && /settings/dashboard/autoShowGameTheater` → `docking.SetInTheater()` + HideDashboard("GameFrame now
|
||||
alive, auto-theatering"). The Android window always exists before the OpenXR session (Unity: 10-30 s), so the game is
|
||||
never a scene app at that moment: the Android screen goes in front of the player as a theater screen, the
|
||||
controllers become a laser mouse (Steam Input switches to app 13830428 at that moment). When the session starts,
|
||||
SteamVR docks the frame back into the dashboard and opens the dashboard on it (`[ShowDashboardOverlay]
|
||||
{"overlayKey":"valve.steam.desktopgame.<appid>","sReason":"SetDockLocation"} bOpenDashboard true`): the Android
|
||||
screen with a pointer again, the session stays VISIBLE (no input) until something closes the dashboard. Steam
|
||||
sometimes closes it itself ~0.3 s later, FramePort's dashboard worker only after the first VR frame (AC Nexus loads
|
||||
for seconds after the session starts) and only when `IsDashboardVisible` says so; in one of the owner's three AC
|
||||
Nexus sessions with the window (2026-10-23 17:23) it stayed open on the Android screen until the owner pressed the
|
||||
button. The timeline in `vrwebhelper_systemui.txt` was the same for every window session of AC Nexus (16:40, 17:01,
|
||||
17:23) and for UNDERDOGS headless: overlay created → auto-theatering → session → SetDockLocation; without the
|
||||
window (17:48 onwards) none of it. So the race between the game's session/first frame and these dashboard steps
|
||||
decides per game and per start whether the player is left on the Android screen.
|
||||
|
||||
Fix (agent 119): the launcher (new line `_theater_hold <pkg> $$`, added to existing launchers by `upgrade_launchers`; log `<base>/theater.log`) and the dashboard worker turn `dashboard.autoShowGameTheater` off through
|
||||
IVRSettings (ctypes, `SteamVRSettings.bool_setting`) while a game with `device.text_input_window` runs (deployment
|
||||
recipe has the patch, the marker exists, display mode not "Flat window"), one hold file per launcher pid in
|
||||
`~/.local/share/frameport/theater-holds/`, and turns it on again after the last such game (a worker killed with its game, e.g. Steam stopped: the next
|
||||
game's worker or `ensure_host_fixes` puts it back; purge too). Headless with UNDERDOGS: no auto-theatering, no SetDockLocation, the
|
||||
window overlay never visible, Android's focused window still UnityPlayerActivity. A hold taken only by the worker (with Lepton's start, ~3 s before the window came alive) was too late: the dashboard still auto-theatered (15:01:28), so the hold is taken first thing in the launcher. End-to-end with the agent installed (15:05): theater.log `theater mode off while the game runs` at the launch, no auto-theatering/SetDockLocation, dashboard.log `theater mode back on` at the end; launcher + container killed with -9: the hold stayed, the next `info` (ensure_host_fixes) answered `SteamVR theater mode back on`; SteamVR drops the key from
|
||||
steamvr.vrsettings again when it's back at the default. 2D apps keep theater mode (it is how they are shown).
|
||||
Also: SteamOS 0.5.6 logs the dashboard button as `open_dashboard_action`, not `toggle_dashboard_action`; the worker
|
||||
didn't recognise it and closed each of the owner's presses again (AC Nexus 23:33, ten presses in eight seconds).
|
||||
|
||||
Facts for later: `vrcmd --overlays` lists every overlay with visible/not_visible (works headless);
|
||||
`vrcmd --settings-bool dashboard.autoShowGameTheater` / `--set-settings-bool <section.key> 0|1` (a "true" argument
|
||||
did nothing); `vrcmd --dock-overlay dashboard|theater|world <overlay key>`. Headless sessions never reach FOCUSED,
|
||||
so whether the controllers reach the game, and whether Steam's keyboard still opens for a text field with the
|
||||
window kept in the dashboard, needs the headset.
|
||||
|
||||
## Graceful stop: the game is paused before it ends (GitHub #480, agent v120)
|
||||
|
||||
Just Dance VR lost all progress on the Frame: its log shows `OfflineSave |WARN |OfflineSaveDico_FromString - NeedLoad:
|
||||
False` at every start and no save write ever, and its session logs end with the game still running (no
|
||||
`[OVRManager] OnApplicationPause(true)`). On a Quest an app is always paused before it closes; on the Frame nothing
|
||||
paused it: launch.sh's `stop()` ran `podman kill`, and Lepton's own teardown (it traps TERM/INT) runs
|
||||
`podman exec … /system/bin/reboot -p` + `podman stop -t 0` (Lepton 3.0.5, `liblepton.sh kill_container`). `reboot -p`
|
||||
goes through init, not the activity manager, so no `onPause`/`onStop` either. Games that save in
|
||||
`OnApplicationPause`/`onPause` (and Unity's own PlayerPrefs flush on pause) never saved.
|
||||
|
||||
What works in Lepton's Android 11 (checked 2026-10-24 on the dev Frame, AC Nexus = Unity, headless):
|
||||
- `input keyevent 3` (HOME) does **nothing** (headless: no focused display); the activity stays RESUMED.
|
||||
- `am start -a android.intent.action.MAIN -c android.intent.category.HOME` starts Lepton's Launcher3: the game's
|
||||
activity is PAUSED at once and STOPPED ~0.3 s later; Unity logs `[OVRManager] OnApplicationPause(true)` ~20 ms
|
||||
after the START and `OnApplicationFocus(false)` right after. Lepton's `LeptonProcessObserver` logs the foreground
|
||||
change but doesn't react to it.
|
||||
- `dumpsys activity activities` (~10-30 ms) lists each record as `* Hist #N: ActivityRecord{… <pkg>/<activity> tN}`
|
||||
followed a few lines later by `state=RESUMED|PAUSED|STOPPING|STOPPED …`.
|
||||
|
||||
FramePort (`PAUSE_SCRIPT`, run in the container with `podman exec … sh -c`): if the game's activity is in front, start
|
||||
the HOME intent, poll its state every 0.1 s until STOPPED (or gone), wait 0.4 s more, give up after 3 s in total. A
|
||||
game busy loading can't pause sooner (Unity's main thread doesn't answer, the activity sits in STOPPING: `still
|
||||
state=STOPPING`). A game that isn't in front (it ended itself, or was already sent home) is left alone.
|
||||
- launch.sh: `pause_game` runs first in `stop()` (before Lepton gets its TERM and before `podman kill`, so before
|
||||
Lepton's teardown), only while Lepton still runs (a game that ended or crashed adds no delay); output with times in
|
||||
`<base>/stop-pause.log`. The play log + `_snapshot_close` still follow `podman kill` under the launch lock, so
|
||||
automatic save snapshots now include what the game saved on pause. `upgrade_launchers` adds it to existing Lepton
|
||||
launchers (foveation exports and other lines untouched).
|
||||
- agent `stop` pauses before it stops the launch-test unit (systemd's stop TERMs every process in the unit, Lepton
|
||||
included, at the same moment) and kills the container; result field `paused`. The Monitor's End game pauses before
|
||||
Steam's Exit game, whatever Steam then signals.
|
||||
- Not covered: stopping Steam / restarting the Frame (systemd ends launch.sh and Lepton together and Lepton's
|
||||
teardown races the pause) and launch tests (their own `systemctl stop` also ends the pause's processes).
|
||||
|
||||
**Steam's own Exit game** (the Steam menu's Exit game = `SteamClient.Apps.TerminateApp`; covered since agent v121, see
|
||||
"Steam's Exit game hook" below). Steam's
|
||||
`reaper` is only `prctl(PR_SET_CHILD_SUBREAPER)` + fork/exec + wait (no kill logic); Steam itself signals every
|
||||
process in the game's process tree at once. podman's `conmon` double-forks and is re-parented to the reaper, so the
|
||||
container's processes (Android init, system_server, the game: `game ← main(zygote) ← init ← conmon ← reaper ←
|
||||
steam`) are part of that tree, although their cgroup is podman's `libpod-<id>.scope`, not Steam's
|
||||
`app-steam-app<appid>-<pid>.scope`. Measured: TerminateApp with no pre-pause → launch.sh's `pause_game` starts 0.2 s
|
||||
later and finds `Can't find service: activity` (system_server already gone), container gone after 0.65-0.8 s; the same
|
||||
after moving Lepton, `podman run` and conmon out of Steam's scope into another scope (so Steam kills by process tree,
|
||||
not by cgroup). Lepton itself never logged a TERM (`Exited!` only): `podman run`'s signal proxy killed the container
|
||||
(journal: `container kill` from Lepton's `podman run`). The other idea, starting Lepton outside Steam's process tree (a `systemd-run --user` service), wasn't taken: it
|
||||
needs a headset check that SteamVR still attributes the game to its shortcut (bindings, refresh rate, overlay).
|
||||
FrameBridge can't help: Steam's TERM reaches system_server too.
|
||||
|
||||
Timings on the dev Frame (AC Nexus, Unity, headless, 2026-10-24):
|
||||
|
||||
| path | OnApplicationPause(true) after the stop request | stop request → container gone |
|
||||
|---|---|---|
|
||||
| launch.sh TERM (parent watchdog, Steam stopping only the launcher) | 0.11 s | 1.36 s (pause done after 0.97 s; Lepton's teardown alone ~0.4 s) |
|
||||
| agent `stop` | 0.31 s (agent start + `podman ps` + exec) | 1.59 s (`stop` returns after 1.47 s) |
|
||||
| Monitor End game (`monitor_end_game`: pause, then Steam's Exit game) | 0.23 s | 2.06 s |
|
||||
| a game that doesn't answer (4 of 9 runs: once during its shader warm-up, three times with Unity's main thread already silent before the stop; one Steam run of the same setup had a render-thread crash in Valve's FDM layer with Eye-tracked foveation) | none | capped: pause gives up after 3 s (`still state=STOPPING`), agent `stop` 4.2-4.3 s |
|
||||
| Steam's own Exit game, before agent v121 | none (see above) | 0.65-0.8 s |
|
||||
| Steam's own Exit game with the hook (agent v121, Steam launch, `TerminateApp` called in SharedJSContext) | 0.18 s / 0.29 s | 1.50 s / 1.72 s (worker's pause 0.86-0.89 s, `state=STOPPED after 2 polls`) |
|
||||
| ... a game that doesn't answer (1 of 3 runs, Unity silent before the stop) | none | 3.24 s (pause gave up after 2.56 s, `still state=STOPPING`) |
|
||||
|
||||
### Steam's Exit game hook (agent v121)
|
||||
|
||||
Every Exit game path in Steam's UI is `SteamClient.Apps.TerminateApp(gameid, false)` in `sp.js`, run in
|
||||
SharedJSContext: the in-headset frame menu (`valve.steam.gamepadui.frame.menu`, a popup without scripts of its own; it
|
||||
has its own `SteamClient` object, which Steam's code doesn't use), the exit dialog (`#GameAction_ConfirmExitGameTitle`;
|
||||
SteamVR itself → `OpenVR.QuitAllVR`), "exit the running game and start this one", CANCEL while waiting for a VR game
|
||||
window, `ExitRunningGames`. `SteamClient.Apps.TerminateApp` is a writable, configurable property there. SteamVR's own
|
||||
dashboard (vrwebhelper, `/opt/steamvr/resources/webinterface/dashboard`) has `VRHTML.QuitSceneApp()` ("Exit %1$s")
|
||||
and a `RequestAppQuit` that goes to Steam's quit dialog; on the Frame the menus are Steam's gamepadui overlays.
|
||||
|
||||
Design: launch.sh (Lepton launchers; `upgrade_launchers` adds the line, foveation exports and other lines untouched)
|
||||
starts `_exit_hook <pkg> "$SteamAppId" $$` (log `<base>/exit-hook.log`, opt out `FRAMEPORT_NO_EXIT_HOOK=1`). The
|
||||
worker holds one CEF devtools connection (`SteamDevtools`, stdlib websocket), adds a CDP binding of its own
|
||||
(`Runtime.addBinding fpExitPause<pid>`) and evaluates `EXIT_HOOK_JS` every 2 s: the first run wraps TerminateApp
|
||||
(`window.__fpExit` = {orig, hook, games, pending, log}), later runs only renew the game's registration (its ids:
|
||||
`steam_gameid` of the shortcut appid, the devkit entry's, `SteamGameId`; lease 10 s). A reloaded UI or restarted
|
||||
Steam lost the hook: the next refresh (or reconnect, every 3 s) puts it back. The hook, for a registered id whose
|
||||
binding exists and whose lease is fresh, calls the binding and holds Steam's call; the worker answers at once (ack),
|
||||
runs `pause_app` (≤2.7 s) and then lets the hook call Steam's TerminateApp with the original arguments. Never longer
|
||||
than 3 s (cap), 0.8 s without an ack (worker gone or hung: the binding function outlives its connection, calls then go
|
||||
nowhere), not at all for other ids (Steam games, other shortcuts: straight through, `__fpExit.log` notes `passed`),
|
||||
a second press while pending is dropped (one exit follows). If another script wrapped TerminateApp after the hook, the
|
||||
hook isn't applied again (no loop). The worker removes its registration when its launcher ends. FramePort's own End
|
||||
game (Monitor) pauses first and then calls `__fpExit.unhooked` (past the hook: no second pause). launch.sh's own
|
||||
`pause_game` still runs when Steam's TERM arrives; the game is in the background by then (`not in front`).
|
||||
|
||||
Checked on the dev Frame (2026-10-24, AC Nexus launched through Steam, headless): `TerminateApp` for the game's id from
|
||||
SharedJSContext → `exit-hook.log` "Exit game 11660673876089634816: pausing the game first" → logcat `[OVRManager]
|
||||
OnApplicationPause(true)` 0.18-0.29 s after the call → "paused (state=STOPPED after 2 polls) in 0.86 s; Steam ends it
|
||||
now" → container gone 1.5-1.7 s after the call (0.65-0.8 s before: ~0.9 s added). Ids of other games (`730`-style,
|
||||
a made-up one) passed straight through. The End game path went past the hook (no hook entry). Not checked in the
|
||||
headset: pressing Exit game there (same JS call per Steam's code). Not covered: SteamVR's `QuitSceneApp`, Steam
|
||||
restarts / power-off.
|
||||
|
||||
Diagnostics (agent v122, #480): `collect_diag` returns the last launch's `stop-pause.log`, `exit-hook.log`,
|
||||
`theater.log`, `dashboard.log` and the anchor's `plays.log`, so a user's zip shows whether the pause ran and how the
|
||||
game was closed. Open question from #480 (dev build with the pause, progress still lost): whether the pause ran in the
|
||||
user's session at all, and whether Just Dance VR saves on pause. Its log shows `NetworkApplication.OnApplicationPause`
|
||||
throwing a NullReferenceException at every pause while it's offline (`ChatApp.SetStatus`, "not connected to front end
|
||||
server"), so a save hooked to that handler would never run; a Quest's own quit also goes through
|
||||
XR_SESSION_STATE_EXITING → OVRManager `Application.Quit()` → `OnApplicationQuit`, which FramePort doesn't emulate yet.
|
||||
|
||||
|
||||
### Just Dance VR never writes its local save (`frame.justdance_local_save`)
|
||||
|
||||
With the pause in place, Just Dance VR (0.5.4: Unity 2022.3.15f1, IL2CPP) still lost everything. Found statically with
|
||||
Cpp2IL (diffable-cs + ISIL) and capstone on libil2cpp.so (0.5.4 SHA-256 ff91e080…; file offsets, VA = offset + 0x1000):
|
||||
- Every system saves through `Neon.Gameplay.LocalSave.LocalSaveEvents.RequestSave(string key, object value)` (14
|
||||
callers: PlayerProgression.SavePlayerProgression, InventorySaveManager/AvatarSaveManager/GoalsSaveManager.Save,
|
||||
AchievementSystem, FTUEManager, EmotesAndToysCustomisationManager, SongOfTheWeekManager, PlayerDataTracker,
|
||||
SocialLocalSavesStore) and loads through `RequestHasSaveData`/`RequestLoad` (PlayerProgression.OnSaveDataReady ->
|
||||
OnLocalDataLoaded -> RemapProgression, then SyncData with PlayFab). **RequestSave (0x57f99e8) is a single `ret`.**
|
||||
The only path to `JdOfflineSave.OfflineSave_SetValue` is `LocalSaveManager.OnSaveValueRequested` (subscribed to
|
||||
`SaveValueRequested`) -> `LocalSaveController.SaveData<T>`, so the OfflineSave dico never gets `NeedSave` and its
|
||||
write thread (every 1000 ms) and the forced write on focus loss have nothing to write.
|
||||
- Loading works as written: `RequestLoad` raises `LoadValueRequested` -> `LocalSaveManager.OnLoadValueRequested` ->
|
||||
`LocalSaveController.LoadData<string>` -> `OfflineSave_GetValue` (refused with error 40/41 only when the file was
|
||||
marked corrupted) -> the value goes back to the requesting system's callback.
|
||||
- `OfflineSaveDico_FromString - NeedLoad: False` (a Debug.LogWarning) is printed after every parse that didn't throw,
|
||||
with a boxed constant `false` after `strh wzr, [dico, #0x48]` (NeedSave = NeedLoad = false): normal, not a
|
||||
rejection. The version check and `CheckWholeMapHmac` report problems through io_Iret (41 = HMAC mismatch ->
|
||||
`m_OfflineSaveFileCheckDoneAndFileIsCorrupted`). The HMAC input comes from `GetNativeAccountFields`: two constant
|
||||
string pairs, no user id. A file is only rewritten when a value changes (NeedSave).
|
||||
- On a Quest the progress lives in PlayFab: `BackendRequestManager.GetUserPlatformData` (Meta `Users.GetLoggedInUser`)
|
||||
-> `PlayfabAuthenticate` (`LoginWithCustomID`); `SetPlayerData` -> `PlayFabClientAPI.UpdateUserData`. Without Meta's
|
||||
platform the game never signs in. FramePort doesn't touch that.
|
||||
- `PlayerDataTracker.<WaitForAuth>d__41.MoveNext` returns false in its first step (a `yield break`): per-song records
|
||||
never load either (not changed).
|
||||
- The patch (per build, nothing hardcoded): Cpp2IL gives RequestSave, SaveSystemReady, the static field offsets of
|
||||
SaveValueRequested (0x28) / SaveSystemReadyEvent (0) and LocalSaveEventArgs' Key/Value (0/8: a 16-byte struct, so
|
||||
key and value go in x1/x2). capstone decodes SaveSystemReady: GOT slot of the TypeInfo variable (0.5.4: VA
|
||||
0x7347c48), the metadata init on it (0x3192e20), static_fields at +0xb8, delegate invoke_impl +0x18 / target +0x40 /
|
||||
method +0x28. The 24-instruction body goes into a new R+X PT_LOAD (`elf.append_segment`, takes the PT_NOTE header;
|
||||
0.5.4: VA 0x7a8c000), RequestSave's `ret` becomes `b` there. RequestSave with a body of its own = left alone.
|
||||
- Device (headless, 2026-10-24, first build of the patch): RUNNING at 72 fps twice; the first launch created
|
||||
`files/JdNext.OfflineSave/JdOfflineSave_JdLog.OfflineSave.txt` (keys Achievements, OffSaveFormatVersion, UbiHmac),
|
||||
the second read it. Still to check in the headset: level, unlocks and the finished tutorial after a restart.
|
||||
|
||||
## Expansion file names (#450, #481)
|
||||
The game opens `Android/obb/<package>/main.<versionCode>.<package>.obb` by exact name (Unreal's `OBBData`, Unity's
|
||||
split binary, CryEngine's versionless `main.<package>.obb`); the Frame's filesystem is case-sensitive. Downloads
|
||||
sometimes arrive as `main.203.com.Armature.VR4-003.obb` / `patch.203.com.Armature.VR4-001.obb` (#450: VR4 stopped
|
||||
right after its graphics started). `quest_dump.obb_names` (names only, no disk access) decides per top-level
|
||||
`main`/`patch` file:
|
||||
- renamed on the Frame (the PC file keeps its name): a copy suffix after the package (`-NNN`, ` (N)`, ` - Copy`: never
|
||||
part of a package name) or a different case; the versionCode stays what the file says.
|
||||
- left alone: exact names, Unreal chunk files (`patch.pakchunk27-Android_ASTC.<pkg>.obb`, #471), split parts
|
||||
(`main.<pkg>.part0.obb`, Phantom), other data (`Audio_*.obb`, `overflow1.…`, `chunk.N.…`) and files in subfolders.
|
||||
- warned, sent as they are: another app's name (`main.1716.com.beatgames.beatsaber.lj369vr.obb`, #129), a copy whose
|
||||
right-named file is there too, two copies for one name, a copy suffix on a versionless name (the number may be the
|
||||
versionCode).
|
||||
The installer sends `obb/<Frame name>` and passes the Frame-named manifest as `obb_manifest` (no agent change);
|
||||
`plan_update` compares `quest_dump.frame_manifest(...)` with the deployment, so a rename doesn't show "Update on Frame"
|
||||
again. A game installed earlier under the copy name gets its file renamed on the Frame by SFTP (`installer._reuse_renamed`:
|
||||
moved into `incoming/obb/<new name>`, finalize moves it into place) instead of a second upload. On the Frame itself
|
||||
(#433) the hardlink keeps the PC's name as its source (`_relink_installed(source=)`). Save backups skip `.obb`
|
||||
anyway. Loose copy-named OBBs next to an APK are found by name too (`_named_obbs`), the right-named one wins a tie.
|
||||
Survey of the owner's dumps (2026-10-24, 370 OBBs): no copy suffixes or case differences; the non-standard names were
|
||||
all the game's own (162 Asgard's Wrath 2 chunks, Phantom's `.partN`, Metro's `chunk.N`, Batman's `Audio_*`, PowerWash's
|
||||
DLC, Espire 2's `overflowN`). The old #129 check flagged Asgard's Wrath 2's chunks and Phantom's parts as "named for
|
||||
another app"; they're left alone now.
|
||||
|
||||
## App bundles (.xapk / .apks / .apkm / .apkx, discussion #482)
|
||||
|
||||
**Formats (2026-10-24):** `.xapk` (APKPure: `manifest.json` with `split_apks` [{file, id}] and `expansions`
|
||||
[{file, install_location, install_path}], numbers sometimes as strings), `.apks` (bundletool: `toc.pb`,
|
||||
`splits/<module>-master.apk` + `splits/<module>-<targeting>.apk`, `standalones/`, `universal.apk`, plus `instant/`,
|
||||
`system/`, `archive/` which FramePort skips; SAI: flat `base.apk` + `split_*.apk` + `meta.sai_v1|2.json`), `.apkm`
|
||||
(APKMirror: `base.apk` + `split_config.*.apk` + `info.json`; plain zips since 2021-01, older ones are libsodium
|
||||
secretstream files: not `PK`, byte 1 = pwhash algorithm 1/2, see github.com/souramoo/unapkm; FramePort detects and
|
||||
explains, never decrypts). **`.apkx` isn't a defined format**: no store or tool documents it, FramePort treats it as a
|
||||
zip of APKs and reads the contents. **FrameDrop takes no bundles** (closed source; framedropvr.com/about: "one .apk
|
||||
file. Split packages (.apks, .xapk, .aab) need to be merged into a single APK first"; OBBs only from an `obb` folder
|
||||
next to the APK), so there is no FrameDrop behaviour to match beyond install-link manifests: a manifest `files` entry
|
||||
may now be a bundle (`deeplink.classify` → APK kind).
|
||||
|
||||
**Design (`sources/bundles.py`):** Lepton installs exactly one APK per app folder and OVRPort converts one APK, so
|
||||
`pm install-multiple` inside the container is out; the parts are merged into one APK at scan time, into
|
||||
`<data>/bundles/<package>/<key>/<package>.apk` + `obb/<package>/` (key = sha256 of the zip's member names, CRCs and
|
||||
sizes: a moved/renamed bundle finds the same copy; `bundle.json` records what was used/left out). Library entries keep
|
||||
`bundle` (the user's file); `check_source` unpacks again when the copy is gone; `library.remove_game` deletes the
|
||||
copy; "delete files on this PC" takes the bundle + copy. Base = `universal.apk`, else an arm64 standalone, else the
|
||||
APK without a `split` manifest attribute. Splits: per module one ABI (arm64-v8a; armeabi-v7a only without any 64-bit
|
||||
code: then the a32 bridge rules apply; x86 never), one texture format (astc > etc2 > …), all density/language/feature
|
||||
splits. lib/ + assets/-only splits are merged in Python (base entries keep compression and dates, v1 signature files
|
||||
dropped; manifest: `isSplitRequired`/`requiredSplitTypes`/`splitTypes` attribute records removed by
|
||||
`axml.remove_attrs`, `com.android.vending.splits.required` = false, else Android 11 refuses the lone base with
|
||||
INSTALL_FAILED_MISSING_SPLIT and Play Core's missing-split check stops the app). Splits with resources/code go through
|
||||
REAndroid's APKEditor (Apache-2.0, `tools/apkeditor.py`, newest release ≥ 1.4.9 downloaded into tools/; 1.4.3-1.4.8
|
||||
corrupted some merges; `m -i <dir of exactly the chosen APKs>`: it merges every split it is given, drops the split
|
||||
attributes and every `com.android.vending.*`/`com.android.stamp.*` meta-data). Without Java/network a bundle whose
|
||||
extra splits are only language/density/texture ones falls back to the Python merge (translations/densities lost, base
|
||||
defaults stay); feature modules then fail with a clear error. The merged APK is unsigned: builds sign it with the
|
||||
game's key as usual, `prepare_as_is` signs a copy. `analysis.extra.split_apk` now reads the attributes, not the pool
|
||||
strings (a merged base keeps the strings). A single-APK `.xapk` is extracted unchanged (signature intact).
|
||||
|
||||
**Device check (2026-10-24, headless, dev Frame, isolated FRAMEPORT_HOME):** Escaping Wonderland 1.0.1.1354
|
||||
(`com.CortopiaStudios.Spectre`, Unity, catalog "works", not installed on the Frame) split into a bundletool-style
|
||||
base (everything but `lib/`) + `config.arm64_v8a` split (the 22 `lib/arm64-v8a` libraries, synthetic split manifest),
|
||||
packed as `.xapk` and `.apks`. `frameport scan <xapk>` unpacked and merged it (Python path) in ~30 s incl. analysis;
|
||||
the merged APK has the same 712 entries with identical CRCs/sizes/compression as the original. Built (catalog recipe,
|
||||
OVRPort + Frame fixes, signed with a fresh test key), installed, launch test: RUNNING, FrameBridge loaded, OpenXR
|
||||
instance, submitting frames at 72 fps (pass). Uninstalled afterwards (no saves kept, shortcut removed). APKEditor
|
||||
1.4.9 downloaded and merged the same base + arm64 split in 4 s (split attributes gone, unsigned output); a real
|
||||
language/density split merge through APKEditor wasn't tried on the device (no such bundle in the dumps).
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
# Diagnostics and problem reports
|
||||
|
||||
Two ways users feed results back, both without a GitHub token:
|
||||
FramePort sends results back in two ways, both without a GitHub account token:
|
||||
|
||||
- **Share working recipe…** (game menu, `frameport share-recipe <pkg>`): saves the recipe as a user catalog entry and
|
||||
opens a prefilled issue from `.github/ISSUE_TEMPLATE/working-config.yml`. A maintainer checks it and adds the label
|
||||
`catalog-accepted`. Then `.github/workflows/catalog-from-issue.yml` runs `scripts/catalog_from_issue.py`, which reads
|
||||
only the YAML block, validates it, and writes `catalog/games/<pkg>.yaml`, and the workflow opens a PR.
|
||||
- The PR step needs Settings → Actions → "Allow GitHub Actions to create and approve pull requests".
|
||||
- Create the labels `working-config`, `catalog-accepted` and `bug` once.
|
||||
- **Report a problem…** / **Collect logs** (game menu, Settings, the failure pop-up, `frameport diag report|collect`): writes
|
||||
a redacted zip and opens a prefilled `bug-report.yml` issue.
|
||||
- GitHub has no API for issue attachments, so the user drags the zip in (25 MB max; the bundle stays under 24 MB).
|
||||
- Prefilled links are capped at about 7.5k characters. Free text is trimmed first; the recipe is never cut.
|
||||
- **Report a problem…** / **Collect logs** (game menu → Troubleshoot, Settings, the failure pop-up,
|
||||
`frameport diag report|collect`) writes a zip of logs with personal data removed ([Redaction](#redaction)) and
|
||||
opens a prefilled issue (`bug-report.yml`). You drag the zip into the issue: GitHub has no API for attachments.
|
||||
The zip stays under GitHub's 25 MB limit and holds no game files.
|
||||
- **Share working recipe…** (game menu → Recipe; `frameport share-recipe <pkg>`) saves the recipe as your own catalog
|
||||
entry and opens a prefilled issue (`working-config.yml`).
|
||||
|
||||
Code: `src/frameport/diag/` (`redact.py`, `bundle.py`, `issue.py`), agent command `collect_diag` (agent ≥ 21),
|
||||
Prefilled links are capped at about 7.5k characters; free text is trimmed first, the recipe never.
|
||||
|
||||
**For maintainers:** labelling a recipe issue `catalog-accepted` runs `.github/workflows/catalog-from-issue.yml`:
|
||||
`scripts/catalog_from_issue.py` reads only the YAML block, validates it, writes `catalog/games/<pkg>.yaml` and the
|
||||
workflow opens a PR. The workflow needs Settings → Actions → "Allow GitHub Actions to create and approve pull requests" and the
|
||||
labels `working-config`, `catalog-accepted` and `bug`.
|
||||
|
||||
Code: `src/frameport/diag/` (`redact.py`, `bundle.py`, `issue.py`), agent command `collect_diag`,
|
||||
`Target.collect_diag`, `core/applog.py` (app log + saved job logs).
|
||||
|
||||
## Bundle layout (schema 1)
|
||||
@@ -41,17 +44,19 @@ games/<pkg>/package/ stand-in for the game files (no content):
|
||||
games/<pkg>/target/ from the Frame (or the PC): launch.sh, settings.conf, deployment.json, launch.log,
|
||||
launch-test.log (PC VR), lepton-steamlaunch-<appid>.log, logcat-{main,crash,system,...}.log,
|
||||
ReviveInjector.txt / steam-<appid>.log / game-log-N.txt (PC VR), files.json (+ missing)
|
||||
stop-pause.log, exit-hook.log, theater.log, dashboard.log, plays.log: how the last
|
||||
session ended (pause before the stop, Steam's Exit game hook; play start/end)
|
||||
(game-log-N.txt: Unreal Saved/Logs + crash summaries, Unity Player.log / Player-prev.log /
|
||||
output_log.txt + crash error.log from the Proton prefix, agent v67)
|
||||
threads.txt (agent v79): saved while a Quest game runs (e.g. hangs), each thread's
|
||||
output_log.txt + crash error.log from the Proton prefix)
|
||||
threads.txt: saved while a Quest game runs (for example a hang): each thread's
|
||||
state, CPU ms over 2 s, kernel wait and syscall, plus its TCP sockets (remote = loopback /
|
||||
local / internet + port, no addresses); read once from /proc, the game isn't paused
|
||||
games/<pkg>/target/shaders/ shader dumps (agent v72), when the game wrote any:
|
||||
games/<pkg>/target/shaders/ shader dumps, when the game wrote any:
|
||||
fp_vk_shaders/ adapter vk_shader_dump=1 (Vulkan shim): <size>_<sha256>.spv + index.txt
|
||||
fp_spirv/ adapter zink_shader_dump=1 (OpenGL ES, shader-fix layer under Zink): <size>_<sha256>.spv
|
||||
```
|
||||
Each log keeps at most its last 4 MB (2 MB per file from the Frame). Shader dumps, SPIR-V unchanged (compiled game
|
||||
shaders, not redacted): Vulkan (agent v74) every module the game's newest run (the last `# start` in `index.txt`)
|
||||
shaders, not redacted): Vulkan: every module the game's newest run (the last `# start` in `index.txt`)
|
||||
created, last used first, each up to 4 MB and up to 30 MB in all, with that run's index lines, so a module created
|
||||
well before a GPU hang comes along too (if the zip would pass 24 MB, the least recently used ones are left out
|
||||
after the logs were cut); OpenGL ES (no index): the newest modules (by time written) up to 4 MB.
|
||||
@@ -60,11 +65,10 @@ after the logs were cut); OpenGL ES (no index): the newest modules (by time writ
|
||||
the kernel's `hangcheck detected gpu lockup` line (this-boot-kernel.txt). All modules stay in the game's storage on
|
||||
the Frame (`Android/data/<pkg>/files/fp_vk_shaders/`, Files tab → the game's storage).
|
||||
|
||||
Display queries run only while collecting logs. The Windows event query has a five-second timeout and runs without
|
||||
a console window; the result distinguishes no matching events from a failed or timed-out query. Window visibility,
|
||||
focus and completed move/resize events are recorded in `app.log`, with UTC timestamps for comparison with Windows
|
||||
events. There is no display polling loop or change to display settings. Empty System event results do not rule out
|
||||
a rendering or monitor-link problem.
|
||||
Display queries run only while collecting logs (the Windows event query times out after 5 s and says whether the query
|
||||
found nothing or failed). `app.log` records window visibility, focus and move/resize events with UTC times, to
|
||||
compare with Windows events. No display polling, no change to display settings. Empty System events don't rule out
|
||||
a display problem.
|
||||
|
||||
## Redaction
|
||||
`diag/redact.py` runs on every file and on the issue text.
|
||||
@@ -77,12 +81,12 @@ a rendering or monitor-link problem.
|
||||
- **Kept:** generic accounts that identify no one: `steamos`, `steamuser` (Proton), `root`, `deck`.
|
||||
|
||||
## Debugging from a bundle (no game, no Frame, no GUI)
|
||||
1. `frameport diag inspect <zip>` (`--json` for everything). It prints the versions and warnings, the last launch
|
||||
1. `frameport diag inspect <zip>` (`--json` for everything). The command prints the versions and warnings, the last launch
|
||||
test, and a fresh triage of the newest launch log with the current `catalog/triage.yaml`.
|
||||
2. Match the findings and log lines against `docs/PLAYBOOK.md` (symptom → cause → fix) and `docs/FRAME_RUNTIME.md`.
|
||||
3. Check the analysis in `entry.json` (engine, XR API, `extra`: missing ovr symbols, features, Unreal version) and
|
||||
`package/elf.json` against the heuristics in CLAUDE.md. Compare `recipe.yaml` with catalog games that use the same
|
||||
`package/elf.json` against the patches' heuristics (each patch's `detect()`; [HEURISTICS_EVAL.md](HEURISTICS_EVAL.md)). Compare `recipe.yaml` with catalog games that use the same
|
||||
engine and API.
|
||||
4. Compare `manifest.json → env.frame.build_id` with the build the docs describe: SteamOS updates change the runtime.
|
||||
5. A new failure signature means adding a patch module, a `triage.yaml` signature, a PLAYBOOK row and a unit test (see
|
||||
Conventions). Then `diag inspect` on the same zip should report it.
|
||||
[CONTRIBUTING.md](../CONTRIBUTING.md)). Then `diag inspect` on the same zip should report the new signature.
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
## How should I lay out a game that has OBB files?
|
||||
|
||||
Some Quest games come as an APK (the app file) plus `.obb` files (the game's data). Give every game its own folder,
|
||||
put the APK in it and put the data in a folder named after the game's **package name** (for example `com.Armature.VR4`):
|
||||
Some Quest games come as an APK (the app) plus `.obb` files (the game's data). Give every game its own folder. Put
|
||||
the APK in the game's folder, and the data in a folder named after the game's **package name** (for example `com.Armature.VR4`):
|
||||
|
||||
```tree
|
||||
Games/ ← scan this folder (Add games → Scan a folder)
|
||||
@@ -16,38 +16,35 @@ Games/ ← scan this folder (Add games → Scan
|
||||
└── com.beatgames.beatsaber.apk ← games without OBBs: just the APK
|
||||
```
|
||||
|
||||
- **Data folder name:** the package name (`com.Armature.VR4` above) or `obb`.
|
||||
- **Everything in that folder is copied** to the Steam Frame, subfolders included, so other data files work too.
|
||||
- **Data folder name:** the package name or `obb`. Everything in the data folder is copied to the Frame,
|
||||
subfolders included.
|
||||
- **One game per folder.** Several APKs in one folder count as one game.
|
||||
- **Scanning:** pick the folder that contains the game folders (`Games/` above) to add them all, or one game's folder
|
||||
to add just that game. FramePort looks up to 5 levels deep.
|
||||
- **A single game:** **Add games → Add an APK…** also finds the data folder next to the APK.
|
||||
- **Scanning:** pick the folder with the game folders to add them all, or one game's folder for one game.
|
||||
FramePort looks up to 5 levels deep. **Add games → Add an APK…** also finds the data folder next to the APK.
|
||||
|
||||
Not sure of the package name? It's in the OBB file names (`main.<version>.<package name>.obb`), and the game's page
|
||||
shows it under **Details**.
|
||||
The package name is in the OBB file names (`main.<version>.<package name>.obb`) and under **Details** on the game's
|
||||
page.
|
||||
|
||||
## I moved my games to another folder or drive. How do I update the library?
|
||||
|
||||
Scan the new folder (**Add games → Scan a folder…**, or **Settings → Library folders → Add folder…**). A game whose
|
||||
files are gone from their old place and that FramePort finds again in the new folder keeps its library entry: only
|
||||
where its files are changes. Its name, tags, artwork, your own recipe and what's installed on the Frame stay; you don't
|
||||
have to remove it from the library first. **Rescan folders** in the Library does the same for every library folder.
|
||||
Scan the new folder (**Add games → Scan a folder…** or **Settings → Library folders → Add folder…**). Games found
|
||||
there whose old files are gone keep their library entry: name, tags, artwork, your recipe and the install on the
|
||||
Frame stay. **Rescan folders** in the Library checks every library folder
|
||||
the same way.
|
||||
|
||||
- **Removing a folder:** **Settings → Library folders** lists every folder FramePort scans, with how many library games
|
||||
come from it and whether it's there now (a folder on an unplugged drive shows as not found). The bin icon stops
|
||||
scanning a folder; its games stay in the library. `frameport folders [--add DIR] [--remove DIR]` does the same on the
|
||||
command line, `frameport rescan` rescans.
|
||||
- **Another version in the new folder** (a different APK or game build): **Scan a folder…** on that folder takes it, as
|
||||
**Update with a newer APK…** would (name, tags, art and your own recipe stay). **Rescan folders** leaves such games
|
||||
alone and says so; use the game's **Update with a newer APK…** / **…version…** to switch.
|
||||
- **Two copies** (the old folder is still there): the library keeps the copy it has. To use the other one, delete or
|
||||
rename the old copy and rescan.
|
||||
- PC VR games are recognised by their folder name, or a renamed folder by its program file. Linux apps aren't part of
|
||||
folder scans: add the moved app again with **Add a Linux app…** (it keeps its name, art and switches).
|
||||
- **Remove the old folder** in **Settings → Library folders** (bin icon). The folder's games stay in the library.
|
||||
- **Another version in the new folder:** **Scan a folder…** takes the new version like **Update with a newer
|
||||
APK…** would.
|
||||
**Rescan folders** leaves such games alone; use the game's **Update** menu to switch.
|
||||
- **Both copies still there:** the library keeps the copy it already has. Delete or rename the old
|
||||
copy and rescan.
|
||||
- PC VR games are matched by folder name, or by program file if renamed. Linux apps aren't scanned: add the moved app
|
||||
again with **Add a Linux app…** (the app keeps its name, art and switches).
|
||||
- Command line: `frameport folders [--add DIR] [--remove DIR]`, `frameport rescan`.
|
||||
|
||||
## Can I scan SideQuest backups or AXRB downloads directly?
|
||||
|
||||
Yes. Scan the folder as it is; FramePort finds the APK and its OBB files in both layouts:
|
||||
Yes. Scan the folder as it is:
|
||||
|
||||
```tree
|
||||
SideQuest Backups/ ← scan this folder
|
||||
@@ -71,180 +68,180 @@ AXRB/ ← AXRB's download folder (Downloads/AX
|
||||
└── patched/ ← AXRB's own PC builds: ignored
|
||||
```
|
||||
|
||||
- **Several backups of one game:** FramePort uses the newest one. To use another, scan just that backup's folder.
|
||||
- **AXRB's patched builds** (`patched/`, `*-axrb.apk`) are converted for AXRB's PC runtime and lack the game's data, so
|
||||
FramePort skips them and, on the next scan, replaces a game that was added from one with the original download.
|
||||
- **AXRB export ZIPs:** unpack them first. A game's ZIP holds `base.apk` and `Android/obb/<package>/`, which FramePort
|
||||
finds as is.
|
||||
- **"Game data (.obb) is missing" on the game's page** although you have the file: the backup didn't include it (copy
|
||||
the game's `Android/obb/<package>` folder from the Quest with SideQuest), or it sits in a folder FramePort doesn't
|
||||
look in. Then use the layout in the previous answer.
|
||||
- **Several backups of one game:** FramePort uses the newest. To use another, scan just that backup's folder.
|
||||
- **AXRB's patched builds** (`patched/`, `*-axrb.apk`) are made for AXRB's PC runtime and lack the game's data, so
|
||||
FramePort skips them.
|
||||
- **AXRB export ZIPs:** unpack them first.
|
||||
- **"Game data (.obb) is missing"** although you have the OBB files: the backup didn't include them (copy
|
||||
`Android/obb/<package>` from the Quest with SideQuest), or the files are somewhere FramePort doesn't look. Use the layout in
|
||||
the first answer.
|
||||
- **The game starts and stops at once (Steam shows Resume):** add the game again from its original download or
|
||||
backup, click **Update on Frame**, then report it (last answer on this page) if it still stops.
|
||||
backup, click **Update on Frame**, and report the problem if the game still stops.
|
||||
|
||||
## Can I add .xapk, .apks, .apkm or .apkx files (app bundles)?
|
||||
|
||||
Yes. Add them like an APK: **Add an APK…**, drag them onto the Library, scan a folder, use an install link, or
|
||||
`frameport scan <file>`.
|
||||
|
||||
| File | Made by | Inside |
|
||||
|---|---|---|
|
||||
| `.xapk` | APKPure and others | `manifest.json`, the APK (or `base.apk` + `config.*.apk` splits), `Android/obb/<package>/` |
|
||||
| `.apks` | bundletool, SAI | `splits/base-master.apk` + `splits/base-arm64_v8a.apk` …, or `universal.apk`; SAI: `base.apk` + `split_*.apk` |
|
||||
| `.apkm` | APKMirror | `base.apk` + `split_config.*.apk`, `info.json` |
|
||||
| `.apkx` | various | the same kind of zip under another name |
|
||||
| a folder of split APKs | `adb pull` of `pm path`, unpacked bundles | `base.apk` + `split_config.arm64_v8a.apk` … |
|
||||
|
||||
- FramePort merges the parts into one APK (the Frame installs one APK per game), in FramePort's data folder
|
||||
(`bundles/`). Your file isn't changed. FramePort takes the 64-bit ARM part, else the 32-bit one. Language and screen
|
||||
parts are merged with [APKEditor](https://github.com/REAndroid/APKEditor), downloaded on first use.
|
||||
- OBB files in the bundle, or in an `obb/` or `<package>` folder next to the bundle, are sent like a normal game's data.
|
||||
- The merged APK is signed with the game's own key, like every build. A game that checks its own signature may
|
||||
refuse to run.
|
||||
- **"is a protected APKMirror bundle":** older `.apkm` files are encrypted for APKMirror's installer. Download the
|
||||
app again (current `.apkm` files work), or get an `.apk` or `.xapk`.
|
||||
- Unpacking needs about twice the bundle's size in free space.
|
||||
|
||||
## Where are a game's settings in FramePort?
|
||||
|
||||
Every game has three places; the answers below refer to them.
|
||||
- **Game settings:** right-click the game → **Game settings…**. Render resolution, refresh rate, controllers, menus
|
||||
and more. Changes apply at the next start.
|
||||
- **Patches:** open the game's page → **Customize** (next to **What FramePort will do**). Turn a patch on or off,
|
||||
then click **Update on Frame**.
|
||||
- **The game's menu:** right-click the game, or **…** on the game's page. Groups: **Update**, **On the Frame**, **Saves**,
|
||||
**Name and artwork**, **Recipe** (**Reset to suggested recipe…**, **Share working recipe…**) and **Troubleshoot**
|
||||
(**Report a problem…**).
|
||||
|
||||
- **Game settings:** right-click the game in the **Library** → **Game settings…**. Render resolution (supersampling), refresh rate,
|
||||
controllers, menus and more, in plain words. Changes apply the next time the game starts.
|
||||
- **Patches:** click the game in the **Library** to open its page, then **Customize** (in the **Patches** section).
|
||||
Turn a patch on or off, then click **Update on Frame** at the top of the page.
|
||||
- **The game's menu:** the **…** button at the top of its page (or right-click in the Library → **More actions…**).
|
||||
It has **Reset to suggested recipe…** and **Report a problem…**, among others.
|
||||
|
||||
The sidebar has two tabs for troubleshooting: **Monitor** (the running game's frame rate, CPU and graphics) and
|
||||
**Files** (each installed game's storage on the Frame, under **Game storage**). Quest games expose their Android
|
||||
storage; PC VR and Linux games expose their install folders, including installations on another drive.
|
||||
SD cards, USB drives and other Steam library drives appear under **Drives**, with free space when available.
|
||||
Read-only drives can be browsed and downloaded from, but cannot be changed.
|
||||
For troubleshooting, the sidebar has **Monitor** (frame rate, CPU, graphics) and **Files** (each game's files on the
|
||||
Frame under **Game storage**; microSD cards and other drives under **Drives**).
|
||||
|
||||
## A game doesn't work right. Where do I start?
|
||||
|
||||
1. **Update FramePort** (it offers updates itself; Settings → Updates), then open the game's page: if it shows
|
||||
**Update on Frame**, click it. Recipes and patches improve often.
|
||||
2. **Read the game's page.** The last launch test and your last play session (**Last session**) leave their result
|
||||
there, often with a suggested patch or setting and a button that applies it. Try that first.
|
||||
3. **Changed patches yourself?** The game's menu → **Reset to suggested recipe…** brings back the tested ones.
|
||||
4. **Find your symptom below.** Each answer says how to tell what's wrong and what to do.
|
||||
5. **Still stuck?** Report it (last answer on this page).
|
||||
1. **Update FramePort**, then open the game's page. If the page shows **Update on Frame**, click **Update on Frame**.
|
||||
2. **Read the game's page.** The last launch test and **Last session** often suggest a patch or setting, with a
|
||||
button that applies the suggestion. Try the suggestion first.
|
||||
3. **Changed patches yourself?** **Recipe → Reset to suggested recipe…** brings back the tested ones.
|
||||
4. **Find your symptom below.**
|
||||
5. **Still stuck?** [Report the problem](#how-do-i-report-a-problem-well).
|
||||
|
||||
## The game doesn't start, or stops right away
|
||||
|
||||
How to tell: the game's page shows the last launch test's result (**Last launch test:** …) with an explanation, and
|
||||
Steam returns to **Play** or **Resume** within seconds.
|
||||
The game's page shows the last launch test's result with an explanation.
|
||||
|
||||
- **"Game data (.obb) is missing"** on the game's page: FramePort didn't find the game's data files, so they never
|
||||
reached the Frame.
|
||||
1. Find the game's `.obb` files (named like `main.<version>.<package>.obb`).
|
||||
2. Put them in a folder named exactly the package name (shown in the file names) next to the game's APK, as in the
|
||||
first answer on this page.
|
||||
3. Library → **Add games** → **Scan a folder…** and pick the game's folder.
|
||||
4. Open the game's page and click **Update on Frame**.
|
||||
- **"Lepton didn't start the game this time"**, or it failed only on the first start after an install: click **Play**
|
||||
again.
|
||||
- **"This game is 32-bit only"**: the Frame can't run 32-bit apps, and no patch changes that. Look for a PC VR version
|
||||
of the game.
|
||||
- **"The game quit itself right after starting"**: open the game's page and click **Update on Frame** so the suggested
|
||||
patches apply (many games check for a Quest at start, and a patch removes that).
|
||||
- **Nothing like that, or it still fails:** report it.
|
||||
- **"Game data (.obb) is missing":** the data files never reached the Frame.
|
||||
1. Find the game's `.obb` files (`main.<version>.<package>.obb`).
|
||||
2. Put them in a folder named exactly the package name, next to the APK ([layout](#how-should-i-lay-out-a-game-that-has-obb-files)).
|
||||
3. **Add games → Scan a folder…** and pick the game's folder.
|
||||
4. Click **Update on Frame**.
|
||||
- **"Lepton didn't start the game this time":** click **Play** again.
|
||||
- **32-bit-only APK:** the Frame has no 32-bit support. FramePort's 32-bit bridge (experimental) is suggested; if the
|
||||
game still doesn't run, look for a PC VR version.
|
||||
- **"The game quit itself right after starting":** click **Update on Frame** so the suggested patches apply.
|
||||
- **Anything else:** report the problem.
|
||||
|
||||
## It hangs on a logo, a black screen or a loading screen
|
||||
## The game hangs on a logo, a black screen or a loading screen
|
||||
|
||||
How to tell what it's doing: leave it running and open **Monitor** in FramePort's sidebar.
|
||||
Leave the game running and open **Monitor**.
|
||||
|
||||
- **CPU or graphics busy:** it's still working, often preparing its shaders on the first start. Wait up to 10 minutes
|
||||
once; the next start is quicker.
|
||||
- **CPU and graphics near zero:** it's stuck waiting for something. While it still hangs, open the game's menu →
|
||||
**Report a problem…** and save the diagnostics: that zip shows what each of the game's threads waits for (FramePort
|
||||
1.0.1 and newer), which a zip saved after quitting can't. Attach it to the report.
|
||||
- **The game starts with a video** and hangs at its logo: game page → **Customize** → turn off **Hardware video
|
||||
decoding** → **Update on Frame**, and try again.
|
||||
- **CPU or graphics busy:** the game is still working, often preparing shaders on the first start. Wait up to 10 minutes
|
||||
once; later starts are quicker.
|
||||
- **CPU and graphics near zero:** the game is stuck. **While the game still hangs**, use **Troubleshoot → Report a problem…**: the
|
||||
zip then shows what the game waits for.
|
||||
- **The game starts with a video** and hangs at its logo: **Customize** → turn off **Hardware video decoding** →
|
||||
**Update on Frame**.
|
||||
|
||||
## The picture is wrong
|
||||
|
||||
Most picture problems have a setting or a patch. Change one thing at a time, then start the game again (patches also
|
||||
need **Update on Frame**).
|
||||
Change one thing at a time, then start the game again (patches also need **Update on Frame**).
|
||||
|
||||
| You see | Try |
|
||||
|---|---|
|
||||
| Something shows in one eye only, or one eye is gray (Unity games) | Game page → **Customize** → [Unity: render each eye separately](PATCHES.md#frame-unity_multipass) |
|
||||
| Something shows in one eye only, or one eye is gray (Unity games) | **Customize** → [Unity: render each eye separately](PATCHES.md#frame-unity_multipass) |
|
||||
| Left and right look swapped (depth feels wrong) | **Game settings…** → **Swap eyes** |
|
||||
| Textures flicker, objects pop in and out | **Game settings…** → **Turn off space warp** |
|
||||
| One eye shimmers while menus look steady | Game page → **Customize** → **Eye-tracked foveation (Valve)**: **Fixed**, else **Off** |
|
||||
| One eye shimmers while menus look steady | **Game settings…** → **Foveated rendering (Valve)**: **Fixed**, else **Off** |
|
||||
| Menus or screens upside down | **Game settings…** → **Fix upside-down menus** |
|
||||
| Curved menus or video screens missing | **Game settings…** → **Show curved menus and screens** |
|
||||
| Blurry | **Game settings…** → raise **Render resolution (supersampling)** (costs frame rate) |
|
||||
|
||||
The [patch reference](PATCHES.md) lists every patch with what it does. If nothing helps, report it with a short
|
||||
description of what you see.
|
||||
Every patch is in the [patch reference](PATCHES.md). Nothing helps? Report the problem and describe what you see.
|
||||
|
||||
## It stutters or runs slowly
|
||||
## The game stutters or runs slowly
|
||||
|
||||
How to tell: open **Monitor** in the sidebar while you play. Smooth play needs the game's frame rate at the refresh
|
||||
rate (for example 72 or 90 fps).
|
||||
Open **Monitor** while you play. Smooth play needs the frame rate at the refresh rate (for example 72 or 90 fps).
|
||||
|
||||
- **Below the refresh rate:** right-click the game → **Game settings…** → lower **Render resolution (supersampling)** (for example to 0.8×) or
|
||||
pick a lower **Refresh rate**. After a session, the game's page may suggest a value.
|
||||
- **At the refresh rate but it still judders:** report it; that points to timing, not speed.
|
||||
- **Below the refresh rate:** **Game settings…** → lower **Render resolution (supersampling)** (for example to 0.8×) or the
|
||||
**Refresh rate**. After a session, the game's page may suggest a value.
|
||||
- **At the refresh rate, but the game still judders:** report the problem.
|
||||
|
||||
## The game pauses or recenters by itself
|
||||
|
||||
The Frame briefly tells games they lost focus, for example when the headset's sensor thinks you took it off.
|
||||
Right-click the game → **Game settings…**: keep **Don't pause on short interruptions** on and raise **Ignore focus
|
||||
dips up to** (for example to 2.5 s).
|
||||
The Frame briefly tells games they lost focus, for example when the headset's sensor thinks you took the headset off.
|
||||
**Game settings…**: keep **Don't pause on short interruptions** on and raise **Ignore focus dips up to** (for example
|
||||
to 2.5 s).
|
||||
|
||||
## Controllers don't work in a game
|
||||
|
||||
Right-click the game → **Game settings…**:
|
||||
In **Game settings…**:
|
||||
|
||||
- **No controllers, or only hands:** turn on **Use controllers**.
|
||||
- **Buttons work but you can't aim at menus:** adjust **Pointer angle**.
|
||||
- **Can't aim at menus:** adjust **Pointer angle**.
|
||||
- **Vibration too strong:** lower **Vibration strength**.
|
||||
- **Buttons repeat or do the wrong thing:** **Controller buttons** → **Mirror** or **Quest pair**
|
||||
([Controller buttons](CONTROLLERS.md)).
|
||||
|
||||
## The Frame turns off or gets hot during a game
|
||||
|
||||
How to tell: if it only happens on battery and never while plugged in, it's power, not FramePort. Heavy games can
|
||||
draw more than the battery delivers, especially at lower charge, and the Frame then turns off without an error.
|
||||
Only on battery, never plugged in? The cause is power, not FramePort: heavy games can draw more than the battery delivers.
|
||||
|
||||
- Play plugged in, or right-click the game → **Game settings…** → lower **Render resolution (supersampling)** to reduce the load.
|
||||
- It also turns off while plugged in: report it. The diagnostics include the log of the boot that ended.
|
||||
- Play plugged in, or lower **Render resolution (supersampling)** in **Game settings…**.
|
||||
- The Frame also turns off while plugged in: report the problem.
|
||||
|
||||
## My progress isn't saved
|
||||
|
||||
1. Quit through the game's own menu, not Steam's **Exit game**. Exit game closes the game at once, and some games only
|
||||
save when they exit normally.
|
||||
2. Check whether it saves at all: **Files** in the sidebar → under **Game storage**, the game → look at the files'
|
||||
dates. Play, save in the game, quit from its menu and look again.
|
||||
3. Nothing changes: report it.
|
||||
1. Quit through the game's own menu. Most games save when Steam's **Exit game** pauses them, but some only save when
|
||||
you quit them yourself.
|
||||
2. Check that the game saves at all: **Files** → **Game storage** → the game → look at the files' dates. Play, save, quit
|
||||
and look again.
|
||||
3. Nothing changes: report the problem.
|
||||
|
||||
Saves stay on the Frame when you update or reinstall a game.
|
||||
|
||||
## Can I back up a game's saves?
|
||||
|
||||
Yes. Steam doesn't sync Quest games' saves, so FramePort copies them to your PC: **Back up saves** next to Play on
|
||||
the installed game's page, or **Back up all saves** on the Steam Frame page (Installed games). Close the game
|
||||
first. A backup holds the game's own storage (`/data/data/<package>` and `/sdcard/Android/data/<package>`), without
|
||||
caches, shader caches and large downloaded content (videos, OBBs, files over 64 MB); the job says when something was
|
||||
left out. FramePort keeps the 10 newest backups per game (Settings → Data). They're in FramePort's data folder under
|
||||
`save-backups/<package>/` (**Open backups folder** in the game's menu).
|
||||
Yes. Steam doesn't sync Quest games' saves, so FramePort copies them to your PC. Close the game first, then use
|
||||
**Back up saves** next to **Play**, or **Back up all saves** on the Steam Frame page. Caches and large downloaded
|
||||
content (videos, OBBs, files over 64 MB) are left out.
|
||||
|
||||
In **Settings → Data**, **Choose backup folder…** selects a different folder, including a local Drive or Nextcloud
|
||||
sync folder. FramePort checks that it can write there. **Use the default** resets the location. Existing backups stay
|
||||
where they are and remain listed for restoring; retention only removes old backups in the currently selected folder.
|
||||
FramePort writes local files; your cloud client handles uploading them.
|
||||
- **Where:** `save-backups/<package>/` in FramePort's data folder (**Saves → Open backups folder**). Settings →
|
||||
**Data** → **Choose backup folder…** picks another, for example a cloud-sync folder. Older backups stay restorable.
|
||||
- **How many:** the 10 newest per game by default (Settings → Data).
|
||||
- **Automatically:** **Back up saves when a game closes** (Settings → Data, off by default). The Frame keeps 3
|
||||
snapshots per game, even while FramePort is closed; FramePort copies the snapshots over when FramePort
|
||||
connects. Connect once after turning the setting on.
|
||||
- **Restore:** **Saves → Restore saves…**. **Undo last restore** in the same window takes you back. Uninstalling a
|
||||
game with *Also delete its saves* backs them up first.
|
||||
|
||||
**Back up saves when a game closes** (Settings → Data, off by default) keeps the newest three changed snapshots per
|
||||
game on the Frame, even while FramePort is closed. Connect once after changing this setting to apply it to the Frame.
|
||||
New snapshots are copied to your backup folder when FramePort connects or polls the connection, marked **automatic**,
|
||||
and count toward the same backup limit. Unchanged saves are skipped. A snapshot captures what the game wrote to disk;
|
||||
it cannot recover progress a forcibly closed game never saved.
|
||||
|
||||
**Restore saves…** puts one back: it replaces the game's saves on the Frame. The Frame keeps a copy of the saves it
|
||||
had, so **Undo last restore** in the same window takes you back. When you uninstall a game and tick *Also delete its
|
||||
saves*, FramePort backs them up first. Command line: `frameport saves backup|list|restore|undo <game>`.
|
||||
Command line: `frameport saves backup|list|restore|undo <game>`.
|
||||
|
||||
## A game with mods crashes
|
||||
|
||||
How to tell: play once without mods and custom content. If it then works, a mod is the cause; the diagnostics zip
|
||||
usually names the mod's library in the crash. Report it to the mod's author, and check for a mod version that matches
|
||||
your game version.
|
||||
Play once without mods. If the game then works, a mod is the cause (the diagnostics zip usually names the mod).
|
||||
Report the crash to the mod's author, and check for a mod version that matches your game version.
|
||||
|
||||
## The game needs files I supply (ports, emulators, mods)
|
||||
|
||||
How to tell where they go: the game says it on screen or in its instructions, and its log names the path (in the
|
||||
diagnostics zip, `launch.log`). It's usually `Android/data/<package>/files`.
|
||||
The game's instructions say where; the place is usually `Android/data/<package>/files` (the game's `launch.log`
|
||||
names the path).
|
||||
|
||||
1. Unpack or extract the files on your PC first: these apps read folders, not disc images (`.iso`) or archives.
|
||||
2. **Files** in the sidebar → under **Game storage**, the game → open that folder → **Upload folder…** (or
|
||||
**Upload files…**). The game's menu → **Add videos and files** opens the same place.
|
||||
1. Unpack the files on your PC first: these apps read folders, not disc images (`.iso`) or archives.
|
||||
2. **Files** → **Game storage** → the game → that folder → **Upload folder…** or **Upload files…**.
|
||||
|
||||
In-game "choose a file" buttons don't work on the Frame, which has no file picker.
|
||||
In-game "choose a file" buttons don't work: the Frame has no file picker.
|
||||
|
||||
## How do I report a problem well?
|
||||
|
||||
1. Reproduce it once, then right away open the game's menu (**…** on its page) → **Report a problem…**. For a hang,
|
||||
do it while the game still hangs.
|
||||
2. Say what you saw in the headset and what you already tried.
|
||||
3. **Collect and open GitHub** saves a diagnostics zip and opens a prefilled game report; attach the zip to it. The
|
||||
zip holds logs, not your game files, and personal details are removed.
|
||||
1. Reproduce the problem, then right away use **Troubleshoot → Report a problem…** in the game's menu. For a hang,
|
||||
report while the game still hangs.
|
||||
2. Say what you saw in the headset and what you tried.
|
||||
3. **Collect and open GitHub** saves a diagnostics zip and opens a prefilled report: attach the zip. The zip holds logs,
|
||||
not your game files, and personal details are removed.
|
||||
@@ -1,16 +1,20 @@
|
||||
# Steam Frame runtime reference (SteamOS 0.3.0, build 20260922)
|
||||
# Steam Frame runtime reference
|
||||
|
||||
## Setup from the project page (checked 2026-10-10, dev Frame, SteamOS build 20260922)
|
||||
Facts about the Steam Frame's runtime that FramePort relies on, checked on the device from SteamOS 0.3.0 (build
|
||||
20260922) onwards; each section says when and on which version where it matters. Symptoms and fixes per game are in
|
||||
[PLAYBOOK.md](PLAYBOOK.md), longer investigations in [DEVICE_NOTES.md](DEVICE_NOTES.md).
|
||||
|
||||
## Setup from the project page (checked 2026-10-10, SteamOS build 20260922)
|
||||
|
||||
- Factory Frames have no browser; Steam's taskbar **+** offers Chromium as a Flatpak (`org.chromium.Chromium`,
|
||||
flathub is configured as a system remote). It is the default handler for https links once installed.
|
||||
flathub is configured as a system remote). Chromium is the default handler for https links once installed.
|
||||
- Desktop Mode has `curl`, `wget`, `python3` 3.12, `konsole`, `kdialog`, `xdg-open`, `avahi-browse`, `systemd-run`;
|
||||
no `wl-copy`/`xclip`.
|
||||
- avahi-daemon runs, but `avahi-browse` from an SSH session fails ("Daemon not running": no system D-Bus access
|
||||
there). A stdlib-Python mDNS query works when it listens on port 5353 in the 224.0.0.251 group (like avahi):
|
||||
replies to a random source port are dropped by the Frame's firewall. `bootstrap/setup.sh` does exactly that.
|
||||
- The Frame reaches the PC's port 8765 over the home Wi-Fi without any firewall change on the PC side here (WSL in
|
||||
mirrored mode with the earlier FramePort rule state). The PC is also visible through the Frame's hotspot
|
||||
- The Frame reaches the PC's port 8765 over home Wi-Fi without a firewall change on the PC (tested with WSL in
|
||||
mirrored mode). The PC is also visible through the Frame's hotspot
|
||||
(`wlanap`, 10.35.78.x): the script lists one PC once, by name and words.
|
||||
- Dolphin's "executable scripts" setting is the default (ask); `.sh` files open with a `bash.desktop` handler that
|
||||
runs them in a terminal. Not used by the setup (a pasted line is simpler and needs no download prompt).
|
||||
@@ -28,10 +32,25 @@
|
||||
200 keys, so ~200 launches after boot every container fails ("create keyring … Disk quota exceeded"). FramePort sets
|
||||
`keyring = false` in `~/.config/containers/containers.conf`; a reboot clears already-leaked keys.
|
||||
- Picks the activity with category LAUNCHER (`APP_ACTIVITY`). Game data is visible at `/sdcard/Android/{obb,data}/<pkg>`.
|
||||
- Ending a game: Lepton traps TERM/INT and tears down with `podman exec … reboot -p` + `podman stop -t 0`; neither
|
||||
pauses the app (no `onPause`, no Unity `OnApplicationPause(true)`). `input keyevent` HOME does nothing in the
|
||||
headless container; `am start -a android.intent.action.MAIN -c android.intent.category.HOME` (Launcher3) pauses the
|
||||
game at once and stops it ~0.3 s later. FramePort does that before its own stops (GitHub #480, DEVICE_NOTES
|
||||
"Graceful stop").
|
||||
- Steam's Exit game (`Apps.TerminateApp`) signals the game's whole process tree at once. `conmon` is re-parented to
|
||||
Steam's `reaper` (a bare child subreaper), so the container's Android processes are in that tree although their
|
||||
cgroup is podman's `libpod-<id>.scope`; moving processes out of Steam's `app-steam-app<appid>-<pid>.scope` doesn't
|
||||
shield them. The container is gone ~0.7 s after the call; nothing in launch.sh can run before that. Every Exit
|
||||
game path in Steam's UI (the in-headset frame menu, Quick Access, "exit the running game?" dialogs) is
|
||||
`SteamClient.Apps.TerminateApp(gameid, false)` in SharedJSContext's code (the menus are popups without scripts of
|
||||
their own; each popup window has its own `SteamClient` object, unused), and `SteamClient.Apps.TerminateApp` is a
|
||||
writable property there. FramePort hooks it while a game runs (launch.sh's `_exit_hook`, DEVICE_NOTES "Graceful
|
||||
stop"). CDP `Runtime.addBinding` works on Steam's CEF (calls arrive as `Runtime.bindingCalled` on the connection
|
||||
that added it; the function stays on the page after that connection closes, calls then go nowhere).
|
||||
- 64-bit only (no AArch32): 32-bit-only APKs run through FramePort's a32 bridge (`frame.a32_bridge`).
|
||||
- `<install>/lepton-shaders/` holds Mesa's shader cache (Lepton sets `mesa.shader.cache.dir=/data/shaders`):
|
||||
`foz_cache.foz` + `foz_cache_idx.foz` per graphics driver (Turnip Vulkan, zink GL), typically 4–6 files and 7–19 MB
|
||||
per game, 2D apps included. It speeds up later starts; deleting it only costs one slower start.
|
||||
per game, 2D apps included. The cache speeds up later starts; deleting the cache only costs one slower start.
|
||||
- **2D apps** (verified 2026-10-02 with an open-source 2048 game): Lepton runs every app headless
|
||||
(`lepton.headless=true`; only OpenXR output reaches the headset) unless the app folder (`STEAM_COMPAT_INSTALL_PATH`)
|
||||
contains a file `lepton-show-flatscreen` (`liblepton/app_metadata.sh`): then Android (11, Waydroid) gets a Wayland
|
||||
@@ -55,7 +74,7 @@
|
||||
changes when the files move.
|
||||
- The game's files (`lepton-app/`, `lepton-data/` = saves, `lepton-shaders/`, `settings.conf`, `launch.log`; PC VR:
|
||||
`game/`, `revive/`, `compatdata/` = Proton prefix; Linux: `app/`) live in `deployment.json["base"]`: the anchor
|
||||
itself on internal storage, or `<mount>/FramePort/<pkg>` on another drive (GitHub #90, agent v63).
|
||||
itself on internal storage, or `<mount>/FramePort/<pkg>` on another drive (GitHub #90).
|
||||
- SteamOS mounts removable drives (microSD) under `/run/media/<user>/<label or uuid>`; the agent's `drives` reads
|
||||
`/proc/mounts` (plus the drives of Steam library folders) and refuses vfat/exfat/ntfs (Lepton's data and Proton
|
||||
prefixes need Unix owners, permissions and symlinks) and read-only mounts. A drive that isn't mounted is an error for
|
||||
@@ -66,7 +85,56 @@
|
||||
`launch.sh` at the new folder and only then deletes the old copy; on the same filesystem it renames instead.
|
||||
Moving a game whose Lepton data holds absolute paths, or a PC VR prefix, is not yet verified on the device.
|
||||
|
||||
## Save backups (Quest games, agent v101, discussion #352)
|
||||
## Room data
|
||||
|
||||
- SteamVR on the Frame has no Meta scene/anchor extensions. Its chaperone supplies only a 2D outline raised to
|
||||
2.43 m, without furniture, ceiling or a room mesh. Valve's default boundary is a 20-point circle of roughly 1 m
|
||||
radius; FramePort refuses to treat it as a room.
|
||||
- FrameBridge's `scene_emul=1` serves saved room data, with a guardian box when no room is available. OVRPort's
|
||||
loader only dispatches functions in its own table: newer Meta entry points go through FramePort's extension shim.
|
||||
MRUK uses discovery (`xrDiscoverSpacesMETA`, older versions `xrQuerySpacesFB`) and requests Space Setup when
|
||||
discovery returns no rooms. Volumes hang below their top-face anchor in Meta's coordinate convention.
|
||||
- Room files use STAGE coordinates (metres, +Y up, floor y=0, -Z forward). The format contract is
|
||||
[mr/ROOM_FORMAT.md](mr/ROOM_FORMAT.md). On the Frame: `~/.local/share/frameport/rooms/<universe>/room.txt`,
|
||||
optional `room-mesh.bin` and `scan.log`; `rooms/current` points to the most recently saved room.
|
||||
- A room belongs to its chaperone universe. The agent reads the last universe switch from `vrserver.txt`, falling
|
||||
back to the newest universe in `~/.config/openvr/config/chaperone_info.vrchap`; dummy 424242 means none.
|
||||
A matching room is selected first, else `current`; the app reports `aligned: false` after a universe change.
|
||||
- Quest launchers copy that room beside `settings.conf` before Lepton starts; FrameBridge reads
|
||||
`dirname($FRAMEBRIDGE_CONFIG)/room.txt`. Stale copies are removed if no room exists or emulation is disabled.
|
||||
Each game's persistent anchors stay in `/sdcard/Android/data/<pkg>/files/framebridge-anchors.txt`.
|
||||
- With `scene_emul`, launch.sh starts `_scan_watch <pkg>` after Lepton, closing fd 9; its exit trap stops the
|
||||
watcher, which also exits if the launcher disappears. The watcher polls once a second and logs to `<base>/scan-watch.log`.
|
||||
In `<base>/lepton-data/external/Android/data/<pkg>/files/`, FrameBridge writes `framebridge-scan-request`
|
||||
(`request <id> <unix_seconds>`); the watcher clears stale request/ack/result files at startup, writes
|
||||
`framebridge-scan-ack` (`ack <id>`), reuses the headset capture/build worker, stores the current room and copies
|
||||
it beside settings.conf before atomically writing `framebridge-scan-result` (`ok <id>` or `failed <id> <reason>`).
|
||||
A global `rooms/.scan.lock` excludes concurrent captures, including manual scans; `rooms/.lock` still protects
|
||||
room publication. Launcher exit cancels the scan; a 350-second deadline leaves time for the six-minute protocol timeout.
|
||||
- The Room tab and `frameport frame room` manage rooms. `from-boundary` creates walls without furniture;
|
||||
`import` / `export` transfer room files; `show` writes an SVG preview; `build <dump.fprs>` syncs the bundled
|
||||
`fp_roomscan` by SHA-256 and runs it on the Frame with `--offline`. `scan` = agent `room_capture`: records the
|
||||
RoomView meshes (`/xrservice/roomview3d/meshData` + `/meshLayout`, `IVRBlockQueue_005`) while in-headset prompts
|
||||
guide the player, then builds the room from that recording.
|
||||
|
||||
### Scan preview
|
||||
|
||||
Live floor coverage is shown by default for manual and game-requested scans; the capture
|
||||
payload accepts `preview: false`, and `frameport frame room scan --no-preview` disables it. A world-locked,
|
||||
upward-facing 6 m square at floor height follows the current raw-to-STAGE calibration and stays centred on the
|
||||
scan's first recorded camera position. Every 16th depth vertex contributes to raw-space 10 cm voxels; at most once
|
||||
per second, newly occupied cells redraw a 256×256 RGBA map in one colour (FramePort's secondary blue), each cell a
|
||||
filled 10 cm square, only the opacity telling floor (<0.3 m, faint), raised surfaces (0.3–1.5 m) and tall surfaces
|
||||
(>1.5 m, strongest, the room's outline) apart; unseen transparent. Overlays have no depth (they draw over hands and
|
||||
passthrough), so the map stays faint; three colours in 3x3 blotches clashed in the first headset test. The map shows
|
||||
coverage, not a validated room boundary. IVROverlay_028 slots 1/3/20/22/33/43/62 (OpenVR 2.15.6 header)
|
||||
create/destroy/sort order (1, below the scan prompts' 10)/set width/set absolute STAGE transform/show/upload raw
|
||||
pixels. Each upload is 256 KiB (at most 256 KiB/s); timing for the first
|
||||
20 uploads and every tenth thereafter, plus failures, goes to `rooms/scans/scan-*.fprs.log` and the built room’s `scan.log`. Overlay errors or an
|
||||
upload taking over 50 ms disable the preview while capture continues; headset appearance and upload cost still
|
||||
need device validation.
|
||||
|
||||
## Save backups (Quest games, discussion #352)
|
||||
- A Lepton game's saves are two folders of `<base>/lepton-data`: `internal/<pkg>` (= `/data/data/<pkg>`:
|
||||
`shared_prefs/`, `files/`, `databases/`; Unreal's `files/UE4Game|UnrealGame/.../Saved` often lives here) and
|
||||
`external/Android/data/<pkg>` (= `/sdcard/Android/data/<pkg>`: Unity's `files/`, Unreal's external `Saved`). On the
|
||||
@@ -78,7 +146,7 @@
|
||||
exec channel's stdout, the result JSON on stderr. Left out: folders named cache/code_cache/no_backup/il2cpp/
|
||||
UnityShaderCache/(Vulkan)ProgramBinaryCache/GPUCache/… at any depth, Unreal `Saved/Logs|Crashes`, FrameBridge's
|
||||
`fb_snap_*`/`framebridge*` files, `.obb/.apk/.foz/.mp4/.mkv/.webm/.mov`, symlinks and special files, and files over
|
||||
64 MiB (big downloaded content; e.g. BattleTalent's `files/Video`). More than 4 GiB of saves is refused.
|
||||
64 MiB (big downloaded content; for example BattleTalent's `files/Video`). More than 4 GiB of saves is refused.
|
||||
- `_restore_saves '<json>'` reads the archive from stdin and treats it as untrusted: only regular files and folders
|
||||
under `internal/`/`external/` (no `..`, absolute paths, links, devices, duplicates), ≤1 GiB per file / 8 GiB in all,
|
||||
each file's sha256 must match the manifest, the manifest's package must be the game. Everything is staged in
|
||||
@@ -107,7 +175,7 @@
|
||||
(archive stdout, result JSON stderr). PC `install/saves.sync_snapshots` runs in the background, verifies the archive
|
||||
hash and package, imports it as a normal backup marked `automatic`, and applies `saves.keep`. A local
|
||||
`<data>/save-snapshots-synced.json` digest ledger prevents reimporting snapshots after pruning or deleting backups.
|
||||
Failures leave snapshots on the Frame for the next poll. This path does no disk/SSH work in rendering.
|
||||
Failures leave snapshots on the Frame for the next poll. The snapshot sync does no disk/SSH work in rendering.
|
||||
|
||||
## OpenXR runtime (as seen by games through overport's loader)
|
||||
- Instance extensions present include KHR_android_create_instance (must be enabled; the adapter adds it),
|
||||
@@ -122,7 +190,7 @@
|
||||
- Layer types (checked 2026-09-30 in `/opt/steamvr/bin/androidarm64/vrclient.so`, the Android-side runtime Lepton
|
||||
mounts at `/data/steamvr/runtime`): its compositors (`CSxrCompositorPrism`, `CSxrCompositorOpenVR`) only have
|
||||
`ComposeLayerQuad` and `ComposeLayerProjection`; cube/cylinder/equirect/equirect2 exist only as enum names, so
|
||||
there is nothing to switch on. It has a layer limit ("Exceeded the layer limit"; the value is logged by
|
||||
there is nothing to switch on. The runtime has a layer limit ("Exceeded the layer limit"; the value is logged by
|
||||
`layer_debug` from `maxLayerCount`). The adapter emulates cylinders (strips) and, per game, equirect layers
|
||||
(`equirect_emul`, an adapter projection layer). Refused cube swapchains use GLES/Vulkan stand-ins;
|
||||
`cube_quads` (default on) copies their faces into six quads for frames without a projection
|
||||
@@ -153,23 +221,24 @@ and left/up/right as Touch Y; right A as Touch A and X/Y/B as Touch B. Left View
|
||||
FrameBridge's `quest_buttons` preference (`0` default, `1` Mirror, `2` Quest pair) enables the extension when offered
|
||||
and suggests a Frame binding translated from the game's Touch suggestions. Common pressure/trigger/thumbstick,
|
||||
pose and haptic paths keep their Touch counterparts; thumbrest touch and system click are dropped, bumpers stay
|
||||
unbound. The player's SteamVR custom bindings can override suggestions. This works on both bridge ABIs, without
|
||||
unbound. The player's SteamVR custom bindings can override suggestions. `quest_buttons` works on both bridge ABIs, without
|
||||
the arm64-only extension shim. See [Controller buttons](CONTROLLERS.md) and the [mapping contract](input/QUEST_BUTTONS.md).
|
||||
Device verification of the new modes is pending integration with the native stream.
|
||||
The runtime accepts the suggested bindings in headless launches; the modes haven't been tried in the headset on
|
||||
many games yet.
|
||||
|
||||
## Lua native pointer compatibility
|
||||
|
||||
Frame's Android processes can map libraries above the lowest 2^47 bytes of address space. The verified xLua API 105
|
||||
engine in BattleTalent 4.7 rejects those otherwise valid pointers in `lua_pushlightuserdata`. This was isolated using
|
||||
engine in BattleTalent 4.7 rejects those otherwise valid pointers in `lua_pushlightuserdata`. The cause was isolated using
|
||||
the unchanged library from the source APK: a real allocation at `0x10000000` round-trips, but its own native `xlua_tag`
|
||||
pointer above 47 bits raises `bad light userdata pointer` and exits. With `frame.luajit_pointers`, both round-trip
|
||||
exactly. No game scripts or account checks are changed. This validates an address-layout compatibility requirement;
|
||||
exactly. No game scripts or account checks are changed. The test validates an address-layout compatibility requirement;
|
||||
the APK was not run on an actual Quest during this investigation.
|
||||
|
||||
The patch is restricted to SHA-256 `6d5860e4a4f1cc2b05c600c63297ed467369739666edca79413fff4374ff2684`.
|
||||
It redirects only the Lua engine's public API to a pinned full-pointer LuaJIT, keeping the original xLua bindings,
|
||||
The patch redirects only the Lua engine's public API to a pinned full-pointer LuaJIT, keeping the original xLua bindings,
|
||||
LuaSocket and game helpers. The patched build survives a 60 s headless launch and creates OpenXR swapchains.
|
||||
Early frame-rectangle errors and backend login errors remain in the capture; this does not verify rendering or play.
|
||||
Early frame-rectangle errors and backend login errors remain in the capture; the launch does not verify rendering or play.
|
||||
An arbitrary `lj_err_throw` followed by Unity destructor failures does not prove this pointer issue: first capture the
|
||||
Lua error text. `Unknown Platform` also appears after this crash is fixed, so it was not the fatal cause here.
|
||||
|
||||
@@ -180,10 +249,10 @@ Lua error text. `Unknown Platform` also appears after this crash is fixed, so it
|
||||
Appending `debug` while preserving inherited flags showed `fdm_injection_CreateInstance: skipped`.
|
||||
A temporary `FDM_DEBUG=enable,debug` launch created the Unity/Manta Vulkan instance and device,
|
||||
detected a 54x54 two-layer density map for 1728x1728 eye buffers, and logged render passes with
|
||||
`fdm 1`, `already_has_fdm=1`, and changing offsets for both eyes. This verifies the injection path,
|
||||
`fdm 1`, `already_has_fdm=1`, and changing offsets for both eyes. The probe verifies the injection path,
|
||||
not visual quality, gaze calibration, sustained gameplay stability, or an FPS/power improvement.
|
||||
`device.foveation` mode `tracked` now explicitly loads the layer and sets `FDM_DEBUG=enable` without
|
||||
debug spam. It is opt-in; do not enable it globally or assume all GLES/Zink and Vulkan renderers work.
|
||||
debug spam. The mode is opt-in; do not enable it globally or assume all GLES/Zink and Vulkan renderers work.
|
||||
Follow-up wearer test on 2026-10-11 FAILED in Batman: large rainbow glitches outside the gaze region
|
||||
and jitter even in clear regions. Restoring the original launcher and restarting removed BOTH symptoms,
|
||||
confirmed by the wearer. Do not recommend forced injection for this Batman build. Reaching foveated
|
||||
@@ -207,7 +276,7 @@ Lua error text. `Unknown Platform` also appears after this crash is fixed, so it
|
||||
That is the only way to reach the Vulkan side of OpenGL ES games (Zink creates the instance inside Mesa).
|
||||
- GL ES: Zink (Mesa GL on Vulkan). Strict GLSL (see PLAYBOOK) and occasional `DEVICE LOST` with MSAA render-to-texture.
|
||||
|
||||
## Proton / Windows games (surveyed 2026-09-29; running a Rift game under it not yet verified)
|
||||
## Proton / Windows games (surveyed 2026-09-29)
|
||||
- Steam on the Frame registers ARM64 compat tools from the app **"Steam Frame ARM64 Compat List"** (appinfo
|
||||
`extended.compat_tools`, found dynamically by the agent): `proton_11-arm64` (4628740, needs
|
||||
`steamlinuxruntime_steamrt4-arm64` 4185400), `proton-experimental-arm64` (4427310), `fex` (3127680, FEX-Emu for
|
||||
@@ -215,10 +284,11 @@ Lua error text. `Unknown Platform` also appears after this crash is fixed, so it
|
||||
SteamLinuxRuntime_4). None are installed by default.
|
||||
- `steam -ifrunning steam://install/<appid>` only opens a confirmation dialog in the headset. The agent's unattended
|
||||
mode writes an appmanifest stub (StateFlags 1026, installdir from appinfo) and restarts Steam, which then downloads it.
|
||||
- **x86_64 Linux apps (agent v61; verified on the device 2026-10-07: an x86_64 glibc test program installed + launch test RUNNING, "machine=x86_64 glibc=2.41"):**
|
||||
- **x86_64 Linux apps (verified on the device 2026-10-07: an x86_64 glibc test program installed + launch test
|
||||
RUNNING, "machine=x86_64 glibc=2.41"):**
|
||||
FEX is Steam app 3127680 (`fex`, installs as `common/FEX-Emu`, ~6 MB, 21 s via the appmanifest-stub path:
|
||||
`install_proton` / `proton_status` with `kind: linux_x86`). Its toolmanifest commandline is
|
||||
`/fex-compat-tool %verb% --`, **no** require_tool_appid: it does not use the Steam Linux Runtime. fex-compat-tool
|
||||
`install_proton` / `proton_status` with `kind: linux_x86`). FEX's toolmanifest commandline is
|
||||
`/fex-compat-tool %verb% --`, **no** require_tool_appid: FEX does not use the Steam Linux Runtime. fex-compat-tool
|
||||
(Python) runs `<FEX-Emu>/usr/bin/FEX` with RootFS `/usr/share/guestos/fex-mesa` (part of the SteamOS image: an
|
||||
x86 Arch-style root with glibc 2.41, Mesa, graphics_provider.json for x86_64 + i386), emulates x86_64 and i386
|
||||
(emulator.json), honors `STEAM_FEX_TSOENABLED`, `STEAM_FEX_MULTIBLOCK`, `STEAM_COMPAT_FEX_CONFIG`, sets
|
||||
@@ -250,8 +320,12 @@ Lua error text. `Unknown Platform` also appears after this crash is fixed, so it
|
||||
`XR_API_LAYER_PATH` (it keeps `XR_ENABLE_API_LAYERS`), so the agent registers the layer as an explicit layer in
|
||||
`~/.local/share/openxr/1/api_layers/explicit.d/` (home is shared into the container) with an absolute library path.
|
||||
- Oculus Store builds that delay-load `LibOVRPlatform64_1.dll` (Platform SDK, for example Lies Beneath) crash with
|
||||
`0xc06d007e` (delay-load module not found): that DLL comes with the Oculus app, which the Frame doesn't have. (No Oculus runtime DLLs or
|
||||
registry keys are needed: Revive's LibOVRRT hook works once OpenXR does.)
|
||||
`0xc06d007e` (delay-load module not found): that DLL comes with the Oculus app, which the Frame doesn't have.
|
||||
- Revive under Proton arm64: Revive's Detours hooks (LoadLibrary, WinVerifyTrust) don't take effect because Wine's
|
||||
kernelbase is ARM64EC code. `pcvr.libovr_redirect` puts Revive's runtime where the game looks for `LibOVRRT*_1.dll`
|
||||
(gets past `-3001`), but games that check the runtime's Oculus signature then stop at `-3021`
|
||||
(`ovrError_LibSignCheck`), which FramePort doesn't bypass. So LibOVR games run only on the PC; OpenVR/OpenXR builds
|
||||
(for example Rick and Morty, Vader Immortal) run directly under Proton. DEVICE_NOTES "Revive under Proton arm64".
|
||||
- Unreal's Oculus plugin (and LibOVR's `ovr_Detect`) first checks for the Windows event `OculusHMDConnected` (created
|
||||
by the Oculus service on a PC) and silently skips VR without it. Revive hooks `OpenEventW` for that — Lies Beneath
|
||||
got as far as OVRPlugin, so the hook evidently works there — but it depends on Detours patching Wine's kernelbase,
|
||||
@@ -263,18 +337,20 @@ Lua error text. `Unknown Platform` also appears after this crash is fixed, so it
|
||||
- Non-Steam shortcut: binary `userdata/<id>/config/shortcuts.vdf`; appid = crc32(exe+title) | 0x80000000;
|
||||
artwork in `config/grid/<appid>p.*` (portrait), `<appid>.*` (landscape), `<appid>_hero.*`, `<appid>_logo.*`.
|
||||
KeyValues names are case-insensitive: Steam may save `Shortcuts` / `AppName` after a devkit registration
|
||||
(GitHub #263). Agent v86 reads and updates keys case-insensitively, keeping Steam's spelling and existing appids.
|
||||
(GitHub #263). The agent reads and updates keys case-insensitively, keeping Steam's spelling and existing appids.
|
||||
Steam reads the file only at start → stop steam.service, edit, start (FramePort does it once per batch).
|
||||
- Processes started from Steam (Konsole, SSH sessions?) share steam.service's cgroup: use `systemd-run --user`.
|
||||
- SSH: `sshd` must be enabled (`sudo systemctl enable --now sshd`), which needs a user password (`passwd`).
|
||||
- Processes started from Steam (Konsole, and SSH sessions started from it) share steam.service's cgroup and end with
|
||||
Steam: run such work through `systemd-run --user`.
|
||||
- SSH: Developer Mode enables `sshd`; FramePort logs in with its own key (the setup adds it), so no user password is
|
||||
needed.
|
||||
- mDNS: avahi-daemon runs by default; hostname `frame` → `frame.local`.
|
||||
- Desktop Mode menu entries (GitHub #84, agent v63): Linux apps get `~/.local/share/applications/frameport-<slug>.desktop`
|
||||
- Desktop Mode menu entries (GitHub #84): Linux apps get `~/.local/share/applications/frameport-<slug>.desktop`
|
||||
(+ an executable copy in `~/Desktop` when that folder exists; Plasma starts executable `.desktop` files there
|
||||
without a trust prompt), `Exec=env FRAMEPORT_DESKTOP=1 "<anchor>/launch.sh"`, marked `X-FramePort-Package=<pkg>`.
|
||||
Plasma's launcher exits right after starting the program, so with `FRAMEPORT_DESKTOP=1` the Linux launcher skips
|
||||
its "Steam parent gone → end the app" watchdog and keeps the desktop's DISPLAY/WAYLAND_DISPLAY instead of taking
|
||||
gamescope's from Steam. Not yet tried from the Frame's Desktop Mode.
|
||||
- Linux apps' own icons (GitHub #99, agent v64): finalize_linux (and every refresh of the menu entries) copies the
|
||||
- Linux apps' own icons (GitHub #99): finalize_linux (and every refresh of the menu entries) copies the
|
||||
app's icon to `<anchor>/artwork/app-icon.{png,svg}`: an AppImage's `squashfs-root/.DirIcon` (usually a symlink),
|
||||
else the `Icon=` of its top-level `.desktop` file (a folder app: the first `.desktop` within 3 levels) looked up
|
||||
next to it, in `(usr/)share/icons/hicolor/*/apps/` and `(usr/)share/pixmaps/`; the biggest PNG ≥128 px, else an
|
||||
@@ -316,7 +392,7 @@ over Quest games.
|
||||
a utility client, live; the path is kept in `~/.config/openvr/config/appconfig.json` `manifest_paths`.
|
||||
`SetApplicationAutoLaunch` is accepted (GetApplicationAutoLaunch → true; vrserver has
|
||||
`CAppInfoManager::StartAutolaunchOverlays`) but on the dev Frame it never reached `steamvr.vrsettings` (no
|
||||
autolaunch entry for `temporalreality.overlay` there, appconfig.json holds only manifest_paths) and the owner's
|
||||
autolaunch entry for `temporalreality.overlay` there, appconfig.json holds only manifest_paths) and the
|
||||
watch didn't start by itself, so FramePort doesn't rely on it (see "Autostart" below). The linuxarm64 vrserver only reads
|
||||
**`binary_path_linux_arm`**: a manifest with `binary_path_linux` alone is skipped ("must specify binary_path for
|
||||
launch_type binary. Skipping"; Steam's own steamapps.vrmanifest entries are skipped the same way), so an app's
|
||||
@@ -324,12 +400,12 @@ over Quest games.
|
||||
`LaunchApplication(key)` then starts the binary (Temporal Reality's launch.sh: process up, overlays created).
|
||||
- A Steam shortcut of an overlay app and a game shortcut run at the same time (Temporal Reality, then 4XVR, both
|
||||
through `steam://rungameid`: both "Game process added", both kept running).
|
||||
- FramePort (agent v75): `register_vr_overlay` writes `<anchor>/frameport-overlay.vrmanifest` (the app's own key,
|
||||
- FramePort: `register_vr_overlay` writes `<anchor>/frameport-overlay.vrmanifest` (the app's own key,
|
||||
name and image from its bundled manifest, binary = launch.sh in `binary_path_linux_arm` + `binary_path_linux`,
|
||||
absolute paths) and registers it; `unregister_vr_overlay`, uninstall and purge remove it; `ensure_host_fixes`
|
||||
registers ones SteamVR missed (it wasn't running). Overlay apps skip launch tests, the Linux launcher's
|
||||
Steam-parent watchdog (`FRAMEPORT_OVERLAY=1`) and don't count as a running game.
|
||||
- **Autostart (agent v76)**: the deployment's `overlay.autostart` is the source of truth (SetApplicationAutoLaunch
|
||||
- **Autostart**: the deployment's `overlay.autostart` is the source of truth (SetApplicationAutoLaunch
|
||||
is still set for SteamVR builds that honour it). While at least one installed overlay app has it on,
|
||||
`ensure_host_fixes` keeps the user service `frameport-vr-overlays.service` (`~/.config/systemd/user`, enabled for
|
||||
default.target, Restart=always) = `frameport_agent.py _vr_overlay_watch`: every 5 s it checks the vrserver it knows
|
||||
@@ -348,17 +424,18 @@ over Quest games.
|
||||
|
||||
## Video of the headset view (surveyed 2026-10-05; used by the Live view tab)
|
||||
- `steamvr-v4l2cam.service` (user unit, part of gamescope-session.target, `Restart=always`) runs SteamVR's
|
||||
`/opt/steamvr/bin/linuxarm64/v4l2cam --output=99`: it reads the compositor's "Headset View" (IVRHeadsetView) and
|
||||
`/opt/steamvr/bin/linuxarm64/v4l2cam --output=99`: v4l2cam reads the compositor's "Headset View" (IVRHeadsetView) and
|
||||
writes it to a v4l2loopback webcam named **"SteamVR"** (`/dev/video99`, 1920x1080 RGB24, advertised 30 fps, frames
|
||||
arrive at the display rate). Nothing on the Frame reads it by default; idle it costs nothing, read ~0.2 core.
|
||||
It shows what the wearer sees (SteamVR home, Steam's panels; a game's layers are expected but not yet seen in it).
|
||||
It shows what the wearer sees: SteamVR home, Steam's panels and games (4XVR's theatre with an overlay on top,
|
||||
2026-10-10).
|
||||
Black and ~1 fps (one frame per ~1.0 s) while the Frame sleeps (standby): a 30 fps stream then repeats each
|
||||
frame in bursts, which looks like a stall in a player. Its size follows SteamVR's headset view (v4l2cam has no size
|
||||
frame in bursts, which looks like a stall in a player. The picture's size follows SteamVR's headset view (v4l2cam has no size
|
||||
option), so the live view only scales down (360p/480p/720p/1080p) or sends it as is ("full").
|
||||
- Sound: `pactl get-default-sink` (`alsa_loopback_device.stereo.alsa_output.platform-sound.HiFi__Speaker__sink`) and its
|
||||
`.monitor` source carry what the Frame plays; the Frame's ffmpeg has the `pulse` input and `aac`. Timestamps: pulse
|
||||
uses the wall clock, v4l2 CLOCK_MONOTONIC → `-ts mono2abs` on the v4l2 input. Don't force
|
||||
`-use_wallclock_as_timestamps` on the pulse input: it stamped bursts of AAC packets with one time.
|
||||
`-use_wallclock_as_timestamps` on the pulse input: that option stamped bursts of AAC packets with one time.
|
||||
- Steam's own game recording / Remote Play / broadcast capture the **gamescope** PipeWire node (`CDesktopCapturePipeWire:
|
||||
... node path: gamescope`): with gamescope's `--backend openvr` that's only the flat Steam UI, not VR. Steam's arm64
|
||||
`libvideo.so` encodes with x264 (vaapi/nvenc paths don't apply). No cast/spectator feature for the Frame's own VR view
|
||||
@@ -367,9 +444,9 @@ over Quest games.
|
||||
`h264_v4l2m2m` hangs, gst `v4l2h264enc` not-negotiated). ffmpeg + libx264 works: FramePort's live view
|
||||
(`install/livestream.py`: `fps=30` before the scale, ultrafast/zerolatency, 3 threads, nice 10, fragmented MP4 on
|
||||
stdout, + AAC 128k) measured 0.34 core at 720p / 0.56 at 1080p with a quiet picture (video only); expect ~1-1.4
|
||||
cores with a busy scene. That is now only the fallback.
|
||||
cores with a busy scene. The x264 path is now only the fallback.
|
||||
- Hardware encoding (2026-10-07): the iris encoder works when driven directly through the V4L2 stateful encoder
|
||||
interface. That is FramePort's `fp_venc` (`native/venc`, spec + every measured value in `native/venc/SPEC.md`).
|
||||
interface. FramePort's `fp_venc` does that (`native/venc`, spec + every measured value in `native/venc/SPEC.md`).
|
||||
Facts:
|
||||
- Device: `/dev/video23` (`/dev/video-enc0` links to it), driver `iris_driver`, M2M multiplanar.
|
||||
- Formats: input NV12/NV21/AB24(RGBA)/QC24/Q08C; output H264/HEVC; sizes 128..8192.
|
||||
@@ -407,7 +484,7 @@ over Quest games.
|
||||
0–N s alongside), plus `frag_keyframe` so that a requested keyframe starts a fragment.
|
||||
- With sound, ffmpeg paces its inputs against each other and pulse's audio arrives later than the wall clock
|
||||
(more while something plays). The video input then counts as ahead, and ffmpeg stops reading it for up to
|
||||
0.7 s, so the writer blocks. That was the cause of 1080p dropping frames: busy 1080p replayed with sound took
|
||||
0.7 s, so the writer blocks. The blocked writer caused 1080p's dropped frames: busy 1080p replayed with sound took
|
||||
34–47 s for 20 s of video. Video alone was fine; `nice`, `-raw_packet_size` and the input queue size didn't
|
||||
matter. Shifting the video input back fixes it, but too far makes ffmpeg hold the video for interleaving and
|
||||
release it in clumps: -1 s gave output gaps of up to 550 ms, a longer and stuttering delay in the browser.
|
||||
@@ -417,7 +494,7 @@ over Quest games.
|
||||
sound about 1 s. Chrome keeps about 0.6 s of audio ahead and stalls below that, whatever the player does:
|
||||
1.1× catch-up gave 8 stalls per 30 s, no speed-up 1–2, same average lag; 50 ms fragments didn't help. So the
|
||||
player doesn't speed up while sound is on.
|
||||
- Quality (owner's headset test, 2026-10-07): 3 Mbit/s CBR at 720p36 showed heavy compression artifacts. The
|
||||
- Quality (headset test, 2026-10-07): 3 Mbit/s CBR at 720p36 showed heavy compression artifacts. The
|
||||
hardware path now uses VBR with a 1.5× peak (the encoder accepts BITRATE_MODE VBR + BITRATE_PEAK) and higher
|
||||
targets: 360p 1.5, 480p 2.5, 720p 5, 1080p 8, Full 10 Mbit/s. The x264 fallback keeps its rates.
|
||||
- The default sink is SUSPENDED while nothing plays; its monitor still delivers (silent) audio.
|
||||
@@ -428,6 +505,12 @@ over Quest games.
|
||||
and key presses (Steam's keyboard, USB/Bluetooth keyboards, `input text`) don't reach VR apps. The app folder's
|
||||
`lepton-show-flatscreen` marker shows the window (VR keeps working) and gives it focus; Steam's on-screen keyboard
|
||||
then opens for its text fields (FramePort patch `device.text_input_window`).
|
||||
- That window is a gamescope client: gamescope (`--backend openvr`, `--virtual-connector-strategy PerAppId`) shows a
|
||||
Steam app's windows as the SteamVR dashboard overlay `valve.steam.desktopgame.<appid>` with mouse input. SteamVR
|
||||
puts a game's window into theater mode while the game isn't a VR scene app yet (setting
|
||||
`dashboard.autoShowGameTheater`) and opens the dashboard on it when the OpenXR session starts: the controllers then
|
||||
drive a mouse pointer on the Android screen. FramePort turns the setting off while such a game runs (see
|
||||
DEVICE_NOTES.md "Typing window takes the controllers").
|
||||
- `/dev/uinput` is writable by the steamos user (ACL for Steam Input): a uinput virtual keyboard works like a real
|
||||
one everywhere (FramePort's "Type on Frame", agent `_keyboard`).
|
||||
- Unity text fields close without a system keyboard; see `frame.unity_text_input` in PLAYBOOK.md.
|
||||
@@ -446,7 +529,7 @@ radius pairs do **not** describe the generated shared-injection density map.
|
||||
The inspected arm64 `libVkLayer_VALVE_fdm_injection.so` has these semantics (virtual addresses):
|
||||
- `init_fdm_fove_level` at `0x1cdf4` calls the cached option reader, then
|
||||
`debug_parse_flags_option` (`0x21d74`) with table `0x27e70`: named tokens `lo` → 1,
|
||||
`med` → 2, `hi` → 3. It does not call atoi/strtol. Unset, empty, unknown and numeric
|
||||
`med` → 2, `hi` → 3. The parser does not call atoi/strtol. Unset, empty, unknown and numeric
|
||||
strings parse to zero and retain the current profile (initialized to 1 at `0x1ce74`).
|
||||
Tokens are case-sensitive and separated by non-alphanumeric/non-underscore characters;
|
||||
matching values are ORed, so combinations can alias another profile. `all` also yields 3;
|
||||
@@ -465,29 +548,50 @@ The inspected arm64 `libVkLayer_VALVE_fdm_injection.so` has these semantics (vir
|
||||
`disable_offsets` fixes the gaze position (`0x1b6c8`), and `yflip` flips the vertical
|
||||
offset (`0x1b9b8`). These three flags do not resize the profile.
|
||||
- `FDM_SWAPCHAIN_SIZE` is parsed by `atoi` (`0x1c518`), clamped to nonnegative, and
|
||||
seeds a known swapchain size (`0x1ccf8`). It is a size/matching hint, not a radius.
|
||||
The synthetic map is square with side `ceil(size/32)` (`0x19048–0x19088`); image
|
||||
association also matches this tile size (`0x16c78`). Changing render resolution changes
|
||||
the pixel extent, tile quantization and potentially the matched render target/map. This can
|
||||
change the visible diagnostic area without making the ignored radius exports effective;
|
||||
a matched device capture is needed to establish which path a particular game used.
|
||||
seeds a known swapchain size (`0x1ccf8`). The value is a size/matching hint, not a radius.
|
||||
The synthetic map (Zink/GLES dynamic-rendering path, `CmdBeginRendering`) is square with side
|
||||
`ceil(size/32)`, size = the render area width (`0x19048–0x19088`); image association also matches
|
||||
this tile size (`0x16c78`).
|
||||
- Where the sharp area lands (device-checked 2026-10-24 with AC Nexus, GitHub #394; details in
|
||||
DEVICE_NOTES.md "Foveation: sharp area centre and size"): for Vulkan games the layer's
|
||||
`xrCreateSwapchain` creates a map of `ceil(w/32) x ceil(h/32)` texels per eye swapchain (one array
|
||||
layer per eye) and fills the preset rectangles in map-normalized coordinates, so the sharp area is
|
||||
centred on the **whole swapchain** (all presets centre at about 0.51, 0.51). The layer knows each view's
|
||||
imageRect (`xrEndFrame` stores them) but doesn't use it for the map. Eye-tracked offsets
|
||||
(`calc_offset`, `0x1b848`) are `gaze NDC x 0.5 x render-area size`, rounded to the offset
|
||||
granularity (8), measured from that same swapchain centre; `disable_offsets` (Fixed) also leaves
|
||||
every image without `VK_IMAGE_CREATE_FRAGMENT_DENSITY_MAP_OFFSET_BIT_EXT` (added at `0x16afc`
|
||||
only when offsets are on). A game that submits a smaller or shifted rect (dynamic resolution,
|
||||
asymmetric eyes: AC Nexus rect=0,0 / 287,0 1726x1728 in 2016x1728, Asgard's Wrath 2 0,0 / 347,0
|
||||
2237x2256 in 2592x2256) gets the sharp area off its view's centre, and the eye's optical axis
|
||||
(asymmetric FOV, for example -1.03/+0.88 rad) isn't the rect centre either. FramePort's
|
||||
`VK_LAYER_FRAMEPORT_fdm_centre` (native/foveation/centre.h, below Valve's layer) rewrites the
|
||||
per-view offsets so the area sits on each eye.
|
||||
- Turnip (freedreno) applies a density map per GMEM **bin**: it reads the map on the CPU at the
|
||||
bin's centre (`tu_calc_frag_area`, texel size `2^ceil(log2(fb/map))` >= 32 px) and renders the
|
||||
whole bin at that density. The visible sharp area is therefore the set of bins whose centre falls
|
||||
in Valve's rectangle: the area's size snaps to the bin grid (bins are ~150-200 px here and get relatively
|
||||
smaller as render resolution grows), so the same preset looks bigger or smaller and changes
|
||||
shape between render scales. The bin snapping is the driver's design, not a setting; FramePort can't
|
||||
pick the bins.
|
||||
|
||||
Per-game library preferences now store `level`. Legacy radius choices are translated on read
|
||||
(and written as `level` on Save): nearest old nominal .30 → lo, .25 → med, .20 → hi,
|
||||
with larger values clamped by nearest choice; zero or unknown/invalid values → Default.
|
||||
This preserves approximate intent, not a claim that these presets implement those radii.
|
||||
The translation preserves approximate intent, not a claim that these presets implement those radii.
|
||||
Explicit `level` wins. Obsolete radius exports are removed from managed launchers.
|
||||
|
||||
For device verification, hold game scene, render resolution and gaze constant; compare explicit
|
||||
`lo`, `med`, `hi` across fresh game starts (Default should match lo absent another runtime override).
|
||||
First use Fixed for a stable comparison, then Eye-tracked to check gaze following. Enable
|
||||
**Darken coarse fragments (test)** and adapter `snapshot=N` to capture submitted eye images headlessly
|
||||
every N seconds (GLES only, left eye at quarter size); compare the undarkened center and peripheral bands. Existing adapter
|
||||
every N seconds (GLES: left eye at quarter size; Vulkan: both eyes at half size, the whole swapchain layer, with
|
||||
imageRect and FOV in the log); compare the undarkened center and peripheral bands. Existing adapter
|
||||
builds need diagnostic support installed. Turn the test and snapshots off before benchmarking.
|
||||
Agent `FOVEATION_ENV_KEYS` must include `"FOVE_LEVEL"` for launcher updates via `set_settings`.
|
||||
|
||||
`device.foveation` enables Valve's shared Vulkan injection and suppresses competing native foveation through
|
||||
`foveation_fix=1`. Game settings stores mode, profile and test selection as a per-game library preference, outside shared recipes. This remains
|
||||
`foveation_fix=1`. Game settings stores mode, profile and test selection as a per-game library preference, outside shared recipes. Foveation remains
|
||||
experimental: complete images were confirmed in RE4, Batman, Deadpool, The Climb 2 and Asgard's Wrath 2,
|
||||
not all Quest games or renderers. RE4 and Batman checks include title/calibration scenes; these are not
|
||||
equivalent to extended gameplay coverage. Asgard gameplay was visually confirmed, without an on/off benchmark.
|
||||
@@ -495,7 +599,7 @@ Batman additionally receives the verified ordinary shared-allocation patch, guar
|
||||
OVRPlugin hash; unknown Batman versions fail explicitly rather than receiving unchecked binary offsets.
|
||||
|
||||
The Climb 2 uses a private `libvrapi_foveation.so` bridge, installed only into that game's APK after the
|
||||
standard bridge patch. It retains images during held-focus visibility dips, separates mono overlay output
|
||||
standard bridge patch. The private bridge retains images during held-focus visibility dips, separates mono overlay output
|
||||
storage from stereo density-map storage, and rate-limits its view-cache writes. Its process-local Valve layer
|
||||
correction supplies both eye offsets and avoids the invalid subpass mask-table reads seen during Alps loading;
|
||||
`yflip` corrects vertical gaze direction. The renderer SHA-256 and 1.0 resolution scale are checked before
|
||||
@@ -505,7 +609,7 @@ the reason. No system library is changed. A SteamOS update requires revalidation
|
||||
Both-eye gaze following was confirmed in Climb's darkening diagnostic, but long-session stability remains
|
||||
experimental. Off disables injection through launcher exports; the private APK compatibility support remains installed.
|
||||
|
||||
**Darken coarse fragments (test)** enables the dormant Vulkan shader diagnostic. It dims RGB where the
|
||||
**Darken coarse fragments (test)** enables the dormant Vulkan shader diagnostic. The diagnostic dims RGB where the
|
||||
actual `FragSizeEXT` footprint exceeds one pixel; alpha is unchanged. Deadpool's shaded regions were confirmed
|
||||
to follow gaze in the headset. Unsupported shader output interfaces are skipped, and multiple passes may
|
||||
compound darkening, so this is a diagnostic rather than a precise composited mask. Do not benchmark with it on.
|
||||
@@ -514,7 +618,26 @@ in Climb the same library also carries the compatibility correction.
|
||||
|
||||
High-resolution Deadpool samples crossed from about 45 to 90 FPS with injection enabled, but temperatures and
|
||||
power rose as it rendered twice as many frames. Foveation does not guarantee lower temperature at uncapped FPS.
|
||||
Preset export/migration has configuration coverage; these preset comparisons have not been device-tested here.
|
||||
Preset export/migration has configuration coverage. Headless device check of the presets (2026-10-24, AC Nexus,
|
||||
Fixed, darkening test + FrameBridge `snapshot=N`, which also reads Vulkan eye images back): DEVICE_NOTES.md
|
||||
"Foveation: sharp area centre and size".
|
||||
|
||||
### User preferences (discussion #365)
|
||||
|
||||
Game settings → Picture provides Default / Eye-tracked / Fixed / Off, a runtime profile, and an advanced diagnostic switch.
|
||||
`set_settings` atomically rewrites just the managed launcher exports and records them in deployment.json;
|
||||
no APK rebuild, reinstall or live device intervention is needed. Changes apply at the next start. Default restores
|
||||
recipe/catalog launcher defaults; explicit preferences win. One-time library migration extracts old recipe choices
|
||||
and leaves internal APK compatibility support. Catalog defaults (including Climb 2 and Batman) still apply to new
|
||||
entries. Shared working configs omit `device.foveation` entirely.
|
||||
|
||||
APK-side: adapter builds automatically prepare Batman's hash-verified allocation fix and Climb 2's
|
||||
renderer-guarded private bridge for verified builds, independently of the runtime choice. Unknown versions skip
|
||||
automatic compatibility edits; explicit catalog requests still fail validation rather than guessing offsets. FrameBridge
|
||||
builds include the dormant diagnostic layer (activated only by `FRAMEPORT_FOVEATION_TEST=1`), so future test toggles
|
||||
need only a launcher update. Existing builds need one normal update to acquire that support. Render resolution
|
||||
(supersampling) changes the game's render resolution: higher is sharper but slower. Climb 2 foveation still requires
|
||||
1.0 scale.
|
||||
|
||||
## Monitoring sources (probed 2026-10-07, SteamOS 0.4.3, kernel 6.18; used by the Monitor tab, agent `_monitor`)
|
||||
|
||||
@@ -523,7 +646,7 @@ All readable by the steamos user without root; the agent reads them directly (no
|
||||
| Metric | Source | Notes |
|
||||
|---|---|---|
|
||||
| CPU | `/proc/stat` deltas; `cpufreq/policy{0,2,5,7}` | 8 cores in 4 clusters: 0-1 / 2-4 / 5-6 / 7 (max 2.27 / 3.15 / 2.96 / 3.05 GHz) |
|
||||
| GPU busy | Sum of `drm-engine-gpu` (ns) deltas in `/proc/<pid>/fdinfo/<fd>` over every render-node fd (msm DRM) | Gives GPU % per process too. A process can hold several render fds: add them all. Lepton games see the node as `/dev/kgsl-3d0` (bind mount of `/dev/dri/renderD128`). `drm-total-memory` reads 0. msm can report garbage engine time (SteamVR's vrcompositor: near 2^64, rising ~1000x faster than real time; idle Frame at 100 %, 2026-10-18): a per-fd delta longer than the interval falls back to `drm-cycles-gpu` / GPU clock (agent v102 `gpu_busy_ns`) |
|
||||
| GPU busy | Sum of `drm-engine-gpu` (ns) deltas in `/proc/<pid>/fdinfo/<fd>` over every render-node fd (msm DRM) | Gives GPU % per process too. A process can hold several render fds: add them all. Lepton games see the node as `/dev/kgsl-3d0` (bind mount of `/dev/dri/renderD128`). `drm-total-memory` reads 0. msm can report garbage engine time (SteamVR's vrcompositor: near 2^64, rising ~1000x faster than real time; idle Frame at 100 %, 2026-10-18): a per-fd delta longer than the interval falls back to `drm-cycles-gpu` / GPU clock (agent `gpu_busy_ns`) |
|
||||
| GPU clock | `/sys/class/devfreq/3d00000.gpu/{cur,max}_freq` | 231–903 MHz in 12 steps |
|
||||
| Memory / pressure | `/proc/meminfo`; `/proc/pressure/{cpu,memory,io}` (`some avg10`) | 16 GB RAM, 8 GB swap |
|
||||
| Temperatures | `/sys/class/thermal/thermal_zone*/{type,temp}` | 48 zones: `cpu*`/`cpuss*`, `gpuss-*`, `ddr`, `nsph*` (NPU), `pm8550*`/`pm8010*` (power ICs), `modem*`, `camera*`, `video`, `max1720x_bat*` |
|
||||
@@ -539,26 +662,9 @@ game processes without one), temperatures every 2 s, the CPU-cluster power rails
|
||||
2-10 s, the stream measured ~10-13 ms CPU per 1 s tick on the device (idle clocks; idle or with 4XVR running) = about
|
||||
1 % of one core, ~0.15 % of the whole CPU. The I2C sensors (power monitors, battery gauge) are the slowest reads.
|
||||
|
||||
### Foveation user preferences (discussion #365)
|
||||
|
||||
Game settings → Picture provides Default / Eye-tracked / Fixed / Off, a runtime profile, and an advanced diagnostic switch.
|
||||
`set_settings` (agent 98) atomically rewrites just the managed launcher exports and records them in deployment.json;
|
||||
no APK rebuild, reinstall or live device intervention is needed. Changes apply at the next start. Default restores
|
||||
recipe/catalog launcher defaults; explicit preferences win. One-time library migration extracts old recipe choices
|
||||
and leaves internal APK compatibility support. Catalog defaults (including Climb 2 and Batman) still apply to new
|
||||
entries. Shared working configs omit `device.foveation` entirely.
|
||||
|
||||
APK-side: adapter builds automatically prepare Batman's hash-verified allocation fix and Climb 2's
|
||||
renderer-guarded private bridge for verified builds, independently of the runtime choice. Unknown versions skip
|
||||
automatic compatibility edits; explicit catalog requests still fail validation rather than guessing offsets. FrameBridge
|
||||
builds include the dormant diagnostic layer (activated only by `FRAMEPORT_FOVEATION_TEST=1`), so future test toggles
|
||||
need only a launcher update. Existing builds need one normal update to acquire that support. Render resolution
|
||||
(supersampling) changes the game's render resolution: higher is sharper but slower. Climb 2 foveation still requires
|
||||
1.0 scale.
|
||||
|
||||
## Steam library scheduling
|
||||
|
||||
Agent v99 holds automatic shortcut restarts while the local app's
|
||||
The agent holds automatic shortcut restarts while the local app's
|
||||
`~/.local/share/frameport-app/gui.alive` heartbeat is under ten seconds old, as well as while a game or Desktop Mode
|
||||
is open. Closed/crashed GUIs expire; the existing twelve-hour hold limit still applies.
|
||||
|
||||
@@ -578,10 +684,10 @@ mode isn't `later` and nothing is pending, and by purge; an otherwise empty `ste
|
||||
SteamOS 0.3.0 facts (checked 2026-10-16): Steam is the static user unit `/usr/lib/systemd/user/steam.service`
|
||||
(Type=simple, Restart=always, TimeoutStartSec 90 s, its own `-` ExecStartPre lines; `extra_args.conf` drop-ins are
|
||||
Valve's documented way to extend it), systemd 257; no user drop-ins exist by default.
|
||||
This automatic application targets SteamOS's `steam.service`; if Steam is started outside that service, use
|
||||
The automatic reapplication targets SteamOS's `steam.service`; if Steam is started outside that service, use
|
||||
FramePort's Restart Steam now action to apply the journal safely.
|
||||
|
||||
Off skips new shortcuts (existing entries remain). Play in Off, and Play for a pending Later entry, starts launch.sh
|
||||
in a detached user systemd unit, just as launch tests do, without adding a devkit entry. Steam/SteamVR must already
|
||||
be running. This loses Steam's per-game overlay/controller setup and Exit game integration; use FramePort Monitor
|
||||
to stop the game. This direct path has unit coverage; headset input/visual behaviour needs device validation.
|
||||
be running. Direct starts lose Steam's per-game overlay/controller setup and Exit game integration; use FramePort Monitor
|
||||
to stop the game. The direct path has unit coverage; headset input/visual behaviour needs device validation.
|
||||
@@ -5,7 +5,7 @@ setup changes on the Steam Frame, how to undo it and what your network needs.
|
||||
|
||||
## Changes on the Frame
|
||||
|
||||
The setup runs [`bootstrap/bootstrap.sh`](../bootstrap/bootstrap.sh), which FramePort sends from your PC. It changes:
|
||||
The setup runs [`bootstrap/bootstrap.sh`](../bootstrap/bootstrap.sh), which FramePort sends from your PC. The script changes:
|
||||
|
||||
| Change | Where | How to undo |
|
||||
|---|---|---|
|
||||
@@ -14,14 +14,14 @@ The setup runs [`bootstrap/bootstrap.sh`](../bootstrap/bootstrap.sh), which Fram
|
||||
| Stops game starts from failing after about 200 launches (a limit in podman, the tool Lepton runs games with). | `[containers]` / `keyring = false` in `~/.config/containers/containers.conf`. | Remove those lines. |
|
||||
| Asks Steam to install **Lepton** (Valve's Android runtime) if it's missing. You confirm it in Steam. | Steam library | Uninstall it in Steam. |
|
||||
|
||||
Valve's Developer Mode helper is the same one the Settings switch uses. It turns on remote login (SSH), the service
|
||||
Valve's Developer Mode helper is the same one the Settings switch uses. The helper turns on remote login (SSH), the service
|
||||
that makes the Frame findable on your network, remote desktop, debugging and crash dumps; turning Developer Mode off
|
||||
turns them all off again.
|
||||
|
||||
The script runs as your user: no root, no `sudo`, no password. Nothing else on the system changes. It also leaves two
|
||||
files: `~/.cache/frameport-setup.sh` (the part that finishes on its own) and its log.
|
||||
The script runs as your user: no root, no `sudo`, no password. Nothing else on the system changes. The script also leaves
|
||||
two files: `~/.cache/frameport-setup.sh` (the part that finishes on its own) and its log.
|
||||
|
||||
If Developer Mode can't be turned on automatically, the script asks you to turn it on in Settings → System →
|
||||
If Developer Mode can't be turned on automatically, the script asks you to turn Developer Mode on in Settings → System →
|
||||
**Enable Developer Mode** and run the setup line again.
|
||||
|
||||
**Later, FramePort adds** (as your user):
|
||||
@@ -29,15 +29,19 @@ If Developer Mode can't be turned on automatically, the script asks you to turn
|
||||
- the games, each with a launcher and its data in `~/Applications/quest-frame/<package>/`;
|
||||
- their Steam library entries and artwork (`shortcuts.vdf` + `config/grid/`);
|
||||
- for PC VR games: an OpenXR layer (`~/.local/share/openxr/1/api_layers/explicit.d/XR_APILAYER_FRAMEPORT_timefix.json`)
|
||||
and Valve's ARM64 Proton with its Steam Linux Runtime (Steam downloads them and restarts once).
|
||||
and Valve's ARM64 Proton with its Steam Linux Runtime (Steam downloads them and restarts once); for x86_64 Linux
|
||||
apps: Valve's FEX;
|
||||
- for Linux apps: entries in Desktop Mode's app menu (`~/.local/share/applications/frameport-*.desktop`);
|
||||
- only when used: a user service that starts SteamVR overlay apps (`frameport-vr-overlays.service`) and, with
|
||||
**Steam library: Later**, a drop-in for Steam's service (`~/.config/systemd/user/steam.service.d/`).
|
||||
|
||||
**Settings → Uninstall FramePort → Also remove from the Frame** deletes the games, their Steam entries, the helper
|
||||
folder, the OpenXR layer and the setup files. Developer Mode, the login line, the podman setting, Lepton and Proton
|
||||
folder, the OpenXR layer, the menu entries, the services and the setup files. Developer Mode, the login line, the podman setting, Lepton and Proton
|
||||
stay: undo them as shown above.
|
||||
|
||||
## Network and firewalls
|
||||
|
||||
The setup is the only time the Frame connects to your PC. It downloads the setup script from FramePort on TCP port
|
||||
The setup is the only time the Frame connects to your PC. The Frame downloads the setup script from FramePort on TCP port
|
||||
8765 (8766 or 8767 if taken), only while the setup page is open and for at most 30 minutes. Everything else goes
|
||||
from your PC to the Frame.
|
||||
|
||||
@@ -51,15 +55,15 @@ How the setup line finds your PC:
|
||||
The setup line's script is [bootstrap/setup.sh](../bootstrap/setup.sh). If the setup just says "timed out", the
|
||||
setup page shows what is likely blocking it after about 45 seconds:
|
||||
|
||||
- **Windows:** allow FramePort when Windows asks. On a network Windows treats as **Public** it stays blocked; set
|
||||
- **Windows:** allow FramePort when Windows asks. On a network Windows treats as **Public** the connection stays blocked; set
|
||||
your home network to Private in Windows' network settings.
|
||||
- **macOS:** with the firewall on (System Settings → Network → Firewall), allow incoming connections for FramePort
|
||||
when asked.
|
||||
- **Linux:** firewalld: `sudo firewall-cmd --add-port=8765/tcp` (until the next restart). ufw:
|
||||
`sudo ufw allow 8765/tcp`, afterwards `sudo ufw delete allow 8765/tcp`.
|
||||
- **WSL** (FramePort's Linux version on Windows): Windows blocks connections into WSL without asking. FramePort
|
||||
adds a temporary firewall rule (one admin prompt) and removes it when setup is done or after 35 minutes. WSL must
|
||||
adds a temporary firewall rule (one admin prompt) and removes the rule when setup is done or after 35 minutes. WSL must
|
||||
share Windows' network: `networkingMode=mirrored` under `[wsl2]` in `%UserProfile%\.wslconfig`, then
|
||||
`wsl --shutdown`.
|
||||
- Or skip the setup line and use **Pair new host** (see [Install and first steps](INSTALL.md#connecting-the-steam-frame)):
|
||||
it needs no connection into your PC.
|
||||
**Pair new host** needs no connection into your PC.
|
||||
@@ -13,10 +13,12 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| Arkio | Quest | ✅ Works | |
|
||||
| Asgard's Wrath 2 | Quest | ✅ Works | |
|
||||
| Assassin's Creed Nexus | Quest | ✅ Works | |
|
||||
| Astro VR Host | Quest | ✅ Works | |
|
||||
| Attack on Titan VR: Unbreakable | Quest | ✅ Works | |
|
||||
| Audica | Quest | ✅ Works | |
|
||||
| AugmentedEmpire | Quest | ✅ Works | |
|
||||
| BAM | Quest | ✅ Works | |
|
||||
| Barbaria | Quest | ✅ Works | |
|
||||
| BARTENDER VR SIMULATOR | Quest | ✅ Works | |
|
||||
| Batman: Arkham Shadow | Quest | ✅ Works | |
|
||||
| BattleGlide | Quest | ✅ Works | |
|
||||
@@ -44,17 +46,21 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| cubism | Quest | ✅ Works | |
|
||||
| cubism | Quest | ✅ Works | |
|
||||
| Dance Central | Quest | ✅ Works | |
|
||||
| Darksword | Quest | ✅ Works | |
|
||||
| Deep Cuts | Quest | ✅ Works | |
|
||||
| Deisim | Quest | ✅ Works | |
|
||||
| Demeo | Quest | ✅ Works | |
|
||||
| Demeter | Quest | ✅ Works | |
|
||||
| Dinosaur Island | Quest | ✅ Works | |
|
||||
| Doom3Quest | Quest | ✅ Works | |
|
||||
| Down the Rabbit Hole | Quest | ✅ Works | |
|
||||
| Drums Rock | Quest | ✅ Works | |
|
||||
| Drunkn Bar Fight | Quest | ✅ Works | |
|
||||
| Drunkn Bar Fight 2 - Early Access | Quest | ✅ Works | |
|
||||
| Eleven Table Tennis | Quest | ✅ Works | |
|
||||
| Eleven: Table Tennis VR | PC VR | ✅ Works | |
|
||||
| EndoparasiticVR | Quest | ✅ Works | |
|
||||
| Epic Roller Coasters | Quest | ✅ Works | |
|
||||
| Espire 2 | Quest | ✅ Works | |
|
||||
| Eye of the Temple | Quest | ✅ Works | |
|
||||
| FrenzyVR | Quest | ✅ Works | |
|
||||
@@ -67,6 +73,7 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| Gravity Lab | Quest | ✅ Works | |
|
||||
| Gunjack | Quest | ✅ Works | |
|
||||
| H.U.N.T | Quest | ✅ Works | |
|
||||
| Hitstream | Quest | ✅ Works | |
|
||||
| I Am Cat | Quest | ✅ Works | |
|
||||
| I Am Monkey | Quest | ✅ Works | |
|
||||
| I Expect You To Die 2 | Quest | ✅ Works | |
|
||||
@@ -81,7 +88,9 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| KillerFrequency | Quest | ✅ Works | |
|
||||
| Ladder | Quest | ✅ Works | |
|
||||
| Lambda1VR | Quest | ✅ Works | |
|
||||
| Laser Dance | Quest | ✅ Works | |
|
||||
| LEGO® Bricktales | Quest | ✅ Works | |
|
||||
| Little Nightmares VR | Quest | ✅ Works | |
|
||||
| Lost Recipes | Quest | ✅ Works | |
|
||||
| Lucky's Tale | Quest | ✅ Works | |
|
||||
| Maestro | Quest | ✅ Works | |
|
||||
@@ -94,6 +103,7 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| Mobile Suit Gundam: Silver Phantom | Quest | ✅ Works | |
|
||||
| Moss | Quest | ✅ Works | |
|
||||
| Moss: Book II | Quest | ✅ Works | |
|
||||
| Movie Deck for Plex | Quest | ✅ Works | |
|
||||
| Munkr Funkr (The Legacy Days) | Quest | ✅ Works | |
|
||||
| Nano | Quest | ✅ Works | |
|
||||
| Neko Atsume Purrfect | Quest | ✅ Works | |
|
||||
@@ -103,12 +113,14 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| Ocean Rift | Quest | ✅ Works | |
|
||||
| Operation Wolf Returns: First Mission VR | Quest | ✅ Works | |
|
||||
| OrbusVR | Quest | ✅ Works | |
|
||||
| Orcs Must Die: By The Blade | Quest | ✅ Works | |
|
||||
| palazzo_santacruz | Quest | ✅ Works | |
|
||||
| Path of the Warrior | Quest | ✅ Works | |
|
||||
| Pistol Whip | Quest | ✅ Works | |
|
||||
| Pistol Whip | PC VR | ✅ Works | |
|
||||
| Please Don't Touch Anything | Quest | ✅ Works | |
|
||||
| PowerWash Simulator VR | Quest | ✅ Works | |
|
||||
| Punch Fit | Quest | ✅ Works | |
|
||||
| QuestCraft | Quest | ✅ Works | |
|
||||
| Racket: Nx | Quest | ✅ Works | |
|
||||
| RacketPinball | Quest | ✅ Works | |
|
||||
@@ -126,10 +138,13 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| Shoot The Zombirds VR | Quest | ✅ Works | |
|
||||
| Shores of Loci | Quest | ✅ Works | |
|
||||
| SilentSlayer | Quest | ✅ Works | |
|
||||
| Simpsons Hit and Run VR | Quest | ✅ Works | |
|
||||
| Smash Drums | Quest | ✅ Works | |
|
||||
| Sniper Elite VR | Quest | ✅ Works | |
|
||||
| Sniper Elite VR: Winter Warrior | Quest | ✅ Works | |
|
||||
| Solero | Quest | ✅ Works | |
|
||||
| Space Pirate Trainer Quest | Quest | ✅ Works | |
|
||||
| SpaceControl | Quest | ✅ Works | |
|
||||
| Spectre | Quest | ✅ Works | |
|
||||
| Sports Scramble (Santa Cruz) | Quest | ✅ Works | |
|
||||
| Star Wars Pinball VR | Quest | ✅ Works | |
|
||||
@@ -146,6 +161,7 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| Taskmaster | Quest | ✅ Works | |
|
||||
| Tea For God | Quest | ✅ Works | |
|
||||
| Test Quest Controllers | Quest | ✅ Works | |
|
||||
| Test Quest MR | Quest | ✅ Works | |
|
||||
| TetrisEffect | Quest | ✅ Works | |
|
||||
| The Boys VR | Quest | ✅ Works | |
|
||||
| The Climb | Quest | ✅ Works | |
|
||||
@@ -162,6 +178,7 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| ToyTrains | Quest | ✅ Works | |
|
||||
| TRIANGLE STRATEGY | Quest | ✅ Works | |
|
||||
| Under Cover | Quest | ✅ Works | |
|
||||
| UNDERDOGS | Quest | ✅ Works | |
|
||||
| Vacation Simulator | Quest | ✅ Works | |
|
||||
| Vader Immortal: Episode I | Quest | ✅ Works | |
|
||||
| Vader Immortal: Episode II | Quest | ✅ Works | |
|
||||
@@ -183,21 +200,26 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| Does it Stack? | Quest | ⚠️ Works with issues | Mixed reality mode does not work (works on Demeo), everything else seems to be OK |
|
||||
| Dungeons of Eternity | Quest | ⚠️ Works with issues | It can not connect to servers |
|
||||
| End Space | Quest | ⚠️ Works with issues | Controls stop responding after the first mission loads. |
|
||||
| Eternal Starlight | Quest | ⚠️ Works with issues | Voice commands do not appear to work. |
|
||||
| Exploding Kittens VR | Quest | ⚠️ Works with issues | Requires online for most modes. |
|
||||
| ExploreVR | Quest | ⚠️ Works with issues | The 3D world warps and shifts with head movement (wrong perspective). |
|
||||
| FGO VR | Quest | ⚠️ Works with issues | Audio stuttering and popping sounds |
|
||||
| Fisherman | Quest | ⚠️ Works with issues | Loading screens have corruption, but everything works otherwise. |
|
||||
| GoldenEye VR | Quest | ⚠️ Works with issues | No sound; otherwise works great. |
|
||||
| HardBullet | Quest | ⚠️ Works with issues | Loading screens (a cube overlay) are missing or corrupted up to FramePort 1.2.1; cube_quads (next release) shows them as six panels, not confirmed in the hea… |
|
||||
| Homeworld: Vast Reaches | Quest | ⚠️ Works with issues | The game starts but after selecting to start a new game it seems to crash. |
|
||||
| JKXR | Quest | ⚠️ Works with issues | Tested in the early part of Jedi Outcast. |
|
||||
| Lawn Mowing Simulator VR | Quest | ⚠️ Works with issues | Left eye textures flicker only when mowing in specific areas. |
|
||||
| MacacoVR | Quest | ⚠️ Works with issues | 2d loading screens are glitchy, otherwise main gameplay is fine |
|
||||
| Majora's Mask VR | Quest | ⚠️ Works with issues | Audio on the file select screen sounds crunchy. |
|
||||
| Myst | Quest | ⚠️ Works with issues | Minor graphical glitches on some objects. |
|
||||
| Phantom: Covert Ops | Quest | ⚠️ Works with issues | DLC/store button crashes (no Meta store). |
|
||||
| Pinball FX VR | Quest | ⚠️ Works with issues | Plays; mixed reality mode not working yet. |
|
||||
| Pinball FX VR | Quest | ⚠️ Works with issues | Plays; space warp off (fewer stutters). |
|
||||
| Plezy | Quest | ⚠️ Works with issues | Some videos play with strong artifacts and wrong colours; the cause is unknown, possibly hardware video decoding. |
|
||||
| PrimedGun v2 | Quest | ⚠️ Works with issues | Vulkan via a statically linked Khronos OpenXR loader, which bypasses FrameBridge (the picture jumps). |
|
||||
| Puzzling Places | Quest | ⚠️ Works with issues | Works fine, but pointers dont show well on main menu, easy to work around. |
|
||||
| Quantum Void | Quest | ⚠️ Works with issues | no hands |
|
||||
| QuestZDoom | Quest | ⚠️ Works with issues | Needs a WAD: put it into QuestZDoom/wads in the game's storage (Files tab) and set QuestZDoom/commandline.txt to "qzdoom -iwad /sdcard/QuestZDoom/wads/<name>… |
|
||||
| Ragnarock | Quest | ⚠️ Works with issues | |
|
||||
| Real VR Fishing | Quest | ⚠️ Works with issues | Seems to be almost 100% functional with meta permissions enabled. |
|
||||
| Silhouette | Quest | ⚠️ Works with issues | Hand-tracking game; the Frame synthesizes hands from controllers, so it is janky. |
|
||||
@@ -208,6 +230,7 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| The Light Brigade | Quest | ⚠️ Works with issues | Judder in weapons |
|
||||
| The Thrill Of The Fight | Quest | ⚠️ Works with issues | Right eye doesn't draw the opponent or the referee. |
|
||||
| Time Stall | Quest | ⚠️ Works with issues | Both eyes distort during movement (unresolved). |
|
||||
| Umurangi Generation VR | Quest | ⚠️ Works with issues | The game freezes after taking a photo, while it saves it, which makes it hard to play. |
|
||||
| Vampire: The Masquerade - Justice | Quest | ⚠️ Works with issues | Game runs fine and playable, on loading there's a random sphere in the middle of the sceen. |
|
||||
| WiiCompiled VR | Quest | ⚠️ Works with issues | To add a game: in FramePort's Files tab, upload your .wcgame file to this game's storage, folder Android/data/org.wiicompiled.quest/files/WiiCompiledOpenXRVR… |
|
||||
| Breachers | Quest | ❌ Doesn't run | Online-only multiplayer that needs Meta's online services, which aren't available on the Frame. |
|
||||
@@ -215,5 +238,6 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
|
||||
| Espire 1: VR Operative (Quest Edition) | Quest | ❌ Doesn't run | Mesa GL driver crash during texture upload. |
|
||||
| GOLF+ | Quest | ❌ Doesn't run | Black screen. |
|
||||
| HITMAN 3 VR: Reloaded | Quest | ❌ Doesn't run | Vulkan driver crash (freedreno), even without Valve layers. |
|
||||
| PianoVision | Quest | ❌ Doesn't run | Starts, but needs camera hand tracking (Android games on the Frame only get hands built from the controllers) and USB MIDI from a piano, which the Frame's An… |
|
||||
| Roblox | Quest | ❌ Doesn't run | Crashes on its first VR frame on the Frame. |
|
||||
| Thief Simulator | Quest | ❌ Doesn't run | Quits at start with the game's own "installed from unknown source" message. |
|
||||
@@ -1,8 +1,8 @@
|
||||
# Evaluating patch heuristics
|
||||
|
||||
`scripts/eval_heuristics.py` compares `engine.suggest(analysis, use_catalog=False)` with the bundled
|
||||
`catalog/games/*.yaml` entries. It does not merge personal or remote catalog recipes into the benchmark.
|
||||
It supports the original dumps command, or a precomputed `scripts/heuristics_snapshot.py` snapshot:
|
||||
`catalog/games/*.yaml` entries. The script does not merge personal or remote catalog recipes into the benchmark.
|
||||
The script supports the original dumps command, or a precomputed `scripts/heuristics_snapshot.py` snapshot:
|
||||
|
||||
```sh
|
||||
.venv/bin/python scripts/eval_heuristics.py '<dumps folder>' --verbose
|
||||
@@ -24,7 +24,7 @@ can move the cutoff. Very small strata can have no holdout. Unsupported entries
|
||||
|
||||
Ground truth contains catalog choices and patch registry defaults, with no inherited heuristic adapter settings.
|
||||
Default expectations are determined per analysis, using each default patch's own detection in the same way as the
|
||||
engine's initial heuristic pass. Thus a conditional default that is not detected (e.g. LTW on a game without LTW)
|
||||
engine's initial heuristic pass. Thus a conditional default that is not detected (for example LTW on a game without LTW)
|
||||
is not expected. Explicit catalog `overport_remove` and `frame_remove` entries remove expected defaults. An absent
|
||||
expected default is represented as `remove:<patch id>`; enabled defaults do not inflate patch scores.
|
||||
|
||||
@@ -59,10 +59,10 @@ Before tuning a rule to remove an "extra", check which kind it is: a gap on an o
|
||||
catalog (fit report), not a detection error. Before adding a patch to a working entry, weigh its benefit (does it
|
||||
fix something the game hits?) against a rebuild prompt for everyone who installed it; adapter settings never need
|
||||
adding (inherited). Results 2026-10-15: strict 5/65 exact, F1 73.8%; dated 39/65, F1 88.6% (dev 90.5%, holdout
|
||||
79.5%). Remaining misses are one-game fixes found at runtime (e.g. Vader's gl_error_clear, BlazeRush's GL settings).
|
||||
79.5%). Remaining misses are one-game fixes found at runtime (for example Vader's gl_error_clear, BlazeRush's GL settings).
|
||||
|
||||
An evaluation writes JSON even when its headline has differences, then exits 1 for compatibility with the original
|
||||
script. It exits 0 when exact, or after producing a fit report.
|
||||
script. The script exits 0 when exact, or after producing a fit report.
|
||||
|
||||
## New patch fit
|
||||
|
||||
|
||||