Commit Graph
38 Commits
Author SHA1 Message Date
Madison 6eaf8e92e5 Mask each eye's hidden area, and time the GPU's frame from timestamps
At 1.5x the GPU needed 11.5 ms a frame (timestamps, vr.timing_gpu_stamps),
over 90 Hz's 11.1, almost all of it the eyes' pixels. The runtime's
visibility mask (XR_KHR_visibility_mask, 15.6% of each eye's image on the
Frame) is now drawn first in each eye's pass at the near plane, black, so
the scene is not shaded there (vr.visibility_mask, on). Two things it took:
the device binds its target and viewport lazily, so the mask binds the
eye's first (halo_vr_bind_current_target); and its depth goes through the
position, not gl_FragDepth, which turned Adreno's early depth culling off
for the rest of the pass (it saved 0.3 ms that way, 1.5 ms this way).

On the device in a30 at 1.5x: the GPU's frame from 11.5 to 10.0 ms (the
eyes 4.4/5.1 to 3.6/4.1), 85.5 fps with 45 misses in 10 s to 89.4 with 2
to 5. Cutscenes (the 3D screen, not masked) hold 78 to 89.

Also: vr.timing_gpu_passes (each pass waited for; Zink's glFinish costs
too much for it to mean much), glQueryCounterEXT and
glGetQueryObjectui64vEXT for the guest, the mask's share logged.
2026-10-03 21:49:13 +09:00
Madison abad4d7352 Draw the player's own legs and torso in VR, behind vr.body (headset-unverified)
Halo draws no body of the player's own in first person. With vr.body
"legs" (off by default) the eyes' passes draw the player's unit, on foot
and outside cutscenes, from a copy of its bones' matrices: the head, neck
and upper arms (and all under them) scaled away, the body turned to the
head's yaw (as the first-person shoulders now are) and moved under the
head, 12 cm behind the eyes. What the unit carries (its third-person
weapon) is not drawn with it, nor is its shadow. The body's bone names are
logged once, with which are hidden. The head-yaw direction is now shared
with the shoulders (vr_head_yaw_forward). Built only; not yet run.
2026-10-03 20:04:06 +09:00
Madison 0288752115 Step the game clock by the headset's display times (headset-unverified)
The frames between ticks are placed by the game clock, which the main loop
stepped by its own measure (the performance counter) of each pass. That
pass also holds the game's tick and the drawing, which wander by
milliseconds from frame to frame (game 6 to 9 ms on the device), so what
moves steadily was drawn at uneven places between the ticks: cutscene pans
and flying banshees juddered (reported worn). The runtime's frame is now
begun at the top of the loop (vr_frame_seconds) and the clock steps from
the last frame's predicted display time to this one's, whole periods; the
loop's own measure stays for when there is no runtime frame.
2026-10-03 13:09:19 +09:00
Madison 23dcfe4fcd Draw the reaching arms two-sided, their insides dark (headset-unverified)
The first-person arms model ends partway up the upper arm, open, where
the Xbox's camera never looked. With the arms reaching for the hands
(vr.arms "ik") that end comes into view and, its inside faces culled, the
arms look hollow: one sees through them (reported worn). While they reach,
the hands model (only it, not the gun) is drawn with culling off and its
back faces shaded to 30% (a pixel shader key bit, back_faces_dark, so no
other draw's program changes): the sleeve reads as a closed, shadowed
inside. The user chose this over folding the upper arms away.
2026-10-03 12:59:04 +09:00
MadisonandClaude Opus 5.5 cff675537d VR settings in the pause menu (untested on device)
When a level's tags load, the VR build adds widget tags cloned from the
solo pause menu's (same font, colours, buttons and boxes): a fifth item,
VR SETTINGS, in the pause list (its button hints move under the mission
objectives to make room), and a screen of nine settings in two columns -
controls, aim, gun hand, turning, room-scale, scope, vehicle view,
steering and cutscenes. A or right steps a setting on, left back, B
returns to the pause menu; the laser pointer clicks them too.

Changes are written into config.toml (config_write_real/_string join
config_write_boolean) and the VR layer takes them up at once
(vr_reload_settings). cache_file_add_tag adds tags made in memory after
a map's own; the buttons' text and changes come through a new game data
input function and two event handler functions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 17:03:30 +09:00
MadisonandClaude Opus 5.5 b5c6ae2a9b Walk the player by walking the room (untested on device)
vr.roomscale (off by default, after the PC mod HaloCEVR's) moves the
player's collision pill each tick by how far the head walked from where
they stand, through collision_move_pill so walls stop or slide it as
walking does. The eyes lean from that standing place blended between its
last two ticks, as the game's camera is, so walking adds no latency and
no judder at the 30 Hz ticks. Vehicles, cutscenes, scripted holds,
death and network games pause it; a jump of over 0.5 m in a tick is
taken up without moving. vr.diag_walk_speed walks the synthetic head for
unattended checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:48:28 +09:00
MadisonandClaude Opus 5.5 b3fd4c345f Float the HUD far off, drawn from the head (untested on device)
The HUD panel defaults to 15 m away and 10 m wide (about 37 degrees, as
the PC mod HaloCEVR's), so the eyes need not refocus between it and the
world. Its pass is drawn from the head's camera with frustum bounds
matching the panel, so nav points and friends' names sit over what they
mark rather than following the hand's aim. The motion sensor turns with
the head too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:33:32 +09:00
MadisonandClaude Opus 5.5 b70ff4cfca Show a zoom in a scope held at the gun (untested on device)
While a hand-aimed weapon is zoomed, a pass between the eyes and the HUD
renders the view along the gun into a corner of the back buffer, sized
to the scope's 768x768 image. It goes through a sight mask (round, or
the sniper rifle's wide one) onto a layer held at the gun, at the PC
mod HaloCEVR's per-weapon offsets. The eyes never zoom: the first-person
gun stays in the hand and the HUD layer drops the zoom mask.

vr.scope and vr.scope_size are the settings; vr.diag_zoom_seconds zooms
unattended for dumps (vr-scope.bmp). The docs now cover the VR controls,
gestures, weapon feel and scope, and credit LivingFray's HaloCEVR for
their designs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:31:34 +09:00
MadisonandClaude Opus 5.5 2b9c4fc065 Steady zoomed aim, and feel the shots (untested on device)
Zoomed in, the aim eases toward the hand's (the PC mod HaloCEVR's
half-life formula: about 15 ms at zoom 1, 25 ms at zoom 2), steadying a
scope; unzoomed it is the hand's as it is. Smoothing in the headset's
recentred space, it is untouched by snap turns.

The local player's shots buzz the weapon hand (and, held in both, the
other more softly), by weapon from its tag's name: pistol, shotgun,
sniper, rocket, fuel rod, flamethrower, needler, plasma rifle, assault
rifle, plasma pistol; a shotgun's pellets are one buzz. vr.haptics
scales every buzz, the gestures' too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:16:59 +09:00
MadisonandClaude Opus 5.5 5a733a26c3 VR controls, gestures and either hand (untested on device)
After the designs of LivingFray's PC mod HaloCEVR, re-implemented here.

vr.controls "vr" (the default) lays the headset's controllers out for VR
over the Xbox pad the game reads: right trigger fire, left trigger zoom,
A jump, B reload and action, X switch grenades, Y switch weapons, right
bumper grenade, left bumper flashlight, right stick click melee, left
stick click crouch, menu pause, view back, held a second to recentre.
"pad" keeps the Xbox layout. The host now reports each controller's own
buttons (hand_buttons) beside the pad it built.

Gestures, taken into the game's own control flags in
handle_one_player_input (before a cutscene's inhibition clears them):
- a hand swung up or down faster than vr.melee_speed (2.5 m/s) melees;
- the off hand brought within vr.flashlight_distance (0.2 m) of the
  head's middle toggles the flashlight;
- ducking vr.crouch_height (0.15 m) below the recentred height crouches,
  standing again within 5 cm;
- the weapon hand's grip at either shoulder (vr.holsters) switches
  weapons, with a tick as the hand enters a holster;
- the off hand's grip holds the gun in both hands (vr.two_handed "grip",
  within 80 cm; "auto" keeps the earlier proximity test);
- palms together and the off hand's grip moves the weapon to that hand.

vr.left_handed (or that swap) puts the weapon in the left hand: the
weapon hand is a variable throughout, its trigger fires and its bumper
throws grenades, and the first-person model is mirrored across the
weapon camera's upright plane with its winding turned over; the IK then
reaches the gun arm from the left shoulder. vr.move_relative moves
relative to the head or a hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:15:16 +09:00
MadisonandClaude Opus 5.5 7c0adfc6d0 Vehicles in first person, steered by the stick (untested on device)
vr.vehicle_view "first_person" (the default) has the director treat every
seat as first person, which hides the player's body, and puts the eyes at
the player's head in the seat (a seat's camera marker is where the chase
camera sits). The view turns with the vehicle's yaw only, the horizon
kept level; a passenger seat facing aside keeps its turn from the
vehicle. The player's own gun is put away in driver and gunner seats.

vr.vehicle_steering "stick" (the default) leaves a driver's facing to the
game, so the right stick steers (and pitches the Banshee) as on a flat
screen while the head only looks; "head" and "hand" steer where you look
or point. Gunners aim with the head relative to the vehicle, so a turret
follows the head without turning the view.

vr.diag_drive_seconds seats the player as the nearest vehicle's driver,
for unattended checks (after cheat_all_vehicles). The headset went
offline before the first vehicle test ran; the build is warning-clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 13:25:19 +09:00
MadisonandClaude Opus 5.5 43820dc19d Reach the first-person arms for the hands (vr.arms)
With the hand aiming, the first-person weapon's camera puts the gun in
the right hand and used to carry both arms along with it. vr.arms "ik"
(the default) gives the arms shoulders where the body is (below and
either side of the eyes, turned with the heading) and solves each arm's
upper arm and forearm, elbows down and out, to reach its hand: the right
the gun's grip as the controller holds it (the wrist and the gun, its
child, stay as they are), the left the left controller, or the gun's
grip for it while the left controller is within 15 cm of it. Out of
reach, the shoulder comes forward rather than the hand letting go.
Fingers and other bones go with the bone above them. "hidden" shows the
gun alone, "animated" the arms as the animation moves them.

Halo's first-person graphs name the hands' bones "wriste"; the gun is the
right one's child.

Cutscene frames have their own dump trigger (vr.dump_cinema_frame).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:38:22 +09:00
MadisonandClaude Opus 5.5 c87cf4413c 3D cutscenes on a screen, fades between views, 90 Hz, mapped stream buffers
Cutscenes play on a 3D screen: each eye's view from the cutscene's camera
moved half the eye separation aside (vr.cinema_separation), its frustum
turned in to converge at vr.cinema_convergence, the console window's
letterbox and titles drawn over it, shown as a quad layer each eye sees
its own of (vr.cinema_distance, vr.cinema_width). A screen (menus,
loading, cutscenes) comes up where the head faces when it appears, and
every change of what is shown comes in from black (a quarter second
black, then a 0.35 s fade), which also hides the few frames of play a
level shows before its opening cutscene.

The headset asks for vr.refresh_rate, now 90 Hz (XR_FB_display_refresh_rate),
and falls back to 72 if the game does not hold 90% of it for 20 seconds
while worn. The game thread measured busy 59% of a 72 Hz frame (8.3 ms).

The renderer's stream and index buffers are persistently mapped where the
driver has GL_EXT_buffer_storage, so an upload is a copy with no GL call
(on Zink each map synchronized the threaded context): each eye's pass went
from 3.2 to 2.2 ms. A full buffer within a frame waits for the GPU
instead of orphaning, which immutable storage cannot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:29:08 +09:00
MadisonandClaude Opus 5.5 06a0e85c18 Aim with the right hand in the headset (vr.aim = "hand"; untested)
With vr.aim "hand", on foot the right controller's aim sets the
player's facing, so shots, grenades and melee follow it; in a vehicle's
or a turret's seat the head still aims. The left stick is turned to move
relative to the head rather than the aim. In a local game the shots
start at the hand (unit_adjust_projectile_ray) unless a wall stands
between it and the unit's eye.

The first-person weapon is posed from a camera placed so that its model
sits in the hand (vr.weapon_offset_*, to tune in the headset). The HUD
crosshair gives way to a reticle on its own OpenXR layer where the
hand's aim meets the world, about a degree across.

Head aiming stays the default until this is checked in the headset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:34:10 +09:00
MadisonandClaude Opus 5.5 a1690d2277 Aim with the head in the headset (untested on device)
While gameplay is in stereo, the player's facing follows the head: the
runtime's frame is begun in player_control_update, so aim and eyes use
the same predicted pose, and the facing becomes the head's direction
turned by a heading of the headset's own. The right stick turns that
heading (vr.snap_turn, 30 degrees, or vr.smooth_turn_speed) instead of
the game; both sticks pressed together recentre. When the game turns
the player itself (a script, a respawn, another pad), the heading takes
it up. View magnetism is off while the head aims, so the view never
turns under it; pitch stops at the game's 85.5 degrees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:27:21 +09:00
MadisonandClaude Opus 5.5 741d83925e Draw gameplay in stereo in the headset (untested on device)
A frame with one player and no cinematic renders the player's window
three times in render_frame's window loop: the left and right eyes, each
from the game's camera turned by its heading and moved by the tracked
eye (vr.world_scale, head reach limited to 0.35 m), with OpenXR's
off-centre field as render_camera_build_frustum's bounds, and each
copied into its swapchain image as it finishes; then the HUD alone on a
transparent ground, shown head-locked (vr.hud_distance, vr.hud_width).

What moves on as it draws does so once a frame: the sky's animation
phase, weather and glow particles run for the left eye only, and the
first-person weapon is posed once, from the head between the eyes. The
fog's screen layers, which assume a symmetric frustum, are left out of
the eyes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:24:20 +09:00
MadisonandClaude Opus 5.5 3248edd149 Build the Android port on macOS
The NDK's host prebuilts live under darwin-x86_64 on a Mac (universal
binaries), and the case-insensitive file system made StdDef.h include
itself instead of musl's stddef.h; include_next skips this directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:06:08 +09:00
MrBruh c5fcfbd46a Kick speed hacks and add bans 2026-10-01 13:12:54 +13:00
MrBruh 71ae1f47bc Fix seventh netcode review 2026-10-01 12:03:52 +13:00
MrBruh e233e1c1bb Fix fifth netcode review 2026-10-01 09:12:07 +13:00
MrBruh 0d548798e5 Fix second netcode review 2026-10-01 03:54:02 +13:00
MrBruh e7d1ed3a67 Harden and fix the netcode 2026-10-01 01:22:46 +13:00
MrBruh a3ed50c56d Remove the lockstep netcode 2026-09-30 22:12:05 +13:00
MrBruh 4adc3a87a5 Remove matching tooling and HALO_LINUX ifdefs 2026-09-30 21:49:46 +13:00
MrBruh 8f3e7de9ca Play PAL maps as the NTSC maps 2026-09-29 22:07:10 +13:00
MrBruh 5c867dc46e Fix teams and player slots for mid-match joins 2026-09-29 15:49:22 +13:00
MrBruh c0c86bda26 Allow joining games in progress 2026-09-29 13:53:13 +13:00
MrBruh 087c1f5685 Improve netcode, add network versions 2026-09-29 11:32:57 +13:00
MrBruh b7874714ae Deterministic FP math 2026-09-28 19:56:00 +13:00
MrBruh f7ee09b8e3 Move settings to config.toml 2026-09-27 23:56:37 +13:00
MrBruh bd9876ac9b Mouse support in menus 2026-09-27 22:05:55 +13:00
MrBruh 50eae19bac Native resolution fullscreen with F11 toggle 2026-09-27 20:42:09 +13:00
Jonas VolmanandClaude Opus 5.5 9a376181f5 Port: grow the game state and object pools for 128 players
The Xbox game state (0x345000 bytes at 0x80061000) ends exactly where the
map-linked tag cache begins and has 948 bytes free, so a 128-player
session cannot even allocate its player datums. The native builds now
place a 16 MB game state (CPU 0xFC0000, GPU 0x40000) at 0x81A00000, above
the tag cache and below everything the Xbox memory window hands out
top-down, and grow the pools that scale with the number of players:

- objects: 2048 -> 8192 headers, 1 MB -> 8 MB pool; the garbage limits
  keep January's proportions (5/10/20 % of the pool, 2.5/5/10 % of the
  headers, 75/50/30 active garbage objects per 16 players)
- cluster partition reference lists 2048 -> 8192 (collideable and
  noncollideable objects, lights); a full list drops objects out of
  rendering, collision and sphere queries
- effects 256 -> 2048, effect locations 512 -> 4096
- particles 1024 -> 8192 (render_particles' visible list, filled without
  a bound check, follows), particle systems 64 -> 256, their particles
  512 -> 4096
- contrails 256 -> 1024, contrail points 1024 -> 8192
  (MAXIMUM_CONTRAIL_POINTS stays the pool size for the same reason)
- lights 896 -> 4096, object looping sounds 1024 -> 4096
- cached object render states and rendered objects 256 -> 1024
- script object-list references 128 -> 1024, objects one explosion can
  damage 64 -> 256

The values live in port/linux/include/halo_port_capacity.h, included by
halo_port_limits.h. With 128 player datums the CPU part holds 14,256,908
bytes (2,258,164 free). Saved games and cores grow to 16 MB. Every change
is under HALO_LINUX; the MSVC objects are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 10:18:07 -07:00
Jonas VolmanandClaude Opus 5.5 db207ea1a4 Networking: 128 players on up to 128 machines in the native builds
The Xbox game allows 16 players on 4 machines (4 split screen players
each). The native builds raise both limits to 128, keeping 4 players per
machine, and leave the byte-matching build unchanged: every change is
under HALO_LINUX (the MSVC objects compare identical).

- Limits: every local copy of the player and machine limits, and the
  bare 4s and 16s that stood for them, follow halo_port_limits.h. The
  player data array, the machine-to-player table (and its reset) and the
  per-tick update arrays grow with them.
- Records: the copies of struct network_game agree on the larger layout
  and check it against halo_port_limits.h; maximum_player_count becomes
  a byte (128 does not fit a signed char); network_player_is_valid
  checks the machine against the machine limit, not the split screen
  one; a machine's input carries MAXIMUM_LOCAL_PLAYERS actions (16/4
  would have become 128/128).
- Protocol: a message holds at most 4095 bytes (its header has a 12-bit
  length), so the 13 KB game settings record is sent in 3.5 KB pieces
  that clients reassemble; the per-tick update of 128 players (3,857
  bytes) fits. Buffers and the packet codec's limits grow to match, and
  the game search uses protocol version 2 so these builds and the Xbox
  game never see each other's games.
- Connections: the host accepts a client connection per machine (the
  split screen count stood in for it), FD_SETSIZE is 256, stream writes
  send all of a message or drop a peer that stops reading instead of
  corrupting the stream, and the input and update queues are larger.
- Host: lobby changes are sent at most every 250 ms instead of on every
  change (a full lobby would otherwise send ~650 MB), in-game joins wait
  in a queue instead of being dropped, the random name and colour
  pickers stop looping forever once names or colours run out, machines
  get 60 s to load a map, and the debug dump lists only the slots in
  use (128 empty slots took seconds to log).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 10:18:07 -07:00
Jonas VolmanandClaude Opus 5.5 08cd0c01d0 Port: add session-limit header and system link address settings
halo_port_limits.h (force-included by the Linux and Windows prefix
headers) defines the native builds' multiplayer limits, 128 players on up
to 128 machines, and the struct network_game layout that follows from
them. Game sources use it only under HALO_LINUX, so the matching build
keeps the Xbox limits (16 players, 4 machines).

xnet.c gains two settings: HALO_NET_ADDRESS binds the game's sockets to
one local address and reports it as the machine's system link address,
so several instances can share one computer on different loopback
addresses; HALO_NET_BROADCAST sends the game's broadcasts to a list of
addresses instead, for machines broadcasts do not reach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 10:18:07 -07:00
MrBruh a06ef74eec Detach framerate from gameplay simulation 2026-09-26 21:49:07 +12:00
MrBruh e29125b50e Port decomp to Android 2026-09-26 16:16:00 +12:00
MrBruh ebb0eac50b Port decomp to Linux 2026-09-26 12:15:09 +12:00