Drop the pass-size logging and SHADPS4_VR_FOVEATION_TEST coarse mode used
to verify the map on the Frame, point the docs at v0.2.0, and record what
was verified on the headset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Astro Bot draws its scene one eye at a time (1200x1280 at preset 5), so
the side-by-side gate never matched it and the map only reached other
wide passes. known_title now publishes the scene's eye size; exactly
that pass gets a single-eye map.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diagnostic for the Frame test: logs each distinct render-pass size once
with whether it can take the map, and SHADPS4_VR_FOVEATION_TEST=1 forces
4x4 everywhere so a driver ignoring the map is obvious.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pipelines had no fragment shading-rate state, so the combiners defaulted
to KEEP and the attached rate map never changed the shading rate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Version 0.20 of the Quest app (versionCode 20) and of the PC package: the collisions at the
end of level 2-1 (Issue #16) and the squares in the lava's glow around the octopus, both in
the emulator and so the same on the headset and on the PC. The notes of both READMEs say
what was wrong and what was tried.
README: the answer to "How do I get the game onto my computer?" is that this project gives
no information on how to get games and that the repository does not include any game data.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Around the octopus of world 2 the glow of the lava had squares in it, lit and dark, that
came and went as he moved (seen on the PC in the headset, and on the monitor at the console's
size).
The game draws the rings that glow into a quarter-size buffer which it clears first and then
has bound twice for the ring draws: as target 0 with a write mask of red, green and blue and
blending on, and as target 1 with a write mask of alpha and blending off. The console's GPU
writes both to the same memory. In Vulkan the image was two attachments of one render pass,
which leaves undefined what it holds afterwards: read back right after the ring draws it
had the rings in some blocks of 4x8 texels and the cleared value in others, and was whole
right before them. (Making the GPU finish all its work before and after the image was
filled changed nothing, which is what ruled out the order of the work as the cause.)
Rasterizer::Draw now asks which of a draw's targets share a surface and makes the draw once
for every one of them, with the others left out of the pipeline as if nothing were bound
there. The shaders are the same. A draw that writes depth with a test that fails on equal
depth, or uses the stencil, would not come out the same twice: it is drawn as before, with a
line in the log.
Tried on the PC at the console's size through the challenge that has the same octopus: the
buffer read back after the ring draws had 34 to 73 hard edges on its grid of four texels
before and none after, over three passes each, and ten pictures of the fight have the glow
whole. Not yet seen in the headset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Issue #16 (and seen on the PC): at the end of level 2-1 the hero stood in the air above the
last mound and ran into a wall that was not there, the island below had the same; its
collisions were most of a block above what was drawn.
The game moves the collision bodies of what moves by sending them to a place: the physics
library gives a body the speed that takes it there in one step, distance / time step, with
the time step of the step last taken, and the step that follows moves it by speed x the new
time step. On the console both are a sixtieth of a second. With the time step the emulator
gives the game, which follows what frames take, the body lands off by its way times the
difference between the two. What moves all the time is put right a frame later. What is sent
once is not: a level sleeps by sections, and a section that wakes sends all its bodies in one
frame from where they slept to where the level has moved since. The last two sections of 2-1
are woken at the top of a tree that carried them 17 units up while they slept, in a frame
that is long because of all that is new in it.
The function that has the library take a step wrote the new time step down and then stepped.
It is changed in place, in both versions of the game, to step with the time step that is
written down, the one every body was sent with, and to write the new one down after
(SHADPS4_TITLE_PHYSICS_STEP=0 leaves the game's own). The places are checked for what the
game has there before anything is written.
SHADPS4_TITLE_PHYSICS_WATCH=1 has the log tell every ten seconds how far the bodies sent
ended from where they were sent. On the PC, the scripted walk through the first level at
about 40 frames a second: with the game's own step, off by the way times the difference of
the two time steps (0.003 for a body sent 0.16 where they were 2% apart, 0.085 for one sent
0.19 across a hitch); with the change 0.0000 over 110,000 sends, in 1.00 and in 1.04.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The session after the last fix played from the PC's speakers again, with the headset
connected from the start: Virtual Desktop's sound device was there, but Windows preferred
the speakers (which the test of the last commit had caused: it chose them by hand, and
Windows keeps to the device chosen last), and Virtual Desktop's runtime names the device
Windows prefers, not its own.
- The device whose name ends with "(Virtual Desktop Audio)" is the headset's while it is in
the system, which it is while Virtual Desktop streams to a headset: whatever Windows
prefers and whatever the runtime in use (SteamVR names none). Its microphone likewise,
while the playback device is there. SHADPS4_XR_STREAM_AUDIO names another ending.
- tools/audio-endpoint.ps1 preferred says which device Windows would play on if all were
there, and pc-audio-default-test.sh makes that one the preferred one again when it is
done, also where it is not there during the test.
Tried against a simulated headset with the monitor's device named as the streaming
program's: the game plays on it while Windows plays on the speakers. Not tried: with
Virtual Desktop itself, which needs the headset worn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A session on the PC ended with "Unhandled Exception code 0xc0000005" in
SDL_GetAudioStreamDataAdjustGain, on the playback thread of Virtual Desktop's sound device,
a moment after the emulator had moved its ports "back" to the PC's speakers.
- Why the speakers: Virtual Desktop has its sound device only while it streams to the
headset. Asked before that, its runtime names what Windows plays on, and the emulator asked
once, at the start; it then held on to the speakers by name and took the sound back to them
three seconds after Windows had moved it to the headset. The runtime is asked again now
whenever a sound device comes or goes (OpenXrHost::AudioDevicesChanged, on the frame
thread), and the ports go where it then says.
- Why that stopped the game: ports opened on "the default device" are moved by SDL when the
system changes its default, and SDL 3.5.0 (SDL_DefaultAudioDeviceChanged) puts each moved
port at the front of the new device's list without setting the back link of the one
behind it; closing a moved port afterwards leaves the list pointing at freed memory, which
the device's playback thread reads. The ports no longer play on "the default device":
each is opened on the device the system plays on at that moment, as the device it is, and
FollowDevice moves it when the system chooses another (after the three seconds it already
waits for devices that come back).
tools/audio-endpoint.ps1 can say and set the device Windows plays on;
tools/pc-audio-default-test.sh changes it while the game plays, and back.
Tried: with the build before, that test stops the emulator (access violation); with this one
it plays on, the ports that are left to the system on the new device and back, the ones
named for a device staying there. The device test of 0.13 (a named device, and the default
device itself, taken out of Windows and put back: the level moves to the other device and
back). Against a simulated headset, whose runtime names another device once one went away:
the runtime is asked again and the ports follow. With SDL's dummy driver, as the headset's
build has it, the ports open as before. Not tried: Virtual Desktop's own device coming and
going, which needs the headset worn, and the Quest app (the headset was in use).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the project's thread on Reddit and the comments under the videos about it, all written
before 0.18. What 0.18 had not answered:
- The PC launcher asked for the Visual C++ runtime again and again, however often it was
installed (one player cut the check out of the launcher to play). Started from a 32-bit
program, "powershell" is the 32-bit one, to which Windows shows the 32-bit system folder
under the 64-bit one's name, and vcruntime140_1.dll only exists in 64 bits. The launcher
hands over to the 64-bit PowerShell first (which also puts the OpenXR runtime's registry
entry where it looks), looks for the files where a 64-bit program finds them and next to
the emulator, names the ones it misses, and its question can be answered with "start the
game all the same". When the emulator ends with "DLL not found" it says what that is.
- The game was in English whatever the console's language should have been: the emulator's
console was set to English and nothing offered another. SHADPS4_CONSOLE_LANGUAGE takes a
language tag ("fr-FR", "pt_BR", "zh-Hans-CN") or the console's number
(Common::ConsoleLanguageFromTag). The PC launcher passes the language Windows is shown in,
or the one chosen in its window from the game's 28 (language=); the Quest app passes the
headset's (language= for another).
- On the PC the headset's controllers did nothing while a gamepad was connected, even one
that only lay there. Whichever was used last plays (Input::PadSource): a button or a stick
of the headset's controllers takes over once the gamepad has said nothing for a quarter of
a second, which leaves a gamepad that a streaming program makes of those controllers
playing as before; the gamepad takes back with its first button, stick or touch. While the
headset's controllers play, GameController leaves the gamepad's input aside, and the
runtime believes a host that says where the controller is over a gamepad's sensors.
- Turning the view by steps, asked for by a player who cannot turn round where they sit:
Runtime::TurnView turns the seat about the head (the head keeps its place in the title's
world, the controller comes round, a reset of the view undoes it). Asked for with a button
the game has no use for in play, held, and the right stick flicked to a side
(Input::ViewTurn): L1 on a gamepad, the grip of the hand that does not hold the controller
on the headset's own; from the Quest app as two new flags of the pad pose message. 30
degrees a step (SHADPS4_VR_TURN, turn=). The Quest app's card names it.
- "Save Migration", a question in a box behind the game's window at the first start
(reported from Linux, where the package's ready-made folder was not used): only asked
where an older shadPS4 left something to move.
README: common questions (getting the game, headsets, controllers, SteamVR, why the picture
looks reprojected, language, Linux), and a player's recipe for Proton, as reported.
Tried: the unit tests (console_language_test, pad_source_test, pad_router_test with the
turn, launcher-test under 64-bit and 32-bit PowerShell, the launcher's hand-over from a
32-bit one); the game on the PC in French, Japanese and Arabic, and in English for a
language it lacks; against a simulated headset with a test gamepad and test presses of the
headset's controllers (SHADPS4_TEST_VIRTUAL_PRESS/_TURN, SHADPS4_XR_TEST_PRESS/_TURN): each
takes over from the other, the view turns by either, the picture turns and the head stays in
place; on a Quest 3 without wearing it: the language reaches the game, the self-test's new
HOST_TURN turns the view with a tracked and with an untracked controller alike, the card
shows its new line. Not tried: any of it worn, and Virtual Desktop's own gamepad emulation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The launcher took a package for the game's update by three of the marks an update can
carry; PATCHGO (0x00200000) is the fourth. By ODevStudio, who reduced their pull request to
this once main had the rest of it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first worn try of the Quest app with the headset's own controllers ended at the
catapult of the first level: the right stick pulled back and let go did not shoot it. And
nothing told the player that the pad is pressed (the water cannon) by pressing that stick in.
Why the catapult failed: the game reads the pad once for every frame it draws (the newest of
the states it is handed), and takes the place it first finds the finger at for the start of
a pull. A stick is at its end within a few hundredths of a second, so the finger that
followed it there had, for the game, begun where it ended. By a model of the game's rule
(tools/tests/touch_game_model.h: read at 0xc04cc3, y = 1 - raw / 950, the catapult of
0x436fc0 wants more than 0.25 s and more than 0.4 of the pad) one fast pull in four failed
at 60 frames a second and two in three at 30, which is what the reports of Issue #2 say and
what the headset draws.
- Input::MovedFinger: a finger that stays where it lands until the game has seen it there,
goes no faster than a finger flicks and counts as arrived once it was seen at its end.
- Input::StickFinger is built on it: the finger comes down behind the pad's middle, on the
side the stick comes from, and is dragged to where the stick points.
- Input::PadGestures: what the game asks of the pad, done by buttons it has no use for in
play (it only asks about them in a few menus, and is still told of them): the pad pressed
while a button is held, a swipe forward for each press of another, a pull back that is
held and let go with a third. On the headset's own controllers the trigger and the grip of
the hand that holds the controller in the game and the other hand's trigger; on a gamepad
without a touchpad R2, R1 and L2. In the Quest app's router, the PC host's code for the
headset's controllers and the PC's gamepads.
- The Quest app shows a card over the game that names those buttons, when the game starts
and whenever OPTIONS is pressed (the game's pause).
- SHADPS4_PAD_TOUCH_TRACE=1 has the log say what the game was given of the touchpad at each
of its reads; input scripts can press the gesture buttons (gesture=press|swipe|pull); the
Quest app's dry start pulls the right stick and presses R1.
Tried: the tests (stick_finger_test and pad_gestures_test against the model at 26 to 60
frames a second, pad_router_test); with the trace, in the game on the PC (62 reads a
second) and on a Quest 3 without wearing it (48 in its sandbox, 36 through the whole app):
a pull is given as 0.74 to 0.76 of the pad over 0.34 s and more, a swipe as 0.67 over a
dozen reads, a press for as long as it is held. Not tried: the catapult itself going off
(a script did not get through the level to it), and all of it worn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app was made for a DualSense and took everything else for a lesser one of those. Players
without one could play on the PC, with the headset's controllers, and not on the headset.
The headset's own controllers: the app reads them through the headset's runtime (OpenXR
actions, the layout the PC host has: A cross, B square, X circle, Y triangle and both
together to blow, grips L1/R1, triggers L2/R2, right stick the finger on the touchpad, left
menu button OPTIONS, both sticks in to reset the view), the right one is the controller in
the game with where it is and how it points, both shake with the game's rumble. The gamepad
the system makes of them for other apps is no longer taken for a gamepad: it says neither
where it is nor whether anybody holds it.
Which of the two: a small router (quest-host/cpp/pad_router.h) between the activity, which
hears of gamepads, and the session, which reads the controllers. The game is played with the
one used last. From a gamepad only a button under a thumb or a stick takes over, never a
trigger or a grip: while a gamepad is held it is hands the headset sees. What buttons mean
about the view besides (the first press of X takes the seat, OPTIONS held resets the view)
moved there from the activity, so that it is the same for both. The start-up menu takes
either; what confirmed it is not pressed in the game.
Gamepads that are not a PlayStation's:
- The right stick as the touchpad's finger is Input::StickFinger now, as on the PC: the
finger lifts where the stick was let go, which the catapult at the end of a level needs.
A button that presses the touchpad touches it in its middle.
- A gamepad without motion sensors followed the hands to where they were and kept pointing
straight ahead. Now the hands turn and tilt it (quest-host/cpp/held_pad.h): the line
between the palms is its sideways axis, the way the hands point is its tilt. The app says
so with a new flag of the pad pose message (HeldInHands), and the emulator lets go of a
controller that was a tracked device once it no longer is (it stayed where that device
was last seen for as long as no motion sensor of its own spoke up).
- Motion sensors are only taken from PlayStation controllers: which way is up for one is
each maker's own affair.
- A right stick on RX/RY, triggers that are only buttons, a Back button the system takes
for its own "back".
For trying a build on a headset whose owner plays with the installed one:
quest-host/build.sh --test-package builds the app under another name, which installs next to
it; the test tools take ASTRO_PACKAGE. The self-test can report a controller the way a
session does (HOST_PAD) and keep pictures of it (HOST_SHOTS).
Tried on a Quest 3 without wearing it: the runtime takes the bindings, the app starts and
plays with a made-up gamepad, and in the game's picture the controller is where and how the
self-test says a tracked controller or hands around a gamepad are, and comes back when they
are lost. tools/tests/pad_router_test.cpp and held_pad_test.cpp try the rest on the PC.
Not tried: any of it worn, and no gamepad other than a DualSense.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the maintainer's 1.04 build support, calibration and documentation from main. Retain the missing PATCHGO flag check and a regression fixture that is larger than the base package.
The first release since 0.13. For the Quest it brings the field-of-view menu of 0.14 to
0.17 and the game's version 1.04; for the PC the same and SteamVR support, spectator views,
a gamepad that can be moved where nothing tracks it, an easier catapult with a stick,
blowing with buttons, and the fixes for the two ways the game stopped as it started.
README-QUEST-VR.md and README-PC-VR.md tell each.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Work of 2026-10-04, committed as it was (README-QUEST-VR.md tells each step):
- 0.14: every start shows a panel in the headset to choose how much of the field of view
the game draws (left and right on the D-pad or the left stick, X to play). Less is the
same pixels over fewer degrees, a sharper picture; the choice is remembered. Below all of
it, each eye gets a soft-edged squircle border, drawn where the host copies the frame.
- 0.15: the percent is of the headset's own view, read from OpenXR before Play is offered
and handed to the emulator before the game starts (SHADPS4_VR_HEADSET_FOV_TAN), 50 to 100.
- 0.16: the border sits around each eye's straight ahead, which a headset's uneven view
does not have in the middle of the picture.
- 0.17: both eyes draw the same, even view around straight ahead that takes in the
headset's own (SHADPS4_VR_FOV_SYMMETRIC), so that what one eye shows the other shows too
when the view is narrowed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>