No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
nak f7c070ddc1 Document the real gamepad bindings from the cooked DefaultInput.ini
The configs are stored uncompressed in the pak, so the bindings could be read
directly rather than guessed. Resolves the trigger ambiguity in the old notes:
the digital triggers are Fire/FireAlt while the trigger *axes* are Thrust, so
both readings were right.

Also documents why flying on a pad feels broken (Thrust shares the triggers
with Fire and right-stick Y with LookUpRate), and records the observed-but-
unconfirmed B / bumper bindings, which the ini cannot show because it routes
every pad key through a catch-all GamePadEvent action into Blueprint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 13:54:34 -07:00
play-flat.sh first commit 2026-08-16 13:36:18 -07:00
play-vr.sh first commit 2026-08-16 13:36:18 -07:00
README.md Document the real gamepad bindings from the cooked DefaultInput.ini 2026-08-16 13:54:34 -07:00
setup.sh first commit 2026-08-16 13:36:18 -07:00
vr-env.sh Rewrite README around practical use; drop stale controller claim 2026-08-16 13:46:12 -07:00

PonyVRVille on Linux

Launcher scripts and notes for running the abandoned Oct-2019 Windows build of Pony-VR-Ville (Unreal Engine 4.14.3, OpenVR 1.0.2) in VR on Linux — through WiVRn, with no SteamVR and no ALVR in the chain.

What you need

The game Not in this repo — see below.
Steam installed Proton sources its runtime from it. Does not need to be running.
GE-Proton Auto-detected from compatibilitytools.d. Tested on GE-Proton10-32.
WiVRn Flatpak flatpak install flathub io.github.wivrn.wivrn. Bundles xrizer.
WiVRn headset client Must match the server version. Install it from the dashboard's "Install the app" tab over USB.
A gamepad Strongly recommended. See Playing it.

Getting the game

Pony-VR-Ville was an abandoned fan project. The Oct-2019 Windows build these scripts were written against:

https://www.dropbox.com/scl/fi/fvubj2jxznrmi3vo3kqiw/PonyVRVille-10-2019-Build.rar

Unpack it onto a real filesystem — not exFAT, since Proton needs a prefix with working permissions. ~/Games/PonyVRVille is where the scripts look by default:

~/Games/PonyVRVille/
├── Engine/
├── PonyVRville/
└── PonyVRville.exe

Quick start

./setup.sh                # once per machine

flatpak run io.github.wivrn.wivrn    # start WiVRn, connect the headset

./play-vr.sh              # play in VR
./play-flat.sh            # play on the monitor, no headset

Connect the headset before launching, or game audio lands on the desktop speakers instead of the headset.

Both play scripts take --stats for the stat unit frame-time overlay.

The scripts find the build at ~/Games/PonyVRVille, or in their own directory or its parent if you copy them into the build. Anywhere else, point GAME_ROOT at the folder holding PonyVRville/ and Engine/:

GAME_ROOT=/mnt/games/PonyVRVille ./setup.sh

Other overrides: PROTON, PREFIX, STEAM_ROOT, WIVRN_FILES, VR_RUNTIME_NAME.

Playing it

Motion controllers do nothing in this game — they never did. It descends from a 2015 Oculus DK2 project, which predates Touch controllers entirely; there is no motion-controller support in the content at all. This is not a WiVRn, xrizer or Proton limitation, and no amount of runtime fiddling will change it.

The headset does head tracking and aiming. Everything else is a gamepad (recommended — an Xbox-style pad over XInput) or keyboard and mouse.

These are read straight out of the cooked DefaultInput.ini:

Gamepad Keyboard / mouse
Move left stick WASD, arrows
Turn right stick ← → , mouse
Look up / down right stick ↑ ↓ mouse
Jump A Space
Fire right trigger LMB
Alt-fire left trigger RMB
Fly — thrust up right trigger, or right stick ↑ LShift
Fly — thrust down left trigger, or right stick ↓ LCtrl

Flying on a pad is genuinely confusing, and it is the game's fault. The Thrust axis is mapped to the trigger axes and to right-stick Y, both of which already do something else: the same right trigger is also the digital Fire button, and right-stick Y is also LookUpRate. So pulling a trigger to climb also shoots, and the stick fights between looking and thrusting. LShift / LCtrl on the keyboard is the unambiguous way to fly.

Observed in-game but not in the config — treat as provisional, and corrections welcome:

B talk to a pony
right bumper cycle spell — fireball / magic missile / bomb
left bumper (hold?) set teleport destination
F9 summon pegasi

These are missing from the ini because the ini punts: alongside Jump, Fire and FireAlt, it binds a catch-all GamePadEvent action to every single pad key and lets the character Blueprint decide what each one means. BP_Bomb, MagicMissile, Fireball and Teleport all exist as assets in the pak, so the list above is the right set of features — only the exact buttons need confirming with the HMD on.

A pad is also the only sane way to hand the headset around at a party — nobody wearing it can find the keyboard.

Performance tuning

Edit PonyVRville/Saved/Config/WindowsNoEditor/Engine.ini (writable config, no need to repack the pak):

[/Script/Engine.Engine]
bSmoothFrameRate=False        ; cooked config caps at 75fps, fights 72/90Hz
                              ; setup.sh already writes this one

[SystemSettings]
vr.PixelDensity=0.8           ; biggest single GPU lever
r.ScreenPercentage=85
r.PostProcessAAQuality=2      ; cooked default is 3

Frame budgets: 72 Hz = 13.9 ms, 90 Hz = 11.1 ms. Measured on a Ryzen 7 5700G + RX 6700 XT, Quest 3S over WiVRn:

Scene Frame Game Draw GPU
Flat, interior 9.78 ms 9.55 10.01 5.62
VR, interior (3840×1920) 15.12 ms 5.47 4.68 10.03

The bottleneck moves between flat and VR. Flat, the draw thread is the wall (~10.8 ms in the exterior) and GPU-side quality settings barely help. In VR the draw thread drops to ~4.7 ms and the GPU becomes the constraint — the good case, because pixel density and screen percentage trade almost linearly into frame time. vr.InstancedStereo=True is already in the cooked config, so both eyes go out in one draw call; that is why stereo is cheap on the draw thread.

Audio

play-vr.sh sets PULSE_SINK=wivrn.sink so the game opens WiVRn's sink directly and audio follows you into the headset. That sink only exists once the headset has connected, so connect first; otherwise the script warns and falls back to the desktop default.

To rescue a session that is already running, move the stream live:

pactl list short sink-inputs          # find the "Unreal Engine" stream id
pactl move-sink-input <id> wivrn.sink

PULSE_SINK is still the better path — UE negotiates its channel count against whatever sink it opens first, so a multichannel desktop default makes it open 4 channels that PipeWire then downmixes into WiVRn's stereo sink, smearing the spatial audio.

Known issues (all cosmetic, all pre-existing)

  • Tutorial video is black. Instruction_Video is a UE MediaPlayer asset fed through WmfMedia, which has no H.264 decoder in the prefix. The clips are plain MP4s in PonyVRville/Content/Movies/ — play them in any Linux video player. protontricks with Media Foundation codecs would fix it properly.
  • Unable to load movie: Movies/Default_Startup.mp4 — that file is not in the pak.
  • Failed to compile Material /Game/MyAssetsMisc/M_Highlight, and a statue missing convex collision — both baked into the 2019 cook, not Linux-related.
  • app requested unknown interface "IVRExtendedDisplay_001" — extended-display mode, unused.

How it works

PonyVRville.exe (OpenVR 1.0.2, D3D11)
  → bundled openvr_api.dll
  → C:\vrclient\bin\vrclient_x64.dll     Proton's OpenVR shim
  → xrizer bin/linux64/vrclient.so       OpenVR → OpenXR translation
  → libopenxr_wivrn.so                   WiVRn's OpenXR runtime
  → wivrn-server → headset

D3D11 goes through DXVK to Vulkan as usual. SteamVR is not involved at any point — Proton still needs a Steam install to source its runtime, but Steam does not need to be running.

HKCU\Software\Wine\VR — why non-Steam VR "just doesn't work"

The one genuinely unguessable step. Proton's vrclient will not load its native runtime unless this registry key holds the Vulkan extension lists, and the key is written only by Proton's steam_helper — that is, only when the game is launched through Steam. Launch any other way and you get:

err:vrclient:get_vulkan_extensions_from_registry Could not create key, status 0x2
trace:vrclient:load_vrclient Error getting extensions from registry

which reaches the game as OpenVR error 105 VRInitError_Init_InterfaceNotFound. That error is deeply misleading — nothing is missing. Every interface the game asks for (IVRSystem_012, IVRCompositor_016, IVRClientCore_002) is present in both Proton's shim and the native runtime.

setup.sh writes the six values. After that:

trace:vrclient:load_vrclient got openvr runtime path: .../xrizer
LogHMD: SteamVR initialized.

xrizer, not OpenComposite

The WiVRn Flatpak bundles both OpenVR→OpenXR translation layers. VR_RUNTIME_NAME picks one; the scripts default to xrizer, which is what all of this was tested against. OpenComposite loads too, but got far less use here.

The input-profile differences between the two are moot for this game, since it takes no motion-controller input under either — judge them on tracking and frame pacing, not input.

Already investigated — don't re-debug these

  • Motion controllers "not working". The game has no motion-controller support. Of 3807 pak entries, only four game assets reference any VR symbol and every one is HeadMountedDisplay (head tracking): MyCharacter, MyCharacter2, Example_Map, PonyBase-PinkiePie. There are zero MotionController references in game content, and DefaultInput.ini binds only keyboard, mouse and gamepad. (VR_grab_Cue and VR_teleport_Cue in the pak are Unreal's VR Editor assets under Engine/Content/VREditor/Sounds/, cooked in incidentally and referenced by nothing.)
  • Reading the input bindings. No pak extractor needed — the cooked configs are stored uncompressed as plain text. grep -abo ActionMappings <pak> gives the offset (~2.62 GB into this build), then dd a few KB from there and pipe it through strings.
  • Legacy OpenVR interface versions. All present on both sides.
  • PROTON_VR_RUNTIME not being exported. It reaches the process fine; verified via /proc/<pid>/environ.
  • ALVR being out of date. v20.14.1 is current. The breakage was SteamVR 2.16.7 build 23791826, which fails to register vrcompositor on Linux under ALVR direct mode — see SteamVR-for-Linux #923. Symptom: timeout - vrcompositor process is not running in vrserver.txt, plus ALVR's "Desync detected. Attempting recovery." The WiVRn path avoids it entirely.

Reference