A probe in vrserver and the device/draw diagnostics established that the Steam Frame renders its home on the headset,
not on the PC (the compositor draws nothing, the vrlink streaming driver makes no environment shaders), so a PC-side
layer can't change its home. Kept as the record of that investigation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The compositor records its environment on a deferred D3D11 context, a different object with its own vtable than the
immediate context we hooked — so our draw hooks never saw it and the home never replaced Aurora (connected, but 0
draws). Hook every context's vtable: the immediate one, and each deferred one as CreateDeferredContext /
CreateDeferredContext1 makes it, keeping each vtable's own original draw/PSSetShader so calls dispatch correctly.
classify and the constant-buffer upload now work on whichever context records the draw.
Also fix the crash guard false-tripping: it armed when Frame Home connected and disarmed only after 30 drawn frames,
so when the home never drew (this very bug) every compositor restart counted as a crash and it wrote . Arm only
once we actually swap a draw, so never-drawing can't be mistaken for crashing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On a real headset the layer connected but replaced nothing, so the compositor draws its environment through a path
the layer wasn't catching. Track the bound pixel shader per device context (not only the immediate one we hooked, in
case the environment is recorded on a deferred context), name every pixel shader from its .fxo, and the first time
each is drawn log its name and whether it was on our context or another. The 5-second heartbeat now always prints
(home draws, distinct shaders seen here/other, connected). This makes one short headset session show exactly which
shaders and context the environment uses, to aim the fix. The diagnostic stops itself after a few seconds of draws.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Some environments store colour (sRGB) textures as HDR-encoded ASTC — the Focused vista's sky, water and glow. The
LDR decoder fills those with magenta error texels (the vista showed a huge magenta plane). We already re-decode
linear UNORM textures through the HDR profile for the same reason; do it for colour textures too: when the LDR decode
comes back mostly magenta, re-decode through the HDR profile and sRGB-encode the clamped linear colour so the sRGB
view samples it right (alpha stays linear). Verified by rendering the vista before/after. (A few of the vista's
special detail textures are neither plain LDR nor plain HDR colour and still look speckled; left for a follow-up.)
Also: a GUI-subsystem Windows build now sends the tool modes' log (--snapshot, --panorama, --bench) to the file,
since there's no console for them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The panel's fonts were loaded only from the Frame's Linux paths (/usr/share/fonts), so on a Windows PC none loaded
and almost no text drew. Try Windows' own Segoe UI and Arial after the Frame's Noto/DejaVu, so each weight has a real
font on either platform. Verified by rendering the panel headless (--render): all text is back.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
frame-home.exe and frame-switcher.exe were console-subsystem binaries, so a console window popped whenever SteamVR
or the panel launched them. Build them as Windows GUI apps (-mwindows; they already log to a file, never a terminal,
since stderrIsTerminal() is false on Windows), so nothing shows a console.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On the Steam Frame a layer inside SteamVR's compositor draws the home where it would draw Aurora, so the home is the
environment itself: at boot, during game loads, behind the Steam menu, with the controllers and passthrough, and it
steps aside for a game the instant it opens. On a PC this was faked with SteamVR's home-app slot and a flat 360 photo
in the gaps, which launched and handed off like an app and showed a picture, not the live home. This does it properly
on any PC that runs SteamVR, the Frame's way:
- Windows: the compositor is Direct3D 11, so the layer is a proxy d3d11.dll beside the compositor (layer/
frame_home_layer_d3d11.cpp, built from the .def that forwards every other export to the real d3d11.dll, kept as
fh_d3d11real.dll). It wraps the compositor's device, finds Aurora's draws by their shader bytecode and draws the
home over the sky, leaving the floor and stars off; everything else passes straight through. Pure pass-through in
any process that isn't vrcompositor.
- The home's frames cross from Frame Home (Vulkan) to the compositor (D3D11): the layer owns kSlots shared textures
and hands Frame Home their NT handles over a named pipe; Frame Home imports them (VK_KHR_external_memory_win32) and
renders into them. No cross-API lock — a slot is published only once the GPU has finished it and the four rotate,
as on Linux. The shared State stays Frame Home's.
- The Environments panel runs frame-home --system and keeps it alive (one at a time, a pid file across panel
restarts), the same systemEnv path as the Frame. The installer places/removes the proxy and marks the layer with a
root/layer folder, in place of the old home-app registration and background pictures.
- Safe like the Vulkan layer: no home, or one stale over half a second, and the compositor draws Aurora as usual;
the layer turns itself off after two unhealthy compositor starts, and root/layer/off turns it off by hand.
Linux keeps its Vulkan layer (unchanged). Verified without a headset on the null driver: the proxy loads and wraps
the compositor without crashing, recognises Aurora's shaders, and the frame hand-over completes (Frame Home connects,
imports the textures, renders). The compositor only draws the environment while presenting to a headset, so the final
on-headset image is the one step the null driver can't exercise.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Switching homes fades through black in a scene app too (SteamVR's compositor fade), and the home stays black
until its textures are in and 45 frames in a row are on time: Windows makes new GPU memory resident on first use,
so a just-loaded home stalled for a second or two (100 ms frames) after its textures were up.
- Auto resolution remembers where each home settled (autoscale.txt) and starts there; far over budget it steps
down further at once: a heavy home after a light one no longer starts at the light one's 150%.
- stats.json: fade, and hitchesShown (late frames while fully shown).
- Windows: the benchmark's pacing sleeps on the clock it measures with (it ran unpaced at 700+ fps).
- Measured on this PC (RTX 3060): all 48 homes walked by the benchmark at 2000x2000 per eye with 4x MSAA, paced
120 Hz with snap turns, teleports and head whips: 0.19% of 115k frames over budget, the hand tutorials worst.
On SteamVR's virtual headset: every home switched to by the panel, loaded, pictured, SteamVR's background made for
each; light/heavy switches with no late frames shown; a game taking the scene and handing it back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- vr_compat.h: GetProjectionMatrix / GetEyeToHeadTransform called with Microsoft's member ABI on MinGW builds
(SteamVR is built with MSVC; the hidden return pointer goes after this): the first frame crashed there.
- When SteamVR asks it to quit (a game, room setup), Frame Home leaves at once instead of waiting for a load (SteamVR
killed it after 5 s).
- The panel keeps SteamVR's home app running on a PC: SteamVR doesn't start it again after it stopped on its own.
- Logs on Windows: the environment and the panel always write logs/frame-home.log and logs/frame-switcher.log (SteamVR
gives them a hidden console); the panel logs to a file on the headset too now. A crash writes the exception and the
stack's return addresses there.
- Tested on SteamVR's null driver: Frame Home started by SteamVR as its home app, the panel with it, SteamVR's
background made from the home, a crash recovered in seconds, 0.79 ms GPU at 1976x1976 per eye with 4x MSAA.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- frame-home --install/--uninstall and frame-switcher --install/--uninstall check for SteamVR's server first: on
Windows, connecting as a utility started SteamVR. Not running: exit 3 and the installer writes SteamVR's config
files, as on the headset.
- frame/payload: Frame Home, the panel, fenv-tool and the compositor layer built on the Steam Frame from this
source (installed there; locomotion tests 0 failed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- src/home_platform.h: what Frame Home and the panel need from the system (paths, finding and stopping each
other, running a tool, restarting, priorities) for SteamOS, Windows and Linux PCs. On a PC the environments are
Frame Studio's My Environments; Frame Home's own files in %LOCALAPPDATA%\Frame Home (Windows).
- src/openvr_loader.cpp: a PC build finds SteamVR's own OpenVR library at run time (openvrpaths.vrpath), so it runs
on any PC with SteamVR; Vulkan through volk. CMake: FRAME_HOME_PC (default on Windows and x86-64 Linux); the Steam
Frame build is unchanged.
- Windows: Frame Home is SteamVR's home app; SteamVR's classic environment shows a 360-degree picture of the current
home from its spawn (frame-home --panorama --spawn, made by the panel when the home changes) instead of Aurora
while Frame Home starts. Linux PCs: the Steam Frame's compositor layer, as on the headset.
- frame/pc: install-frame-home-pc.ps1 (+ .bat): online through SteamVR's API or offline in its config files; the
SteamVR settings it changes are saved and restored exactly on -Uninstall (tested in a sandbox both ways).
frame-switcher --uninstall. Studio: Properties > Headset > This PC installs it from pc/ beside Studio.
- Packaging and CI: pc/ in the Windows x64 and Linux x64 Studio downloads; FrameHome-PC-windows-x64.zip.
- Thumbnails come from where Frame Home starts you (the first spawn, eyes 1.6 m up): Studio's saves and the
panel's (frame-home --snapshot --spawn).
- docs/pc-steamvr.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ImGui's docking branch (1.91.5): the 3D Viewport, Outliner, Properties and Timeline are areas on a gutter, each
with a header. Drag an area by its header's editor button to dock it elsewhere (as a tab, or splitting an area);
click it to show another editor there. Layouts are kept between runs; Window > Reset Layout restores them.
- Workspaces in the top bar: Layout, Walking, Headset. Ctrl+Space maximizes the 3D Viewport.
- 3D Viewport header: Arrange / Draw Floor, View, Select, Add, Object menus, Global / Local, the world layer and
shading (Material, Solid, Wireframe; GPU Cost, Overdraw, Leave Out in its menu). In the view: the toolbar (T), a
chip naming the home and selection, navigation buttons under the axes, a sidebar (N: Item, Tool, View).
- Properties: tabs as icons down its left edge (Object, World, Environment, Bundle, Headset); sections are panels on
lighter boxes; labels right-aligned in a column. Sound is part of the Environment tab.
- Timeline: transport in its header, a ruler in seconds with the playhead. Status bar: what the mouse does.
- Keys: Shift+A add menu, X asks before deleting, F3 menu search, Alt+A, Space, Numpad 1/3/7 views, T, N.
- Inter (SIL OFL) built in as the interface font; a palette of cool greys, one blue, Frame Studio's orange.
- ImGui patches (marked Frame Studio): the submenu triangle, and dropdown arrows drawn small on the field.
- Self-test: an area dragged to dock elsewhere, Reset Layout, workspaces, F3 (104/104).
- Guides follow the new menus.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- World > Add > Invisible Wall: a 2 x 2.5 m box (Object role) standing on the floor across the view. Frame Home
never draws it; it stops you and is never stood on.
- Meta's invisible walls (a solid collider with nothing drawn, 2 m or taller: Cascadia's, Space Station's, Desert
Terrace's, Haven's) load as invisible walls instead of surfaces, so the play space they bound holds.
- Walk grid: a drawn floor's edge exemption no longer applies within a body's radius of a wall, so a wall through a
drawn floor takes that floor away. tools/walktest/wallcheck: walls at every angle never have walkable area inside.
- New items land on the home's own surfaces when the spot is beyond the walkable area.
- The Planet (World > Add > Planet, Planet Pull) is gone; older files' planet zones still load.
- ImGui menus: a safe triangle from where the pointer was on the item to the whole submenu; crossing other items
on the way neither opens them nor closes it (resting 0.3 s still does).
- Self-test: invisible wall placed, walked into (with and without the area), through a drawn floor, undone (98/98).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Bundle tab: name, environments with their pictures (read in place), add the open one / My Environments / any
.fenv, reorder, remove, double-click to open (unpacked first), Save / Save As, Install on Steam Frame (onto the
Headset tab's list), Unpack into My Environments. Saving and unpacking run in the background.
- .fbundle opens from File > Open, the Bundle menu or a drop; the installer takes bundles and shows the first .fenv.
- HslRenderer::createPicture / releasePicture for UI thumbnails.
- Inspector tabs: ImGui draws a tab's label with the padding at BeginTabBar, so the snug padding is pushed there too.
- Self-test: bundle make, save, validate, reopen and unpack (93/93).
- docs/bundles.md written; Studio guide and README follow.
- Linux: no GLFW_INCLUDE_NONE redefinition, freopen's result checked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/fenv_bundle.h: a ZIP of bundle.json and the .fenv files stored as-is, copied
in pieces (zip64 from the start when 4 GB or more). Write, read the manifest,
read one environment's fenv.json + thumbnail in place, unpack all or one
(.part then rename, checksummed; never replaces a different file: "name 2"),
validate.
fenv-tool bundle / unbundle, and info lists a bundle's environments. The
switcher unpacks bundles dropped into envs/ into their environments and
deletes them (frame-switcher --unpack-bundles does it at once). The installers
copy .fbundle files alongside .fenv; only a .fenv sets the one shown.
tools/bundletest checks it against real environments (--big: past 4 GB).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- LICENSE: GNU Affero General Public License v3 (as the author's MetaDB),
shipped in every package; third_party/qhe/LICENSE.md keeps Quest Home
Editor's MIT terms for the code that started there
- Install on Steam Frame lives in the Inspector's Headset tab (no floating
window); FS_INSPECTOR_TAB=<tab> starts Studio on a tab
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Getting started, headset setup, installing and using Frame Home, the .fenv
format and fenv-tool, performance, building, troubleshooting, and an index.
Stubs for the Studio guide and bundles.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the window is laid out as an editor dock: a tool strip (drawn icons) on the
left edge, the Explorer, the 3D view, and a tabbed Inspector on the right
(Selected, World, Environment, Sound, Headset); the timeline and the status
bar run along the bottom
- the old one-piece properties panel is split into one function per tab;
selecting something shows the Selected tab
- the Frame menu is now Headset (and opens the Headset panel); the tuning
tooltip describes the paced walk
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- a newer push to the same branch cancels the run it replaces
- the Latest build pre-release gets every build that worked; a v* tag needs all
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ImGuizmo is compiled with -fno-strict-aliasing on GCC/Clang: its type
punning let Clang for x64 turn gizmo drags into NaN transforms
- 32-bit: the .fenv packer keeps LZMA within a 32-bit address space (saves
failed with out-of-memory); failed saves are logged
- std::to_string of file times and the 32-bit descriptor handle cast compile
on every standard library
- Linux fonts: Noto Sans, then Liberation Sans, then DejaVu; the Play/Pause
button sizes to its label; macOS fonts are rasterized at Retina density
- self-test stages allow at least 3000 frames (software GPUs draw ~30 a second)
- headset payload rebuilt from this source
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Frame Studio now builds for Windows (x64, x86, ARM64), Linux (x64, x86, ARM64)
and macOS (Apple Silicon, Intel):
- studio/platform.h with Win32 and POSIX implementations (dialogs through
tinyfiledialogs on Linux/macOS), a GLFW window beside the Win32 one, and a
platform-neutral frame pacer
- MoltenVK support in the renderer (portability enumeration) and bundled in
the macOS app
- 32-bit builds: large-address-aware, LZMA packing within a 32-bit address space
- unaligned file reads in the loaders go through loadUnaligned (no UB)
- scripts/build-studio.sh, studio/build_windows.bat [x64|x86|arm64],
cmake/llvm-mingw.cmake, and a GitHub Actions workflow building every target
Frame Home installs without Studio: frame/install-frame-home.{sh,ps1} and
"Install Frame Home.bat" (packaged as FrameHome-installer.zip).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>