- Shell 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
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> |
||
| play-flat.sh | ||
| play-vr.sh | ||
| README.md | ||
| setup.sh | ||
| vr-env.sh | ||
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_Videois a UEMediaPlayerasset fed throughWmfMedia, which has no H.264 decoder in the prefix. The clips are plain MP4s inPonyVRville/Content/Movies/— play them in any Linux video player.protontrickswith 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 zeroMotionControllerreferences in game content, andDefaultInput.inibinds only keyboard, mouse and gamepad. (VR_grab_CueandVR_teleport_Cuein the pak are Unreal's VR Editor assets underEngine/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), thendda few KB from there and pipe it throughstrings. - Legacy OpenVR interface versions. All present on both sides.
PROTON_VR_RUNTIMEnot 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
vrcompositoron Linux under ALVR direct mode — see SteamVR-for-Linux #923. Symptom:timeout - vrcompositor process is not runninginvrserver.txt, plus ALVR's "Desync detected. Attempting recovery." The WiVRn path avoids it entirely.