Fold the Steam Frame guide into the README, with a quick start

docs/steam-frame.md is gone. The README now opens with a quick start that
goes from the disc to playing (emulation setup, disc extraction, the
container build, installing with Frame Control, updating), and carries what
the guide held that a player or contributor needs: recommended settings, the
Frame controller map, known issues, troubleshooting (log lines, the pacing
line, crash backtraces), building on the Frame or with Docker (including
Unraid's qemu registration), how the native build works and why the Android
flavour cannot show a picture. Links to the old guide point at the README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HBRGKTE1GnN2ah8gcZKr3
This commit is contained in:
Claude committed 2026-10-05 06:22:35 +00:00
1 parent 26c8305c80
commit 0a3bcc99bd
7 files changed
+263 -644

No files matched your search

+253 -86
View File
@@ -29,122 +29,289 @@ C++ and compiled for the Frame's ARM64 CPU, and it renders through SteamVR's Ope
---
## What this fork adds
## Quick start
Everything below is in [`docs/steam-frame.md`](docs/steam-frame.md), with the reasoning and the
readings it is based on.
This is the whole way from your disc to playing in the headset, building on an x86_64 Linux PC. The
first build takes a few hours, most of it compiling Dawn under emulation; later builds reuse it.
[Other ways to build](#other-ways-to-build) covers a faster machine and the Frame itself.
- **A native SteamOS build.** The game links SteamVR's OpenXR runtime directly and lets it create
the GPU device it renders with, so each eye is copied straight into the headset's swapchain with
no second device and no sharing between them. It runs fullscreen in the headset only; there is
no desktop window to draw.
- **Built for the Frame's Snapdragon 8 Gen 3**: compiled with `-mcpu=cortex-x4`.
- **The Frame's controllers.** Its own interaction profile is bound, so beside what the Quest Touch
layout already does, the left D-pad is the Wii Remote's D-pad (tricks, menus), the left View
button pauses and the left shoulder opens the settings panel.
- **120 Hz.** The headset is asked for 120 Hz, exactly two display refreshes per game frame at the
game's 60 FPS, so motion is even. `[vr] refresh_rate` changes it.
- **Every refresh from the game.** SteamVR halved the app's rate and filled every other refresh
itself. The game now submits its last frame again, at the pose it was rendered for, on each
refresh it has no new frame for (`[vr] repeat_frames`), so SteamVR runs it at the full 120 Hz.
- **Eye-tracked foveation.** Variable-resolution rendering whose sharp centre follows your gaze
through the Frame's eye tracking, instead of staying fixed straight ahead. `[vr] foveation` sets
the strength and `[vr] eye_tracked_foveation` turns the tracking off.
- **Standalone defaults**: the game's own object culling and medium foveation, as on the Quest.
The render scale defaults to 0.8; on the Frame 1.0 is SteamVR's recommended 1728x1728 and **1.25
is the panels' native 2160x2160**, which the Frame renders with time to spare.
- **Crash diagnostics on Linux.** A native crash logs the faulting thread, its pc and a backtrace,
which is how a crash on race restart was traced to the density maps sharing memory Dawn unmaps.
- **Build tooling.** `Launcher/build-dawn-linux.sh` builds the patched Dawn (Aurora's WebGPU
layer) the VR backend needs, and `Launcher/local-build.sh` gained `--openxr`, `--dawn-package`,
`--headset steam_frame` and `--cpu`.
**You need:**
- a Steam Frame with Developer Mode on (Steam Settings → System → Enable Developer Mode, then set a
user password), on the same network as your PC;
- your own clean PAL `RMCP01` disc image of Mario Kart Wii (ISO, WBFS, RVZ, ...);
- an x86_64 Linux PC with about 20 GB free and 16 GB of memory or more.
The Steam Frame also runs Android apps through its Lepton layer, and the Quest app gained a
`steamFrame` flavour for it, but it cannot show a picture there: Lepton's graphics driver lacks
the memory-sharing extensions the Android backend needs. The native build is the way to play.
## Requirements
- A Steam Frame with Developer Mode on and SSH set up (Steam Settings → System → Enable Developer
Mode, then set a user password). [Frame Control](https://github.com/saphid/frame-control) makes the
rest easier: it sets up an SSH key and installs the game into your Steam library.
- A clean, unmodified **PAL `RMCP01`** disc image of Mario Kart Wii, dumped by you. ISO, GCM,
GCZ, CISO, WBFS, WIA and RVZ can all be extracted. Other regions and patched executables are
rejected.
- Somewhere to build, all of them running an ARM64 Debian container:
- **an x86_64 Linux PC** with podman and qemu (emulated, so slow: the first build takes hours);
- **a stronger Linux machine or server** with Docker, the same way and faster;
- **the Frame itself**, in a podman container there.
- About 20 GB of free disk space on the build machine for the toolchain, Dawn and the game.
The PC commands below work in bash, zsh and fish.
> [!NOTE]
> Nobody here will tell you where to get the game. Dumping your own disc is on you, and links to
> game files won't be provided or tolerated. For the same reason there is no ready-built game to
> download: releases hold the source, and the game is always built from your own disc.
## Building and installing
### 1. Set up ARM64 emulation on the PC
The commands are in [`docs/steam-frame.md`](docs/steam-frame.md): [on a Linux
PC](docs/steam-frame.md#building-it-on-a-linux-pc), [with Docker on another
machine](docs/steam-frame.md#building-it-with-docker-on-another-machine) or [on the
Frame](docs/steam-frame.md#building-it-on-the-frame). In short:
```bash
sudo pacman -S --needed podman qemu-user-static qemu-user-static-binfmt # Arch, CachyOS
sudo systemctl restart systemd-binfmt
podman run --rm --platform linux/arm64 docker.io/library/debian:trixie uname -m
```
1. Extract your disc with [nodtool](https://github.com/encounter/nod) and copy `sys/main.dol` and
`files/rel/StaticR.rel` into `Assets/`. Keep the extracted disc: the game reads it at run time.
2. Start a Debian trixie ARM64 container and install the build packages, the bundled clang 22,
CMake and Ninja (`Launcher/prepare-portable-tools.sh --arch aarch64`), and .NET 8.
3. Build the patched Dawn once: `Launcher/build-dawn-linux.sh`. Under emulation give it `--jobs 4`
on a 16 GB machine; more parallel jobs can run it out of memory.
4. Build the game: `Launcher/local-build.sh ... --openxr --dawn-package <dawn>/package --headset
steam_frame`, with `--parallel` sized to the machine's memory (8 for 32 GB).
5. Copy the extracted disc to the Frame, and either send the `out` folder to the Frame with
[Frame Control](docs/steam-frame.md#installing-it-with-frame-control), which adds it to your
Steam library, or copy it over with `scp`.
6. Write `~/.local/share/WiiCompiled/Config.toml` on the Frame with your disc's path, then start
the game from the library in the headset:
The last command must print `aarch64`. On Debian or Ubuntu install `podman qemu-user-static
binfmt-support` instead. If podman complains about subordinate ids, run
`sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER` and log in again.
```toml
[paths]
dvd_root = "/home/steamos/wiicompiled/disc"
```
### 2. Get the code and extract your disc
### Recommended settings
```bash
mkdir -p ~/wiicompiled; cd ~/wiicompiled
git clone https://github.com/mitch030504/Wiicompiled_VR_Frame.git
curl -fL -o nodtool https://github.com/encounter/nod/releases/download/v2.0.0-alpha.10/nodtool-linux-x86_64
chmod +x nodtool
./nodtool extract "/path/to/Mario Kart Wii.wbfs" disc-extract
mkdir -p Wiicompiled_VR_Frame/Assets
cp disc-extract/sys/main.dol disc-extract/files/rel/StaticR.rel Wiicompiled_VR_Frame/Assets/
```
All of them are in the headset's settings panel (left shoulder button, **VR** tab) as well as in
`Config.toml`:
`disc-extract` must hold `sys/` and `files/` directly; keep it, the game reads it when it runs.
### 3. Build, inside an ARM64 container
```bash
podman run -it --name wiicompiled-frame --platform linux/arm64 -v ~/wiicompiled:/work docker.io/library/debian:trixie bash
```
Then, in the container's bash prompt:
```bash
apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl git python3 xz-utils unzip file pkg-config g++ binutils libicu-dev zlib1g-dev \
libvulkan-dev libx11-dev libx11-xcb-dev libxcb1-dev libxext-dev libxrandr-dev libxinerama-dev \
libxcursor-dev libxi-dev libxss-dev libxtst-dev libxkbcommon-dev libwayland-dev wayland-protocols \
libdecor-0-dev libegl-dev libgl-dev libgles-dev libdrm-dev libgbm-dev libasound2-dev libpulse-dev \
libpipewire-0.3-dev libudev-dev libdbus-1-dev libusb-1.0-0-dev
cd /work/Wiicompiled_VR_Frame
Launcher/prepare-portable-tools.sh --arch aarch64 --destination /work/tools # clang 22, CMake, Ninja
T=/work/tools/toolchain-aarch64/bin
curl -fsSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 8.0 --install-dir /work/dotnet
Launcher/build-dawn-linux.sh --work-dir /work/dawn --cc $T/clang --cxx $T/clang++ \
--cmake $T/cmake --ninja $T/ninja --jobs 4 2>&1 | tee -a /work/dawn.log
Launcher/local-build.sh --output-dir /work/out --cc $T/clang --cxx $T/clang++ --fuse-ld lld \
--cmake $T/cmake --ninja $T/ninja --dotnet /work/dotnet/dotnet --parallel 4 \
--openxr --dawn-package /work/dawn/package --headset steam_frame 2>&1 | tee -a /work/game.log
exit
```
- Dawn is done when it prints `Patched Dawn for Linux ready`, the game when it prints
`MKWCBUILD:OUTPUT=/work/out`.
- `--jobs 4` and `--parallel 4` suit 16 GB of memory. Compiles take more memory under emulation, and
16 at once froze a 16 GB laptop. Use 8 with 32 GB.
- If it stops, `podman start -ai wiicompiled-frame` gets you back in. Run `cd /work/Wiicompiled_VR_Frame;
T=/work/tools/toolchain-aarch64/bin`, then the step that stopped: both scripts resume.
### 4. Install on the Frame
The easiest way is [Frame Control](https://github.com/saphid/frame-control): set up its connection
to the Frame, then drag `~/wiicompiled/out` onto **Send to Frame**. Name it `WiiCompiled` and keep
**Launches** on `WiiCompiled`. It adds the game to your Steam library, in `~/devkit-game/WiiCompiled/`
on the Frame.
Then copy the disc over and, on a first install, tell the game where it is (Frame Control's SSH
key answers to `frame`; use `steamos@<frame-ip>` otherwise):
```bash
ssh frame mkdir -p wiicompiled
scp -r ~/wiicompiled/disc-extract frame:wiicompiled/disc
ssh frame 'mkdir -p ~/.local/share/WiiCompiled && printf "[paths]\ndvd_root = \"/home/steamos/wiicompiled/disc\"\n" > ~/.local/share/WiiCompiled/Config.toml'
```
Without Frame Control, `scp -r ~/wiicompiled/out frame:wiicompiled/` and start
`~/wiicompiled/out/WiiCompiled` from a terminal in the Frame's Desktop Mode, with SteamVR running.
### 5. Play
Start **WiiCompiled** from the library in the headset. Open the settings panel with the left
shoulder button, go to the **VR** tab and set the [recommended settings](#recommended-settings).
### Updating
Pull the new code, rebuild, and replace only the executable. Copying to a new name and moving it
into place works even while an old copy is open:
```bash
cd ~/wiicompiled/Wiicompiled_VR_Frame && git pull
podman start -ai wiicompiled-frame
# in the container: cd /work/Wiicompiled_VR_Frame; T=/work/tools/toolchain-aarch64/bin, then the
# build-dawn-linux.sh and local-build.sh lines from step 3 (only what changed is rebuilt), then exit
scp ~/wiicompiled/out/WiiCompiled frame:devkit-game/WiiCompiled/WiiCompiled.new
ssh frame 'cd ~/devkit-game/WiiCompiled && chmod 755 WiiCompiled.new && mv -f WiiCompiled.new WiiCompiled'
```
## Recommended settings
All of these are in the headset's settings panel (left shoulder button, **VR** tab) and in
`~/.local/share/WiiCompiled/Config.toml`. Quit the game before editing the file; it writes its
settings back when it closes.
| Setting | Value | Why |
| --- | --- | --- |
| `[vr] render_scale` | `1.25` | The panels' native 2160x2160 per eye. |
| `[vr] foveation` | `medium` | `off` costs the most GPU time. |
| `[vr] repeat_frames` | `true` (default) | Keeps SteamVR at 120 Hz. |
| `[vr] frame_interpolation_fps` | `0` | Interpolation made things worse on the Frame. |
| `[video] resolution_multiplier` | `2` | The game's own frame; 4x is too heavy for the Frame's GPU. |
| `[vr] render_scale` | `1.25` | Scales SteamVR's recommended eye size, 1728x1728 on the Frame: 1.25 is the panels' native 2160x2160, which the Frame renders in 9 to 11 ms a frame with medium foveation. |
| `[vr] foveation` | `medium` | `off` shades every pixel and costs the most. See [Known issues](#known-issues) if images double. |
| `[vr] repeat_frames` | `true` (default) | Without it SteamVR halves the game's rate and fills refreshes itself. |
| `[vr] frame_interpolation_fps` | `0` | Rendering in-between frames needs 120 eye pairs a second, which made things worse on the Frame. |
| `[video] resolution_multiplier` | `2` | The game's own frame, which the eyes are made from. 4x is far too heavy for the Frame's GPU. |
Keep SteamVR's own refresh rate at 120 Hz. Motion Smoothing makes no difference here.
Keep SteamVR's refresh rate at 120 Hz. Motion Smoothing makes no difference to this game.
## Controls
The Frame's controllers are bound through their own profile, so the left D-pad works:
| Frame controller | Wii Remote mode | Gamepad mode |
| --- | --- | --- |
| Right A | A | South (A) |
| Right B | C (look behind) | East (B) |
| Right trigger | B | Right trigger |
| Right stick up / down | 1 / 2 | Right stick |
| Left View | + (pause) | Start |
| Left shoulder | Settings panel | North (Y) |
| Left D-pad | Wii Remote D-pad | D-pad |
| Left stick, left trigger | Nunchuk stick, Z | Left stick, left trigger |
| Grips, stick clicks, motion, aim | as on Quest Touch ([`OPENXR.md`](OPENXR.md), Controllers) | as on Touch |
| Right X, Y, menu and shoulder | unbound | unbound |
## Known issues
- **Doubled images** in races and on the HUD, worst while racing, sometimes in the right eye only.
The suspect is foveation on the Frame's graphics driver. If it bothers you, try foveation **Off**
in the panel (it costs GPU time), and report whether it helped.
- **Foveation follows the right eye less well** than the left, and its sharp area may feel small
on Low and Medium.
Both eyes get the same frames, so a doubling in one eye points at foveation: the Frame's driver
(Turnip) draws a foveated screen tile at lower resolution and scales it back up, and each eye's
density maps change with its gaze. Try foveation **Off** if it bothers you, and report whether it
helped.
- **Foveation follows the right eye less well** than the left. Convergence on near content (the HUD
screen at 2 m, the cockpit) is not yet corrected for.
- **VR frame interpolation** is not recommended on the Frame.
- **The Quest app's `steamFrame` flavour** (an Android build for the Frame's Lepton layer) cannot
show a picture: Lepton's graphics driver lacks the memory sharing it needs. Use the native build.
- **The Quest app's `steamFrame` flavour** cannot show a picture in the Frame's Android layer
([below](#the-android-flavour)). Use the native build.
## Troubleshooting
**Logs.** Each run gets a folder under `~/.local/share/WiiCompiled/Logs/` on the Frame, holding
`console.log` and, after a crash, `crash_sigsegv.txt`. A working start logs, in order:
1. `OpenXR initialized: runtime 'SteamVR/OpenXR'`;
2. `Dawn will create its device through the runtime`;
3. `Fragment density maps: enabled`;
4. `OpenXR Vulkan swapchains ready ... same-queue native eye copies`;
5. `display refresh rate 120 Hz requested`, then `OpenXR session state -> FOCUSED`;
6. `OpenXR interaction profiles: left /interaction_profiles/valve/frame_controller_valve`;
7. `OpenXR eye gaze: available`, then `tracking`.
The first one missing says where it stopped. `Linux Vulkan OpenXR requires a Dawn built with
Aurora's patches` means the game was built without `--dawn-package`.
**Smoothness.** With `[diagnostics] openxr_logging = true`, the log gets a pacing line every second:
```bash
ssh frame 'cd ~/.local/share/WiiCompiled/Logs && d=$(ls -t | head -1) && grep -h "xr-diag\] 1.0" "$d/console.log" | tail -5'
```
A healthy race reads `predicted-rate=120.0Hz`, `new=60 repeat=60` and `late=0`. Fewer than 60 `new`
frames means the GPU is over budget: lower `render_scale` or `resolution_multiplier`.
**Crashes.** `console.log` then names the faulting thread and gives its pc and a backtrace as
`module+offset`. On the build machine,
`addr2line -f -C -e Wiicompiled_VR_Frame/native-build/WiiCompiled 0x<offset>` turns an offset in
`WiiCompiled` into a function: the executable is not stripped.
**Building.** A build that freezes the machine has run out of memory: lower `--jobs` or
`--parallel`. A missing CMake package means installing its `-dev` package in the container and
running the same command again.
## Other ways to build
**On the Frame itself.** SteamOS's root file system is read-only, but it ships podman. Over SSH:
```bash
mkdir -p ~/wiicompiled && cd ~/wiicompiled
git clone https://github.com/mitch030504/Wiicompiled_VR_Frame.git
podman run -it --name wiicompiled-frame -v ~/wiicompiled:/work:Z docker.io/library/debian:trixie bash
```
It is native ARM64, so no emulation, but the Frame has less memory and cooling than a PC. Extract
your disc as in step 2, with `nodtool-linux-aarch64` instead of `nodtool-linux-x86_64`, copy
`main.dol` and `StaticR.rel` into `Assets/`, and run step 3's commands in the container. The game
lands in `~/wiicompiled/out`.
**With Docker on a stronger machine** (a server, for example). The same container and commands
work, and much faster with more cores and memory. Copy `~/wiicompiled` over without Dawn's build
tree, and run the build detached so a closed SSH session does not stop it:
```bash
rsync -a --exclude dawn/build --exclude 'dawn/dawn-*' --exclude disc-extract \
~/wiicompiled/ root@<server>:/srv/wiicompiled/ # from the PC
docker run -d --name wiicompiled-frame --platform linux/arm64 -v /srv/wiicompiled:/work \
debian:trixie bash -c 'bash /work/build-game.sh >> /work/game.log 2>&1'
```
`build-game.sh` holds step 3's `apt-get` line and `local-build.sh` command, with `--parallel 8` for
32 GB. `docker start wiicompiled-frame` runs it again after an update.
On some hosts, for example Unraid 7 with kernel 6.18, `binfmt_misc` registrations are per container.
`tonistiigi/binfmt --install arm64` then reports success, but Debian answers `exec format error`.
Register qemu on the host instead, with the `P` flag the tonistiigi build of qemu expects. Without it,
every program loses its first argument, and `uname -m` prints `Linux`:
```bash
docker create --name qemu-src tonistiigi/binfmt
docker cp qemu-src:/usr/bin/qemu-aarch64 /usr/local/bin/qemu-aarch64
docker rm qemu-src
echo ':qemu-aarch64:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/local/bin/qemu-aarch64:POCF' > /proc/sys/fs/binfmt_misc/register
docker run --rm --platform linux/arm64 debian:trixie uname -m # aarch64
```
Unraid keeps `/usr/local/bin` in memory, so repeat this after a reboot.
## How it works
- **Same-device rendering.** SteamVR's OpenXR runtime creates the Vulkan instance and device that
Dawn (Aurora's WebGPU layer) renders with, through Aurora's patches to Dawn
(`aurora-main/patches/dawn`). Each eye is copied into SteamVR's swapchain on Dawn's own queue, so
nothing is shared between GPU devices. This is the PC's Vulkan backend
(`runtime/src/vr/openxr_vulkan_win32.cpp`), compiled for Linux. Dawn links statically, so the
patched Dawn is built once (`Launcher/build-dawn-linux.sh`). Its `aurora-dawn.json` declares the
Vulkan hook and density map ABIs that Aurora compiles against.
- **Headset only.** The game neither shows nor finishes rendering a desktop window, and is compiled
for the Frame's Cortex-X4 cores (`-mcpu=cortex-x4`, overridable with `--cpu`).
- **Every refresh from the game.** The game draws 60 frames a second. When it handed SteamVR only
those, SteamVR ran it at half rate and made up every other refresh itself, even with Motion
Smoothing off. With `[vr] repeat_frames`, the pacing thread waits for the next frame until 1.5 ms
before SteamVR's next wake, then resubmits the last frame at the pose it was rendered for.
SteamVR turns that to the current head pose.
- **Eye-tracked foveation.** `XR_EXT_eye_gaze_interaction` gives the gaze, which
`runtime/include/vr/eye_gaze.h` turns into each eye's view. Aurora picks a fragment density map
centred on the gaze, snapped to cells of about 3 degrees (`aurora-main/lib/gfx/foveation.hpp`),
and keeps up to 128 per eye. Each map has a memory block of its own: Turnip reads a map through a
host mapping, and Dawn's buffer uploads unmap the shared blocks they sub-allocate from. That
crashed the game when a race restarted, until each map got its own block.
- **120 Hz.** `[vr] refresh_rate` (default 120 on the Frame, `0` keeps the headset's own) is asked of
SteamVR through `XR_FB_display_refresh_rate`. SteamVR only offers the rate set in its own settings.
### The Android flavour
The Quest app also has a `steamFrame` flavour (`android/Build-Quest.ps1 -Headset frame`), for the
Frame's Android layer, Lepton. It builds and starts in VR there, but cannot show a picture. The
Android backend hands each eye from Dawn's device to its own OpenXR device through
`VK_ANDROID_external_memory_android_hardware_buffer` and sync fds, and Lepton's Turnip has neither,
nor `VK_KHR_external_memory_fd`. The native build needs no sharing, which is why it is the one to
play. [`docs/quest-port.md`](docs/quest-port.md) covers the Android build.
## Reporting problems
Open an issue on this repository with:
- what you did and what you saw (which eye, where in the picture, racing or menus);
- the run log: each run has a folder under `~/.local/share/WiiCompiled/Logs/` on the Frame, with
`console.log` and, after a crash, `crash_sigsegv.txt`;
- the run's `console.log` (and `crash_sigsegv.txt` after a crash) from
`~/.local/share/WiiCompiled/Logs/` on the Frame;
- or, for a build problem, the last lines of the failing step.
[Running it](docs/steam-frame.md#running-it) lists the log lines a working start shows, in order;
the first one missing says where it stopped.
Problems that also happen on a PC or a Quest belong upstream, in
[WiiCompiled OpenXR VR](https://github.com/iChris4/Wiicompiled_VR).