OpenCE gametypes named in the server browser (network version 37), and
the host may leave the shield effects to each player (SHIELD EFFECTS'
PLAYERS' OWN, network version 38). The conflict: our Android listing
declarations stay beside upstream's p2p_set_game_listing with its new
kind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New gametypes (SWAT, Infection, Gun Game, Hardcore), shield effects the
host's choice in network games, co-op ally friendly fire, network fixes
(scenery shields, tripmines, co-op teleports), Custom Edition HUD and
texture fixes, ASTC for Android GPUs without S3TC.
Left out on purpose: upstream's two Android workarounds for Mali phones,
the GL shadow state refreshed before every draw (prepare_draw) and the
geometry cache bypassed (mirror_range). Both would cost every draw of
both eyes on the Quest, which never showed the corruption they work
around; their tests are skipped. Conflicts: the GL capabilities log
keeps multiview and adds ASTC; xbox_textures.c keeps vr.c's texture work
counters; README.md stays the player's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shield glow (display.shield_glow and its Light Show intensity) and the
overshield effect (display.overshield_effect) were each player's own, in
network games too. The glow lights what is around a shield as it flares,
so a player who had it saw enemies a player without it did not, and the
overshield effect keeps an overshielded player lit: anyone who did not
turn them on was at a disadvantage.
Now a network game's are the host's, for every machine: SHIELD EFFECTS in
Server Setup > Player Options (network.shield_effects): STOCK (the
default), GLOW, LIGHT SHOW, OVERSHIELD, GLOW+OVERSHIELD or
SHOW+OVERSHIELD. Each player's own settings apply offline only (the
campaign, split screen), and Video Setup's helps say so.
- network_shield_effects.c: the host sends its choice to each client
reliably when it is ready (distributed message 91) and to them all when
it changes, as the aim assist policy is sent; a client takes it only
from the host's stream, whole and valid, and draws stock until it has it.
shield_glow.c and overshield_effect.c draw what it says in a network
game.
- Network version 36: a build that would draw its player's own cannot
join. network_format.txt recorded.
- Player Options' rows 27 apart in place of 30 (ce_menus.py's
CHILD_OFFSETS), so that the tenth fits above the help, which keeps its
three lines, and the screen's divider and buttons stay where they were;
test_aim_assist.py's layout check follows.
Checked: ninja android builds, the changed files compile for the web, and
CI's tests pass (936). On the Android emulator, Player Options shows the
row and steps through its values with each value's help, and Extra
Effects shows its new helps. Not played as a network game.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Match stock CE resource sheets by pixel identity and translate stock meter
redraws to CE layouts. Preserve custom artwork, correct mixed-use meter
channels and warning placement, and draw aligned controller glyphs with
High-Res HUD enabled or disabled. Scope Bigass Open v1.1 ammo changes to
its known map name and checksum.
Based on OpenCE testing 4cb621cd. Keep upstream Android texture ownership,
eviction and validation unchanged; use its residency helper in the new
stock CE upscale lookup. No unrelated main-branch commits, networking
changes, regression test files or investigation documents are included.
Validation: Windows portable release build passed; 175 local CE HUD and
Android regression checks passed, 4 asset-dependent checks skipped.
Additional residency checks cover Android-managed pixels, desktop cache
entries, identity rejection and map reset. git diff --check passed.
Android/Web targets are unavailable in this Windows build graph; no new
runtime gameplay validation was performed for this rebase.
Player-Note: Fix Custom Edition HUDs, ammo counters and prompts while preserving custom artwork.
A scenery or device whose shield is all damage can break (Rat Race's
energy shields, the tripmines of Altis94's Bigass) was no static target
of the netcode, which took only those with a body to destroy
(network_damage.c's static_target). So a client's hits on it were never
reported to the host and the host never sent its damage: clients could
not break the shields, and when the host broke one, clients still saw it
and stood on it while the host saw them floating.
static_target now takes those with a shield too. The host sends its
damage to them, as to destructible scenery, and takes clients' hits on
them. Clients, which otherwise run no object's shields but take the
host's state of them, run these objects' shield stun and recharge
themselves (damage.c's objects_update_shields), since the host sends no
state of the map's scenery: each machine deals the same damage and
recharges the same way, so the shield's regions go and come back on
every machine.
Network version 34: a host and a client must agree on which objects'
damage is shared. NETCODE.md says so and network_format.txt is recorded
for 34.
Checked: the Windows build; the new harness test test_static_target (a
body, a shield only on scenery and on a device, neither, a unit, no
object, and three negative controls, one the old rule); the whole harness
on Windows fails the same 245 environment-bound tests as testing does,
no others; network_format.py. Not yet checked in a game: a client
breaking Rat Race's shields and Bigass's tripmines, and seeing the host's
broken and recharged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Server Setup's Co-op Options has ALLY FRIENDLY FIRE
(network.coop_ally_friendly_fire), ON by default as in the campaign. OFF,
the players' weapons, grenades, melee and vehicles do their AI allies no
damage, so that no player can kill Captain Keyes on Truth and
Reconciliation, the Marines, or the other allies a level needs. The AI's
own hits of its allies, the enemies' damage, and FRIENDLY FIRE between
players are unchanged. A hit is the players' when its owner is a player
and the victim is a friendly unit that is no player's, so friendly-team
vehicles are immune to the players too, splatter included. An ally that
cannot die cannot raise an allegiance incident, so the players cannot turn
the Marines hostile either.
The rule is in game_engine_friendly_damage (game_engine.c), which only the
host's damage reaches: a client's hits are the host's to deal. The host
sends the choice with the game's settings, as bit 1 of network_game's
cooperative_flags beside PLAYER COLLISIONS', so network version 33: a
client of version 32 would play the game's settings without it. NETCODE.md
says so, and network_format.txt is recorded for 33.
The menu row is tools/port_settings.py's (COOP_SETUP_SCREENS); only the
server settings screen was written again. Its 8 rows fit above the help
area.
Checked: the Windows build; the harness tests, the new
test_coop_ally_friendly_fire (a player's hit of an ally ON and OFF, the
AI's own hit, players' friendly fire in every mode, no unit, the campaign
without co-op, and four negative controls) and test_coop_scaling_menu with
the eighth row; network_format.py; the Co-op Options screen drawn with the
row; and in play as the host on a10 and a50 (Keyes and the Marines
unharmed with OFF) and b30 (no splatter). Not yet: a client's hits online,
d20's ending, c20's Sentinels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ringfall's side of it:
- The bots: OpenCE's own (bots.c, a local split screen game's) are not
used in VR builds (their lobby hook would take Ringfall's host bots
off, and their bots.count and bots.skill differ); Ringfall's are now
host_bots.c, so that OpenCE's bots.c merges as it changes. Each
platform's bots.count and bots.skill are its own (config_setting_index
takes this platform's of two of a name).
- HDR is never on in VR (d3d8_gl.c); the flat window's present is
OpenCE's, inside the VR port's branch for it.
- The VR frame begins where the events are pumped (sdl_platform.c), as
before, now that exit_after_check is apart.
- The message numbers: OpenCE's aim assist policy (90) before the VR
port's poses (240), which stay last.
- The visibility counters keep the VR port's snapshot ring (both eyes'
counts halved); OpenCE's tone mapping goes before the antialiasing, off
the VR eyes' pass.
- Join codes, the online list and OpenCE's probes all in the signalling
topics; the HUD's PNG helpers both ways; the disc import keeps the
language file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"highest" chose level 4 for every model, and render_model_parts draws
nothing for a permutation without geometry there, so those parts
disappeared; and a10's cryotube's top level (a cutoff of 3000) is its
inside only, which "highest", and "native" at 4K, showed from outside.
With display.model_detail other than "stock", each permutation now draws
its most detailed level at or below the model's that has geometry, and a
level with a cutoff over 1000 only while the camera is inside the model's
bounding sphere (port/linux/game/model_detail.c), from eltanschauung's
#282. "stock" draws the model's level as before; so does
rasterizer_debug_model_lod.
Checked: python -m pytest -q tools/harness (#282's lod-* cases in the
transparent enclosure test and its interior, bounds and permutation cases
in test_model_detail.py, with negative controls); models.c, d3d8_gl.c and
model_detail.c compile (-fsyntax-only) with the Linux, Android guest and
web commands.
The policy is a new distributed message (90) that a client must not ignore, so machines without it cannot play with those that send it: HALO_PORT_NETWORK_VERSION goes from 31 to 32, with its line in NETCODE.md's versions and port/linux/network_format.txt recorded again. network_aim_assist.c called main_menu_is_active without interface/ui_widget.h, so C89 took it as returning an int. The setting's README row moves to the network.* rows. Checked: network_format.check() is clean, test_linux_port -k network_format and the aim assist harness tests pass, network_aim_assist.c compiles without warnings.
A map that map sharing downloaded runs only scripts that keep to the
player's own game: one that calls what changes the player's settings or
other players' games (player0_look_invert_pitch, sv_end_game, sv_say) or
sets a global that outlives the map is disabled, as for any map's script
that calls what no map's may, and debug.txt says which. Map sharing names
each map it downloads in custom_maps/downloaded_maps.txt, so they stay
held to this in later runs; game.downloaded_maps names more, or "*" for
every map. The campaign's game_speed and game_save_totally_unsafe stay
allowed.
The setting's entry came into port_config.c with the profile commit before
this one (a conflict's side of it); it is the one entry now.
(cherry picked from commits 7d02d46210ba672512e36b39ffd2c0b0d8e4f3b3 and
d3718e78d4889140a44f8050075b56143e4b7732, wired to OpenCE's map sharing in place of that
fork's map families)
A busy multiplayer fight fills the Xbox's per-frame lists of what a view
draws: 512 particle runs and 128 dynamic lights. Past them plasma bolts,
charge glows and flashlights stop drawing. The desktop builds now draw
2048 and 512. These lists are rebuilt every frame, outside the game state,
and nothing of them reaches the network. Android's and the web build keep
the Xbox's.
(cherry picked from commit f1abebdd6e89de292569ea466496fa0484476e0e; OpenCE already drew
2048 transparent geometry groups on every build, so only the particle runs
and lights are taken, with OpenCE's guards for the builds that keep the
Xbox's sizes)
The picked log_address change declared the function inside
transport_address.c; the port's functions the game's sources call are
declared in halo_linux_source_fixups.h. Map sharing, which OpenCE added
since, logged a closed connection's address whole: it now goes through
log_address too. debug.log_addresses is in the settings table.
The picked keep-alive fix moves the Xbox game's begin game, graceful exit
and pregame keep-alive field lists under their own messages and makes the
keep-alive a long, its size on the wire. No byte on the wire moves but the
keep-alive's last two, which were the sender's stack and are zero now
(tools/test_network_messages.py compares every message's bytes with the
old table's), but the change is one to what the machines send each other,
and network_format.txt records the table: version 31, recorded, and said
in NETCODE.md.
A client on the same elevator discarded a vertical error of any size; it now
keeps its floor's height only within 4 world units, the platform's travel
over a round trip, so a teleport or fall in height still corrects. Network
version 30: every machine now carries an elevator's riders by the same
tick's movement, simulation they must agree on. distributed_riding_elevator
(was distributed_riding_moving_elevator) is named for elevators at rest too
and looks the elevator up as a machine, as bipeds.c finds it; the source/
changes are marked port:, the original's one-tick-late carry noted as fixed,
and device_machines.c's new functions moved to its public code.
Checked: ninja linux (debug) and ninja android build; pytest tools/harness
passes (492, with a new case and negative control for the bound);
network_format.txt recorded and its test passes.
The touch controls stay off in VR alongside the chat's on-screen
keyboard (xinput_sdl.c); the join codes and the online list beside the
server browser's listed hosts (p2p.c).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A controller's D-pad Up opens an on-screen keyboard for a line to everyone
(the D-pad only repeats the left stick's movement in the game). The stick,
or D-pad Left and Right, moves between its keys, A types one, X deletes, Y
switches capitals, D-pad Up and Down turn the line to everyone, the team or
the vehicle, Start sends it and B closes the keyboard; its last row has
Aa, #+= (the symbols' page), space, DEL and SEND. It is drawn at the bottom
right, clear of the lines above the motion sensor; on Android at the bottom
in the middle, between the touch controls, as the lines are at the top
left there.
The touch controls' D-pad Up opens it too, and a tap on a key types it
(touch_input.c hands the taps outside the menus to it). While it is up the
game gets none of the first controller (xinput_sdl.c), nor the touch
controls' stick and look.
The automated tests press the controller's buttons through
test_input_press: debug.network_test_chat now has each client type
"gg ez" on the keyboard and send it to the team. A drawn Linux client typed
it and the host received "chat (team): Player 2: gg ez"; the frames show
the keyboard over the game, the key picked lit, the typed line growing.
The harness gains three cases and six negative controls for the keys'
characters, moving between them and a tap's key (64 pass); CI's Linux test
step passes (493, 6 skipped).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This reverts 4ac609f0, the revert of #395, so the chat is back as it was
merged: T, Y and Tab, team and vehicle lines, the host's /mute, the
VOICE AND TEXT CHAT settings page and the Android placement.
The one conflict is source/text/draw_string.c, where #395's streamer mode
and testing's (90dd65ca, text_filter.c) both hook the drawn text; testing's
hook is kept. #395's own filter (chat_filter.c, game.streamer_mode) is left
in place, unhooked, for the next commit to take out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
testing's SOUND PROCESSING page and the chat's VOICE AND TEXT CHAT page
both split rows off Audio Setup in tools/port_settings.py. Both are kept,
as two category rows, sound processing first. The settings screens and
menus.json are regenerated from it, so they match testing's apart from the
chat's page, its row on Audio Setup and Controls Setup's COMMUNICATION
group.
Checked after the merge: the Linux debug build, the network format check,
the CI Linux test step (467 passed, 6 skipped), and the three-machine
system-link chat test, /mute and /unmute included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Network games had voice chat but no way to type to the other players, as
the PC version's chat did. T (controls.chat) types a line to everyone and
Y (controls.team_chat) one to the team; Enter sends it, Escape cancels it.
The lines are drawn above the motion sensor with the HUD's font: the
player's name in their team's colour (blue without teams), then the text
in a pale blue, and fade after ten seconds (port/linux/game/network_chat.c).
A client sends its line to the host (_distributed_message_chat_up), which
takes it only from the client's own stream and one of that machine's
players, five at once and one each 1.5 seconds after, its text cut to 63
characters of printable ASCII, and sends it on to the machines that hear
it (_distributed_message_chat_down): all, or a team's line those with a
player of the team. Older builds drop both kinds as unknown, so the
network version stays 29, as for the vote kick; network_format.txt
records the two kinds.
While a line is typed the keyboard drives no player, as with the console
(xinput_sdl.c), nor voice chat's push to talk or the debug keys. Controls
Setup gets a COMMUNICATION group with PUSH TO TALK, CHAT and TEAM CHAT
(the controls screen's spinner strings changed by hand to what
port_settings.py writes: ce_menus.py needs the PC version's tags).
Checked: tools/harness/tests/test_chat_logic.py (the text's cleaning, the
host's rate, who hears a team's line, the fade, with negative controls);
the CI Linux test step; three machines on system link with
debug.network_test_chat (new), every machine showing the lines to all in
one order and a team's line only on its team's machines; and a host drawn
on Xvfb with lines typed on its keyboard through xdotool (sent on Enter,
not on Escape, the player still while typing).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first-person model hung from a camera at one fixed place from the
weapon hand's controller (18 cm behind, 10 cm left, 14 cm above), but
each weapon's animations hold the hand elsewhere from that camera: the
pistol's wrist came out 15 cm before the controller (22 cm before where
the free hand's wrist is, 7 cm behind its grip), the plasma pistol's 11,
the assault rifle's 8 cm aside and 10 cm low; the weapon arm looked
longer, reaching forward. Measured on the Quest 3 (2026-10-17).
Now the wrist as the weapon is held ready is kept for each weapon
(eased, the last eight weapons), and the camera put so that it lands
where the free hand's does: 7 cm behind the grip, along the aim. The
model, its barrel, the laser and the shots move with the camera, as
before. A weapon not yet seen is held at the old place for its first
frame. vr.weapon_hand_forward/_right/_down (0 by default) move the hand
on the weapon; vr.weapon_forward/_right/_down, the old fixed place, are
gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The one conflict was the generated Graphics screen, which gained HIGH-RES
HUD on main (#380) and HDR and HDR DISPLAY here. It was resolved by
writing the screens again with tools/port_settings.py: the generator
gives the merged file byte for byte. tools/test_web_build.py's menu test
passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the Quest 3, in big online games (26-35 players), the picture was
doubled and shifted from the start as the head turned, nauseating; with
48 bots it was not, and with the eyes drawn one by one it was not online
either. Not the frame rate: one by one, online at 72 Hz, the compositor
repeated as many frames and nothing was seen. The Steam Frame had the
same doubling with multiview (2026-10-05) and draws them one by one
since. So both now do by default; debug.multiview (was vr.multiview, and
debug.multiview_steamvr) draws them at once, for finding what the shared
pass draws wrongly. The refresh rate's steps take up the extra cost.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
debug.fixed_step runs a game of one machine a tick a frame, each frame a tick's time, so it plays the same every time, faster than real time; debug.network_test_cues runs console lines at their ticks from the first tick every player is in; debug.network_test "log" logs a game of one machine, and the scripted player follows the game's clock. All off by default.
A game's level name is the map's own (custom_maps\hugeass), so no screen or
server browser listing shows a hash, and the game record's map version is
the map's hash's first 32 bits, which a client finds its file by. Maps are
no longer renamed; a download beside another version of its name is named
<name>_<hash>.map and listed as "(2)"; maps the last build renamed get
their names back. Network version 29: the level name and the map version
mean something else.
The game chooses a model's level of detail by its size in pixels on the
Xbox's 480 lines, whatever the resolution, so at high resolutions distant
models drop to their simpler levels long before it would show.
display.model_detail (HALO_MODEL_DETAIL): "stock" (the default) chooses as
the Xbox did; "native" by the size on the display's lines (without
supersampling's factor); "highest" draws the models native does, each at its
most detailed level. The first-person weapon and the sky are unchanged.
Android draws 480 lines, so there native is stock.
Checked: the same b30 view at 854x480 and 2400x1315 gave the same sizes in
stock; at 1080p native measures 2.25 times as much and moves the corpses and
weapons up a level, highest puts everything drawn at its top level. The
level chosen is used only to draw (render_model's callers are the objects',
the first-person weapon's and the sky's drawing), so nothing a network game
agrees on changes; stock multiplies by exactly 1. Not checked with ssaa2x or
on a 4K display, nor built for Android or the web.
Push to talk becomes an action of the BUTTONS page (vr.button_talk),
on an eighth button there, the free hand's grip (its default): it can
trade places with any other action (the move stick's click, say, so
that a player holding a weapon in both hands can talk).
The free hand's grip as a button is pressed once held 0.15 s, at the
threshold halo_vr_two_hand takes the foregrip at, and not at all while
it holds the weapon's foregrip or the Warthog's wheel: taken in that
time, the press is theirs until the grip opens. Before, the runtime's
grip button (which closes before the foregrip's threshold) could open
the microphone for a frame or two as the hand took the weapon.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the Quest 3 the shader worker took away the long frames, but a 15-bot
game on Blood Gulch still had seconds at a time of 9-13 ms of GPU a frame
(GPU% 0.91-0.94, 4-37 stale frames a second, the picture doubled), between
spells of 5-7 ms; "medium" held 90 Hz.
- vr.graphics "auto", the new default (the VR options' Graphics row has
Auto): "high", less what the GPU's time asks to leave out. host_xr.c
reads XR_META_performance_metrics' app/gpu_frametime each frame (the
metrics are now on wherever the runtime has them; logged only with
vr.log_performance), handed to the guest by host_xr_gpu_time.
- vr_graphics.c: past four fifths of the frame (8.9 ms at 90 Hz, scaled
with the rate) for three quarters of a quarter second's frames, the next
steps are left out, as many as their known costs say the excess takes:
the shield's glow, object shadows, reflections, specular light, lens
flares, bump maps, the rest of "low". Steps that leave out nothing
drawn (the glow without display.shield_glow) are passed over. A step's
cost is learnt from the GPU time before it and the second after it
settled. An effect comes back once the GPU time plus its cost stays
under seven tenths of the frame for 3 s; an effect that had to go again
within 10 s of coming back makes the wait twice as long, up to a minute,
back to 3 s after a minute without leaving anything out. Each change is
logged with the GPU time.
- Where the headset does not tell the GPU's time (SteamVR: the Steam
Frame) "auto" draws everything, as "high" did.
- shield_glow.c draws no glow while "auto" leaves it out.
- debug.vr_graphics_measure leaves each effect out in turn (1.5 s on, 1.5 s
off) and logs its cost by the GPU time, with means every round.
Checked: the APK builds, android_abi_check finds nothing, the desktop and
web paths compile; a host simulation of vr_graphics.c with a lagging
synthetic GPU time (heavy spells of 12 ms, effects of 0.3-2 ms) left out
two or three steps within 0.6 s of a spell and brought them back 3 s
after it, longer after a second spell; the measure mode recovers the
simulated costs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each Custom Edition (English, French, German, Italian, Spanish, Japanese,
Korean, Chinese) has the PC version's menu string lists in its language
in loc.map, by the tags' names. The menus showed the English of
strings.xml whatever edition the maps came from. Now each <strings> names
its PC tag (map=) and each <string> that is the PC version's its index
there; menu_tags.c takes those strings from the loc.map in custom_maps
(or the Custom Edition install's maps folder), read through a
bounds-checked reader in custom_edition_cache.c. The strings the port
adds or rewrites have no index and stay English. A setting,
display.menu_language ("maps", "english"), keeps English for whoever
wants it.
strings.xml is written by ce_menus.py as before; port_settings.py's
OVERRIDE_ORIGINALS maps the lists the port rewrites to the PC strings
they keep. The text without the new attributes is unchanged.
The text is drawn by text_hires.c, as the English is. The pictures with
words (titles, the main menu's items) are still English. CJK editions'
characters are not in Overpass and fall back to the map's font.
Checked: the Windows build has no warnings. With an Italian Custom
Edition's loc.map, all 1164 strings with an index come from it, and the
Multiplayer, Settings and Item Options screens show Italian, accents
included (window hidden, debug.menu_open). With "english" the same
screens are as before (without a loc.map the code takes the same path,
not run). test_linux_port.py's menu tests pass. Android and the web were
not built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Quest's driver compiles and links a shader program the first time it
is drawn with, 10 to 30 ms each, which the frame waits for: a long frame
at a map's start (14 programs, 256 ms) and near its end (61 in 780 ms),
seen in VR as stale frames.
- port/linux/src/xgpu_program_cache.c: a linked program is written to
shader_cache in the data folder, named by its sources' hash, with its
binary (glGetProgramBinary), the driver that made it (GL_RENDERER and
GL_VERSION) and both sources. Asked for again it is loaded with
glProgramBinary when the driver, the sizes, the checksum and the sources
agree; otherwise compiled as before and written again. A refused binary
only falls back to compiling.
- d3d8_gl.c makes its shaders with their sources and compiles them only
when a program of them must be linked (shader_make, program_make), so a
program from the cache costs no compile. Each program entry keeps its
cache key and lists itself once per map.
- Each map keeps the programs it drew with (shader_cache/maps/<map>.txt,
written every 20 s while it grows and as the map goes). As a map loads
(game_initialize_for_new_map, a port: hook) its programs are loaded from
their binaries; after a driver update compiled again from the kept
sources, in the background with GL_KHR_parallel_shader_compile where the
driver has it (polled each frame, binaries written two a frame), else
for at most two seconds of the load.
- display.shader_cache (true) turns it off; the folder is emptied past
three quarters of 192 MB or 12000 files at the start, and may be deleted.
- VR's long frame line now also says how many programs came from the cache
and what textures were uploaded (and decoded from DXT: the Quest has no
S3TC), menu pictures decoded and render targets made, and in how long,
for the menus' long frames that compile nothing.
Checked: the APK builds; the desktop and web paths compile; a host harness
with a mock driver went through the first run, a second run (all loaded),
a driver change (compiled from the kept sources, with and without
background compiling), a corrupt and a truncated file (compiled again) and
a missing list. On the emulator (no binary formats) the cache logs itself
off and a 4-bot game on Blood Gulch runs as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Quest 3 at 90 Hz with graphics "high" spends 5-9 ms of GPU a frame
on the eyes and shows stale frames (ASW, doubled pictures as the head
turns); "medium" holds, so the frame is short of GPU time. The eyes are
now drawn at full detail about each lens's centre and coarser towards
their edges with GL_QCOM_texture_foveated (the Quest's Adreno 740), where
the lenses blur them anyway.
- vr.foveation "off", "low", "medium" (the default) or "high", a row
on the VR options' MORE page (dim where the GPU has no foveation, as the
Steam Frame's zink); read as it changes.
- The focal point of each eye is its lens centre, tangent 0, in the
picture's coordinates: off the middle, the Quest's views being
asymmetric. Full detail within sqrt(1 + area) / gain of it (0.69, 0.58,
0.43 of the half width), down to a least density of 1/2, 1/4, 1/8.
- Only the eyes' targets: the multiview layers, which nothing else draws
into; one eye at a time, the back buffer only from the eye's camera to
its copy into the headset's image, so the HUD, menus, scope, crosshair
and names drawn into it after stay at full detail. A texture is first
foveated when the setting is not off (it cannot be undone); "off"
afterwards sets the gain to 0, full detail everywhere.
- The GL ES log line names foveation, GL_KHR_parallel_shader_compile and
the program binary formats the driver has; the first foveated target
logs the extension's features and focal points, vr.c the level and the
lens centres.
Checked: the APK builds; the desktop and web paths compile (no foveation
code outside HALO_ANDROID). Not tried on a headset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's base vertex extension (#273) changed the same lines of gl.h and
gl_initialize as this branch's half float render targets. Both are kept:
the extension's optional entry points and base vertex detection, then the
half float check, and the log line names both.
Checked: the tree is the same as this branch rebased on main; the Windows
release build has no warnings; a 20-second bloodgulch run with HDR and GL
debugging had no GL errors; the menu generator test passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts:
- rasterizer.h: upstream raised the transparent geometry groups to 2048
as we had (halo_port_capacity.h); theirs.
- d3d8_gl.c: the ES capabilities' log with upstream's base vertex and our
multiview.
- p2p.c, p2p_internal.h, p2p_signal.c: upstream's several joins and
reconnect, with our join codes' state and the codes' and public list's
updates each pass.
- network_server_manager.c: a machine downloading the map is not refused,
and the bots' room logic stays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Network version 28: the host now holds a downloading machine's players out
of the game, and keeps a multiplayer map's first scripted BSP. Dynamic lights
and the flashlight stay on past the Xbox limits, and a second weapon picked
up while firing is readied. Machines of version 27 would disagree on these.
- A large co-op game's respawn fallback no longer picks a teammate who is in
the air, outside the BSP, or riding an AI vehicle.
- A machine still downloading the map times out when it goes silent.
- Held players join through the late-join queue, so they get the same checks.
A machine with no held players is started instead of being refused.
- The co-op teleport uses world positions, which are right for seated players.
- Overshield drain no longer cuts off a hit's flash, and hits over 0.02 flash.
- The menu XML is the generator's (Custom Edition's layout) again.
- port: markers on two new declarations.
Checked: ninja linux, android and web build; tools/harness,
test_touch_menu and test_web_build pass.
Every transparent thing a frame draws takes a group: each transparent
part of a model, each widget (light volumes, antennas, flags, glows),
each batch of dynamic geometry (particles, contrails), each transparent
surface of the structure. The Xbox's 384 were a limit the native builds'
larger pools of objects, widgets, effects and particles could pass: a
frame past it was drawn without the rest, and debug.txt said "too many
transparent geometry groups". A part refused a group has no sort record
(models.c checks the group's index), so nothing else changes.
Checked on Windows in b30 co-op with eight times the enemies and a bot,
75 s a run: origin/main overflowed in 3 runs of 4; a build counting the
groups asked for showed up to 464 in a frame, while the dynamic triangle
and vertex buffers peaked at 123 and 328 of their 1024 and the dynamic
triangles at 10272 of 49152, so they are left as they are. With this
change, the same count showed 391 in a frame and no overflow; two more
runs had none. The pool is 2048 * 160 bytes, malloc'd; the model order
pass keeps 14 bytes a group on the stack (28 KB). Harness: the
transparent_plane_order tests pass at the new size (natively on Windows;
the other 44 failures there are the same on origin/main, POSIX headers).
Android and web builds not built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hosts send players who join without the game's Custom Edition map the map
and the resource maps it needs; the other players help, torrent-style.
Network version 27: new map sharing messages, and players of an internet
game now reach each other.
The back buffer and its multisampled renderbuffer become GL_RGBA16F
(GL_HALF_FLOAT on Android) when display.hdr is on, so blended light past
white is kept instead of clipped. A tone mapping pass over each window's
3D view, before the anti-aliasing and the HUD, brings it back into range:
neutral (identity below 0.8, a hue-preserving shoulder above), ACES
(Narkowicz) or extended Reinhard, after display.hdr_exposure. It writes
colour only, leaving the game's destination alpha alone.
The display blit becomes a draw that clamps to 1, or, with
display.hdr_output on desktop, writes scRGB to a floating-point window
up to the display's HDR headroom (SDL's window HDR properties); a driver
without such a window falls back to 8 bits. Android turns HDR on only
with ES 3.2 or GL_EXT_color_buffer_(half_)float and caps MSAA at the
samples RGBA16F allows. Settings are read between frames; toggling
display.hdr recreates the back buffer's storage.
Checked: the Windows build (clang, no warnings); bloodgulch and a30 with
MSAA, FXAA and SMAA, toggling each of the settings while playing with
HALO_GL_DEBUG, without GL errors; the frame rate unchanged, the tone
mapping pass 0.04 ms at 1080p; neutral leaves the picture as without
HDR below 0.8; display.hdr_output on an HDR OLED display. The Android
guest's sources pass clang -fsyntax-only for arm64_32; neither Android
nor the web build was run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>