Revert of c2aceb64: on the Quest 3 the tap did not toggle the flashlight.
The flashlight's button stays; the on-foot vignette that came after it is
kept. The roadmap and README no longer name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The product no longer carries Halo in its name; the game it plays is named
only to say which disc image it needs.
- The app: "Ringfall: Mjolnir VR", application id
io.github.cesarus85.ringfall (the Java classes keep the decompilation's
package), its update provider's authority from the id; the launcher's,
updater's, host's and VR options' texts; the OpenXR application name.
- New icon and Steam library pictures, a tilted, broken ring with a falling
streak (tools/ringfall_art.py, from shapes and Overpass; the helmet and
the HALO logo gone).
- The Steam Frame: ~/Documents/RingfallMjolnirVR, RingfallMjolnirVR.apk,
ringfall-mjolnir-vr.sh, Install-Ringfall-Mjolnir-VR.desktop and
ringfall-mjolnir-vr-steam-frame.zip; the installer renames the folder of
the builds before (~/Documents/HaloCE) and replaces their "Halo VR"
shortcut and pictures.
- Releases: only the Android builds, as ringfall-mjolnir-vr-android-*.zip
(the updater's names), and the Frame's installer; the repository
Cesarus85/ringfall-mjolnir-vr.
- The signing key's default alias ringfall.
- README.md is the project's own; the port's moved to docs/OPENCE_README.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The VR options get a fourth page, COMFORT (PLAY, COMFORT, BUTTONS, MORE;
the tabs beside the title share its width): Turning, Snap turn, Smooth
turn speed and the vehicle vignette move there from PLAY, joined by the
on-foot vignette and its strength. PLAY keeps the hands, laser,
crosshair, motion tracker, body, fingers and the Warthog's wheel.
- Snap turns step through 10 to 45 degrees by 5, then 60, 75 and 90 (were
30, 45, 60, 90); smooth turning through 30 to 150 degrees a second by
15, then to 300 by 30 (were 60 to 180 by 30); the vehicle vignette
through Off, 15, 25, 35, 50, 60, 70, 85 and 100% (were Off, Light 35%,
Medium 70%, Strong). The old values are among the steps; the defaults
(snap 30, 120 a second, vignette 70%) are unchanged.
- vr.foot_vignette ("off" default, "turning", "moving") and
vr.foot_vignette_strength (0.7): on foot the eye images darken from
their edges while the turning stick turns smoothly and, with "moving",
while the move stick moves, as far as the sticks are pushed beyond
their dead zone (fully at three quarters), eased in and out as the
vehicle vignette is. Not in a vehicle's seat (whether or not its view
is first person), not in the menus; snap turns have none. The stronger
of the two vignettes is shown. Off by default: nothing changes.
Checked in the emulator (debug.vr_options): the four tabs and the rows
without overlaps, each row's steps, the values kept over a restart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Either hand brought to the side or the top of the head and away again, as
a tap, toggles the flashlight (vr.flashlight_gesture, default true; a row
on the VR options' BUTTONS page); the flashlight's button keeps working.
After HaloCE-Quest-VR (CC0), whose off hand near the head's middle
toggles it, made stricter against false toggles:
- The zone is in the head's axes: beside the head (7 to 24 cm out, from
10 cm below the eyes to 18 cm above) or over it (within 12 cm of its
middle line, 8 to 30 cm up), never more than 6 cm in front of the eyes,
so a scope held to the eye, a two-handed weapon, a punch and the wrist's
motion tracker, all in front of the face, are outside it.
- A tap: the hand comes from 35 cm or more from the head's middle, enters
slower than 3 m/s (a throw past the ear is faster), stays 40 to 600 ms
(adjusting the headset takes longer), alone (both hands at the head is
the headset adjusted) and leaves 4 cm past the zone's edges; toggles are
at least a second apart.
- None while the menus are up, while recentring, while a grenade is held
or for 0.6 s after one is thrown, within 0.7 s of a melee swing, with
both hands on the weapon, with the weapon hand while the scope is drawn,
or with a hand on a Warthog's wheel; such a hand must leave the head
before it taps.
- A toggle presses the flashlight's button (white) for 100 ms, as B's tap
does, and gives the tapping hand a short rumble.
The tap's state machine was run off the headset in a small harness over
taps beside and over the head, a turned head, a second tap within the
cooldown, a long dwell, both hands, a scope at the eye, the scope flag, a
fast throw past the ear, a held grenade and a two-handed grip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A BUTTONS page in the VR options (between PLAY and MORE) puts jump, the
flashlight (held: switch grenades), action/reload, switch weapon, crouch,
zoom and melee on any of seven buttons: A, B, X, Y, the move and turning
sticks' clicks and the weapon hand's grip. Moving an action to a button
swaps it with the action there, so every action always has a button;
"Reset buttons to defaults" puts them back. The triggers, the free hand's
grip (both hands on the weapon), the menu button, the Steam Frame's
bumpers, D-pad and View button keep what they do, and the menus keep
their buttons.
Settings vr.button_jump, _light, _action, _weapon, _crouch, _zoom and
_melee ("a", "b", "x", "y", "move_stick", "turn_stick", "grip"; the
defaults are today's layout), named as right-handed. vr.c reads them with
the live settings and, after the left-handed mirroring
(controllers_mirrored), reads each action's button and hands it on as the
button that action has by default (controls_remapped), so xinput_sdl.c's
table is unchanged and the defaults change nothing. Because the mapping
sits after the mirroring it works the same on the Quest's Touch and the
Steam Frame's controllers (mirrored on the Frame the left D-pad is A, B,
X, Y), and a mirrored player's swaps are the mirror image of the
right-handed ones. The guest/host ABI is unchanged.
- The melee's aim and the weapon's jab (melee_update) follow the melee's
button, not the grip as such; the grips holding a Warthog's wheel press
nothing whatever action the grip has (moved from xinput_sdl.c).
- The controller buttons page (now on the BUTTONS page) shows each action
under the controller and name of its button, for both layouts and the
mirrored hands; its ON FOOT tab no longer draws the MENUS tab's notes
over its own.
- debug.vr_options (Android, false): the VR options panel off the
headset, for checking its pages in the emulator.
Checked in the emulator (debug.vr_options): the rows, swaps, reset, the
controls page right- and left-handed, the settings kept over a restart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mirrored first-person model leaves its geometry mirrored; a numeric
display drawn mirrored (rasterizer_set_model_skinning's winding flag) now
shows each digit's bitmap turned over in u (1-u on the first map's texture
transform) and swaps the digits' order across the display (the quad of the
n-th digit from the right shows the n-th from the left; digit count from
the counter limit, the compass's single digit unchanged). Only for 2d,
non-screen-space first maps of numeric chicago/generic shaders, only when
mirrored: right-handed and flat output unchanged.
debug.mirror_first_person (Android, default false): off the headset the
first-person weapon is mirrored across the camera's forward/up plane as
the left hand's is in VR, for checking such things in the emulator.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Walking up from a node to the display through parent_node_index never
ended where the root names itself as its parent: the game hung loading a
level left-handed (Steam Frame). Each step now goes only to a parent
before its child, as vr_skeleton_build does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its node ("... display") and what hangs from it are mirrored back about
the display's own centre after the pose is mirrored: it stays where the
mirrored gun has it, reads as in the right hand, and its triangles keep
their winding (rasterizer_xbox.c decides per skinned part). After
HaloCE-Quest-VR (CC0).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The panel's second page (MORE) gets "Graphics" (vr.graphics: Low,
Medium, High, Max), "Refresh rate" and "Resolution" rows, with a note
of what they do. The refresh rate steps through the rates the runtime
offers (XR_FB_display_refresh_rate: host_xr_refresh_rates, new host
imports with host_xr_set_refresh_rate) and changes at once (vr.c asks
for it when vr.refresh_rate changes); where the runtime offers none
(SteamVR on the Steam Frame, whose rate is SteamOS's) the row shows the
rate it runs at, from its frames' period, and stays as it is. The
resolution (vr.resolution_scale, 70 to 120%) is read as the app starts:
the row says when a restart is needed. vr.log_performance's late frames
follow the rate as it changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The VR options panel gets a "Weapon hand" row (vr.handedness) and a
"Mirrored controls" row (vr.mirror_controls, dim when right-handed). Its
rows now go on two pages with tabs beside the title (PLAY, MORE; up and
down run on across them, a tab clicked shows its page). The controller
buttons page is built from the hands' jobs: the weapon hand's trigger
and grip, the free hand's, the move and turn sticks and the face
buttons (Quest X/Y for A/B; Steam Frame D-pad for A, B, X, Y) on the
sides the settings put them; the wrist, wheel and aiming notes name the
hands. network_vr.c's notes name the weapon and the free hand.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vr.handedness = "left" puts the weapon in the left hand: aim, muzzle,
laser, crosshair, scope and turret aim follow the left controller; the
grenade, the foregrip and the fingers' closing round a grenade move to
the right hand, the grip's melee to the left. The first-person model is
posed as the right hand's in a mirror image of the room ("model space":
vr.c mirrors the controllers into it, first_person_weapons.c the body)
and the posed skeleton mirrored back, after HaloCE-Quest-VR (CC0); a
model whose node matrices mirror has its triangles' winding turned over
(rasterizer_set_model_skinning -> d3d8_gl.c, ended with the model and
each transparent group). halo_vr_left_hand is now halo_vr_free_hand.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The elbow could not be lifted out to the side: it bent only towards the
fixed pole (down, out, back). An elbow lifted out rolls the controller's
top in, so the pole now leans towards the hand's underside (the weapon's
for the right arm, the free left controller's for the left; the hand on
the foregrip keeps the fixed pole). With the free arm solved before its
hand is turned it looked right on the Quest: vr.arm_ik defaults to true.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As HaloCE-Quest-VR does it: the free arm is bent to the controller's grip
from the animated pose (the hand free to swing with it), and only then is
the hand turned to the controller and the roll shared along the forearm.
Turning the hand first left the solve to swing it on, which stretched the
arm's skin like a rubber band (Quest, 2026-10-06). vr.arm_ik stays off by
default until it is seen right.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Second Quest test: with the elbow out to the side the right elbow's skin
turned inwards: the shortest turn of the solve left the upper arm's hinge
where it was. vr_align_elbow now rolls the upper arm about its length so
the animation's bend lies in the plane the arm bends in, and the upper
arm takes no share of the hand's roll any more. The open sleeve end could
still be seen: the shoulders are 5 cm higher (20 cm below the neck).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
First Quest test: the free left arm's skin stretched like a rubber band
with the new elbow IK, so vr.arm_ik now defaults to false (the arm's shape
as before, pointed at the shoulder); and the open end of the right
sleeve came into view looking down to the side: the shoulders now stand
4 cm behind the neck instead of 3 cm in front of it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- vr.body = "arms" (default): arms and hands as before; "hands": the arms'
bones shrunk to a point 3.5 cm behind each wrist along the hand's line
so the cuff closes behind the hand (vr_hide_arm, after HaloCE-Quest-VR's
vr_hide_forearms, CC0), no arm IK solved; "off": the hands model is not
drawn (one model draw less an eye) and nothing of the arms posed; the
weapon, its aim with both hands, barrel, laser and crosshair stay.
- vr.c reads vr.body with the live settings (halo_vr_body_mode).
- The VR options panel gets "Body" (Arms and hands / Hands only / Weapon
only) and "Fingers" (Tracked / Off) rows; its twelve rows are 22
pixels apart (were 26) so the panel keeps its box.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- vr_arms.c: vr_finger_nodes, vr_pose_finger (after HaloCE-Quest-VR, CC0,
nearly verbatim): each finger's three joints found by name under the
hand, posed by absolute directions in the palm's frame (a little bent
at rest, a fist at full curl; the thumb up the handle or across the
palm), each segment's subtree turned with its length kept. Its touch
of the world (rays per finger) is left out for the Frame's processor.
- The free left hand takes its controller's curls (halo_vr_finger_curls:
index from the trigger, the others from the grip, the thumb from where
it rests; round a held grenade). On the foregrip the hand keeps the
weapon's pose.
- The hand on the weapon keeps its grip; only its index finger is lifted
part way off the trigger while the controller says it is (touch known
and the finger off it), never where touch is unknown.
- Joints found once per skeleton; the cost is a few dozen node turns.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- port/linux/game/vr_arms.c: vr_solve_arm and vr_distribute_arm_twist,
after HaloCE-Quest-VR (CC0), nearly verbatim: the shoulder reaches 8 cm
and the arm stretches up to 18% out of reach (further, the shoulder
follows the held hand, as this port had it, so the arm stays whole),
bends no further than 145 degrees, the elbow's swing eased per arm
(vr_limb_history), the hand's roll shared by forearm (85%) and upper
arm (25%) within 110 degrees, continuous across half a turn. The
skeleton is read through the graph's index; the nodes under each node
come from one pass a frame (vr_skeleton_build) rather than walking
ancestors per node; time is the headset's frame's (halo_vr_pose_time).
- Both arms use it; the right hand and a left hand on the foregrip stay
where the weapon puts them (hand_stays), so shots, barrel and laser do
not move.
- The free left hand: its wrist and fingers are set on the controller as
before, and the arm now bends to it from the shoulder instead of being
carried rigidly. The ribbons of e377e4dc did not come from skin on the
root (3a.0: none hangs there), so no triangle filter is needed; the
rigid arm stays behind vr.arm_ik = false, read live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- vr.c keeps the torso's heading in LOCAL space (torso_update, after
HaloCE-Quest-VR's vr_body_heading, CC0): the head's heading, steady at
any pitch, drawn halfway towards the hands when both are ahead (at most
45 degrees), followed beyond a 15 degree cone, eased at 10 a second;
reset on a recentre or a gap. In LOCAL space the stick's snap and
smooth turns carry it at once (HaloCE-Quest-VR's "arm IK lags as the
body turns").
- halo_vr_body_frame gives the torso's heading and the neck the head
turns about (9 cm behind the eyes); the shoulders hang from the neck,
square to the torso, so turning the head no longer swings them (as the
head faces the torso, where they were before).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Posed from the camera at the right controller, the first-person weapon
and the arms are the same for both eyes and the scope's view, yet were
built (animation, interpolation, arm IK) for each: twice a frame on
SteamVR, three times zoomed in. The pose is kept for the rest of the
headset's frame (halo_vr_frame_serial) once built from the controller,
for the same weapon; posed from an eye's own camera (the hand not
tracked), each eye still builds its own. vr.log_performance counts the
builds and the poses kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- vr.log_performance reports how long the first-person pose takes a frame
(its animation and the arms), how often it is built, and the arms' part
of it (halo_vr_timing_start/_end).
- Once per hands model it logs how many triangles hang from each node and
how many of those from the skeleton's root ('frame bone24'), read from
the part's own buffers (rasterizer_model_buffer_data, after
HaloCE-Quest-VR, CC0). In the emulator the cyborg's hands have none on
the root: the ribbons of e377e4dc did not come from root skin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: d3d8_gl.c (multiview variants kept beside the per-pixel lit
shaders: shader[4] and lit_shader[4], vertex_shader_get takes both;
multiview targets before multisampling; the picture read resolves both),
nv2a_vsh.c/xgpu.h (lighting and multiview parameters), render.c (no
anti-aliasing pass on VR eye passes), dsound_sdl.c, p2p.h.
VR: anti-aliasing off (the eyes go to the headset from their textures);
the VR online list skips password games (the server browser asks).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixups for #99 and #112: the version raised for the object message's
shader permutation, NETCODE.md brought up to date, a networked glass
break's sound placed where it broke, a frame scratch pool that runs out
logged once, and the new comments marked as the port's.
The host's notice of a broken surface carries no damage effect. The client filled a damage record with zeros and then read tag 0, which is the scenario, and halted. Shards still spawn. They skip the kick from an effect this machine never received.
The frame scratch pool was still the Xbox size after the particle pool grew eight times, so a busy level spent it every frame. It now grows with that particle pool, and a frame that still runs out is logged once.
A console line longer than the buffer halted inside the error printer, before the reason was written to the log. Long lines are cut to fit, and the reason is written first.
- bots: no more than the game's MAXIMUM PLAYERS has room for beside the
people (instead of raising it); the host options show and change the
bots there are
- VR options hint on the PC main menu drawn whole (left-aligned)
- the online and host panels: a click beside them or on their key's B
closes them, on the online panel's X enters a code
- spinners' arrows (drawn just outside them) clickable
- Android flat (the emulator): the mouse drives the menus' pointer, for
testing the menus' clicks with adb shell input mouse
- Frame launcher: Lepton's logcat of the game's container in logcat.txt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- menu_tags.c builds the PC menus in VR too unless vr.menus = "xbox"
- their text fields (Server Setup's game name) typed on the Xbox's
on-screen keyboard in VR
- join code / online panel on the PC Server Browser (X or click its hint),
host options (Y or click) and join code on the PC lobby, VR options on
the PC main menu
- bots: room made for them beside the people (Server Setup's maximum
players), none in co-op
- player_name_valid accepts trailing spaces (Xbox menus pad new profile
names, "New001 ", which the PC lobby refused)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Upstream's online co-op (network_actors.c, network_coop.c) ticks beside the
VR port's VR relay and bots; its message types end at 77, below the VR
port's from 240. The look and move flags the action test reads moved into
player_control_action_test_note: the head's turning goes there too. The
vertex shader's screen-space undo is upstream's on every platform now, with
the multiview eye offset after it on Android. The APK carries upstream's
brokers.txt beside the FDM layer stub; the updaters keep the VR port's own
repository (upstream's now points at OpenCommunityEdition/OpenCE).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The launcher gives Lepton what Steam gives an Android title of its own (a
compat data path, which the app's data links to after an install, and the
shader path), keeps a container id of its own, tries a start that ends at
once again (a new APK's first start only installs it), and stops when the
game was quit. Steam's "Exit game" cannot reach the game in Lepton's
container: the VR options get "Quit Halo" (asked twice), which on Android
now ends the process and leaves quit_requested.txt for the launcher. The
library's pictures are in port/android/steam_frame_grid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hosting a co-op level from the Map screen (CO-OP CAMPAIGN, a level and its
difficulty, then Server Setup) makes a network game on the campaign map
with no game engine, which is what co-op is. Only the host runs the
scripts and the AI; port/linux/game/network_coop.c sends clients what the
scripts do (cutscenes, camera, fades, HUD, sounds, devices, named objects,
animations), and network_actors.c drives the host's AI units on every
machine. Players spawn beside each other, respawn beside a safe teammate,
and spectate while dead or waiting. Skipping a cutscene is a vote.
Players can join a game in progress at any time.
Backing out of a hosted game keeps its server: the lobby's last local
player leaving pauses it (netgame_unjoin_player). Choosing Create again
threw the host's client away, but network_game_start_new_server makes and
joins a server only when there is none, so the new client never joined the
one left over. The lobby then had nobody in it: START added no player, B
could not leave (not in pregame), and the server's map changes failed to
reach the client that was gone.
Create now disposes the client and server first, so it always makes a new
game and joins it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SteamVR offers the Frame's controllers by their own profile
(XR_VALVE_frame_controller_interaction); without bindings for it, it mapped
the Quest's onto them, and X and Y landed on the left one's D-pad. Now: the
right one's A, B, X, Y and menu button are the game's A, B, X, Y and Start
(the Xbox's layout), the left one's View is Back and its D-pad the D-pad,
the bumpers switch grenades and the flashlight. The VR options' page of
buttons shows the Frame's layout there. The runtime's extensions and the
controllers' profile are in the log.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Upstream's server browser of public internet games (p2p_lobby.c) and the VR
port's own public list share the brokers: its topics are a table now, and
the VR port's (join codes' and public games') are entries of it; the VR
port's retained publishes carry MQTT 5's empty properties. The VR port's
public toggle lists the game in the server browser too, and the VR online
panel shows the server browser's games (PC, phones, VR) first, joined by
their invites, then its own list's that it has not.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The triangle the game draws over each teammate goes into the name's slot
over the head (shown in the world there, not on the HUD before the eyes,
where it hung distorted), under the name, or alone when names are off. On
the screen where controllers join by pressing A, a click is the pad's A. On
the screen of games to join, the online games' hint goes into the key bar's
free left and the join status under it, clear of the map's picture.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A choice the stick moved to (a map among the tiles) is no longer taken back
by the pointer resting on another, until the pointer moves well away; and a
click beside a list's slots or a value's box, along the way it steps (where
its arrows are drawn), steps it back or on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>