Drive with the left stick, or the wheel, and fly the Banshee by hand (headset-unverified)

The 2026-10-04 wear found driving with two sticks bad; from the driving
mockup the user chose options A, C and D:

- vr.vehicle_steering "drive" (the new default): the left stick drives
  the Warthog and the Ghost. Sideways turns the driver's facing from the
  vehicle's heading in proportion to the wheels' limit, which Halo steers
  toward; forward and back stay gas and reverse; no strafing. Reversing
  mirrors the angle, so the wheels follow the stick.
- "wheel": as drive, and in a Warthog with both grips held the hands turn
  the wheel and the triggers are the pedals.
- With either, the Banshee flies where the hand points and the Scorpion
  keeps the game's driving, its cannon aimed by the head.

vehicle_vr_steering (vehicles.c) gives the vehicle's type and steering
limit; vr_drive_input (vr_frame.c) the stick or the wheel and pedals;
vr_render_drive_throttle the throttle. Settings version 4 moves the old
default "stick" to "drive"; the VR settings' STEERING offers DRIVE,
WHEEL, STICK and HAND.
This commit is contained in:
Madison committed 2026-10-04 15:00:09 +09:00
1 parent 165fb242ab
commit 69b763561c
9 files changed
+270 -22

No files matched your search

+18 -4
View File
@@ -16,7 +16,8 @@ builds are unchanged: everything here is behind `configure.py --vr` (`HALO_VR`).
| Hand-aimed weapons | Default (`vr.aim = "hand"`); verified unattended with synthetic hands. **Not yet played.** |
| Arm IK | Default (`vr.arms = "ik"`); verified unattended. **Not yet played.** |
| 3D cutscenes on a screen, fades | Default (`vr.cinema_3d`); verified unattended. **Not yet seen worn.** |
| Vehicles: first-person seats, stick steering | Built, **not yet run on the device** (the headset went offline mid-test). `vr.vehicle_view` "first_person" (default) or "chase"; `vr.vehicle_steering` "stick" (default), "head" or "hand". |
| Vehicles: first-person seats | Worn 2026-10-04 (the Warthog bounced; code-ea's eased seat view since). `vr.vehicle_view` "first_person" (default) or "chase" |
| Driving: `vr.vehicle_steering` "drive" (default: the left stick steers in proportion, gas and reverse on it too), "wheel" (both grips on the Warthog's wheel, triggers as pedals), the Banshee flying where the hand points; "stick", "head", "hand" kept | Built after the 2026-10-04 wear (two-stick driving found bad; the user chose options A, C and D of the driving mockup). **Not yet worn** |
| VR-native controls, gestures, left-handed play | Built (`vr.controls = "vr"`, default; `"pad"` keeps the Xbox mapping). **Not yet run on the device.** |
| Aim smoothing when zoomed, haptics | Built, **not yet run on the device.** |
| Picture-in-picture scope | Built (`vr.scope`), **not yet run on the device.** |
@@ -418,8 +419,21 @@ Diagnostics:
camera marker is the chase camera's place, so it isn't used.
- The view turns with the vehicle's yaw only, keeping the horizon level. A passenger seat
facing aside (the Pelican's) keeps the turn it had when you sat down.
- The driver's right stick steers as in flat Halo, and your head only looks. Gunner seats aim
with your head, and the handheld weapon is hidden in driver and gunner seats.
- **Driving** (`vr.vehicle_steering`, chosen from the 2026-10-04 driving mockup):
- "drive" (default, option A): the left stick drives the Warthog and the Ghost. Sideways
steers in proportion (the driver's facing is the vehicle's heading turned by the stick times
the wheels' limit, and Halo turns the wheels toward the facing); forward and back are gas
and reverse; the Ghost no longer strafes. Reversing, the angle is mirrored so the wheels
follow the stick as a car's do. The head looks freely, and the right stick does nothing.
- "wheel" (option C): as drive, and in a Warthog with both grips held the hands turn the
wheel (the tilt of the line between them across the head's heading; a quarter turn is
full lock) while the triggers are the pedals (right gas, left brake and reverse).
- With either, a flier (the Banshee) flies where the hand points (option D), and the
Scorpion keeps the game's driving with its cannon aimed by the head.
- "stick" (the right stick steers, as in flat Halo), "head" and "hand" remain, for every
vehicle. Settings version 4 moves the old default "stick" to "drive".
- Gunner seats aim with your head, and the handheld weapon is hidden in driver and gunner
seats.
- `vr.diag_drive_seconds` seats you as a driver for unattended tests; spawn a vehicle first
with `cheat_all_vehicles`.
- Untested on the device.
@@ -455,7 +469,7 @@ the Frame's display at 72 Hz, which Halo held before.
| 3 | General jitter, pop-in at the edges | Explained by the half rate above; no fix yet beyond 72 Hz |
| 4 | The menu jitters up and down when opened | Menus are on a world-locked panel. **Not yet worn** |
| 5 | Getting into the Warthog is unsmooth | code-ea (the eased seat view) |
| 6 | Steering with one stick and gas on the other | Options to choose from (mockup); nothing built |
| 6 | Steering with one stick and gas on the other | The user chose A, C and D: built as `vr.vehicle_steering` "drive" (the default), "wheel", and the Banshee flying by hand with either. **Not yet worn** |
| 7 | Holding X went to third person | Not explained by the log, and the director keeps first person in seats with `vr.vehicle_view` "first_person". Physical X sends Black (switch grenades) and Halo's prompts say "X" for Action: a face-button option is in the same mockup. Turn on `vr.diag_buttons` for the next wear to catch it |
| 8 | No laser in menus | A beam and a dot over the panel. **Not yet worn** |
| 9 | QUIT TO STEAM in the VR settings | Removed |
+2 -2
View File
@@ -151,8 +151,8 @@ static struct vr_menu_setting
{ "ROOM-SCALE", "vr.roomscale", _vr_setting_boolean, 2, 0, { { "OFF", "false" }, { "ON", "true" } } },
{ "SCOPE", "vr.scope", _vr_setting_boolean, 2, 0, { { "ON", "true" }, { "OFF", "false" } } },
{ "VEHICLES", "vr.vehicle_view", _vr_setting_string, 2, 0, { { "INSIDE", "first_person" }, { "CHASE", "chase" } } },
{ "STEERING", "vr.vehicle_steering", _vr_setting_string, 3, 0,
{ { "STICK", "stick" }, { "HEAD", "head" }, { "HAND", "hand" } } },
{ "STEERING", "vr.vehicle_steering", _vr_setting_string, 4, 0,
{ { "DRIVE", "drive" }, { "WHEEL", "wheel" }, { "STICK", "stick" }, { "HAND", "hand" } } },
{ "CUTSCENES", "vr.cinema_3d", _vr_setting_boolean, 2, 0, { { "3D", "true" }, { "FLAT", "false" } } },
{ "MORE...", NULL, _vr_setting_page, 1, 0, { { "", "" } } },
/* the second page: the HUD and the body */
+126 -4
View File
@@ -94,6 +94,13 @@ enum
_vr_steering_stick,
_vr_steering_head,
_vr_steering_hand,
/* the left stick drives (the default): sideways steers in proportion,
forward and back are gas and reverse; a flier flies where the hand
points */
_vr_steering_drive,
/* as drive, and with both grips held the Warthog's wheel turns with the
hands, the triggers its pedals */
_vr_steering_wheel,
};
static boolean vr_first_person_vehicles(
@@ -117,15 +124,112 @@ static int vr_vehicle_steering(
if (steering < 0 || generation != vr_settings_generation())
{
generation = vr_settings_generation();
char const *setting = config_string("vr.vehicle_steering");
generation = vr_settings_generation();
steering = !strcmp(setting, "head") ? _vr_steering_head :
!strcmp(setting, "hand") ? _vr_steering_hand : _vr_steering_stick;
!strcmp(setting, "hand") ? _vr_steering_hand :
!strcmp(setting, "stick") ? _vr_steering_stick :
!strcmp(setting, "wheel") ? _vr_steering_wheel : _vr_steering_drive;
}
return steering;
}
static real vr_yaw(real_vector3d const *forward);
/* source/units/vehicles.c: the vehicle's type and its wheels' steering
limit (degrees) */
short vehicle_vr_steering(long vehicle_index, real *limit_degrees);
/* the vehicle types (vehicles.c) */
enum
{
_vr_vehicle_tank = 0,
_vr_vehicle_jeep,
_vr_vehicle_boat,
_vr_vehicle_plane,
_vr_vehicle_scout,
_vr_vehicle_fighter,
_vr_vehicle_turret,
};
/* how a driver is steered this frame (vr.vehicle_steering and the vehicle):
drive and wheel apply to the Warthog and the Ghost; a flier (the Banshee)
flies where the hand points; the Scorpion keeps the game's own driving, its
cannon aimed by the head */
static int vr_driver_steering(
void)
{
int steering = vr_vehicle_steering();
real limit;
short type;
if (!vr_render.seat.seated || !vr_render.seat.driver ||
(steering != _vr_steering_drive && steering != _vr_steering_wheel) ||
!TEST_FLAG(_object_mask_vehicle, object_get(vr_render.seat.vehicle_index)->object.type))
{
return steering;
}
type = vehicle_vr_steering(vr_render.seat.vehicle_index, &limit);
if (type == _vr_vehicle_fighter || type == _vr_vehicle_plane)
return _vr_steering_hand;
if (type == _vr_vehicle_tank)
return _vr_steering_head;
if (steering == _vr_steering_wheel && type != _vr_vehicle_jeep)
return _vr_steering_drive;
return steering;
}
/* drive and wheel: the facing the vehicle steers toward, its heading turned
by the steering in proportion to the wheels' limit (Halo turns a vehicle's
wheels toward its driver's facing, within that limit). Reversing, Halo turns
the wheels the other way; so does this, so that they follow the stick as a
car's do. */
static boolean vr_drive_facing(
int steering,
real_vector3d *facing)
{
struct object_datum *vehicle = object_get(vr_render.seat.vehicle_index);
float steer, pedals;
int wheel;
real limit, angle, yaw;
if (!vr_drive_input(steering == _vr_steering_wheel, &steer, &pedals, &wheel))
return FALSE;
vehicle_vr_steering(vr_render.seat.vehicle_index, &limit);
/* (a Ghost turns toward its facing as fast as it is off it: a little
more reach) */
if (limit < 20.0f)
limit = 45.0f;
angle = steer * limit * (_pi / 180.0f);
if (dot_product3d(&vehicle->object.forward, &vehicle->object.translational_velocity) < -0.001f)
angle = -angle;
yaw = vr_yaw(&vehicle->object.forward) - angle;
facing->i = (real)cos(yaw);
facing->j = (real)sin(yaw);
facing->k = 0.0f;
return TRUE;
}
void vr_render_drive_throttle(
short local_player_index,
real_vector2d *throttle)
{
int steering;
float steer, pedals;
int wheel;
if (local_player_index != local_player_get_next(NONE) || !vr_active() || cinematic_in_progress())
return;
steering = vr_driver_steering();
if (steering != _vr_steering_drive && steering != _vr_steering_wheel)
return;
/* the stick's sideways steers rather than strafes (a Ghost's) */
throttle->j = 0.0f;
if (vr_drive_input(steering == _vr_steering_wheel, &steer, &pedals, &wheel) && wheel)
throttle->i = pedals;
}
static real vr_yaw(
real_vector3d const *forward)
{
@@ -832,7 +936,7 @@ void vr_player_control_facing(
angles = player_control_get_facing_angles(local_player_index);
/* a driver steered by the stick (vr.vehicle_steering "stick"): the game
takes the right stick as it would, and the head only looks */
if (seated && vr_render.seat.driver && vr_vehicle_steering() == _vr_steering_stick)
if (seated && vr_render.seat.driver && vr_driver_steering() == _vr_steering_stick)
{
vr_render.hand_origin_valid = FALSE;
return;
@@ -840,14 +944,32 @@ void vr_player_control_facing(
{
/* the hand aims on foot, and a driver's seat steered by it; the head
in any other seat (a turret aims where the head looks) */
boolean hand_may_aim = !seated || (vr_render.seat.driver && vr_vehicle_steering() == _vr_steering_hand);
static char const *const steering_names[] = { "stick", "head", "hand", "drive", "wheel" };
static int steering_logged = NONE;
int steering = seated && vr_render.seat.driver ? vr_driver_steering() : NONE;
boolean hand_may_aim = !seated || steering == _vr_steering_hand;
real heading = vr_render.seat.heading;
if (steering != steering_logged)
{
if (steering != NONE)
platform_log("vr: driving %s, steered by %s", tag_get_name(object_get(vr_render.seat.vehicle_index)->definition_index),
steering_names[steering]);
steering_logged = steering;
}
if (!vr_aim(angles->yaw, seated, hand_may_aim, vr_seat_view() ? &heading : NULL, forward.n))
{
vr_render.hand_origin_valid = FALSE;
return;
}
/* driving with the left stick (or the wheel): the vehicle steers
toward its heading turned by it; the head only looks */
if ((steering == _vr_steering_drive || steering == _vr_steering_wheel) && !vr_drive_facing(steering, &forward))
{
vr_render.hand_origin_valid = FALSE;
return;
}
}
player_control_set_facing(local_player_index, &forward);
+4
View File
@@ -116,6 +116,10 @@ int vr_render_unzoomed_view(void);
(`position`, `height`, `width` as biped_get_physics_pill gives them) by
where the head walked this tick, as far as the world lets it */
void vr_render_room_scale(long biped_index, union real_point3d *position, real height, real width);
/* handle_one_player_input: a driver's throttle with vr.vehicle_steering
"drive" or "wheel" (no strafing; with the hands on the wheel, the triggers) */
union real_vector2d;
void vr_render_drive_throttle(short local_player_index, union real_vector2d *throttle);
/* motion_sensor_update: the yaw (radians) the first local player's motion
sensor turns with, the head's, not the hand's aim; 0 to leave the
facing's */
+36 -9
View File
@@ -267,7 +267,7 @@ static const struct config_setting config_settings[] =
{ "vr.enabled", _config_boolean, "true", "HALO_VR", _environment_value, _platform_vr,
"Play in the headset (OpenXR); false shows the game on the flat screen\n"
"as the phone build does." },
{ "vr.settings_version", _config_integer, "3", "HALO_VR_SETTINGS_VERSION", _environment_value, _platform_vr,
{ "vr.settings_version", _config_integer, "4", "HALO_VR_SETTINGS_VERSION", _environment_value, _platform_vr,
"The version of these settings' meaning, kept by the game to bring an\n"
"older file up to date (1: vr.hud_distance and vr.hud_width default to 0,\n"
"the HUD mode's own; 2: vr.turn_mode and vr.snap_angle replace\n"
@@ -340,10 +340,16 @@ static const struct config_setting config_settings[] =
{ "vr.vehicle_view", _config_string, "\"first_person\"", "HALO_VR_VEHICLE_VIEW", _environment_value, _platform_vr,
"Vehicles seen from the seat, turning with the vehicle and the horizon\n"
"kept level (\"first_person\"), or from the game's chase camera (\"chase\")." },
{ "vr.vehicle_steering", _config_string, "\"stick\"", "HALO_VR_VEHICLE_STEERING", _environment_value, _platform_vr,
"What a driver steers with: \"stick\" (the right stick, as on a flat\n"
"screen; the head only looks), \"head\" (where you look) or \"hand\" (where\n"
"the right controller points). Gunners aim with the head." },
{ "vr.vehicle_steering", _config_string, "\"drive\"", "HALO_VR_VEHICLE_STEERING", _environment_value, _platform_vr,
"What a driver steers with. \"drive\": the left stick, sideways steering\n"
"in proportion and forward and back the gas and reverse, the head free to\n"
"look. \"wheel\": as drive, and in a Warthog with both grips held your\n"
"hands turn the wheel and the triggers are gas (right) and brake (left).\n"
"With either, the Banshee flies where your hand points and the Scorpion\n"
"drives as in the game, its cannon aimed by the head. \"stick\" (the right\n"
"stick steers, as on a flat screen), \"head\" (where you look) and\n"
"\"hand\" (where the right controller points) apply to every vehicle.\n"
"Gunners aim with the head." },
{ "vr.arms", _config_string, "\"ik\"", "HALO_VR_ARMS", _environment_value, _platform_vr,
"With vr.aim \"hand\": the first-person arms. \"ik\" reaches them from the\n"
"shoulders to the hands (the left to the left controller, or to the gun\n"
@@ -990,7 +996,15 @@ static void config_clamp_ranges(void)
/* ---------- versions */
/* the meaning of the VR settings now (vr.settings_version) */
#define CONFIG_VR_SETTINGS_VERSION 3
#define CONFIG_VR_SETTINGS_VERSION 4
/* a string setting's value in memory, "" for none */
static const char *config_string_value(const char *name)
{
long index = config_setting_index(name);
return index >= 0 && config_values[index].string ? config_values[index].string : "";
}
/* sets a setting's value in memory from text */
static void config_set_value(const char *name, const char *text)
@@ -1016,7 +1030,9 @@ out. A file with no vr.snap_turn gets the defaults (added with the rest).
2 to 3: vr.resolution_scale at the old default 1.5 becomes the new 1.25: the
eyes' images are now drawn at the render size (no longer shrunk to the
recommended 1728), so 1.25 (2160, about the Frame's panels) is sharper than
the old 1.5 was, and leaves outdoor levels the frame time 90 Hz needs. */
the old 1.5 was, and leaves outdoor levels the frame time 90 Hz needs.
3 to 4: vr.vehicle_steering at the old default "stick" becomes the new
"drive" (the left stick drives; the two-stick driving was found bad worn). */
static char *config_migrate(const char *text, toml_datum_t table)
{
static const struct { const char *name; double old_default; } hud[] =
@@ -1122,8 +1138,19 @@ static char *config_migrate(const char *text, toml_datum_t table)
}
}
}
config_set_value("vr.settings_version", "3");
edited = config_edit_text(result ? result : text, "vr.settings_version", "3");
if (found < 4 && !strcmp(config_string_value("vr.vehicle_steering"), "stick"))
{
edited = config_edit_text(result ? result : text, "vr.vehicle_steering", "\"drive\"");
if (edited)
{
free(result);
result = edited;
config_set_value("vr.vehicle_steering", "drive");
platform_log("settings: vr.vehicle_steering was the old default \"stick\": now \"drive\"");
}
}
config_set_value("vr.settings_version", "4");
edited = config_edit_text(result ? result : text, "vr.settings_version", "4");
if (edited)
{
free(result);
+9
View File
@@ -162,6 +162,15 @@ unsigned int vr_take_actions(void);
int vr_weapon_hand(void);
/* 1 while the gun is held in both hands */
int vr_two_handed(void);
/* ---------- driving (vr.vehicle_steering "drive" and "wheel") */
/* this frame's driving from the hands: `steer` -1 (left) to 1 (right) from
the left stick's sideways, or with `allow_wheel` and both grips held (the
hands on the wheel) from the turn of the line between them; then `wheel` is
1 and `pedals` the triggers, -1 (left: brake, reverse) to 1 (right: gas).
0 without a tracked frame */
int vr_drive_input(int allow_wheel, float *steer, float *pedals, int *wheel);
/* ---------- room-scale (vr.roomscale)
Walking about the room walks the player: each tick on foot the player is
+57 -3
View File
@@ -1197,6 +1197,7 @@ static void synthesize_views(void)
{
static const float fov[2][4] = { { -1.028f, 0.885f, 0.864f, -1.048f }, { -0.885f, 1.028f, 0.864f, -1.048f } };
float yaw[4], look[4];
float walk[3] = { 0.0f, 0.0f, 0.0f };
int eye;
if (vr.sweep_count > 0)
@@ -1208,9 +1209,6 @@ static void synthesize_views(void)
yaw[1] = sinf(vr.diag_yaw * 0.5f);
yaw[2] = 0.0f;
yaw[3] = cosf(vr.diag_yaw * 0.5f);
float walk[3] = { 0.0f, 0.0f, 0.0f };
/* vr.diag_walk_speed: the head walking ahead, metres a second */
if (vr.diag_walk_speed != 0.0f)
{
@@ -1391,6 +1389,62 @@ static void head_offset(float out[3])
out[2] = head[2] * scale;
}
/* ---------- driving */
/* the left stick's dead zone, the rest of its travel stretched to 0..1 */
#define DRIVE_STICK_DEAD_ZONE 0.15f
/* the wheel: the hands this far apart at least; a quarter turn is full lock */
#define DRIVE_WHEEL_MINIMUM_SPAN 0.15f
#define DRIVE_WHEEL_FULL_LOCK 1.5707964f
int vr_drive_input(int allow_wheel, float *steer, float *pedals, int *wheel)
{
float x = vr.frame.thumb[0];
*steer = 0.0f;
*pedals = 0.0f;
*wheel = 0;
if (!vr.active || !(vr.frame.flags & HALO_XR_FRAME_FOCUSED))
return 0;
if (allow_wheel && vr.grip_held[0] && vr.grip_held[1] &&
(vr.frame.hand_valid[0] & 1) && (vr.frame.hand_valid[1] & 1))
{
static const float xr_forward[3] = { 0.0f, 0.0f, -1.0f };
float forward[3], right[2], across[3], horizontal, sideways, angle;
int axis;
/* the line from the left hand to the right, seen from the driver's
seat: level is straight ahead, the right hand lower is a right
turn (measured across the head's heading, so leaning doesn't steer) */
rotate(vr.frame.head.orientation, xr_forward, forward);
horizontal = sqrtf(forward[0] * forward[0] + forward[2] * forward[2]);
if (horizontal > 1e-3f)
{
right[0] = -forward[2] / horizontal;
right[1] = forward[0] / horizontal;
for (axis = 0; axis < 3; axis++)
across[axis] = vr.frame.grip[1].position[axis] - vr.frame.grip[0].position[axis];
sideways = across[0] * right[0] + across[2] * right[1];
if (sideways * sideways + across[1] * across[1] > DRIVE_WHEEL_MINIMUM_SPAN * DRIVE_WHEEL_MINIMUM_SPAN)
{
angle = atan2f(-across[1], sideways);
*steer = angle / DRIVE_WHEEL_FULL_LOCK;
if (*steer > 1.0f) *steer = 1.0f;
if (*steer < -1.0f) *steer = -1.0f;
*pedals = vr.frame.trigger[1] - vr.frame.trigger[0];
*wheel = 1;
return 1;
}
}
}
if (fabsf(x) < DRIVE_STICK_DEAD_ZONE)
x = 0.0f;
else
x = (x - (x > 0.0f ? DRIVE_STICK_DEAD_ZONE : -DRIVE_STICK_DEAD_ZONE)) / (1.0f - DRIVE_STICK_DEAD_ZONE);
*steer = x;
return 1;
}
/* ---------- room-scale (vr.roomscale), after the PC mod HaloCEVR's: each
tick the player is moved by how far the head walked from where they stand */
+1
View File
@@ -629,6 +629,7 @@ static void handle_one_player_input(
{
unsigned long actions = vr_render_actions(local_player_index);
vr_render_drive_throttle(local_player_index, &input.throttle);
if (actions & VR_RENDER_ACTION_MELEE)
SET_FLAG(input.unit_control_flags, _unit_control_use_equipment_bit, TRUE);
if (actions & VR_RENDER_ACTION_FLASHLIGHT)
+17
View File
@@ -2707,3 +2707,20 @@ animate:
return TRUE;
}
#ifdef HALO_VR
/* port (port/linux/game/vr_render.c, the headset's driving): the vehicle's
type (_vehicle_type_*) and how far its wheels steer, in degrees */
short vehicle_vr_steering(
long vehicle_index,
real *limit_degrees)
{
struct vehicle_definition *definition =
vehicle_specific_definition_get(object_get(vehicle_index)->definition_index);
real high = (real)fabs(definition->unknown308);
real low = (real)fabs(definition->unknown30c);
*limit_degrees = high > low ? high : low;
return definition->vehicle_type;
}
#endif