Compare commits

..
Author SHA1 Message Date
spoopyghosty0 8a2029c079 Docs: update screenshots and videos 2026-10-11 03:38:33 +00:00
spoopyghosty0 9016982394 Showcase: the pretend setup server has open_asks/prune like the real one (the install tutorial failed to record); test that it offers everything the GUI uses 2026-10-10 23:25:14 -04:00
spoopyghosty0 cad2cd366c BlazeRush: min_app 1.0.0 (ships with 1.0) 2026-10-10 23:24:03 -04:00
spoopyghosty0 a5820e68d7 BlazeRush works (GitHub #57, from Klownicle's guide; headset check pending): frame.blazerush (1.0.349 only, fingerprint + words checked: avatar wait NOP, avatar renderer RET, shader cache cache/br_shader_v1), device.config_sync (agent v78: saved resolution in user_config.xml from the runtime eye size x scale at finalize, settings changes and before each start), catalog recipe with the GL shim fixes and scale 1.7, triage vrapi-fov-before-tracking / gl-integer-attribs 2026-10-10 23:18:42 -04:00
spoopyghosty0 aff939a6d8 BlazeRush native fixes (GitHub #57, Klownicle's guide): GL shim gl_int_attribs (integer vertex inputs fed as floats re-set with glVertexAttribIPointer per draw, per-context reflection dropped on link/binary/delete) and gl_highp_markers (matching vertex shaders compute in highp); VrApi bridge answers FOV properties 7/8 before any session with the live FOV saved by an earlier session (also located before tracking); FrameBridge logs the runtime's unscaled eye size 2026-10-10 23:18:41 -04:00
spoopyghosty0 5e725fbd08 Issue chooser: links to Discussions (questions, ideas) and the docs 2026-10-10 23:16:30 -04:00
spoopyghosty0 1cb9e6f65b docs/brand: GitHub social preview image (1280 x 640) 2026-10-10 23:11:13 -04:00
spoopyghosty0 7a843962e8 Catalog: Time Crisis II VR (test build) works (shared working config)
Closes #173
2026-10-10 23:09:47 -04:00
spoopyghosty0 51220a4942 docs/brand: square FramePort icon (1024 px PNG + SVG) for project pages and listings 2026-10-10 23:00:24 -04:00
spoopyghosty0 981daa3c2d Unreal: start without the OBB check (frame.unreal_skip_obb_check, GitHub #159)
Epic's DownloaderActivity checks the OBB's name and size and, with the manifest's
bVerifyOBBOnStartUp=true, CRC-checks the whole file; its screens are invisible in Lepton,
so games like Contractors seem to hang at 'Displayed .../.DownloaderActivity'. The patch
flips that manifest boolean (axml.set_meta_data_bool, one 4-byte value); analysis
unreal_verify_obb (ANALYSIS_VERSION 11) suggests it. Triage unreal-obb-check-stuck (fatal)
fails such launch tests; triage keeps system_server's lines about the game's own activities.
2026-10-10 22:49:21 -04:00
spoopyghosty0 9781c2bb6b Showcase: sharper README teaser (1280 px, 20 fps, WebP drawing preset from quality 95, up to 8 MB) and tour/install videos (CRF from 17, up to 20 MB); the teaser showed compression artifacts 2026-10-10 22:32:47 -04:00
spoopyghosty0 a7c349835c Install looks for a game's data files again when it was added without them (OBBs copied next to the APK afterwards; GitHub #156) 2026-10-10 22:29:52 -04:00
spoopyghosty0 9a48eac3e6 Catalog: TRIANGLE STRATEGY works (shared working config)
Closes #171
Closes #85
2026-10-10 22:18:23 -04:00
spoopyghosty0 9082c684ed test_vr_overlay: expect systemd quotes only where the path needs them (CI's checkout path has no spaces; the v1.0.0 build failed on it) 2026-10-10 22:17:22 -04:00
spoopyghosty0 84235fbc9f FramePort 1.0.0 2026-10-10 22:04:58 -04:00
spoopyghosty0 8accd6221a Install links: the homepage's example button is a demo link (frameport.app/demo/cool-game.json, published on the site). FramePort recognises every form of it without any network request and answers with a demo install question for "Cool Game"; Install tosses its cover out of the logo's portal (easter eggs off: "Nice, it works!"), nothing is downloaded or added. The landing page marks it as a demo link; open-link says it's the demo and stops 2026-10-10 21:47:57 -04:00
spoopyghosty0 c37eebb59f Site: link the rift-on-frame branch from How it works 2026-10-10 21:47:04 -04:00
spoopyghosty0 5b0bd2589b PC Steam shortcuts: Revive games (all started by ReviveInjector.exe) are told apart by the game in the launch options; a second Revive game overwrote the first one's shortcut 2026-10-10 21:44:47 -04:00
spoopyghosty0 231e5e50cd Merge branch 'worktree-agent-ad6131f295772bee1' 2026-10-10 21:39:29 -04:00
spoopyghosty0 423b2e1c3e Rename… for every library entry: the name is kept from then on and reaches Steam without a new shortcut (agent v77 rename)
Game menu (Library right-click, game page "Name and artwork") and CLI 'frameport rename <pkg> "<name>"|--reset'.
The title is locked (title_locked + auto_title for the reset); rescans, Rift store matches and install links only
update auto_title; a chosen name gets no Quest/Rift twin suffix. Installed copies: the Frame's deployment.json title
changes and the shortcut is rewritten with the same appid (matched by Exe), one Steam restart; PC VR installs on this
PC keep their appid too (reinstalls and library updates now record the shortcut's real appid). A Frame that isn't
connected shows "Update name on Frame" on the game page until then or the next install.
2026-10-10 21:38:56 -04:00
spoopyghosty0 b6c5f7c18e Scanning: SideQuest backup folders and AXRB downloads import with their OBBs. AXRB's own PC-patched copies (patched/, *-axrb.apk, no OBBs) are skipped and a rescan replaces games added from them (they started and stopped at once: Steam showed Resume); AXRB's other store files go with the OBBs; an APK inside another game's data folder isn't a game; backups are named after the package, not the backup time. FAQ explains both layouts 2026-10-10 21:29:22 -04:00
spoopyghosty0 74320e607f Site: How it works explains PC VR games on the Frame and on the PC (automatic Revive for Rift games), plus two experimental paths (Quest games in PC VR, Rift games on the Frame alone), closed by default. Install video: download from frameport.app 2026-10-10 21:13:29 -04:00
spoopyghosty0 d87e3b3b5f Merge branch 'worktree-agent-ad5dca676e297ca5d' 2026-10-10 20:59:46 -04:00
spoopyghosty0 c4153be8bc Docs: FramePort's overlay autostart service (agent v76), dev Frame result 2026-10-10 20:58:57 -04:00
spoopyghosty0 b5a16a2680 Overlay apps start with SteamVR through FramePort's own service (agent v76)
SteamVR's auto-launch flag (SetApplicationAutoLaunch) didn't persist on the dev
Frame and a registered watch didn't start by itself. The deployment's overlay
autostart flag is now the source of truth: ensure_host_fixes installs the user
service frameport-vr-overlays.service while an installed overlay app has
autostart on (removed when none has, on uninstall/purge, or with the kill switch
~/.local/share/frameport/vr-overlays.disabled / FRAMEPORT_NO_OVERLAY_AUTOSTART).
It polls every 5 s for a new vrserver (pid + start time, remembered across agent
updates), waits until SteamVR answers, gives SteamVR's own auto-launch 15 s and
starts each autostart overlay app that isn't running (process of its folder or
GetApplicationProcessId) through LaunchApplication, in a child process with a
timeout. Log: ~/.local/share/frameport/vr-overlays.log (capped).
2026-10-10 20:53:32 -04:00
spoopyghosty0 f6870da310 Merge branch 'worktree-agent-a541d9c7207d19375' 2026-10-10 20:52:50 -04:00
spoopyghosty0 6c8cda5d7a Godot apps: start without a clipboard service (frame.godot_clipboard, GitHub #134)
Godot 4.2-4.4's Kotlin code casts getSystemService(CLIPBOARD_SERVICE) with a
non-null 'as ClipboardManager'; Lepton has no clipboard service, so the app
crashes with a NullPointerException in Godot.<init> (4.3/4.4) or on first use
(4.2's lazy property). In place in classes*.dex, matched exactly: the
Intrinsics.checkNotNull before that check-cast becomes nops (older kotlinc
branch forms: the branch is fixed), and Godot's getClipboard/hasClipboard/
setClipboard return at once (Dex.return_early gains String -> "").
Analysis field godot_clipboard (ANALYSIS_VERSION 10), triage
godot-no-clipboard, PLAYBOOK row, unit tests on synthetic dex. Checked against
godot-lib 4.1.4-4.7.2 from Maven Central and the 4.3/4.4.1 export templates:
4.2.2, 4.3.0, 4.4.1 patched (D8 re-reads the result), the others untouched.
2026-10-10 20:52:23 -04:00
spoopyghosty0 7331d9320a Catalog: VR4 zeroes a second permutation of the campaign shader that reads its loop counter before setting it (6604-byte module, found in the reporter's shader capture; GitHub #140) 2026-10-10 20:40:44 -04:00
spoopyghosty0 dccdcaab96 SteamVR overlay apps on the Frame (agent v75)
Host OpenVR overlays are composited over Lepton (Quest) games on the Frame;
Windows overlay apps register through Proton's vrclient. Detect overlay apps
(bundled .vrmanifest with is_dashboard_overlay, or OpenVR + IVROverlay +
self-registration), patch pcvr.vr_overlay and the Linux entry fields
vr_overlay/vr_overlay_autostart, agent register/unregister/launch_vr_overlay
(IVRApplications through SteamVR's libopenvr_api.so; manifest with
binary_path_linux_arm, the only key the Frame's vrserver reads). Overlay apps
skip launch tests and the Linux Steam-parent watchdog and don't count as a
running game. Probe sources in native/vroverlay_probe.
2026-10-10 20:01:07 -04:00
spoopyghosty0 99c6a88f90 Merge remote-tracking branch 'origin/main' 2026-10-10 19:19:43 -04:00
spoopyghosty0 da4f26197d Merge branch 'worktree-agent-ad1c44db9b43374d4' 2026-10-10 19:17:23 -04:00
spoopyghosty0 02361b7c99 Merge diagnostics: every shader of the newest session (agent v74, GitHub #140), issue forms valid YAML again, plain-issue fallback for sharing (GitHub #167) 2026-10-10 19:17:21 -04:00
spoopyghosty0 d6bbea05ca Sharing: fall back to a plain issue when the form opens empty (#167)
Some browsers (Flatpak ones on Linux) opened the issue form with the
title but every field empty. The issue links now also come as the
Markdown body GitHub's forms write (### sections, the recipe as a yaml
block, which catalog_from_issue.py reads) and a plain issue link with
that body, trimmed to the same URL limit. The GUI copies the body to the
clipboard when it opens the form and offers "Form empty? Open a plain
issue"; the CLI prints the plain link. A shared recipe is marked shared
only after the link was opened.
2026-10-10 19:14:00 -04:00
spoopyghosty0 65d3c2ab67 Monitor: per-module collection needs agent 73 (main's 72 lacks it); correct agent versions in comments
The {"modules": …} message and take_screenshot reached main with agent 73 (the ui-refresh
branch's v63-v66 numbers collided with main's). MIN_AGENT_MODULES was 65, so agents 65-72
were sent a message they ignore.
2026-10-10 19:13:42 -04:00
spoopyghosty0 327f213f56 serialize_flet_updates: tolerate Flet versions without Session.patch_control or Prop (log, keep the rest) 2026-10-10 19:11:49 -04:00
spoopyghosty0 05c24cc9e0 Issue forms: quote the descriptions so the templates are valid YAML
Both descriptions contain ': ' in a plain scalar since 3546232, which YAML
reads as a mapping: the issue forms didn't parse.
2026-10-10 19:10:50 -04:00
spoopyghosty0 6e31ae52c4 Auto pairing scan: skip container/VM/VPN interfaces and CGNAT, back off after empty scans
Network scans for Valve's pairing port now leave out docker/bridge/veth/tun/tap/wg/tailscale/
zerotier/vEthernet/utun-style interfaces and loopback, link-local and 100.64/10 addresses
(discovery.local_subnets, also used by the Frame search). Each scan that finds nothing doubles
the wait (30 s up to 5 min); another set of networks or opening the Frame page starts over.
2026-10-10 19:09:34 -04:00
spoopyghosty0 7830444c87 pyproject: Development Status 5 - Production/Stable 2026-10-10 19:07:56 -04:00
spoopyghosty0 9ace9d3bb3 Catalog: Vacation Simulator starts; controllers aren't tracked yet (shared working config)
Refs #163 (kept open: controllers not tracked)
2026-10-10 19:07:04 -04:00
spoopyghosty0 a994e86ba5 Catalog: The Thrill Of The Fight runs with Unity's overlay copy skipped; the right eye misses the opponent and referee (shared working config)
Closes #164
Closes #125
2026-10-10 19:07:01 -04:00
spoopyghosty0 e776116162 Catalog: Fruit Salon works (shared working config)
Closes #165
2026-10-10 19:06:59 -04:00
spoopyghosty0 1e814af1d4 Auto pairing: hold off only while a setup request is open (not for allowed, denied or expired ones) 2026-10-10 19:06:26 -04:00
spoopyghosty0 f3ed77deb0 Docs: diagrams instead of ASCII art
Architecture: the module overview and the self-update flow are SVG diagrams
(docs/images, shown on GitHub and the site); the update details the old drawing
carried are a short list under it. FAQ: the game-folder layout is a tree block
the site draws as a real file tree (scripts/filetree.ts). Diagrams scroll sideways
on phones instead of shrinking. Docs pages on phones: the sidebar no longer kept
its desktop offset, which pushed it over the page title.
2026-10-10 19:05:26 -04:00
spoopyghosty0 d5245aa496 Install links: FramePort's https://frameport.app/install?… page is the documented button link (pasted links parse too); README, docs, site and in-app texts no longer point to FrameDrop's buttons (its links still work). README and site: 1.0 drops the proof-of-concept line and says plainly that not every game runs 2026-10-10 19:05:22 -04:00
spoopyghosty0 e4a62d6e90 Pairing: setup requests expire when the Frame stops polling; setup.sh explains HTTP 429
Open requests whose /wait polling stopped for more than 2 x WAIT and decided ones older than
DECIDED_KEEP are dropped (on /hello and every few seconds); the Frame page shows only open,
unexpired requests and redraws when one expires. setup.sh tells a full queue apart from an
unreachable PC. The fallback Frame name in a request is translated.
2026-10-10 19:04:47 -04:00
spoopyghosty0 dcc57f29c8 Diagnostics: send every shader module of the newest session (#140)
Agent v74: with an index.txt, collect_diag sends every module named in the
newest session (after the last '# start'), deduplicated, last used first,
each up to 4 MB and up to 30 MB in all, plus that session's index lines.
A reporter's GPU hang came ~10 s after the last new module, so the newest
written modules missed the culprit. Without an index (fp_spirv) nothing
changes. The bundle drops the least recently used modules last when the
zip would pass its size limit.
2026-10-10 19:03:59 -04:00
spoopyghosty0 d87d085842 CLI frame pair: don't announce the pairing server over mDNS (it can't answer setup-URL requests) 2026-10-10 19:01:23 -04:00
spoopyghosty0 3930617518 Issue reports: a game's report is titled "[Game report] <game>" with the game-compatibility label; reports about FramePort itself "[App] FramePort" with bug ("[Problem]" titles read as a list of app faults) 2026-10-10 19:00:35 -04:00
spoopyghosty0 deaf1f08a6 Setup line: curl -fsSL https://frameport.app/s (fail on HTTP errors, explicit https) 2026-10-10 19:00:18 -04:00
spoopyghosty0 76ba1e4ffa Merge pull request #166 from spoopyghosty0/showcase/update
Update docs screenshots and videos
2026-10-10 18:04:55 -04:00
spoopyghosty0 62728dc562 Site: Cloudflare and Google analytics, with a cookie choice
Cloudflare Web Analytics (cookieless) runs when the CF_ANALYTICS_TOKEN repo
variable is set. Google Analytics (GA_MEASUREMENT_ID) loads only after the
visitor clicks Allow on a small card with equal Allow/Decline buttons; the choice
is remembered, Cookie settings in the footer asks again, Decline removes the
_ga cookies and Global Privacy Control counts as Decline. Downloads are counted
as file_download events. New privacy page. Without the variables the site has
no analytics.
2026-10-10 17:56:37 -04:00
spoopyghosty0 6454c7f37d Docs: update screenshots and videos 2026-10-10 21:33:45 +00:00
spoopyghosty0 a4f73a88b7 Merge origin/main into site
Conflicts kept main's features with the site branch's wording: the uninstall
dialog's new delete-saves option, hardware video decoding and last-session help,
new Game settings (plain labels), agent 73 (main's 72 plus the Take screenshot
command from v66). Main's new texts follow docs/STYLE.md (for example, patch/setting
instead of fix). Pages rebuilds on docs changes and daily; the CNAME file is
gone (the domain is a Pages setting).
2026-10-10 17:18:37 -04:00
spoopyghosty0 e5658ed441 Site: SEO for frameport.app
Sitemap and robots.txt, complete Open Graph and Twitter tags, JSON-LD
(the app as a free SoftwareApplication with its current version; docs as
articles with breadcrumbs), a written description for each docs page, a
releases Atom feed, a 404 page, and titles that say what people search for.
2026-10-10 16:54:26 -04:00
spoopyghosty0 6bfaaf788f Site: What's new page and section from GitHub releases
/changelog/ shows every release's What's new part (the same parts the app's
changelog shows): the dev build as Coming next, the latest release large,
then a timeline; the home page shows the latest changes. Release jobs
restart the pages workflow so a new release appears on the site.
2026-10-10 16:41:42 -04:00
spoopyghosty0 07da1c27a3 Site: shorter, consistent copy, served at frameport.app
Plain words on every page, one label per action, no jargon on the home
page. The site moves to the custom domain frameport.app (CNAME, no
/frameport base); the setup line is curl -sL frameport.app/s | bash.
2026-10-10 16:34:22 -04:00
spoopyghosty0 35462324bb Docs: shorter, consistent copy
README is a short overview that links to the install guide; repeated
sections are said once; Share working recipe everywhere; the setup
script names the real buttons; a shorter notice, the same as the site's.
2026-10-10 16:34:19 -04:00
spoopyghosty0 2c1149815d App: shorter, consistent interface text
Dialogs, help texts and tooltips cut to what the user does or gets, one
name per action (Start setup, Uninstall FramePort, Update art on Frame,
Rebuild only), plain launch-test results (C.test_result), plain summaries
for every Game setting. The setup line now points at frameport.app.
2026-10-10 16:34:17 -04:00
spoopyghosty0 c9b600d186 Developer docs, CLI and launch-test diagnoses: consistent terms
US spelling, patch (not fix) for patches, current button names. Every
diagnosis now opens with one plain sentence before the technical detail.
2026-10-10 16:34:04 -04:00
spoopyghosty0 ca14fad3ce Merge Steam Input gamepad for flat Android apps (device.steam_gamepad, GitHub #162) 2026-10-10 16:12:47 -04:00
spoopyghosty0 c4fa6557e1 Steam Input gamepad for 2D Android apps (device.steam_gamepad, agent v72, GitHub #162)
Lepton's Android only gets keyboard, pointer and touch from the Wayland seat. The opt-in patch (2D apps; suggested
when the manifest declares android.hardware.gamepad or LEANBACK_LAUNCHER, ANALYSIS_VERSION 9) makes the agent record
steam_gamepad in deployment.json and give the launcher a line that exports SDL's hint
(LEPTON_ENV_SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD) and puts FramePort's own Podman wrapper (agent/bin/podman,
written by ensure_host_fixes, independent of the video codec) first on PATH. For the game's podman run it bind-mounts
Steam's virtual pads (uinput, 28de, gamepad buttons) at /dev/input/eventN plus an Xbox 360 key layout, then hands on
to the next Podman after itself (the codec wrapper or Podman), so both wrappers chain in either order.
FRAMEPORT_NO_GAMEPAD=1 turns it off for one start; failures keep the arguments unchanged. Verified headless on the
dev Frame with a uinput stand-in pad: Android's EventHub opens it with our layout and dispatches BUTTON_A.
2026-10-10 16:09:16 -04:00
spoopyghosty0 1bb08bab36 Wording: style word list and length rules 2026-10-10 16:00:23 -04:00
spoopyghosty0 cccafe86bc Merge vk_shader_dump (Vulkan shim shader capture, GitHub #140) 2026-10-10 15:56:59 -04:00
spoopyghosty0 6fb4a52051 Vulkan shim: vk_shader_dump writes the game's SPIR-V modules with a creation index (GitHub #140)
Adapter setting vk_shader_dump=1: every distinct module once to files/fp_vk_shaders/<size>_<sha256>.spv, each
vkCreateShaderModule as one index.txt line (order, time), to find the shader behind a GPU hang for a vk_shader_fix.
Agent v72 collect_diag returns the newest dumped modules (Vulkan and OpenGL ES dumps); diagnostics bundle them.
Triage gpu-hang suggests the dump for the session's graphics API (FrameBridge's swapchain formats).
2026-10-10 15:56:01 -04:00
spoopyghosty0 5f42c4bb01 Triage: unity-data-missing fails a launch test when Unity can't read the game's data files (incomplete OBB or one from another version; Batman passed while stuck at boot, GitHub #155) 2026-10-10 15:50:47 -04:00
spoopyghosty0 37b8ac78a7 Game page shows Update on Frame after a newer APK of an installed game was added (builds record the APK version they were made from; GitHub #161); triage only offers fixes that can matter for the game (a Vulkan game was offered the GLES-only 360° emulation; GitHub #138) 2026-10-10 15:50:44 -04:00
spoopyghosty0 26b86f3f9d Catalog: Eleven (Quest) and GOLF+ unsupported (Meta online services / the game's login refuse the platform), Beat Saber DLC isn't detected, Doom3Quest works (walk/run switching is the game's reaction to slowdowns)
Closes #160, closes #147, closes #150, closes #133, closes #77
2026-10-10 15:44:28 -04:00
spoopyghosty0 8d50d5a065 Catalog: 13 shared working configs (Freedom, Zombieland VR, Caves, Ancient Dungeon, Containment Protocol, Down the Rabbit Hole, End Space, ExploreVR, Racket: Nx, RC Pilot Trainer, Retropolis, Shores of Loci, SynthRiders)
Closes #132, closes #141, closes #142, closes #143, closes #144, closes #145, closes #148, closes #149, closes #151, closes #152, closes #153, closes #154, closes #158, closes #122, closes #93
2026-10-10 15:44:24 -04:00
spoopyghosty0 ce257b429a Site: two neon worlds, a portal-first download page, one-eye live view
- Two more Frame-side worlds (envs-neon.ts): a rain-soaked neon megacity, and a Tron-like arena with light cycles.
- A game's world comes from a hash of its title (FNV-1a): with seven worlds the old palette mix gave every
  title the same remainder, so one world never came up and another four times in ten.
- Download page: no round countdown ring; the countdown runs round the portal's own rim, a tunnel of rings
  sinks away inside it (the rim is a masked band, so the way through stays dark).
- The trial's live view shows one eye, as the app's does.
- Headings reveal slower again.
2026-10-10 14:59:39 -04:00
spoopyghosty0 7e95c50f39 Game page: open the last launch test's log
The Steam Frame (or This PC) card gets a log button when the game's last launch test ran there: it opens the
saved log (last_test.log_path) in the launch log viewer. The showcase's demo library writes a short made-up log for
each passed test so the button shows in the docs pictures.
2026-10-10 14:44:54 -04:00
spoopyghosty0 61ecc4d2ee Site: interactive docs, a download page, two more worlds, solid rocks
- Docs: search across every page (highlights where you land), definitions for jargon on hover, tables you can
  filter and sort, step lists you can tick off, the tutorial video in place, zoomable pictures, section links that
  copy, / [ ] keys, a reading path per goal on the overview and a mark on pages you've read.
- /download/: every Download button leads here; a countdown while the package goes through the portal, then the
  download starts; a manual button stays, with version, size and SHA-256 from the release and the next steps.
- Two more Frame-side worlds: an aurora over a frozen lake, and a reef. Shared helpers moved to envkit.ts.
- Asteroids and floating islands no longer tear: corners shared by several faces move together (moveCorners).
- Built on: every project links to its page. Compare: no update row; Rift games not supported by the others.
- Headings reveal a little slower.
2026-10-10 13:27:10 -04:00
spoopyghosty0 391f43df95 Site: three hero worlds, a 3D live view, a closer app trial, real screenshots
- The Frame side of the hero picks one of three worlds per game (grid, islands, space); the particles option is gone.
- The trial's Live view shows the same worlds through two lenses (vrscene.ts), one scene per stream, CSS fallback without WebGL.
- The trial looks more like the app: window bar, Library header/filters/shelf, game page, Frame card with now playing and power.
- The install log follows the game: package, conversion, its patches, version and note; the recipe opens under board rows.
- Calmer motion: no button drift, smaller click ring and cursor light; the gallery carousel shows the real screenshots again.
2026-10-10 12:28:15 -04:00
spoopyghosty0 60d8ab021b Install walkthrough: the setup line and Allow, in the video and on the site
- Install tutorial video (docs/showcase/videos/install.yaml, re-recorded by record_video.py): Start setup, a card
  for the setup line on the Frame (with the Developer Mode route in its note), the Frame asking and Allow clicked,
  a card for what the Frame then does by itself. Showcase: the fake pairing server takes asks; hook `frame_asks`.
- Site: the Install section is a six-step walkthrough (download, Start setup, the line or Developer Mode on the
  Frame, Allow, it sets itself up, add games and play), each step with a small live scene: the download unpacks,
  Start setup shows the line, Konsole types it and finds FramePort, Allow moves on, the setup ticks off, a game
  installs. The other ways link to /setup.
2026-10-10 10:16:13 -04:00
spoopyghosty0 dfab8d68fd Pair Frames in Developer Mode without a click on the PC
While no Frame is connected, FramePort watches for Frames in Developer Mode (Valve's devkit mDNS, else a scan every
30 s for Valve's pairing port, one entry per Frame by host key). One that already lets FramePort in is connected; one
that doesn't is offered FramePort's key through Valve's pairing every few seconds (refused at once until "Pair new
host" is open on the Frame, then the request shows in the headset; a 45 s pause after an unanswered one). So the user
only opens Pair new host and approves in the headset.
- frame/autopair.py (no Flet, callbacks), app._start_auto_pair, setting frame.auto_pair (switch on the Steam Frame
  page, default on); never with FRAMEPORT_HOME or FRAMEPORT_NO_AUTO_PAIR (tests, screenshots).
- Checked on the dev Frame: found by the scan (this WSL PC doesn't hear its mDNS), FramePort's key recognized, no
  pairing request sent. The headset approval path needs a Frame that doesn't know FramePort yet.
2026-10-10 09:45:51 -04:00
spoopyghosty0 8167cb86c6 Merge the shared hardware video decoder (PR #128, adapted: per game, off switch, VP9 fixes)
# Conflicts:
#	agent/frameport_agent.py
2026-10-10 02:25:41 -04:00
spoopyghosty0 44bc689348 Shared video decoder (codec revision 8): hidden VP9 frames no longer end the EOS drain early
Iris returns an empty capture buffer (bytesused 0, no LAST flag, time 0) for every VP9 frame that isn't shown
(alt-ref frames split from superframes). FFmpeg's V4L2 wrapper takes any empty capture buffer during a drain
for the end of the stream, so the first hidden frame decoded after EOS ended it and the pictures still in the
driver were lost (two-pass VP9 4K: 573/600 and 617/640, always the last frames); outside a drain the empty
buffers were returned as pictures, which the component then guessed away by counting hidden frames.
build.py now patches v4l2_context.c to requeue empty capture buffers without LAST/ERROR; the drain ends at the
LAST buffer, or after a skipped empty buffer at one silent second, so it can't block. The component's
hidden-frame counting is removed. Dev Frame (Batman's container): two-pass VP9 4K 600/600 and 640/640 in
hardware with the same Y hashes as OMX.google.vp9.decoder (seek phase too), VP9 without hidden frames,
H.264 4K and HEVC 4K 600/600, VP9 surface output 600/600. Two builds (different dirs and NDK copies) give the
same libstagefrighthw.so 141de01f.
2026-10-10 02:22:48 -04:00
spoopyghosty0 007db344d7 Shared video decoder: remove a game's old per-game codec folder once its launcher is converted, also when the recipe (not deployment.json) asks for hardware decoding (seen with Batman on the dev Frame) 2026-10-10 01:01:33 -04:00
spoopyghosty0 3fb2621030 Shared video decoder: per game (frame.hw_video_decode) with a global off switch; 8K retry at start and VP9 fixes from the hw-video-decode branch
- Opt-in per game: only launchers of games whose recipe has frame.hw_video_decode (install stage, no APK change;
  suggested from analysis media_codec, ANALYSIS_VERSION 8) get the codec line (deployment.json hw_video_decode,
  finalize + upgrade_launchers). Games that had agent 70's per-game codec (Batman) keep it; their old
  <base>/frameport-codec folders are removed afterwards. Batman: catalog + migration batman_video_patches
  (frame.hw_video_decode + the hidden adapter setting surface_native, no more package check); 4XVR catalog.
- Off switch: Settings -> Installing "Hardware video decoding" (library video.hw_decode, default on) -> agent
  video_codec_switch (video-codec/disabled, checked by the launcher line and the wrapper); FRAMEPORT_NO_HW_VIDEO=1
  for one game.
- Connection: the codec is checked once per connection, also after a failed install; a Frame keeps the same or a
  newer revision built elsewhere (logged) instead of flip-flopping between PCs.
- Wrapper: the merged media_codecs.xml goes to $XDG_RUNTIME_DIR/frameport-video, never into the version directory.
- Decoder: refused (busy) Iris sessions retry until 20 s after the plugin loaded (SteamVR's link holds a session
  ~12 s at game start), else 2 s, before the software fallback; VP9 capped at 4096x2304 (XML + component, larger
  decodes in software); hidden VP9 frames' pictures dropped. Manifest revision 7.
- build.py builds from paths with spaces and from freshly extracted FFmpeg sources. Artifacts rebuilt here, identical
  from two trees (ext4 and the NTFS repo path, two NDK copies); PR #128's own sources rebuild to its binary.
- AGENT_VERSION 71 (matches the connection's gate); triage hw-video-decoder-busy, PLAYBOOK row, docs.
2026-10-10 00:58:24 -04:00
spoopyghosty0 39ca9a5f0d Catalog: Audica works (shared working config)
Closes #126
2026-10-10 00:28:39 -04:00
spoopyghosty0 e64e8a95e0 Uninstall dialog: option to also delete the game's saves and everything it stored on the Frame (mods, downloads); they survived every uninstall before (GitHub #130) 2026-10-10 00:28:37 -04:00
spoopyghosty0 69e888c7fe Install on this PC works on Windows again: the agent (loaded for its VDF code) imported the POSIX-only fcntl at the top (ModuleNotFoundError for every PC VR install; GitHub #131) 2026-10-10 00:28:36 -04:00
spoopyghosty0 ce809c432d Merge PR #128: shared hardware video decoding (Lucas-Mathieu) 2026-10-10 00:23:42 -04:00
spoopyghosty0 cdf900c6b7 Docs: the setup URL first, the other ways kept; what the Frame offers for it
INSTALL and FRAME_SETUP describe the setup line, the Allow step and the announcement; the setup command, USB cable
and Valve's pairing stay. FRAME_RUNTIME records what was checked on the dev Frame (Chromium via Steam's +, mDNS from
the Frame only on port 5353, avahi-browse not usable over SSH). README's quick start uses the line.
2026-10-10 00:23:09 -04:00
spoopyghosty0 d5b5b7751e Project page: /setup, the setup line in Install, the trial's connect flow
- /setup: the three steps (Start setup on the PC, the line on the Frame with Copy, Allow), a terminal that plays what
  the script prints, what it changes, and every other way (setup command, USB cable, Valve's pairing, address).
- The site serves bootstrap/setup.sh as /s and /setup.sh (copied at build time; the workflow rebuilds when it changes).
- Install step 2 starts with a "Setup page" tab; the trial's Steam Frame page and guided tour use the same flow
  (the Frame asks, matching code, Allow).
2026-10-10 00:23:08 -04:00
spoopyghosty0 351712172c Setup URL: the Frame finds FramePort, you allow it, the usual setup runs
One line for every Frame and PC, served by the project page: curl -sL spoopyghosty0.github.io/frameport/s | bash.
- bootstrap/setup.sh finds FramePort (stdlib mDNS on 5353 for _frameport-pair._tcp; else the USB cable's address and
  a /24 scan of /ping), asks with /hello, shows 4 digits, waits for Allow, then runs exactly the typed line's
  `curl -fsS <pc>/<code> | bash`: bootstrap.sh is unchanged.
- PairingServer (no second server): /ping, /hello (max 3 open asks), /wait (long poll; code only once allowed),
  decide(); announces itself over zeroconf while running (name, port, two words for this PC's key; never the code).
- Connect page: "Start setup" shows the static line, incoming asks as Allow / Deny cards (a toast elsewhere);
  "Use the setup command" keeps the address + code line; USB setup unchanged.
- The app key is created under a lock (the announcement and the page asked for it at once on a first start).
- Tests: approval/deny/limits/timeouts, the code never in the announcement, setup.sh end to end against a real server
  with a stub bootstrap (allowed and denied), digits match. ui_smoke --setup-url drives the page.
Checked on the dev Frame with a stub bootstrap: found by mDNS and by the scan fallback, allowed, code handed over.
2026-10-10 00:23:06 -04:00
spoopyghosty0 f99aded40e Project page round 4: VR worlds through the portal, live screens, a page that reacts
- Hero, Frame side: the card is gone; the game opens into an abstract VR world (three.js, look around by dragging or
  tilting the phone) or into particles that gather into a shape for the game. A TEMPORARY toggle under the scene
  switches between the two for comparison. three.js loads only when the hero is on screen.
- A Steam Frame you can turn (the logo's visor, extruded) next to "Connect the Frame".
- No pictures of the app on the page: the carousel's slides are live copies of its screens (type, drop a file, end a
  process, switch themes), the video tiles play short silent loops (made at build time with ffmpeg), the install
  button is a live control.
- Scroll story: the hero flies toward its portal, the install log completes line by line, section lines draw in.
- Everything reacts: a portal light follows the pointer, clicks on empty space ripple blue then orange, headings rise
  in word by word and light up under the pointer, labels unscramble, numbers count up, each icon moves its own way.
2026-10-09 23:15:21 -04:00
spoopyghosty0 f631e5fc1b Project page round 3: docs pages, contributors, carousel, a fuller app demo
- Hero: the Frame-side card is the game running in the headset (one view per eye, the scene moving), switching on
  as it lands while its frame rate climbs to 72 and the pacing line draws.
- App demo: working search, right-click menu, select several and install them (queued), install from a link (the
  app's link rules), Screenshots tab with a viewer, Settings with the real themes switching live, Files that take
  real files from the desktop (they never leave the tab), process table with End, Live view quality, launch-test
  result, technical patch ids, toasts; Wi-Fi or USB cable when connecting. Progress ticks no longer rebuild the page.
- Screenshots: a carousel (arrows, keys, swipe, thumbnails, autoplay that pauses for the reader, full screen).
- Compared: four headline differences (free and open source first), every difference as user questions behind
  "Show all differences"; the README says the same.
- Wi-Fi or USB cable said everywhere; the setup command explains itself part by part; first-start steps per system.
- Field notes removed, the notice kept in its own band.
- Built on: contributors wall (code, pull requests, issues; scripts/contributors.mjs, saved list as fallback).
- Docs: user-facing docs/*.md as pages (links rewritten, GitHub heading ids, contents with scroll position, copy
  buttons).
- Games board rows open to show the recipe; the install log replays with another tested game.
2026-10-09 22:29:55 -04:00
Lucas-Mathieu 26cee1551e Deploy shared hardware video decoding for compatible Lepton apps 2026-10-10 03:36:56 +02:00
spoopyghosty0 82e2a40791 Project page round 2: app demo, livelier hero, comparison, sharper videos
- "Meta Quest" only as a description (headline about the Steam Frame), trademark line in the footer.
- Hero cards: generated covers in the style of the app's placeholder art (hue from the title), the same app icon on
  both sides of the portal, a one-line APK name, stage ticker and progress, Play ripple, sparks; titles sized so
  words never break. Click the portal for the next game; the scene leans toward the pointer.
- "The app": a clickable, simplified FramePort window (Library, game page with patch switches, Steam Frame, Files,
  Live view, Type on Frame, Monitor with live sparklines) and a guided tour from a fresh start: connect the Frame,
  scan a folder, install, play. The showcase videos play in a lightbox.
- Compared: FramePort, FrameDrop and Valve's own tools, from their own pages (checked 2026-10-09); the same table in
  the README.
- record_video.py --size WxH (e.g. 2560x1440): films at that many device pixels; postprod reads the size at call
  time and draws cards at the 1920 layout scaled up. Default output unchanged.
2026-10-09 21:26:37 -04:00
spoopyghosty0 1c158930cd Project page: an Astro site for GitHub Pages, and the install button's landing page
site/ (Astro 7, static, served at spoopyghosty0.github.io/frameport/) takes the Portal look of the UI refresh:
- Hero: a catalog game crosses the logo's tilted portal, from an APK on the PC's (blue) side to a game in the
  Steam library on the Frame's (orange) side; reduced motion holds it halfway, like the logo.
- How it works as one real recipe's install log (PC stages blue, Frame stages orange), the showcase tour and
  screenshots (synced from docs/ at build time, never by hand), field notes on what doesn't work, install steps,
  credits.
- Games board from catalog/games: prebuilt at build time, then refreshed in the browser from main (one contents
  API call, only changed files fetched, 6 h localStorage cache); any failure keeps the built list.
- /install/?manifest=…|?url=…: where an "Install with FramePort" button lands; opens frameport://install, and if
  FramePort didn't take it, says how to get it (download for this OS, try again, copy the link). Link rules mirror
  deeplink.check_url; node tests cover them and the board's rows.

.github/workflows/pages.yml builds and deploys on main (Settings → Pages → Source: GitHub Actions, once).
2026-10-09 20:22:26 -04:00
spoopyghosty0 f812ea8716 Codec wrapper (PR #96): find the host's podman when Lepton calls it with the Android guest's PATH (its boot-wait, app-pid and logcat-mirror podman exec calls failed, so launch.log lost logcat and the container could be stopped early) 2026-10-09 17:54:44 -04:00
spoopyghosty0 3dc0a95379 Catalog: Doom3Quest uses frame.gl_multiview_fbo (HUD/PDA shown, no frame-rate drop: Xandrix1987's headset test, GitHub #77) 2026-10-09 16:30:33 -04:00
spoopyghosty0 f8244f582d Catalog: cubism works (shared working config)
Closes #121
2026-10-09 16:29:36 -04:00
spoopyghosty0 69fbdfdc4a SUPERHOT VR (Quest): create its cloud save folder at install, so the launcher gives it group write permission before the very first start (the game creates and checks it in the same millisecond; GitHub #120); triage text says to start again 2026-10-09 16:29:34 -04:00
spoopyghosty0 7d9ef9c6f8 Full charge: one pulse after the ring refills 2026-10-09 16:18:55 -04:00
spoopyghosty0 e042d1f1ee Full charge: the ring empties at once, then spins while it refills 2026-10-09 16:15:03 -04:00
spoopyghosty0 2bb88b211b Full charge: the battery ring spins while its bar drains and refills, then pulses 2026-10-09 16:10:20 -04:00
spoopyghosty0 27694243f2 Screenshots: copy an image to the clipboard; holiday showers; perfect pacing stays
- Screenshots tab: Copy image (a button on the card under the pointer, the right-click menu and the viewer) puts
  the full-size screenshot on this PC's clipboard (core/clipboard.py: Windows/WSL PowerShell bitmap + file,
  macOS osascript, Linux wl-copy/xclip; Flet's set_image in web mode)
- Clicking a holiday badge showers the window: New Year confetti, winter snow, Valentine hearts, digits of pi,
  clovers, hopping eggs, bouncing pumpkins (moved like the confetti: one page update per phase)
- Perfect pacing after 2.5 minutes on target; the Perfect pacing pill and line stay until the rate slips
- Full charge: only the ring pulses (with a glow behind it), the percentage stays put
- Upload hop a little slower
2026-10-09 15:59:02 -04:00
spoopyghosty0 e6c00ec4b8 Easter eggs round 3: toss from the wordmark's portal, dancing badges that work, bigger perfect pacing
- Seven logo clicks: the glow, rings and the tossed cover come out of the wordmark's portal (between Frame and Port)
- Holiday badges dance on hover again: the logo's box grows to hold the badge on holidays (Flutter only hit-tests
  inside a control's bounds); spins grow while they turn, bigger wobble and hop
- Perfect pacing: the game card glows and pulses blue/orange on an opaque card, the number grows in the portal
  gradient, a Perfect pacing pill
- The logo's spin (searching for FramePort) is slower
- Hello: a headset peeks up from the window's corner and waves, outside the typing box
- Full charge: the battery ring pulses instead of sparkling
- Upload hop: a little lower and quicker, after 2.5 minutes instead of 5
- The fps mood tooltip opens above the number, clear of the pointer
2026-10-09 15:04:35 -04:00
spoopyghosty0 ef3272a31b Catalog: Vader Immortal III's campaign intro starts black by design (owner) 2026-10-09 15:04:18 -04:00
spoopyghosty0 b0f3ef2698 Catalog: Vader Immortal Episodes II and III work (owner's headset test) with Episode I's Unreal fixes (quest precompile, key map, thumb touch, pose_time_fix), all found in their code by the heuristics 2026-10-09 14:37:11 -04:00
spoopyghosty0 1711e7f7d9 Docs: session triage (PLAYBOOK rows for the session findings, CLAUDE.md note incl. the pac_hints survey) 2026-10-09 14:22:22 -04:00
spoopyghosty0 ff6cdf3b6a Triage real play sessions: the connection refresh fetches a finished session's log (agent v70) and triages it (launch-test signatures + new space-warp-used question and gpu-hang, find_rp_state for unreal-msrtt-crash, slow-frames from FrameBridge pacing, focus-dips), stored as last_session; game page "Last session" callout; value suggestions (adapter.key=value); FrameBridge-only fixes applied live via set_settings; CLI frameport session [--apply] 2026-10-09 14:22:20 -04:00
spoopyghosty0 28517a1999 Agent v70: session_log (the newest play session's launch.log sliced to 4 MB + its crash logcat + kernel GPU hang lines), last_play in list_installed, launch tests mark their session in plays.log 2026-10-09 14:22:17 -04:00
spoopyghosty0 2480d203ec Easter eggs: portal toss, more holidays, perfect pacing, self-search, hello, full charge, upload hop, on air
- Seven logo clicks: the portal glows and rings, a cover is tossed out and falls across the window on a random arc
- Holiday badges dance on hover; new: Valentine's, Pi Day, St Patrick's, Easter weekend, April 1 (upside-down
  logo until hovered), New Year's party popper
- Monitor: a minute within 0.5 fps of target turns the fps chart into the portal gradient
- Library search "frameport": the logo spins and says "That's me!"
- Type on Frame: typing "hello" makes a headset wave (keys still go to the Frame)
- The battery ring sparkles on reaching 100 % on the charger
- Uploads over five minutes: the transit cover hops now and then
- Live view tab: a blinking ON AIR sign while streaming
- Showcase hooks perfect_pacing, battery, long_upload, press_keys
2026-10-09 14:18:08 -04:00
spoopyghosty0 af9c207ddf FrameBridge: always log the runtime's focus losses and returns with their length (focus: lost / back after N ms), before focus_hold hides a dip; for session triage 2026-10-09 14:03:23 -04:00
spoopyghosty0 88045bcf4e Merge remote-tracking branch 'origin/main' into merge/pr96 2026-10-09 13:43:24 -04:00
spoopyghosty0 5147710fad Merge Batman cutscene playback (hardware HEVC decoding, stereo composition) by Lucas-Mathieu (GitHub #96)
Adapter rebuilt here; the codec plugin rebuilt here with native/hevc/build.py (NDK r27c, Lepton SoftOMX fingerprint 456e912c) is byte-identical to the contributed one (c1c2a73e). Agent version 70 -> 69 (main was at 68).
2026-10-09 13:43:22 -04:00
spoopyghosty0 9c45cff09a Heuristics: suggest pose_time_fix for Unreal 4 games with Vader Immortal's Oculus input (the unreal_thumb_touch code match). Catalog: Phantom, Robo Recall and Time Stall get frame.unreal_thumb_touch + pose_time_fix (their code matches Vader's; not yet checked in these games), Star Wars: Tales gets frame.unreal_thumb_touch instead of proximity_emul (as Vader) 2026-10-09 13:43:07 -04:00
spoopyghosty0 c4983b6e18 Easter eggs; the Settings source link sits on the left
- ui/easter.py: seven quick clicks on the sidebar logo send a game's cover out of its portal for a somersault over
  the wordmark; the Monitor's frame rate has a mood tooltip; holiday badges on the logo (a pumpkin Oct 24-31, a
  snowflake Dec 20 - Jan 2); install milestones (confetti in the portal colours and a message at the 1st, 10th and
  100th different game on the Frame). Milestones show once ever: they count different games (updates and
  reinstalls don't), are stored as shown before they're shown, and the first use seeds from the games already on a
  Frame (no "first game" after an app update). Off with setting ui.easter_eggs; Reduce motion keeps the words only.
- C.kv puts control values on the left; C.link (no indent) for the About page's source link.
- Showcase: the demo library has the easter eggs off; steps hooks easter_eggs / fps_dip and a clicks action;
  record_video.py takes a storyboard file path (demos outside docs/).
2026-10-09 13:39:42 -04:00
Lucas-Mathieu b4848c0424 Harden codec deployment and isolate native video hooks 2026-10-09 19:24:51 +02:00
spoopyghosty0 3a46b78361 Catalog: Clockwork (Quest), Beat Saber, Eleven: Table Tennis VR and Pistol Whip (PC VR, run directly without Revive) from shared working configs
Closes #116, closes #117, closes #118, closes #119
2026-10-09 13:21:50 -04:00
spoopyghosty0 75bbc6eb5f Heuristics: suggest scene_emul for every game that declares Meta's USE_SCENE permission, not only mixed-reality-only ones (VR HOT's room setup retried forever: LoadSceneModel failed / no XR_FB_spatial_entity_query; with scene_emul the room setup returned without errors). Catalog recipes still win (6 verified games declare USE_SCENE and run without it) 2026-10-09 13:06:44 -04:00
Lucas-Mathieu da638aea99 Merge remote-tracking branch 'origin/main' into fix/batman-cutscene-playback
# Conflicts:
#	agent/frameport_agent.py
#	artifacts/SHA256SUMS
#	artifacts/arm64-v8a/libopenxr_loader_generic.so
#	artifacts/armeabi-v7a/libopenxr_loader_generic.so
#	native/adapter/frame_adapter.c
2026-10-09 18:29:12 +02:00
spoopyghosty0 7c37b479db Agent v68: launch tests count their window from Lepton's 'Waiting for app' (up to 240 s more for boot + install). The first start after an APK change installs the app first; a 45 s window from the launcher stopped the container mid-install and left a broken installed APK ('base.apk is not zip') that never started again (VR HOT) 2026-10-09 11:58:54 -04:00
spoopyghosty0 fa473c5ada frame.unreal_thumb_touch: UE4 OculusInput's ThumbUp from the capacitive touches instead of near-touch, which the Frame never reports (thumbs always pointed up). Three instructions in SendControllerEvents (masks 0x2/0x8 -> 0x0f00/0x000f, NearTouches load -> Touches), matched exactly; suggested where the code matches (analysis unreal_thumb_touch, ANALYSIS_VERSION 7): Vader Immortal Ep. I, Robo Recall, Phantom: Covert Ops, Time Stall, Star Wars: Tales. Vader's recipe uses it instead of proximity_emul (the binding didn't animate the thumbs). Found by Klownicle, GitHub #49 2026-10-09 11:35:36 -04:00
spoopyghosty0 1e6ce04e4f pose_time_fix: never move a recent predicted display time; monotonic times only told apart when the clocks are more than 4 display periods apart and the time is clearly nearer the monotonic now (GitHub #49: on a Frame with XrTime only ~68 ms ahead of CLOCK_MONOTONIC, a request at the frame's display time after a hitch was taken for a monotonic time and moved +67.7 ms). Found by Klownicle, GitHub #49 2026-10-09 11:35:35 -04:00
spoopyghosty0 5cd5b119e7 Lint: extraneous parentheses (ruff UP034) 2026-10-09 11:33:00 -04:00
spoopyghosty0 186138cc3c Game page: a patch the build left out because the OVRPort runtime already has the fix (build.superseded, e.g. haptic_fix with runtime 3.4.3-aa54c3f) no longer counts as a settings change: 'Update on Frame' stayed after every update (owner's VR HOT, Klownicle's Vader, GitHub #49) 2026-10-09 11:32:40 -04:00
spoopyghosty0 98c7ca980a Triage: Lepton's own short lines survive the game-process filter. With the package known, game_lines dropped every line of 3 words or fewer, so Lepton's 'Boot complete!' vanished and the transient 'is not a running context' failed launch tests in the GUI although the games ran (owner's runs: Come Closer, AntiZeroGames CH, SKYBOX; Klownicle's Vader, GitHub #49). Only logcat lines are filtered by pid now 2026-10-09 11:29:12 -04:00
spoopyghosty0 13736d2524 Showcase: placeholders in the setup command; Monitor charts grow live on film
- The install tutorial (app screen and card) shows curl -fsS <your-PC-address>:8765/<one-time-code> | bash, so
  nobody takes the example for their own command; the card says FramePort fills them in.
- Videos: opening the Monitor tab no longer replays two minutes of samples (its charts raced to catch up); the
  charts start there and grow live, as in the app. The pretend frame rate holds ~72 fps with a short, shallow dip
  now and then (it sat at 55-61 fps for seconds). Both videos re-recorded.
2026-10-09 11:20:13 -04:00
spoopyghosty0 b2470b962a Catalog: Max Mustard is listed by its name, not its package name Matilda (GitHub #110) 2026-10-09 11:10:09 -04:00
spoopyghosty0 2a705bc6d1 frame.gl_multiview_fbo: single-view twins for multiview programs drawn into flat framebuffers (GitHub #77)
Mesa enforces OVR_multiview's rule that a draw's program declares as many views as the draw framebuffer has and drops the draw silently; Doom3Quest compiles every vertex shader with layout(num_views=2) and draws its HUD/PDA into 2D-texture FBOs, which stay black on the Frame.

native/glmv (libfpglmv.so, same length as libGLESv3.so): the engine library's dlopen string is rewritten to it and it becomes the first DT_NEEDED. It records stage sources at link time and, when a multiview program draws into a framebuffer without views, draws with a lazily built single-view twin (num_views layout blanked, gl_ViewID_OVR -> 0u; attribute locations, block bindings and uniform values copied), then rebinds the original. Opt-in and experimental (analysis gl_multiview_libs, ANALYSIS_VERSION 6), setting gl_mv_debug, triage gl-multiview-twin-failed.

Host tests: the rewriter on Doom3Quest's 19 shaders (GPL-3.0 fixtures) and glmv.c against a stand-in GL that applies Mesa's rule. Untested on the device.
2026-10-09 10:54:40 -04:00
spoopyghosty0 a069c79c73 Showcase: any number of videos (docs/showcase/videos/<name>.yaml), plus an install tutorial
- record_video.py replaces record_tour.py: one storyboard per video with its output, a start state (demo or
  fresh profile, Frame connected or not, start page, live or frozen stream), setup steps before filming, filmed
  scenes and instruction cards (eyebrow, heading, numbered steps, a command, a note) for what happens outside
  FramePort. --changed-since records the videos whose storyboard (or the recorder) changed; several videos record
  in separate processes.
- videos/install.yaml: download, first start, the welcome screen's tool download, the setup command, the command
  on the Frame (card), connected, scanning a folder, installing and playing a game (81 s).
- demo_home profile "fresh": FramePort's first start (empty library, welcome screen, nothing on the Frame); its
  games wait for the pretend folder scan. Fakes for a first start: no Frame discovery on the real network, the setup
  command shows the docs' example address, a pretend tool download, steps hooks first_run and pairing_done.
- CI records changed (or named) videos; releases attach every docs/media/frameport-<name>.mp4 as
  FramePort-<name>.mp4. INSTALL.md and README link the tutorial; SHOWCASE.md documents the storyboard format.
- Cards are opened by their title in both videos (a card's middle is its Install button).
2026-10-09 10:37:22 -04:00
spoopyghosty0 5d44831757 Agent v67: keep launch.log filling when Lepton's logcat mirror dies ('logcat: Unexpected EOF!' right after the game starts, about 1 launch in 50: Vader Immortal on Lepton 3.0.5, Under Cover on 2.8.14). launch.sh starts _logcat_keeper, which then reads the container's logcat itself (podman exec ... logcat -T 2000, up to 5 restarts) so the dashboard auto-hide and launch tests see the game; upgrade_launchers adds it to existing launchers 2026-10-09 10:14:57 -04:00
spoopyghosty0 456880501b PC VR: a catalog recipe verified with another build (different VR APIs) keeps the build's own launch arguments; Electron launchers rank below the game; agent v67 collects Unity's Player.log / output_log.txt / crash error.log for launch tests and diagnostics; triage unity-vr-init and unity-crash (GitHub #105) 2026-10-09 10:10:33 -04:00
spoopyghosty0 63f7ae12e1 Catalog: Vader Immortal: Episode I works (owner's headset test of the GitHub #49 fixes by Klownicle) 2026-10-09 10:06:44 -04:00
spoopyghosty0 fa69ffefd2 Vader Immortal: loading card, controls, lightspeed shaders, thumbs (GitHub #49)
Klownicle's five fixes for Vader Immortal: Episode I, reimplemented as FramePort patches (found by Klownicle,
GitHub #49; their prebuilt APK is not used):

- frame.unreal_quest_precompile: UVRUtils::GetQuestShaderPrecompilePercent returned 0.0 on its non-Quest branch, so
  the menu waited forever on the loading card; that branch now returns 1.0. Located by symbol, every instruction
  around it checked (bl IsRunningOnSantaCruz; tbz w0,#0; fmov s0,wzr; ldp; ret); other builds stay untouched.
- frame.unreal_quest_keymap: the RPOC key selector (shared by AddAxisMapping and AddActionMapping, so both mapping
  kinds were affected) picked the empty Gear VR key set for an Oculus HMD; the tbz to it becomes a nop. Located as
  the common callee of both functions, pattern-checked.
- frame.zink_shader_fix + native/zinkfix (libVkLayer_fp_shaderfix.so): a Vulkan layer under Zink for GLES games
  that applies zink_shader_fix entries (vk_shader_fix format) to the SPIR-V Zink generates; it adds itself to
  GraphicsEnv's debug layer list from a constructor (the engine library loads it first). zink_shader_dump=1
  captures modules. Vader's recipe carries the two lightspeed-shader fixes (12 OpStores each).
- adapter proximity_emul (FrameBridge): OVRPlugin's thumb/index proximity actions get bindings to the capacitive
  touch inputs when the runtime lacks XR_FB_touch_controller_proximity (OVRPort offers the extension anyway):
  generic replacement for the libUE4 ThumbUp patch.
- Vader's recipe: pose_time_fix + proximity_emul, status unknown (to test in the headset), min_app 0.12.1.

Analysis records unreal_quest_gates (ANALYSIS_VERSION 4). Star Wars: Tales from the Galaxy's Edge (same studio)
has neither gate (different engine build) and gets a catalog entry with pose_time_fix + proximity_emul.

Headless on the dev Frame (Lepton 3.0.5): the layer loads into Zink's instances, the proximity bindings are
accepted, and the game passes the loading card to the "guardian smaller than recommended, press any button" screen.
2026-10-09 09:20:15 -04:00
spoopyghosty0 f78bc82cc4 frame.vivox_audio_route: Vivox voice chat without Android 12 audio routing (GitHub #101)
Newer Vivox builds (Green Hell VR) call AudioManager communication-device methods (API 31) from com.vivox.sdk.AudioChangeListener without a version check; Lepton is Android 11 -> NoSuchMethodError. The methods that call them now return at once (in-place dex edit, Dex.return_early). Detected by analysis vivox_api31 (ANALYSIS_VERSION 4), triage vivox-api31. Verified headless: Green Hell starts, Vivox initialises, ~65-70 fps.
2026-10-09 09:09:24 -04:00
spoopyghosty0 dfacf6a206 FrameBridge: serve cube swapchains the runtime refuses (GitHub #107)
Budget Cuts Ultimate asks for a 2048x2048 cube swapchain (faces=6); the Frame's runtime has no cube layers and refuses it (-2), OVRPlugin carries on with no images and crashes in ovrp_EndFrame4 (memset). cube_standin (default on, GLES) serves it as a GL cube map in the game's context and drops its layers. Triage cube-swapchain-refused (supersedes unity-render-crash/native-crash). Verified headless: the game runs on at ~70 fps.
2026-10-09 09:09:23 -04:00
spoopyghosty0 b2f14c1e2e Catalog: SUPERHOT VR (Rift) keeps the build's own launch arguments (GitHub #105: the older Oculus + SteamVR build needs -vrmode OpenVR; the catalog recipe from the OpenXR build dropped it) 2026-10-09 08:49:53 -04:00
spoopyghosty0 9ee9355348 SHOWCASE.md: the double render, --fast and failed shots 2026-10-09 08:44:57 -04:00
spoopyghosty0 0a2dbc3140 Library: a clear divider under the On your Frame shelf; showcase: in-game screenshots, verified renders
- Library: an "All games" label and a 2 px rule (blue→orange in Portal themes) between the shelf and the grid,
  hidden with the shelf.
- The pretend Frame's screenshots are the games' own in-game store screenshots (never cover art), installed games
  first; the running demo game is BONELAB (it has screenshots, so Take screenshot adds a real one).
- render_docs: a shot whose steps fail is retried in a fresh window and never written (a window that didn't connect
  had produced an empty Library); every shot is rendered twice in fresh windows and kept only when both agree (a
  misdrawn glyph, "7.θ W", slipped through once); changes from 0.04 % of the pixels count (the sidebar card
  showing another game is 0.12 %, run-to-run noise under 0.007 %). --fast renders once.
- record_tour: SHOWCASE_DEBUG=1 prints each step's start time. Docs images and the tour re-rendered.
2026-10-09 08:44:25 -04:00
spoopyghosty0 03d7a311bf Catalog: Batman: Arkham Shadow gets sync_guard to test (GitHub #102: quit to Steam's waiting screen after the Frame runtime crashed in xrSyncActions, the input race Myst had) 2026-10-09 08:32:50 -04:00
spoopyghosty0 f7108c0ca4 Fix: a page change could die when a background thread set a control property mid-update
Flet packs a patch by iterating each control's _values dict; Prop.__set__ adds/removes keys there without a lock.
A property set on a background thread (the live Frame card's sample, job progress, the library loader) while the
event loop packed a patch raised "dictionary changed size during iteration" and the update was lost (seen in CI:
a sidebar click left the old page showing). serialize_flet_updates() now makes property writes take the same
re-entrant lock as patches (app.FLET_LOCK). Reproduced with Flet's own encoder: 16 failures in 4 s before, none
after; test_property_writes_wait_for_a_patch_being_packed. The showcase server and ui_smoke serialize like the app.
Tour re-recorded with the fix (clean, no fallback clicks).
2026-10-09 08:04:55 -04:00
spoopyghosty0 cd5119278b Catalog: The Light Brigade (works with issues)
Closes #114
2026-10-09 07:56:14 -04:00
spoopyghosty0 6ab3608a44 Catalog: Gravity Lab (works)
Closes #113
2026-10-09 07:56:06 -04:00
spoopyghosty0 c0539af368 Catalog: Does it Stack? (works with issues)
Closes #112
2026-10-09 07:56:02 -04:00
spoopyghosty0 c6363140f4 Catalog: Walkabout Mini Golf (works)
Closes #111
2026-10-09 07:55:58 -04:00
spoopyghosty0 187105b729 Catalog: Matilda (works)
Closes #110
2026-10-09 07:55:55 -04:00
spoopyghosty0 b983c0f11f Catalog: The Tale of Onogoro (works)
Closes #108
2026-10-09 07:55:52 -04:00
spoopyghosty0 c7cee33889 Catalog: Please Don't Touch Anything (works)
Closes #106
2026-10-09 07:55:49 -04:00
spoopyghosty0 d9d73b8aec Triage: Lepton 3.0.5's transient 'is not a running context' (printed while its container is still starting, then 'Boot complete!') no longer fails a launch test (container-not-started unless Boot complete!; VR4 ran at 72 fps on the dev Frame but was marked failed) 2026-10-09 07:55:24 -04:00
spoopyghosty0 48455b9b72 pose_time_fix: robust clock offset (the largest of the last ~2 s of xrWaitFrame samples; hitches made single samples dip by up to 2.5 s and pushed fixed times up to 2 s into the future), far-past requests located at the frame's display time, nothing moved before 8 samples. Headless survey of 54 games: no other game asks for head/views on the monotonic clock; UE4 OVRPlugin 1.89 games and The Room VR ask for XrTime ~0 every frame and work today, so the setting stays suggested for Unity built-in Oculus games only. BattleSisters on the Frame: every moved request now at 0.0 ms from the display time 2026-10-09 01:35:15 -04:00
spoopyghosty0 21d72e51ef showcase workflow: verification on ui-refresh done (3 runs: screenshots identical to the committed ones, tour recorded cleanly); main only again 2026-10-09 01:17:36 -04:00
spoopyghosty0 222633e462 Showcase: nav step (sidebar click checked against the page that shows; falls back to opening the route when a click's handler died in a Flet update race seen on CI); CI renders with Python 3.12 2026-10-09 01:08:16 -04:00
spoopyghosty0 f64c93b338 Showcase: every shot waits for its own page before the picture (CI caught Monitor still showing the warm-up page); settling needs 4 identical frames 2026-10-09 00:55:07 -04:00
spoopyghosty0 429899d6fc showcase workflow: one verification run on ui-refresh (temporary) 2026-10-09 00:40:52 -04:00
spoopyghosty0 f1486609ab test_showcase: the demo build's environment variables don't outlive the test 2026-10-09 00:40:51 -04:00
spoopyghosty0 e35170d4f9 Showcase: CI workflow, release asset, docs; screenshots and the tour rendered from the demo library
.github/workflows/showcase.yml renders the docs screenshots on UI pushes to main (and the tour when its storyboard or
scripts change, or on Run workflow), keeps every render as an artifact and opens/updates the PR showcase/update
when a picture visibly changed. Releases attach the tour as FramePort-tour.mp4. docs/SHOWCASE.md explains running
it, the step language and the CI flow; README opens with the teaser (link to the full MP4). Renders refuse
placeholder art (--allow-missing-art). tests/test_showcase.py: demo library, privacy scan, image threshold,
manifests (every docs image has a shot), step validation, element picking, pointer paths, ffmpeg graphs.
scripts/demo_record.py is replaced by the tour.
2026-10-09 00:37:39 -04:00
spoopyghosty0 80a5c0b6d6 Showcase: scripted demo tour (docs/showcase/tour.yaml -> record_tour.py)
One window on the demo library, driven by name like a person would (drawn pointer, eased curved paths, a ring on
each click), filmed with Chrome's screencast (1080p, constant 30 fps from the frame timestamps), then captioned
(lower thirds in the app's colours), given title/end cards and crossfaded by postprod.py (ffmpeg only). Writes
docs/media/frameport-tour.mp4 (+ poster, best quality within 12 MB) and the README teaser docs/images/tour-teaser.webp.
Installs in the tour run a scripted job through the real queue; --draft for quick storyboard checks, --names lists
the clickable names. Element finding prefers the innermost match (a list's text includes its rows').
2026-10-09 00:22:19 -04:00
spoopyghosty0 63f0125954 Showcase: demo library + docs screenshot renderer
scripts/showcase/: the fakes split out of ui_smoke (the pretend Frame now reads the demo library's state, fixed
dates, a Take screenshot fake, a frozen monitor stream for stills), web.py (the GUI in web mode driven by name
through Flutter's semantics tree, drawn cursor, eased pointer paths, settle detection, one browser per window),
steps.py (the step language shared by screenshots and the tour), demo_home.py (21 catalog games from
docs/showcase/demo-library.yaml + demo-analyses.json, art and store details fetched once and cached) and
render_docs.py (docs/showcase/shots.yaml -> docs/images, rewritten only on a visible change; --check for CI).
2026-10-09 00:00:13 -04:00
spoopyghosty0 af798c0bf3 Catalog: BattleSisters entry parses again (my previous edit put colons into a plain YAML text block); written with catalog.to_yaml 2026-10-08 22:55:47 -04:00
spoopyghosty0 ed714c9d65 Catalog: BattleSisters plays with hands following the controllers (adapter pose_time_fix, owner's headset test) 2026-10-08 22:51:26 -04:00
spoopyghosty0 832b401630 Pose time fix (adapter pose_time_fix, pose_debug): OVRPlugin locates its "now" poses at the monotonic clock
BattleSisters' hands lagged behind the controllers (also with ovrp_hold_physics on or off). pose_debug=1 (new,
FrameBridge: per 5 s and per located space, the requested time minus the predicted display time) showed on the dev
Frame (SteamOS 0.4.5): XrTime runs 2.56 s ahead of CLOCK_MONOTONIC, the hand spaces were located at -2564 ms (the
monotonic "now" passed on as an XrTime: OVRPort's dispatcher converts XR_KHR_convert_timespec_time 1:1 and
FrameBridge's emulation is never asked) and the head at XrTime 0.1 s (~1450 xrLocateViews/xrLocateSpace per 5 s).

pose_time_fix (default off; suggested for Unity built-in OVRPlugin games): in xrLocateSpace(s)/xrLocateViews a time
nearer the monotonic clock than XrTime's "now" is moved by the offset measured at xrWaitFrame, one more than 0.5 s
before the display time goes to "now". Headless with it on: every far-past request moved, located times -9.8..0 ms
from the display time. Host unit test for the time rules; PLAYBOOK row.
2026-10-08 22:41:34 -04:00
spoopyghosty0 5e7c07c98e Merge origin/main into ui-refresh
BONELAB 1.2974 / Sniper Elite VR / Star Wars Pinball VR fixes (pac_hints, unreal_ovrp_entrypoints, unreal_gl_shim, ovrpshim settings, ANALYSIS_VERSION 3) and catalog entries. Only conflict: the generated locales/template.json (regenerated). Agent stays v66 (main didn't bump it).
2026-10-08 22:38:32 -04:00
spoopyghosty0 00d2b3bf6b Deleting a screenshot also removes Steam's stereo copy (<name>_vr.jpg) that VR screenshots get 2026-10-08 22:33:39 -04:00
spoopyghosty0 02c697c2f0 Take screenshot via OpenVR: RequestScreenshot (SteamVR captures) + SubmitScreenshot (Steam saves it under SteamVR), ctypes on SteamVR's libopenvr_api.so; verified on the device. The dashboard's debug screenshot_request did nothing and is gone. Messages for SteamVR not running / headset asleep 2026-10-08 22:22:53 -04:00
spoopyghosty0 511051952c Screenshots: Take screenshot button (agent v66 take_screenshot = SteamVR's own screenshot_request over its web mailbox; waits for Steam's file, says when the headset is in standby) 2026-10-08 22:09:23 -04:00
spoopyghosty0 114b7cb4d6 Catalog: BONELAB works on 1.2068 and 1.2974 (owner's headset test of frame.slz_vulkan_hooks) 2026-10-08 22:08:29 -04:00
spoopyghosty0 a4d3ce5e00 Merge BONELAB 1.2974 fix by UnicronNL (GitHub #82)
frame.slz_vulkan_hooks: Stress Level Zero's plugin no longer intercepts Unity's Vulkan start-up and samplers (it called an invalid function right after OVRPlugin's pre-init instance was destroyed). Test fixture rebuilt by us, byte-identical. Owner's headset test: BONELAB 1.2974 plays well.

Closes #82

# Conflicts:
#	docs/GAMES.md
#	tests/test_patches.py
#	tests/test_triage.py
2026-10-08 22:07:54 -04:00
spoopyghosty0 b3095b110a Merge Sniper Elite VR fixes by UnicronNL (GitHub #81)
ovrpshim frame-begin gate and held-back physics update (per-game settings ovrp_begin_gate / ovrp_hold_physics, on in Sniper Elite VR's recipe), xrshim PCM vibrations, unity_oculus_check revision 5; artifacts rebuilt by us. Owner's headset test: Sniper Elite VR plays, Accounting+ unchanged, BattleSisters unaffected (settings off).

Closes #81

# Conflicts:
#	artifacts/SHA256SUMS
#	docs/GAMES.md
2026-10-08 22:06:18 -04:00
spoopyghosty0 dce2362cbb Live view: the in-window player reconnects (3x a minute) when its stream ends while the live view runs; starting without a Frame connection says so (it used to fail the helper upload with a NoneType error and fall back to x264); the relay logs viewers joining and leaving with the reason 2026-10-08 21:57:10 -04:00
spoopyghosty0 fb284b6fc4 Catalog: Star Wars Pinball VR (works; frame.unreal_gl_shim + frame.unreal_ovrp_entrypoints + frame.pac_hints, needs FramePort 0.12.1)
Closes #83
2026-10-08 21:56:46 -04:00
spoopyghosty0 c0df52b981 Unpaired pointer-authentication hints (frame.pac_hints, GitHub #83): with VR running, Star Wars Pinball VR died with SIGILL ILL_ILLOPN at an autiasp (*pc=0xd50323bf) on Unreal's HttpManager thread: its libUE4.so has 38 paciasp but 40 autiasp (OpenSSL's Poly1305 NEON assembly). Quest CPUs run PAC hints as NOPs, the Frame's CPU checks them. In a library whose counts differ, every paciasp/autiasp becomes a NOP in place (libraries using retaa/pacibsp forms are left alone). Suggested by the new triage signature pac-unpaired 2026-10-08 21:50:01 -04:00
spoopyghosty0 6782ace43d Live view: status and actions on one row, Play in/Quality/address on a second (one row squeezed the status into single words); a bottom gap keeps the in-window player off the window edge; while it plays in the browser or mpv the stage says so with Open in browser; ui_smoke renders the streaming bar with a local test stream 2026-10-08 21:48:11 -04:00
spoopyghosty0 71447c932f Live view: the in-window player falls back only when it shows no picture (position not moving within 12 s) or the stream really ends; media_kit's complete=false at start isn't a failure, its error messages are logged 2026-10-08 21:36:25 -04:00
spoopyghosty0 a1b0a3f350 Unreal OVRPlugin stand-ins (frame.unreal_ovrp_entrypoints, GitHub #83): Unreal's Oculus module dlsyms every ovrp_* function of the OVRPlugin it was built against and ANDs the results; OVRPort's OpenXR OVRPlugin lacks ovrp_GetPTWNear (Star Wars Pinball VR, UE 4.25 / OVRPlugin 1.44), so the wrapper failed, OculusHMD never pre-initialised and the game crashed without an HMD. Analysis records the engine library's ovrp_* names (extra.unreal_ovrp_lookups, ANALYSIS_VERSION 3); the patch compares them with the shipped libOVRPlugin.so and adds a generated libfp_ovrpstubs.so (DT_NEEDED of the plugin: dlsym on its handle searches its dependencies) whose stand-ins return ovrpFailure (stubgen result=). libUE4.so stays byte-identical. Suggested when the newest OVRPort runtime's plugin (tools.overport.runtime_lib) lacks a looked-up name (bundled fallback before a runtime is downloaded) 2026-10-08 21:33:06 -04:00
spoopyghosty0 df864e9fed GL shim keeps Unreal's multiview (GitHub #83): Unreal 4.25 only enables mobile multiview when GL_OVR_multiview, GL_OVR_multiview2 and GL_OVR_multiview_multisampled_render_to_texture are all listed (FOpenGLES::ProcessExtensions), so hiding the last one with gl_hide_msrtt switched multiview off. For Unreal (libUE4.so/libUnreal.so loaded, MSRTT hidden, multiview kept) the shim keeps that extension visible and maps glFramebufferTextureMultisampleMultiviewOVR to the single-sampled glFramebufferTextureMultiviewOVR (log 'multiview multisampled render-to-texture (N samples) drawn single-sampled'). Unity/other engines unchanged. Previous source rebuilt byte-identical before the change 2026-10-08 21:26:30 -04:00
spoopyghosty0 45bcc76436 Live view plays in the window (flet-video, Windows/macOS), else an mpv window, else the browser; Play in picks, Open in browser always there, automatic fallback
ui/live_players.py: embedded_available (win/mac with flet_video; Linux only with Flet's full client + libmpv), find_mpv, order (pick first, browser last), low-latency mpv properties, MpvWindow (fails over when mpv quits at once). flet-video==1.0.2 only off Linux (its Linux plugin links libmpv.so.1: the bundle wouldn't start without it). Relay unchanged: native players read /stream.mp4 (checked with ffprobe/ffmpeg, from the start and mid-stream).
2026-10-08 21:22:08 -04:00
spoopyghosty0 6595a31048 ovrpshim: frame-begin gate and held-back physics update only with the per-game settings ovrp_begin_gate / ovrp_hold_physics (default off; Sniper Elite VR's recipe turns both on). With them BattleSisters' hands lagged and its loading screen stuttered (owner's headset test); Accounting+ was fine. libfp_ovrp.so rebuilt 2026-10-08 21:13:28 -04:00
spoopyghosty0 1a6c3321a1 Empty screens: bigger portal on a soft portal light, inviting headings and what each tab does (feature cards); idle Live view as a framed screen with Start; tips under Type on Frame 2026-10-08 21:03:37 -04:00
spoopyghosty0 b21ef9ece6 Library cards: the hover zoom stays inside the card (cover in its own rounded clip); a strip of art showed below the bottom fade 2026-10-08 20:33:59 -04:00
spoopyghosty0 9b3c3b330a Cover buttons: play arrow centred (Material's glyph is already optically offset), smaller (60 px cards, 48 px shelf); ui_smoke shoots the Install/Update cover buttons 2026-10-08 20:22:46 -04:00
spoopyghosty0 46d8c5c379 demo_record: cards clip (hover, Play, launch rings, game page Play/Install); ui_smoke's pretend Frame can launch games 2026-10-08 20:15:12 -04:00
spoopyghosty0 f4bcb658e1 Unreal GL shim (frame.unreal_gl_shim, GitHub #83): Star Wars Pinball VR crashes ~3 s in with fault addr 0x10000 on the RHIThread in libgallium_dri.so: Unreal's mobile MSAA renders through multisampled render-to-texture and Zink's find_rp_state indexes rendering_state_cache[6] one past its end. The GL shim hides MSRTT (gl_hide_msrtt); it now also keeps multiview for Unreal (libUE4.so/libUnreal.so, like Unity). Offered for Unreal GLES games, suggested by the new triage signature unreal-msrtt-crash, not from the APK alone. Old glshim source rebuilt byte-identical before the change 2026-10-08 20:11:40 -04:00
spoopyghosty0 76542b53e5 Library cards: hover lift, art zoom and scrim; frosted Play/Install button that fills on hover; launch animation (portal rings + Starting on Frame…); filled Frame glyph on solid buttons
C.CoverButton (grid + shelf), library.card_hover/hover_motion/scrim, game page Play shows Starting on … with a spinner; glyphs.solid + frame-solid.svg for primary buttons; reduce motion skips lift/zoom/rings. ui_smoke: card hover, button hover and launch frames.
2026-10-08 20:05:15 -04:00
spoopyghosty0 088dac8fb6 Callout bars centre their icon (text + buttons in one row); wrapping text keeps it at the first line 2026-10-08 19:48:06 -04:00
spoopyghosty0 4a511ca6e3 Merge origin/main into ui-refresh
Main's microSD drives (agent v63), Linux app icons (agent v64), OBB detection, re-analysis and catalog entries,
on top of the UI refresh. Our monitor modules ({"modules": [...]}) move to agent v65 (MIN_AGENT_MODULES = 65).
Main's new UI uses the refreshed components: "Move to…" is in the game page's sectioned menu (MenuState.movable),
the .obb question and the Move dialog use C.dialog, the Frame page's drive picker C.dropdown, line-style icons.
2026-10-08 17:01:24 -04:00
spoopyghosty0 35da9c569a Updates show a changelog: every version since yours in the update dialog, What's new after updating, Settings changelog
updates.fetch_changelog (GitHub releases list, cached 6 h, offline-safe), whats_new() strips the release footer, changelog_for/recent_history/pending_news (setting update.last_seen_version); update dialog lists each version (newest open), Library bar after an update, Settings -> Updates What's new...; ui_smoke --update uses fake changelog data.
2026-10-08 16:56:10 -04:00
spoopyghosty0 dec7b2a9ec Transit portal in 3D: the cover passes over the rim's blue half and under its orange half; soft drop shadow
The portal is drawn as two layers around the cover (portal_back: shadow, hole, PC half; portal_front: Frame half), arcs of one ellipse in a shared viewBox. The track is taller than the cover so the ring shows above and below it. Shadow faked with stacked translucent outlines (flutter_svg has no filters). TRANSIT_REV 2.
2026-10-08 16:53:57 -04:00
spoopyghosty0 ba1ed72a55 docs: Install with FramePort button (Portal glow badge: still, animated, hover SVG + 2x PNG) and INSTALL_BUTTON.md 2026-10-08 16:42:04 -04:00
spoopyghosty0 e4f70144e9 Unity split-binary builds expect an OBB too (GitHub #92): analysis extra.unity_split → expects_obb, ANALYSIS_VERSION 2
analysis/unity_split.py: an XR-plugin build (Oculus XR Plugin / Unity OpenXR library) without assets/bin/Data/UnitySubsystems/ in the APK (zip names only), or BuildSettings listing more scenes than the APK has levelN files (globalgamemanagers loose, or its node read from the head of data.unity3d: only the blocks up to it). Exact on the 48 Unity APKs of both dump folders: all 13 split builds flagged, none of the 17 full builds (whose OBBs are asset bundles or sound banks). The missing-OBB warning and launch-test finding now cover them; existing entries are re-analysed at the next start.
2026-10-08 16:39:12 -04:00
spoopyghosty0 020334b37b OBBs found by file name: (main|patch).<versionCode>.<package>.obb next to the APK, in an obb folder beside an apk folder, or in a SideQuest backup (GitHub #85, #91)
quest_dump.find_data: after the folder-layout checks, search the APK's folder, its parent and up to 3 levels below each (folders with their own APKs are other games) for the package's expansion files; the folder with the APK's own versionCode wins, per kind the newest file not newer than it, else any version. When that folder holds other things too (the APK, other games' OBBs), the entry gets data_files and only those files are uploaded, counted and deleted with the game; no language-pack/content-file lookup there. Both dump folders scan identically (62/47 games).
2026-10-08 16:39:11 -04:00
spoopyghosty0 24a98ab263 Linux apps use their own icon (agent v64): Desktop Mode entry, Steam shortcut, library
The agent copies an AppImage's .DirIcon / the bundled .desktop file's Icon= (hicolor, pixmaps; symlinks resolved
and kept inside the app folder) to <anchor>/artwork/app-icon.{png,svg} at finalize_linux and when menu entries are
refreshed, and uses it for the menu entry's Icon= and the Steam shortcut (PNG only) unless the user chose an icon
(artwork/.icon-source = custom, sent by the PC with every art upload). StartupWMClass comes from the app's .desktop
file. A PNG goes back to the PC and becomes the library icon when there is no pick or store icon (no .picked;
.app-icon marker); folder apps get it at add time.

Closes #99
2026-10-08 16:33:16 -04:00
spoopyghosty0 ba2ddef525 Transit: the portal stays at the PC/Frame boundary and the game passes through it during the upload; short Library right-click menu (full menu on the game page)
- transit.position(): Analyze/Patch/Sign bring the cover up to the portal (fixed at PORTAL_AT = 0.5), Upload carries it across under the portal (drawn on top), Install/Test on to the headset; steps row weighted so Upload sits under the portal
- menus.quick_menu: Play · Open game page · Select · Install/Update · Game settings… · More actions… · Uninstall · Remove; job: Show progress · Cancel · Open game page
- More actions… opens the game page and its full '…' menu (a ContextMenu around the button, opened from code: app.open_game_menu)
- tests for the position mapping and the short menu; help text and CLAUDE.md updated
2026-10-08 16:28:56 -04:00
spoopyghosty0 58563b99a5 Re-analyse library entries an older FramePort analysed (GitHub #104)
detect.ANALYSIS_VERSION (stored as analysis.extra.analysis_version, bumped
whenever analyze() gains a field): Quest/Android entries below it whose APK
still exists are analysed again in a background thread at the GUI's start
and before a build (pipeline.refresh_analyses). Only analysis and suggested
change; recipes the user didn't edit follow the new suggestion (e.g. an SDL
app gets frame.sdl_clipboard once sdl_java exists), user recipes, tags, art,
titles and builds stay. An unreadable APK keeps its entry and is marked
analysis_failed (not retried until the next bump); a missing one is tried at
a later start. Analyze again sets the version too. Rift/Linux entries skipped.
PLAYBOOK row + CLAUDE.md note.
2026-10-08 16:25:35 -04:00
spoopyghosty0 9013a7d13c Catalog: AgeOfJoy (works)
Closes #103
2026-10-08 16:14:38 -04:00
spoopyghosty0 f36a17b46a Catalog: Pistol Whip (works)
Closes #100
2026-10-08 16:14:36 -04:00
spoopyghosty0 7335ff2ce5 Catalog: VR HOT Quest (works)
Closes #98
2026-10-08 16:14:33 -04:00
spoopyghosty0 21c039f9b3 Catalog: Retronika (works)
Closes #97
2026-10-08 16:14:31 -04:00
spoopyghosty0 b20591540b scripts/demo_record.py: record short clips of the real GUI's animations (transit, live card, navigation, selection, themes)
- One Playwright context per clip (record_video), web mode with ui_smoke's fake Frame; MP4 (x264) + poster JPG via ffmpeg, blank start trimmed
- ui_smoke: install_fakes()/attach_fake_frame() factored out so the recorder reuses them (behaviour unchanged)
2026-10-08 15:40:40 -04:00
Lucas-Mathieu 8814b1ce56 Merge remote-tracking branch 'origin/main' into fix/batman-cutscene-playback
# Conflicts:
#	src/frameport/pipeline.py
2026-10-08 17:49:12 +02:00
spoopyghosty0 f3dee5425e Library: the On your Frame shelf stays in select mode (hiding it moved the grid under a drag-select) 2026-10-08 00:13:25 -04:00
spoopyghosty0 676b025c5b ui_smoke: steps wait for their screenshot and mouse actions (names always match what they show)
- driver/shooter hand-off: the driver runs a step, signals ready, then waits (240 s max) for the shooter's screenshot + mouse actions before the next step; fixed STEP_SECONDS and the compensating sleeps (library-typed/-cleared, --hover, --gestures, --links) removed
- new step library-shelf-hover (the shelf's first card hovered: its Play button); hover positions per step
- mouse actions that save their own picture (menus) skip the duplicate -mouse shot; the -mouse shot waits the paint delay (typed search showed half-faded results)
- --links keeps the 1440x900 Add games click; --gestures positions updated for the shelf, fake screenshots keep the same day layout at any hour
2026-10-08 00:10:09 -04:00
spoopyghosty0 fac72d17c8 Setup checklist with inline fixes, gentle motion (Reduce motion setting), Settings index
- C.checklist/C.Check replace status_row: Frame 'Ready to play' (Install Lepton, Install Proton, Test, Restart the Frame when kernel keys run high), Settings Tools (Install per missing tool) and This PC
- Views fade in (150 ms AnimatedSwitcher, same-route redraws swap in place); the wordmark portal pulses once on connect; Settings > Appearance 'Reduce motion' (ui.reduce_motion) turns both off
- Settings: fixed index column (Appearance first, Remove FramePort last) that scrolls to sections; ui_smoke step settings-index
- Tests: checklist items, section order, reduce-motion default
2026-10-07 23:49:39 -04:00
spoopyghosty0 a2fa6021f5 Library 'On your Frame' shelf, cover-tinted game pages, portal empty states
- Library: 'On your Frame' row above the grid (installed/outdated games on the connected Frame, most recently used first, max 8, wide covers, Update ready pill, Play on hover, click opens, right-click = the game menu); hidden while searching/filtering, in select mode or without a Frame; cards reused, updated with the grid
- Game page: cover tint (thumbs.compute_tint: Pillow quantize, most common colourful entry, clamped to lightness 0.35-0.55 / saturation <= 0.55, cached as t_tint_<sha8>.json next to the thumbnails, computed in prewarm or in the background) as a glow around/inside the hero and a faint wash at the top of the page
- C.empty_state: the transit portal + the context icon on a soft glow (C.portal_scene) for every empty state
- Tests: shelf selection, tint helpers, compute_tint cache; radius-token test ignores RadialGradient fractions
2026-10-07 23:35:49 -04:00
spoopyghosty0 c492e662cb Quiet rows (actions on hover, portal-fade selection), Live view and Type on Frame trimmed, transit upload progress monotonic
- C.quiet/C.reveal: row/tile actions + checkboxes at opacity 0 until hover, keyboard focus or selection (space kept)
- Files rows: checkbox shows on hover/focus/select mode, actions on hover/focus or when the row is selected; selected rows get C.selected_style
- Screenshots: checkbox on hover/focus or while anything is selected; selected tiles get a 2 px accent border + subtle fade; labelled "Select all"
- Library select mode: selected cards get the 2 px accent border + subtle fade (hover scale/shadow unchanged)
- selected_style(subtle=True) for overlays on pictures
- Live view: one help line + help icon, status/Quality/Start in one bar, portal + headset empty-state hint while not streaming
- Type on Frame: the repeated callout replaced by one line + help icon under the input
- Transit: Monotonic keeps the bar from moving back within a run (a new stage cleared the job's fraction); cover 1.4x larger and placed just before the portal; tests
2026-10-07 23:20:59 -04:00
spoopyghosty0 ce803340b0 Install progress as a transit: the game crosses the portal from the PC to the Frame (sidebar, game page, Activity)
- glyphs.transit_parts: the logo's monitor, portal and headset drawn whole (same paths/strokes), per theme colours, cached by TRANSIT_REV
- ui/transit.py: pure stage-name mapper (Analyze/Patch/Sign/Upload/Install/Test, overall fraction, waiting/failed, frame/pc) + tests
- components.Transit: created once, set() updates properties; cover rides into the portal at the progress head
- sidebar activity card (compact), game hero (updated from _on_job, no re-render), Activity running tile
- ui_smoke: a pretend running install (transit-game, transit-activity)
2026-10-07 23:08:58 -04:00
spoopyghosty0 3e8a480ea6 Android version / web wrapper: warnings only, never unsupported from the manifest alone (owner's call): the recipe notes, game page (warning callout) and CLI say what the manifest asks for; a launch test's triage (android-too-new, web-wrapper) decides 2026-10-07 23:08:36 -04:00
spoopyghosty0 4be34d20ca Live Frame card: the running game, its frame rate and the battery in the sidebar, from the shared monitor hub (Settings switch)
- app owns one MonitorHub; the Monitor tab subscribes (all modules) instead of owning a MonitorSession; the hub reconnects, _poll retries an error
- sidebar Frame card: battery ring (C.gauge), name + connection line; now-playing row (art, title, playing time, fps by level, 2-min sparkline) opens the Monitor
- card subscribes to games + battery every 5 s while connected, paused during uploads; setting ui.live_frame_card in Settings > Appearance
- ui/frame_card.py pure helpers + tests; ui_smoke injects FakeMonitorSession into the hub
2026-10-07 22:53:11 -04:00
spoopyghosty0 ea2cd893b4 Linux apps in Desktop Mode's menu: per-app switch on the game page, docs
The game page's "What FramePort will do" for a Linux app gets a Desktop Mode switch ("In the menu and on the
desktop", default on): kept in the library entry (desktop_entry; patches don't apply to Linux apps), passed to
finalize_linux at install and applied at once on the Frame through the agent's desktop_entry command when the app is
installed. The agent side (menu + desktop .desktop files, launcher with FRAMEPORT_DESKTOP=1, refresh for existing
installs, removal on uninstall/purge) came with agent v63. Help text desktop_entry, INSTALL/FRAME_RUNTIME/PLAYBOOK,
CLAUDE.md. Game Mode's Steam Input problem from the report is separate.

Closes #84
2026-10-07 22:46:38 -04:00
spoopyghosty0 a90a8c27dc Install to and move games to a microSD card: Frame page Storage section, game menu Move to…
The Steam Frame page lists the Frame's drives (filesystem, free space, games on each; exFAT/NTFS cards say why they
can't hold games) and sets where new games go (library setting install.drive, default internal storage). Game menu
→ "Move to…" (Frame-installed games, agent v63) picks a drive in a dialog and queues a Frame job that follows the
agent's copy with progress (cancellable until the copy starts). The game page and the Frame page's installed list
show "on SD Card" / "SD Card not inserted"; a game whose card is out counts as installed, not "Not installed".
Help texts install_drive / move_game, ui_smoke --fake-frame drives + the Move dialog, docs (FRAME_RUNTIME "Where
FramePort keeps games", INSTALL, PLAYBOOK row, CLAUDE.md).

Closes #90
2026-10-07 22:46:37 -04:00
spoopyghosty0 9d0d72c40a Agent v63: games on another drive (microSD) and Desktop Mode entries for Linux apps
#90: `drives` lists internal storage, the drives under /run/media (from /proc/mounts) and the drives of Steam
library folders, with free space and whether they can hold games (vfat/exfat/ntfs can't: Lepton data and Proton
prefixes need Unix permissions and symlinks; read-only mounts neither). Games on a drive live in
<mount>/FramePort/<pkg>; the anchor stays internal, so the Steam shortcut never changes. prepare/prepare_pcvr/
prepare_linux take dest (free space from it); a drive that isn't mounted is an error, never a silent fallback to
internal storage, and an installed game keeps its folder. `move` (refused while the game runs, free-space check)
renames on the same drive, else copies with cp -a (in podman's user namespace when podman is there: container-owned
files), checks file count + bytes, retargets symlinks into the old folder (LibOVRRT redirect), points launch.sh at
the new folder (Quest: the app_dir line; Linux/PC VR: written again from the install record, which now keeps env and
game args) and rewrites deployment.json before removing the old copy; detached via systemd-run with a status file
(move_status). list_installed adds drive + drive_missing; launch refuses a game whose card is out; cleanup may remove
leftovers in a drive's FramePort folder (never an installed game); purge/uninstall remove an empty FramePort folder.
PC: install/drives.py (list, setting install.drive, move with progress, picks a running move up again), targets
pass dest for Quest/PC VR/Linux installs, CLI `frameport frame drives`, `frameport frame move <pkg> --to`,
`frameport install --dest`.

#84: finalize_linux writes ~/.local/share/applications/frameport-<slug>.desktop (+ an executable copy on ~/Desktop
when it exists; Exec quoted per the Desktop Entry spec, X-FramePort-Package marks ours) unless desktop_entry is off;
`desktop_entry` command switches it per app; ensure_host_fixes adds entries for existing Linux installs and drops
stale ones; uninstall/purge remove them. The Linux launcher with FRAMEPORT_DESKTOP=1 skips the Steam-parent
watchdog and Steam's display (upgrade_launchers updates existing launchers).
2026-10-07 22:46:36 -04:00
spoopyghosty0 56d2ac117f Missing OBB: analysis extra.expects_obb (Unreal's GameActivity.bHasOBBFiles, UE4 and UE5 names); a game that expects one but has no data folder gets an error callout on the game page, a question before install (default: skip) and a warning in frameport scan/install, and a launch test without frames gets the missing-obb finding from the library (GitHub #85: TRIANGLE STRATEGY was installed without its OBB and hung silently after OVRPlugin's JNI_OnLoad). In the dumps every Unreal game with the flag has its data folder 2026-10-07 22:42:41 -04:00
spoopyghosty0 cb8202d891 Scan: OBBs in more backup layouts (quest_dump.find_data_dir): a <package> folder with .obb files up to 3 levels below the APK's folder (obb/<package>/, OBB/, Android/obb/<package>/) or 2 below its parent, skipping neighbouring folders with APKs of their own (other games/versions); <game>/apk(s)/ + <game>/obb/<package>/ (SideQuest-style backups) is one game named after <game>. The data folder is always the one holding the .obb files (obb/<package>/ used to be sent as obb/ and land one level too deep). Both dump folders scan exactly as before 2026-10-07 22:42:40 -04:00
spoopyghosty0 029b0674d1 Web wrappers: a Trusted Web Activity (manifest meta-data android.support.customtabs.trusted.DEFAULT_URL, resolved through resources.arsc, or androidbrowserhelper's LauncherActivity) is analysis extra.web_wrapper with its URL; suggested status unsupported, the game page/CLI say to open the site in a browser (GitHub #86 Mahjong Table VR opened Meta's com.oculus.browser, which the Frame doesn't have). Triage web-wrapper 2026-10-07 22:42:39 -04:00
spoopyghosty0 c852473a43 Android version gate: analysis records the APK's minSdk (extra.min_sdk, read from the original: OVRPort lowers it to 29); above 32 the suggested status is unsupported and the game page/CLI say which Android it needs (GitHub #71/#72: minSdk 34 apps crash at start on android.window / VarHandle fences, Lepton runs Android 11). minSdk 32 stays allowed: 16 working catalog games declare it. Triage android-too-new 2026-10-07 22:42:38 -04:00
spoopyghosty0 a4e8e20fcd Library: start typing anywhere to search
- Keys typed on the Library (no dialog open, search not focused) go into the search and focus it, cursor at the end; Backspace removes the last character; Esc in a non-empty search clears it
- Pure helpers library.typed_char / should_capture (tested); C.dialog_open reads Flet's page._dialogs (snack bars don't count)
- Search hint "Type to search games and tags"
- "Add a PC VR game folder…" is now "Add a PC game folder…" (it adds Windows games too)
- ui_smoke: library-typed step types "arc" after clicking the header
2026-10-07 22:41:34 -04:00
spoopyghosty0 fc1bee2673 Menus: grouped sections with headers, Select near the top, launch test under Troubleshoot, destructive last; compact rows
- ui/menus.py (no controls): menu_sections() builds a game's menu for the Library right-click and the game page's "…" (Header entries for Artwork/Recipe/Troubleshoot, destructive items last after a divider; the page leaves out what its hero and Where-it's-installed cards offer; Type on Frame left the game menu; "Run launch test on …"; Analyze again also on the page); process_menu() for Monitor (Copy first, End…/Force quit… with distinct icons, End game… last, disabled "Protected by SteamOS")
- C.menu_items: 32 px rows, small-caps headers (dropped when empty), ICON_S icons, disabled items without a handler; C.menu_button/C.check_item; menus get a BORDER_STRONG border + shadow (PopupMenuButton and theme popup_menu_theme for ContextMenus)
- Add games menu: new labels/icons, Rescan folders at the end (toolbar button kept); docs/help updated
- Icons whose ROUNDED variant is filled are OUTLINED again (Monitor nav, Sleep, file types, …); the consistency test checks an explicit line-style allow-list
- Library Tags chip: the tag icon no longer overlaps its label
- tests/test_menus.py; ui_smoke shoots the Add games and game page menus
2026-10-07 22:30:48 -04:00
spoopyghosty0 acf1ef1da2 haptic_fix: keep revision 2 (owner's call, drop this commit to keep #81's bump)
The PCM conversion is preventive (Sniper Elite VR and Hyper Clinic send envelopes), and revision 3 would show
'Update on Frame' for every build with adapter.haptic_fix (suggested for every OVRPlugin game), including builds
where upstream.py already left the workaround out. Same reasoning as rect_clamp. New builds get the PCM conversion
anyway (the artifact is rebuilt).
2026-10-07 22:27:16 -04:00
spoopyghosty0 5fe120e897 Sniper Elite VR follow-up: merge the new haptic OOM PLAYBOOK row into the existing one (same OVRPort bug, now with the upstream-fix note); wrap unity_oculus_check's description to 120 columns (ruff); regenerate docs/GAMES.md 2026-10-07 22:25:43 -04:00
spoopyghosty0 41624c8dbd Rebuild libfp_ovrp.so and libframe_xrshim.so from the reviewed sources (native/build.py --only ovrpshim,xrshim, NDK r27c; the previous sources reproduce the committed artifacts byte for byte) 2026-10-07 22:23:19 -04:00
UnicronNL e8ec39db86 Sniper Elite VR: works (GitHub #81)
- native/ovrpshim: libunity.so's ovrp_BeginFrame/ovrp_EndFrame go through the shim; the frame wait waits <= 50 ms
  for the last waited frame to begin and skips otherwise (the game deadlocked at its Init scene when Unity skipped a
  begin); a begin/end of another index while a wait is outstanding gets the waited index. The physics-step
  ovrp_Update2 is held back (prediction 0 located the controllers in the past on the Frame: the hands lagged).
  frame.unity_oculus_check revision 5.
- native/xrshim: PCM vibrations (XrHapticPcmVibrationFB) become one plain vibration too; haptic_fix revision 3.
- Catalog: works with frame.unity_oculus_check + haptic_fix, min_app 0.12.1. Triage unity-frame-not-begun (info),
  PLAYBOOK rows and how to get all-thread backtraces with gdbserver64 inside the container, test.
2026-10-07 22:21:08 -04:00
spoopyghosty0 936d4b188b BONELAB: catalog note leads with the per-build status (GAMES.md showed only 'One recipe for both builds.'); regenerate docs/GAMES.md (also adds the missing Doom3Quest row) 2026-10-07 22:20:24 -04:00
UnicronNL e9ab1489cc BONELAB 1.2974: frame.slz_vulkan_hooks (GitHub #82)
Stress Level Zero's graphics plugin (libSLZQuestNative.so) hooks Unity's Vulkan start-up and vkCreateSampler; on
the Frame its vkCreateInstance wrapper calls an invalid pointer right after OVRPlugin's pre-init instance is
destroyed (pc 0, a jump into an unloaded library, or a hang). The new patch turns the plugin's two registrations into
no-ops (found through its exported functions, 4 bytes each), so Unity starts Vulkan itself; ~35 s of shader
prewarming without the plugin's pipeline cache, then 72 fps (headless). One BONELAB recipe serves 1.2068 and 1.2974.
Triage slz-vulkan-hook-crash, PLAYBOOK row, tests with a fixture built from tests/fixtures/src/fakeslz.S.
2026-10-07 22:15:57 -04:00
spoopyghosty0 1043eb1d1b Catalog: Richie's Plank Experience (works)
Closes #95
2026-10-07 22:13:33 -04:00
spoopyghosty0 0f444da595 Catalog: RUINSMAGUS (works)
Closes #89
2026-10-07 22:13:27 -04:00
spoopyghosty0 fda644d036 Catalog: GORN2 (works)
Closes #88
2026-10-07 22:13:19 -04:00
spoopyghosty0 fa0fbbd29a Catalog: Time Crisis VR (Experimental) (works)
Closes #80
2026-10-07 22:13:14 -04:00
spoopyghosty0 ad8847509b Shared components: one dialog, button, field, segmented, selection and spinner style everywhere (+ guard test)
- theme: RADIUS_XS, ICON_S/M/L, DIALOG_S/M/L, T_DISPLAY (scaled); tooltip radius token
- components: C.dialog/C.viewer (12 px corners, semibold title, size tokens), C.danger (+outline), C.primary_menu (Add games looks exactly like C.primary), C.field/C.search/C.dropdown, C.segmented, C.selected_style, C.spinner, C.hoverable; C.dot scales its size
- every AlertDialog, ProgressRing, TextField, Dropdown and the Monitor SegmentedButton migrated; Files locations, exe rows, theme cards and the sidebar use selected_style (dual: portal fade + white icon)
- ROUNDED icons everywhere (all OUTLINED had a twin), CLEAR -> CLOSE; raw radii/title sizes -> tokens; ink on clickable tiles/chips/switch labels
- tests/test_ui_consistency.py guards it (AST over ui/)
2026-10-07 22:08:36 -04:00
spoopyghosty0 627c05bf98 UI text: snooze for Later, Frame picker, clear labels, tr() everywhere, one vocabulary
- updates.snooze()/hidden(): the update banner's "Not now" hides a version for 24 h (update.snoozed); "Skip this version" unchanged
- Frame page: "Switch Frame…" opens a picker of remembered Frames (+ "Find another Frame…"); separate Disconnect button
- Labels: Update Steam art on Frame, Reset to suggested recipe… (asks first; pipeline.reset_recipe without the duplicated Rift branch), Uninstall from this PC…, Change program…, Share working recipe…, Force quit/End game…, ellipsis on everything that opens a dialog/picker or asks
- One Developer Mode path (Settings → System → Enable Developer Mode / Settings → Developer → Pair new host); linux_app help points to FEX
- tr() for the remaining visible strings (install status, job stages, update dialog, settings, files); this PC, recipe, program, for example, 50%, US spelling, Steam IDs, arm64
- Long callouts (live view, Type on Frame, pairing, library empty state) shortened, details in help texts
- tests/test_ui_text.py: untranslated literals, "..."/"e.g.", vocabulary, Switch Frame not wired to disconnect; snooze expiry test
2026-10-07 21:50:21 -04:00
spoopyghosty0 b82b714165 Monitor hub: one modular monitor stream for every view (agent v63: _monitor collects only requested modules)
- agent: {"modules": [...]} control message, monitor_plan(); default = all (older clients unchanged); GPU busy needs the process scan
- frame/monitor_hub.py (no Flet): subscribers with modules + interval, one session for the union and the fastest interval, fan-out, pause, reconnect, filter/pause/kill/end_game pass-throughs
- MonitorSession.set_modules (agent >= 63); tests/test_monitor_hub.py
2026-10-07 21:42:37 -04:00
spoopyghosty0 3446999109 Upstream fixes replace our workarounds per build (patches/upstream.py)
Each build probes OVRPort's output for registered upstream fixes and leaves out the workarounds they make
unnecessary (recipes unchanged: builds with an older runtime keep them). Logged ("not needed: ..."), recorded in
build.superseded, left out of settings.conf at install, shown on the game page and in `frameport show`;
FRAMEPORT_KEEP_WORKAROUNDS=1 turns it off. Registered: ovrport.haptic_envelope (-> adapter.haptic_fix) and
ovrport.microphone_stream (-> frame.ovr_microphone), both fixed in OVRPort runtime 3.4.3-aa54c3f (ovrport/app#73;
haptics verified in the headset with Lucky's Tale). Probes checked against both real runtimes; fixtures in
tests/fixtures/src/fakeruntime.S.
2026-10-07 21:02:12 -04:00
spoopyghosty0 0bfdfbd0e8 Themes for the features merged from main: Monitor tab, Sparkline, Portal's own info colour
- Monitor: its colour maps (levels, process groups, CPU clusters, power split) refill on a theme change; restyle()
  stops and drops the Monitor view like the other kept views
- Sparkline's default colour is read when it's built (it was captured at import)
- Portal themes: INFO is a cyan of its own (#5CD0E6); it equalled PC's blue, so the Monitor showed Steam and SteamVR
  processes, and CPU and memory, in one colour
- Translation template regenerated after the rebase
2026-10-07 20:13:29 -04:00
spoopyghosty0 19addc1570 ui_smoke: a warm-up screen while the web fonts load (the first shot measured text with a fallback font: wrapped and overlapping labels) 2026-10-07 20:01:10 -04:00
spoopyghosty0 1f35c77a69 Selected sidebar tab: white icon on the portal fade in dual themes (an orange icon clashed with it) 2026-10-07 20:00:45 -04:00
spoopyghosty0 5973b6c85c Wordmark with a portal between Frame and Port; glyphs match Material icons in menus
- Dual themes: "Frame" (accent), a small tilted portal (rim from the accent into SECONDARY around a dark hole,
  generated in the active theme's colours, also for installed themes: glyphs.portal_mark), "Port" (SECONDARY)
- Glyphs: stroke 2 (was 1.75, lighter than Material's icons); menu items tint them TEXT like Material's own icons
- ui_smoke: waits until the web server listens (a fixed 6 s failed with a second smoke run on the machine)
2026-10-07 20:00:45 -04:00
spoopyghosty0 d74dc1aa61 Portal (OLED) and Original themes, installable theme files, both portals in the Portal theme
- Built-ins: Portal (default), Portal (OLED) (true black) and Original (the former violet; "classic" still maps to it)
- Portal now uses both of the logo's portals: orange stays the main action and selection; the new SECONDARY token
  (blue) colours outlined buttons, progress bars and switch tracks; "dual" themes add the blue-to-orange sidebar
  edge, the two-colour wordmark and the selected tab's fade. Original looks exactly as before
- Theme files: <data>/themes/*.json (name, base, dual, any subset of the colour tokens), checked on install (dark
  background, readable text, known colours; T.ThemeError says why). Settings -> Appearance: "Install theme file...",
  "Copy this theme as a file" (a template), a bin icon removes an installed theme
- docs/THEMES.md + docs/themes/example-theme.json (kept valid by a test); ui_smoke --themes includes installed themes
2026-10-07 20:00:44 -04:00
spoopyghosty0 7b14207f81 Portal theme, FramePort logo and glyphs, live theme switching in Settings
- Logo (a monitor that goes through a tilted portal and comes out as the Steam Frame) in the sidebar and welcome
  screen; src/assets/icon.png is the bundles' app icon (flet build; PyInstaller fallback via --icon)
- 24 px line glyphs (ui/icons/glyphs, ui/glyphs.py) replace Material icons for FramePort's own concepts: Frame,
  launch test, PC, live view, screenshots, Type on Frame, verified recipe, rebuild; shown as tinted images through
  C.as_icon and the button/menu helpers (same size as the Material icons next to them)
- Colour themes, dark only: "portal" (default; orange accent, blue PC side, status colours chosen so warnings never
  read as the accent) and "classic" (the previous violet). Settings -> Appearance theme cards switch live:
  app.restyle() rebuilds the shell and drops cached views (setting ui.theme)
- Theme colours are read when building: default arguments no longer capture tokens, module-level colour maps refill
  through T.on_change; tests/test_theme.py checks both
- ui_smoke.py --themes renders the picker and every theme after a live switch
2026-10-07 20:00:30 -04:00
Lucas-Mathieu e9eedb2241 Merge remote-tracking branch 'origin/main' into fix/batman-cutscene-playback
# Conflicts:
#	artifacts/SHA256SUMS
2026-10-08 01:57:46 +02:00
Lucas-Mathieu d3a35c307a Fix Batman native cutscene decoding and stereo composition 2026-10-08 01:57:29 +02:00
spoopyghosty0 d6f87f79a6 Live view: status updates from the ticker/start/stream-end threads go through components.LoopUpdater (the GUI logged 'dropped a patch for unknown control' during the owner's test, the same cause the Monitor tab fixed) 2026-10-07 19:52:09 -04:00
spoopyghosty0 24a1ef9f1d Live view: less delay with sound. The -1 s video input offset (1080p fix) made ffmpeg release video in clumps (output gaps up to 550 ms: a longer, stuttering browser delay, worse with sound); -0.25 s keeps 1080p from stalling and the output steady (measured on the Frame with sound playing). Player: no speed-up while sound is on (Chrome keeps ~0.6 s of audio ahead and stalled 8x/30 s at 1.1x, 1-2x without; same ~1 s lag; muted ~0.3 s); the Sound on tooltip says so 2026-10-07 19:44:46 -04:00
spoopyghosty0 ae4904c7d9 Live view: 1080p no longer drops frames with sound on (ffmpeg paced its inputs against pulse's late audio and stopped reading the video for up to 0.7 s, blocking fp_venc's writes; -itsoffset -1 on the video input keeps it from counting as ahead). Found with fp_venc's new timing stats (write_ms 17-38 avg, 260 max at 1080p) and a busy 1080p replay into the real pipeline: 20 s took 34-47 s with sound, exactly 20 s with the fix; output A/V spans unchanged 2026-10-07 19:13:45 -04:00
spoopyghosty0 b142f7c0ac Live view: Stop ends a backed-up stream (fp_venc blocked in a write never saw stdin EOF and ffmpeg's output wasn't drained after the channel closed: both stayed on the Frame holding the headset view open, found after the owner's 1080p run). The script reports its process group; close() waits 2 s and then kills the group. Helper uploads use their own temp name (two starts raced on fp_venc.new). Verified on the Frame: forced kill path and the normal stop both leave nothing running 2026-10-07 18:37:11 -04:00
spoopyghosty0 934f356927 Live view: the panel warns when the Frame drops frames (encoder stats per 10 s: late + skipped slots >= 5 %, 'choose a lower quality'); fp_venc stats add per-window convert/write timing and no-free-buffer counts to find why 1080p stalled (owner: 1080p choppy, 360p smooth; log: 208 of 320 slots, 172 late, 104 skipped) 2026-10-07 18:09:15 -04:00
spoopyghosty0 a61764960d Live view: even frame timestamps and VBR after the first headset test
Owner's headset test (720p): heavy compression artifacts and many dropped-looking frames.
- Frames stamped on arrival (wall clock) bunched when ffmpeg read several at once (gaps of 0-10 ms and 45+ ms). The
  input stays on the wall clock (read in step with pulse's audio; setts alone held output ~7 s), and setts now
  numbers the output frames on fp_venc's fps grid: every gap exactly 1/fps on the Frame, audio aligned.
- fp_venc --peak: VBR capped at the peak (else CBR as before), level picked for the peak. The hardware path uses
  1.5x peaks and higher targets (360p 1.5, 480p 2.5, 720p 5, 1080p 8, Full 10 Mbit/s); 3 Mbit/s CBR blurred every
  head turn. The x264 fallback keeps its rates.
- fp_venc's info/stats lines go to the app log (late/skipped frames next time); its errors still explain a stop.

Verified on the Frame: probe with VBR (no refused control); end to end through livestream.start at 720p32: 403
frames all 31 ms apart, audio 0-12.57 s next to video 0-12.58 s, late viewer on its keyframe at once, nothing left
after stop. Picture quality with motion still needs the headset.
2026-10-07 18:00:59 -04:00
spoopyghosty0 55a376cdfd fp_venc: exact NEON fast paths for 3:2 (720p) and 3:1 (360p) scaling
The box boxes repeat every 3 source pixels at these ratios, so the averages become rounding shifts (3:2: block
sizes 1, 2, 4) or an exact multiply (3:1: x7282 >> 16 = /9 for every sum), with no per-pixel scalar loop. Same
bytes as the generic path; 480p (852 px wide, irregular) keeps the generic path. Self-test adds 1158x648 -> 432/216
(columns left over after the 48-pixel steps) and times all four qualities.

Frame (2026-10-07): selftest ok (184 cases); 1920x1080 -> 720: 1.3-1.65 ms (was 5.6), -> 360: 0.7 ms,
-> 1080: 0.6-1.0 ms, -> 480: 2.8 ms.
2026-10-07 17:50:59 -04:00
spoopyghosty0 da7655ee25 Live view hardware encoder: fixes from the first device run
- fp_venc: accept the source's buffer count (the Frame's v4l2loopback has max_buffers=2; asking for 3 failed the
  probe); OUTPUT buffers remember which picture they hold, so repeats (standby, still scenes) skip the conversion
  (720p: 35% -> 2.5% of a core with the headset asleep).
- Pipeline: raw H.264 stamped on arrival (-use_wallclock_as_timestamps, same clock as pulse) with a 32-byte probe:
  -framerate was ignored, -fflags nobuffer lost the first seconds, frames numbered from 0 held all output ~7 s next
  to the sound. frag_keyframe so a requested keyframe starts a fragment (a joining viewer starts there).

Verified on the Frame (headset asleep): selftest ok (104 cases), probe at 360p-1080p, end to end through
livestream.start: 720p36 + AAC in 0.1 s fragments, audio and video spans match, a second viewer starts on its
requested keyframe at once, nothing left running after stop; FP_VENC_DISABLE=1 falls back to x264.
2026-10-07 17:50:57 -04:00
spoopyghosty0 0ae754fa5d Live view: hardware H.264 encoder (fp_venc), x264 stays as fallback
native/venc/fp_venc: own clean-room driver of the Frame's iris encoder (V4L2 stateful encoder interface, written from
native/venc/SPEC.md = kernel docs + values measured on the Frame). Reads SteamVR's headset-view webcam, box-scales and
converts RGB24 to NV12 (NEON, bit-identical scalar reference), CBR H.264 with SPS/PPS on every IDR, one frame per slot
of an even fraction of the panel rate read through DRM (96 Hz -> 32 fps). stdin "k" = keyframe, EOF = stop;
--probe/--selftest. Static freestanding aarch64 binary in artifacts/linux-arm64-bin (reproducible).

livestream: the script probes fp_venc and pipes it into ffmpeg (-c:v copy + AAC), else the old x264 30 fps path; the
helper is synced to ~/.local/share/frameport/bin by sha256 at stream start (no agent change); a new viewer asks for a
keyframe and waits for it; the status line shows the encoder and fps. Tests: hardware/fallback script branches,
keyframe-on-join, helper sync; tests/test_venc.py compares convert.c with a Python reference (native marker).
Not yet run on the device.
2026-10-07 17:50:55 -04:00
spoopyghosty0 ecfd2408cf Monitor: the detail boxes' first update goes through the event loop like the ticks (sent from the connect thread it could arrive after a tick's patch: 'dropped a patch for unknown control'); README screenshot with the new layout 2026-10-07 17:33:21 -04:00
spoopyghosty0 44dfd43692 Monitor feedback: updates from the stream's thread go through Flet's event loop (components.LoopUpdater: the window showed nothing until a click and the charts moved in bursts); intervals 0.1/0.25/0.5 s (0.5 s default; processes still scanned every 2 s, their GPU % averaged over it); charts in 1-second buckets (2 minutes at any interval); same-name processes with one parent collapse into a group row (click to open, end or force-kill all); tile headers line up; detail cards share one height; temperature boxes with mini charts and a Fan box; 'Monitor is using … of the Frame's CPU' 2026-10-07 17:26:20 -04:00
spoopyghosty0 29876ec046 README: Monitor section + screenshot (docs/images/monitor.png, ui_smoke --fake-frame --game: the fake monitor follows --game and sends two minutes at once so the charts are full). Monitor game card: the game's artwork is an art_fill control inside the box (copying its image onto the box showed nothing) 2026-10-07 17:05:15 -04:00
spoopyghosty0 3667f504ec Monitor agent after device tests (4XVR on the dev Frame): full names for processes whose comm the kernel truncated (Android's last 15 characters), power rails per cluster every 5 s and battery current/voltage every 2 s (I2C reads), temperatures every 2 s, fast render-fd rescans only for busy young game processes, end_game reports a game that wasn't running 2026-10-07 16:55:11 -04:00
spoopyghosty0 375b976dcd Monitor tab: a live view of the Frame while the tab is shown (running game with fps, CPU/GPU/memory/temperature/power/battery tiles with 2-minute sparklines, details: per-core load, sensors, power split, network) and its processes (filter Game / Steam & SteamVR / All, search, sort; right-click: end, force kill, end game; critical programs ask again). frame/monitor.py = session + pure helpers, C.Sparkline/MeterBar on Flet's canvas (no charts extension), ui_smoke FakeMonitorSession + --only, docs 2026-10-07 16:55:09 -04:00
spoopyghosty0 2a74e5ff64 Agent v62: _monitor stream for the Monitor tab (CPU/GPU/memory/pressure/temperatures/fan/power rails/battery/network, running FramePort games with fps from FrameBridge pacing and container cgroup usage, processes grouped by game/Steam/SteamVR/desktop with per-process GPU % from DRM fdinfo; end a process or a game). Kernel threads, command lines and render fds are cached so a sample stays cheap on the Frame 2026-10-07 16:55:06 -04:00
spoopyghosty0 0c623c2efc Install question: each file's name on its own line (long names end in an ellipsis, full name on hover), its type, size and checksum below; the narrow label column wrapped long file names 2026-10-07 16:51:32 -04:00
spoopyghosty0 d49b3ae884 Install links: FramePort-only manifest fields (frameport.description / frameport.icon) shown in the install question and used as the game's icon and description; readable titles for bare file links; handler scripts wait for the open window to take a link (a just-closed window's heartbeat swallowed clicks on Windows), the heartbeat is removed on close, and the Windows handler runs under conhost --headless (no console flash) 2026-10-07 16:35:21 -04:00
spoopyghosty0 f811261507 Lint: line lengths, import order, unused loop variable (ruff) 2026-10-07 16:09:03 -04:00
spoopyghosty0 ec701e6e56 Install links (FrameDrop's "Install with FrameDrop" button protocol), drop files on the Library, add a lone Windows .exe, VR / flat window choice, x86_64 Linux apps through FEX
- deeplink.py: framedrop:// and frameport:// links and the pasted https://framedropvr.com/install?manifest=|url= link; manifest framedrop.install/v1; https only (http on loopback), no credentials, no LAN addresses (also after DNS and redirects), sha256 checked, cancel/mismatch leave no .part
- urlhandler.py: per-scheme registration (Settings → Install links, links.framedrop / links.frameport, default on; another program's scheme only taken on request) through a handler script that queues the link and starts FramePort (a flet build bundle can't take a URL argument); the GUI's heartbeat + link inbox
- GUI: Add games → Install from a link… and Add a Windows program (.exe)…; confirmation before any download; files dropped on the Library (packaged app); CLI frameport open-link
- patch device.display_mode (Automatic / VR / Flat window), verified on the Frame with 4XVR
- x86_64 Linux apps run through FEX (agent v61: FEX installed unattended, STEAM_COMPAT_DATA_PATH set, RootFS from SteamOS's /usr/share/guestos/fex-mesa); verified on the Frame with an x86_64 test program; triage linux-x86-no-fex
2026-10-07 16:06:40 -04:00
spoopyghosty0 360dc105e1 Type on Frame is a sidebar tab (was a dialog): the virtual keyboard is connected while the tab is shown and removed when another tab opens or the Frame disconnects; the Frame page button and the game menu open the tab, the keyboard icon on the Frame card is gone. README screenshot redone 2026-10-07 09:24:17 -04:00
spoopyghosty0 892c7c13bd FrameBridge input_diag: report failed vibrations from the adapter's own xrApplyHapticFeedback (always hooked for haptic_scale, so the PR's wrapper never ran); adapter artifacts rebuilt 2026-10-06 23:13:52 -04:00
Molier 3b6128a1b0 FrameBridge input_diag (off by default, hooked only when on): logs each controller profile a game suggests with the runtime's answer (every path of a rejected one), the profile the runtime reports for each hand, OpenXR functions the runtime lacks and failing input, haptics and perf calls, each line once. Triage signature input-call-failed, PLAYBOOK row, host test tests/test_input_diag.py; artifacts not rebuilt 2026-10-06 23:13:51 -04:00
spoopyghosty0 4c42a528bd Merge pull request #79 from spoopyghosty0/catalog/issue-77
catalog: com.drbeef.doom3quest
2026-10-06 23:08:48 -04:00
github-actions[bot] 0e7c5264a0 catalog: com.drbeef.doom3quest (from #77) 2026-10-07 02:59:42 +00:00
spoopyghosty0 69a4fa6fa7 Notes: upstream trackers (#73 microphone, #74 haptics, #75 VrApi bridge) linked from the workarounds and the release checklist 2026-10-06 20:37:25 -04:00
spoopyghosty0 e6b9808beb Catalog: BlazeRush (unsupported: menu room unusable) 2026-10-06 18:22:47 -04:00
spoopyghosty0 b34330dc85 Catalog: Lambda1VR (works; needs its game data) 2026-10-06 18:22:45 -04:00
spoopyghosty0 ae69fe3831 Catalog: The Boys VR (works with space warp off) 2026-10-06 18:22:42 -04:00
spoopyghosty0 6d2dc95cc8 Catalog: Jurassic World Aftermath Collection (works; frame.vrapi_stub) 2026-10-06 18:22:40 -04:00
spoopyghosty0 474be9d34b FramePort 0.12.0 2026-10-06 18:21:32 -04:00
spoopyghosty0 7b4dff7d84 Notes: 2026-10-06 issue triage round (headset results, what was ruled out) 2026-10-06 18:19:12 -04:00
spoopyghosty0 885070a00c Catalog: Metro Awakening plays with space warp off and without Valve's VALVE_rpo layer (random jumping polygons otherwise; owner's headset test) 2026-10-06 17:22:56 -04:00
spoopyghosty0 67b58238a0 Catalog: Pinball FX VR with space warp off (stutters gone after loading in the owner's headset test) 2026-10-06 17:08:10 -04:00
spoopyghosty0 a1c87618f6 VrApi bridge: upstream-patches/input.cpp.patch (FramePort's input diagnostics) 2026-10-06 17:07:40 -04:00
spoopyghosty0 b4cec19283 VrApi bridge: input diagnostics also log the controller types asked for (and refused), button bits seen and thumbstick deflection (BlazeRush: no thumbstick, no hands) 2026-10-06 17:07:30 -04:00
spoopyghosty0 109c7e449c FrameBridge: a vibration with amplitude 0 becomes xrStopHapticFeedback (OVRPlugin stops vibrations that way; the last buzz otherwise ran its full 2 s in Jurassic World, The Boys VR, Creed) 2026-10-06 16:28:45 -04:00
spoopyghosty0 05ad7e4d25 GL shim: with GL_EXT_multisampled_render_to_texture hidden, its two functions become single-sampled stand-ins (TBXR calls them regardless: GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE, black eyes in Lambda1VR); frame.tbxr_vendor links the shim into TBXR's library 2026-10-06 15:37:08 -04:00
spoopyghosty0 1e96abebbb Agent v59: close Steam's 'Resume game' menu for 120 s after the first VR frame, up to 10 times (it came back after the first 30 s); still stops once the player opens the dashboard 2026-10-06 15:24:16 -04:00
spoopyghosty0 69da709ea2 Vibration: haptic_fix converts envelopes with their RMS instead of their peak (vibrations felt far stronger than on a Quest); new per-game setting haptic_scale ('Vibration strength') scales every vibration in FrameBridge and logs the first requests. VrApi bridge: input/vibration diagnostics every 5 s (BlazeRush controllers) 2026-10-06 15:22:57 -04:00
spoopyghosty0 d5f9c248e0 frame.avatar_stub: replace Meta's avatar loader with a do-nothing library when it can't find its driver and aborts the game (BlazeRush: OVRAvatar-Loader DisplayErrorAndExit); suggested by triage avatar-driver-missing 2026-10-06 14:53:53 -04:00
spoopyghosty0 cc39547e33 FrameBridge: add XR_KHR_opengl_es_enable when an app enables no graphics API extension (Lambda1VR's TBXR creates a GLES session without it; Meta's runtime tolerates that); log the final instance extension list once per launch. Lambda1VR now runs headless at 72 fps 2026-10-06 14:50:34 -04:00
spoopyghosty0 096d24e42b FrameBridge: when xrCreateSession fails with XR_ERROR_GRAPHICS_REQUIREMENTS_CALL_MISSING, ask for the graphics requirements on the app's behalf and retry once (Lambda1VR: Failed to create XR session: -50); triage graphics-requirements-missing 2026-10-06 14:33:02 -04:00
spoopyghosty0 2995413434 VrApi bridge: vrapi_PollEvent returns ovrSuccess_EventUnavailable (1002), vrapi_SetTrackingSpace returns an ovrResult, vrapi_CreateTextureSwapChain/2 take VrApi's 32-bit format enum (found while preparing the upstream PRs) 2026-10-06 14:31:40 -04:00
spoopyghosty0 4b34dbd218 Platform stubs: also stub ovr<Enum>_ToString helpers OVRPort's loader lacks (BlazeRush: ovrPeerConnectionState_ToString); ToString stubs return an empty string, not NULL; triage missing-ovr-symbol matches them 2026-10-06 14:08:20 -04:00
spoopyghosty0 a775e63e6d Install: build a game that was never built instead of failing with KeyError 'apk' (frameport install right after a scan) 2026-10-06 14:01:08 -04:00
spoopyghosty0 3cd4affcf6 frame.ovr_microphone: don't suggest it from libil2cpp.so (Unity's C# platform wrapper names every Meta function, so every Unity game with the Platform SDK matched); triage ovr-microphone-crash still finds a Unity game that crashes there 2026-10-06 13:58:56 -04:00
spoopyghosty0 51e553039a Catalog: Beat Saber (com.beatgames.beatsaber.lj369vr) (works)
Closes #68
2026-10-06 13:53:36 -04:00
spoopyghosty0 27ff49a137 Catalog: Creed (works)
Closes #60
2026-10-06 13:53:33 -04:00
spoopyghosty0 e70acf9269 Catalog: BodyCombat (works)
Closes #56
2026-10-06 13:53:29 -04:00
spoopyghosty0 1f6fce0fcc Catalog: Pinball FX VR (works with issues, MR not yet)
Closes #50
2026-10-06 13:53:25 -04:00
spoopyghosty0 188cb8fe68 Metro Awakening: libfp_vk.so rebuilt with the shim's new fixes; catalog entry with the contributor's recipe (use_alt, vk_spec_fixes, vk_hide_fdm); docs/GAMES.md regenerated; lint 2026-10-06 13:46:53 -04:00
UnicronNL 0744a126cb Vulkan shim: drop depth resolves of subpasses without depth, leave out null-image barriers and refuse null-image views (vk_spec_fixes), vk_hide_fdm hides fragment density maps (Metro Awakening crashed in the Frame's Vulkan driver, GitHub #65) 2026-10-06 13:46:52 -04:00
spoopyghosty0 1c8a0d63e7 BONELAB: libfp_ovrp.so rebuilt with the user-presence wrapper; catalog status 'issues' (1.2068 plays, the newer 1.2974 still crashes at start); docs/GAMES.md regenerated 2026-10-06 13:46:51 -04:00
UnicronNL 124ec594a6 frame.unity_user_presence: report the headset as worn to Unity's Oculus XR Plugin (BONELAB 1.2068's rig froze: OVRPlugin's ovrp_GetUserPresent2 turns 0 while worn, GitHub #66) 2026-10-06 13:46:31 -04:00
spoopyghosty0 90b528f9f2 Lint: wrap a long comment 2026-10-06 13:46:30 -04:00
UnicronNL 2da6082166 frame.ovr_microphone: guard OVRPort's ovr_Microphone_GetOutputBufferMaxSize against a not-yet-opened stream (The Walking Dead: Saints & Sinners Ch. 2 crashed after the logo, GitHub #67) 2026-10-06 13:46:30 -04:00
spoopyghosty0 3e61b07254 frame.asset_files: list the game data's *.pak content files as installed default asset files and answer DownloadById at once with the path (Star Wars: Tales from the Galaxy's Edge went black after loading, GitHub #61); shares the language-pack library 2026-10-06 13:46:08 -04:00
spoopyghosty0 48c3716ef5 frame.tbxr_vendor: Team Beef ports load openxr_loader_<maker> and pick Meta's path only for "meta": add an empty libopenxr_loader_valve.so and let "valve" match those checks (Lambda1VR stopped at start, GitHub #59) 2026-10-06 13:28:10 -04:00
spoopyghosty0 dca2d8b762 VrApi bridge: vrapi_PollEvent, RecenterPose, SetDisplayRefreshRate, GetSystemPropertyFloatArray (BlazeRush couldn't load: cannot locate symbol vrapi_PollEvent, GitHub #57); static check 'VrApi functions resolvable', triage vrapi-symbol-missing; upstream-patches regenerated (graphics.cpp.patch was stale: the Vulkan format twin was missing from it) 2026-10-06 13:23:52 -04:00
spoopyghosty0 2c4bbdbfec frame.vrapi_stub: replace Meta's leftover VrApi loader with a same-exports library that answers 0 (OVRPlugin on OpenXR called vrapi_SetPropertyInt before init and the loader aborted, GitHub #62); triage vrapi-before-init, direct-vrapi no longer matches it 2026-10-06 13:12:59 -04:00
spoopyghosty0 8a08733be8 Agent v58: Play re-adds a devkit entry Steam forgot (no NOT_IN_LIBRARY, no Steam restart); installs and art updates of devkit games only copy art; the app treats Steam's AppError_16 as already starting and ignores repeated Play clicks for 20 s (GitHub #41/#42) 2026-10-06 13:09:39 -04:00
spoopyghosty0 d7193bea19 device.foveation: per-game choice for Valve's eye-tracked foveation layer (default / fixed = FDM_DEBUG=disable_offsets / off = VK_INSTANCE_LAYERS=""), catalog field foveation (GitHub #69) 2026-10-06 13:06:36 -04:00
spoopyghosty0 c3522a05bb macOS: keep the entitlements when ad-hoc signing (re-signing stripped them; Scan a folder failed with ENTITLEMENT_NOT_FOUND, GitHub #63) 2026-10-06 13:06:35 -04:00
spoopyghosty0 8a9a428f1e Delete files on this PC: also remove the download manifest next to the game when it lists only the game's files (the folder stayed, so the download manager still listed the game) 2026-10-06 13:06:34 -04:00
spoopyghosty0 919d059add Notes: the jni build override and re-tagging a failed release build 2026-10-06 12:14:37 -04:00
spoopyghosty0 674c26f2d9 Build: override jni to 1.1.0 instead (jni_flutter 1.0.4 also generates bindings that require jni ^1.1.0; Flet's template pins 1.0.0) 2026-10-06 12:04:20 -04:00
spoopyghosty0 a6daa5cccb Build: pin jni_flutter 1.0.4 (1.0.4+1, published today, requires jni ^1.1.0 but Flet pins jni 1.0.0; all flet build bundles failed) 2026-10-06 11:54:47 -04:00
spoopyghosty0 6f89123017 FramePort 0.11.1 2026-10-06 11:44:01 -04:00
spoopyghosty0 518c94d79f Vader Immortal: Episode I catalog entry (works with issues, GitHub #49): plays the intro, then stays on its loading card; plain recipe without diagnostics 2026-10-06 11:42:04 -04:00
spoopyghosty0 edf9f494e8 Agent v57: one launcher per game (flock on <base>/.launch.lock). Play pressed again while Lepton still booted made the second Lepton stop the first container and both died (Vader Immortal, BattleSisters); a second launch now exits and leaves the first running. upgrade_launchers adds it to existing launchers; verified on the device 2026-10-06 11:25:56 -04:00
spoopyghosty0 0db053c31f FrameBridge snapshot=N: every N seconds the left-eye image a GLES game submits is saved as fb_snap_0-7.ppm in its files folder (headless launches have a black headset view). Vader Immortal: the intro plays, then its loading card never advances 2026-10-06 09:26:38 -04:00
spoopyghosty0 7439f082db BattleSisters works (owner-verified 2026-10-06: buttons via the Oculus device-model fix; its first controller vibration ran OVRPort's haptic-envelope bug into an 11 GB OOM kill). haptic_fix is now suggested for every game that ships Meta's OVRPlugin; existing FramePort recipes pick it up after the next update 2026-10-06 09:01:40 -04:00
spoopyghosty0 f12f4aec4c FrameBridge layer_debug: count xrAcquire/Wait/ReleaseSwapchainImage per 5 s (Vader Immortal's per-frame leak in SteamVR's runtime; hooked only with layer_debug, after the other wait/release hooks) 2026-10-06 01:23:58 -04:00
spoopyghosty0 8d95592f53 Unity 2019 built-in Oculus input on Lepton (BattleSisters, GitHub #48): libunity only reports controllers when the device model starts with "Oculus"; Lepton's Android is "Valve Lepton", so Unity polled only Go-style controllers and the game's buttons never arrived. unity_oculus_check revision 4 gives those strncmp calls length 0 (in place); on the device libunity now polls the Touch controllers 2026-10-06 01:19:47 -04:00
spoopyghosty0 44d23f778d FrameBridge diagnostics from Vader Immortal (GitHub #49): layer_debug logs xrDestroySwapchain and per-5 s xrBeginFrame/xrEndFrame counts; strip_color_bias (drop XR_KHR_composition_layer_color_scale_bias, 2 = the layer's whole extension chain) and frame_balance (end an open frame before the next begin), both opt-in. Vader leaks ~430 GPU mappings a second inside SteamVR's runtime (GPU timing command buffers per frame); none of these stop it 2026-10-06 01:07:11 -04:00
spoopyghosty0 7a40e47e67 FramePort 0.11.0 2026-10-06 00:21:37 -04:00
spoopyghosty0 9401b7fc87 Accounting+ works (owner-verified 2026-10-06: the motion warning continues with the click emulation); catalog entry needs FramePort 0.10.1 2026-10-06 00:18:20 -04:00
spoopyghosty0 e510ea88f0 ovrpshim: log ovrp_GetNodePresent for Unity's built-in XR input and report a hand node present while its Touch controller is connected (BattleSisters: Unity's XR controller devices, which its buttons are read from, never appeared) 2026-10-06 00:17:25 -04:00
spoopyghosty0 4c2b6c9dee Start-up recursion (GitHub #58): library.load() refreshes recipes from the catalog, and the catalog's first load read its auto-update setting through library.load(), which refreshed recipes again before the catalog was cached (~140 nested loads: a long blank window, or a crash in the Windows bundle). The setting is read as stored (library.peek_setting) and the catalog step can't re-enter itself 2026-10-06 00:17:14 -04:00
spoopyghosty0 cb689c576a ovrpshim: Touch trigger or A/B/X/Y presses count as Unity mouse clicks (GetMouseButtonDown icall replaced): Accounting+'s motion warning waits for a click Lepton never delivers; input probe logs trigger/grip values 2026-10-06 00:06:18 -04:00
spoopyghosty0 bd2e1a8410 unity_oculus_check revision 3: Unity's built-in Oculus input creates Touch controllers only on device model 'Oculus Quest'; Lepton's Android is 'Valve Lepton', so the compared string is repointed in place (BattleSisters: hands tracked, buttons dead although OVRPlugin reported every press) 2026-10-05 23:39:53 -04:00
spoopyghosty0 608fac9d68 unity_oculus_check: the input probe can be switched on for a diagnostic build (FRAMEPORT_INPUT_PROBE=1) 2026-10-05 23:18:14 -04:00
spoopyghosty0 919c0e291e focus_hold on by default with 5 s (the Frame's wear sensor flickers "HMD off" for 0.5-2 s while worn and Quest games pause each time; Lucky's Tale); Unity 2019 games on the built-in VR loop (no Oculus XR Plugin) get the ovrpshim frame wait (BattleSisters flooded 'outside of frame bounds' and hung the GPU); frame.ovr_trace: opt-in Meta Platform SDK request tracer (native/ovrtrace) for games stuck waiting (Vader Immortal, GitHub #49); catalog: I Am Cat pose_consistency, Lucky's Tale haptic_fix, The Room VR (all owner-verified) 2026-10-05 22:02:06 -04:00
spoopyghosty0 23f4192cfd Game page offers a newer catalog config for recipes the user edited (GitHub #10: a saved Game settings dialog stopped catalog fixes; saving without a change no longer marks the recipe as the user's); launch tests fail when frames stop while the process lives on (frames-stopped) and on Unity render-thread crashes in the GL driver (unity-render-crash) 2026-10-05 21:03:02 -04:00
spoopyghosty0 3391a981b8 Unity fixes from the 2026-10-05 headset round: unity_runtime_msaa_off also no-ops QualitySettings.set_antiAliasing; frame.unity_no_overlay_copy (The Room VR: OVROverlay's fade copy crashed Zink; works now); unity_oculus_check also for Unity 2019 without the frame-wait shim (BattleSisters starts VR; then a GPU lockup); frame.unity_gl_shim + gl_hide_msrtt (GL shim reads every settings source); xrshim converts haptic amplitude envelopes before OVRPort's dispatcher (haptic_fix; GitHub #9, found by Klownicle); FrameBridge pose_consistency (GitHub #8, proposed by Klownicle) 2026-10-05 20:55:36 -04:00
652 changed files with 95676 additions and 3432 deletions

No files matched your search

+10 -11
View File
@@ -1,31 +1,30 @@
name: Problem report
description: A game or FramePort doesn't work (FramePort → "Report a problem…" fills this in and saves a diagnostics zip).
title: "[Problem] "
labels: ["bug"]
name: Game report
description: 'A game doesn''t run right on the Frame, or FramePort itself misbehaves (FramePort fills this in: "Report a problem…").'
title: "[Game report] "
labels: ["game-compatibility"]
body:
- type: markdown
attributes:
value: |
Please attach the **diagnostics zip** FramePort saved (drag it into the "Diagnostics" box below). It contains
logs, the recipe and device details with personal data removed — no game files. Without the app, it can be
read with `frameport diag inspect <zip>`.
Please attach the **diagnostics zip** FramePort saved: drag it into the "Diagnostics" box below. It holds logs
and device details with personal data removed, and no game files.
- type: input
id: game
attributes:
label: Game
description: Title — package id (version, engine / XR API); empty for app problems
description: Title and package name; leave empty for problems with FramePort itself
- type: textarea
id: description
attributes:
label: What happens?
description: What you did, what you expected, what you saw (in the headset, if it started)
description: What you did, what you expected and what happened
validations:
required: true
- type: textarea
id: findings
attributes:
label: Launch test / triage
description: Filled in by FramePort from the last launch test
label: Launch test
description: Filled in by FramePort
- type: textarea
id: recipe
attributes:
+7
View File
@@ -1 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: Questions and ideas
url: https://github.com/spoopyghosty0/frameport/discussions
about: Setup help, how-to questions and feature ideas go to Discussions.
- name: Docs and FAQ
url: https://frameport.app/docs/
about: Install steps, game folder layouts and common questions.
+11 -11
View File
@@ -1,32 +1,32 @@
name: Working configuration
description: Submit a recipe that works on the Steam Frame (FramePort → game menu → "Share working config…" fills this in).
title: "[Working config] "
name: Working recipe
description: 'Share a recipe that works on the Steam Frame (FramePort fills this in: game menu → "Share working recipe…").'
title: "[Working recipe] "
labels: ["working-config"]
body:
- type: markdown
attributes:
value: |
Thanks! A maintainer reviews the recipe; once it's labelled `catalog-accepted`, a pull request adding it to
`catalog/games/` is opened automatically. Please don't paste game files or links to them.
Thanks! Once a maintainer accepts the recipe, it becomes built-in for everyone. Please don't share game files
or links to them.
- type: input
id: game
attributes:
label: Game
description: Title — package id (platform, version, engine / XR API)
description: Title and package name (e.g. Lucky's Tale — com.playful.LuckysTale)
validations:
required: true
- type: input
id: result
attributes:
label: Result
description: works or issues (+ the last headless launch test)
description: works or issues
validations:
required: true
- type: textarea
id: recipe
attributes:
label: Recipe
description: The catalog entry (catalog/games/<package>.yaml). Don't edit the package line.
description: Filled in by FramePort. Don't edit the package line.
render: yaml
validations:
required: true
@@ -34,16 +34,16 @@ body:
id: environment
attributes:
label: Environment
description: FramePort, tool and SteamOS versions
description: FramePort and SteamOS versions
- type: textarea
id: notes
attributes:
label: Notes
description: What you checked in the headset, known issues, anything unusual
description: What you checked in the headset and any known issues
- type: checkboxes
id: confirm
attributes:
label: Confirmation
options:
- label: I played the game in the headset with this recipe (headless launch tests can't check the picture).
- label: I played the game in the headset with this recipe.
required: true
+38 -1
View File
@@ -96,8 +96,27 @@ jobs:
if: runner.os == 'macOS'
run: |
app=$(find dist -maxdepth 3 -name "*.app" -type d | head -1)
codesign --force --deep --sign - "$app"
# Re-signing replaces the signature, entitlements included: pass them again, or the file picker refuses to
# open ("ENTITLEMENT_NOT_FOUND", GitHub #63). flet build writes them to Release.entitlements; the
# PyInstaller fallback has none, so it gets the same set.
ent=build/flutter/macos/Runner/Release.entitlements
if [ ! -f "$ent" ]; then
ent="$RUNNER_TEMP/FramePort.entitlements"
cat > "$ent" <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>com.apple.security.cs.allow-jit</key><true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>
<key>com.apple.security.network.client</key><true/>
<key>com.apple.security.network.server</key><true/>
<key>com.apple.security.files.user-selected.read-write</key><true/>
</dict></plist>
EOF
fi
codesign --force --deep --sign - --entitlements "$ent" "$app"
codesign --verify --deep --strict --verbose=2 "$app"
codesign -d --entitlements - "$app" 2>/dev/null | tee /dev/stderr | grep -q "files.user-selected.read-write"
# ---- package (archives keep permissions/symlinks) + provenance ----
- name: Package (Windows)
@@ -131,6 +150,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
actions: write # starts the pages workflow
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v10.2.0
@@ -140,6 +160,12 @@ jobs:
merge-multiple: true
- name: Wheel (CLI install via uv tool / pipx / pip, and its self-update)
run: uv build --wheel -o out && ls out
- name: Videos (docs/media, rendered by the showcase workflow; frameport-<name>.mp4 → FramePort-<name>.mp4)
run: |
for f in docs/media/frameport-*.mp4; do
[ -f "$f" ] || continue
n=$(basename "$f" .mp4); cp "$f" "out/FramePort-${n#frameport-}.mp4"
done
- name: Checksums
run: cd out && sha256sum * > SHA256SUMS.txt && cat SHA256SUMS.txt
- name: Publish release
@@ -160,6 +186,11 @@ jobs:
echo; cat packaging/release-footer.md; } > "$notes" # short: the update dialog shows these notes
gh release delete "$tag" --yes 2>/dev/null || true
gh release create "$tag" out/* packaging/FramePort-selfsigned.cer --title "FramePort ${tag}" --notes-file "$notes"
- name: Refresh the website's changelog (a release made with github.token starts no other workflow)
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: gh workflow run pages.yml --ref main
dev-release:
# "Run workflow" with dev ticked: replaces the rolling `dev` pre-release (never shown by the automatic update check;
@@ -169,6 +200,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
actions: write # starts the pages workflow
steps:
- uses: actions/checkout@v7
with:
@@ -200,3 +232,8 @@ jobs:
gh release delete dev --yes --cleanup-tag 2>/dev/null || true
gh release create dev out/* packaging/FramePort-selfsigned.cer --prerelease --target "$GITHUB_SHA" \
--title "FramePort dev build ${VERSION}" --notes-file "$notes"
- name: Refresh the website's changelog (a release made with github.token starts no other workflow)
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: gh workflow run pages.yml --ref main
+1 -1
View File
@@ -1,5 +1,5 @@
name: catalog-from-issue
# A maintainer adds the label `catalog-accepted` to a "Working configuration" issue → this opens a PR adding the
# A maintainer adds the label `catalog-accepted` to a "Working recipe" issue → this opens a PR adding the
# recipe to catalog/games/. Never runs for unlabelled issues; the issue text is only read by the validating script
# (never interpolated into shell commands). Needs Settings → Actions → "Allow GitHub Actions to create pull requests".
on:
+72
View File
@@ -0,0 +1,72 @@
# The project page (site/, Astro) on GitHub Pages: https://frameport.app/
# Rebuilt when the site, the docs it renders, the catalog or the showcase renders change on main, when a release is
# published, and once a day (contributors, releases and the games board stay fresh). The games board also refreshes
# itself from the catalog on main in the visitor's browser, so a catalog change shows before the next build.
# One-time setup: Settings → Pages → Source: GitHub Actions; custom domain frameport.app (kept in the settings, not a
# CNAME file: GitHub ignores that file for Actions deployments).
name: pages
on:
push:
branches: [main]
paths:
- 'site/**'
- 'docs/*.md'
- 'catalog/games/**'
- 'bootstrap/setup.sh'
- 'docs/images/**'
- 'docs/media/**'
- 'docs/badges/**'
- 'src/frameport/ui/icons/**'
- '.github/workflows/pages.yml'
release: # the changelog page shows every release (CI's own releases start this through workflow_dispatch)
types: [published]
schedule:
- cron: '17 5 * * *'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: site
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
cache-dependency-path: site/package-lock.json
- uses: actions/configure-pages@v6
- run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends ffmpeg # the video tiles' short previews
- run: npm ci
- run: npm test
- run: npm run build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # the contributors list (higher API limit)
# analytics (Settings → Secrets and variables → Actions → Variables); unset = none on the site
PUBLIC_CF_ANALYTICS_TOKEN: ${{ vars.CF_ANALYTICS_TOKEN }}
PUBLIC_GA_ID: ${{ vars.GA_MEASUREMENT_ID }}
- uses: actions/upload-pages-artifact@v5
with:
path: site/dist
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v5
+107
View File
@@ -0,0 +1,107 @@
name: showcase
# Docs screenshots and the videos (the demo tour, the install tutorial, …), rendered from the demo library
# (docs/showcase/, scripts/showcase/; see docs/SHOWCASE.md). Opens or updates one pull request (branch
# showcase/update) when a picture really changed; the renders are also kept as a workflow artifact either way.
on:
workflow_dispatch:
inputs:
videos:
description: "Videos to record too: names from docs/showcase/videos (e.g. tour install), all, or empty"
type: string
default: ""
push:
branches: [main]
paths:
- "src/frameport/ui/**"
- "src/frameport/locales/**"
- "docs/showcase/**"
- "scripts/showcase/**"
concurrency:
group: showcase-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
pull-requests: write
jobs:
render:
runs-on: ubuntu-22.04
timeout-minutes: 60
env:
PYTHONUTF8: "1"
PYTHONIOENCODING: utf-8
TZ: UTC
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # the push's changed files decide whether the video is recorded again
- uses: astral-sh/setup-uv@v10.2.0
- name: ffmpeg and fonts
run: sudo apt-get update && sudo apt-get install -y ffmpeg fonts-roboto fonts-noto-color-emoji
- run: uv sync --extra dev --python 3.12 # the Python the renders are made with locally
- name: Chromium for Playwright
run: uv run playwright install --with-deps chromium
- name: Store art cache (fetched once from the stores the app uses)
uses: actions/cache@v6.1.0
with:
path: ~/.cache/frameport-showcase
key: showcase-art-${{ hashFiles('docs/showcase/demo-library.yaml') }}
restore-keys: showcase-art-
- name: Docs screenshots
run: uv run python scripts/showcase/render_docs.py --keep showcase-out/screenshots
- name: Videos
# a push records the videos whose storyboard (docs/showcase/videos/<name>.yaml) changed, or all of them when
# the recorder changed (UI tweaks alone don't re-record ~10 MB files); Run workflow records what it's asked
env:
INPUT_VIDEOS: ${{ inputs.videos }}
BEFORE: ${{ github.event.before }}
run: |
if [ "$GITHUB_EVENT_NAME" = workflow_dispatch ]; then
read -ra names <<< "$INPUT_VIDEOS"
if [ ${#names[@]} -eq 0 ]; then echo "no videos asked for"; exit 0; fi
[ "${names[0]}" = all ] && names=()
uv run python scripts/showcase/record_video.py "${names[@]}" --work showcase-out/videos
else
uv run python scripts/showcase/record_video.py --changed-since "$BEFORE" --work showcase-out/videos
fi
- name: Keep the renders
if: always()
uses: actions/upload-artifact@v7
with:
name: showcase
path: |
showcase-out/screenshots
showcase-out/videos/*/video.json
docs/media
docs/images/tour-teaser.webp
if-no-files-found: ignore
retention-days: 14
- name: What changed
id: diff
run: |
git status --short -- docs/images docs/media
if [ -n "$(git status --porcelain -- docs/images docs/media)" ]; then
echo "changed=true" >> "$GITHUB_OUTPUT"; fi
- name: Pull request
if: steps.diff.outputs.changed == 'true' && github.ref == 'refs/heads/main'
uses: peter-evans/create-pull-request@v8.1.1
with:
branch: showcase/update
delete-branch: true
add-paths: |
docs/images/*
docs/media/*
commit-message: "Docs: update screenshots and videos"
title: "Update docs screenshots and videos"
body: |
Rendered by the `showcase` workflow from the demo library (docs/showcase/demo-library.yaml) with a
pretend Frame: only pictures that visibly changed are included. Look them over before merging (the
run's `showcase` artifact has every render).
Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+10
View File
@@ -20,3 +20,13 @@ CLAUDE.local.md
REPORT.md
.ruff_cache/
.claude/worktrees/
# project page (site/)
site/node_modules/
site/dist/
site/.astro/
site/public/media/
site/src/assets/media/
site/media-hq/
site/public/s
site/public/setup.sh
+368 -28
View File
@@ -10,8 +10,20 @@ Read `docs/PLAYBOOK.md` (symptom → fix) before debugging a game, and `docs/FRA
## Layout
- `src/frameport/` — Python package. `pipeline.py` is the API the CLI (`cli.py`) and GUI (`ui/`, Flet 1.0) share.
- `ui/` — `app.py` shell (sidebar with Frame connection + activity cards, routing, actions, 30 s connection poll),
`theme.py` (dark design tokens; change colours/spacing only there), `components.py` (pill, card, callout,
status_row, art_fill, confirm, `update()` = safe update: in Flet 1.0 reading `.page` of an unmounted control
`theme.py` (design tokens; change colours/spacing only there. Dark-only colour themes (owner: no light mode):
built-ins `portal` (default), `portal_oled` (true black) and `original` (the old violet; alias `classic`). Portal =
the logo's dichotomy: orange ACCENT = main action + selection, blue SECONDARY = secondary (outlined) buttons,
progress, switch tracks, PC; `dual` themes add the blue→orange sidebar edge, two-colour wordmark (`C.wordmark`) and
selected-tab fade (`C.portal_gradient`). Installable theme files `<data>/themes/*.json` (docs/THEMES.md: name,
base, dual, any subset of `T.TOKENS`; refused if light/unreadable, `T.ThemeError`), Settings → Appearance: cards
switch live via `app.restyle()` (rebuilds the shell, drops cached views; setting `ui.theme`), "Install theme
file…", "Copy this theme as a file", bin icon removes. Read tokens as `T.NAME` when building — never as a default argument or module-level constant (they
keep the import-time theme; module maps refill through `T.on_change`, `tests/test_theme.py` checks defaults)),
`glyphs.py` + `icons/` (the logo `logo.svg`/`logo-solid.svg` = a monitor that comes out of a tilted portal as the
Frame, and 24 px line glyphs `fp:frame|port|patch|container|test|pc|sync|live|shot|keys|recipe|link`, copied into
the assets dir and shown as tinted `ft.Image`s: pass `G.FRAME` … wherever an icon goes, `C.as_icon` / the button
helpers handle both kinds; `src/assets/icon.png` = the bundles' app icon), `components.py` (pill, card, callout,
checklist, art_fill, confirm, `update()` = safe update: in Flet 1.0 reading `.page` of an unmounted control
raises), `jobs.py` (background FIFO job queue, one at a time, cancel via Reporter; no Flet), `views/`
(library: search/filters/tags/sort as pure tested helpers; game: hero + one-click install, patches under
"Customize"; frame: device + readiness + installed, or connect wizard; files: the Frame's file manager (persistent
@@ -26,15 +38,27 @@ Read `docs/PLAYBOOK.md` (symptom → fix) before debugging a game, and `docs/FRA
launch.sh logs `start/end <unix>` to `<anchor>/plays.log` (upgrade_launchers adds it; Proton launchers exec → start
only) and shots are matched by time; thumbnails cached in `<data>/screenshots-cache/<frame>/`, shown by asset URL;
delete leaves screenshots.vdf alone (Steam rewrites it at exit); game menu → "Screenshots" = `go("screenshots",
pkg)`); live view (`views/live.py` + `install/livestream.py`: the Frame's built-in SteamVR "headset view" webcam
pkg)`; Copy image (card hover button, right-click, viewer) = `core/clipboard.copy_image` (Windows/WSL: PowerShell
bitmap + file via %TEMP%, macOS osascript PNG, Linux wl-copy/xclip; Flet's set_image only in web mode)); live view (`views/live.py` + `install/livestream.py`: the Frame's built-in SteamVR "headset view" webcam
(`steamvr-v4l2cam.service` → v4l2loopback "SteamVR" /dev/video99, see docs/FRAME_RUNTIME.md) + the default
output's pulse monitor (sound) → ffmpeg/x264 + AAC (qualities scale down only; "full" = SteamVR's size) →
fragmented MP4 on an SSH exec channel's stdout (the script stops ffmpeg on stdin EOF) → relay on 127.0.0.1 (keeps
init + fragments since the last keyframe of the *video* track for late viewers) → player page
output's pulse monitor (sound) → `fp_venc` (`native/venc`, own clean-room V4L2 driver of the Frame's iris
hardware encoder: RGB24→NV12 box downscale with NEON, H.264 CBR, one frame per slot of an even fraction of the
panel rate read from DRM, e.g. 96 Hz → 32 fps; stdin `k` = keyframe, EOF = stop; `--probe` / `--selftest`;
artifact `linux-arm64-bin/`, synced to `~/.local/share/frameport/bin` by sha256 at stream start, no agent change)
piped into ffmpeg (`-c:v copy` + AAC), else the old ffmpeg/x264 30 fps path (qualities scale down only; "full" =
SteamVR's size; stderr `live: encoder=… fps=…` → status line) →
fragmented MP4 on an SSH exec channel's stdout (stdin EOF stops it) → relay on 127.0.0.1 (a new viewer asks
the hardware encoder for a keyframe and waits for it, else gets init + fragments since the last keyframe of the
*video* track) → player page
`install/live_player.py` (MSE; starts muted as browsers require, "Sound on" button; 0.3 s cushion, catches up at
1.1x, seeks only when >2 s behind: seeking to the very edge starved it) opened
in the user's default browser: Flet can't show video outside `flet build` bundles; the stream outlives the tab and
stops on disconnect/window close); settings; welcome; activity panel). Files, Screenshots and the Library share right-click menus
stops on disconnect/window close); monitor (`views/monitor.py` + `frame/monitor.py` + agent v62 `_monitor`: one
JSON sample per tick over an SSH exec channel while the tab is shown (stopped in go/disconnect/on_close, the
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
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).
@@ -42,7 +66,36 @@ Read `docs/PLAYBOOK.md` (symptom → fix) before debugging a game, and `docs/FRA
await a FilePicker) must be coroutine functions or go through `page.run_task`: Flet doesn't await a lambda's
coroutine (the Files row Download button silently did nothing). `ui_smoke.py --fake-frame --gestures` drives real
mouse drags/right-clicks.
Live Frame data comes from one app-owned `app.monitor_hub` (`frame/monitor_hub.py`): subscribers name modules +
interval, the hub runs one agent `_monitor` stream (union of modules, fastest interval; per-module collection
needs agent v73), reconnects after a lost stream and stops it when nobody subscribes; `_poll` retries an "error".
Subscribers: "monitor" (the Monitor tab, everything, only while shown) and "card" = the sidebar's live Frame card
(`ui/frame_card.py` helpers: battery ring, "now playing" row with fps + 2-min sparkline → click opens Monitor;
games + battery every 5 s, paused while a job's stage is "Upload…", only while connected and setting
`ui.live_frame_card` (Settings → Appearance, default on) is on; else the battery comes from the 30 s poll).
ui_smoke injects `FakeMonitorSession` as the hub's `session_factory`.
User tags live in library entries (`tags`), filters in library setting `ui.library`.
Easter eggs (`ui/easter.py`, owner's wish; never mentioned in the UI or docs): seven quick clicks on the sidebar
logo (the *wordmark's* portal glows + rings, a cover is tossed out of it and falls across the window on a random
arc: `toss_path`, `page.overlay` at the computed window position `_wordmark_portal`; the logo's portal in
themes without one), the Monitor's fps number has a mood tooltip (above the number, clear of the pointer) and
2.5 minutes within 0.5 fps of target (`PERFECT_SECONDS`) make the game card glow and pulse blue/orange for 5 s,
the number grow in the portal gradient and the sparkline take it; the "Perfect pacing" pill + line stay until the
rate slips (`PerfectPacing.done`, `_show_perfect`, `Sparkline.portal`), holiday badges on the logo that dance on
hover and shower the window on click (`HOLIDAY_MOTION`; `SHOWERS`/`shower_plan`: New Year confetti, winter snow,
Valentine hearts, Pi digits, clovers, hopping eggs, bouncing pumpkins; `holiday`: Valentine's
heart, Pi Day, St Patrick's clover, Easter Sat-Mon egg (computus), April 1 = the logo upside down until hovered,
Halloween pumpkin, Dec 31-Jan 1 party popper, else Dec 20-Jan 2 snowflake; on those days the logo's box grows to
hold the badge: Flutter only hit-tests inside a control's bounds; `FRAMEPORT_TODAY` pretends a date),
Library search "frameport" (logo spins, "That's me!"), Type on Frame "hello" (keys still go to the Frame; a
headset peeks up from the window's corner and waves), the battery ring (only the ring, with a glow behind
it; not the percentage) empties at once and spins while its bar refills, then pulses, on reaching 100 % plugged (`charged_now`, `ring_pulse`, `ring_glow`), a long upload (>2.5 min, `HOP_AFTER`) makes the transit cover hop, the Live view
tab shows a blinking ON AIR sign while streaming
(`app.sync_on_air`), install milestones (confetti +
message at the 1st/10th/100th *different* game: settings `fun.installed` = game ids, `fun.milestones` = shown;
each milestone shows once ever, recorded before it's shown; updates/reinstalls never count; the first use seeds
from games already on a Frame and marks passed milestones shown). Setting `ui.easter_eggs` (no UI) turns them
off; the showcase demo library has them off (steps hook `easter_eggs` for egg demos). Reduce motion: no animation.
**Performance rules** (the app froze before): never put image bytes in controls — artwork is served by URL from the
GUI assets dir (= user data dir; `ft.run(assets_dir=…)`), as thumbnails (`artwork/thumbs.py`, Pillow); the
library view is persistent, streams cards in batches from a background thread and filters by toggling visibility;
@@ -50,16 +103,45 @@ Read `docs/PLAYBOOK.md` (symptom → fix) before debugging a game, and `docs/FRA
**Never recreate clickable controls on progress ticks** (sidebar, activity tiles): update their properties —
replacing them 5×/s swallowed clicks (couldn't leave the Library during an upload).
Labelled switches: `C.switch(label, …)` (Material's default label colour is dark on our dark theme).
Cover cards (Library grid + shelf): `library.card_hover`/`hover_motion` (lift, art zoom inside the clipped frame,
radial `scrim`, accent-tinted shadow) and `C.CoverButton` (frosted round button, accent under the pointer; Play =
blue then orange ring ripple + "Starting on Frame…" pill for STARTING_S; its `.status` layer must sit *below*
the button in the Stack, else it takes the clicks); the game page's Play turns into "Starting on …" with a spinner
(`GameView._starting`). Reduce motion: no lift/zoom/rings. Filled buttons use a glyph's `-solid` variant
(`glyphs.solid`, e.g. `frame-solid.svg`: the outline glyph faded into the orange).
Empty states (`C.empty_state(..., features=[(icon, heading, sentence)])`): with features the portal is bigger, a
soft portal light (`C.portal_glow`) fills the screen and `C.feature_row` says what the screen will show (the
not-connected Files/Screenshots/Live/Type/Monitor tabs and the empty Library); the idle Live view is a framed
"screen" with its own Start button + `live_features()`, Type on Frame shows `keyboard_tips()` under the box.
Setup checklists (Frame → Ready to play, Settings → Tools / This PC) = `C.checklist([C.Check(ok, title, detail,
help, fix=(label, icon, handler), extra=control)])`: ok True/False/"warn"/None (= spinner); the fix button shows
inline only while an item isn't ready — wire existing actions only. Motion: `app.body` holds an
`ft.AnimatedSwitcher` (`app._show_view`): a new route gets a new wrapper (150 ms fade), a same-route redraw swaps
the wrapper's content (no animation; persistent views just remount); the wordmark's portal (`wordmark().data`)
pulses once when frame_state turns "connected"; both off with setting `ui.reduce_motion` (Settings → Appearance
"Reduce motion", `app.reduce_motion`). Settings has a fixed index column (`settings.SECTIONS` order, Appearance
first, Remove FramePort last): entries scroll the section column via `scroll_to(scroll_key=…)` (sections wrapped
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`/`status_row`/`kv`, tooltips via `C.tip()` (wraps). Game actions for the Library
right-click menu (one `ft.ContextMenu` around the grid, filled on right-click) and the game page's "…" menu 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). 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
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;
uploads are interruptible (Cancel checked per MiB) and resumable (big files via SFTP `.part` append, small files
streamed in tar batches; the agent counts files already in `incoming/`). Multi-select in the Library queues
installs after asking every needed question up front. Failures end in one pop-up (Resume / Uninstall / log).
- Other drives (GitHub #90, agent v63): anchors stay in ~/Applications/quest-frame, a game's files may live in
`<mount>/FramePort/<pkg>` (`deployment.json` base). Agent `drives` (/proc/mounts, /run/media + Steam library
drives; vfat/exfat/ntfs/read-only refused), prepare*/finalize_linux `dest` (unmounted = error, never a fallback;
installed games keep their base), `move` (detached systemd-run + `move_status`; cp -a under podman unshare,
count+bytes check, symlink retarget, launch.sh: Quest app_dir line / Linux+PC VR rewritten from the record's
`launcher` field, else text swap), list_installed `drive`/`drive_missing` (install_state keeps such games
"installed"). PC: `install/drives.py`, library setting `install.drive` (Frame page → Storage), game menu
"Move to…" (job kind tool-frame), CLI `frame drives`/`frame move`/`install --dest`. Untested on the device.
- **PC VR repacks are pre-patched to run directly** (proven: Rick and Morty, Vader Immortal run when the exe is
launched directly; Revive breaks them). So Rift games default to `as_is` = install the copy unchanged and launch
the exe directly (`pcvr.xr_timefix` for the Frame OpenXR-1.1→1.0 fix, `pcvr.no_crash_reporter` for Unreal).
@@ -72,7 +154,11 @@ Read `docs/PLAYBOOK.md` (symptom → fix) before debugging a game, and `docs/FRA
doesn't do). `VD.bat` is Virtual Desktop's launcher: ignore it except as an exe-location hint. Library migration
`rift_run_direct` resets existing recipes.
Auto launch-test is skipped on PC installs (it would start the game on the user's desktop). Launch tests collect the Unreal
game log + crash summaries from the Proton prefix; triage `unreal-crash`. Lies Beneath via Proton without Revive
game log + crash summaries from the Proton prefix; triage `unreal-crash`. Agent v67 adds Unity's logs (LocalLow/<Company>/<Product>/Player(-prev).log via `<Name>_Data/app.info`,
`output_log.txt`, Temp/…/Crashes/*/error.log; `unity_logs`) to launch tests and diagnostics; triage `unity-vr-init` /
`unity-crash`. A catalog recipe verified with another build (catalog `xr` ≠ the build's) keeps the build's own
`pcvr.launch_args` (`engine._other_build`; GitHub #105: SUPERHOT VR's Oculus+OpenVR build needs `-vrmode OpenVR`,
the catalog's is the OpenXR build); Electron launchers next to a game rank −30 (`rift.is_electron`). Lies Beneath via Proton without Revive
crashed (UE 4.23 "Unhandled exception").
- Rift scanning: `sources/rift_dump.scan` = the scanned folder's subfolders are games (one per folder; a folder is a
game if all candidate exes sit under one child), recursing into collections; `analysis/rift.py` walks once,
@@ -111,12 +197,28 @@ Rick and Morty runs on the Frame via its catalog recipe (OpenVR, no Revive),
set: the name on a colour from the title hash + the APK icon, `steam_set_for`; 2D Android apps get no store lookups) and tags: how it runs, the original platform
(Meta Quest / Oculus Rift), genres, user tags — merged with tags set in Steam (non-Steam shortcuts can't hold a
description).
- Linux apps (GitHub #31, library kind `linux`, `linux.<slug>`): GUI = Add games → "Add a Linux app (arm64)…" /
- Linux apps (GitHub #31, library kind `linux`, `linux.<slug>`; x86_64 builds run through FEX (app 3127680, no SLR:
RootFS /usr/share/guestos/fex-mesa from the OS image; needs `STEAM_COMPAT_DATA_PATH`, see docs/FRAME_RUNTIME.md):
agent v61 `linux_x86_tools`/`pick_tool`, `install_proton`/`proton_status` `kind: linux_x86`, `finalize_linux
x86_64`, `installer.ensure_proton(kind=)`; an arm64 program wins over an x86_64 one): GUI = Add
games → "Add a Linux app…" /
"…folder…" (`app.add_linux` job → `pipeline.add_linux_app`); game page `linux_summary` (program + Change…, AppImage,
OpenXR, source) instead of recipe/patches, `C.missing_libraries` callout (Frame deployment, else last install);
no Analyze/Rebuild/recipe/share/Game settings actions, Frame only; platform "Linux" badge + library filter; Steam
tags "Linux app on Frame"/"Linux"; `_follow_catalog` skips them; local files of a lone AppImage = the file only.
Desktop Mode entries (GitHub #84, agent v63): finalize_linux writes `frameport-<slug>.desktop` to
~/.local/share/applications (+ ~/Desktop if it exists; `X-FramePort-Package` marks ours), launch.sh with
`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.
- 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`).
- Rename… (game menu "Name and artwork", Library right-click; CLI `frameport rename <pkg> "<name>"|--reset`):
`pipeline.rename_game` sets `title` + `title_locked` (+ `auto_title` = the automatic name for reset; rescans, Rift
store matches and links then only update `auto_title`; a locked name gets no twin suffix); `sync_title` → Frame
agent v77 `rename` (deployment.json title, same shortcut appid: upsert matches by Exe) + art + one Steam restart, PC
`PcReviveTarget.rename`; an unreachable Frame → `steam_name_stale` (game page "Update name on Frame", next install).
- `Recipe.as_is` = install unchanged (pre-patched libraries): `pipeline.prepare_as_is`; auto for APKs that already
contain FrameBridge (`frame_patched`). For Rift it changes nothing (the dump is never modified; the Frame copy
still gets launch fixes like the crash-reporter rename — `-nocrashreports` alone doesn't stop UE 4.23) — it does
@@ -131,6 +233,10 @@ Rick and Morty runs on the Frame via its catalog recipe (OpenVR, no Revive),
- `patches/` — **the unit of modularity**. `base.py` (Patch interface, registry), `overport.py` (overport CLI patch ids,
discovered dynamically via `overport patches`), `frame/*.py` (one module per Frame fix), `settings.py` (FrameBridge
adapter keys + device files as patches). Add a patch = add a module that calls `register(...)`.
`upstream.py`: upstream fixes that replace a workaround per build (a probe finds the fix in OVRPort's output →
the build leaves the workaround out, `build.superseded`; recipes unchanged). Registered: `ovrport.haptic_envelope`
(→ `adapter.haptic_fix`, `frame/haptic_envelope.py`) and `ovrport.microphone_stream` (→ `frame.ovr_microphone`),
both fixed in OVRPort runtime 3.4.3-aa54c3f (ovrport/app#73; haptics owner-verified with Lucky's Tale 2026-10-07).
- `analysis/` — APK/ELF inspection (`detect.py`), `elf.py` (pyelftools reads; own DT_NEEDED writer), `stubgen.py`
(generates the ovr_* stub .so without a compiler).
- `apk/` — `axml.py` (binary manifest editor), `workspace.py` (staged zip edits), `sign.py` (apksigner; it aligns too).
@@ -150,8 +256,12 @@ Rick and Morty runs on the Frame via its catalog recipe (OpenVR, no Revive),
- `diag/` — user feedback without tokens (docs/DIAGNOSTICS.md): `redact.py` (every file/issue text: IPs, hosts,
home dirs, Steam ids, dump folders → placeholders), `bundle.py` (redacted diagnostics zip; agent v21
`collect_diag`; `frameport diag collect|inspect|report`), `issue.py` (prefilled GitHub issue-form links, ≤7.5k
chars). "Share working config" → `working-config.yml` issue → maintainer label `catalog-accepted` →
`catalog-from-issue.yml` workflow (`scripts/catalog_from_issue.py` validates) opens a catalog PR. App log:
chars; `*_links` = `IssueLinks(form, plain, body)`: body = the Markdown the form writes (`### <label>`, recipe in
a ```yaml block; labels checked against the templates), plain = `?title=&body=` link. GitHub #167: some (Flatpak)
browsers opened the form with empty fields → `app.open_issue` copies the body to the clipboard and offers "Form
empty? Open a plain issue"; keep the templates valid YAML, quote `: ` in descriptions). "Share working config" →
`working-config.yml` issue → maintainer label `catalog-accepted` (a plain issue needs `working-config` added by
hand) → `catalog-from-issue.yml` workflow (`scripts/catalog_from_issue.py` validates) opens a catalog PR. App log:
`core/applog.py` (`<data>/logs/app.log`, finished GUI jobs in `<data>/logs/jobs/`).
- Self-update (docs/ARCHITECTURE.md "Self-update", docs/INSTALL.md "Updating"): `_version.py` = the only version
(`frameport.__version__`; pyproject reads it via hatch `dynamic`; app log, diagnostics, User-Agent, Settings use
@@ -165,9 +275,15 @@ Rick and Morty runs on the Frame via its catalog recipe (OpenVR, no Revive),
signed, `ready.json`); `apply()` writes + starts detached `apply.ps1`/`apply.sh` (wait for pid → Windows: copy over,
replaced files kept in `updates/<ver>/previous` because the zip has no top folder; macOS/Linux: mv to `.old` + swap,
rollback, `xattr -dr` quarantine → relaunch; `<data>/logs/update.log`). `ui/updater.py`: background check (10 s,
then 6 h), sidebar card (hidden control, toggled), Library bar (`library_bar`), notes dialog (`ft.Markdown`), job
then 6 h), sidebar card (hidden control, toggled), Library bar (`library_bar`), changelog dialog, job
`app-update` that restarts only when no other job is active, `apply_pending_at_start()` in `main()` for
"Install updates automatically"; Settings → Updates. CLI: `--version`, `frameport update [--check (exit 10)]
"Install updates automatically"; Settings → Updates. Changelog: `updates.fetch_changelog` (releases list,
`app-releases.json`, same 6 h cache, offline → cached / []) → `ChangelogEntry` (`whats_new()` keeps only the tag
message's "What's new" part: CI's intro, update hint and release-footer.md stripped); `between`/`changelog_for`
(installed < v ≤ offered, dev entry only when on/choosing dev, skipped versions included), `recent_history`;
the update/dev dialogs show one collapsible section per version (`_changes_column`, newest open); after an update
`pending_news` (setting `update.last_seen_version`; first start only records it; offline → retried next start)
→ Library "was updated" bar → "See what's new…"; Settings → Updates "What's new…" = recent history. CLI: `--version`, `frameport update [--check (exit 10)]
[--yes]`, once-a-day stderr hint read from the cache only (`refresh_cache()` in a daemon thread writes no
settings, to avoid read-modify-write races). `scripts/update_smoke.py <archive>` runs the real extract + swap script
(no relaunch) — CI runs it on all three OS with the archive it just built.
@@ -176,7 +292,17 @@ Rick and Morty runs on the Frame via its catalog recipe (OpenVR, no Revive),
by hand: the "Update now" button path (same apply(), called from the running app). macOS: CI smoke only.
- `agent/frameport_agent.py` — runs **on the Frame** (python3 stdlib only), JSON over SSH. Owns the install layout,
launch.sh template, Steam shortcuts (binary VDF), launch tests. Bump `AGENT_VERSION` when changing it.
- `bootstrap/bootstrap.sh` — one-time Frame setup served by the pairing server (sshd, app key, avahi service, Lepton).
- `bootstrap/bootstrap.sh` — one-time Frame setup served by the pairing server (app key, podman fix, Developer Mode, Lepton).
`bootstrap/setup.sh` — the setup URL (`curl -fsSL https://frameport.app/s | bash`, copied to the site by
`site/scripts/sync-media.mjs`): finds the PC (stdlib mDNS on 5353 for `_frameport-pair._tcp`, which the pairing
server announces while open; else the USB address and a /24 scan of `/ping`), `/hello` → the user clicks Allow (4
digits on both sides, `ask_digits`) → `/wait` hands over the code → runs bootstrap.sh exactly like the typed line.
Checked on the dev Frame 2026-10-10 (mDNS and scan paths) with a stub bootstrap; tests/test_setup_url.py.
Auto-pairing (`frame/autopair.py`, `app._start_auto_pair`, setting `frame.auto_pair`, default on; off with
FRAMEPORT_HOME/FRAMEPORT_NO_AUTO_PAIR): while no Frame is connected, Frames in Developer Mode (devkit mDNS, else a
30 s scan of the /24s for :32000/login-name, deduped by host key) are connected if FramePort's key works, else
offered the key via devkit.register every 4 s (refused at once until Pair new host is open; 45 s back-off after an
unanswered request). This WSL dev PC never hears the Frame's mDNS (Hyper-V firewall): the scan finds it.
- `catalog/games/<package>.yaml` (installed apps also fetch these from GitHub `main`, see "Catalog updates") — 38 recipes (34 verified 2026-09-28; Deadpool VR, 4XVR, NEX Player and AC Nexus's
90 Hz default confirmed later by the owner); `catalog/triage.yaml` — log signatures → fixes.
- `native/` — sources of the prebuilt binaries in `artifacts/` (adapter, VrApi bridge patches, GL shim, stubs).
@@ -201,12 +327,20 @@ Repo is on an NTFS drive (`core.fileMode=false`); line endings are LF (`.gitattr
artwork, frames.json, the app's SSH key which the dev Frame authorizes).
- Device checks: `frameport test <pkg>` / `frameport parity-device --results <parity.json> --baseline <launch.txt>
[--test-only]`; the pre-FramePort baseline is `PATCHED/_known-good-2026-09-28/_frame-state/baseline-launch.txt`.
- Docs screenshots (`docs/images/`): `python scripts/scrub_library.py ~/.local/share/frameport <dir> --status works,issues` (copies
library + artwork only; titles replace folder names, local paths → `D:/Games/...`, sort by size) then
`FRAMEPORT_HOME=<dir> python scripts/ui_smoke.py --out <shots> --docs --fake-frame --game <pkg>` (set
FRAMEPORT_JAVA/_OVERPORT_JAR/_APKSIGNER_JAR so no tool download toast appears; `--viewport 1280x2600` + crop for the
patch list). Check every PNG for paths, IPs, user names and repack/scene names before committing.
README rules (owner): states the project is a proof of concept, provides no piracy tools, credits the wrapped
- Docs screenshots + videos (docs/SHOWCASE.md): never by hand. `uv run python scripts/showcase/render_docs.py`
renders `docs/showcase/shots.yaml` into `docs/images/` (every shot twice, kept when both agree; only visibly changed
files are rewritten; `--check` for CI) and `scripts/showcase/record_video.py [names] [--draft]` records the
storyboards `docs/showcase/videos/<name>.yaml` (tour = FramePort in use + README teaser; install = the install
tutorial: instruction cards for the download / the command on the Frame, the rest filmed from a first start) into
`docs/media/frameport-<name>.mp4`. Both build the demo library first (`demo_home.py`, profile `demo` or `fresh`:
catalog games from `demo-library.yaml` + `demo-analyses.json`, art/details cached in ~/.cache/frameport-showcase, a
pretend Frame from `showcase-frame.json`) in a temp FRAMEPORT_HOME: no personal data, refuses the real data dir
and placeholder art; discovery is off and the setup command shows placeholders for the address and code. Steps find elements
by name through Flutter's semantics tree (`scripts/showcase/web.py`: text, tooltip; the innermost match wins) —
give new icon-only controls a tooltip. The `showcase` workflow renders on UI pushes to main (+ the videos whose
storyboard changed, or the ones named on dispatch) and opens PR `showcase/update`; releases attach every video as
`FramePort-<name>.mp4`. `scripts/scrub_library.py` (the owner's own library) is only for private screenshots now.
README rules (owner): says not every game runs (no longer "proof of concept" since 1.0), provides no piracy tools, credits the wrapped
projects (most functionality is theirs); neutral technical wording; no Quest2Frame mentions anywhere.
- GUI smoke test: `uv pip install flet-web playwright && playwright install chromium`, then
`FRAMEPORT_HOME=<test dir> python scripts/ui_smoke.py --out <dir> [--game <pkg>] [--frame steamos@<host>] [--update]` (`--update` = fake release: banner, dialog, Settings → Updates) and look
@@ -271,7 +405,10 @@ The Frame runs firewalld (22 and 32000 open). PC side: the setup server needs in
firewall blocks it silently (`DefaultInboundAction Block`): `pairing.ensure_reachable` adds a temporary rule via one
UAC prompt, removed when the server stops (flag file in %TEMP%, max 35 min); hints per OS after 45 s without a request
(`pairing.firewall_hint`). Flet 1.0 patches aren't thread-safe → `app.serialize_flet_updates()` (a dialog shown while a
scan redraw ran never closed: "dropped a patch for unknown control").
scan redraw ran never closed: "dropped a patch for unknown control"); it also makes property writes (Flet's
`Prop.__set__`) take the same `app.FLET_LOCK`: a property set on a background thread while the loop packed a patch
changed the control's `_values` mid-iteration ("dictionary changed size during iteration", a sidebar click's page
change died while the live Frame card took a sample). Anything that serves the app (ui_smoke, showcase) calls it too.
**Lepton:** needs an activity with category **LAUNCHER** (Quest apps often only have INFO → "APP_ACTIVITY is empty").
**2D apps:** Lepton runs every app headless (`lepton.headless=true`, only OpenXR output reaches the headset) unless the app folder (`<base>/lepton-app/`) has a `lepton-show-flatscreen` file (liblepton/app_metadata.sh) → Waydroid window on gamescope; agent v28 `set_flatscreen` at finalize for `vr_kind == "none"`. Android 11's navbar covered the
app's controls → patch `device.hide_navbar` (default on for vr_kind none, migration `flat_hide_navbar`) exports
@@ -413,7 +550,7 @@ GPU. Learned in the headset: quads can't be a background (the Frame draws quad l
whatever the order: hid 4XVR's balcony/controllers), and the Frame doesn't reproject a projection layer from its own
pose (images drawn only after head turns, or ahead from xrWaitFrame and sometimes late, wobbled);
`equirect_flip/face/res/fps/stereo`), `stable_local`, `focus_hold` (only after 3 s FOCUSED, dips <600 ms),
`aim_pitch/aim_yaw/aim_forward`, `refresh_rate`, `layer_debug` (diagnostics). 4XVR re-creates LOCAL spaces every 2–4 s
`aim_pitch/aim_yaw/aim_forward`, `refresh_rate`, `layer_debug` (diagnostics); focus_hold is default-on since 2026-10-05. 4XVR re-creates LOCAL spaces every 2–4 s
(menu recentring suspect); its theatres are baked 7680×3840 equirect2 images (`assets/100.png` …). Test 360° videos
(NASA, public domain) are in the Frame's ~/Videos; copies in `~/Downloads/frameport-360`. 4XVR's "Internal Storage"
lists its own `/sdcard/4XPlayer`, not Movies: agent v25 `link_media` hard-links sent files into an app's own top-level
@@ -492,7 +629,10 @@ shortcuts.vdf and writes once more if Steam put its old copy back (`shortcuts_lo
auto-repair (`_add_then_play`) crashed (its job got a Job, not a reporter). Uploads resume on a transient OSError.
Lepton's Android 11 has no clipboard service (134 services; checked with `podman exec … service check clipboard`):
SDL/LÖVE apps crashed at start → `frame.sdl_clipboard` (`apk/dex.py`: in-place dex edit, nops one invoke, fixes
the header checksum/signature; verified with LÖVE for Android 11.5, GitHub #24 Dramatic Shape). 2D apps (vr_kind none)
the header checksum/signature; verified with LÖVE for Android 11.5, GitHub #24 Dramatic Shape). Godot 4.2-4.4 (Kotlin `as ClipboardManager`,
GitHub #134 EndoparasiticVR) → `frame.godot_clipboard` (nops the `Intrinsics.checkNotNull` before that check-cast; Godot's
clipboard methods return at once; analysis `godot_clipboard`, ANALYSIS_VERSION 10; checked on real godot-lib/templates
4.2.2-4.4.1, not on the device; 4.1 and 4.5+ don't throw). 2D apps (vr_kind none)
keep every suggested patch that isn't about VR (`needs_vr = False`), not a fixed list. FramePort on the Frame:
`frame/local.py` (127.0.0.1, own key authorized; "This Frame (experimental)"; app data in
~/.local/share/frameport-app because ~/.local/share/frameport is the agent's) and Steam library changes wait while
@@ -505,7 +645,7 @@ recipe keeps it on). I Am Cat works with issues (judder; scale 0.8 no help). Rob
libroblox, also with the Vulkan shim). Installs skip the Steam restart when the shortcut is unchanged and wait while a
game runs (agent v37). New default fixes in FramePort's code reach games already in a library: after every app update
`library._follow_catalog` re-derives each non-user recipe once (setting `recipes.app_version`; tests switch it off via
`library.REFRESH_ON_UPDATE`); analysis fields added later still need a re-analysis. Troubleshooting techniques: docs/PLAYBOOK.md "Debugging techniques". Unity `boot.config` "vulkan" substring mislabels GLES games as Vulkan
`library.REFRESH_ON_UPDATE`). Analysis fields added later: **bump `analysis/detect.ANALYSIS_VERSION`** (stored as `analysis.extra.analysis_version`); older entries whose APK is still there are analysed again at the GUI's start (background thread, not a job: ~8 s per 900 MB APK) and before a build (`pipeline.refresh_analyses`, GitHub #104); only `analysis`/`suggested` change, user recipes stay; an unreadable APK gets `analysis_failed` = the version (not retried until the next bump). Troubleshooting techniques: docs/PLAYBOOK.md "Debugging techniques". Unity `boot.config` "vulkan" substring mislabels GLES games as Vulkan
(I Am Cat ran GLES); OVRPlugin's "Unavailable OpenXR extension: XR_FB_scene" is routine (no longer triaged).
**Round 4 (2026-10-04):** XR_KHR_android_surface_swapchain is listed by the Frame's runtime but returns
FUNCTION_UNSUPPORTED → adapter `surface_emul` (default on, `native/adapter/surface_swapchain.c`): an ordinary runtime
@@ -524,7 +664,7 @@ picker at all: a FramePort fix would be an injected picker activity. Generated c
Steam placeholder set (no Steam art at all) → fixed in artwork/steam.py.
**Steam library failures (GitHub #21/#30):** on some Frames Steam never loads FramePort's shortcuts.vdf entries (same SteamOS build and arm64 beta client as the dev Frame; IDs correct; file written before Steam starts): Play → console_log `GameAction [AppID <id>] … RequestingLicense → UpdatingAppInfo → LaunchApp failed with AppError_9` (= "Game configuration unavailable", Steam treats the id as a store app). Cause unknown; agent v42/43 diagnostics: `launch` returns `steam` (started / error + code / silent from console_log), `collect_diag` adds `steam_library` (real Steam dir, beta, Steam start vs vdf write time, entries per account, devkit games) + `steam-console.txt` (incl. Steam's `logs/shortcuts.previous.txt` if present; steamclient.so has "LoadShortcuts: rejecting attempt to load shortcuts: invalid account ID"). **Fallback (agent v43):** when Play gets AppError_9 the agent registers the game through Steam's devkit interface (what Valve's Devkit Management Tool does, MIT: `devkit-1 steam://devkit-1/<~/.steam/steam.token>/create-shortcut?response=<file>&gameid=<id>&directory=~/devkit-game` written to ~/.steam/steam.pipe; answer file / `.error`; `~/devkit-game/<id>/launch.sh` = link to the game's launch.sh, `<id>-argv.json` ["launch.sh"] (relative to the folder), `<id>-settings.json` {steam_play 0, compat_tool ""}). Steam adds it live (no restart), picks its own appid (read back from shortcuts.vdf by DevkitGameID), shows it as a normal non-Steam game named "Devkit Game: <id>" (OpenVR 0, but VR works: owner-verified 2026-10-04), art copied to grid/<appid>*. Ids: letter first, then letters/digits/_ only (spaces, '-', leading digit → "missing/invalid arguments"). Later Plays use the devkit entry; uninstall (`devkit_unregister`, `delete-shortcut` live) and purge remove it. Verified on the dev Frame: register/launch/unregister; the AppError_9 trigger itself only in unit tests. GitHub #42 (two Steam accounts, neither MostRecent): Steam rewrote the active account's shortcuts.vdf without FramePort's entries right after starting, and added the devkit entry live without saving it, so `devkit_appid` (vdf only) failed ("Steam added the devkit entry but didn't save it"); agent v55 `devkit_appid_from_log` reads Steam's `sanitize shortcut app id "~/devkit-game/<id>/launch.sh": replacing 0 with N` console line. GitHub #41 (one account): same Steam behaviour, and every install restarts Steam, which forgets the never-saved devkit entry. Agent v56: the log fallback reads only the current session's console_log.txt; Play re-registers a devkit entry that gets AppError_9; art updates also write the devkit entry's grid art (`copy_grid_art`). Open: why some Frames' Steam never loads or saves shortcuts.vdf (single account too).
**Exit game (GitHub #36, 2026-10-04):** `Apps.TerminateApp` (what Exit game calls; reachable for tests through Steam's CEF devtools on 127.0.0.1:8080, SharedJSContext, `Runtime.evaluate` — a stdlib websocket client is enough) closed both a normal shortcut and a devkit entry on the dev Frame within 1-3 s. SIGTERM to Steam's `reaper` alone left launch.sh + Lepton (setsid) + the container running (an earlier headset launch ran on for 87 min); SIGTERM to launch.sh cleans up in ~4 s. Agent v44: launch.sh's 2 s loop ends the game when its parent (the reaper) is gone; `upgrade_launchers` (from ensure_host_fixes) adds that to existing launchers in place. Which in-headset exit path fails is still unknown. Screenshots on the Frame: all under SteamVR's appid 250820 (`760/remote/250820/screenshots`, screenshots.vdf has creation time, no game).
**SteamVR dashboard at game start (2026-10-04):** the dashboard (Resume game / controller / VR options) was open whenever a FramePort game started (also over Lepton's 2D launcher). Steam's UI exposes `SteamClient.OpenVR.VROverlay.{IsDashboardVisible,HideDashboard,ShowDashboard}` (CEF devtools, `steam_js` in the agent). Agent v44: launch.sh starts `_dashboard_worker` (waits for FrameBridge's first `pacing:` line in launch.log, then hides a visible dashboard for 25 s, at most 3 times; log `<base>/dashboard.log`; opt out with env FRAMEPORT_KEEP_DASHBOARD=1). Verified on the dev Frame (dashboard shown → 4XVR launched via Steam → hidden ~5 s after the first frames); not yet seen in the headset. **Headset (ITR2, 2026-10-05): too late** — Steam shows its frame menu (`valve.steam.gamepadui.frame.menu`, vrwebhelper_systemui.txt `[PooledPopups] Showing`) ~0.3 s after the game's first submitted frame (FrameBridge `new layer:`), but the first `pacing:` summary comes ~8 s later; the owner had pressed Resume (`[HideDashboard] return_to_game`) before the worker looked. Agent v52: watches from the first `new layer:` line (incremental log reads, 0.5 s polls, 30 s window). Agent v53: stops for good once the player opens the dashboard with the controller (`toggle_dashboard_action` in vrwebhelper_systemui.txt since the worker started): v52 closed it 60 ms after each press, and the game, paused for it, stayed paused.
**SteamVR dashboard at game start (2026-10-04):** the dashboard (Resume game / controller / VR options) was open whenever a FramePort game started (also over Lepton's 2D launcher). Steam's UI exposes `SteamClient.OpenVR.VROverlay.{IsDashboardVisible,HideDashboard,ShowDashboard}` (CEF devtools, `steam_js` in the agent). Agent v44: launch.sh starts `_dashboard_worker` (waits for FrameBridge's first `pacing:` line in launch.log, then hides a visible dashboard for 25 s, at most 3 times; log `<base>/dashboard.log`; opt out with env FRAMEPORT_KEEP_DASHBOARD=1). Verified on the dev Frame (dashboard shown → 4XVR launched via Steam → hidden ~5 s after the first frames); not yet seen in the headset. **Headset (ITR2, 2026-10-05): too late** — Steam shows its frame menu (`valve.steam.gamepadui.frame.menu`, vrwebhelper_systemui.txt `[PooledPopups] Showing`) ~0.3 s after the game's first submitted frame (FrameBridge `new layer:`), but the first `pacing:` summary comes ~8 s later; the owner had pressed Resume (`[HideDashboard] return_to_game`) before the worker looked. Agent v52: watches from the first `new layer:` line (incremental log reads, 0.5 s polls, 30 s window). Agent v59: watches 120 s and hides up to 10 times (the menu came back after 30 s in the owner's sessions). Agent v53: stops for good once the player opens the dashboard with the controller (`toggle_dashboard_action` in vrwebhelper_systemui.txt since the worker started): v52 closed it 60 ms after each press, and the game, paused for it, stayed paused.
**Space warp / ITR2 (2026-10-04):** Into The Radius 2 (UE5, `libUnreal.so` + OVRPlugin, Vulkan) uses Application SpaceWarp (extra swapchains `376x376 format=97` motion vectors + `format=129` depth); #35 reports flickering textures. OVRPort's `patch_disable_space_warp` has no effect on UE5, and hiding XR_FB_space_warp in FrameBridge's enumerate doesn't reach the game either: **OVRPort's dispatcher offers/enables XR_FB_space_warp itself**. FrameBridge `hide_space_warp` (per game, off) hides it and strips `XrCompositionLayerSpaceWarpInfoFB` (1000171000) from the projection views (log `hide_space_warp: removed space warp info`); the game still renders its MV swapchains. Test build installed on the dev Frame; flicker not yet checked in a headset. Heuristic: UE5 + OVRPlugin shows it as an option (off). `equirect_emul` is suggested for Unreal games whose graphics API isn't detected ("GLES or unknown"); on Vulkan it switches itself off (Myst/Riven/ITR2): harmless, but Unreal Vulkan detection is a known gap.
**PR #34 (merged 2026-10-04, Lucas-Mathieu):** `native/adapter/audio_metadata.h` patches Meta XR Audio Wwise (only build ID e1619e7f…, Batman: Arkham Shadow 1.4.1) so queued audio metadata isn't freed while the current audio frame references it (smoke-bomb crash). Verified headless: installs ("metadata reclamation follows …"), 72 fps; we added a once-per-second scan limit. Outside PRs: review source, rebuild `artifacts/` ourselves, merge locally (contributor's commit kept), push main.
**Heuristics eval (2026-10-04):** `scripts/eval_heuristics.py "<VR CyberDeck Portable>"` → 19/44 recipes exact, 322/352 fields (most diffs: `frame.unity_text_input` suggested where older catalog recipes lack it; the rest are runtime-only findings: use_alt, vk_shader_fix, focus_hold, sync_guard, multipass). `PATCHED/` copies aren't scored (already converted).
@@ -535,15 +675,209 @@ Steam placeholder set (no Steam art at all) → fixed in artwork/steam.py.
**Start activity / QuestCraft / ITR2 pop-in (2026-10-05):** WiiCompiled's VR part (`QuestActivity`, SDL, process `:game`) stays behind Lepton's flat window when the launcher is shown (`device.text_input_window`). `frame.start_activity` (opt-in; analysis `extra.vr_activity` = the original APK's activity with Meta's VR category when it isn't the launcher) gives that activity's filter LAUNCHER and every other LAUNCHER becomes INFO, per element (`axml.set_start_activity`); Lepton's own `apk-info-extractor --print-activity-name` (on the Frame under Lepton/liblepton/apk_extractor) then answers QuestActivity. **OVRPort gives every MAIN activity LAUNCHER + VR categories**, so the VR activity must come from the original APK's analysis, not the converted manifest. Lepton has no activity override (`APP_ACTIVITY` is reset when app_metadata.sh is sourced). QuestCraft reaches Minecraft 1.21.5 (Fabric, Vivecraft, Sodium; LTW "Large Thin Wrapper" GL 3.0 on the Frame's Zink) and stops at `OpenGL error 1282` in `WindowFramebuffer` createTexture (Minecraft's crash report in `files/instances/1.21.5/crash-reports/`, Pojlib's `files/latestlog.txt`); the vrclient SIGSEGV / "pthread_mutex_lock called on a destroyed mutex" afterwards is only the shutdown (Unity's render thread still in vrclient while the instance is destroyed). ITR2 "models popping in and out" in game: Unreal reads user config from **internal** storage (`lepton-data/internal/<pkg>/files/UnrealGame/<Project>/<Project>/Saved/Config/Android/Engine.ini`, not external); `r.AllowOcclusionQueries=0` there broke its menus (removed again). Vulkan shim `vk_query_slots` (two slots per occlusion query) never saw ITR2 create an occlusion query pool, and the run with it crashed (Unreal RenderThread SIGSEGV, caught by sentry-native: no tombstone; the backtrace is in `cache/sentry/*/.sentry-native/*.run/*.envelope` with module offsets) - left off. Next suspect: Unreal 5 mobile HZB occlusion reading depth stored with Valve's FDM injection layer; test = launcher `VK_INSTANCE_LAYERS=''` (Lepton then loads neither VALVE_rpo nor VALVE_fdm_injection, Fossilize still). QuestCraft: LTW overrides glGetError and returns 0 with `LIBGL_NOERROR` set; Lepton passes `LEPTON_ENV_<NAME>` to the app as `<NAME>`, so catalog `lepton_env: {LEPTON_ENV_LIBGL_NOERROR: '1'}` gets Minecraft past the GL error 1282; then black picture (see the catalog entry). FrameBridge logs `new layer` once per layer type per process and projection views only for the first frames, so a second session in the same process (Vivecraft after Unity) needs `layer_debug=1`.
**Vulkan validation / ITR2 solved (2026-10-05):** Khronos' validation layer can't come from Lepton (its layer dir is in the read-only guest image; the Linux copies on the Frame are glibc) and a global `debug.vulkan.layers` property (via the LEPTON_GFXRECON_FP_PROPS trick) kills the container at boot. Working recipe: bundle `libVkLayer_khronos_validation.so` (Khronos android-binaries release, arm64-v8a, stored uncompressed) into a test APK re-signed with the game's key (`apk.sign.sign`), install with `frameport install <pkg> --apk`, and set the Vulkan shim's `vk_validation=1` (it adds the layer at vkCreateInstance; findings under the logcat tag VALIDATION in launch.log, each message ID capped at 10). ITR2's flicker + windows behind models = Unreal's Qualcomm shader-resolve subpasses with a depth resolve of a single-sampled depth attachment (VUID-04908/-03179); `vk_spec_fixes=1` drops that resolve (its TRANSFER_DST half never applies to ITR2: the main depth is transient). Valve's VALVE_rpo layer removed ITR2's fog (catalog `lepton_env: VK_INSTANCE_LAYERS: VK_LAYER_VALVE_fdm_injection`). `r.ViewDistanceScale=2` in the internal Saved/Config/Android/Engine.ini only exists on the owner's Frame (the catalog can't write internal storage yet). Launch tests: Lepton prints a transient "is not a running context" on the first start after an APK change (agent v54 `not_started` waits 30 s for "Boot complete!").
**GitHub #38/#39 (2026-10-05):** PowerWash Simulator stuck at "Waiting.." on a reporter's Frame = Unity sized the right-eye image rect past its swapchain (rect 268+1656 on a 1920-wide swapchain; the runtime's recommended size differs per Frame) and SteamVR rejected every frame (`xrEndFrame failed -25`, XR_ERROR_SWAPCHAIN_RECT_INVALID), after which the game stopped calling xrEndFrame. FrameBridge `rect_clamp` (default on; sizes noted in xrCreateSwapchain; clamped layers count as swapped) keeps projection/quad rects inside their swapchain; triage `swapchain-rect-invalid`. Not reproducible on the dev Frame with `scale` (Unity's eye sizing isn't just the recommended width); no regression there (72 fps). The adapter's revision was NOT bumped (that would mark every installed game outdated): affected users rebuild. The Room VR (#38) = Unity's render thread SIGSEGV in libgallium_dri.so ~30 ms after the game recreates its eye swapchain with samples=4 (FrameBridge retries samples=1); the game's own code turns MSAA on (not OVRManager); candidate fix: hide GL_EXT_multisampled_render_to_texture (GL shim) - needs the APK to test. The launch test passed both (render thread dead / no frames): triage gap.
**Headset round 2026-10-05 (evening):** I Am Cat's judder = repeated xrLocateViews of one display time returning
slightly different poses → FrameBridge `pose_consistency` (owner-verified, works). Lucky's Tale freeze on touching a
save slot = OVRPort's OpenXR dispatcher converting XR_FB_haptic_amplitude_envelope with ns read as s (GitHub #9) →
xrshim `haptic_fix` (owner-verified); its remaining short pauses were the wear sensor ("HMD off/on" 0.5-1 s in
`logs/eyetracking.txt`, not vrserver.txt) → **`focus_hold` is on by default with 5 s** (owner's choice; C defaults
too). The Room VR works (`unity_no_overlay_copy`). BattleSisters (Unity 2019.4 built-in VR, no libOculusXRPlugin.so)
flooded "outside of frame bounds" ~2400/s and hung the GPU (kernel hangcheck, zink DEVICE LOST) → the ovrpshim frame
wait now also applies to Unity 2019 without the Oculus XR Plugin (`UnityOculusCheck.legacy_loop`). Vader Immortal
(UE4, stuck on the loading image after the intro, GitHub #49): VRP repacks carry a Frida gadget (`libfrda.so`,
config `hijack_responses`), but OVRPort's `patch_clean_up_frida` removes its loadLibrary call, so it never runs on
the Frame; `frame.ovr_trace` (opt-in, `native/ovrtrace`: 1138 exported ovr_* stubs → real loader functions, logcat
tag `fp_ovrtrace`: calls, PopMessage answers, unanswered requests every 10 s) is the next diagnostic.
**Unity built-in VR input (2026-10-05):** probe builds (`FRAMEPORT_INPUT_PROBE=1 frameport build …`; it logs
trigger/grip crossings too) showed OVRPlugin returns the full Touch state for every controller mask (also Go masks);
presses reach the games' OVRInput. Accounting+ (Il2CppDumper v6.7.46 reads its Unity 2017 metadata v24, run on Windows)
passes stance selection (NewtonVR grip/trigger) but its motion warning waits for `Input.GetMouseButtonDown(0/1)`,
which Lepton never delivers (no focused Android window) → ovrpshim registers its own
`UnityEngine.Input::GetMouseButtonDown(System.Int32)` icall (il2cpp_add_internal_call, after Unity's: resolve first)
that adds a click in the frame a Touch trigger or A/B/X/Y is newly pressed (`unity_oculus_check` revision 3).
BattleSisters (Unity 2019 InputSystem/XR InputDevices via `VrHandInput`): libunity's Oculus module (OVRPlugin
function table: a global pointer at 0x16b57c0 in this build, slots filled by name, e.g. +0x160 GetControllerState,
+0x168 State2, +0xe8 GetNodePresent) only reports controllers when `strncmp(deviceModel, "Oculus", 6) == 0` (next to
the Go check `deviceModel == "Oculus Pacific"`); Lepton's model is "Valve Lepton" → Go/unknown → only Go masks polled,
buttons dead. `unity_oculus_check` revision 4 (`oculus_model_checks`, Unity 2019 only) turns the 17 compares' length
into 0 (`orr w2, wzr, #6` → `mov w2, #0` after the adrp/add of the "Oculus" literal; the string stays: it is also
Unity's VR device name). Device result: libunity now polls `ovrp_GetControllerState(0x3)` (Touch); owner-confirmed
2026-10-06: buttons work. Its first controller vibration then hit OVRPort's haptic-envelope bug (11 GB, OOM kill) →
`haptic_fix` is now suggested for every game with libOVRPlugin.so (settings detect), BattleSisters works. Ruled out before: ProductName, GetNodePresent, the device-model string itself, exports.
Accounting+ works (owner, 2026-10-06).
**Vader Immortal (UE4, GitHub #49, 2026-10-06, headless):** stuck after the intro on an in-game image (the splash
quad ends ~6 s in; then the game's own projection frames, 72 fps, balanced xrBeginFrame/xrEndFrame). Not the Platform
SDK (`frame.ovr_trace`: only user + entitlement, both answered) and not the repack's Frida gadget (OVRPort's
`patch_clean_up_frida` removes its loadLibrary). It **leaks ~430 GPU mappings (/dev/dri/renderD128) and ~20 MB a
second** (6.5 GB + swap after 5 min, then 26 fps): page-fault stacks (perf -e page-faults, offsets resolved with the
process maps + vrclient.so's own symbols; its text segment is at file offset + 0x4000) end in SteamVR's runtime:
`xrBeginFrame → CSxrCompositorOpenVR::BeginFrame → SubmitExplicitTimingData → CVRCompositorSharedTextures::
BeginGPUTimingCommandBuffer` and `xrEndFrame → CVRCompositorClient::SubmitWithArrayIndexAndTime`. Ruled out: the
layer color scale/bias + image layout structs (FrameBridge `strip_color_bias` 1/2, diagnostic), Valve's Vulkan
layers (VK_INSTANCE_LAYERS=""), array swapchains in general (Lucky's Tale/I Am Cat flat). Kernel tracepoints aren't
allowed for steamos. FrameBridge: `layer_debug` logs xrDestroySwapchain and per-5 s xrBeginFrame/xrEndFrame counts;
`frame_balance` (ends an open frame before the next begin) exists but Vader never leaves one open. Next (built,
not yet run: the Frame slept): `layer_debug` also counts xrAcquire/Wait/ReleaseSwapchainImage per 5 s (hooked only
with layer_debug, after the other conditional hooks) to see whether Vader skips a wait or release: it doesn't (361/361/361 per 5 s).
FrameBridge `snapshot=N` (`snapshot_gl.c`, GLES): every N s the left-eye image the game submits is read back on its
own context and saved as `files/fb_snap_0-7.ppm` (quarter size) — headless launches show a black headset view, this
shows what the game draws. Vader: Lucasfilm logo (an OBB mp4: video works), then its loading card (portrait + segmented
bar) that never advances; the async loader thread sleeps and OBB reads stop (~168 MB of a 2.7 GB pak).
**SteamVR overlay apps (agent v75, 2026-10-10):** host OpenVR overlays (VRApplication_Overlay) are composited over Lepton (Quest) games on the Frame (probe checker seen over 4XVR in the headset view, `native/vroverlay_probe`); Windows ones register through Proton's vrclient too (Temporal Reality's Windows build created its overlays; fpsVR never reaches OpenVR under Proton). Detection `analysis/vroverlay.py` (bundled `.vrmanifest` with `is_dashboard_overlay`, else OpenVR + IVROverlay + self-registration markers, engine Other; Rift fingerprint `RIFT_ANALYSIS`), patch `pcvr.vr_overlay` (param autostart), Linux entry fields `vr_overlay`/`vr_overlay_autostart` (game page switch); agent `register_vr_overlay`/`unregister_vr_overlay`/`launch_vr_overlay` (IVRApplications via ctypes, live; manifest `<anchor>/frameport-overlay.vrmanifest`, the app's own key, binary = launch.sh). **SteamVR on the Frame reads only `binary_path_linux_arm`** (binary_path_linux alone is skipped). Overlay apps: no launch test, no Linux Steam-parent watchdog (`FRAMEPORT_OVERLAY`), not a "running game". A Steam overlay-app shortcut and a game shortcut run together. SteamVR's SetApplicationAutoLaunch didn't persist on the dev Frame (the watch never started by itself) → **agent v76: FramePort's own autostart**: the deployment's `overlay.autostart` is the source of truth; `ensure_host_fixes` (`ensure_overlay_service`) keeps the user service `frameport-vr-overlays.service` (`_vr_overlay_watch`, 5 s polls of the known vrserver's /proc stat) only while an overlay app has autostart on (register/unregister, uninstall, purge and the kill switch `~/.local/share/frameport/vr-overlays.disabled` / `FRAMEPORT_NO_OVERLAY_AUTOSTART` update it); a new vrserver (pid + start time, kept in `~/.cache/frameport-vr-overlays.json` across agent updates) → child `_vr_overlay_round`: wait for IVRApplications, 15 s grace, `launch_vr_overlay` for apps not running (folder process or GetApplicationProcessId); log `~/.local/share/frameport/vr-overlays.log`. Dev Frame: adopted the running vrserver, skipped the already running watch; a start after a real SteamVR start/boot is not yet seen. Details: docs/FRAME_RUNTIME.md "Overlay apps".
**Lepton's logcat mirror dies (agent v67, 2026-10-09):** occasionally launch.log ends with `logcat: Unexpected EOF!` right after the game starts (about 1 launch in 50, Lepton 2.8.14 and 3.0.5): no dashboard auto-hide, no launch-test result. launch.sh's `_logcat_keeper` then reads `podman exec lepton-steamlaunch-<appid> logcat` itself into launch.log (up to 5 restarts while the game runs).
**Double launch (agent v57, 2026-10-06):** a second Play while Lepton still boots (~10 s with nothing to see) made
the second Lepton stop the first one's container ("Waiting for steamlaunch-<appid> (PID …) to exit", exit 137
"(starting)", "Clearing baked app data due to early exit") and both died (Vader, BattleSisters). launch.sh now takes
`flock` on `<base>/.launch.lock` (fd 9, inherited by Lepton; the watchdog and dashboard helpers close it with
`9>&-`); a second launch exits 0 and logs to `<base>/launch-dup.log`. `upgrade_launchers` adds it to existing
launchers. Verified on the device (second launch during boot: ignored, the first kept running). Vader on its loading
card ignores input too (owner pressed/held every button: presses reach the runtime, the card never changes).
**Issue triage round (2026-10-06, owner's headset):** vibration in OVRPlugin games = 2 s vibrations updated per frame and
stopped with amplitude 0; FrameBridge turns amplitude 0 into xrStopHapticFeedback (else each buzz ran 2 s), haptic_fix
uses the envelope RMS (was its peak), per-game `haptic_scale` → Creed/The Boys/Jurassic World fine. Lambda1VR (TBXR):
loader named by Build.MANUFACTURER (`frame.tbxr_vendor`), no graphics extension enabled (FrameBridge adds
XR_KHR_opengl_es_enable + asks for the requirements on -50), always multisampled render-to-texture (GL shim gives
single-sampled stand-ins when GL_EXT_multisampled_render_to_texture is hidden) + its xash/ data → works. Jurassic World:
`frame.vrapi_stub`. Metro Awakening: contributor's vkshim fixes + hide_space_warp + no VALVE_rpo (jumping polygons like
ITR2). Pinball FX VR: hide_space_warp (stutters). Eleven: a Meta online request fails after platform init → needs Meta
services. Star Wars Tales: top half black + freeze after loading; ruled out: asset-file paks (never requested), Valve
foveation (off: no change), GL errors (MESA_DEBUG=1: none), EGL_BAD_ACCESS once in OVRPlugin init (harmless); the game's
eye image reads back black. BlazeRush: VrApi bridge exports + ovr*_ToString stubs + avatar stub get it to the menu room
at 72 fps, full input reaches it (diagnostics `input 5s`: Touch type only, sticks 1.0, buttons, poses 0x8f), but the room
draws no controllers and ignores input; avatar loader forced to its "Failed" path (no logged-in user) changed nothing.
`frame.ovr_trace` can't trace Unity games (P/Invoke dlsym; nothing imports ovr_*). Headless: the VrApi bridge's 30 s
head-pose deadline ends VR mode without a worn headset (not a game bug).
**Lepton storage (2026-09-30):** each app's /sdcard (= /storage/emulated/0 → `<base>/lepton-data/external`) has `Movies`/`Download`/`Documents` symlinked to the Frame's `~/Videos`/`~/Downloads`/`~/Documents` (liblepton/mounting.sh, only if they exist at start); agent v24 `storage_targets` reads that mapping. Android's MediaProvider canonicalises paths to /home/steamos/... and rejects every file ("doesn't appear under [/system/media...]"), `sm list-volumes` is empty: the media index never works, apps must browse folders. Lepton installs with `adb install -g` (runtime permissions granted, MANAGE_EXTERNAL_STORAGE too). Files: `install/files.py`, `frameport frame send|storage`, GUI Files tab (formerly Frame → Send files).
**SteamVR per-app settings (2026-09-30):** editing steamvr.vrsettings while SteamVR runs is lost; the web API (127.0.0.1:27062 /app/setsettings) needs `x-steamvr-secret`. `native/vrsettings` = `fp_vrsettings.exe` (freestanding, OpenVR `FnTable:IVRSettings_003` as a Utility app, loads SteamVR's bin/win64/openvr_api.dll) sets them live and SteamVR persists them: section `steam.app.<shortcut appid>`, keys `preferredRefreshRate` (float) and `motionSmoothingOverride` (0 global, 1 on, 2 off, 3 always). Steam Link (vrlink) lists the Frame's rates 72/80/90/96/108/120/144 in vrserver.txt and follows the per-app preference ("host preferred N Hz"; whether the key is honoured is unverified in-headset yet). Judder metric: vrcompositor.txt session summary dropped + "Timed out. N total" (Stormland: 0 dropped but 313 timeouts in 2 min); fpsVR (`%LOCALAPPDATA%\fpsVR\*.json`, 0.1 ms histograms) gives p99 CPU/GPU ms. `pcvr.steamvr_tuning` (default on, PC only) applies on Play: highest rate whose budget ≥ p99×1.05, at least one step down, smoothing on.
**Language packs (merged from PR #20, 2026-10-04):** overport's `libovrplatformloader.so` is a dispatcher that `dlopen`s Meta's own loader (`libovrplatformloader_meta.so` / `_meta_q1.so`, also `libpxrplatformloader.so`) and keeps its own message queue; `ovr_LanguagePack_GetCurrent/SetCurrent` are 8-byte `return 0` stubs in it, `ovr_AssetFile_GetList` forwards to Meta's loader. `frame.langpacks` (opt-in, `native/langpack`) serves `<tag>.lang` files from the game's data; `elf.hide_exports` marks the loader's exports STB_LOCAL (bionic and glibc only match GLOBAL/WEAK; verified for glibc, bionic's `is_symbol_global_and_defined` is from memory). `libfp_langpack.so` is built here (`python native/build.py --only langpack`) and committed. Owner-verified 2026-10-04: Deadpool VR with only `en.lang` and the patch on plays English dialogue and runs normally (headless launch tests show 2-6 fps while it loads: not a regression sign). A dispatcher answer that arrives after we answered the timed-out GetList is dropped (one answer per request); games already in a library get `lang_packs` filled after an app update (`library._refresh_data_fields`). The library logs to logcat under the tag `fp_langpack` (dirs looked in, packs found, every language-pack call), so it shows up in the game's `launch.log`; an `ovr_AssetFile_GetList` the dispatcher leaves unanswered for 1.5 s is answered with our packs alone. Deadpool VR (Unreal) accepts a pack only when its `Metadata` equals the game's own version string (`ULanguagePacksSubsystem` compares it with `%s.%s.%s.%s.%s` built from the build info, e.g. `1.0.40.356975.Quest` = versionName; found by disassembling `libUE4.so`): the patch writes the APK's versionName into the library (`@FPMETA@` slot, `with_metadata`), `FRAMEPORT_LANGPACK_META` overrides it. Deadpool VR (2026-10-04, headset): German became selectable with that Metadata, but dialogue stayed silent (even English once reported as a pack) while the path was spelled `/sdcard/Android/obb/<pkg>/x.lang`; with the `/storage/emulated/0/Android/obb/<pkg>/x.lang` spelling (Unreal's own, now listed first in `scan_dirs`) German dialogue plays. `FRAMEPORT_LANGPACK_SKIP=<tags>` or a file `fp_langpack_skip` in the obb folder leaves packs out of the list (experiments).
**Cube swapchains (GitHub #107, 2026-10-09):** the Frame's runtime refuses `faceCount=6` swapchains (-2,
XR_ERROR_RUNTIME_FAILURE); OVRPlugin ignores that ("CreateSwapchain for eye 0: 0x0, 0 stages") and crashes in
ovrp_EndFrame4 (memset). FrameBridge `cube_standin` (default on, `native/adapter/cube_standin.c`) serves a refused cube
swapchain as one GL cube-map texture in the app's context (GLES only) and drops its layers. Verified headless with
Budget Cuts Ultimate (2048² sRGB, 12 mips; runs on at ~70 fps); what the cube layer showed is simply missing.
**Hardware video decoding (PR #96, PR #128 by Lucas-Mathieu, adapted 2026-10-10, agent v71):** `native/hevc` = one
OMX plugin `OMX.frameport.{avc,hevc,vp9}.decoder` (FFmpeg v4l2m2m on Iris /dev/video-dec0, FFmpeg software decoders as
fallback inside the component, Vulkan copy for big native surfaces). The connection installs it once per Frame
(`ensure_video_codec` → agent `video_codec_status`/`install_video_codec`: `~/.local/share/frameport/video-codec/versions/
<manifest sha>` + `current` symlink). A Frame keeps the same or a newer `revision` (no flip-flop between PCs) → **bump
`revision` in native/hevc/build.py with every artifact change**; a failed install isn't retried during the connection.
Per game: only launchers of games whose recipe has `frame.hw_video_decode` (install stage, no APK change; suggested
from analysis `media_codec`, ANALYSIS_VERSION 8; deployment.json `hw_video_decode`) get the codec line → the Podman
wrapper (native/hevc/podman.py) mounts the plugin into that container (merged media_codecs.xml in
$XDG_RUNTIME_DIR/frameport-video). Off for every game: Settings → Installing (library `video.hw_decode` → agent
`video_codec_switch` = video-codec/disabled); one game: `FRAMEPORT_NO_HW_VIDEO=1 %command%`. `upgrade_launchers`
converts agent ≤70 launchers (Batman's `<base>/frameport-codec` → keeps the line, folder then removed). Batman: catalog
+ migration `batman_video_patches` (`frame.hw_video_decode` + hidden adapter setting `surface_native`). Iris: 8K
refused (ENOMEM) while any other decoder session is open; SteamVR's vrlinkrunthread holds one ~12 s at game start →
refused opens retry until 20 s after the plugin loaded (else 2 s), then software; VP9 7680x3840 never returned a
picture → VP9 ≤4096x2304; hidden VP9 frames come back as empty capture buffers (bytesused 0, no LAST):
FFmpeg's wrapper ended the EOS drain at the first one (two-pass VP9 lost its last ~25 frames) → build.py requeues
them (codec revision 8; drain ends at LAST, or 1 s after a skipped empty buffer; dev Frame 2026-10-10: 600/600 =
OMX.google.vp9 Y hashes). Rebuilds are deterministic (ext4 + NTFS path
with spaces, two NDK copies → same; rev 8 141de01f…; PR #128's own sources → its 27a2d749…). Not yet run on the device.
**Vulkan shader dump (GitHub #140, 2026-10-10):** adapter `vk_shader_dump=1` (Vulkan shim, `native/vkshim/shader_dump.h`;
applies where vk_sanitize can: Unreal/Other arm64) writes each distinct SPIR-V module once to
`files/fp_vk_shaders/<size>_<sha256>.spv` (tmp + rename) and one `index.txt` line per vkCreateShaderModule (`<seq> <ms>
<unix ms> <name> new|known|again|failed`, O_APPEND) to find the module behind a GPU hang. Agent v72 `collect_diag`
returns `shaders` (newest modules of fp_vk_shaders + fp_spirv, ≤4 MB each, base64) → bundle
`games/<pkg>/target/shaders/`. Triage `gpu-hang` suggests both dumps; `triage.graphics_api` (FrameBridge's
xrCreateSwapchain formats: <0x1000 Vulkan, else GL) keeps only the session's API's (`API_ONLY`). Host-tested only.
Agent v74: a reporter's hang came ~10 s after the last new module, so the culprit wasn't among the newest written →
with an index naming modules, `shader_dumps` sends every module of the index's **newest session** (after the last
`# start`), deduplicated, last used first, each ≤4 MB, ≤30 MB in all (`session` = how many it names) + that session's
index lines; no index (fp_spirv) = the old newest-by-mtime 4 MB. One JSON line (~40 MB base64) over the SSH channel
is read whole by `Frame.agent` (fine at that size); `_Writer.fit` drops the least recently used modules last, after
cutting logs, to stay under the 24 MB zip limit.
**Unreal OBB check (GitHub #159, 2026-10-10):** Epic's DownloaderActivity checks OBB name+size (OBBData), then with `bVerifyOBBOnStartUp=true` CRCs the whole OBB; its screens are invisible in Lepton (stuck at `Displayed …/.DownloaderActivity`, triage `unreal-obb-check-stuck`) → `frame.unreal_skip_obb_check` (analysis `unreal_verify_obb`, ANALYSIS_VERSION 11) flips the manifest boolean (`axml.set_meta_data_bool`); skips only the CRC pass. Host-tested only.
**Vivox API 31 (GitHub #101, 2026-10-09):** newer Vivox builds (Green Hell VR) call Android 12 AudioManager
communication-device methods from `com.vivox.sdk.AudioChangeListener` with no SDK check → NoSuchMethodError on Lepton's
Android 11. `frame.vivox_audio_route` (analysis `vivox_api31`, ANALYSIS_VERSION 4) makes every such method return at
once (`Dex.return_early`: return-void / `const/4 v0,0; return v0`; nopping the invoke would leave a move-result the
verifier rejects). Older Vivox (Eleven Table Tennis, BattleSisters) lacks that code and isn't matched. Verified
headless: Vivox initialises, 150 s at ~65-70 fps.
**Steam Input gamepad for 2D apps (GitHub #162, agent v72, 2026-10-10):** Lepton's Android only has the Wayland seat's `wayland_touch/keyboard/pointer`. Steam makes its virtual pad (uinput, `/devices/virtual/input`, 28de:11ff, "Microsoft X-Box 360 pad N", ACL for steamos) only while the Frame's controllers are on (controller.txt "Steam Controller reserving XInput slot 0"; gone when they sleep), also with no game running; steamos-manager's 28de:0000 keys device isn't a pad (no BTN_SOUTH). Lepton's container root (system_server too) = steamos (`keep-id:uid=0`), so a bind-mounted `/dev/input/eventN` opens. Opt-in `device.steam_gamepad` (vr_kind none; suggested for `android.hardware.gamepad`/`LEANBACK_LAUNCHER`, analysis `gamepad`, ANALYSIS_VERSION 9): deployment.json `steam_gamepad` → launcher `GAMEPAD_LINE` (after the codec line: exports FRAMEPORT_GAMEPAD + LEPTON_ENV_SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD, puts `agent/bin` first on PATH) → `PODMAN_WRAPPER` (written by ensure_host_fixes; non-`run` calls go straight on; `run` imports the agent's `podman_run_args`; hands on to the next Podman after its own PATH entry with itself removed from PATH, so it chains to the codec wrapper either way round) adds the pads + `Vendor_28de_Product_<pid>.kl` (Xbox 360 layout). Verified headless with a uinput stand-in pad (Stremio's container: EventHub `classes=0x80000141`, our key layout, KeyEvent BUTTON_A dispatched); with Steam's real pad and a game: not yet. Pads made after the start aren't seen until the next one.
**Multiview programs on flat framebuffers (GitHub #77, Doom3Quest, 2026-10-09, prototype):** Mesa enforces OVR_multiview's
"program num_views == draw framebuffer views" rule (`draw_validate.c`, the draw is dropped silently); Qualcomm doesn't.
Doom3Quest compiles every VS with `layout(num_views=2) in;` and draws its HUD/PDA into 2D-texture FBOs → black. Opt-in
`frame.gl_multiview_fbo` (analysis `gl_multiview_libs`, ANALYSIS_VERSION 6; own-engine GLES only): `native/glmv` =
`libfpglmv.so` (12 chars = "libGLESv3.so": libdoom3.so's one `.rodata` dlopen string is rewritten in place, its qgl*
table comes from dlsym on that handle; also first DT_NEEDED for its direct gl*/egl* imports). Such draws use a lazily
built single-view twin (view 0, uniforms copied per draw); eglMakeCurrent resets the per-thread cache. Host-tested only
(rewriter on all 19 Doom3Quest shaders + glmv.c against a stand-in GL, `tests/test_gl_multiview_fbo.py`); the host's
Mesa 23.2 llvmpipe has no OVR_multiview. Headset-verified by Xandrix1987 (2026-10-09: HUD/PDA shown, no fps drop) -> in the Doom3Quest catalog recipe.
**BlazeRush (GitHub #57, Klownicle's verified guide, 2026-10-10; FramePort's version headless-tested only):**
`frame.blazerush` (1.0.349 only: exec-segment SHA + every word checked, offsets from PT_LOAD) NOPs the avatar-readiness
wait (0x4b2dbc), RETs the avatar renderer (0x4b2adc), renames `cache/shader_cache` → `cache/br_shader_v1`. GL shim
(now also exports glShaderSource/glLinkProgram/glProgramBinary/glDeleteProgram/glDraw*: pass-through unless a setting
is on) `gl_int_attribs` (integer inputs fed via glVertexAttribPointer re-set with glVertexAttribIPointer per draw,
reflection cached per EGL context + program, dropped on link/binary/delete) and `gl_highp_markers` (vertex shaders with
every '|' marker: mediump→highp except on in/out/uniform lines); host test `tests/test_blazerush.py` with a stand-in
GL. FOV: the game reads VrApi props 7/8 right after vrapi_Initialize (no session; the Frame has no
XR_EPIC_view_configuration_fov) → the bridge saves the live FOV (also located before tracking, headless: 118.87×120.04
on the dev Frame) in `files/framebridge-vrapi-fov.txt` and answers with it at the next start (log `FOV properties: …
(saved by an earlier session)`). `device.config_sync` (catalog field `config_sync`, agent v78): user_config.xml
r_3dwidth/height, r_width/height from the runtime eye size (FrameBridge logs `runtime recommended eye size`) × scale,
written at finalize, set_settings and by launch.sh before Lepton starts (`_config_sync`). Headless runs at scale 1.7
end in `zink: DEVICE LOST` right after the bridge's 30 s no-tracking deadline tears the session down (gpu fault at the
teardown; not at scale 1.0): a headless-only artifact, the headset never hits that deadline.
**Session triage (agent v70, 2026-10-09):** launch tests never reach FOCUSED, so real play sessions are triaged
too. list_installed gives each game `last_play` {start, end, test} from `<anchor>/plays.log` (launch tests write a
`test <unix>` line first → test sessions are only marked); `session_log` returns the newest session's launch.log
(`<base>/session.log`, ≤4 MB: head + tail + FrameBridge/crash lines between) + that session's logcat-crash.log +
`journalctl -k` GPU lines ("kernel: …"). The GUI's connection refresh (`app._check_sessions` → `pipeline.sessions_due`,
background thread; sessions >7 days old are skipped) runs `pipeline.triage_session` = `validate/session.analyze`
(triage.yaml signatures incl. `space-warp-used` (info, `question:`, never auto-applied), `gpu-hang` (`report: true`)
+ computed `slow-frames` (pacing windows vs the nearest refresh rate) and `focus-dips` (FrameBridge always logs
`focus: lost` / `focus: back after N ms`, session_fixes.c)) → library `last_session` / `last_session_checked` → game
page "Last session" callout (Try this fix / Rebuild with this fix / Yes-No question / Report / Dismiss). Suggestions may
be values (`adapter.scale=0.85`, `triage.split_suggestion`); adapter-only fixes are pushed live (`apply_suggestions_live`
→ agent set_settings, like the Game settings dialog). CLI `frameport session <pkg> [--apply]`. PC VR (Proton) launchers
log no session end, so they aren't triaged yet. pac_hints stays triage-only: a survey of all 68 dump APKs found unpaired
PAC hints in 21 libraries of 18 games (OpenSSL's 38/40 in most UE4 libUE4.so and libEOSSDK.so, UE5 libUnreal.so ~400
vs +2-3, libass, libopencv, …), most of them games that work, so default-on would rewrite many working builds.
**Unresolved (as of 2026-09-28):** Arcsmith (right-eye distortion) and Time Stall (both eyes) — swap, tracking, Valve
layers, depth, pacing ruled out. Sniper Elite VR (DEVICE LOST), Espire 1 (Mesa GL upload crash), HITMAN 3 (freedreno
crash): use PC versions.
**Install links / FrameDrop button protocol (2026-10-07, not yet clicked end to end from a browser):** FrameDrop
(framedropvr.com, a closed-source Windows sideloader) defines "Install with FrameDrop" buttons:
`https://framedropvr.com/install?manifest=<url>|url=<file>` → that page opens `framedrop://install?…` (1.6 s, else its
home page); manifest `{"schema":"framedrop.install/v1","name","files":[{"url","sha256"}]}` (name = Steam title; .apk
or Linux .zip). `deeplink.py` (no Flet) parses framedrop://, frameport:// and the pasted https link, enforces its
rules (https; http only on loopback; no credentials; no LAN/loopback/link-local IPs, also after DNS and redirects; URL
ends in a file name), caps manifests at 256 KiB, ignores non-hex sha256 (FrameDrop's own example has a placeholder),
downloads into `<data>/downloads/<slug>-<hash>/` (OBBs → `obb/` next to the APK, `.part` removed on cancel/mismatch);
`pipeline.add_from_link` routes APK / Linux / exe and sets `title` + `title_locked` + `link` (a bare file link keeps
FramePort's title). GUI: `views/link_dialog.py` (always asks first), Add games → "Install from a link…", CLI
`frameport open-link [--yes --no-install --gui]`. **A `flet build` bundle can't take a URL argument** (the Flutter
host treats any argv as a developer page URL), so `urlhandler.py` registers a script, not FramePort.exe: Windows
HKCU `Software\Classes\{framedrop,frameport}` → hidden PowerShell `frameport-link-handler.ps1`; WSL (source runs)
the same keys → `wsl.exe -d <distro> -e sh frameport-link-handler.sh`; Linux `frameport-links.desktop` +
`xdg-mime`; macOS unsupported (Apple Events, paste instead). The script drops the link into `<data>/links/*.link`
and starts FramePort unless `<data>/gui.alive` is < 10 s old (`gui.starting` stops double starts); the GUI's
`_watch_links` thread touches the heartbeat and opens links (newest window session). Settings → Install links: one
switch per scheme (`links.framedrop`, `links.frameport`, default on); a scheme another program owns (FrameDrop) is
only taken with "Use FramePort for these links" (`register([s], force=True)`); off removes only FramePort's own
registration (`MARK` in the command). Never registered with FRAMEPORT_HOME/FRAMEPORT_NO_LINK_HANDLER (tests,
screenshots). Same round: files dropped on the Library (`ui/dropped.py`, bundles only like the Files tab), "Add a
Windows program (.exe)…" (`pipeline.add_windows_exe`: exe in Downloads/home/drive root copied alone into
`<data>/windows-apps/<slug>/`), patch `device.display_mode` (Automatic / VR / Flat window → `InstallContext.display`,
`installer.show_window`). FramePort-only manifest extension `"frameport": {"description", "icon"}` (bad values ignored; icon: same URL rules,
≤2 MiB, Pillow-checked, ≥32 px, saved as PNG in `<data>/downloads/icons/`, shown in the question by asset URL, then
`sources.apply_custom(pkg, "icon")` unless `.picked` exists; description fills `details.description` only when
empty). Bare file links get a title guessed from the file name (`title_from_filename`: version/arch dropped, package
names → last part). Windows test of 0.12.1.dev191 (2026-10-07): a button click opened the dialog; but a click right
after closing FramePort did nothing (the closed window's heartbeat was < 10 s old) → the handler scripts now wait
up to 4 s for the link file to be taken before trusting the heartbeat, the window's CLOSE event / atexit delete
`gui.alive`, and the Windows command runs under `conhost.exe --headless` (plain `-WindowStyle Hidden` flashed a
console). Screens: `scripts/ui_smoke.py --links --fake-frame --game <pkg>` (tall pages: `--viewport
1280x7000`; Flutter's popup menu ignores Escape).
Demo link: the homepage's example button = `deeplink.DEMO_MANIFEST` (https://frameport.app/demo/cool-game.json, published
from site/public/demo/); every form (https button page, frameport://, framedrop://, pasted) → `InstallRequest.demo`,
no network (fetch_manifest returns `demo_manifest()`, download refuses), `link_dialog.show_demo` (Install =
`easter.demo_install`: Cool Game's cover out of the portal; eggs off → "Nice, it works!"), CLI just says so.
## Releases, CI, GitHub
Maintainer-only notes (accounts, credentials, key locations) live in the git-ignored `CLAUDE.local.md`.
Public repo `github.com/spoopyghosty0/frameport` (branch `main`). Push a `v*` tag → CI (`.github/workflows/build.yml`)
@@ -557,6 +891,7 @@ Installed apps find the release themselves (self-update), so the notes are what
replace the rolling `dev` pre-release (`dev-release` job; same assets + SHA256SUMS). Automatic update checks ignore
pre-releases; testers use Settings → Updates → "Install the latest dev build…" (`updates.check_dev`,
`Updater.install_dev`). `parse_version` sorts 0.9.0 < 0.9.1.devN < 0.9.1, so testers get the next release normally.
- **Upstream trackers:** issues labelled `upstream` (#73 microphone, #74 haptics, #75 VrApi bridge) list our workarounds for OVRPort bugs and how to drop them; check them against OVRPort's latest release before each release.
- **Release checklist:** bump `src/frameport/_version.py` (the only version; `scripts/package.py` fails a tag build
whose tag ≠ `v<_version>`), commit, `git tag -a vX.Y.Z -m "FramePort X.Y.Z" -m "<What's new, Markdown bullets>"`,
push the commit and the tag. Never publish a release without its `SHA256SUMS.txt` (the updater refuses it) and keep
@@ -572,6 +907,11 @@ Installed apps find the release themselves (self-update), so the notes are what
Windows console) plus PYTHONUTF8; macOS builds need `--python-version 3.12 --arch arm64` (cryptography has no wheels
for flet's default Python / x86_64 cross-build), with a PyInstaller fallback step; `astral-sh/setup-uv` has no
floating major tags after v7 → pin the exact version; force-moving a tag starts duplicate runs (cancel one).
- `jni` (2026-10-06): jni_flutter 1.0.4/1.0.4+1 generate bindings that require jni ^1.1.0 while Flet's build template
pins jni 1.0.0 → every `flet build` bundle failed (`JniVersionCheck`, "generated bindings expect package:jni
^1.1.0"). pyproject `[tool.flet.flutter.pubspec.dependency_overrides] jni = "1.1.0"` fixes it (pinning
jni_flutter 1.0.4 does not); drop it once Flet's template moves to jni 1.1. A failed tag build publishes nothing:
delete and re-push the tag on the fixed commit.
- macOS runner (2026-10-02): `macos-latest` jobs went unassigned (cancelled after 15 min, no steps); `macos-15`'s Xcode
16.4 fails a Flutter plugin (`NWPath has no member`); `macos-26` (Xcode 26) builds with `flet build`. The PyInstaller
fallback (`package.py --pyinstaller`) passes `--yes` so a failed `flet build`'s folder doesn't stop it at a prompt.
+94 -83
View File
@@ -7,117 +7,127 @@
![Platforms](https://img.shields.io/badge/platforms-Windows%20%7C%20macOS%20%7C%20Linux-blue)
![Steam Frame](https://img.shields.io/badge/Steam%20Frame-supported-1b2838?logo=steam&logoColor=white)
Install games that target the Meta Quest, Android, or general PCVR onto your **Valve Steam Frame**. FramePort handles everything from uploading game files, setting up your Frame, injecting compatibility patches, and adding shortcuts to your Steam library. FramePort aims to be as simple as possible by taking advantage of the fact that the Steam Frame runs on Linux.
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.
![Library](docs/images/library.png)
[![FramePort: library, one-click install, play, monitor](docs/images/tour-teaser.webp)](docs/media/frameport-tour.mp4)
> **Notice:** FramePort explicitly does NOT download, share, or unlock games. You must provide legally obtained game
> executables. Core features of FramePort simply download and wrap other published tools (see [Built on](#built-on))
> with patches provided by FramePort adding a hardware compatibility layer. This enables users to use games/apps legally
> purchased on sites like [SideQuest](https://sidequestvr.com/).
▶ [Watch the full tour](docs/media/frameport-tour.mp4) · New to FramePort? [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.
>
> 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.
## Features
- **Painless setup:** one short command on the Frame. No root, no `sudo`, no password.
[What it changes](docs/FRAME_SETUP.md).
- **Type on Frame:** use your computer's keyboard on the Frame: in VR apps, Android apps, Steam and the desktop.
[More](#type-on-frame).
- **One click per game:** convert, patch, sign, upload, add to Steam with artwork, launch test. Artwork that can't be found automatically can be picked from the stores or replaced with your own images.
- **Per-game recipes:** a tested catalog plus detection rules; every patch explained in plain words.
- **FrameBridge:** FramePort's OpenXR adapter emulates what the Frame natively lacks (passthrough, room, controller models,
curved and 360° layers); game settings as simple switches.
- **Beyond Quest:** Android apps as windows, PC VR via Proton or Revive, Windows (non-VR) games via Proton, a Files
tab with drag and drop.
- **Linux apps:** install arm64 Linux apps (AppImage, a folder, or a zip/tar archive) on the Frame with a Steam
shortcut; they run natively on SteamOS.
- **Screenshots tab:** the screenshots you took in the headset, sorted by game (matched by play time) and day;
view them and download them to your computer.
- **Live view tab:** watch what the headset shows, with sound, in a browser window on your computer.
- **Self-updating** releases, redacted diagnostics, one-click problem reports and working-config sharing.
![Library](docs/images/library.png)
## Quick start
- **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 90+ games; other games get suggested patches, each explained in
plain words.
- **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).
- **Updates itself**, and reports problems without personal data.
1. [Download](https://github.com/spoopyghosty0/frameport/releases/latest) and unzip the build for Windows, macOS
(Apple Silicon) or Linux, then start FramePort.
2. **Connect the Frame** (once):
1. In FramePort click **Steam Frame → Show setup command**. Keep FramePort open; the Frame and your computer must
be on the same Wi-Fi.
2. On the Frame open the **SteamVR dashboard → Launch a program → Desktop**: the Linux desktop opens on a virtual
screen.
3. Open the app menu (bottom-left corner of that desktop) → **System → Konsole** (or search for Konsole).
4. Type the command FramePort shows exactly as shown (on-screen keyboard or any USB/Bluetooth keyboard) and press
**Enter**. This will run the following [bash setup script](bootstrap/bootstrap.sh).
5. After a few seconds the desktop closes by itself (Steam restarts once); that's expected. If
Steam asks to install **Lepton**, confirm it. FramePort shows the Frame as connected within a minute. No
password needed.
3. **Add games → Scan a folder** with your game backups (APK + OBB, or PC VR game folders).
4. Open a game → **Install on Frame**, then play it from the Frame's Steam library.
Details for each: [Install and first steps](docs/INSTALL.md).
Full guide, firewalls and troubleshooting: [docs/INSTALL.md](docs/INSTALL.md). Questions (e.g. how to lay out games
with OBB files): [docs/FAQ.md](docs/FAQ.md).
## Type on Frame
Typing in VR is painful, so FramePort turns your computer's keyboard into a keyboard for the Frame. Open **Type on
Frame** (keyboard icon on the sidebar's Frame card, the Steam Frame page, or a game's menu), select a text field in
the headset and type: searches, logins, chat, in any app, in Steam or on the desktop. Paste longer text to type it in
one go. Nothing to install: FramePort adds a virtual keyboard on the Frame while the window is open, without root.
![Type on Frame](docs/images/type-on-frame.png)
Unity apps whose text fields close the moment you select them on the Frame (no system keyboard there) get a per-game
fix, so Steam's on-screen keyboard and Type on Frame work in them too.
[Details](docs/INSTALL.md#typing-on-the-frame).
## Screenshots
Screenshots you take in the headset show up in FramePort's **Screenshots** tab, grouped by day and matched to the game
you were playing. Open one full size, step through them, and download single shots, a selection or all of them to
your computer.
![Monitor](docs/images/monitor.png)
![Screenshots](docs/images/screenshots.png)
![Screenshot viewer](docs/images/screenshot-viewer.png)
## Quick start
## Live view
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:
The **Live view** tab streams what the headset shows, with its sound, to your computer: click **Start live view** and
it opens in your default web browser (full screen with a double-click; click **Sound on** to hear it, as browsers start
videos muted). Pick 360p to 1080p, or the headset view's full size. The picture comes from SteamVR's built-in headset
view on the Frame and is encoded there while you watch (about one CPU core), so stop it when you're done. It's black
while the headset sleeps.
```
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.
Full guide, firewalls and troubleshooting: [Install and first steps](docs/INSTALL.md). How to lay out game folders:
[FAQ](docs/FAQ.md).
## Compatibility
If a game has already been tested with FramePort, it will automatically use the optimal game config. Otherwise, FramePort
will attempt to guess key patches. If you find a new config that works for an app you are testing, please consider submitting it to the community!
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).
**[List of tested games](docs/GAMES.md)**
## Compared with other tools
**Tested something? Share it.** In FramePort open the game → **…** → **Share working config…** (it fills in the
recipe for you) or **Report a problem…** (attaches a diagnostics zip with personal data removed). Without the app:
[share a working config](https://github.com/spoopyghosty0/frameport/issues/new?template=working-config.yml) · [report a problem](https://github.com/spoopyghosty0/frameport/issues/new?template=bug-report.yml). Shared configs become built-in recipes for everyone.
FrameDrop and Valve's own tools install an app as it is. FramePort differs in four ways:
- **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 90+
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.
<details>
<summary>All differences</summary>
| | **FramePort** | **FrameDrop** | **By hand with Valve's tools** |
|---|---|---|---|
| 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) |
| 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 90+ 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: &lt;title&gt;"; 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 |
| 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 |
| Use the Frame from your PC | Live view, Monitor, Files, Screenshots, Type on Frame | Not stated | No |
</details>
Other tools as described on their 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).
## Built on
Most of the work is done by these projects:
[OVRPort](https://github.com/Android-XR-Bridge/OVRPort) (Quest → OpenXR, originally
[ovrport/app](https://github.com/ovrport/app)) · Valve Lepton, Proton and SteamVR ·
[Revive](https://github.com/LibreVR/Revive) · Mesa (Zink) · [Khronos OpenXR SDK](https://github.com/KhronosGroup/OpenXR-SDK)
· Eclipse Temurin, Android apksigner and NDK · [Flet](https://flet.dev) · OculusDB and Steam store data.
What FramePort adds itself: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
What FramePort adds: [Architecture](docs/ARCHITECTURE.md).
## Documentation
| | |
|---|---|
| [INSTALL.md](docs/INSTALL.md) | Install, connect, update, PC VR, files, problem reports |
| [FRAME_SETUP.md](docs/FRAME_SETUP.md) | What setup changes on the Frame, networks and firewalls, undoing it |
| [COMPATIBILITY.md](docs/COMPATIBILITY.md) | What runs and how well |
| [GAMES.md](docs/GAMES.md) | Tested games and how well they run |
| [PLAYBOOK.md](docs/PLAYBOOK.md) | Symptoms and fixes per game |
| [FRAME_RUNTIME.md](docs/FRAME_RUNTIME.md) | Steam Frame runtime facts |
| [ARCHITECTURE.md](docs/ARCHITECTURE.md) | How the code is organised |
| [CONTRIBUTING.md](CONTRIBUTING.md) | Code, recipes, translations |
| [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 |
| [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 |
| [Contributing](CONTRIBUTING.md) | Code, recipes, translations |
## Development
@@ -128,8 +138,9 @@ uv run frameport --help # command line
uv run pytest # tests
```
## AI Usage Notice
While I would like to program everything manually, I no longer have much free time for personal projects. As a result I make use of AI tools to make it significantly quicker to debug compatibility issues.
## AI usage
I don't have much free time for this project, so I use AI tools to debug compatibility problems faster.
## License
+2
View File
@@ -11,6 +11,8 @@ FramePort is GPL-3.0-only (see `LICENSE`). It includes or downloads the followin
| Android NDK runtime (statically linked libc++) | native Android libraries | Apache-2.0 with LLVM exception, plus legacy notices (`native/vrapi-bridge/licenses/ANDROID-NDK.txt`) |
| Flet and Flutter (desktop app runtime) | release bundles | Apache-2.0 / BSD-3-Clause |
| flet-dropzone / desktop_drop (drag-and-drop) | release bundles | Apache-2.0 / MIT |
| FFmpeg 7.1.1 hardware HEVC wrapper (LGPL configuration, without GPL/nonfree components) | `artifacts/hevc/libstagefrighthw.so`; source/rebuild instructions in `native/hevc/build.py` and `native/hevc/README.md` | LGPL-2.1-or-later (`artifacts/hevc/COPYING.FFmpeg`); unmodified source: https://ffmpeg.org/releases/ffmpeg-7.1.1.tar.xz |
| AOSP Android 11 native media/utility headers | `native/hevc/platform/` | Apache-2.0; copyright/license notices retained in the headers |
| Python packages (paramiko, zeroconf, psutil, pyelftools, capstone, UnityPy, PyYAML, requests, typer, pyaxmlparser, Pillow, cryptography, …) | release bundles | their own licenses (see each package's metadata) |
FramePort's own native code (the FrameBridge adapter, GL/Vulkan/OpenXR shims, the Windows helpers) is GPL-3.0-only.
+3639 -63
View File
File diff suppressed because it is too large. Load diff
+17 -8
View File
@@ -1,15 +1,24 @@
48a3ecad77ec0f3215b29070e994590eb9579e04d3c88a47f37f306f9aeb01d9 ./arm64-v8a/libfp_langpack.so
a5b03bb7ae5d953d7ded2bf117b0a19c57fae604f16f54310847f8e89593dde6 ./arm64-v8a/libfp_ovrp.so
b5949e9e90e4c51e2733b9f6cd3c857a447552ac7291aec340407c27b034b600 ./arm64-v8a/libfp_vk.so
5becb96ee86fbda0ce98c0e0fd064f8ba092314aceaa400e3dcc785705dc3c23 ./arm64-v8a/libVkLayer_fp_shaderfix.so
e7554c6343ee2989b0a273ee6230e65c25bfe6499eefd00de19f5f0fee58754f ./arm64-v8a/libfp_langpack.so
321da502e0f8f46f0880aad39fe6b84bdef88f925164568814cf704a1b31a577 ./arm64-v8a/libfp_ovrp.so
40defdaddcda53bd2649eb48076fae1622bfc2bfc88e9e03c94fc8b45af2a4f2 ./arm64-v8a/libfp_ovrtrace.so
e8c0965c188665a4e565e4a3af983ef1866d1cf9225665cc90d8df286bc96ef3 ./arm64-v8a/libfp_vk.so
383054f8b3b41dde76d062c71856cd3163655e50009c08454bc1793f393ead65 ./arm64-v8a/libfpg.so
8e00552d1ab90f82749db3caad4f773351fa8a28edcc5ea5cbd1b31c434ff32a ./arm64-v8a/libframe_xrshim.so
3dfcb0f2bec47857bcecc7261c58e076d872d4649718c40850f29e10c75477dd ./arm64-v8a/libglshim.so
0780f29b4844bcf5070929d8118cfcc2e7da2f16dd0dbfe1eaf12be7f5567401 ./arm64-v8a/libopenxr_loader_generic.so
5432674a59d02f411cd853a5dc57e3fa547d1ac518f25bf433fc54c43677177a ./arm64-v8a/libfpglmv.so
8a8f6b1da8952cb3933a5b53558424a5fc56043b2527a16bb6dd62a00d2de01a ./arm64-v8a/libframe_xrshim.so
19ba5ed3c4b780f5ae46251f6958f552acfc5f13c8bb39f995579593fa8990c6 ./arm64-v8a/libglshim.so
449e13bbe58de6718d4e530177924e09552a95d748a082739899144e2cb3ccf3 ./arm64-v8a/libopenxr_loader_generic.so
1feaeafad467c4cafdf2b018a4d84b0bee200c3f711697b4ce97e66a3ba256ca ./arm64-v8a/libovrplatformcompat.so
32525cd0a9ee3d9993d8871cd6905bdcf22cde4f4ba28f5db6434ce7d6554758 ./arm64-v8a/libvrapi.so
9c82f4ff57395e220329a22d0f0f47ce88b77132bed4dfb7b57809077fd0d6c4 ./armeabi-v7a/libopenxr_loader_generic.so
1d83eb94ec1df44f29a5f525dce4d5b608dbbb13a9c7a5efeb80a4b1dc54a721 ./arm64-v8a/libvrapi.so
47f736dfeffce6aa56be2fc049c2d11b87b0d1f7389db7ee1737fcded9457811 ./armeabi-v7a/libopenxr_loader_generic.so
1871eae093432d277da4bc751bf5f3269dfcf11f9168260b0c3caadb0dedb19d ./dex/oculusos-stubs.dex
b634ab5640e258563c536e658cad87080553df6f34f62269a21d554844e58bfe ./hevc/COPYING.FFmpeg
141de01f0d40b97d4f9a720db5ab8ea442f6aa35db5272048899292884994a1c ./hevc/libstagefrighthw.so
4516971bbb2b635e19b14d2d37b9353962876f7de02043e7457327052c1fcbc0 ./hevc/manifest.json
46c3dcad2c43cc30bea3b3680b362ed84a99c15d60714bbbbb422c80ecf90405 ./hevc/media_codecs_frameport.xml
529ea35d5d82474afe9e09ad1216d269317f56dd24a05fb100fced52b7b4dbca ./hevc/podman.py.txt
1aa733117cf57ccff7cf23f0425dbfd73b0332a3ef1c905ceeca0ccd0449c4e0 ./linux-arm64/XR_APILAYER_FRAMEPORT_timefix.json
28c2430a02bbd8902c5bfb9562c6fd0e318654f9e05c095bfeb94b0450ab1b07 ./linux-arm64/libxr_frameport_timefix.so
b10b3a5c10c3339fc63fd9f6cb4d01ea29be11a863d0ebad1389ea69fd471940 ./linux-arm64-bin/fp_venc
c2c70f4af1a5c3e6089dff1130161dd417f8044f99ec639eeecf1083930aab16 ./win-x64/fp_oculushmd.exe
60ebc5fea05b8945082e2ca8b9f2eb74616012a26eb50133af273dff0fcddc42 ./win-x64/fp_vrsettings.exe
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+502
View File
@@ -0,0 +1,502 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!
Binary file not shown.
+19
View File
@@ -0,0 +1,19 @@
{
"revision": 8,
"runtime_sha256": "456e912c75cd389abcf6a63bc80e2a53bdc334371d00b200c93680388ae955e2",
"files": {
"libstagefrighthw.so": "141de01f0d40b97d4f9a720db5ab8ea442f6aa35db5272048899292884994a1c",
"podman.py": "529ea35d5d82474afe9e09ad1216d269317f56dd24a05fb100fced52b7b4dbca",
"media_codecs_frameport.xml": "46c3dcad2c43cc30bea3b3680b362ed84a99c15d60714bbbbb422c80ecf90405",
"COPYING.FFmpeg": "b634ab5640e258563c536e658cad87080553df6f34f62269a21d554844e58bfe"
},
"codecs": [
"video/hevc",
"video/avc",
"video/x-vnd.on2.vp9"
],
"build": {
"ndk_revision": "27.2.12479018",
"ffmpeg_source_sha256": "733984395e0dbbe5c046abda2dc49a5544e7e0e1e2366bba849222ae9e3a03b1"
}
}
+32
View File
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<MediaCodecs>
<Decoders>
<MediaCodec name="OMX.frameport.hevc.decoder" type="video/hevc" rank="64">
<Limit name="size" min="128x128" max="8192x8192" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="block-count" range="1-138240" />
<Limit name="blocks-per-second" range="1-7864320" />
<Limit name="bitrate" range="1-245000000" />
<Limit name="concurrent-instances" max="1" />
</MediaCodec>
<MediaCodec name="OMX.frameport.avc.decoder" type="video/avc" rank="64">
<Limit name="size" min="128x128" max="8192x8192" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="block-count" range="1-138240" />
<Limit name="blocks-per-second" range="1-7864320" />
<Limit name="bitrate" range="1-245000000" />
<Limit name="concurrent-instances" max="1" />
</MediaCodec>
<MediaCodec name="OMX.frameport.vp9.decoder" type="video/x-vnd.on2.vp9" rank="64">
<Limit name="size" min="128x128" max="4096x2304" />
<Limit name="alignment" value="2x2" />
<Limit name="block-size" value="16x16" />
<Limit name="block-count" range="1-36864" />
<Limit name="blocks-per-second" range="1-2211840" />
<Limit name="bitrate" range="1-245000000" />
<Limit name="concurrent-instances" max="1" />
</MediaCodec>
</Decoders>
</MediaCodecs>
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/python3
"""Add shared codecs to Lepton containers without editing their shared rootfs."""
import hashlib
import json
import os
import re
import shutil
import sys
from pathlib import Path
from xml.etree import ElementTree as ET
def mounts(directory, config, args):
if not args or args[0] != "run":
return []
name = None
for i, arg in enumerate(args):
if arg == "--name" and i + 1 < len(args):
name = args[i + 1]
elif arg.startswith("--name="):
name = arg.partition("=")[2]
if config.get("scope") == "shared":
# The common launcher supplies these values. Do not intercept arbitrary
# Podman containers or depend on a game's package/modified APK.
appid = os.environ.get("SteamAppId", "")
if not re.fullmatch(r"[0-9]+", appid) or name != f"lepton-steamlaunch-{appid}":
return []
app = Path(os.environ.get("STEAM_COMPAT_INSTALL_PATH", ""))
if app.name != "lepton-app" or not app.is_dir():
return []
root_arg = None
for i, arg in enumerate(args):
if arg == "--rootfs" and i + 1 < len(args):
root_arg = args[i + 1]
elif arg.startswith("--rootfs="):
root_arg = arg.partition("=")[2]
if not root_arg or not root_arg.endswith(":O"):
return []
root = Path(root_arg[:-2]).resolve()
else: # old per-game deployments remain compatible during migration
if name != f"lepton-steamlaunch-{config['appid']}":
return []
root = Path(config["lepton"]).resolve().parent / "images" / "rootfs"
expected_root = str(root) + ":O"
if not any(arg in (expected_root, "--rootfs=" + expected_root) for arg in args):
return []
device = Path("/dev/video-dec0")
runtime = root / "vendor/lib64/libstagefright_softomx.so"
upstream_plugin = (root / "vendor/lib64/libstagefrighthw.so").exists()
for i, arg in enumerate(args):
if arg == "--mount" and i + 1 < len(args):
fields = dict(item.split("=", 1) for item in args[i + 1].split(",") if "=" in item)
target = fields.get("destination", fields.get("target"))
if target == "/vendor/lib64/libstagefrighthw.so":
upstream_plugin = True
if target == "/vendor/lib64/libstagefright_softomx.so" and fields.get("source"):
runtime = Path(fields["source"])
if upstream_plugin:
print("FramePort video: using the runtime's hardware codec plugin", file=sys.stderr)
return []
if not device.exists() or hashlib.sha256(runtime.read_bytes()).hexdigest() != config["runtime_sha256"]:
print("FramePort video: device or runtime ABI differs; retaining the stock codecs", file=sys.stderr)
return []
xml = ET.parse(root / "vendor/etc/media_codecs.xml")
if not any(node.get("href") == "media_codecs_frameport.xml" for node in xml.getroot().findall("Include")):
ET.SubElement(xml.getroot(), "Include", href="media_codecs_frameport.xml")
# Immutable plugin versions can serve simultaneous app launches and
# different Lepton installations. Never share a temporary XML filename.
# The version directory stays as the agent verified it: the merged list goes to the user's runtime dir.
root_key = hashlib.sha256(str(root).encode()).hexdigest()[:16]
merged = merged_dir() / f"media_codecs.{root_key}.xml"
temporary = merged.with_suffix(f".{os.getpid()}.tmp")
xml.write(temporary, encoding="utf-8", xml_declaration=True)
temporary.replace(merged)
# Lepton supplies its own /dev tmpfs. A Podman --device node disappears
# beneath it; a bind mount matches Lepton's existing GPU/sound device setup.
result = ["--mount", f"type=bind,source={device},destination=/dev/video-dec0,rw"]
for path, target in (
(directory / "libstagefrighthw.so", "/vendor/lib64/libstagefrighthw.so"),
(merged, "/vendor/etc/media_codecs.xml"),
(directory / "media_codecs_frameport.xml", "/vendor/etc/media_codecs_frameport.xml"),
):
if not path.is_file():
raise FileNotFoundError(f"missing video codec mount: {path}")
result += ["--mount", f"type=bind,source={path},destination={target},ro"]
print("FramePort video: loading the Iris hardware codec plugin for this container", file=sys.stderr)
return result
def merged_dir():
runtime = os.environ.get("XDG_RUNTIME_DIR", "")
base = Path(runtime) if runtime and Path(runtime).is_dir() else Path.home() / ".cache"
path = base / "frameport-video"
path.mkdir(mode=0o700, parents=True, exist_ok=True)
return path
def switched_off(directory):
"""FRAMEPORT_NO_HW_VIDEO=1 (e.g. in a game's Steam launch options) or the Frame-wide switch (FramePort's
setting; the agent writes video-codec/disabled) leave every container with Android's stock codecs."""
if os.environ.get("FRAMEPORT_NO_HW_VIDEO", "") not in ("", "0"):
return True
return directory.parent.name == "versions" and (directory.parent.parent / "disabled").exists()
def real_podman(directory):
"""Find Podman independently of deployment.json, without recursing into this wrapper."""
own_bin = (directory / "bin").resolve()
own_script = Path(__file__).resolve()
# Lepton runs its `podman exec` calls (boot wait, app pid, logcat mirror) with the Android guest's PATH
# (/product/bin:/system/bin:...), which has no host Podman: the system folders come after PATH. Failing there
# broke Lepton's logcat mirror and app-pid checks, and the container was stopped early.
entries = os.environ.get("PATH", os.defpath).split(os.pathsep) + ["/usr/local/bin", "/usr/bin", "/bin"]
for entry in entries:
folder = Path(entry or os.curdir).resolve()
if folder == own_bin:
continue
found = shutil.which("podman", path=str(folder))
if found:
executable = Path(found).resolve()
if executable.parent != own_bin and executable != own_script:
return str(executable)
raise RuntimeError("no real Podman executable found outside the codec wrapper directory")
def main():
directory = Path(__file__).resolve().parent.parent
args = sys.argv[1:]
fallback = real_podman(directory)
if switched_off(directory):
os.execv(fallback, [fallback, *args])
try:
config = json.loads((directory / "deployment.json").read_text())
podman = Path(config.get("podman", fallback)).resolve()
if podman.parent == (directory / "bin").resolve() or podman == Path(__file__).resolve():
raise ValueError("configured Podman points to the codec wrapper")
extra = mounts(directory, config, args)
launch_args = [args[0], *extra, *args[1:]] if extra else args
os.execv(str(podman), [str(podman), *launch_args])
except Exception as exc: # a codec/configuration failure must never prevent the stock container from starting
print(f"FramePort video: retaining stock codecs: {exc}", file=sys.stderr)
os.execv(fallback, [fallback, *args])
if __name__ == "__main__":
main()
Binary file not shown.
+7 -8
View File
@@ -30,13 +30,13 @@ user_systemd() {
say "FramePort setup for $(hostname) ($(. /etc/os-release; echo "$NAME $VERSION_ID"))"
say "Authorizing the FramePort app's key"
say "Letting FramePort log in"
key=$(curl -fsS "$PC_URL/key?code=$PAIR_CODE")
[[ "$key" == ssh-ed25519\ * ]] || { echo "Could not fetch the app's key from $PC_URL (is the app still open?)"; exit 1; }
[[ "$key" == ssh-ed25519\ * ]] || { echo "Couldn't reach FramePort at $PC_URL. Is it still open?"; exit 1; }
mkdir -p ~/.ssh && chmod 700 ~/.ssh && touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys
grep -qxF "$key" ~/.ssh/authorized_keys || echo "$key" >> ~/.ssh/authorized_keys
say "Configuring podman for Lepton"
say "Setting up Lepton's containers"
# rootless podman leaks one kernel keyring per container start; ~200 game launches would exhaust the quota
mkdir -p ~/.config/containers
grep -qs '^ *keyring *=' ~/.config/containers/containers.conf || printf '[containers]\nkeyring = false\n' >> ~/.config/containers/containers.conf
@@ -117,7 +117,7 @@ JOB
if [[ -f /etc/steamos-devkit-enabled ]]; then
say "Developer Mode is on"
bash "$JOB" finish "$PC_URL" "$PAIR_CODE" "$STEAM_CONFIG" "$DEVKIT_HELPER" 2>&1 | tee "$LOG"
say "Done. Return to FramePort on your PC: this Frame should now appear as connected."
say "Done. FramePort on your PC shows this Frame as connected."
exit 0
fi
@@ -126,9 +126,8 @@ if [[ ! -x "$DEVKIT_HELPER" || ! -f "$STEAM_CONFIG" ]] || \
! user_systemd systemd-run --user --collect --quiet --unit="frameport-setup-$$" \
bash -c 'bash "$0" "$@" >"$HOME/.cache/frameport-setup.log" 2>&1' \
"$JOB" devmode "$PC_URL" "$PAIR_CODE" "$STEAM_CONFIG" "$DEVKIT_HELPER"; then
echo "Couldn't turn it on automatically. Turn it on in Settings > System > Developer, then run this command again."
echo "Couldn't turn it on. Turn it on in Settings → System → Enable Developer Mode, then run this again."
exit 1
fi
echo "Steam restarts to turn it on. That closes the desktop in a few seconds and the Frame returns to its"
echo "normal view; setup finishes on its own. If Steam asks to install Lepton, confirm it."
echo "Then return to FramePort on your PC: the Frame appears as connected within a minute."
echo "Steam restarts and the desktop closes. Confirm Lepton if asked."
echo "Then check FramePort on your PC."
+234
View File
@@ -0,0 +1,234 @@
#!/usr/bin/env bash
# FramePort setup from the project page: the same for every Frame and every PC, nothing to copy from the PC. Run in
# the Frame's desktop terminal (SteamVR dashboard -> Launch a program -> Desktop, then System -> Konsole):
# curl -fsSL https://frameport.app/s | bash
# with FramePort open on your PC at Steam Frame -> Connect. Options: --pc <address[:port]> (skip the search).
#
# What it does:
# 1. finds FramePort on your network (it announces itself over mDNS while that page is open; else the USB cable's
# address and a quick scan of this network)
# 2. asks it to set up this Frame: FramePort shows the same 4 digits as this terminal, you click Allow there
# 3. runs FramePort's setup script from your PC, the one the typed line `curl -fsS <pc>/<code> | bash` runs (it
# authorizes the app's key, configures podman, turns on Developer Mode, asks for Lepton)
# Nothing changes on the Frame before you allow it on the PC.
set -euo pipefail
say() { printf '\n\033[1;36m==> %s\033[0m\n' "$*"; }
PORTS="8765 8766 8767"
USB_PC=10.86.200.234 # the PC's fixed address on the Frame's USB cable network (Developer Mode only)
PC=""
while [[ $# -gt 0 ]]; do
case $1 in
--pc) PC=${2:-}; shift 2 ;;
--pc=*) PC=${1#--pc=}; shift ;;
-h|--help) sed -n '2,13p' "$0" 2>/dev/null || true; exit 0 ;;
*) shift ;;
esac
done
for tool in curl python3; do
command -v "$tool" >/dev/null || { echo "This needs $tool, which SteamOS normally has. Run the setup command FramePort shows instead."; exit 1; }
done
# FramePort PCs, one per line: name<TAB>address:port<TAB>words. Stdlib Python: avahi isn't always answering on the
# Frame, and its firewall only lets mDNS in on port 5353, so this listens there in the group, like avahi does.
find_pcs() {
python3 - "$PORTS" "$USB_PC" <<'PY'
import json, socket, struct, sys, time, urllib.request
from concurrent.futures import ThreadPoolExecutor
PORTS = [int(p) for p in sys.argv[1].split()]
USB_PC = sys.argv[2]
SERVICE = "_frameport-pair._tcp.local"
def qname(name):
return b"".join(bytes([len(p)]) + p.encode() for p in name.split(".")) + b"\0"
def read_name(d, off):
labels, jumped, end = [], False, off
while True:
n = d[off]
if n == 0:
off += 1
break
if n & 0xC0 == 0xC0:
ptr = struct.unpack("!H", d[off:off + 2])[0] & 0x3FFF
if not jumped:
end = off + 2
jumped, off = True, ptr
continue
labels.append(d[off + 1:off + 1 + n].decode(errors="replace"))
off += 1 + n
return ".".join(labels), (end if jumped else off)
def mdns(seconds=3.0):
found, srv, txt, addr = {}, {}, {}, {}
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if hasattr(socket, "SO_REUSEPORT"):
try:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
except OSError:
pass
s.bind(("", 5353))
s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton("224.0.0.251") + socket.inet_aton("0.0.0.0"))
query = struct.pack("!6H", 0, 0, 1, 0, 0, 0) + qname(SERVICE) + struct.pack("!HH", 12, 1)
s.sendto(query, ("224.0.0.251", 5353))
except OSError:
return []
end, again = time.time() + seconds, True
while time.time() < end:
if again and time.time() > end - seconds / 2:
s.sendto(query, ("224.0.0.251", 5353))
again = False
s.settimeout(max(0.05, min(0.5, end - time.time())))
try:
d, src = s.recvfrom(9000)
except socket.timeout:
continue
try:
_, _, qd, an, ns, ar = struct.unpack("!6H", d[:12])
off = 12
for _ in range(qd):
_, off = read_name(d, off)
off += 4
for _ in range(an + ns + ar):
name, off = read_name(d, off)
rtype, _, _, rdlen = struct.unpack("!HHIH", d[off:off + 10])
off += 10
if rtype == 12 and name.lower() == SERVICE:
found.setdefault(read_name(d, off)[0], src[0])
elif rtype == 33:
srv[name] = (read_name(d, off + 6)[0], struct.unpack("!H", d[off + 4:off + 6])[0])
elif rtype == 16:
items, p = {}, off
while p < off + rdlen:
n = d[p]
k, _, v = d[p + 1:p + 1 + n].decode(errors="replace").partition("=")
items[k] = v
p += 1 + n
txt[name] = items
elif rtype == 1 and rdlen == 4:
addr[name] = socket.inet_ntoa(d[off:off + 4])
off += rdlen
except (IndexError, struct.error, UnicodeError):
continue
out = []
for inst, src in found.items():
target, port = srv.get(inst, ("", PORTS[0]))
props = txt.get(inst, {})
out.append((props.get("pc") or inst.split(".")[0], f"{addr.get(target) or src}:{port}", props.get("words", "")))
return out
def ping(host_port):
try:
with urllib.request.urlopen(f"http://{host_port}/ping", timeout=1.5) as r:
info = json.load(r)
return (info.get("pc", host_port), host_port, info.get("words", ""))
except Exception:
return None
def scan():
"""This network's /24 (and the USB cable's PC address) for a FramePort setup port."""
hosts = [f"{USB_PC}:{p}" for p in PORTS]
try:
out = __import__("subprocess").run(["ip", "-4", "-o", "addr"], capture_output=True, text=True).stdout
except OSError:
out = ""
for line in out.splitlines():
parts = line.split()
if len(parts) > 3 and parts[1] not in ("lo",) and "/" in parts[3]:
mine = parts[3].split("/")[0]
base = mine.rsplit(".", 1)[0]
hosts += [f"{base}.{i}:{p}" for i in range(1, 255) if f"{base}.{i}" != mine for p in PORTS[:1]]
def open_port(hp):
h, p = hp.rsplit(":", 1)
try:
with socket.create_connection((h, int(p)), timeout=0.4):
return hp
except OSError:
return None
with ThreadPoolExecutor(64) as ex:
live = [hp for hp in ex.map(open_port, hosts) if hp]
return [r for r in map(ping, live) if r]
pcs = mdns()
if not pcs:
pcs = scan()
seen = set()
for name, hp, words in pcs:
key = (name, words) if words else hp # one PC on several links (home Wi-Fi and the Frame's hotspot): once
if key not in seen:
seen.add(key)
print(f"{name}\t{hp}\t{words}")
PY
}
say "FramePort setup for $(hostname)"
if [[ -n "$PC" ]]; then
[[ "$PC" == *:* ]] || PC="$PC:${PORTS%% *}"
WORDS=""
else
echo "Looking for FramePort on your network (in FramePort on your PC: Steam Frame → Start setup)..."
mapfile -t pcs < <(find_pcs)
if [[ ${#pcs[@]} -eq 0 ]]; then
echo
echo "FramePort wasn't found. Check that:"
echo " - FramePort is open on your PC, at Steam Frame → Start setup"
echo " - the Frame and the PC are on the same Wi-Fi (or connected with a USB cable)"
echo "Or run the setup command FramePort shows there (Use the setup command)."
exit 1
fi
pick=0
if [[ ${#pcs[@]} -gt 1 ]]; then
echo "More than one FramePort is open on this network:"
for i in "${!pcs[@]}"; do
IFS=$'\t' read -r name hp words <<<"${pcs[$i]}"
echo " $((i + 1))) $name ($hp) $words"
done
# stdin is this script itself (curl | bash): ask on the terminal; without one, take the first
if { exec 3</dev/tty; } 2>/dev/null; then
read -r -u 3 -p "Which one? [1-${#pcs[@]}] " n
exec 3<&-
[[ "$n" =~ ^[0-9]+$ && $n -ge 1 && $n -le ${#pcs[@]} ]] || { echo "No such number."; exit 1; }
pick=$((n - 1))
else
echo "No terminal to ask in: using the first. (Choose with --pc <address:port>.)"
fi
fi
IFS=$'\t' read -r name PC WORDS <<<"${pcs[$pick]}"
echo "Found FramePort on $name ($PC)${WORDS:+, PC words: $WORDS}"
fi
nonce=$(python3 -c 'import secrets; print(secrets.token_hex(12))')
digits=$(python3 -c 'import hashlib, sys; print(f"{int(hashlib.sha256(sys.argv[1].encode()).hexdigest()[:8], 16) % 10000:04d}")' "$nonce")
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
status=$(curl -sS -o "$tmp" -w '%{http_code}' -G --data-urlencode "host=$(hostname)" --data-urlencode "nonce=$nonce" \
"http://$PC/hello") || true
case $status in
200) reply=$(cat "$tmp") ;;
429) echo "FramePort at $PC already has several setup requests waiting. Allow or deny them there (requests from"
echo "setups that ended go away by themselves within a few minutes), then run this again."; exit 1 ;;
000|"") echo "FramePort at $PC didn't answer. Is it still open at Steam Frame → Start setup?"; exit 1 ;;
*) echo "FramePort at $PC refused the request (HTTP $status). Run this again, or run the setup command FramePort shows."
exit 1 ;;
esac
id=$(python3 -c 'import json, sys; print(json.loads(sys.argv[1])["id"])' "$reply")
WORDS=$(python3 -c 'import json, sys; print(json.loads(sys.argv[1]).get("words", ""))' "$reply")
say "FramePort on your PC asks to set up this Frame"
printf ' Click Allow there if it shows \033[1;33m%s\033[0m (PC: %s)\n' "$digits" "${WORDS:-?}"
code=""
for _ in $(seq 12); do # up to about 20 minutes
status=$(curl -sS -o "$tmp" -w '%{http_code}' -m 115 "http://$PC/wait?id=$id" || echo 000)
case $status in
200) code=$(python3 -c 'import json, sys; print(json.load(open(sys.argv[1]))["code"])' "$tmp"); break ;;
202) continue ;;
403) echo "Not allowed on the PC. Nothing was changed on this Frame."; exit 1 ;;
*) echo "Lost FramePort at $PC (HTTP $status). Run this again, or run the setup command FramePort shows."; exit 1 ;;
esac
done
[[ -n "$code" ]] || { echo "Not allowed in time. Run this again when you're at your PC."; exit 1; }
say "Allowed. Running the setup from $PC"
curl -fsS "http://$PC/$code" | bash
@@ -6,10 +6,14 @@ notes: 'Native OpenXR GLES video player. 360 theatres/videos (equirect2 layers,
at 72 fps (the runtime asks for 4536 px per eye). Confirmed in the headset by the owner (2026-10-01). Its "Internal
Storage" list is /sdcard/4XPlayer: send videos with the game page''s "Add videos".'
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.'
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).'
tested_version: 2.0.22
engine: Other
xr: OpenXR
frame:
- frame.hw_video_decode
adapter:
equirect_emul: 1
stable_local: 1
@@ -17,4 +21,6 @@ adapter:
scale: 0.75
verified:
date: '2026-10-01'
updated: '2026-10-10'
min_app: 0.12.1
source_hint: 4XVR Video Player
+6 -6
View File
@@ -1,9 +1,9 @@
package: com.Armature.VR4
title: VR4
status: works
notes: Installs the no-ForceQuit build and fixes a campaign shader that hung the GPU after the opening cutscene.
details: Entering the campaign gave a black screen, slowing audio and a crash (Mercenaries loaded). One fragment shader
reads a loop counter before setting it, which hangs the GPU; the Vulkan shim zeroes it (vk_shader_fix). The
notes: Installs the no-ForceQuit build and fixes two campaign shaders that hung the GPU at cutscenes.
details: Entering the campaign gave a black screen, slowing audio and a crash (Mercenaries loaded). Two permutations of one fragment shader
read a loop counter before setting it, which hangs the GPU; the Vulkan shim zeroes it (vk_shader_fix). The
no-ForceQuit build is kept. Diagnosed and confirmed on a Frame by the reporter of GitHub issue 10.
tested_version: '2.3'
engine: Unreal
@@ -14,8 +14,8 @@ use_alt: true
adapter:
# one campaign shader reads an uninitialized loop counter and hangs the GPU (black screen, slowing audio, crash);
# the Vulkan shim inserts two OpStores that zero it and its accumulator (from the reporter's capture, GitHub
# issue 10)
vk_shader_fix: 6488:5dcd842db8d21e3fdd13ec916d2e34dd0cfdf12a92fffaf8a27115072ef55c59:2624:0x0003003e,138,18,0x0003003e,150,42
# issue 10); a second permutation of the same shader (6604 bytes) hung the GPU at a later cutscene (GitHub #140)
vk_shader_fix: 6488:5dcd842db8d21e3fdd13ec916d2e34dd0cfdf12a92fffaf8a27115072ef55c59:2624:0x0003003e,138,18,0x0003003e,150,42;6604:a0e6edd8a8e969acbefe1a193f0bc535d8c0c18d004def4c361840a9f2601f13:2956:0x0003003e,122,18,0x0003003e,135,42
verified:
date: '2026-10-02'
known_good_sha256: 40ff74e37264b28ae8826923b69d0731d9620094c76462be34159643d6ae91b3
@@ -24,5 +24,5 @@ verified:
frame_build: '20260930.6234839'
agent: 26
issue: 1
updated: '2026-10-04'
updated: '2026-10-11'
source_hint: Resident Evil 4
@@ -0,0 +1,23 @@
package: com.CortopiaStudios.DTRH
title: Down the Rabbit Hole
status: works
tested_version: 01.04.434
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
- frame.unity_runtime_msaa_off
- frame.unity_gl_shim
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: ce9e02e9b8fc64dc37bec2bd869d2ed1c4a99f28adffd6b5ec38c87b3cb639f7
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 145
source_hint: '8430807483680247'
@@ -0,0 +1,19 @@
package: com.CortopiaStudios.Graves
title: GORN2
status: works
tested_version: 1.10.1
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-07'
known_good_sha256: d8a7949edc90c19902eb89f888ad6a350475387d630991767b3097667ad16d4c
app: 0.12.0
overport_cli: 1.2.5
issue: 88
source_hint: GORN 2
@@ -0,0 +1,24 @@
package: com.CyberneticWalrus.DoesitStackMeta
title: Does it Stack?
status: issues
notes: Mixed reality mode does not work (works on Demeo), everything else seems to be OK
tested_version: 1.02 (3683)
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
- frame.oculusos
device:
- device.text_input_window
adapter:
haptic_fix: 1
refresh_rate: 90.0
verified:
date: '2026-10-09'
known_good_sha256: e9fce6ac812f1bb317a7a6959be89c5ff54e87f56af9ad4c42dff5fecad4efe8
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261006.6173745'
agent: 59
issue: 112
source_hint: Does It Stack
@@ -0,0 +1,25 @@
package: com.FireproofStudios.TheRoomVR
title: The Room VR
status: works
notes: Plays normally (it used to stay on a grey screen).
details: Unity 2018.4 with built-in Oculus support. It started VR only with frame.unity_oculus_check, then Unity's
render thread crashed in the GL driver (Zink) when OVROverlay copied a 4x4 fade texture into its overlay layer;
frame.unity_no_overlay_copy skips that copy (the fade still works). frame.unity_runtime_msaa_off keeps the eye buffer
at one sample (also GitHub issue 38).
tested_version: '1.02'
engine: Unity
xr: VrApi
frame:
- frame.unity_no_msaa
- frame.unity_oculus_check
- frame.unity_text_input
- frame.unity_runtime_msaa_off
- frame.unity_no_overlay_copy
device:
- device.text_input_window
verified:
date: '2026-10-05'
issue: 38
updated: '2026-10-05'
min_app: 0.11.0
source_hint: The Room VR
+20
View File
@@ -0,0 +1,20 @@
package: com.ForwardXP.nuke
title: Please Don't Touch Anything
status: works
notes: Seems functional
tested_version: '2.2'
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
device:
- device.text_input_window
verified:
date: '2026-10-08'
known_good_sha256: 17460c8691b9ff82f6c7ca5597afd0aacf1a94aa30ebb6a6108802991b60f51b
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261006.6173745'
agent: 59
issue: 106
source_hint: apks
@@ -0,0 +1,21 @@
package: com.FourPlayersStudio.Retronika
title: Retronika
status: works
tested_version: 3.2.601
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-07'
known_good_sha256: 290bfda3969547d5f1ff621ab5a8be9dd322fc9eee6b243c863576d4ff5b4b81
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261006.6173745'
agent: 59
issue: 97
source_hint: Retronika
@@ -0,0 +1,21 @@
package: com.FunktronicLabs.TheLightBrigade
title: The Light Brigade
status: issues
notes: Judder in weapons
tested_version: '746'
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-09'
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 114
source_hint: The Light Brigade
@@ -0,0 +1,36 @@
package: com.ILMxLAB.VaderImmortal.ep1
title: 'Vader Immortal: Episode I'
status: works
notes: 'Plays (owner''s headset test): loading card, grip/trigger, hands and the lightspeed sequence fixed. Thumbs
follow the touches with frame.unreal_thumb_touch (Klownicle''s engine fix).'
details: Unreal Engine 4 (GLES). Found and verified in gameplay on a Frame by Klownicle (GitHub issue 49); FramePort
reimplements them as patches that match the game's code exactly and change nothing when it differs. The engine's
Quest checks (IsRunningOnSantaCruz) answer "not a Quest" on the Frame - the menu waited for a Quest-only shader
precompile that never starts (frame.unreal_quest_precompile makes the non-Quest branch report 100 %), and the
game's key map picked the empty Gear VR controls (frame.unreal_quest_keymap keeps the Quest set). Two lightspeed
shaders, as the Frame's GL driver (Zink) compiles them, read loop counters and accumulators before setting them
and hang the GPU; a Vulkan layer under Zink inserts the stores that zero them (frame.zink_shader_fix, zink_shader_fix;
the captured modules are from Lepton 2.8.14 and stop matching if a Frame update changes the driver's output -
the layer logs that). OVRPlugin asks for poses at its own clock (pose_time_fix). Unreal's Oculus input animates the
thumbs from near-touch, which the Frame never reports (frame.unreal_thumb_touch reads the touches instead; binding
the proximity to the touch inputs in FrameBridge didn't reach the game). The Frame's SteamVR runtime
leaks about 20 MB a second while the game runs (reported to Valve).
tested_version: 1.1.1+667256.cl.387770
engine: Unreal
xr: VrApi
alt_overport:
- patch_remove_unreal_force_quit
frame:
- frame.unreal_quest_precompile
- frame.unreal_quest_keymap
- frame.unreal_thumb_touch
- frame.zink_shader_fix
adapter:
pose_time_fix: 1
zink_shader_fix: 12016:b2919629761ad268e0b21c72ac8520f81b76e72c3adc26897f97eae9a3e70a66:3092:0x0003003e,166,82,0x0003003e,177,82,0x0003003e,183,82,0x0003003e,189,82,0x0003003e,256,82,0x0003003e,266,82,0x0003003e,272,82,0x0003003e,278,82,0x0003003e,318,82,0x0003003e,328,82,0x0003003e,334,82,0x0003003e,340,82;11436:6f18be49f2aaa2452f962eb16f7af744c5c22a7b25f37ce681e0d6399e35a869:2932:0x0003003e,164,80,0x0003003e,175,80,0x0003003e,181,80,0x0003003e,187,80,0x0003003e,254,80,0x0003003e,264,80,0x0003003e,270,80,0x0003003e,276,80,0x0003003e,316,80,0x0003003e,326,80,0x0003003e,332,80,0x0003003e,338,80
verified:
date: '2026-10-09'
issue: 49
updated: '2026-10-09'
min_app: 0.12.1
source_hint: Vader Immortal- Episode I
@@ -0,0 +1,24 @@
package: com.ILMxLAB.VaderImmortal.ep2
title: 'Vader Immortal: Episode II'
status: works
notes: Plays (owner's headset test, 2026-10-09) with Episode I's fixes, which FramePort finds in this episode's
code too.
details: 'Unreal Engine 4 (GLES), the same engine build as Episode I: the Quest-only shader precompile and key map
(frame.unreal_quest_precompile, frame.unreal_quest_keymap), thumbs from the capacitive touches (frame.unreal_thumb_touch)
and poses at OVRPlugin''s clock (pose_time_fix). Episode I''s lightspeed shader fix doesn''t apply. The campaign
intro once stayed black with its dialogue playing; it rendered on the next run.'
tested_version: 2.0.3+667261.cl.387778
engine: Unreal
xr: VrApi
alt_overport:
- patch_remove_unreal_force_quit
frame:
- frame.unreal_quest_precompile
- frame.unreal_quest_keymap
- frame.unreal_thumb_touch
adapter:
pose_time_fix: 1
verified:
date: '2026-10-09'
min_app: 0.12.1
source_hint: Vader Immortal- Episode II
@@ -0,0 +1,24 @@
package: com.ILMxLAB.VaderImmortal.ep3
title: 'Vader Immortal: Episode III'
status: works
notes: Plays (owner's headset test, 2026-10-09) with Episode I's fixes, which FramePort finds in this episode's
code too.
details: 'Unreal Engine 4 (GLES), the same engine build as Episode I: the Quest-only shader precompile and key map
(frame.unreal_quest_precompile, frame.unreal_quest_keymap), thumbs from the capacitive touches (frame.unreal_thumb_touch)
and poses at OVRPlugin''s clock (pose_time_fix). Episode I''s lightspeed shader fix doesn''t apply. The campaign
intro starts on a black screen with only dialogue: that is the game, not a bug.'
tested_version: 3.0.3+667263.cl.387932
engine: Unreal
xr: VrApi
alt_overport:
- patch_remove_unreal_force_quit
frame:
- frame.unreal_quest_precompile
- frame.unreal_quest_keymap
- frame.unreal_thumb_touch
adapter:
pose_time_fix: 1
verified:
date: '2026-10-09'
min_app: 0.12.1
source_hint: Vader Immortal- Episode III
@@ -4,14 +4,25 @@ tested_version: '1.03'
source_hint: Sniper Elite VR
engine: Unity
xr: VrApi
status: unsupported
notes: 'GPU hang (zink: DEVICE LOST) even with MSAA off.'
details: Legacy VrApi via OVRPlugin.
status: works
notes: Plays with controllers that follow the hands (owner's headset test, Quest 1 build 35713).
details: 'Unity 2019.4 on its built-in Oculus VR (no Oculus XR Plugin). Its legacy frame loop never waited for frames
(GPU hang, zink: DEVICE LOST) and Unity reported only Go controllers: frame.unity_oculus_check. Its frame wait then
deadlocked at the Init scene (Unity skipped beginning a frame) and the physics-step pose update located the
controllers in the past (hands lagged): both fixed in that patch (revision 5), switched on for this game by ovrp_begin_gate and ovrp_hold_physics. Grabbing an object in the tutorial
vibrates the controller; OVRPort''s envelope conversion then allocated ~1 GB/s until the memory cap ended the game:
haptic_fix. The Quest 1 build has no arraysources_assets.hd, which the game loads on the Frame (it detects a Quest
2) and then skips the .sd bundle: textures are missing unless the .sd file is also present under the .hd name in the
game''s obb folder.'
frame:
- frame.unity_no_msaa
- frame.unity_oculus_check
adapter:
haptic_fix: 1
ovrp_begin_gate: 1
ovrp_hold_physics: 1
pcvr_alternative: Sniper Elite VR (Steam PC VR). Winter Warrior (Quest) works on the Frame.
verified:
date: '2026-09-28'
overport_cli: 1.2.3
overport_runtime: 3.4.3-23204ea
known_good_sha256: e80883eb6945e267a375edf0ffd789268686440819add9d4dee9fdbdf466cbe4
date: '2026-10-07'
updated: '2026-10-07'
min_app: 0.12.1
@@ -0,0 +1,24 @@
package: com.MightyCoconut.WalkaboutMiniGolf
title: Walkabout Mini Golf
status: works
tested_version: '6.7'
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
- frame.oculusos
device:
- device.text_input_window
adapter:
controller_models: 1
haptic_fix: 1
scale: 1.5
verified:
date: '2026-10-08'
known_good_sha256: cf3b5cbe131af8a2baaa3f5c665b1c7001c8b299473abfcf98d90e3c02b08a2f
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 111
source_hint: Walkabout Mini Golf
+10 -6
View File
@@ -1,13 +1,14 @@
package: com.NewFolderGames.IAmCat
title: I Am Cat
status: issues
notes: The view vibrates slightly, even when holding still.
status: works
notes: Steady view with pose_consistency (the judder is gone).
details: Unity 2022.3 with the Oculus XR Plugin falls back to GLES (Zink) on the Frame, where its single-pass
(multiview) rendering drew the right eye wrong (GitHub issue 8); frame.unity_multipass renders each eye in its own
pass (OculusSettings.GetStereoRenderingMode + its inlined read in OculusLoader.Initialize). Poses and frame times
are consistent, but every frame reaches the runtime one display period late (two passes on Zink) and the game
clamps its physics step to 10 ms, so the view judders; scale 0.8 didn't change it. Possible next steps are fixing the
multiview path instead or the game's fixed timestep.
clamps its physics step to 10 ms. The judder came from the game locating its views about three times per frame and
getting slightly different poses each time; FrameBridge pose_consistency returns the same views for repeated
queries of one display time (proposed in GitHub issue 8). The grey right eye on the loading screen is still open.
tested_version: 1.4.0.0
engine: Unity
xr: OpenXR
@@ -16,8 +17,11 @@ frame:
- frame.unity_multipass
device:
- device.text_input_window
adapter:
pose_consistency: 1
verified:
date: '2026-10-03'
date: '2026-10-05'
issue: 8
updated: '2026-10-04'
updated: '2026-10-05'
min_app: 0.11.0
source_hint: I Am Cat
@@ -0,0 +1,22 @@
package: com.PeanutButton.Retropolis
title: Retropolis
status: works
tested_version: '0.87'
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
- frame.oculusos
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: 4656b980fccd812d8edac15805dcd86c2f44029abdab98c3187699d1fc878f88
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 153
source_hint: com.PeanutButton.Retropolis
@@ -0,0 +1,29 @@
package: com.PixelToys.BattleSisters
title: BattleSisters
status: works
notes: Plays; controller buttons and vibration work, hands follow the controllers (pose_time_fix).
details: Unity 2019.4 with Unity's built-in Oculus support. frame.unity_oculus_check starts VR (Meta's system-app
check), adds the frame wait its legacy loop never makes (without it the GPU hung, black screen) and lets Unity's
Oculus input accept Lepton's device model (it only reported controllers on a device named "Oculus ...", so buttons
were dead). haptic_fix stops the first controller vibration from freezing the Frame (OVRPort's loader read its
duration in nanoseconds as seconds and the game ran out of memory). With pose_time_fix the hands follow the controllers
- OVRPlugin asked for hand poses at Android's monotonic "now", which on SteamOS 0.4.5 is 2.56 s behind the runtime's
XrTime, so the hands lagged far behind.
tested_version: 1.2.4
engine: Unity
xr: VrApi
frame:
- frame.unity_no_msaa
- frame.unity_oculus_check
- frame.unity_text_input
- frame.unity_runtime_msaa_off
- frame.unity_gl_shim
adapter:
haptic_fix: 1
pose_time_fix: 1
verified:
date: '2026-10-08'
issue: 48
updated: '2026-10-08'
min_app: 0.12.1
source_hint: Battle Sister
@@ -0,0 +1,22 @@
package: com.ScytheDevTeam.ContainmentProtocol
title: Deep Cuts
status: works
tested_version: 1.1.3271
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
- frame.oculusos
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: d8d4bfb0954059c5ed1bcaa0edfb8b18f154ff710049f4808c42cfd8d88ee5a2
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 144
source_hint: com.ScytheDevTeam.ContainmentProtocol
+18 -8
View File
@@ -1,15 +1,25 @@
package: com.StressLevelZero.BONELAB
title: BONELAB
status: unsupported
notes: Crashes while starting its graphics (Vulkan); no fix yet.
details: SIGSEGV in vkCreateInstance, called from the game's own Vulkan hooks (libSLZQuestNative.so) into code that no
longer exists, right after OVRPlugin's pre-initialization creates and destroys an OpenXR instance. Keeping the
runtime (vrclient.so) and Steam's Fossilize layer loaded didn't help, so the stale pointer is to memory freed with
that first instance.
status: works
notes: Builds 1.2068 and 1.2974 play (owner's headset tests). One recipe serves both builds - 1.2068 needs frame.unity_user_presence (head tracking and controls); 1.2974 also needs
frame.slz_vulkan_hooks (72 fps after about 35 s of shader prewarming), which changes nothing on 1.2068.
details: Both builds - the game runs (72 fps), but its player body stayed frozen - no head tracking, the controllers stuck to the
model, no buttons - although OpenXR input and OVRPlugin's poses were fine. A few seconds after start (when Unity
switches XR loaders) OVRPlugin reports the worn headset as not worn (ovrp_GetUserPresent2 -> 0), and BONELAB's
Marrow rig only follows the player while Unity's HMD device reports UserPresence (OpenControllerRig checks
XRHMD.IsUserPresent every frame). frame.unity_user_presence reports the headset as worn. Build 1.2974 only - it adds Stress
Level Zero's graphics plugin (libSLZQuestNative.so), which hooks Unity's Vulkan start-up (vkCreateInstance/
vkCreateDevice) and vkCreateSampler; on the Frame its vkCreateInstance wrapper calls an invalid pointer right after
OVRPlugin's pre-init instance is destroyed (a jump to 0, into an unloaded library, or a hang). frame.slz_vulkan_hooks
switches its two registrations off, so Unity starts Vulkan itself; the plugin's pipeline cache isn't used.
tested_version: 1.2974.57485
engine: Unity
xr: OpenXR
frame:
- frame.unity_user_presence
- frame.slz_vulkan_hooks
verified:
date: '2026-10-04'
updated: '2026-10-04'
date: '2026-10-08'
updated: '2026-10-08'
source_hint: BONELAB
min_app: 0.12.1
+22
View File
@@ -0,0 +1,22 @@
package: com.ToastVR.Matilda
title: Max Mustard
status: works
tested_version: 1.1.0
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-08'
known_good_sha256: 9a67459fa36c3b9ac0d035e6c43b1809b39772699333e8cfd7d2a4d7f17926a7
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 110
updated: '2026-10-09'
source_hint: com.ToastVR.Matilda
@@ -0,0 +1,22 @@
package: com.ToastVR.RichiesPlankExperience
title: Richie's Plank Experience
status: works
tested_version: 2.3.585
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
- frame.oculusos
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-07'
known_good_sha256: 7eb1366c2398e554624a5d66aefb0c731c64b1b6944bc1726d9187f123c37fb7
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20260922.6101926'
agent: 59
issue: 95
source_hint: apks
+22
View File
@@ -0,0 +1,22 @@
package: com.UNIVRS.Freedom
title: Freedom
status: issues
notes: Volume can't be changed in the game.
tested_version: 1.0.2950
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: 24662272c4cbe5dc7c4f3c803bdb9dead9661ac3d59bf80cca2e260256acc4bc
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 132
source_hint: AOT VR
@@ -0,0 +1,23 @@
package: com.VR_HOT.VR_HOT_Quest
title: VR HOT Quest
status: works
tested_version: 0.8.8.1
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
- frame.oculusos
device:
- device.text_input_window
adapter:
controller_models: 1
haptic_fix: 1
verified:
date: '2026-10-08'
known_good_sha256: c321606c536eba1673572a9f44fd9e20d2cb5bd9609d536189e6ad7057b9cd67
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20260922.6101926'
agent: 59
issue: 98
source_hint: VR-HOT-Quest-0.8.8.1
@@ -0,0 +1,22 @@
package: com.XRGames.ZombielandVR
title: ZombielandVR
status: works
tested_version: 1.7.0
engine: Unity
xr: VrApi
frame:
- frame.unity_text_input
- frame.vrapi_stub
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: 750067107cd739c80b65516dd1c18219283b2a29b39430bb21ed85d48b9b94ac
app: 0.12.1.dev252
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 71
issue: 141
source_hint: ZombielandVR
+12 -5
View File
@@ -1,16 +1,23 @@
package: com.YourCompany.RoboRecall
title: Robo Recall
tested_version: '1.0'
source_hint: 'Robo Recall- Unplugged'
engine: Unreal
xr: VrApi
status: works
notes: Needed the LAUNCHER category fix.
details: Source APK is a community 'patch+savefix+90Hz' build.
details: Source APK is a community 'patch+savefix+90Hz' build. Shares Vader Immortal's Unreal 4 Oculus input (GitHub
49) - thumbs follow the touches (frame.unreal_thumb_touch) and controller poses are located at the right time
(pose_time_fix); added 2026-10-09 from Vader's headset results, not yet checked in this game.
tested_version: '1.0'
engine: Unreal
xr: VrApi
alt_overport:
- patch_remove_unreal_force_quit
frame:
- frame.unreal_thumb_touch
adapter:
pose_time_fix: 1
verified:
date: '2026-09-28'
overport_cli: 1.2.3
overport_runtime: 3.4.3-23204ea
known_good_sha256: 4ce6e3563da131e288645f4b62a634ac98043e55bc42b7c0ad5f461207d5f53c
updated: '2026-10-09'
source_hint: Robo Recall- Unplugged
+25
View File
@@ -0,0 +1,25 @@
package: com.YourCompany.SWP_VR
title: Star Wars Pinball VR
tested_version: '1.4'
source_hint: Star Wars Pinball VR
engine: Unreal
xr: VrApi
status: works
notes: Plays (owner's headset test); one short audio hiccup seen.
details: 'Unreal 4.25 (GLES) built against OVRPlugin 1.44. Four Frame problems in a row: Zink crashed on Unreal''s
multisampled render-to-texture (fault addr 0x10000 on the RHIThread): frame.unreal_gl_shim, which keeps Unreal''s
multiview and draws those passes single-sampled; OVRPort''s OVRPlugin lacks ovrp_GetPTWNear, so Unreal''s Oculus
module never started VR (then a null pointer in GetMultiViewSceneColor): frame.unreal_ovrp_entrypoints; the engine''s
OpenSSL assembly has unpaired return-address checks the Frame''s CPU enforces (SIGILL on the HttpManager thread):
frame.pac_hints. Needs its OBB (main.10765).'
frame:
- frame.unreal_gl_shim
- frame.unreal_ovrp_entrypoints
- frame.pac_hints
adapter:
haptic_fix: 1
verified:
date: '2026-10-08'
issue: 83
updated: '2026-10-08'
min_app: 0.12.1
+23
View File
@@ -0,0 +1,23 @@
package: com.asg.clockworkdev
title: Clockwork
status: works
tested_version: '1.29'
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
- frame.unity_runtime_msaa_off
- frame.unity_gl_shim
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-09'
known_good_sha256: 99a14eed320ba267153963c2d87d4e11e943b9920d48dfd5352c681de28ba95e
app: 0.12.1.dev203
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 67
issue: 116
source_hint: com.asg.clockworkdev
@@ -0,0 +1,22 @@
package: com.beatgames.beatsaber.lj369vr
title: Beat Saber
status: works
notes: Purchased DLC isn't detected (ownership comes from Meta's servers).
tested_version: 1.40.8_7379
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
- frame.unity_runtime_msaa_off
- frame.unity_gl_shim
device:
- device.text_input_window
verified:
date: '2026-10-06'
known_good_sha256: 6366a5411328b21de9e2da034af78290882b3c27b7a8f0c61688196e91320055
app: 0.11.0
overport_cli: 1.2.5
frame_build: '20261005.6135832'
agent: 56
issue: 68
source_hint: Beat Saber
@@ -1,6 +1,7 @@
package: com.beatgames.beatsaber
title: Beat Saber
status: works
notes: Purchased DLC isn't detected (ownership comes from Meta's servers).
tested_version: 1.40.8_7379
engine: Unity
xr: OpenXR
+15 -5
View File
@@ -1,16 +1,26 @@
package: com.camouflaj.manta
title: 'Batman: Arkham Shadow'
tested_version: 1.4.1-350961
source_hint: 'Batman- Arkham Shadow'
engine: Unity
xr: OpenXR
status: works
notes: Large data set (~27 GiB) incl. all language packs.
details: Uses Meta XR Audio (Wwise), handled by patch_meta_xr_audio. Includes all language packs (~28 GB).
details: 'Uses Meta XR Audio (Wwise), handled by patch_meta_xr_audio. Cutscenes are 8K HEVC panoramas: frame.hw_video_decode
decodes them on the Frame''s hardware, adapter.surface_native shows them in stereo (PRs #96, #128). Includes all
language packs (~28 GB). sync_guard (to test, GitHub #102) - a reporter''s game quit to Steam''s waiting screen
after the Frame runtime crashed in xrSyncActions (CVRInputLatest::UpdateActionState), the input race Myst had.'
tested_version: 1.4.1-350961
engine: Unity
xr: OpenXR
overport_extra:
- patch_disable_space_warp
frame:
- frame.hw_video_decode
adapter:
sync_guard: 1
surface_native: 1
verified:
date: '2026-09-28'
overport_cli: 1.2.3
overport_runtime: 3.4.3-23204ea
known_good_sha256: 796d1cab08f614d72c50d9702974c719c0908f5de91183cfe02d1fd5eb09551a
updated: '2026-10-10'
min_app: 0.12.1
source_hint: Batman- Arkham Shadow
@@ -0,0 +1,21 @@
package: com.characterBank.ruinsmagus
title: RUINSMAGUS
status: works
tested_version: 1.2.3
engine: Unity
xr: VrApi
frame:
- frame.unity_text_input
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-07'
known_good_sha256: 230e5625fbdfebb47c4d96aeb87f45270e4e6c79a33c08315c90626ab7465fe5
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261006.6173745'
agent: 59
issue: 89
source_hint: RUINSMAGUS
@@ -0,0 +1,22 @@
package: com.cloudheadgames.pistolwhip
title: Pistol Whip
status: works
notes: 120 HZ
tested_version: 1.6.0.2
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-08'
known_good_sha256: ebd97af54c59d59d473c98492c1bb26ce6059b7e59f27a08acfc5ada23d0b774
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261006.6173745'
agent: 59
issue: 100
source_hint: Pistol Whip
+25
View File
@@ -0,0 +1,25 @@
package: com.coatsink.alone
title: Jurassic World Aftermath Collection
status: works
notes: Plays (owner's headset test, 2026-10-06).
tested_version: v3.16
engine: Unity
xr: VrApi
frame:
- frame.unity_no_msaa
- frame.unity_oculus_check
- frame.unity_text_input
- frame.unity_runtime_msaa_off
- frame.unity_gl_shim
- frame.vrapi_stub
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-06'
known_good_sha256: 0566b39d96258fa15eb1b03f27a7ad4219eb5442b0355cd803ed1cf13313f1b3
app: 0.12.0
updated: '2026-10-06'
min_app: 0.12.0
source_hint: Jurassic World Aftermath Collection
@@ -1,17 +1,18 @@
package: com.crowscrowscrows.AccountingPlus
title: Accounting+
status: issues
notes: Starts in VR, but doesn't get past its "press any button" screen.
status: works
notes: Plays normally, including the "press any button" screen after choosing to sit or stand.
details: Unity 2017.4 built-in Oculus support. frame.unity_oculus_check gets VR started (Meta's system-app check and
the frame wait its legacy loop never makes, libfp_ovrp.so), ~72 fps. Controller input reaches the game's OVRInput
cleanly (both Touch controllers connected, individual button bits, input focus 1 while focused) but the screen
doesn't continue; Cpp2IL can't read this Unity version to see what it waits for.
the frame wait its legacy loop never makes, libfp_ovrp.so), ~72 fps. The motion warning after the stance selection
waits for a mouse click (Input.GetMouseButtonDown), which a Quest delivers for the controller press and Lepton never
does; the same library now counts a newly pressed trigger or A/B/X/Y as that click.
tested_version: 1.24.35
engine: Unity
xr: VrApi
frame:
- frame.unity_oculus_check
verified:
date: '2026-10-04'
updated: '2026-10-04'
date: '2026-10-06'
updated: '2026-10-06'
min_app: 0.11.0
source_hint: Accounting+
+22
View File
@@ -0,0 +1,22 @@
package: com.curif.AgeOfJoy
title: AgeOfJoy
status: works
tested_version: '1.0'
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
- frame.oculusos
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-08'
known_good_sha256: 9780e71e417b86290147c67c83755305801d8957909734c717a519034edefdf9
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261006.6173745'
agent: 59
issue: 103
source_hint: AgeOfJoy 05 rc30
+23
View File
@@ -0,0 +1,23 @@
package: com.drbeef.doom3quest
title: Doom3Quest
status: works
notes: HUD and PDA show with frame.gl_multiview_fbo (Xandrix1987's headset test, GitHub 77, no frame-rate drop with
the PDA open). Walking and running can switch when the frame rate drops, which seems to be the game's own behaviour.
details: Doom3Quest compiles every vertex shader for two views (OVR_multiview) and draws its HUD/PDA into single-view
framebuffers. Mesa (the Frame's GL) rejects that combination as the spec requires and drops the draw, so they
stayed black; frame.gl_multiview_fbo draws those passes with a single-view copy of the shader. Xandrix1987's framebuffer.cpp
fix went upstream as a PR to Team-Beef-Studios/Doom3Quest.
tested_version: 1.4.8
engine: Other
xr: OpenXR
frame:
- frame.gl_multiview_fbo
verified:
date: '2026-10-06'
known_good_sha256: 3c733f5cd08b6fa5d9e2775b1c1aee36b5943518b5d8451ba87e3c59a702afdf
app: 0.12.0
overport_cli: 1.2.3
issue: 77
updated: '2026-10-09'
min_app: 0.12.1
source_hint: doom3quest148
+17
View File
@@ -0,0 +1,17 @@
package: com.drbeef.lambda1vr
title: Lambda1VR
status: works
notes: 'Needs the game data: upload the release''s xash folder to the game''s storage (Files tab) and set --msaa
1 in xash/commandline.txt. Owner''s headset test, 2026-10-06.'
tested_version: 1.7.3
engine: Other
xr: OpenXR
frame:
- frame.tbxr_vendor
verified:
date: '2026-10-06'
known_good_sha256: 10bbccceb8d9a84ff3bcc9a21fd59db52784ff7742998bbe5b30d938dd7b97d2
app: 0.12.0
updated: '2026-10-06'
min_app: 0.12.0
source_hint: Lambda1VR
+25
View File
@@ -0,0 +1,25 @@
package: com.endspace.quest
title: End Space
status: issues
notes: Controls stop responding after the first mission loads.
tested_version: 1.0.6.1
engine: Unity
xr: VrApi
frame:
- frame.unity_oculus_check
- frame.unity_text_input
- frame.unity_runtime_msaa_off
- frame.unity_gl_shim
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: c5eb892311a130686f238e6f61cbdb619324f5c21f8c0641c0fcd49d77369eaf
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 148
source_hint: com.endspace.quest
@@ -0,0 +1,22 @@
package: com.forcefieldxr.explorevr
title: ExploreVR
status: issues
notes: The 3D world warps and shifts with head movement (wrong perspective).
tested_version: 1.2.1
engine: Unreal
xr: VrApi
alt_overport:
- patch_remove_unreal_force_quit
frame:
- frame.nodebug
adapter:
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: 04576c46a91f4bb851a344c75e022b17a5d2837d054e7323a184b81436cf8019
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 149
source_hint: com.forcefieldxr.explorevr
+11 -5
View File
@@ -1,18 +1,24 @@
package: com.forcefieldxr.timestall
title: Time Stall
tested_version: '1.0'
source_hint: Time Stall
engine: Unreal
xr: VrApi
status: issues
notes: Both eyes distort during movement (unresolved).
details: Legacy VrApi via OVRPlugin.
details: Legacy VrApi via OVRPlugin. Shares Vader Immortal's Unreal 4 Oculus input (GitHub 49) - thumbs follow the
touches (frame.unreal_thumb_touch) and controller poses are located at the right time (pose_time_fix); added 2026-10-09
from Vader's headset results, not yet checked in this game.
tested_version: '1.0'
engine: Unreal
xr: VrApi
alt_overport:
- patch_remove_unreal_force_quit
frame:
- frame.unreal_thumb_touch
- frame.nodebug
adapter:
pose_time_fix: 1
verified:
date: '2026-09-28'
overport_cli: 1.2.3
overport_runtime: 3.4.3-23204ea
known_good_sha256: 50c4d05b663b2f1851f5a7d650a89fd34587d65fafba13cc58671c96f7016db6
updated: '2026-10-09'
source_hint: Time Stall
@@ -0,0 +1,25 @@
package: com.fyian.TheThrillOfTheFight
title: The Thrill Of The Fight
status: issues
notes: Right eye doesn't draw the opponent or the referee.
tested_version: '251113.0'
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
- frame.unity_runtime_msaa_off
- frame.unity_gl_shim
- frame.unity_no_overlay_copy
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: af86dfbd6f7d3ab504d1f4f92f4dbc4c0f0cb36e30443b80fa0b91322f481394
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 164
source_hint: ThrillOfTheFight_Release
+9
View File
@@ -0,0 +1,9 @@
package: com.golfscope.proputt
title: GOLF+
status: unsupported
notes: Black screen. The game's servers reject the login on this platform (Authentication not supported).
engine: Unity
xr: OpenXR
verified:
date: '2026-10-10'
issue: 147
+24
View File
@@ -0,0 +1,24 @@
package: com.harmonixmusic.kata
title: Audica
status: works
tested_version: 1.0.3.4
engine: Unity
xr: VrApi
frame:
- frame.unity_no_msaa
- frame.unity_oculus_check
- frame.unity_text_input
- frame.unity_runtime_msaa_off
- frame.unity_gl_shim
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-09'
known_good_sha256: bc2c00fe049fd97643a296e622e3a90e08f0bd796ee454b0278525e9d72f8139
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 126
+27
View File
@@ -0,0 +1,27 @@
package: com.ilmxlab.tales
title: 'Star Wars: Tales from the Galaxy''s Edge'
status: unknown
notes: Not working yet - after the intro logos and a loading animation the picture goes black (GitHub issue 61).
Gets the controller fixes found for Vader Immortal (same studio) to test.
details: Unreal Engine 4 (GLES) by ILMxLAB, like Vader Immortal, but a different engine build - it has none of Vader's
Quest-only branches (no GetQuestShaderPrecompilePercent or RPOC key map; it precompiles shaders through Unreal's
own pipeline cache), so frame.unreal_quest_precompile/_keymap don't apply. Its seasons and Wwise banks are Meta
platform asset files (frame.asset_files). Vader's OVRPlugin findings apply to it as well - poses asked for at
OVRPlugin's own clock (pose_time_fix) and thumbs from near-touch, which the Frame never reports (frame.unreal_thumb_touch).
Ruled out for the black picture so far (see the diagnostics in issue 61) - asset-file paks, Valve's foveation,
GL errors; the game's own eye image reads back black.
tested_version: 1.1.9+734762.cl.439699
engine: Unreal
xr: VrApi
alt_overport:
- patch_remove_unreal_force_quit
frame:
- frame.asset_files
- frame.unreal_thumb_touch
adapter:
pose_time_fix: 1
verified:
issue: 61
updated: '2026-10-09'
min_app: 0.12.1
source_hint: Star Wars- Tales from the Galaxys Edge
+21
View File
@@ -0,0 +1,21 @@
package: com.kluge.SynthRiders
title: SynthRiders
status: works
tested_version: 3.6.7a1
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
- frame.oculusos
device:
- device.text_input_window
adapter:
controller_models: 1
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: fb982ad7617629df50190c98d1de906ac9b41903d782c0ada0087b32fc95a5f1
app: 0.12.0
overport_cli: 1.2.5
issue: 158
source_hint: com.kluge.SynthRiders
@@ -0,0 +1,24 @@
package: com.markschramm.gravitylab
title: Gravity Lab
status: works
notes: Works great, along with passthrough mode
tested_version: '1.221'
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
- frame.unity_runtime_msaa_off
- frame.unity_gl_shim
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-09'
known_good_sha256: 82c95ee7487a130b6e37b967dd97c8362bf4d7e873fcff823f2c7877a62ff99f
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 113
source_hint: Gravity Lab
@@ -0,0 +1,21 @@
package: com.miketeevee.shoresofloci
title: Shores of Loci
status: works
tested_version: '1.2'
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: c223a99cc1f1597a477a4677c1390cf744d517bd1e8fa7cf82e20b1baefaba26
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 154
source_hint: com.miketeevee.shoresofloci
+12 -5
View File
@@ -1,18 +1,25 @@
package: com.nDreams.PhantomQuest
title: 'Phantom: Covert Ops'
tested_version: '1.2'
source_hint: 'Phantom- Covert Ops'
engine: Unreal
xr: VrApi
status: issues
notes: DLC/store button crashes (no Meta store). Installs the no-ForceQuit build.
details: The regular build quits itself on the Frame (System.exit after a failed platform check), so the no-ForceQuit
build is installed.
build is installed. Shares Vader Immortal's Unreal 4 Oculus input (GitHub 49) - thumbs follow the touches (frame.unreal_thumb_touch)
and controller poses are located at the right time (pose_time_fix); added 2026-10-09 from Vader's headset results,
not yet checked in this game.
tested_version: '1.2'
engine: Unreal
xr: VrApi
alt_overport:
- patch_remove_unreal_force_quit
use_alt: true
frame:
- frame.unreal_thumb_touch
adapter:
pose_time_fix: 1
verified:
date: '2026-09-28'
overport_cli: 1.2.3
overport_runtime: 3.4.3-23204ea
known_good_sha256: bb02b20a23fbe5e7a580ecde15a3cd015a4183421a51aab4a4dbcddbe7a09d1d
updated: '2026-10-09'
source_hint: Phantom- Covert Ops
@@ -0,0 +1,21 @@
package: com.newfoldergames.fruitsalon
title: Fruit Salon
status: works
tested_version: 1.0.2
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: 1c2e6e44e046e1683670a6b0f97e7766fd5edd0c888443910ce3c8e2291a9865
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261006.6173745'
agent: 59
issue: 165
source_hint: Fruit Salon
+19
View File
@@ -0,0 +1,19 @@
package: com.odders.shapeit
title: BodyCombat
status: works
tested_version: 3.0.0
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
device:
- device.text_input_window
verified:
date: '2026-10-05'
known_good_sha256: 1aa5b2a46b25c86db65e3355a575af14b5d4d89b967c812274f181aab2e62b33
app: 0.9.1
overport_cli: 1.2.5
frame_build: '20260930.6234839'
agent: 48
issue: 56
source_hint: LES MILLS BODYCOMBAT
+23
View File
@@ -0,0 +1,23 @@
package: com.onehamsa.RNXQ
title: 'Racket: Nx'
status: works
tested_version: 2.8.31
engine: Unity
xr: VrApi
frame:
- frame.unity_text_input
- frame.unity_runtime_msaa_off
- frame.unity_gl_shim
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: f78de8051c5808e4244d9fa757050d6c131350112315ad6e01e2b1457f2e2053
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 151
source_hint: '9030540110323274'
@@ -0,0 +1,23 @@
package: com.owlchemylabs.vacationsimulator
title: Vacation Simulator
status: issues
notes: Controllers aren't tracked (reported; not yet diagnosed).
tested_version: 1.3.0.40142
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
device:
- device.text_input_window
adapter:
haptic_fix: 1
refresh_rate: 90.0
verified:
date: '2026-10-10'
known_good_sha256: b02e0a24292a8305ca752fef1884356f0191b53793d356b999cd50bba3fe7a3c
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 163
source_hint: com.owlchemylabs.vacationsimulator
+12 -8
View File
@@ -1,11 +1,12 @@
package: com.playful.LuckysTale
title: Lucky's Tale
status: issues
notes: Runs out of memory within a few minutes and freezes the Frame until the game is killed.
details: Unity's asset loading thread (Loading.PreloadManager) grows the game's native heap by about 1 GB/s
shortly after start (10+ GB plus all swap, then the kernel kills it; perf page-fault profile, 2026-10-04). The text
window and the MSAA patch aren't the cause (same with the window off); the OBB is byte-identical to the original.
The earlier "headset restarted in a menu" reports (GitHub issue 9) were most likely this. No fix yet.
status: works
notes: Plays normally; touching a save slot no longer freezes the Frame (haptic_fix).
details: Touching a save slot vibrates the controller through XR_FB_haptic_amplitude_envelope; OVRPort's OpenXR
loader converts that envelope with the duration in nanoseconds read as seconds and allocates an enormous buffer, so
the Frame ran out of memory and froze (found by the GitHub issue 9 reporter). FrameBridge's extension shim turns the
envelope into a plain vibration before OVRPort's loader sees it (haptic_fix). Brief pauses came from the headset's
wear sensor flickering, now hidden by focus_hold (on by default).
tested_version: 1.0.55
engine: Unity
xr: VrApi
@@ -14,8 +15,11 @@ frame:
- frame.unity_runtime_msaa_off
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-03'
date: '2026-10-05'
issue: 9
updated: '2026-10-04'
updated: '2026-10-05'
min_app: 0.11.0
source_hint: Luckys Tale
@@ -0,0 +1,22 @@
package: com.quaternionsoftware.rcpilottrainer
title: RC Pilot Trainer
status: works
tested_version: 0.7.0.2
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
- frame.oculusos
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: 1b98f0dea74f058a838ac17299a0e1c67362cb1ecfbc97e4cb2aad3bcbdfb173
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 152
source_hint: com.quaternionsoftware.rcpilottrainer
+20
View File
@@ -0,0 +1,20 @@
package: com.rrrgames.CaveCrave
title: Caves
status: works
notes: First cave loads up perfectly, didn't play to unlock other game modes
tested_version: 1.1.4
engine: Unity
xr: OpenXR
frame:
- frame.oculusos
adapter:
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: 9720a9af42590dfdb742f278352583e56f36efbf5d96c56c59ac4b6a615f276e
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 142
source_hint: com.rrrgames.CaveCrave
@@ -0,0 +1,18 @@
package: com.square_enix.android_meta.trianglexr
title: TRIANGLE STRATEGY
status: works
notes: Works in XR, with and without Arcturus Vision color (switched while playing).
tested_version: '1.0'
engine: Unreal
xr: OpenXR
alt_overport:
- patch_remove_unreal_force_quit
adapter:
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: 86e8297adeb4056eecfbc175438ef25387df67dd0a15ae79afbe6f2e67319a0c
app: 0.12.0
overport_cli: 1.2.5
issue: 171
source_hint: com.square_enix.android_meta.trianglexr
+17
View File
@@ -0,0 +1,17 @@
package: com.survios.Creed
title: Creed
status: works
tested_version: CRU-386950
engine: Unreal
xr: OpenXR
alt_overport:
- patch_remove_unreal_force_quit
verified:
date: '2026-10-05'
known_good_sha256: c319dd95198a0a96a883aa70f6be9c843da0a265967fa4a7343ce7e6f4bfdc87
app: 0.10.0
overport_cli: 1.2.5
frame_build: '20260930.6234839'
agent: 54
issue: 60
source_hint: Creed Rise to Glory
+22
View File
@@ -0,0 +1,22 @@
package: com.tvb.cubism
title: cubism
status: works
tested_version: 1.8.0
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-09'
known_good_sha256: e4e000392e53111a991c1c78653c9241b8007f81dcdb126a4f4b2003033f5a6e
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 121
source_hint: com.tvb.cubism
@@ -0,0 +1,26 @@
package: com.vertigogames.ImpactDevelopment
title: Metro Awakening
status: works
notes: Plays (owner's headset test) with the Vulkan shim's fixes, fragment density maps hidden, space warp off and without
Valve's VALVE_rpo layer (random jumping polygons in 3D without the last two, as in Into The Radius 2).
details: Unreal 5.2 (Vulkan) quit itself (ForceQuit, use_alt), then crashed three times in the Frame's Vulkan driver
(Turnip) on things Quest's driver ignores - a depth resolve in a subpass without a depth attachment, image barriers
and image views for a missing image. Unreal turns on fragment-density-map foveation because the driver offers the
extension and then expects the density map from the headset, which the Frame never provides; vk_hide_fdm hides the
extension from the game (Valve's own foveation layer is unaffected).
tested_version: 1.0-916944
engine: Unreal
xr: OpenXR
alt_overport:
- patch_remove_unreal_force_quit
use_alt: true
adapter:
vk_spec_fixes: 1
vk_hide_fdm: 1
hide_space_warp: 1
lepton_env:
VK_INSTANCE_LAYERS: VK_LAYER_VALVE_fdm_injection
verified:
date: '2026-10-06'
updated: '2026-10-06'
source_hint: Metro Awakening
@@ -0,0 +1,22 @@
package: com.zenstudios.PFXVRQuest
title: Pinball FX VR
status: issues
notes: Plays; mixed reality mode not working yet. Space warp off (fewer stutters).
tested_version: '1.9'
engine: Unreal
xr: OpenXR
alt_overport:
- patch_remove_unreal_force_quit
adapter:
equirect_emul: 1
hide_space_warp: 1
verified:
date: '2026-10-05'
known_good_sha256: ec5491f6a809c0ee9624e4a43aa0f2c307eb048763492d32a00ff4e0511efe54
app: 0.9.1
overport_cli: 1.2.5
frame_build: '20260930.6234839'
agent: 48
issue: 50
updated: '2026-10-06'
source_hint: Pinball FX VR
@@ -0,0 +1,22 @@
package: de.erthu.ancientdungeonfull
title: Ancient_Dungeon
status: issues
notes: Multiplayer isn't available (offline).
tested_version: ea0.1.10.2
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-10'
known_good_sha256: 0bcbd6d01d085183df586ee692267341a9ea15d1f52fbc33265601d55c73c2d2
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 59
issue: 143
source_hint: de.erthu.ancientdungeonfull
+21
View File
@@ -0,0 +1,21 @@
package: io.ARVORE.TheBoysVR
title: The Boys VR
status: works
notes: Plays with space warp off (owner's headset test, 2026-10-06; black screen reported before).
tested_version: 1.1.0.345
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
device:
- device.text_input_window
adapter:
haptic_fix: 1
hide_space_warp: 1
verified:
date: '2026-10-06'
known_good_sha256: 5df46b1771611c607229ee59298d983a4d78a57591d4864a02da2df5d2b6f97b
app: 0.12.0
updated: '2026-10-06'
min_app: 0.12.0
source_hint: The Boys
+21
View File
@@ -0,0 +1,21 @@
package: jp.co.amata.nvr
title: The Tale of Onogoro
status: works
tested_version: 1.1.0248
engine: Unity
xr: OpenXR
frame:
- frame.unity_text_input
device:
- device.text_input_window
adapter:
haptic_fix: 1
verified:
date: '2026-10-08'
known_good_sha256: d6258a22a0a6e4588bbec36170f126bffc44ed64784f99e37cf4b03ad212fb79
app: 0.12.0
overport_cli: 1.2.5
frame_build: '20261006.6173745'
agent: 59
issue: 108
source_hint: The Tale of Onogoro
+18
View File
@@ -0,0 +1,18 @@
package: org.timecrisis.quest
title: Time Crisis VR (Experimental)
status: works
notes: Fully playable at 120hz
tested_version: 0.8.3
engine: Other
xr: OpenXR
frame:
- frame.sdl_clipboard
verified:
date: '2026-10-06'
known_good_sha256: 4e67411a8e9c0b5fbb19ca7908c4222aa5ad1b2d47a8f4b5018b4fe9c2f5324e
app: 0.12.0
overport_cli: 1.2.3
frame_build: '20261005.6135832'
agent: 59
issue: 80
source_hint: TimeCrisisVR-v0.8.3-quest
@@ -0,0 +1,17 @@
package: org.timecrisis2.arcadevr
title: Time Crisis II VR (Test)
status: works
tested_version: 0.2.0
engine: Other
xr: OpenXR
frame:
- frame.sdl_clipboard
verified:
date: '2026-10-10'
known_good_sha256: 7829a4cb84298a03ebe539b893916a962aecc4119f189f9fa9ceeab24c747bc5
app: 0.12.0
overport_cli: 1.2.3
frame_build: '20261007.6125817'
agent: 59
issue: 173
source_hint: TimeCrisis2VR-v0.2.0-quest3
@@ -0,0 +1,11 @@
package: quest.eleven.forfunlabs
title: Eleven Table Tennis
status: unsupported
notes: Stops at the splash screen. The game needs Meta's online services after start, which aren't available on
the Frame.
engine: Unity
xr: OpenXR
pcvr_alternative: The PC VR (Rift) version works on the Frame through FramePort.
verified:
date: '2026-10-10'
issue: 160
+23
View File
@@ -0,0 +1,23 @@
package: rift.beat_saber
title: Beat Saber
status: works
notes: 'Supports SteamVR/OpenXR itself: runs directly, without Revive.'
engine: Unity
xr: OpenXR
kind: rift
quest_package: com.beatgames.beatsaber.lj369vr
pcvr:
- pcvr.xr_timefix
- pcvr.steamvr_tuning
pcvr_remove:
- pcvr.revive
as_is: true
verified:
date: '2026-10-09'
known_good_sha256: dc80bd1f8e46e7b7cdc4fbf51a84e68f2918d7481581bee9c21f65e269c6bf8b
app: 0.12.1.dev232
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 67
issue: 117
source_hint: Beat Saber
@@ -0,0 +1,23 @@
package: rift.eleven_table_tennis_vr
title: 'Eleven: Table Tennis VR'
status: works
notes: 'Supports SteamVR/OpenXR itself: runs directly, without Revive (arguments -vrmode OpenVR).'
engine: Unity
xr: LibOVR+OpenVR
kind: rift
pcvr:
- pcvr.launch_args
- pcvr.xr_timefix
- pcvr.steamvr_tuning
pcvr_remove:
- pcvr.revive
as_is: true
verified:
date: '2026-10-09'
known_good_sha256: 2cdfe3b096aef0bf76726156699381f7847ca4d17e7222359521982778470705
app: 0.12.1.dev232
overport_cli: 1.2.5
frame_build: '20261007.6125817'
agent: 67
issue: 118
source_hint: Eleven Table Tennis VR
Loaded 100 of 652 files, more files were not shown because too many files have changed in this diff. Show more