255 Commits
Author SHA1 Message Date
Madison 41a599a871 Prepare public Lambda1VR source and remove tracked SDK paths 2026-10-11 17:46:14 +09:00
Madison f3773cb04d Fix LAN host slots and OpenXR loader for paired Android builds 2026-10-11 14:18:03 +09:00
Madison aacf6e5a0f Frame diagnostics: layer poses of the menu laser in the dump, composite_menu.py; status after the Steam-in-place, laser and 120 Hz runs (dev only) 2026-10-05 16:47:07 +09:00
Madison 77150eeb57 Frame diagnostics: thread CPU and waits in [vr-perf], load and exit_seconds keys, perf_run.sh (dev only)
[vr-perf] now has the game thread's own CPU time per frame (CLOCK_THREAD_CPUTIME_ID), the
time in xrWaitFrame and in acquiring the swapchain image (with its wait), and the eye size, so
the split between work and waiting is there (lessons 39, 50, 62). The load key loads a save
instead of starting a map, exit_seconds leaves after that many seconds of rendering.
perf_run.sh does one run and adds the Adreno's time at each frequency over it.
2026-10-05 16:47:07 +09:00
Madison 3372d5af6e Frame diagnostics: aim the synthetic hands, show the pointer in the dump, level-HUD analysis (dev only)
diag_hand_yaw and diag_hand_pitch turn the synthetic hands' aim from the head's,
so the menu laser can be pointed. vr-state.txt says where the cursor is (u, v and
the pixel in the screen image). analyze_roll.py compares HUD-on and HUD-off dumps at
different rolls and says how far the world and the HUD turned. Host test for the
laser maths (ray against the quad, beam and dot poses).
2026-10-05 16:47:07 +09:00
Madison a5c1c07503 Frame diagnostics: diag_roll, diag_press, diag_gap and a log line per button (dev only)
diag_roll tilts the synthetic head, to check the HUD stays level. diag_press
holds buttons down for a while ("A@300+30, LG@400+20", frames after the
map starts) so the controller map can run with nobody holding controllers:
the map code runs when there are presses to come, with the runtime's own
inputs under them. diag_gap sets how far apart the synthetic hands are, for
the two-handed hold against the grab. The map logs each press and what it
made of it. A host test builds the effective map table for both hands and
every context and fails on an input doing two things, an unlisted alias or an
action nothing reaches.
2026-10-05 16:47:07 +09:00
Madison ee43034963 Frame diagnostics: eye poses and traced depth probes in the dump, cmd key (dev only)
vr-state.txt now has each eye's position and rotation in head space,
and a grid of probes: rays from the left eye through pixels of the
image, traced into the world, with the depth along the view axis and the
disparity a correct stereo pair should show there. analyze_stereo.py
block-matches the right eye at those points and prints predicted against
measured (numpy and pillow in a venv). A new cmd key runs console commands
about five seconds after the map starts, for cheats, cvars, giving the
suit to get a HUD.
2026-10-05 16:47:07 +09:00
Madison 208cb7d1c7 Frame diagnostics: read the eyes with the driver's GL, more state in the dump (dev only)
The readback used gl4es's glReadPixels, which doesn't know the swapchain's
framebuffers. It uses the driver's now. The dump logs the driver's
framebuffer bindings, viewport and scissor, the share of pixels that
aren't black and the connection state, and vr-state.txt says whether a map
is running. New probe key: clear each eye to a flat colour before the dump,
to tell a broken readback from a broken picture. [vr-perf] shows game=active
once a map is running.

VR_ARCHITECTURE.md has what the first unattended runs found.
2026-10-05 16:46:58 +09:00
Madison 231f5513a2 Add unattended test switches for the Frame build (dev only)
vr-diag.ini in the data folder, read once at start, does nothing when
it isn't there: force_render (draw and run the game while the headset
is in standby, synthetic head and hands), diag_yaw, autostart and map,
dump_frame (both eyes as BMP plus a state file), exit_after, a [vr-perf]
line every 10 seconds and oneshot (the file gets renamed once read).

The parser and BMP writer are in VrDiagCore.c so they can be tested on
the host. VrDiag.c is the glue, and the hooks in the VR files are a line
or two each behind L1VR_STEAM_FRAME.

Builds. Haven't run it on the headset yet.
2026-10-05 16:46:57 +09:00
Madison e6b3e26003 Frame: make the menu tile with the Frame's button help at build time
make_frame_tile.py reads the release tile from the git-ignored
assets/background, takes the Quest help off it, draws the Frame's, and
writes it into build/frame-assets. The frame build type's assets override
the release tile with it, a gradle task runs it before mergeFrameAssets.
Quest and Pico builds are untouched. If the tile or Pillow and numpy are
missing, the Frame build goes on with the release tile and a warning.
2026-10-05 16:46:57 +09:00
Madison 3c5d2cc813 Frame: right stick up and down do nothing, D-pad is weapons and laser, flat screen view is a setting
Right stick up/down no longer toggle the flat screen view or the laser
sight. D-pad left/right are previous/next weapon, up is the last weapon
and down toggles the laser sight. The D-pad crowbar is gone, the crowbar
is the grip behind the head. With the other hand behind the head, the
right stick click is quick save and View is quick load.

The flat screen view is the vr_flat_screen cvar now, with a Flat Screen
View checkbox on the Video options page in the Frame build only. In
multiplayer, View still toggles the scoreboard.
2026-10-05 16:45:00 +09:00
Madison afbc400cfe Frame: size the eye image from vr_resolution_scale, ask for a refresh rate
The Frame build rendered the runtime's 1728 square recommendation, but the
panel is 2160 square. The eye image is now the recommended size times
vr_resolution_scale (archived, 1.25 by default, which is 2160), held to what
the view (maxImageRect) and the swapchain allow; the engine's screen size is
set to match before the swapchains are made. The log has the recommended size,
the largest the view and swapchain take, and what was used. The HUD shift
and the rest work from the real width, so they follow.

vr_refresh_rate (archived, default 90, standard section 9) is asked for
with xrRequestDisplayRefreshRateFB: the highest the runtime offers that isn't
over it. The offered list, the request and the result are logged. The
earlier "now: 90 Hz" at a 13.89 ms frame period was a mismatch in what
was read, not asked for; vr_refresh follows the real frame period.

Frame build only. Builds, haven't run it on the headset yet.
2026-10-05 02:02:49 +09:00
Madison 571fed0ef5 Frame: the menu laser, always on top of the screen
The menu cursor was worked out from the controller's angles, not from
where its ray meets the screen, and no beam was drawn. Now the ray from the
weapon hand's aim pose (aim/pose, -z, the runtime's own pointing ray, not
the grip) is cast against the flat screen quad, and the cursor is where it
lands. A beam from the controller to that point, and a dot where it meets
the panel, are quad layers of their own, submitted after the screen, so
SteamVR draws them over it and nothing in the eye image (or the HUD, or
the world) can cover them. They show only while the screen is up and the
hand is tracked. The beam is a thin quad turned about its axis to face the
eye; the little swapchain behind both is one flat colour.

Frame build only. Builds, haven't had it in the headset yet.
2026-10-04 23:34:56 +09:00
Madison c97235b543 Frame: turn the level HUD the right way, and keep camera-facing sprites level
The first level-HUD commit turned the HUD the same way as the world instead
of against it (found by comparing eye dumps with the HUD on and off, at
roll +-20: the HUD turned 20 degrees the opposite way to the world).
Fixed, and it now follows the world.

Camera-facing sprites (plain sprites, glows, muzzle flashes, the oriented
parallel ones before their own roll) face the view with the head's roll
taken out, so they stay upright in the world when the head tilts. Not in a
scope. vr_hud_level 0 turns both off.
2026-10-04 23:30:55 +09:00
Madison f32023fff2 Frame: vr_hud_level, to turn the level HUD off
1 (the default) keeps the HUD and the text of scripted sequences level
with the world when the head rolls, 0 lets them roll with the head like
they did. Mostly so the difference can be measured, but some people may
want the old way.
2026-10-04 23:01:59 +09:00
Madison 51c2ffd4d5 Frame: keep the HUD and the text of scripted sequences level when the head rolls
The 2D pass is drawn into the eye image, so it rolled with the head. Now
the HUD pass (the HUD, game_text and titles such as the tram credits,
the centre print, the pause icon) is turned the other way by the roll the
world is drawn with, about the optical axis, before the HUD distance shift,
which is along the image because the eyes are. So it follows the head's
yaw and pitch but not its roll.

The screen layer (menus) is a world-locked quad that never rolls with the
head, and a scope is left as it was. The screen fade stays unrotated so it
still covers the whole image.

Frame build only. Builds, haven't had it in the headset yet.
2026-10-04 22:08:22 +09:00
Madison b126446f9c Frame: use Half-Life's own gamepad layout for the controllers
The Frame's controllers had the Quest layout moved around, with the
torch on a bumper, crouch on A and so on. Half-Life's 25th anniversary
update ships its own Steam Input layout for a gamepad, so this follows
that, and adds the VR parts: the grips, and the head for looking.

Playing: left stick moves (click toggles crouch), right stick turns
(Lambda1VR's snap or smooth turn), right stick click is the flashlight,
the weapon hand's trigger fires and the other trigger alt fires, A jump,
B crouch while held, X use, Y reload, bumpers previous and next weapon,
D-pad up and down the same, right is the last weapon and left the
crowbar. Menu pauses and, held for half a second, quick saves. View
taps the flashlight (the scoreboard in multiplayer), held for one
second recentres, three seconds recentres and takes the head height.

Grips are for hands: the weapon hand's grip is use from that hand
(the game already finds what's near the hand and in front of it), or the
crowbar when pressed with the hand behind the head. The other hand's
grip is the two-handed hold when that hand is near the weapon, and use
from that hand when it's well away from it. That's decided when the grip
goes down (hysteresis between 35 and 55 cm), and logged.

In a menu the weapon hand's laser and trigger click, A confirms, B and
View go back, the D-pad and the left stick move, the bumpers page, the
right stick scrolls.

The right stick up flicks the flat screen view and down the laser sight,
as those two have nowhere else to go. Walk (+speed) is gone, the stick is
analog. With the other hand behind the head, the torch and screen view
buttons are quick save and load, as on the Quest.

The map is one table (VrFrameMap.c) and VrInputFrame.c turns it into what
the scheme code reads, so it's easy to change. Double clicking jump for
crouch and automatic use by waving a hand are off on the Frame (saved
settings are updated once); vr_gesture_triggered_use stays on, because
it's what makes the game take use from each hand within a short reach.

Builds. Haven't had it in the headset yet.
2026-10-04 22:06:54 +09:00
Madison 1eac3db3ce Frame: put the HUD at vr_hud_distance
The client DLL pushes every HUD item sideways by width / 36 per eye
(it works that out from vr_stereo_side). On the Frame's lenses that's
about 4.5 degrees an eye, so the HUD sat around 0.4 m away and was
hard on the eyes. The DLL is a submodule, so while it draws the HUD the
eyes are given as the mono ones (which is zero for it), and the whole
HUD pass is shifted here instead, by the pixels that put it
vr_hud_distance metres ahead given that eye's real position and focal
length. That also puts the centre print and the engine's FPS counter at
the same place. Scope mode and the screen layer are left alone.

vr_hud_distance is archived, in metres, and defaults to 1.0. The shift
for each eye is logged once.

Frame build only. Builds, haven't had it in the headset yet.
2026-10-04 21:06:56 +09:00
Madison 02e68f7916 Frame: draw each eye from the runtime's eye pose
The engine moves each eye a fixed 65 mm times vr_worldscale along the
side and draws both with the head's rotation, and the layer was
submitted with the head's pose for both views. On the Frame the runtime
says where each eye is and which way it points, so use that:

- the world is drawn from each eye's position in head space, scaled by
  vr_worldscale, with the eye's turn relative to the head if it has one
- each projection view is submitted with that eye's pose (the head's
  placed in the stage, then the eye's in the head), from the same
  predicted time the render used
- the mono eyes of a scope and the flat screen layer keep drawing from
  one point, as before

The eye maths is in VrEyeMath.c, which has no engine or OpenXR types
so it can be tested on the host. Frame build only.

Builds. Haven't had it in the headset yet.
2026-10-04 21:06:51 +09:00
Madison 6ff4159d3c Frame: log what the runtime says about each eye
Position and rotation of each eye in head space, the fov and the eye
separation, once at the start, and again when the poses first are valid.
Needed to check the stereo against what the engine assumes.
2026-10-04 20:58:15 +09:00
Madison ce8b7f8d6d Frame: set the swapchain attachments with the driver's GL
The first run on the Frame had both eyes' framebuffers come back as
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, and the eyes stayed black. The
plain gl* names in libxash.so are gl4es's (it's linked first), and gl4es
swaps a texture name it hasn't seen for a new empty texture of its own,
so the swapchain image never made it into the attachment. The EXT path
doesn't have the problem since it gets its functions from eglGetProcAddress.
The plain GLES 3 fallback I added does the same now. The framebuffer is
still made and bound through gl4es, because gl4es refuses to bind a
framebuffer it didn't make and the engine draws through it.

Also on the Frame build:
- the engine's messages show up in logcat (tag Xash). They always went
  there, but the default developer level is 0 and the commandline.txt
  from the assets has no -dev, so nothing was printed. The default is
  3 there now, -dev on the commandline still wins.
- vr_refresh comes from the frame period instead of the list of rates
  the runtime offers. SteamVR picks the rate per app, it said 90 on
  offer while the display ran at 72, and the positional movement
  scaling uses vr_refresh.

Builds. Haven't had it in the headset yet.
2026-10-04 19:10:33 +09:00
Madison 5c2769c0ea Set the JavaVM pointer that jni_shutdown uses
jVM is a static that nothing assigns, so jni_shutdown dereferences NULL.
The compiler takes that as can't-happen and drops the code after the
call, so when the game quits (AppThreadFunction after Host_Shutdown and
TBXR_LeaveVR) it runs off the end of the function into whatever comes
next. On the Frame that was a call to a fortified snprintf with garbage
arguments and an abort. On the Quest it probably crashes or does
something random on the way out too, it just doesn't matter there since
the app is closing anyway.

JNI_OnLoad gets the VM, so keep it there.
2026-10-04 19:05:49 +09:00
Madison 247d8be660 Keep the game data in Documents on the Frame
On the Quest the data is in /sdcard/xash. On the Frame that would be gone
whenever the Android container gets reset, so it uses
/sdcard/Documents/Lambda1VR there, which is the headset's own
~/Documents/Lambda1VR. The Java side, the chdir and the engine's
default path all follow it. The Quest path is the same as before.

The container on the Frame has no settings page for the all files
permission, so on that build we just check we can write to the folder
and only ask if we can't (and don't fall over if there's no page).
2026-10-04 18:44:21 +09:00
Madison afe3c6f890 Add Valve Frame controller bindings
On the Frame build the bindings go to the Frame's own profile first
(valve/frame_controller_valve), then Index, then Touch. The Pico profile
isn't tried there. Every profile logs what the runtime said to the
suggestion.

The game's actions stay the same: sticks, triggers, grips, stick clicks
and A/B are where they are on Quest. The Frame has no X/Y on the left
controller, so the torch is the left bumper (or the right X) and the
screen view is View (or the right Y). Menu is the right one and is the
pause. Holding View for a second recentres, three seconds also takes the
head height as the standing height, with a buzz on both controllers.
The left-handed schemes get the same physical buttons.

The layout only kicks in when the runtime says the Frame profile is
the active one, so Index or Touch controllers on SteamVR behave like they
do on Quest. Index has no Menu for apps, so its pause is the left
trackpad pressed hard.

Builds. Haven't tried it in the headset yet.
2026-10-04 18:43:20 +09:00
Madison 9ad98dcf51 Make the OpenXR setup work on SteamVR for the Frame build
SteamVR doesn't offer what the Quest and Pico runtimes do, and the frame
loop assumed they were there. All of this is behind L1VR_STEAM_FRAME, so
the Quest and Pico code paths stay as they were.

- Only OpenGL ES is required. Everything else (android create instance,
  performance settings, refresh rate, the Valve controller extension) is
  enabled when the runtime lists it. The android create instance struct
  only goes in the chain if that extension is on.
- No Pico or Meta calls on the Frame, and no assuming OPENXR_HMD is set.
- The graphics binding gets the EGLConfig instead of the surface.
- The eye framebuffers fall back to plain GLES 3 when there's no
  multisampling, so the runtime doesn't need the EXT extension.
- Render at the recommended size, not 1.3x of it.
- In standby (shouldRender false) the frame goes in with no layers and
  the game waits too. Before, the server kept simulating.
- SteamVR quitting the app (exiting, loss pending) goes through the
  normal shutdown instead of spinning on events.
- A recentre that takes the head's yaw and floor position, for the
  controller code to call. It keeps the view yaw and the world position,
  so nothing jumps.
- Log what's going on with an [openxr] prefix: runtime, extensions,
  swapchain format and size, session state, refresh rates.

Builds. Haven't tried it in the headset yet.
2026-10-04 18:41:58 +09:00
Madison b3058f76f6 Add a Steam Frame build type with the Khronos OpenXR loader
The Frame runs Android apps in a container (Lepton) that has no OpenXR
loader broker, so the APK needs its own loader. This adds a "frame" build
type: ./gradlew assembleFrame.

It's release plus:
- applicationId .frame, minSdk 30 (the container is Android 11)
- a manifest overlay that drops the headtracking requirement and the
  Quest/Pico metadata, since the install fails with it
- the Khronos loader 1.1.63, fetched from Maven Central at build time,
  checked against a SHA-256 and unpacked into build/ (not committed)
- L1VR_STEAM_FRAME for the native code, BuildConfig.STEAM_FRAME for Java

Quest builds don't see any of this. I also added the gradle wrapper script
to Projects/Android, since the wrapper jar and properties were already
there, and -Wall for the VR sources.

Haven't run it on the headset yet.
2026-10-04 18:36:18 +09:00
DrBeef 99a0d62ebb Fix black edges + prevent accidental weapon switching 2026-09-02 22:52:05 +01:00
DrBeef 8fa5e6b276 Use our updated background 2026-01-07 19:03:32 +00:00
DrBeef 3e3572d89e Multiple fixes to improve QoL
- Prevent the highest IPD setting on Quest 2 and Quest 3s from making the view move with the controller
- Smooth Turn menu option (so user doesn't have to faff with setting a low value)
- Gesture Trigger Use is now enabled by default
- Fixed positional not working at higher refresh rates
- Improvements to the use of the pointer in the menu screen (also seems to fix scroll bars)
- Improved menu item layout
- change to sv_game.c which seems to stop the menu hanging on Start New Game sometimes
- Adjusted draw location of hands
- Don't draw offhand backwards if "Reverse Torch Direction" is enabled
2026-01-05 11:42:10 +00:00
DrBeef d6d9ee4d08 Updated Android API level to 32
improved permission handling request
new crowbar pointer sprite
2025-10-16 10:07:17 +01:00
DrBeef 482bd03fd1 few minor fixes
- permissions bug
- updated gradle configuration
- fix missing mouse cursor
2025-10-15 22:28:55 +01:00
Simon 6fe7d75c0c Option to reverse the direction of the flashlight 2023-07-01 15:46:59 +01:00
Simon 20f282c161 Fix incorrect movement speed on the diagonal (Pico) 2023-05-29 14:16:13 +01:00
Simon c0b09aab2d Ensure menu displayed in front of player on start-up 2023-05-01 17:38:42 +01:00
Simon 1281fe7e4c Updated SDK for gesture use (thanks @petr666 ) 2023-05-01 17:28:40 +01:00
Simon cb3000f57f Implement clean shutdown
- increase version number to 1.6.1
2023-05-01 09:57:09 +01:00
Simon ae567ebd9b Fix crash on quest firmware v53 2023-04-28 19:47:17 +01:00
Simonandpetr666 5352b02167 Extra changes required for use gesture
Co-Authored-By: petr666 <5103207+petr666@users.noreply.github.com>
2023-03-17 21:33:19 +00:00
Simon 1724d3e3ca Merge branch 'feature/use-gesture' of https://github.com/petr666/Lambda1VR into petr666-feature/use-gesture 2023-03-17 21:28:34 +00:00
SimonandIgor Valjevic 6ace772cd3 @MuadDib's Use Gesture
Co-Authored-By: Igor Valjevic <177352+MuadDib@users.noreply.github.com>
2023-03-17 20:32:47 +00:00
Simon 425a209319 Renamed cvar 2023-03-17 20:19:47 +00:00
Simon 385af10513 Fix issues with Op Force 2023-03-17 20:04:26 +00:00
Simon 2aa7dfddc5 Final bits for OpenXR support
fixed projection
2023-03-16 22:05:05 +00:00
Simon 1f60a9d2c4 Starting to get OpenXR working
Runs on the Pico, but has issues (no headtracking)
2023-03-15 23:48:41 +00:00
Petr Bartos aecb3eeb67 Use gesture 2023-02-24 19:47:22 +01:00
Simon 8d3b6851fa Update to blushift
allow blue HUD
2022-11-04 23:52:06 +00:00
Simon bcd365ebab Merge branch 'master' of https://github.com/DrBeef/Lambda1VR 2022-09-20 20:40:14 +01:00
Simon 079e67a0fa Crowbar..
Made the corwbar a little "longer" and requires lower velocity to trigger a hit.. should be easier to hit headcrabs with
2022-09-20 20:40:11 +01:00
Grant Bagwell 8200a40bcb Fix issue when people have their menu buttons switched 2022-09-20 19:47:23 +02:00
Simon 50d94ce705 Couple of extra changes...
- Reverse direction primary thumbstick cycles weapons
- Default ladder climbing to use HMD angles, rather than off-hand
2022-09-09 19:34:58 +01:00