On Damnation two of three of the bots' path searches failed (1078 against
527 in 80 s), and the goal's floor was not found 357 times: a search had
to reach the goal's surface itself, and the floor under the goal was
looked for 3 world units down. As OpenCE's bots do (bots.c), a search now
goes to as near the goal as it reaches (the bot searches again from
there), and the floor is looked for 6 units down. A path that ends short
of a goal above the bot takes a ladder near it (now within 8 units).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bot whose way is not found twice running, or that is stuck, with the
foot of a ladder going up within 5 world units, walks to the foot, turns
to the ladder and walks into it (as a biped climbs a climbable surface,
bipeds.c), and on past its top a moment; then it finds its way anew. Ten
seconds without getting up, it gives that up for fifteen. The ladders are
the level's climbable upright surfaces, near ones facing alike merged,
found the first time one is wanted in a game. Reported: bots stuck in a
gap of Damnation's that only a ladder leads out of.
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>
Previously the README did not name OpenCE, list what the port adds, or say what players need before they start. It now leads with that and moves the developer material to the end.
- An introduction naming OpenCE (Open Community Edition), with the icon, Download / Play in browser / Discord buttons and an updated screenshot of the latest version.
- The disc image comes first, then the downloads, with a Guide column linking each platform's README in place of the Platforms section.
- Multiplayer, Custom Edition maps (with Halo PC's own maps), mods, and a table of the graphics options and their defaults.
- Testing, optimisation profiles and profiling builds under "For developers".
- Credits naming the upstream projects and main contributors.
- A disclaimer that OpenCE is an unofficial fan project, as on the web site.
#319 put FRIENDLY FIRE in place of SETTINGS, written when co-op's pause
menu had no SETTINGS; testing now gives network co-op SETTINGS through the
campaign's pause menu, so as merged co-op lost it. pause_list_patch now
adds SETTINGS always, then END GAME for a multiplayer host or FRIENDLY FIRE
in network co-op (still two buttons, the four tags reserved), and
pause_settings_patch runs in co-op again. Six rows at the PC pitch need a
box 66 taller than the stock 159, which had no frame (only 195, for five),
so tools/port_settings.py draws the pause and objectives boxes at 225 too
(the same redraw), and pause_campaign_grow_box picks the frame by height.
Single-player and split-screen pause menus are unchanged.
Checked: menu_tags.c and menu_functions.c with -fsyntax-only for Linux,
the Android guest and the web; tools/harness and test_touch_menu.py pass.
On Xvfb, d40 with co-op forced in a local build showed the six rows and
the 225 box with the prompts under them, FRIENDLY FIRE switching
network.coop_own_friendly_fire; a10 still logs five rows (36 more, 195)
and bloodgulch SETTINGS. No network game was run.
"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.
Such an effect draws from the game's random seed, which every machine must use alike; played in place of the projectile's own on this machine only, it would change how the seed is used there (on a host, for everyone). overshield_effect_hit_effect_allowed refuses it, with effects.c's flag bit named where its enum cannot be reached. Checked: overshield_effect.c compiles without warnings.
Seeing around the vehicle from behind it is an advantage the other players of a multiplayer game do not have (raised in the PR's review), so reference_seat refuses while a game engine runs. The README row and the menu help say so. Checked: passenger_camera.c compiles without warnings; the FOV and Viewmodels screen written again by port_settings.settings_files() is the one committed.
display.anisotropic_filtering defaulted to 4, which changes the stock look on every platform, phones included; the rules want a departure from stock to be chosen, and the author offered 0. The setting's comment, its README row and the TEXTURE FILTERING help (no longer "4X costs very little") say so. Checked: the Graphics screen written again by port_settings.settings_files() is the one committed.
With audio.channels "auto" by default, a desktop whose device reports six or more channels (an HDMI sink, a virtual 7.1 headset) changed its mix unasked; the rules want a departure from stock to be chosen, so the default is "stereo", and an unknown value plays stereo too. audio_start set audio_device_name twice. tools/harness/audio.py copies MAXIMUM_OUTPUT_CHANNELS, which the 5.1 mix's structures now use, so the sound stream tests compile again. Checked: dsound_sdl.c compiles without warnings; the harness's sound tests pass.
distributed_correct_own_unit now times the host's echo of a prediction for the latency meter (distributed_note_own_round_trip), which tools/harness/tests/elevator_correction.c, taking the function from the source, did not declare, so it did not compile. Checked: test_elevator_correction.py passes (14).
Co-op players choose their own friendly fire, and one away is hurt by all
Network version 32, which #257's policy raised and this has not been pushed apart from, also carries the friendly fire choice in a client's input (network_format.txt recorded again).
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.
On testing, gametype_option_help also holds TEXT CHAT at OFF while the game is listed (TEXT_CHAT_SPINNER, TEXT_CHAT_OFF, p2p_hosting_listed), which the PR's harness test, written before it, did not declare, so coop_scaling_menu.c did not compile. Checked: python -m pytest -q tools/harness -k 'pickup or vehicle or coop or scaling or menu' passes (58).
The PR's Graphics screen was edited by hand: tools/port_settings.py did not name display.match_timer among the Graphics rows, so the generator put MATCH TIMER on Video Setup's own screen, and test_web_build's check that the menus match their generator failed. The setting gets its row in the settings table of port/linux/README.md. Checked: the screens written again by port_settings.settings_files() are the ones committed.
Since the check of the maps folder (e075fffa2), a game whose maps folder has no ui.map says so and quits, so every StageKit run stopped before its map loaded and CI's Linux test step failed (16 tests). The runner now writes the eight bytes the check reads ('head' and the Xbox's version 5); nothing in a run reads more of ui.map, as it had none before. Checked: python -m pytest -q tools/stagekit/tests with the --portable --release build passes (20), and fails the same 16 without this change.
SDL_GetCurrentVideoDriver is not among the SDL functions the Android guest bridges, so the llvmpipe warning stopped the guest image from linking. The warning points at the Linux README's NVIDIA on Wayland, so it now has the same guard as the rest of #275. Checked: ninja android, linux and web build.
progress_bar_download_display, progress_bar_download_end and
progress_bar_downloading were for a download screen that is not merged; they
are removed and progress_bar_eachframe is as it was. Without loading.tga the
texture is no longer filled with clouds the port made: it is left as
created, black, as the game leaves it, so that no invented picture ships.
The disc image importers' comments say so.
Checked: node --test "port/web/tests/*.test.js", pytest tools/harness and
tools/test_web_build.py; the changed C files compile (-fsyntax-only) with
the Linux, Android guest and web commands; XisoExtractor.java with javac.
The loading screen (interface/progress_bar.c: a light sweeping from left to
right over a picture, with a glowing trail) drew nothing: it uses the
fixed-function pipeline (SetVertexShader with 0), which d3d8_gl.c did not
have, and its picture, loading.tga, is on no retail disc.
- d3d8_gl.c draws fixed-function immediate-mode draws with a vertex shader
of its own (nv2a_fixed_function_glsl), positions transformed by the
world, view and projection matrices; and keeps a front buffer (a 640x480
render target taking the middle of the frame at BlockUntilVerticalBlank)
for GetBackBuffer with -1, the frame before, whose blur makes the light's
trail. The screen is drawn in the middle 640 columns, as the menus are on
a wide screen.
- After it, the rasterizer's caches of the device's states and of its
vertex shader are made the device's again (rasterizer_forget_hardware_state,
rasterizer_vertex_shader_runtime_forget): it sets them itself; and
SelectVertexShader (a packed program) always leaves the fixed-function
pipeline.
- On OpenGL ES (Android, the web) the fixed-function shader turns
Direct3D's clip space as the others do (HALO_GLES); without OpenGL
(debug.null_renderer) it is not compiled.
- The picture is the Xbox's own: the retail default.xbe holds it (320 by
240, runs of 4-bit greys after its width and height), and importing a disc
image now writes it into the maps folder as loading.tga: port/linux/src/
xiso.c, the Android app's XisoExtractor.java and the web page's
xiso-worker.js. Without one (a maps folder copied by hand) the light
sweeps over clouds the game makes. No game data is in the repository.
- progress_bar.c also has a mode a download screen over the menus can use
(a share given, from 0, drawn over the menus, and let go of).
- A picture that cannot be written whole is not left cut short (the
loading screen would read past it).
Checked: port/web/tests/xiso.test.js takes the picture out of made-up
executables (and an image's default.xbe); the Java decoder gives the same
bytes as the JavaScript one; xiso.c's picture from a Rev 2 disc image
matched a separate decoder's byte for byte; the Linux CI steps and the
web build and its tests in Docker; the Windows release build.
[The disc image parts (xiso.c, xiso-worker.js, XisoExtractor.java and
xiso.test.js) redone on the rewritten disc-image readers.]
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 010110026e719c3e8c38d36b93d57cf67b616230)
The tool's documentation and comments compared an x86-64, x32 or arm64
build against the i686 one, builds the project does not have. They now
compare a build with a simulation, compiler or maths change against one
without, and the wrapper's example is a tracer rather than an emulator.
Copies outliving the tool: the copies ran in the tool's process group and
were stopped only by play()'s finally, so SIGTERM or SIGHUP to the tool
(which Python does not turn into an exception) left them running, and they
ignored SIGTERM. Each copy now runs in a session (a process group) of its
own, started with the stop signals blocked so that it is always noted as
running; the group is ended with SIGTERM, then SIGKILL two seconds on, on
every way out: returning, an exception, Ctrl-C, SIGTERM and SIGHUP (which
now raise), and atexit. Killed outright, the kernel ends the copies
(PR_SET_PDEATHSIG). Stopped by a signal, the tool says so and exits 128 plus
its number.
Exit status: the worst of the runs was max(), so an ERROR (2) in one run
hid a FAIL (1) in another. A FAIL is the finding and an ERROR only a gap in
it: any FAIL now gives 1, else any ERROR 2 (documented in the tool and
NETCODE.md).
--tolerance persist=3.5 raised a ValueError traceback; each value is now
checked (whole numbers, finite, not negative, drift_share at most 1) and
refused through argparse, as is an unknown name or a missing "=".
--window keeps the caller's DISPLAY or WAYLAND_DISPLAY (documented, and
--display names an X display instead); headless copies now get neither,
so that none can open a window on the caller's desktop. HALO_EXIT_AFTER, the
copies' safety stop, now counts --skip too, as it works headless.
Checked: tools/test_net_compare.py, 28 passed (20 new: the exit status, the
tolerances, the copy's folder, the displays, stop() ending a copy that
ignores SIGTERM and its child, and the tool stopped by SIGINT, SIGTERM,
SIGHUP or SIGKILL leaving no copy). With the maps: i686 (ninja linux)
against x86-64 (ninja linux_x86_64), bloodgulch and beavercreek with --swap:
4 of 4 games in step, exit 0 (drift 0.0%, 0.0%, 3.5%, 14.2%), with no copy
left running.
(cherry picked from commit a53d86ccc2216d0949a6c089f57f6e8bd534c93c)
The host's drift figures for net_compare.py (distributed_divergence in
network_distributed.c) were zeroed only as each second's line read them:
the last taken prediction's ticks and each player's latest input tick
stayed from the game before, so a game's first ticks could be compared
against the last game's, and before any game they were 0 rather than none.
network_distributed_new_game now clears them, the ticks to NONE.
The input's tick and the prediction's time are the client's word: a hostile
one near LONG_MIN made "newest - TICKS_PER_SECOND" and "time - 1" overflow
(undefined behaviour). Both are now guarded.
The harness's elevator prediction test (tools/harness) did not compile
since the drift figures came in (distributed_take_prediction notes them):
it now takes distributed_divergence's definition and
DIVERGENCE_APART_DISTANCE from network_distributed.c, as it takes the
functions, so it runs the real code.
Nothing the machines send or must agree on changed: the figures are the
host's log's only, so HALO_PORT_NETWORK_VERSION stays.
Checked: python -m pytest -q tools/harness, 293 passed (5 failed before);
ninja linux and ninja linux_x86_64 build; net_compare.py with both builds
(the next commit's message).
(cherry picked from commit dcfed92a3a7687755b50bcc075669addb9704c3e)
To show that a new build (the x86-64 one, an arm64 one) plays network
games with the i686 Linux build, and to keep checking it. It plays the two
together on the loopback, A hosting and B joining and with --swap the other
way round, each copy with a seeded bot, headless, and compares the per-
second logs of the network test: for every player, the host's view and the
client's at the same tick (positions, aim, vitality, weapons, rounds,
grenades, scores, life), within tolerances taken from how the netcode
predicts and corrects, and the host's drift figure (the previous commit).
It prints PASS, FAIL with the first divergent player, tick and field, or
ERROR, and exits 0, 1 or 2. It needs the retail maps, so it is a local or
self-hosted check: NETCODE.md ("Comparing two builds") describes it, the
log line's format and the calibration; README.md's new Testing section and
AGENTS.md list it. tools/test_net_compare.py tests its judging of logs
without the maps, in CI.
Calibrated with an i686 build against itself, 60-second games both ways
round on bloodgulch, beavercreek, hangemhigh, chillout, prisoner, ratrace,
longest and carousel, up to four games at once: 63 of 64 pass (one copy
crashed, reported as an error), and 12 of 12 with scripted hits and kills
(--events). A build of the same sources whose players run 0.5% or 2%
faster (a change made only for the test, not committed) fails all 16
games, at the first second compared. One whose gravity is 0.5% or 2%
stronger passes: it changes only the ticks in the air, which the drift
cannot tell from what identical builds show; NETCODE.md says so.
(cherry picked from commit 0adc286c6497bffe8edfe297181aa39c2b0bbb8e)
The network test's per-second line (debug.network_test) now ends with
"| drift <ticks> <apart> <mean> <largest>": on the host, the ticks in which
it moved a client's player one tick on from where it took the client's
last prediction, with the client's input of that tick, as the client did,
and how far apart the two moves were. Builds that simulate alike move the
player alike to the last bit, so this is what shows a build that simulates
otherwise; the positions the machines log agree either way, as the netcode
corrects every copy toward the host's. tools/net_compare.py (next commit)
judges two builds by it.
Only counters and the test's log line change: nothing sent between
machines, nothing a game does with them, so the network version stays.
Checked with i686 builds against themselves (0 to 13% of such ticks apart,
up to 42% on a loaded computer) and against one whose players run 0.5% or
2% faster (77 to 93%).
(cherry picked from commit 95b2ddf86d4abc279c4ed462789e0ff7a75a7881)
platform_pump_events returned at once when there was no window, and it is
where debug.exit_after (HALO_EXIT_AFTER) was checked and where SDL's quit
events, which SDL makes of SIGINT and SIGTERM, were read. So a game with
debug.null_renderer (the network tests, net_compare.py), or without a
display, never stopped by itself after debug.exit_after, ignored SIGTERM
and Ctrl-C, and never answered the updater's question with
debug.update_answer.
Without a window (debug.null_renderer, or after the window was asked for
and could not be made) it now checks debug.exit_after, reads the events for
a quit request and runs updater_poll when debug.update_answer answers it,
on the thread that first pumps (the game's, as each frame ends); the rest
of the events, of no window, are dropped. The time for debug.exit_after
starts there at the first frame. With a window nothing changes.
Checked with the null renderer (SDL_VIDEO_DRIVER=dummy, no DISPLAY) on
bloodgulch, ninja linux and ninja linux_x86_64: with HALO_EXIT_AFTER=8 both
builds exit with status 0 after 8.2 s ("exiting after debug.exit_after"),
where the builds before ran until killed at 25 s; SIGTERM at 10 s ends both
with status 0 in 0.1 s ("asked to quit (no window)"), where the build
before ran on.
(cherry picked from commit a5531f3e5774b29ce27602bad7c7c2468c2fc074)
The culling tests could be changed only with the console's
structure_culling, which did not last past the game's end. The setting
(HALO_STRUCTURE_CULLING) gives the starting bits, 31 (every test) by
default as before, read in render_initialize; the console command still
changes them while the game runs. Documented in the settings table.
Checked: tools/harness (640 passed, the structure culling tests among
them), test_linux_port's config section and menu settings tests, and the
changed files compiled with -fsyntax-only for Linux, the Android guest
and the web.
The change sent Mesa (radeonsi, Intel, the Steam Deck) and Intel's Windows
driver to read every query with glGetQueryObjectuiv, which Mesa's GL thread
makes wait for everything before it: that is why the counts were copied
into a buffer. Only AMD's proprietary driver (GL_VENDOR "ATI Technologies
Inc.") now reads them on the CPU; every other driver keeps the fenced
buffer. When the queries cannot be made, the game no longer exits: it logs
it and every visibility test reports fully visible. The new comments and
logs are written in the file's style, with the Mesa note restored.
Checked: d3d8_gl.c compiles with -fsyntax-only with the Linux, Android
guest and web commands from build.ninja (with -w removed), with no
diagnostics beyond those of the file before the change.
configure.py goes back to mode 100644, which the pull request changed by
accident. The help, messages and README lose their typos, AGENTS.md and
README.md mention --windows-cross beside `ninja windows`, and the Windows
README says that the SDK needs versioned Include and Lib folders, which a
plain xwin splat lacks. The StdDef.h shim is linked again when a dangling
link is left behind, where os.symlink raised before.
Checked with python configure.py --help and a configure run, and
py_compile of the changed scripts.
OpenCE moved from CC0 1.0 to AGPL-3.0-or-later (4b09168e7), and Ringfall:
Mjolnir VR moves with it: README's Legal section and THIRD_PARTY_NOTICES
say so (releases before this one stay CC0 1.0, LICENSES/CC0-1.0.txt).
Its disc image readers were written again from a description of the
format, extract-xiso's code and notice gone. The app's (XisoExtractor.java)
is OpenCE's new one with Ringfall's language folders (maps_es, maps_it)
added again; the Steam Frame's (steam_frame_xiso.py) is written anew the
same way, the language folders kept; the bundle and the release job no
longer carry extract-xiso's licence. Also in: bnunu/halo-1's latest
matches, the web build's WebRTC and loading fixes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
map_share_restart.c stands in for what download_finish calls, which now
names the downloaded map in custom_maps' list (downloaded_maps_mark);
test_server_teardown.py's negative control finds the grace period's guard
as it now reads, with HALO_WEB.
The Android guest's C library cannot link pthread_once (its cleanup
handlers are not there), so the picked log_address.c stopped the Android
build from linking. The salt is made once by the first thread to log an
address, the others waiting for it.
The picked netcode checks add DISTRIBUTED_TIME_WINDOW_TICKS, which
network_format.txt records, and they and the second copy's ports change
what the machines take from each other: version 31, not yet released,
says so in NETCODE.md.
Several of the fixes taken from the forks changed source/ without the
port: comment that marks a change from the original (the second copy's
datagram ports, the downloaded maps' refusals, the main menu's clock,
the one-magazine HUD states, the render lists' sizes and others), and a
comment still named a function of that fork's. Each change now says so.
A tick waits for an input update with an action for each player, and the
menus change who the local players are (signing profiles in and out, a
lobby's network game owning the queues). In a lobby, or once a client's
clock waited for a game, the main menu's scene stopped ticking; and a
screen flagged to pause game time (Mods, the map and gametype screens of
setting up a game) paused the clock that runs the scene, which then
started again from its beginning as the screen closed.
The main menu's scene now always ticks on this machine's clock
(game_time_menu_scene), its players idle where the queues have no input
for them, and at the main menu those screens no longer pause it. The
distributed netcode's per-tick work is not done for the scene: a lobby's
machines do not share it.
(cherry picked from commits 8444785804f8cfb413020da5a03fc5dd65f918e9, 6267d199117f87e56a3f41fc8932966a39a3feb2
and 3a4097bb48f34245b750028a5c0ae28e03bae037, made again on OpenCE's game_time_update,
which has no separate clock connection for the scene)
The high-res HUD draws a message's controller button by its place among
the HUD globals' button icons, but only once the map's message icons
bitmap had a picture at the button's sequence. Halo PC's message icons
have 13 sequences to the Xbox's 24, so on a Custom Edition map a prompt's
button past them was not drawn at all, and the rest only because the map
had some other picture there ("Hold [rocket launcher] to swap" on
coldsnap, with the high-res HUD off). The high-res button is now tried
first. With display.high_res_hud off, a Custom Edition map still draws
its own pictures.
Found in commit 4c3e74cf29341ff344aca09febc0fab7e1d19919, which instead
reads the Xbox's button pictures out of ui.map for every Custom Edition
map; that is not taken.
network_game_server_dispose keeps the Xbox's one-second grace period
(SleepEx) only for builds other than Windows, Android and Linux, whose
transports need none; the web build is no __linux__ to Emscripten, so it
still waited a second each time a hosted game ended. It is left out there
too.
(cherry picked from the server part of commit bea41d710988e9aeac6cec44f4fd69b5203906e2,
whose dispose change OpenCE already had for the native builds)
_data_packet_verify read an uninitialised field_size for a first field
outside the packet's version, and gave later ones the size of the field
before them. No field of the game's packets has a version limit, so no
packet changes; the size starts at 0.
(from commit c01c68537bcbda79f0e4def4211cad611dfdee74's data_packets.c
change; the rest of that commit, comments rewritten, is not taken)
A maps folder holding Halo PC's maps (one pointed at a Halo PC install's,
say) had its ui.map, a Custom Edition or retail Halo PC cache file, loaded
as the Xbox's: game_load() failed, and the player got a blue screen.
Before the window opens the desktop builds now read ui.map's header: when
it is Halo PC's, or there is no ui.map, the game says where the Xbox maps
go (the data root's maps folder, named in full) and that Custom Edition
maps go in custom_maps beside it, and quits. Halo PC maps in maps/ itself,
which play only from custom_maps, are named in a warning. Runs with no
window log it instead. The README's game data section says so.
(cherry picked from commit 223c4aaf16b2819c57cd31d470d5dd04c9daa4f7, made again for
OpenCE's custom_maps folder in place of that fork's maps/ce and md_maps,
and not on the web build)
A Halo PC map's animation graph may have more nodes than its model
(beavercreek_rev_beta's cyborg has a graph of 20 on a model of 19), which
Halo PC's engine posed past the biped's node matrices. Such a dead body
was left unrelaxed here. It is now posed in a copy of the biped's node
matrices with the graph's every node, the extra ones where their parents
are, and the model's taken back.
Reported by Sabriel.
(cherry picked from the limp body part of commit 5fdbccf6cdf4f5be4bcaf1b90f4fe712877ddb4b;
OpenCE's checker already let such a graph be, and its own check of the
nodes is kept for the counts and links)
The new profiles pick took its side of a conflict in port_config.c whole,
and with it game.move_old_map_folders, which moves that fork's Halo PC map
folders: OpenCE has no such folders, and nothing read the setting.
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)
Mbed TLS is built without locks of its own, and on Linux two threads use
it: the self-updater's (posix_update.c, its update check at start-up) and
internet play's (posix_dtls.c, its certificate and its handshakes with
browsers). Two threads setting up its PSA crypto or drawing on its random
generator at once could free a hash context under the other and crash.
The Linux build now defines MBEDTLS_THREADING_C and
MBEDTLS_THREADING_PTHREAD for Mbed TLS and its users, which makes its PSA
core and random generator take their own locks (Mbed TLS 3.6); its config
is left unchanged. The other builds use it on one thread.
Ported from commit 04fa0517b32cde1c52ead7a2f21d249d0c5459e2, which made
the threads take turns with one lock held over each whole request; here
the updater's download cannot hold internet play's thread up.
The Join Game server browser shows each ping coloured: green under 80 ms,
yellow under 160, red above. A text colour hook of the menus'
(pc_menu_text_color) gives the ping its colour; the rest keep their own.
(cherry picked from commit 335c6522e9fb7aa33762e3f2cf27c518ac0e48ce, without that fork's
Online Games screen, the thresholds and colours kept in menu_functions.c)
The PING column showed nothing, as a host was reached only on joining. The
browser now probes each listed host as a join would reach it: the tunnel
made with the listing's invite (a signalling request of its own token,
answered on its own topic), the shortest of three pings kept, and the
tunnel closed again. Hosts on any version answer, as it is the ordinary
join. A few are probed at once, each again every two minutes and on
REFRESH. The game sees nothing of a probe's peer, and a join of a host
being probed takes its tunnel up. A game with a password is not probed:
its listing hides its token. Direct Link's games show the open tunnel's
ping.
(cherry picked from commit 98889fc72bc8cfd765b765d6e86c973c2040f841, merged into OpenCE's
internet play: its retired sessions keep their identifier, and its
server browser's NAT advice is kept in the README)
SDL drives a DualSense or DualShock 4 itself through its hidraw device,
which only udev rules (Steam's) open to the player; without them the
controller was not found at all. With input.playstation_kernel_driver on,
where the kernel has its driver of them (hid-playstation, hid-sony), the
game leaves the controllers to it: their buttons, sticks, triggers and
rumble, through the input devices a player can read, but not the
lightbar or the motion sensors SDL's own has. Off by default.
(cherry picked from commit fa1ff85ebd46ce183522baa5a91c9a29c9006c9f, made a setting, off
by default, as it gives up what SDL's driver has)
In Join Game's server browser a click selects a game, its row in the
yellow-outlined picture Custom Edition draws for it, and a click on the
selected game, a double click, A, Enter or JOIN joins it; one click
joined it before. The keys move the selection.
(cherry picked from commit 472d232246568ed2e3ee56bc93d97a3530feea54, without that fork's
Online Games screen; OpenCE's rows already take the focus only from a
click, not from the pointer passing over them)
An old build sees none of the public games (each list leaves out other
network versions), and said only that there were none. The hosts heard
listing a game of a newer version are now counted (by their keys' hashes,
forgotten as a game is), and the server browser's empty list says so:
"2 games are on a newer version: update the game to join".
(cherry picked from commit 4a4a4134519b76a5bcfd11e0e47cf397d7a228a2, made again on OpenCE's
server browser, whose listings come through p2p_lobby.c; that fork's
counted its own game list's)
The debug build this port comes from unlocked every campaign level on
every difficulty for each new profile. Retail Xbox Halo does not: a new
profile starts at The Pillar of Autumn and each level opens when the one
before it is finished. New profiles now start that way.
- game.unlock_all_levels (off by default) keeps the old behavior for new
profiles, for those who want it.
- profile_reset_campaign in the console clears the current profile's
finished levels and last played level, after a confirmation
("profile_reset_campaign yes"). Profiles made by earlier versions had
every level open and cannot be told apart from a real full clear, so
nothing is changed automatically.
(cherry picked from commit 224d9a757575509efb0dedab094bd0d34df59e96, without its top README
table, which OpenCE does not have)
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 first frame that drew each HUD picture decoded its PNG, which held that
frame up. On the desktop builds a map's HUD PNGs (and its sprite sheets')
are now decoded as the map loads, on a thread of their own, so that the
first frame only uploads them; what is decoded and not drawn is let go when
the map is unloaded. Android and the web build decode each when it is
first drawn, as before.
(cherry picked from commits afa4feaee4935b118dbc6d94140587842205cbfe and
27a22a2ded1bc78323f7f4bf35033dd843f7b956, made again on OpenCE's high-res HUD, its sprite
sheets queued too, and not on the web build)
The full reference list pick left a light's clusters out under
HALO_SERVER, its dedicated server's define, and the debug.txt roll's
header check under HALO_XBOX_CONSOLE; OpenCE defines neither, so both
were dead or always true. The comment no longer cites a dedicated
server.
A dedicated server's debug.txt grew 2 GB in 12 hours, almost all of it
"maximum light cluster references per map (8192) exceeded", written each
time a light could not join a cluster: thousands of times a second on a
busy map. A full reference list now says so the first time a map, and the
times after are counted and told when the map's lists are let go or made
anew. What fits in the lists is as before.
The dedicated server draws nothing, and a light's clusters are only used
to draw (the lights of an object or the map, and a unit's illumination,
which only debug_unit_illumination reads, from the lights drawn that
frame). The server (HALO_SERVER) no longer joins lights to clusters, so
it does none of that work each tick and never fills the lists.
(cherry picked from commit 50cc6274fb5f7697c6f3c25394e15e22f87106f2)
A line the same as the one before is counted rather than written again
("last line repeated N times", when a different line comes). When
debug.txt passes 64 MB it is renamed debug.old.txt (replacing the one
before) and a new one starts, with the header again for crash reports'
addresses. A game left running for days had no limit to its log.
(cherry picked from commit d4b46c829dcdaa53ee72d7d2901bc166115b0e69, without its dedicated
server README; the web build's copy of each line to the page's log is
kept)
The desktop builds now name a config.toml.txt beside the real file
(Windows hides the .txt an editor may add, so the file looks right and
does nothing), and Linux names a config.toml in the folder the game was
started from when that is not the executable's:
settings: .../config.toml.txt is not read: Windows saved it as
config.toml.txt; rename it to config.toml
(cherry picked from commit b4f73a749f62cc5ed28092b7155d04638f5bb84d, without its macOS
check, and not on the web build)
display.mode = "Windowed" was not "windowed", so the window followed
display.fullscreen instead, without a word. The settings that take one
of a few words (display.mode, resolution_scaling, anti_aliasing, menus,
player_names, the chat, voice, co-op and streamer words,
crash_reports.upload and others) now take them in any case, and one that
is none of them is logged once:
settings: display.mode = "Fullscreenn" is not "fullscreen",
"borderless", "windowed" or empty; using display.fullscreen's
(cherry picked from commits b1d3eacadbc4047f6294c324b7d8e23009d3f2c1 and
80196e309a8af878309151cbec6794b8386759a8, made again with OpenCE's settings' words and
the port's own case compare, which the Android guest has)
One TOML mistake, such as an unquoted word or a capitalized False, put
every setting back at its default. The line the parser stops at is now
skipped (in memory; the file is left as the player wrote it) and the
file read again, up to 16 times, so every other setting still counts.
Each skipped line is logged with its number and the likely fix:
config.toml line 87 cannot be read (invalid value), so it is skipped:
true and false are lowercase, high_res_text = false
config.toml line 100 cannot be read (invalid value), so it is skipped:
text needs quotes, menus = "pc"
(cherry picked from commit b3ee742ce7aef353e3f06af72b7cf83fff23544d, made again on
OpenCE's config_load: the file is not completed or rejoined while a line
of it is skipped, and the case compare is the port's own, which the
Android guest has)
A one-magazine weapon's second magazine, read as empty, still set off the
secondary states: the secondary trigger ready, flash, reload and "no ammo
for reload" crosshair states, and the draw states of its total and loaded
ammunition (flashing, disabled), which coldsnap's and hugeass's master HUD
draws as NO FUEL beside the weapon's own NO AMMO. These now apply only to a
weapon with a second magazine.
(cherry picked from commit 488bd83eafaabf26a7e392eaf8b240b1319e07c3; OpenCE already cleared
the state first, kept the overlays off and checked two of the crosshair
states, so only the rest is taken, and no CHANGELOG.md)
Only the 64-bit builds showed "In Menus" with the game's picture while not
hosting; the 32-bit ones cleared the activity, so Discord showed nothing.
(cherry picked from commit e69b5c9f48fb71282ef18efba8ae5c949ff55a7a)
A second game on one computer listens on another port and hears none of the
host's broadcast advertisements, and the host answered searches no more
than once each advertisement interval. Such a searcher is now answered
directly every time; the broadcast keeps its interval.
(cherry picked from commit b49c83d1c871a23f76825bdcdc52a228167f1c37)
The unreliable queue kept only a datagram's source address, so every
reader saw port 0. The host's answers to where a client's datagrams come
from (a second game on its computer, a direct answer to a game search) went
to port 0. The queue now keeps the port after the address.
(cherry picked from commit 781b98e9333c2cb85a85360267b24cc6656100c1)
Both games bound the client port (SO_REUSEADDR lets Windows share it and
hands each datagram to one of them), the host matched a distributed
datagram to the first machine at its address (its own), and sent every
client's per-tick datagrams to the fixed client port. The second game got
only the reliable messages: no units, no spawn, no co-op cutscene camera,
its camera left at the origin (the purple screen), and in PvP it waited
for space to clear.
A client now listens on any free port when another game has the client
port, the host takes a distributed datagram from the machine at its address
that isn't its own before its own, and answers each machine where its
datagrams come from. A game search from another port is answered there too.
(cherry picked from commit 0f4e71eba0787b8d3cd28c436cdb44ec6d79abda)
Hosting or joining with the intro movie still playing halted in
texture_cache_steal_memory (xbox_texture_cache.c #320): the movie holds
the texture cache's spare memory, which starting the map takes too. The
test now skips the movie, as a button press does, and waits for the menu
to settle again.
(cherry picked from commit 2d4a7deff6fda9bf3e8b9926a9353dd625be70b1)
(cherry picked from commit 47671d6b6ce24b693eb640c8398e78ef3e63a877)
A machine that asked to join as a game ended, before the server switched
to the pregame, was left waiting behind the one the server holds: the
server refused every later join with "network_game_add_player() failed"
and sat in the lobby for good. A host that starts its next game by itself
(network_test.c's host, which the dedicated servers run) never recovered.
Those joins are now dropped when the next game starts. Only the host's own
bookkeeping changes; nothing on the wire does.
From JoshRob297's halo-ce-touch (18a6fc58), reworked for our tree, which
has no network_game_server_port_set_minimum_players.
Co-authored-by: JoshRob297 <99092299+JoshRob297@users.noreply.github.com>
(cherry picked from commit 4582817b2bb4241b53bfa4c523f02aa267595feb)
(cherry picked from commit be7362f2d0b9983e3e12a25f5c76871be5e9132f)
A game with no brokers.txt beside its config.toml (one run from somewhere
the builds did not put it) had no brokers, so no invites and no listings.
Without the file, the game now uses the same list (a test keeps the two
alike); an empty file still means no brokers.
(cherry picked from commit e728f63b590affb701dfc0ad3bde803bedd3b1c1, without its dedicated
server, macOS and 64-bit build parts, which OpenCE does not have)
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.
Players post debug.txt in public, and a host's log names everyone who
joined it. Every address the game logs now goes through one helper,
log_address: a public one is written as a tag (addr#3f2a9c), the same for
the same address all run long, so lines still match up, with its port as
it is. Local network, loopback and internet play's stand-in addresses stay
whole. debug.log_addresses in config.toml writes them all whole, for
debugging one's own network.
Display and logging only: nothing sent or compared changes.
(cherry picked from commit c76df1d00a88072d50af64cde39fb957fa5b842c, without its
64-bit-only xnet.c line)
(cherry picked from commit 8b64b14a13ddaa573b09bcf2006a8450af8d9a04, without that fork's 64-bit Windows
job; the four hashes checked against today's downloads)
The Android build's musl download and the Windows build's SDL3 package
are refused unless their SHA-256 matches the pinned one, and the Android
build's SDL3 clone unless its tag is the pinned commit.
(cherry picked from commit 11d25e2b1b7570ff1ae5da1537f886be66d5924e, without
its macOS build script, which OpenCE does not have; the hashes and the
commit checked against musl 1.2.5, SDL3-devel-3.4.16-VC.zip and
release-3.4.16 as downloaded today)
audio.buffer_frames sets the audio device's buffer in sample frames on the
desktop builds: 512 by default, as before (11 ms at 48 kHz), and up to
8192 for a machine whose sound cuts out when the mixer is late (2048 is
43 ms). Android keeps its 1024 and the web build SDL's own.
HALO_AUDIO_BUFFER_FRAMES overrides it.
(cherry picked from the audio part of commit
580fd78feeffba4e1ada8510f617a0103a49ce5c; OpenCE's windows already draw at
their size in pixels, and its macOS default and build changes do not
apply)
48 of the high-res HUD's textures have bitmaps Halo Custom Edition lays out
alike (the same sprites, the same size or twice it), compared against its
bitmaps.map (port/assets/hud/custom_edition.json). In a Custom Edition map,
such a bitmap that is Halo PC's own, read from bitmaps.map, is drawn with
the texture without the pixels' check, as the port's HUD looks; a map's own
HUD art, and the 21 sheets laid out otherwise (the meters, the reticles),
are drawn as they are.
(cherry picked from commit 503e78331a6885571af662037a79e7a59f5c4937, merged
into OpenCE's high-res HUD, which already had the loader's stock tag check
and a larger texture table)
Some Custom Edition maps' tools left shorts big-endian, as tag files store
them: Hornet's Nest's assault rifle muzzle flash light holds function
types of 256 where the stock light holds 1 ("zero"). The tag checker
reset such a value to 0 ("one"), so the light's functions read as one
where the map meant zero. A two-byte enum of a Custom Edition map whose
value is past its field's values now takes the value with its bytes
swapped when that is one of them, else 0 or none as before.
Ported from commit cc70ed67f86bc20a8e172ddf000eda0b1ca2e3e1, which made the
same repair at run time in periodic_functions.c; OpenCE's checker corrects
the value as the map loads, before the game reads it.
The per-second line averaged every count over its 60 frames, which hides
the frame that stalls. It also tells the textures uploaded in those
frames (how many, and their size: a texture cache filling up uploads
them again as the view turns), and the most draws and the longest time
of one frame.
(cherry picked from commit 68e58d36cb5321e95f87d9b30581458b1df0aaad)
Starting a system link game on Blood Gulch in the Windows build stopped
on two colour range asserts: rgb_colors_interpolate's result (a
structure decal's random tint) and a transparent shader stage's
animated constant colour. Both blend two valid colours by a factor in
[0, 1], and the native builds' x87 code can carry such values (and
periodic_function_evaluate's 255*(1.0f/255.0f)) at more than single
precision, a hair past 1. The blends are pinned to [0, 1] before the
asserts, and periodic functions return values in [0, 1].
(cherry picked from commit 5c1e49f189e09212d6bc9568649b1cdb7b1e005e, with
the changes marked port: in place of that fork's HALO_LINUX, which OpenCE
never defines, and without its README 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.
Several of the fixes taken from the forks changed source/ without the
port: comment that marks a change from the original, and three removed
the notes on the original's bugs without saying they were fixed. Each
change now says so.
The campaign's level list and the multiplayer map list moved on whenever
their first or last row had the focus, so a mouse resting on either end
scrolled the list every frame. They now move on only when the d-pad put
the focus there (list_scrolls_at_end), as every other list in the menus
does. The host screen's listing spinner also checks that its option
exists before reading whether it shows.
(cherry picked from part of commit c75850e945093518881d1ffa184dd145967422e1,
a larger tidy of that fork's menus; only these two fixes apply to OpenCE)
Some European discs (the Spanish/Italian one, for example) have no maps
folder at the root, only one per language (maps_es, maps_it, ...), so
setting up from such a disc image stopped with "the disc image has no maps
folder".
When there is no maps folder, the extractor now takes the language folder
for the system's language (the desktop's LC_ALL, LC_MESSAGES or LANG; the
Android device's locale; the browser's language), else maps_en, else the
first one, and copies it to maps as before. debug.txt says which one it
took.
(cherry picked from commit 6cb6dfa7332934f6d86c7b4feefb645e76f977bb, made
again on OpenCE's disc readers, and in the web build's xiso-worker.js with
a test)
The portable build's own SDL was configured without XInput 2.1's scroll
classes: SDL's check links a test program against the system root's
libX11, whose libxcb the system root has not got, so the check failed.
Built that way, SDL still drops the wheel's button events as emulated by
XInput 2 but reads no scroll valuators, so under X11 the wheel sent
nothing. CMake's checks now link with --allow-shlib-undefined, and
tools/linux_sysroot.py requires SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO.
(cherry picked from commit 311f02bb50d4a0d2ffaf372a474ca0e8e98168c6, without
its 64-bit system root, which OpenCE does not have)
A binding names a key by where it sits (its scancode, named as on a US
keyboard), so WASD stays under the fingers on any layout, as Halo PC's
DirectInput keys did. The menus showed those US names, so on a German
keyboard the zoom key "Z" (the key right of the left Shift, labeled Y)
showed as Z, and pressing the key labeled Z to rebind it showed Y. The
menus now show each key by the character the keyboard's layout gives it
(SDL_GetKeyFromScancode); keys with no character of their own keep their
names. config.toml keeps the US names, so a config means the same keys on
any keyboard.
(cherry picked from commit 86bf862d0ebf47f252fb41a0a77fdd52f96615f1, without
its dedicated server stub, and with the settings table in
port/linux/README.md)
- An unknown setting whose name belongs to one setting says which section
it goes under (menus at the top of the file: under [display]).
- A file that cannot be parsed says every setting is at its default until
it is fixed, and that a word value needs quotes.
- port/linux/README.md says so.
(cherry picked from commit d474fac1feb9b7faa55229d9e48c91aadadfc5d2, without
its macOS check, and with its README note in port/linux/README.md's
settings)
test_xdk_headers_compile_for_the_game and test_p2p_signatures_and_listings
compile for i686 Linux, and the p2p one runs what it builds. On Windows they
failed whenever clang was on the PATH: no Linux system headers, and shlex
stripped the backslashes from ninja's command. CI runs them on Linux.
(cherry picked from commit 6aa077faf95dd270fb2cea1b4caa65ef67e96dca)
In a checkout whose path had a space, the portable Linux build failed in
SDL's configuration with errors that did not say why: CMake was given the
toolchain file's path unquoted, and SDL's build splits the system root's
include folders from pkg-config at their spaces. The paths are now quoted,
and the SDL step stops at once with a message saying to move the checkout;
port/linux/README.md says so too.
(cherry picked from commit 21e0aa6531e92547f0b8dc53120674da32207a1c)
debug.txt's gamepad line gives the pad's kind (ps5, xboxone, ...) and the device SDL reads it through: on Linux, a hidraw node when SDL's own driver has the pad (a DualSense's lights and rumble), an event node when the kernel's does. A player whose pad misbehaves can say which.
(cherry picked from commit ff238bfaae21b52a819de093e5141a028373e1f3)
"event handler function failed" said nothing of which: the gametype
editor logged it fifteen times. It names the function's number and the
widget's tag, taken before the function runs (it may delete the widget).
(cherry picked from commit 5f4494f42b1919dc1e38c6f8e168b646768e74f1)
"attempt to play a sound that was not a mono 22k compressed sound..."
now says which sound tag, and its compression, encoding and sample
rate, so a report of it can be traced to the map that has it.
(cherry picked from commit e859daf04476ad2b0608f130e9b63ac0cf7140a8)
(cherry picked from commit 9e9104b26907b748ace50e004fd5e9f2c0167b11)
The campaign's scripts print their progress and music cues ("mission
script is running", "levels\b30\music\b30_01", the dialog lines), which
the Xbox never showed. They are the game's chatter now, on screen when
config.toml's game.console_log is "all", like the rest of it; print and
inspect typed at the console still answer whatever the setting is.
(cherry picked from commit 114453cc1a84be4fc5622053d2afb57c1ae58e7f)
mark_written cleared a page's protected mark before making the page
writable. A second thread whose write to that page faulted in between
found the mark clear and took the fault for a crash: Windows passed it on
to the crash reporter, and Linux and Android reported a crash and put back
the previous handler, so the next watched write ended the game. The I/O
thread's platform_contiguous_write and the main thread's writes meet this
way on a shared page.
A page made writable by the watch is now marked so (2), and a fault on it
is retried; a page the watch does not know (0: freed, or not watched) is
still a crash.
(cherry picked from commit 57baaec22eef2332ee69b1b4a79686e3bb8f0e3d, made
again on OpenCE's watches without the fork's 64-bit and macOS code)
A build without a number, or one with update.auto off, now logs that it
does not look for a new version and why, rather than nothing.
(cherry picked from commit 05ee6d14c82009d0dd368adff01b96d63356f98b, with
OpenCE's conditions: a build number and update.auto)
The update question is SDL's message box, a system dialog, which crashes
the game under gamescope (the Steam Deck's Game Mode). There the game does
not ask: it writes the new version in the log, and the player updates from
the desktop or with debug.update_answer. gamescope is known by
GAMESCOPE_WAYLAND_DISPLAY, XDG_CURRENT_DESKTOP, or Steam's SteamDeck and
SteamGamepadUI together, which Wine passes on to the Windows build.
(cherry picked from commit 1339ca420f710882dacdf1151a63f96db6211c29)
The self-updater now refuses a download larger than it expects (the
release check at 1 MB, the zip at 256 MB, in place of one 512 MB limit for
both), stops unpacking a stored entry once it passes its stated size (an
inflated one already stopped), accepts only plain release file names, and
unpacks the zip from the one copy it read.
(cherry picked from commit 2045acc0b6d2fe2306d10c9cc66b4342ae8ef90e, without
its check of a release's version name: OpenCE reads a build number)
Players post their logs, and an invite lets anyone join while its host
runs. The hosting line and a new invite for a listed game now log the
link's first digits only; the whole link stays on the clipboard and in the
menus.
(cherry picked from commit e8c35a43bccc6047f1bf7494614876a0a6eb5d84, without
its macOS, startup-stash and dedicated server parts, which OpenCE does not
have)
It goes into a file's path on macOS and into the handshake; an id that is
not a number is ignored, as an empty one is.
(cherry picked from commit 56b38e49b7af6be0054f5c13eb446a4f36baaacc)
A STUN server whose name did not resolve was given all its attempts with
no address, and the only way back (attempts reset after a refresh's time)
needed an address. So a lookup that failed once, as when the game hosts or
joins before the network is up after a wake, left that server unused
until a restart: no public address was offered, and joins across NATs
failed unless UPnP forwarded a port. It is now looked up again every 25
seconds until found.
(cherry picked from commit 775aaf9adb92af58336b41010dc82cf397e67ade)
broker_sync_topics went on after a subscribe or unsubscribe whose send
closed the broker (a send error, or its output full), and recorded every
wanted topic as had. broker_close had just forgotten them, and nothing
on reconnecting does, so the next sync found nothing to do: the broker
stayed without the host, join, slot or query topics until they changed
again. It now stops when the broker is no longer ready.
(cherry picked from commit e4bc27dcf68ea0de841bb1c097465cd67009cfbc)
broker_publish_listing answered 1 even when the publish's send closed
the broker, so its caller recorded the listing's version as published.
Closing reset that version and the broker's will cleared the slot, but
the caller then set it back, and the listing was missing from that broker
until the next republish (up to 30 seconds) or a tombstone was skipped.
It now answers whether the broker is still ready.
(cherry picked from commit dfe3514d75a5ca79e0403796da45ad6dbf55dea9)
A browser took a retained listing only if its time was near its own, but a
live one at any age: anyone watching the brokers could publish an old, signed
listing of a host long gone again and fill browsers with dead games. A live
listing now has to be within an hour of this machine's clock, wide enough for
a host whose clock is off.
(cherry picked from commit fbdbd07300230e42ca92474b141eb8b78fae8683)
A joiner's offered addresses were all tried. Ones in 0.0.0.0/8, multicast,
reserved or broadcast, or with port 0, are now dropped before the host sends
anything to them. Loopback and LAN addresses stay, for two copies on one
machine or network.
(cherry picked from commit 69bd0d6a5669534a8b7fb3fa5d639af933c42381)
The picked change named the fork's file_contains and file_length, which
OpenCE's validator does not have: tag_validate_file_contains checks every
file of a Custom Edition map's offsets. The pixel data is moved to 0 only
once, so a map of no bytes cannot check its bitmaps forever.
A bitmap whose pixels its group's data offset put outside the map was given
the start of the file as a negative offset, which the bitmap's own check
then corrected again: the tags as corrected were not clean when checked
again. It is now given the group's data, moved to the start of the file
when the data starts at its end. Found by fuzzing.
(cherry picked from commit 6b38c3df8daf16ab134f81287404f94ba5b07f06)
The map check cut a gbxmodel's shaders to 32, the tools' maximum, and set
the parts that used the rest to shader 0, so they drew with the wrong
shader. The game reads a model's shaders only through their block, by its
parts' shader indices, which the check holds to the block; Halo PC's
engine took more (h3_foundry's spartan has 56). The block's maximum is
now the tools' (TAG_SCHEMA_TOOL_BLOCK), as for the other blocks Custom
Edition's tools went past.
Ported from commit accc904940b00dcdbea3418c3f34bea5748a9a1e, which made the
same change in that fork's own loader. Reported by Sabriel.
The packet table had begin game, graceful exit and keep-alive each on its
neighbor's field list (the Xbox game's table order is not their type
order), so a keep-alive's short was encoded and decoded as a long: the host
sent 2 bytes of stack past it, and a client wrote 2 bytes from the network
past it on its stack. The field lists now sit under the messages whose wire
they describe, and the keep-alive is a long. Not a byte on the wire moves:
begin game still carries a short (16 bits of its long), exit and keep-alive
a long, so OpenCE and earlier builds play as before; the keep-alive's 2
bytes that came off the stack are zero now. tools/test_network_messages.py
encodes every message under AddressSanitizer and compares the bytes with
upstream's table.
(cherry picked from commit ee32bdad42615402053291344e3c89f8bcc04161)
A checkpoint (game_save) waits for a safe moment and gives up after
240 ticks; it checks every 10. Those counts were advanced once a frame,
which on the Xbox was once a tick. At 60 frames a second or more a
checkpoint gave up in a few seconds or less, often before the fight it
waited for was over, so few checkpoints came: on Halo (a30) the player
could get only the first. The counts now advance once a game tick, as
the Xbox's did: 8 seconds at any frame rate.
tools/test_checkpoint_timing.py checks it at 30 to 990 frames a second.
(cherry picked from commit abc42998232b6261220a764e6de44641067ff2f5)
A screen's button bar (SELECT and BACK under a list) steps sideways, so
once the wheel scrolled past a list's last row onto the bar it went back
and forth between the two buttons until a row was chosen again with the
mouse. A touch drag already left the bar with up and down, as the d-pad
does; the wheel now does the same.
(cherry picked from commit 2ddad079fa5435a70a383d757de16b960c421505)
A tap on the touchscreen focuses a row and activates it in the same frame,
before the list's update has read which row has the focus, so New Game
started the level shown before (Pillar of Autumn on a new profile, as
players on Android saw), and the map and gametype lists took the entry
shown before too. Each list's choice is read again from its focus when it
is activated. A mouse hovers first, so the desktop builds were not seen to
do this.
(cherry picked from commit fba908137d3a3dfe8b61a5a84678f7c2bb4435b8)
Only the host runs the AI. An actor picks its unit's grenade type as it
gives it grenades, so on a client an AI unit kept no grenade type at all.
When the host's control told it to throw, unit_throw_grenade_begin found
no grenades of type NONE and the throw never began: a client saw no
grenade, then took the blast that the host's damage brought.
A client now gives an AI unit the type of the grenades it carries, as
the host's inventory says, when it has none or none of its type left,
and never in the middle of a throw.
(cherry picked from commit 92aaf6388fdfe37b640cacd162fd101dbae0ebe2)
debug_objects_unit_mouth_apeture placed its label at an uninitialized
marker when the unit had no head.
(cherry picked from commit a56597a9666a354a0a8f9e7f2e017cc418d4922a)
A cutscene that leaves the players their controls shows the black bars
without starting a cinematic; clients took the host's letterbox only while
a cinematic ran, so they had none.
(cherry picked from commit 43f646b586626fa8a154d87118abefe6d31d2ced)
A machine still downloading the map when the game started has its players held out of it until the download finishes (OpenCE's). One whose download did not finish before the game ended came back to the pregame without its players, as the reset dropped the held ones; they are put back in the game's list instead, as the other players are kept.
(cherry picked from commit 6aa90bfb59557b8c98e2c8543aa4c42d9e3918c1)
On a network client the overshield's drain arrives with the host's updates
after the HUD has taken its last shield value for the tick, so every frame
the shield looked a step lower than the HUD remembered and the shield
damage sound played for the whole overshield. The Xbox plays nothing.
The meter's flash already told the drain from a hit (a step of 0.05 or
less from past full); the sound now uses the same test, so a drain is
silent on host and client while a real hit on an overshield still flashes
and sounds.
(cherry picked from commit fd9c918313076ecc686209a0f4c75a6ef23340c4)
The kill announcement picked Running Riot whenever the killer's kills in a
row were a multiple of five, and zero is one. On the Xbox the killer had
just been credited with the kill, so it was never zero; a network client's
copy of the statistics can have the kill credited to no one or to someone
else until the host's statistics arrive, and announced Running Riot after
one or two kills.
A killer with no kills in a row now gets the plain kill message.
(cherry picked from commit 1b6cf950532f6d9590690c86c253def5395a132f)
build_game_variant_team_oddball left its variant uninitialized while
every other builder clears it, so the fields it does not set (the name
and the flags beyond those it sets among them) were whatever was on the
stack.
(cherry picked from commit 4c3e7664aab234e1b56a84a3b3dd3c8d1bebf58a)
A failed CreateVertexBuffer or CreateIndexBuffer may leave its output
unwritten, which was then tested uninitialized.
(cherry picked from commit 28f0a433093e4b7d6082123b45e19fd37beee262)
A ground contrail (or another type the renderer lacks) left its
triangle and vertex buffers locked and allocated, and the rasterizer's
lock operation set, for every frame it was drawn.
(cherry picked from commit b335386569e840c601eaf238d7585c73fa60516c)
(cherry picked from commit 56f5d99b1d6e81c30c4021c2437b2bd71475c83d)
A weapon's charging loop still on a channel as a network game ended
("still counted as playing ... let go", sound_cache_close) was finished
by the next map's sound_render against the next map's sound cache, which
halted the game as the next network game loaded (data.c: xbox sound index
is unused or changed). sound_dispose_from_old_map stops every channel
still holding a permutation, so its count is given back while the map's
sounds are there.
(cherry picked from commit 8e5b3f9f23beb9f015a0caf487caa82ada16c73e)
OpenCE's leak fix releases the surfaces the capture took; the device's
depth buffer may be none (D3DDevice_GetDepthStencilSurface's NOTFOUND),
and D3DResource_Release reads the resource it is given.
(cherry picked from commit ff654a774061c74d30f31ce986d7ebdefcafad74)
The virtual keyboard kept its keyboard tag from the main menu's map after
that map was unloaded, so Change Name in Settings from a game's pause
menu opened it on a stale tag and the game stopped. The keyboard is now
let go with the old map's interface, as its tags are; at the main menu it
is set up again, and in a game Change Name does nothing.
(cherry picked from commit 4c05c5b3237330669f55c2bdb123b7cde4e4e056)
When every player dies in network co-op, the host brings them back at
the last checkpoint without reverting. A revert restores the players'
all-dead flag from the saved game; this path left it set. With it set,
players_update_after_game neither respawns a player who dies nor ends
the game, so the next death, of one player or of all, left everyone
waiting for a respawn that never came. The flag is now cleared once a
player is back.
(cherry picked from commit 2e7c88b538b8aada54d0ce877740fb1be091e17d)
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 Xbox's postgame map select wrappers already carry
main_menu_if_no_history, with which the stock event code calls
main_goto_main_menu() when Back closes the last screen. The PC menus'
connected_map_select_wrapper, which menu_tags.c opens in their place,
was given the same flag by tools/port_settings.py in 4301ca88d, so the
hard-coded names and the network_game_abort() call in
widget_instance_go_back_to_previous were not needed, and they changed
stock split-screen behaviour (a split-screen game is a local server).
widget_instance_go_back_to_previous is as it was before 7d128e1bd.
Checked: the harness and touch menu tests (491 passed), test_web_build
(11 passed), ui_widget.c compiled for Linux and the Android guest.
Without the keyboard focus, SDL_SetWindowRelativeMouseMode only sets
SDL_WINDOW_MOUSE_RELATIVE_MODE, so platform_mouse_capture(TRUE) at window
creation or when a menu closed while alt-tabbed went into the fallback
(hidden cursor, grab, warp) where relative mode works. It now releases the
mouse and clears the flag until SDL_EVENT_WINDOW_FOCUS_GAINED, whose call
then tests relative mode for real (SDL_SetWindowRelativeMouseMode returns
at once only when the flag is already set). The fallback needs no clearing
of its own: SDL clears the flag when relative mode fails.
The click that took the mouse back is removed: it undid F12's release on
any click, and the click also reached the game. The fallback does not need
it. The Linux README lists lib32-libxi.
Checked against SDL 3.4.16's SDL_video.c, SDL_mouse.c and SDL_keyboard.c;
sdl_platform.c compiles (-fsyntax-only) for Linux (debug and release),
Windows (clang, i686-pc-windows-msvc) and the web (emcc).
The first start now uses a disc image found next to the executable
without a file picker, and where no file picker can be shown it says to
put the image there and quits, instead of asking again without end. The
READMEs still described only the file picker. Checked against
platform_offer_game_data and data_find_image in sdl_platform.c: the
folder of the executable is searched, .iso and .xiso match in any case,
the question is still asked, and Windows runs the same code.
NETCODE.md and the comment in distributed_client_delete gave a release
host's unchecked assertions as the reason a host deletes a flag or ball;
that cause has not been found. They now say it is not known and that the
client follows the host's word whatever the reason. The notes on the
skipped assertions also say they cover every deletion of
distributed_client_delete, the client's own objects the host has not told
it of included, not only the host's word. Comments and documentation only.
Only apply the configured window mode when it changes so saving sensitivity or other settings preserves the F11 choice.
Checked with eight local regression assertions against the latest testing baseline. The original behavior and a negative control failed as expected. Live gameplay and a full build of this baseline were not checked.
Create and validate typed query objects, retain each batch until its results
are consumed, and publish completed counts in submission order with the
scale captured for each test. Read available queries directly on the CPU
on AMD and other vendors, avoiding the mapped delivery path that lost AMD
results. On NVIDIA, preserve throughput with disjoint batch storage and
zero-timeout polling of a fence placed after all query-buffer copies.
Keep previous counts while work is pending and quarantine failed batches.
Consolidate the renderer changes developed in GulchCE through
b0c5d017 and 95748a74ce13a77444a7bb3bfe236bf97cfbb21d, as validated at
e5347765275bf324513eca4d044b15b3f6fc12b8, onto OpenCE testing 0110c160.
Only d3d8_gl.c and gl.h change. No MCC or fork features, GLES behavior,
network protocol, build files, tests, or documentation are included.
Validation: all 18 external delayed-GPU and negative-control regression
cases pass against this upstream tree; Windows portable release builds.
The implementation matches the AMD-confirmed and NVIDIA-benchmarked
renderer after excluding the three MCC additions in the fork's file.
Android/web targets are unavailable on this Windows host; branch CI
provides those builds. Android GPU runtime confirmation remains pending.
The bots make room for a person who joins, and the server browser's
listing counted their slots as free, but the LAN advertisement and the
join's check still asked for a free slot: with 15 bots and the host a
lobby of 16 was advertised closed, and a Quest on the LAN was refused
("attempted to join a closed game"). Both now count a bot's slot in a
lobby as a person's, as the listing does (network_game_server_room_for_person).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A press pauses as before, now as it is let go; held 0.4 s, the scores
show as long as it is, as the Xbox's Back. In the menus it is Start at
once. The Quest's controls page says so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chat's lines go at the top left on Android, where the touch controls
keep the bottom left free; VR has none, so it lays them out as the desktop
does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
The mixer outputs stereo or 5.1 (audio.channels: "auto", "stereo" or
"5.1"; HALO_AUDIO_CHANNELS; Linux and Windows). On 5.1 the speaker config
reports AC-3, as an Xbox set to Dolby Digital, so the game sends its 2D
voices to the center and back mix bins itself; the speaker mix bins 0-5
are SDL's 5.1 channel order. 3D voices pan with equal power between
adjacent speakers at the ITU-R BS.775 angles, spread out when close or
above, at the stereo pan's power; stereo voices positioned in the world
pan their middle the same way and keep their width on the front pair.
The voices' resampling, low passes and reverb send are the stereo mix's;
the reverb plays from the front and back pairs, the voice chat from the
front pair, and the limiter takes all six channels. Stereo output mixes
as before. Android and the web stay stereo.
"auto" is the Xbox's own behaviour: the game asked the console's speaker
setting and mixed for 5.1 when Dolby Digital was on.
Checked on Windows with a 6-channel device: "sound: 5.1 surround" is
logged and the device opens; with "stereo", and with no 6-channel
device, the output is the stereo mix as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Android the touch controls' overlay keeps every finger while it shows
(the look, outside its controls), so a tap on the keyboard never reached
the game. While the keyboard is up the overlay now hides, as it does for a
menu or a cinematic, and lets the fingers through: a tap on a key types it,
and a tap elsewhere closes the keyboard, as B and the other touch controls
are hidden then.
A tap's place is a fraction of the screen, which the viewport fills, so the
keys' place is now kept as a fraction of the viewport, not of the HUD's
window inside it, which is smaller on Android.
Checked on the x86 Android emulator (with #396's fix for its ARM
translation, not part of this change): the touch D-pad's Up, held as a
finger holds it, opened the keyboard at the bottom of the screen and hid
the touch controls; taps on h and i typed "hi", a tap on SEND said it
("chat: Player: hi"), the keyboard closed and the controls came back; a
tap away from the keys closed it too.
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>
A player's scoreboard menu (right-click, then the player) had Mute voice,
this machine's own choice. It is now Mute voice and chat: this machine
plays none of their voice and shows none of their lines, and tells no one.
It is offered whether or not voice chat is on.
The host's menu adds Mute for everyone / Unmute for everyone. It is the
chat's /mute for 20 minutes (every machine told "Milenko muted for 20
minutes"), and it now silences the player's voice too: the host relays
none of a muted player's machine's voice (voice_host_relay), as it relays
none of their lines. /mute from the chat mutes their voice the same way.
debug.network_test_chat now also has the host mute the last player for
everyone (network_chat_host_mute, as its scoreboard does) and unmute them,
and the last player's machine mute the host's for itself. On three Linux
machines over system link, with debug.voice_test's tone as open mic:
- the host's mute: no machine showed the player's line, they alone were
told the minutes left, and the host and the other client stopped hearing
their voice until the unmute (one second heard at the edge);
- the client's own mute: only that machine stopped showing the host's lines
and hearing the host's voice.
CI's Linux test step passes (484, 6 skipped).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The host's TEXT CHAT row (Server Setup > Teamplay Options; in co-op, Voice
and Voting; network.text_chat) is ON (the default: lines with links are
refused), WITH LINKS or OFF: one row, as Teamplay Options has room for one
more. A game listed in the server browser (p2p_hosting_listed: a public
internet game) never has the text chat; a private game, joined by its
invite, and a LAN game have it as chosen.
The host sends its settings to every client in a new message,
_distributed_message_chat_config (89, one byte of flags), as they change
and every 3 seconds. A client without them, as from a host without the
chat, has the chat off. Pressing T where it is off says "Text chat is off
in this game". A line the settings refuse is refused on the sender's
machine and again by the host, whatever the client knew, which tells the
sender why: a changed build gets no further. Older builds drop the new
kind, so the network version stays 29.
Links are found by chat_text_has_link: a scheme, "www.", four numbers and
three dots, or a name and a common top-level domain ("discord.gg/x",
"example.com"), with the dots also written "(dot)", "[dot]", "(.)" or "[.]";
"1.5", "e.g.", "U.S.A" and "wait.comrades" are not links.
TEXT CHAT on Settings > Audio > Voice and Text Chat (display.text_chat)
HIDEs the chat for purists and streamers: no lines, and T and Y open
nothing. /mute's minutes are 20 when none are given.
debug.network_test_chat now also has each machine say a line with a link
(a client's sent past its own check, network_chat_say_unchecked, for the
host to refuse), then the host turns the chat off and on again around one
more line each. On three Linux machines over system link: every machine
showed "Links aren't allowed in this game" and "Text chat is off in this
game" once, the clients' from the host, and no refused line reached anyone.
The settings screens were drawn and the TEXT CHAT row's help follows its
choice. The harness gains four cases and six negative controls (55 pass);
CI's Linux test step passes (484, 6 skipped).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No change in behaviour:
- chat_message_new builds a line's message (header and line), which
chat_host_send, chat_host_notice and chat_send each did by hand.
- chat_send_to_clients sends a message to every client machine.
- chat_line_text turns the typed line into wide characters too, in place
of the drawing's own loop.
- chat_player_name_length is the one count of a name's characters, for the
line's name and its ASCII copy.
- CHAT_DRAWN_SIZE and the comments about the chat's old filter are gone;
testing's streamer mode keeps a word's length, so a line's buffers are
CHAT_TEXT_SIZE.
The name match keeps its own lower-casing (chat_lower): the console's
network_game_server_name_begins_with is static in network_server_manager.c,
and the harness takes chat_name_match without the game.
Checked: the Linux debug build, the chat harness (45), and the three-machine
system-link chat test (lines, channels, /mute, /unmute) as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Testing's streamer mode (90dd65ca, text_filter.c) hides offensive words in
every string drawn, through draw_string.c, so it already covers the chat's
lines and players' names. The chat's own filter goes: chat_filter.c and
its harness tests, game.streamer_mode (which also shared
HALO_STREAMER_MODE with streamer.enabled) and the STREAMER MODE row on the
VOICE AND TEXT CHAT page. Settings > Network Setup > Streamer Mode is the
one toggle. The README's Streamer mode section now names the chat.
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>
On the x86 Android emulator every automated network test (debug.network_test
"join", "browse" or "host:<map>") stopped the app with SIGILL a few seconds in,
in hud_calculate_point. That was not an assertion: logcat's ndk_translation
line names the instruction, fcvtzs s0, s0, which clang makes of
(short)fast_ftol(point.x) (convert in the SIMD register, store its low half).
The emulator's ARM translation has none of the scalar conversions from
floating point to an integer in a SIMD register (fcvt{a,m,n,p,z}{s,u} s/d,
with or without fraction bits); the general-register and vector forms work.
The guest image had 141 of them, the HUD's the first to run once a game is
drawn. Devices run them, so only the emulator was affected.
tools/android_asm_convert.py now writes each as fmov to the destination, then
the vector form on it: the copy clears the other lanes, which convert 0.0 to
0, so the register and FPSR end as the scalar form leaves them (the game
reads FPSR through _statusfp).
Checked: on the emulator, each replacement against the general-register
form, 315 cases (every rounding mode, signed and unsigned, single and
double, saturating, NaN, infinity, denormal): the same register and FPSR,
except that the translation's own vector fixed-point form leaves out the
invalid flag on overflow (the guest has no fixed-point conversions). The
rebuilt image has none of the scalar forms left. debug.network_test =
"host:bloodgulch" now hosts and plays on the emulator (game started,
player states logged for 45 s, no signal); "join" searches for 150 s
without stopping. tools/test_android_asm_convert.py (new cases, and clang's
output through the pipeline), ci_build android debug and
tools/test_touch_layout.py pass.
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>
The host types "/mute <player> [minutes]" in the chat (5 minutes when it
gives none, 1440 at most), or "/unmute <player>". The player is found as
the console's kick finds one: the whole name in either case, else the one
player whose name begins with what was typed; none, or more than one, is
said to the host alone. Every machine is told "Milenko muted for 5
minutes" and "Milenko unmuted", as a notice in the chat: a down line with
the new _chat_line_notice_bit, its text alone, drawn in a pale gold
(white in the white style).
The host keeps a mute by the player's name in ASCII, so one who leaves and
joins again under it is still muted, and its minutes by its own clock. It
drops a muted player's lines as they come up and tells that machine alone
"You are muted for N more minutes". The mute ends by itself. A client's
/mute is shown "Only the host can mute players" and never sent, and the
host takes a line that came up as a line, never as a command. The wire
format and the network version (29) are unchanged.
debug.network_test_chat now has the host mute the last player for a
minute, each machine say a line, the host unmute them and each say
another. On three Linux machines over system link the muted player's line
reached no one, they alone got the minutes left, every machine showed the
mute and the unmute, and the clients' own /mute was refused. A drawn
client showed the notices in the chat. The harness gains ten cases and
eleven negative controls for the command's parsing, the name match and
the mute's time (412 pass).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Android the touch controls' Down button and Move stick are at the bottom
left, where the chat lines went (above the motion sensor). There the lines
now start under the weapon's panel at the top left, in the same column, and
go down: the line being typed first, then the newest, the older ones below
it. At most four show at once, the typed one among them: a fifth would
reach the tops of the Up and Fire buttons. The other builds draw them as
before.
Checked on the x86 emulator (host:bloodgulch: the test's three lines, two
typed with the keyboard, a third being typed): the typed one and the three
newest show, clear of the touch controls. Linux debug build and the
harness (391) pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Streamer mode replaced the words of its list only in the chat's lines and
names, but a player named with a slur showed it on the scoreboard, above
their head, in the kills and in the lobby. Every text the game draws or
measures now goes through the filter at draw_string.c's start, both
draw_string and draw_unicode_string, so names are replaced wherever they
show and the text's width matches what is drawn. A text's format codes (a
| and the character after it) are kept, each run between them filtered on
its own, so a code never joins the words beside it ("|t" before "its").
The same strings are drawn every frame, so a cache of 48 keeps it cheap; a
text too long for its entries is drawn as it is rather than cut short.
The chat no longer filters its own lines (the draw does), so the line you
type shows replaced too. "hoe" now shows as "rake": "garden hoe" held the
word it replaced.
debug.network_test_name names a machine's player in an automated test
game, to test this.
Checked: tools/harness/tests/test_chat_filter.py (the format codes, the
cache, every replacement passed through the filter again unchanged, with
negative controls); a host on Xvfb, streamer mode on, named "fuckhead",
with a client named "n1gger": the chat showed "fudgehead" and the
scoreboard "nugget" and "fudgehead", while debug.txt kept the names as
they were; the CI Linux test step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PC version's chat could talk to everyone, the team or the vehicle.
While a line is typed, Tab now turns it to each in turn ("Say:", "Say to
team:", "Say to vehicle:"), as T and Y still start a line to everyone and
to the team. A vehicle's line goes to the players riding in the vehicle
the sender rides in: the host finds each player's outermost parent object
(a unit in a seat is its vehicle's child), so any map's vehicles count,
Custom Edition maps' too, and a sender on foot sees their line alone. The
host now always sends a line back to its sender's machine. The channel is
a second bit of the line's flags, so the wire format is unchanged.
debug.network_test_chat has the host seat the last two players in one
vehicle and every machine say a line to it.
Checked: tools/harness/tests/test_chat_logic.py (who hears a vehicle's
line, the channel through the flags, with negative controls); three
machines on system link, the two clients seated in one Warthog seeing
each other's vehicle lines and the host's, on foot, seeing only its own;
a host on Xvfb turning a line to the vehicle with Tab, and typing a line of
f's without meleeing; the CI Linux test step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
display.chat_style (CHAT TEXT on Audio Setup's VOICE AND TEXT CHAT page)
picks the chat's colours: "default", the player's name in their team's
colour and the text a pale blue, as the PC version drew them, or "white",
all of it white, as Custom Edition. The line typed follows it too.
Checked on Xvfb: a host's lines drawn white, and the page with its new
row; the CI Linux test step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Streamer mode needs a row a player reaches in a game, from the pause
menu's SETTINGS. Network Setup is not offered there and has no room on
desktop, and Audio Setup has none either, so its VOICE CHAT and VOICE
VOLUME rows move to a page of their own, VOICE AND TEXT CHAT, opened from
Audio Setup as Video Setup opens GRAPHICS, with STREAMER MODE under them.
The screens are port_settings.py's settings_files and menus.json lists the
new one, both written as tools/ce_menus.py writes them (it needs the PC
version's tags, so only these were written: the other screens come out
unchanged). Checked on Xvfb: both screens drawn, STREAMER MODE changed with
the keyboard and OK writing streamer_mode = true to config.toml; the CI
Linux test step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A player who streams needs the chat on screen to stay clean whatever the
others type. game.streamer_mode (off by default) shows the slurs, swearing
and sexual words in the chat's names and lines as harmless, funny ones
(fuck: fudge, cunt: chop stick, faggot: flurry), keeping their case and
fitting their endings (fucking: fudging, faggots: flurries). Only what
this machine shows changes: the lines go to every machine as typed, so the
network is unchanged, and debug.txt keeps them so
(port/linux/game/chat_filter.c).
It finds the words in the spellings that dodge a plain list: digits and
signs for letters (n1gg3r, c00n, $hit), l for i, q for g, k for c, v for u,
ph for f and * for a vowel; letters and endings typed more than once; a
word spelled out with separators (n i g g e r); and a few words inside
others (sonofabitch, thundercunt). Each word may stand alone, with endings,
at a word's start or anywhere, and harmless words that hold one are kept
as typed (Scunthorpe, snigger, Niger, assassin, cocktail, rapper, FAQ).
Checked: tools/harness/tests/test_chat_filter.py (8 cases with 11
negative controls); every word of the repository's documents and of an
English dictionary of 370,000 words (dwyl/english-words) run through the
filter, the harmless ones it changed kept, and a community-submitted list
of 2,800 entries used to find gaps (neither list is in the repository: the
community one has no licence); a host on Xvfb with streamer mode on,
lines typed on its keyboard shown replaced while the client logged them
as typed; and the CI Linux test step.
No Settings row yet: Network Setup has no room on desktop and is hidden
from the pause menu's SETTINGS, so where the toggle goes is still to be
chosen; config.toml and HALO_STREAMER_MODE set it meanwhile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The text drawing gives every string a hard shadow, a black copy a unit
down and right; the PC version's chat had a softer one, further out. The
chat lines now draw theirs as translucent black copies one to three units
down and right, which add up to a dark middle with a soft edge, and the
text's own shadow clear. Checked on a host drawn on Xvfb against the
reference screenshot.
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>
Use the existing Monitor actor type to keep authored squad counts even when its encounter is hostile. Check the effective actor variant too, including starting-location overrides and major upgrades, before placing an extra actor. Ordinary placement and enemy squads retain their existing paths.
Ten focused regression checks pass, including negative controls for both guards. Only the host creates these actors; no message or protocol change is required.
The disc was placed for the weapons at the old fixed place (7 cm above
the hand, 22 cm ahead of it): with the pistol now held 20 cm further
back it sat at the muzzle. It moves as the weapon did (forward and up;
sideways the barrel places it, as before).
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>
Audio Setup had no room for another row (nine on the desktop, as Video and
Network Setup), so Reverb moves with the new Resampler row (SINC, the
default, or LINEAR) to a SOUND PROCESSING screen opened from Audio Setup,
as Video Setup's categories are; the devices and volumes stay on Audio
Setup. The setting is read again when it changes, so the TODO saying it
could only be set in config.toml goes, and the help, the setting's comment
and the README say what linear does: it lets more of the images above a
sound's rate through, which is what sounds brighter.
The mixer's harness test takes the resampler from dsound_sdl.c, which now
calls config_string: it stubs it, and a new case checks linear mode against
linear interpolation worked out in double precision at every rate (with a
negative control). Checked: the harness and touch menu tests, test_web_build,
ninja linux, android and web, and both screens drawn on Xvfb with
HALO_AUDIO_RESAMPLER=linear; the menus regenerated with tools/ce_menus.py
change only the two audio screens.
A mod's model can name a shader of any group the map has, but
shader_transparent_chicago_extended is Gearbox's addition for Halo PC and
the Xbox game has none: the note says to use a transparent chicago shader
instead, and that a Custom Edition map's chicago extended shaders are
converted to chicago shaders as the map loads.
The gametype's time limit ends a game, and its time left is drawn on the
scoreboard, but a game without one shows no time at all, and the
scoreboard is a button hold away. The clock players time the weapons by
was a stopwatch beside the television.
It counts up from the game's first tick, which is the tick the items'
respawn periods count from as well (game_engine_update_item_spawn's
game_time_get() % respawn_period), so an item is back on the map at every
multiple of its period on the clock: on Chill Out the overshield is up at a
minute, the rocket, camo and overshield at two. Elapsed rather than
remaining for the same reason: a game with a time limit is the one a
player most wants to time the rockets in, and its time left is on the
scoreboard already.
It is drawn at the bottom right, where the versions players know it from
put it, clear of the first person's weapon. The HUD's own text colour is
lost against a map as bright as it, so the clock is lifted toward white and
drawn whole. ticks_to_unicode_time_string leaves the minutes out below a
minute, which a countdown reaches only at its end but a clock counting up
starts at, so the digit is put back at the call.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01194FWhCaedcdGjEksr1H8w
The match clock is opt-in: Custom Edition's HUD has no match clock, so the
HUD looks as it did unless a player asks for one. MATCH TIMER goes on the
graphics settings screen after GLOW INTENSITY. The screen is as
tools/ce_menus.py writes it with the row. Nothing draws the clock yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01194FWhCaedcdGjEksr1H8w
That disc has no maps folder but one for each language, maps_es and
maps_it, and the disc image setup stopped: "The disc image has no maps
folder: it is not a Halo disc." The Quest's (XisoExtractor.java) and
the Steam Frame's (steam_frame_xiso.py, RINGFALL_LANGUAGE or the
system's) now take the folder of the device's language, else English's,
else the first, and write its language into maps/language.txt. The game
takes that as the Xbox's language (XGetLanguage) while game.language is
unset, so the menus speak it too. (Reported for ChupathingyCE, another
OpenCE fork, in its issue 145; written anew here.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Carry riders by the current collision transform rather than the previous tick's movement. Preserve supported riders' height through stopped-device predictions and delayed client echoes without suppressing normal jumps, departures or authoritative teleports. Local history only; network messages and protocol version are unchanged.
Validation on upstream main: 379 CPU/menu/assembly tests, including 32 focused elevator cases and negative controls; all three changed C files compile for Windows x86. Earlier native integrated-fork probes passed on b40/c40 at simulated 100/200 ms RTT through reversals and phase snaps.
(cherry picked from commit a38b7db0f4eb4ccc66f5445a6e9c4eef2fb0114a)
A scope's zoom (the pistol's, the sniper rifle's 2x and 8x, any weapon whose
HUD has a convolution screen effect) draws a warp: copies of the screen a
radius apart, blended outside the scope's mask, in two full-screen passes on
the Xbox. To keep the copies from standing apart as ghosts at high
resolutions, f815e1cc split the radius into as many times the passes as the
screen's pixels to the Xbox's one, up to six: ten full-screen passes (and
their target copies) at 2160 lines. On an M4 Mac mini in Chrome (ANGLE on
Metal) at 3840x2160 that took a frame from 17-22 ms to 34 ms with the
pistol zoomed on b30 and from 16.7 ms to 22-56 ms with the sniper rifle on
a50, all of it GPU time: with the screen effect off, a zoomed frame took
what an unzoomed one did, though zoom raises the draws (the level's LOD and
culling: 230-350 draws to 470-640 on b30) nowhere near enough to matter.
It also narrowed the blur: N passes at a radius over N have 1/N of the
variance, under half the Xbox's at 2160 lines.
The Xbox's passes are now drawn as they were, at the effect's radius, and
two more, at a third and a ninth of it, put eight copies between each two:
up to nine screen pixels to the Xbox's one (2160 lines are 4.5) they stand
no farther apart than the Xbox's did in its own pixels, two more again up
to 81. The blur's variance is 6% more than the Xbox's. At 640x480 nothing
changes.
Checked at 3840x2160 (headless Chrome, device pixel ratio 2): b30 pistol
zoomed 17-22 ms, as unzoomed (was 34 ms); a50 sniper rifle 2x and 8x zoomed
16.7 ms, as unzoomed (was 22 ms, 56 ms in one view). New harness test
(tools/harness/tests/test_screen_effect_passes.py): the pass count and
radii, the gaps between the copies and the blur's width at screen scales
1 to 1000, with negative controls.
Co-authored-by: Bridge <agent@bridgedesktop.com>
xgpu_texture_cache_begin_frame now also reads the high-res HUD's
settings (#378), drops an entry's upscaled sheet (#380) and forgets the
recent lookups. The test's fake world gains those members and functions,
with settings that never change. Its 3 cases and 4 negative controls
pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The one conflict was in xgpu_texture_cache_begin_frame's declarations.
Main gained the high-res HUD's refresh of cached bitmaps (#378) and here
the sweep counts time (idle_since, now); both are kept, declarations
before statements.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With multisampling, the tone map reads the resolved back buffer and draws
into its multisampled renderbuffer, so it need not copy the texture first:
the copy is kept only for the case that draws into the texture it reads.
The shader takes each pixel with texelFetch, and the copy was a nearest
blit of the same format, so what is drawn is unchanged.
Checked in a 48-machine co-op at 2560x1440 with msaa4x, uncapped, in
alternating windows of one game: the tone map's GPU time falls from 236
to 224 us a frame, and with the presentation's resolve from 270 to 228 us.
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>
Settings > Gamepads > Button Layout gets a sixth choice, Bumper Jumper as
the Master Chief Collection has it for Halo: Combat Evolved: jump on the
left bumper (the Duke's white), melee on the right (its black), switching
grenades on A, action and reload on B and the flashlight on X; the
triggers, stick clicks, Back and Start are the default layout's. The Edit
Profile picture, which shows the Xbox's pictures of its five layouts,
keeps the row's own picture for this one, of which the Xbox has none.
A layout the build does not know (a later build's) now gets the standard
buttons. With no case for it, every control was on A, start and back
with it, and input_abstraction_update_local_player_preferences' assertion
stopped the game, release builds too. Builds before this one still stop
when they load a profile saved with Bumper Jumper.
The buttons are mapped on each machine before its players' actions are
sent, and profiles are not sent (only names and colours), so
HALO_PORT_NETWORK_VERSION is unchanged.
Checked: tools/harness/tests/test_button_presets.py (every layout keeps
start and back and gives each control a button of its own, Bumper
Jumper's mapping, an unknown layout is the standard one, and three
negative controls), built 64-bit on macOS; the settings screens written
again with port_settings.settings_files(), which changed only Gamepads,
and test_web_build.py's and test_linux_port.py's menu checks; the units
that include player_profile.h and menu_functions.c compiled for i686
Linux against the portable system root. Not yet played with a controller,
and not built for Windows, Android or the web.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Recheck committed font settings before cached font selection, including when
high-resolution text was disabled at first use. At each frame boundary,
invalidate only cached HUD/title bitmaps affected by a changed setting so
both enabling and disabling rebuild the appropriate texture on next use.
Keep decoded replacements, font atlases, world textures and menu Apply/Cancel
semantics intact. This corrects shared cache behavior; it adds no MCC data,
map-family routing or networking/simulation changes.
Validation: Windows portable release build and 370 focused production-code,
HUD, menu and MCC checks passed. Tests cover repeated toggles, disabled text
at startup, font scale/cache reuse, selective bitmap refresh and Apply/Cancel.
Android and Web targets were attempted but are unavailable in this Windows
build graph. Live Custom Edition validation is limited by unavailable maps.
(cherry picked from commit ddeb650d82a73b570e182ff2bfd87e3cc3daa98d)
Match visible A/B/X/Y and supplied trigger/thumbstick artwork to the Xbox
bitmap bounds and center prompts around the visible glyph. Keep Black and
White unchanged. Preserve all three stick press frames and rename the
Menu Gems display label to Dark Gems without changing saved values.
Use the original HUD ONE/INVSRCALPHA blend for sticks and triggers, with
premultiplied asset edges, to avoid dimming them by prompt alpha twice.
Increase the existing high-res texture table so every new frame loads.
All changes remain in the existing high-res feature and menu wiring;
low-res fallbacks and unrelated icon indices retain their existing paths.
Checked: 358 focused HUD/menu/MCC tests on testing, Windows portable release
builds on testing and a clean upstream checkout, and hidden Xbox a10
captures for size, spacing and brightness. Android/Web targets are absent
from this Windows build graph. No protocol or simulation changes.
(cherry picked from commit c21ec87e6fbc6eb3370597dbdbc92cbe5464bb61)
Xbox button definitions override prompt text with full-opacity colors.
Honor that existing override for Classic, Gems and Menu Gems while
retaining text alpha for icons without it. Embedded artwork supplies RGB.
Validated original Xbox a10/Blood Gulch definitions, opaque asset
interiors, Windows portable release compilation, and production-function
checks for all three styles, full/partial alpha, fallback, and cleanup.
Android/Web targets were unavailable in the Windows configuration.
No MCC/fork code, tests, documentation, assets or workflow changes included.
(cherry picked from commit ce6cf74623912bf442efcfb2e0d6849a84f86011)
Use the supplied transparent A/B/X/Y artwork for Gems, fitted within the existing button bounds. Preserve the original menu redraws as Menu Gems and share their Black/White glyphs between both gem styles. Classic remains the default. Keep the source PNGs and deterministic asset conversion, and reserve enough placeholder entries for all styles across map changes.
Validated the Windows release build and 269 focused checks, including generated texture bounds, centering, repeatable output, all three native/MCC style transitions, disposal, and menu row spacing. Existing menu glyph PNGs remain unchanged.
(cherry picked from commit 5f66c51927f09c31095e224b0cbfbeb8aeef0bb8)
Default high-resolution gameplay A/B/X/Y/Black/White buttons to plain Xbox faces made from the existing redraws. Retain the shaded faces as Gems and keep glyph geometry unchanged. Show Button Style below High-Res HUD only while enabled, following pending edits and closing the row gap when hidden. Recreate owned glyph placeholders on style changes and dispose them on map unload.
Validated the Windows release build, hidden menu captures with both styles and HUD off, isolated Xbox gameplay, and 268 focused production-code and menu checks including repeated visibility/focus and glyph lifecycle changes. Android and Web builds were attempted but their targets are unavailable in this Windows configuration.
(cherry picked from commit e8f36b8aa2b906a738856ac198efbd591cc9bcc0)
The game's own updater (the Quest's) cannot on the Steam Frame: Lepton
installs the APK in the game's folder there at every start. So the
launcher does it: while the game runs it asks GitHub for the latest
release, fetches a newer one's Steam Frame bundle into update/, checks
it (its build.txt is the release's number), and puts the APK and the
launcher in place as the next start begins, before Lepton runs the APK.
update.auto = false in config.toml stops it, as on the Quest; a build
with no number (build.txt, which the bundle now carries and the
installer copies; 0 for local builds) never looks; without the internet,
or when anything fails, nothing changes. The in-game updater no longer
starts there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
32-bit libSDL3 loads libXi.so.6 and libxkbcommon.so.0 with dlopen at run time. Where a 64-bit distribution's 32-bit SDL3 package leaves those out (such as Arch Linux's lib32-sdl3), SDL falls back to X11/XWayland without XInput2 and SDL_SetWindowRelativeMouseMode fails with SDL_Unsupported(), or leaves SDL_WINDOW_MOUSE_RELATIVE_MODE set before the window has focus so non-relative motion is dropped.
When SDL_SetWindowRelativeMouseMode does not take, platform_mouse_capture now clears SDL_WINDOW_MOUSE_RELATIVE_MODE, hides the cursor, grabs the mouse in the window (which lets XWayland's xwl_seat_maybe_lock_on_hidden_cursor lock the pointer and emulate warps), and warps the pointer back to the middle of the window after motion.
Checked with ninja linux and ninja android.
tools/stagekit writes a scenario's map in code (a floor in a box, props, players' starts) and plays it headless, a tick a frame, the same every time. Every player is logged each second and tests check what happened: walking, an obstacle, an elevator ridden up and down. 20 tests, about 2 s, CI-safe.
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.
Increase the existing Xbox faces from 20 to 26 HUD units, centre them on the text capital midpoint and compensate for the text renderer's following-run inset. Preserve their original artwork and split-screen scale. Checked the portable Windows OpenCE build and hidden 1280x720 gameplay captures; the Press X to prompt has balanced visible gaps. Retain the repository's rocket reticle unchanged at the user's request.
Upscale the original vehicle/passenger labels, warnings, reload dots and
messaging sheets without changing their glyph layout or sprite coordinates.
Use the existing Xbox controller redraws for A/B/X/Y/Black/White prompts;
other controller symbols retain the original Xbox art through upscaling.
Match the existing high-resolution vehicle meter contour to remove the
double wrench. Restore the rounded diamond sniper reticle, contain the
flashlight fill and assign discrete thresholds to whole ammunition bullets.
The HUD module owns interpolation and tag selection. A narrow texture-cache
hook is needed to obtain decoded original pixels and invalidate generated
textures with their source; the messaging hook selects the existing button
art. Both respect the High-Res HUD setting. MCC's separate draw/texture
paths remain intact. No MCC bitmap data is embedded; label interpolation
was retained at the user's request, with its limited sharpness acknowledged.
Validated Windows portable release build; 246 focused HUD tests; 15,360
real GPU ammunition checks; hidden Xbox HUD captures enabled/disabled;
Xbox/MCC/Xbox transitions at 30 and 120 FPS with no exceptions. Player health
and sniper zoom-label pixels outside the targeted regions are unchanged.
Android/web targets are unavailable in this Windows build configuration;
Custom Edition gameplay data is unavailable, so that path was inspected.
No simulation or network format changes; network version stays unchanged.
(cherry picked from commit 5398553e4a95d7d68376acf023e6ca671066a614)
Apply the existing campaign gradient to the three- and four-button online
pause artwork, retaining the translucent footer. Admit full-screen Xbox
network campaigns to the existing Settings insertion while keeping local
split-screen unchanged and preventing network Settings from pausing time.
Checked Windows release build, pause-art and campaign-context harnesses,
and hidden two-peer Xbox multiplayer and co-op sessions. The co-op client
can select and open Settings. Android/web builds are unavailable in this
Windows configuration; no wire-format or simulation contract changed.
(cherry picked from commit 04cd1e25e910c5234fee323033b1efb8c71e8e0a)
Restore the pause header and footer bands while retaining Settings, the
campaign body gradient, and the Xbox footer alpha of 191/255. Initialize
the scripted timer scaling flags so stack data cannot shrink its digits.
Move Edit Gametypes and its divider inside the connected panel.
This implementation-only upstream contribution excludes MCC, regression
tests, documentation, optional regeneration commands and workflows. The
complete fixes and tests remain on GulchCE testing and the local validation
branch. Original cherry-picked changes below were combined and scoped to
OpenCE; authorship is retained.
Validated: portable release Windows builds on both bases; 25 upstream
art/timer tests and 52 GulchCE tests before upstream test exclusion; hidden
Xbox and MCC pause captures, both timer HUD settings, and multiplayer menu
captures. Final generator matches all committed pause SVGs. Android/web
targets are unavailable in this Windows configuration; online co-op was
not replayed.
(cherry picked from commit a0c20572f8bfa6af0d2f94c57abef32357750bf0)
(cherry picked from commit c76a3a596f7dbe8ced581b2e6c759aae0baababb)
(cherry picked from commit f0804e1e5c857db6dc84ff16c72d3f8b8abf95a2)
(cherry picked from commit bff920f6a5ec614caf98ff5e8555a0320b0a29f1)
(cherry picked from commit b1b59eb7237d901dba10aa0b5187b184f3f44a7b)
On Linux, SDL's file picker needs the desktop portal, which the 32-bit
game reaches through a 32-bit libdbus-1.so.3, or zenity. Where neither is
present, SDL reports an error, but the game took it for a cancelled
choice and asked the same question again, without end.
The game now looks first for a disc image (.iso or .xiso) next to the
executable, names it in the question and extracts it without a file
picker. When no picker can be shown, it says why (put the image next to
the game, or install zenity) and quits. Cancelling the picker still asks
again, and if extracting the image found fails, the next try opens the
picker.
The texture cache dropped a texture once it had gone 1800 frames without
being drawn, and checked every 600 frames: half a minute and ten seconds
at 60 frames a second. At 360 frames a second it is five seconds, so
looking away from part of a level for five seconds dropped its textures,
and turning back converted and uploaded them again; the faster the
display, the more often. On b30 at 357 frames a second, 105 textures were
dropped in 38 seconds of play.
The cache now sweeps every 10 seconds of time (SDL_GetTicks, which the
Android guest and the web have too) and drops the textures not drawn since
the sweep three sweeps before: 30 to 40 seconds after they were last
drawn, which is what it did at 60 frames a second. The frame counter still
orders the palette variants.
Checked with a harness test (tools/harness/tests/test_texture_idle.py)
that runs xgpu_texture_cache_begin_frame from the sources against a fake
clock: a texture last drawn 1 or 25 seconds in is dropped 30 to 40 seconds
later at 360 frames a second, and at 60 as before; on main the 360 cases
fail (dropped after 5.7 and 6.7 seconds) and the 60 one passes. Four
negative controls. Not measured: the time saved, which is small on an
RTX 4080 (each upload took 1.6 ms or less) and larger where conversion and
uploads cost more.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
biped_check_discard erases a biped that has fallen 2000 units below a
map it has left. On a client the biped is often the host's, whose
delete network_objects_may_delete refuses, and the host stops sending
the state of objects outside the map, so the biped stays there, falling,
and the check logged "is in a bad place ... erasing" every tick: a
co-op client's debug.txt had 6122 such lines for a few flood carriers
in three minutes (up to 3942 a minute), each a localtime, a vsnprintf,
a write and a flush on the game thread. The check now leaves the host's
bipeds to the host, as the falling damage erase already does (49350528).
Checked with a harness test (tools/harness/tests/test_biped_discard.py)
that runs biped_check_discard and network_objects_may_delete from the
sources: the host's biped on a client is neither logged nor deleted over
30 ticks, a client's own biped and the host's are erased once, and one
above the limit is left; without the new condition the client case fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The index cache (a3277d94) keeps an index array drawn twice the same in
one 8 MB buffer, and draws from it every frame. An array was written into
that buffer the moment it was kept, while queued draws read the buffer:
WebGL on Metal (ANGLE, which Chrome and Safari use on a Mac) waits for the
GPU to finish everything queued before such a write, as #262 found for the
stream and index uploads. While a level is explored arrays are kept every
few frames, so play hitched again and again, worse the longer one played:
one WebGL call of 20 to 40 ms every few frames, and Chrome's GPU process in
-[MTLCommandBuffer waitUntilCompleted] (macOS sample). Resolution and
window size made no difference.
Now there is a buffer for each frame of the ring, drawn from only in that
frame, as #262's chunks are. An array kept is listed, and written into a
frame's buffer as that frame begins (index_cache_begin_frame), when the GPU
is done with it; until its frame's buffer has it, a draw of it uploads its
indices as before. Android and the native builds are unchanged.
Checked on an M4 Mac mini, Chrome 155, b30 with the scripted walking bot
(HALO_TEST_INPUT=bot:1), six 10-second windows from a fresh load each:
before: 60, 56.2, 57.6, 51.4, 52.6, 51.6 fps; frames over 25 ms
3, 43, 24, 61, 57, 89; longest 152 ms
after: 60, 59.9, 59.6, 60, 60, 60 fps; frames over 25 ms
2, 6, 3, 2, 3, 0 (the same as with the cache bypassed)
The cache still serves 80 to 99% of indexed draws, as before. ninja web
builds; d3d8_gl.c's changed lines compile without warnings under -Wall;
port/web/tests/*.test.js pass, and tools/test_web_build.py passes but for
test_the_sockets_inside_the_page, which fails the same without this change
on macOS (getentropy undeclared in web_net.c, compiled with the host's cc).
Co-authored-by: Bridge <agent@bridgedesktop.com>
Bridge-Chat: #60
A network co-op game's pause menu has FRIENDLY FIRE ON or FRIENDLY FIRE OFF
(network.coop_own_friendly_fire, on unless changed). Two players hurt each
other only if both have it on, and then as the host's FRIENDLY FIRE says.
A player with no input for over 70 seconds is hurt by every player,
whatever either chose, until their next input: the choice does not shelter
someone who has left the controls. Enemies and AI allies are as before, and
no other kind of game changes.
Only the host deals damage, so only the host keeps the choices. A client's
input says its player's in a byte that was padding, and the host counts
each player's ticks without a button pressed or let go, or a stick or the
trigger held. The network version is 30 for that byte (29 is the Custom
Edition map names'): an older host would ignore a player's choice.
The button is in the pause menu because a co-op game had no way to its
settings: it opens the campaign's pause menu, which gets SETTINGS only in
single player. pause_campaign_patch lays the menu out as it does single
player's with SETTINGS: five rows, 30 apart, in the taller box. Like
SETTINGS, the button needs the PC version's menus.
Checked on Windows in two games of two hidden copies on a30, one player
hitting the other every 10 seconds, keys pressed by a script, once with the
host's player hit and once with a client's. Hits took shields with FRIENDLY
FIRE ON; none did after switching it OFF; with the player left idle, the
hits from 70 seconds on took shields again; and none did after the next
key. A multiplayer game's pause menu, opened on a client, still has RESUME
GAME, SETTINGS and LEAVE GAME. Not tried with a gamepad or in split screen.
Built and run here on Windows only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A player's game froze for about 48 s at a time, in the menus too, and online each freeze ended in "connection failed"; a dump showed the frame's swap waiting inside the graphics driver. The log now says so whenever showing a frame takes 2 s or more, so such a report points at the driver without a dump.
A game the browser cannot reach shows "Could not reach <name>" for a few seconds. When this machine's NAT gives each destination its own port and no router forwards the tunnel's (what the log already reports), it now shows "Strict NAT, turn on UPnP: could not reach <name>", the advice first so that a long name is what gets cut.
tools/harness/tests/test_latency_meter.py takes latency_meter_color from
latency_meter.c and, from network_distributed.c, the round trip timed by
when its tick was sent, its smoothing, the host's silence on a client and
a client's on the host, with the constants they use, and checks them in a
fake world: the colours and bars at 79/80, 149/150 and 249/250 ms; a tick
no longer kept (or never sent) gives no round trip; at most 9999 ms, also
across the millisecond clock's wrap; the first sample as it is, then an
eighth of each difference; a connection problem after more than 2000 ms
of silence, never before the host's first message, after the game or on
the host. Eight negative controls (a bound moved by one, the stale-tick
check or the ceiling taken out, a quarter's smoothing, no game-over test)
each fail their case. Two source checks: the setting is off by default
and the netcode refreshes the meter only with it on, and the scoreboard
draws a known ping alone in its colour.
Checked: python -m pytest -q tools/harness tools/test_touch_menu.py (358
passed, 16 of them these).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucYZ9cvWn3nrnk7kK3S5X
With display.latency_meter on, the scoreboard's Ping column agrees with
the meter: where this machine times a player's round trip itself (the
host: every client's, its own players' 0; a client: its own players'), the
column shows that figure, and each known ping is drawn on its own in the
meter's colour for it (green, yellow, red). The other players' pings on a
client are the host's measure in ticks, as the pings message brings them.
With the setting off, the scoreboard is drawn as before.
Without this the two disagreed on one screen: on a LAN the host's measure
in ticks reads 0 to 4 ms while the meter, which counts each machine's wait
for its next tick, reads 25 to 35; with 80 ms of netem the scoreboard said
81 and the meter 107. Nothing more is sent: the pings message still
carries the host's measure in ticks.
Game source change (port: comment): game_engine.c's scoreboard takes a
player's ping from latency_meter_player_ping (distributed_player_ping with
the setting off) and draws a known one alone at the row's fourth tab stop.
Checked: Linux debug and release, web and Android debug and release
build. With netem's 40 ms each way, the host's scoreboard showed 0 and
108 with "Max ping 108 ms", the joiner's 0 (the host's) and 99 with "Ping
99 ms", in the meter's colours; with the setting off the scoreboard looks
as origin/main's (team colours, the host's measure).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucYZ9cvWn3nrnk7kK3S5X
display.latency_meter (HALO_LATENCY_METER, default false) shows this
machine's round trip to the host at the screen's top right while a network
game is played: "Ping 45 ms" with four bars drawn as quads, green below
80 ms, yellow below 150, red from 150. The host shows "Max ping", its
slowest client's. A client that has heard nothing from the host for two
seconds shows "Connection problem", pulsing; the host logs a client silent
as long. It is not drawn in the menus, after the game, over a co-op
cutscene (but a connection problem), or when the host plays alone. It is a
setting, off, because the stock HUD has no such meter; only in config.toml.
Nothing more is sent, so the network version stays 29. The meter reuses
the round trips the distributed netcode already takes: the host each
client's from the host tick its input names, a client its own from the
client tick the host's state of its player (or, driving, its vehicle)
names. Those are counted in ticks; the meter times the same echoes in
milliseconds by when this machine sent the tick they name (the last 64
ticks' send times kept), smoothed as TCP smooths its own. A client also
notes when the host's latest message came. All machines, system link,
internet play's tunnel and a browser's WebRTC channel alike, carry these
messages, so the meter reads the same over each. What is shown is
refreshed in the tick twice a second, and only with the setting on;
halo.log then has the round trips every ten seconds ("latency: ...") and
each connection problem as it starts and ends.
Ported from the Halo CE for PS Vita port (BirchWoodGod, contributed under
CC0), where it is on by default.
Checked: Linux debug and release, web and Android debug and release
build; tools/network_format.py: the wire format is the one recorded. A
system link host and joiner in a network namespace with netem's 40 ms each
way: the meter read 89-123 ms on the joiner and 94-128 ms on the host (80
ms of network and each machine's wait for its next tick); all traffic
dropped for 5 s: "Connection problem" on the joiner after
2247 ms, the host "Max ping 2980 ms" in red and its log "machine 1 silent
for 2190 ms", both heard again after the blackout. On loopback with no
delay: 25-35 ms. With the setting off, the game logs the same lines as
origin/main (numbers apart) and config.toml differs only by the new
setting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucYZ9cvWn3nrnk7kK3S5X
tools/harness's bots_lobby takes network_game_server_port_bots from
network_server_manager.c, the players' functions from
network_game_manager.c and the bots' machines and names from bots.c, and
checks who joins a lobby, on which team, who leaves, and that no bot joins
a game other machines may join, another gametype, a game once started or a
lobby with no player. Each of its 8 negative controls (a guard or a team
rule taken out) fails its case.
tools/test_bots.py plays a local Slayer game with bots on Blood Gulch and a
Team Slayer game on Chill Out for 75 s each (debug.network_test "local:",
SDL's offscreen video driver) and checks that every bot walks, the bots
kill and die, the game ends cleanly with no assert, and that a system link
game gets no bot whatever bots.count asks. It needs the retail maps in
assets/maps and skips without them.
Checked: python -m pytest -q tools/harness tools/test_touch_menu.py, 361
passed; tools/test_bots.py, 3 passed in 4 minutes; test_linux_port.py and
the format tests, 214 passed, 8 skipped (Custom Edition maps, perfetto);
test_web_build.py, 10 passed. tools/test_death_timing.py does not compile
with clang 22 here, on origin/main too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucYZ9cvWn3nrnk7kK3S5X
Computer players for a game played on this computer alone: bots.count (0,
the default, to 15) of them join the lobby of a local split screen Slayer
game, as far as it has room, on the teams bots.teams asks for ("even", or
"against" the players), and follow the players' team changes there. A bot
is the one player of a machine of its own at the top of the machine indices
(113 up), which no real machine takes, so every machine of the game - only
this one - spawns, respawns and scores it as any player.
network_server_manager.c adds and removes them in the pregame;
players_update_before_game takes each bot's action of the tick from
port/linux/game/bots.c in place of its empty queue.
A bot looks for enemies (the AI's line of sight, its skill's reach and
field of view), reacts, turns at a limited speed with an aim error that
settles and leads by its weapon's projectile speed, fires in bursts or a
press a shot, throws grenades, strikes close up and strafes; otherwise it
goes where an enemy was last seen, to a better weapon lying near (the
action button once the game offers the swap), or about the map's starting
locations and item spawns. It walks the paths the campaign's AI finds:
path.c's searches over the walkable surfaces every stock multiplayer map
carries, on a copy of them less those with no room above (Longest's walls)
and those under scenery or machines its path's obstacle avoidance found no
way past (Sidewinder's blast door); stuck, it jumps, sidesteps and looks
again. The searches run in the tick, three a tick at most, so two runs of
the same input play the same. bots.skill sets reaction, sight, aim, turning
and bursts; debug.bot_trace logs each bot's state.
Nothing changes in system link or internet games: no bot joins a game that
accepts remote connections, and bots_update_actions returns unless a local
game runs, so nothing the machines send each other changes and the network
version stays the same. Other gametypes get no bots yet (their goals come
in a later change). The code is BirchWoodGod's, from the Halo CE for PS
Vita port's offline bots, contributed under CC0; this is their core.
Checked (Linux, debug and release builds, SDL's offscreen video driver,
debug.network_test "local:" with debug.test_input "bot:1" as the player):
- 150 s games on Blood Gulch (Slayer, 6 bots; Team Slayer, 7, legendary),
Chill Out (Team Slayer, 5, heroic), Longest (Slayer, 8), Sidewinder (Team
Slayer, 9), Hang 'em High (Team Slayer, 5, "against"), Damnation (Slayer,
6, easy) and Prisoner (Slayer, 15): every bot walked 125-280 units, the
bots killed 7-118 times and died, teams evened or all against the
player, and every game ended cleanly with no assert. With
debug.bot_trace, bots took up rocket launchers, shotguns and sniper
rifles, and debug.txt had none of the distributed netcode's idle action
lines.
- bots.count 0: a 60 s local game logs the same lines as origin/main with
only the network test's local mode (numbers apart), and config.toml
differs only by the new settings' lines (and the network test's
comment). bots.count 6 in a system link
game (host:) and in a local CTF game: no bot joins, nothing logged.
- ninja web and ninja android (Emscripten 6.0.10, NDK r27c) build, debug
and release.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucYZ9cvWn3nrnk7kK3S5X
debug.network_test "local:<map>[:<variant>...]" sets up the game as the
Xbox menus' Multiplayer > Split Screen does (split_screen_game_initialize:
a server that accepts no remote connections, its client, the split screen
pregame screen) instead of hosting a system link game, then picks the map
and variant, adds controller 1's player and starts it as "host:" does. A
game no other machine can join had no automated way to be played; the bots
of the next commit play in such games.
Checked: "local:bloodgulch:slayer" on a build of origin/main with this
change alone loads and plays the game (one player, debug.test_input
"bot:1"), logs every second as "host:" does and ends with debug.exit_after;
"host:" games are as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucYZ9cvWn3nrnk7kK3S5X
parse_unicode_string took the character after every '|' and only then
looked for the 'n' of "|n", so a string ending in '|' skipped its
terminator and was drawn on through whatever followed it, until a zero.
Players' names, server messages and a map's string lists are drawn this
way, so any player can send one. Any other '|' also ate the character
after it, and kept the result of the character before (on a string's
first character, the parser's uninitialized one). The next character is
now taken only when it is 'n'; any other '|' is a character, as
parse_string has it. "|n" is unchanged.
Checked: new harness test unicode_line_breaks takes parse_unicode_string
from draw_string.c; its cases are bar-n-ends-a-line, bar-is-a-character and
bar-at-the-end. On the old code the last two fail, and each of its 2
negative controls (the next character taken whatever it is, the '|' left
with the last result) fails its case. tools/harness and test_touch_menu:
347 passed. ninja linux (debug and release) and ninja web build with no
new warnings; Android not built here.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ucYZ9cvWn3nrnk7kK3S5X
NVIDIA's OpenGL driver can treat SDL's popup fullscreen window as exclusive even when no display mode is selected, blanking the monitor on Alt+Tab. Clear popup and topmost styles for borderless windows before context creation and presentation, including after SDL restores them. Explicit fullscreen keeps its existing behavior.
Checked: Windows release build; hidden native window probe covering styles, bounds, focus, repeated application and mode isolation; git diff --check. Live Alt+Tab was not tested. Android and web targets were unavailable; pytest was not installed.
memory_watch.c's SIGSEGV handler is installed before main (for the crash
report with the null renderer), and the device's memory_watch_initialize
returned early because it was already active. A library that installed its
own SIGSEGV handler in between (a GL driver as the window opened, a sound
library) then took the faults of the pages memory_watch_protect made
read-only: a crash or a hang at the first write to a texture.
memory_watch_initialize now asks which handler is installed and, where it
is not this file's, installs it again, chained to the one found; the
renderer's frame (memory_watch_begin_frame) checks the same, as a library
can install one at any time. A handler that chains back to this one ends
in the default action instead of a loop.
The same report (signal, registers, stack, calls, to the terminal and
debug.txt) now covers SIGBUS, SIGILL, SIGFPE and SIGABRT. Each is handed on
to the handler before it, or to the default action: a fault runs its
instruction again under it, and a signal sent (kill, abort) is raised again;
a sent signal that was ignored stays ignored. The registers are read by
name on x86-64 too, so that the file compiles there for its test.
Checked: tools/test_memory_watch.py (new; memory_watch.c compiled as it is
with a window of the test's own memory) installs a library's handler over
this one, then initialize: the watched write is noted and let through, the
library's handler gets only an unrelated fault (after the report), and the
next frame puts this one back after a handler that jumped out. Each signal
is reported once and ends the process with that signal. The old file, with
the same main (-m32), leaves the library's handler in place and dies at the
watched write. ninja linux builds.
The SIGSEGV handler that writes the crash report (the faulting address
and the calls) to the terminal and debug.txt is memory_watch.c's, and
only the renderer's device installed it (d3d8_gl.c): with
debug.null_renderer, as the network tests run, no device is made, and a
crash said nothing, as a looping sound's crash in null-renderer network
games did. A crash before the device was made went unreported too.
The handler is now installed when the program starts, as the Windows
build's crash handler is.
Checked: a null-renderer game sent SIGSEGV wrote the report to its log and
debug.txt; the crashes of a forced looping sound crash in null-renderer
games were reported with the faulting address.
A carousel network game between two builds, null renderer on both
(the cross-build sync check's calibration), ended with the joining copy's
SIGSEGV. Its core dump (kept by systemd-coredump) shows
update_channel_for_looping_sound reading a NULL looping sound
(sound_manager.c:2912): the track's looping sound had been deleted by
process_looping_sounds (not refreshed since the last frame, around the
respawn), and the track had started in the same millisecond as the
frame (start_time 55674, render_time 55674, ticks_elapsed 0).
refresh_sound skips the tracking of a sound that has not started yet
(start_time >= render_time), and the tracking is what stops a track
whose looping sound is gone; so the track went on to update_channels.
That needs three frames in one millisecond: never on the Xbox, but the
null renderer runs up to 21 (measured in these games). The original's
bug; refresh_sound now stops such a track first.
Checked: a new asset-free test (tools/harness/tests/test_sound_orphan.py)
runs the real refresh_sound in frames in the same millisecond and later,
with a negative control (the test of the start time alone) failing it.
A build that skips every seventh frame's looping sound refreshes crashed
at the same line in 8 of 8 carousel games without the change, and
played 16 of 16 with it; 40+ unforced carousel games played through.
Sounds are each machine's own: nothing the machines send or agree on
changes.
A client on the Steam Frame, in an internet game on carousel, stopped on the
game's exception screen: "objects.c #829: !(weapon_is_flag(object_index))".
The host's object changes had told it to delete a weapon that on the client
is a flag or an oddball (weapon_is_flag reads the weapon tag's "must be
readied" bit, which in the maps only those two have), and
distributed_client_delete deleted it as it deletes any of the host's
objects. The game never deletes a flag or ball while a game type runs, and
asserts so twice: in object_delete_initial_recursive and in weapon_delete.
A release build's assertions only note it in debug.txt, so a release host
can delete one and tell its clients; a client built without --release then
halts, flat or VR alike.
A client follows the host's word: a flag or ball it deletes so is let go of
first (distributed_client_release_item takes it out of its carrier's hands,
as any weapon of the host's), the goal its game type showed at it is cleared
(game_engine_objective_weapon_deleted: CTF's flag and carrier goals,
oddball's ball goal), and the two assertions are not made for an object
distributed_client_delete deletes (network_objects_client_deleting). The
host and the game's own deletions keep them.
On spark, a host told its clients that each flag (one in the client's
player's hands) or the ball was deleted, as a host deleting one does, and
of it again the next tick. Before, a flat client and a VR client halted on
the same assertion as the Frame, in CTF and in oddball; now neither does,
the client's player lets go of the flag, the flag comes back when the host
tells of it again, host and client count the same items, and the player
is killed and respawns after it. A slayer game of deaths and respawns plays
as before.
Files:
port/linux/game/network_objects.c
source/objects/objects.c
source/items/weapons.c
source/game/game_engine.c, source/game/game_engine.h
port/linux/NETCODE.md
Use the natural C implementation from punpckhdq/halo 745c66b8. All 621 object fingerprints and inherited exact verdicts are unchanged; the single asm-implemented park is retired. Fresh full checkpoint: 1663 tests pass, 5 skipped, 159 subtests. Current scoring already counted the assembly: no additional function or byte credit.
Video Setup's Graphics screen gets a MODEL DETAIL row, which sets
display.model_detail. Its values are stock, native and highest. Its XML
is written by tools/port_settings.py's settings_files(), as
tools/ce_menus.py writes it; no other screen changes.
Checked: tools/test_web_build.py's test that the menus match their
generator; the screen opened with debug.menu_open shows the row with the
setting's value, above the help.
Video Setup's Graphics screen gets a TEXTURE FILTERING row, which sets
display.anisotropic_filtering. Its values are 0 (off, as on the Xbox),
2, 4, 8 and 16. Its XML is written by tools/port_settings.py's
settings_files(), as tools/ce_menus.py writes it; no other screen
changes.
Checked: tools/test_web_build.py's test that the menus match their
generator; the screen opened with debug.menu_open shows the row with the
setting's value, above the help.
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.
The Xbox game never asks for anisotropic filtering: every sampler is linear
or point with an anisotropy of 1, so floors, walls and terrain seen at a
grazing angle blur into their small mip levels. display.anisotropic_filtering
(HALO_ANISOTROPIC_FILTERING) samples the map's mipmapped, linearly filtered
textures (base, detail, bump and environment maps) along the angle, at most
2, 4, 8 or 16 times and at most the GPU's limit; 0 filters as the Xbox did.
Textures the game renders itself are left alone: at 16x the water's ripple
map turned the far ocean into noise. Lightmaps have no mip levels and are
unchanged.
Checked: a10's cryo bay floor and b30's beach, off, 4x and 16x on the same
frozen frame (grates and sand stay sharp, the water is unchanged); about
+0.08 ms a frame on a10 at 1080p; tools/harness passes. Android and the web
apply it only where GL_EXT_texture_filter_anisotropic exists; not built here.
A client that joins a lobby on a Custom Edition map it does not have
downloads it by map sharing. When the host started the game meanwhile, the
host held the client's players out, as it should, but the client, whose
download began in the lobby, did not know the game had started and asked
for its player again; the host took the request as a late joiner's and
started it at once. The client then loaded a map it did not have yet:
cache_files_precache_map_begin's damaged disc error, which stops the game
for good. Stock build-201 does the same with its own host.
- The host (network_server_manager.c): a player asked for by a machine
still downloading the map waits with those held at the start
(network_game_server_hold_requested_player), and joins with them, the
machine started as a late joiner, once its download is done
(network_game_server_release_map_fetchers).
- The client (network_client_manager.c): a start that comes while the
map is still being downloaded, from a host of an earlier build, is put
off until the map is there (network_game_client_begin_game,
network_game_client_idle_pregame), and dropped if the game it was for is
over by then (another map, or another game).
- NETCODE.md's "Map sharing" says what happens when a host starts during a
download (it said the host waited, which it has not since version 28).
Nothing sent changes: no network version.
Checked: the Linux CI steps, the web build and its tests, and the Windows
release build. End to end on Linux in Docker, the host starting the lobby
game at once while the client downloads (the case that stopped the game):
this build both ends (the host held the player and started it at game tick
296 once the map was there); build-201 hosting this build's client (the
start put off until the map was there); this build hosting build-201's
client (held, then played). Each client played the game.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the "pc" menu style, a LAN game whose players were all on the host's
computer could not start a local splitscreen game. This patch fixes that
check.
START NOW only starts a countdown for a game that takes no remote connections, has two machines, or
is "alone" (server_alone). server_alone only passed if there was literally one player, making splitscreen play impossible.
Note the "xbox" menu style did not have this bug since its splitscreen
didn't accept remote connections at all.
server_alone becomes server_players_on_one_machine: TRUE if there is at
least one player and every player is on the same machine.
One player still qualifies. Machines that have joined without a player are not
counted, so a lone host's countdown is not stopped when one connects (the
per-tick server_ok_to_countdown check).
The countdown is fully controlled by the host machine, so there is no
change to the network protocol.
There's an existing test failure on test_elevator_prediction which also
exists on main, ignore.
Video Setup gets a third category, EXTRA EFFECTS, beside Graphics and FOV
and Viewmodels: a screen for the visual modifiers the stock game does not
have, so Graphics keeps to how the game is drawn. Shield Glow and Glow
Intensity move to it from Graphics; the settings themselves, their
defaults and their names are unchanged.
The screens were written again with tools/port_settings.py; only Video
Setup, Graphics and the new screen changed, and menus.json lists the new
screen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtk9vPUKbWd48oBMzXuzd
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>
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>
Merge upstream/main and preserve its map-sharing includes and updates.
Use message aim-assist policy 87, clear of upstream map-sharing IDs 83-86. Raise network version 28 to 29 because this feature adds a required wire message; record the new format and update its documentation.
Tests compile production message IDs and reject collisions. 293 CPU/menu regressions and three menu/wire-format checks pass.
nv2a_psh.c translated the DOT_ZW texture stage to a dot product and dropped the depth the stage replaces the fragment's depth with. Every z-sprite particle (a bullet impact's puff, for one) was depth tested as its flat card, so the surface it stands on cut it in half along a straight line through the hit point.
The game's effect shader (rasterizer_xbox_transparent_geometry.c, the z-sprite branch) feeds stages 2 and 3 so that their quotient is the depth of a point up to the sprite's radius nearer than its card, shaped by the second map, in the depth buffer's units (16777215 for the 24-bit buffer). The translated shader now writes that quotient over 16777215 to gl_FragDepth. Only shaders with a DOT_ZW stage change their text.
Checked on a macOS fork of this translator (OpenGL and Metal, a30, assault rifle fired at the lifeboat's floor): puffs are whole, and decals and hit positions are unchanged. Not built or run on this tree's Linux, Windows, Android or web builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011KLToxkJE32PPuVNUQEe9a
Offer Per Player, Static Multiplier and None for authored campaign weapons, grenades, health kits, overshields and camouflage. Share amount rows with extra enemies, cap pickups at 8x, retain ammunition and checkpoint counts, and only place copies on clear nearby ground. Use ordinary host-created replicated objects without changing the wire protocol.
(cherry picked from commit 6f3a75507690a03382759f0f1b70c13e38abc851)
Scale extra growth by the integer ratio 2/3 in both modes before rounding down. Default 50% Per Player now yields the first extra vehicle with four players. Update setting descriptions and count expectations. All 25 focused vehicle/menu tests and the Windows build pass.
(cherry picked from commit c1881dd5e73fa49b45c3b29b7eb74b9ba9e08b7c)
Apply a flat 0.5 modifier to additional vehicles in per-player and static modes. Update generated setting descriptions and documentation. Verified all 25 focused vehicle/menu tests, including counts from 1 to 128 players, lifecycle checks and a negative control for the modifier. The combined Windows build and isolated startup test pass.
(cherry picked from commit c3a54123390e56603b4001fbba2ae94f450981c6)
Offer Per Player, Static Multiplier and None. Register authored campaign vehicles, anchor delivered cargo after ground contact, and replace player-driven vehicles after both bounding spheres clear the home with a 15 percent buffer. Keep host authority, pad collision checks, allocation limits and checkpoint budgets. Replicate ordinary vehicles without changing the wire protocol.
(cherry picked from commit 75abf60ed676b0b5b8a20de4f82068c9a3ba281f)
With display.overshield_effect (Video Setup > Graphics > Overshield, off
by default: the game's own), this machine draws the overshield's flare
rising up the body from the feet as it charges (2 seconds; 3 from a
shield that was down), then lit, whole, for as long as it lasts, its
energy at 30% of its speed, in its own color (the powerup's, 253, 78,
116) whatever shield color its player chose; its glow rises and stays
with it, and with display.viewmodel_shield it comes up the first-person
arms. A hit on it plays what the projectile plays on an Elite's shield,
where that only shows and sounds. The rise is a height fade in the vertex
shader (d3d8_gl_set_height_fade).
Reworked after review: the setting the earlier version removed is back,
off by default, so the stock overshield is unchanged unless a player
turns it on. Only drawing and sounds change; no network change (the wire
format check passes). Checked: the changed units compile for Linux; menus
regenerated (only Graphics changed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtk9vPUKbWd48oBMzXuzd
Cutscene skipping in co-op restores a checkpoint. The legacy reset assumed player queue slot zero existed and dereferenced NULL when it was empty. Iterate the rebuilt live queues and restore each update counter, including remote players. Empty arrays are safe. The old reset fails six regression cases; all 278 standalone CPU tests pass with the fix. Message formats and network version are unchanged.
The web build reads the desktop settings, so display.hdr_output would
ask a browser's canvas for floating-point pixels it does not have. The
web build now keeps the window 8-bit, as Android does; the renderer
already keeps its scRGB path off under HALO_GLES.
With NVIDIA's driver on Windows, an OpenGL window with floating-point
pixels in fullscreen or borderless turns black, then oversaturated,
unless the NVIDIA Control Panel's "Vulkan/OpenGL present method" is
"Prefer layered on DXGI Swapchain". The README says so.
Checked: the Windows build; display.hdr_output on an HDR OLED display
with that setting. The web build was not built here (no Emscripten).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
display.hdr and display.hdr_output could be set only in config.toml or
the environment. Two rows on the Graphics screen, after Glow Intensity,
set them as the screen's other rows set theirs; HDR DISPLAY is shown on
the desktop platforms only, as the setting applies there only. The
category's help names HDR.
Checked: the two generated screens are the only files that change, and
tools/test_web_build.py's check that the menus match their generator
passes; the Windows build opens the screen (debug.menu_open) with both
rows above the help.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The settings table gains display.hdr, display.hdr_exposure,
display.hdr_tone_mapping and display.hdr_output; "What operates" gains
"HDR", which says what the back buffer holds, where the tone mapping
runs and what the HUD and the menus keep; the game source changes name
the tone mapping in render/render.c.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
NVIDIA's EGL draws into a Wayland window through egl-wayland (or
egl-wayland2), which distributions ship 64-bit only: Arch's
lib32-nvidia-utils has libEGL_nvidia and libGLX_nvidia, and no package has
a 32-bit libnvidia-egl-wayland. SDL 3.4 prefers Wayland where the
compositor has fifo-v1 (KDE Plasma 6, GNOME), and its OpenGL there is EGL,
so the 32-bit game fell back to Mesa's llvmpipe: drawn on the processor,
a flickering window that never finished loading. On X11 (XWayland) SDL's
OpenGL is GLX, which the 32-bit driver has.
With NVIDIA's module loaded (/sys/module/nvidia), a Wayland session, and
neither libnvidia-egl-wayland.so.1 nor libnvidia-egl-wayland2.so.1
loadable by this process (the names NVIDIA's EGL loads them by,
/usr/share/egl/egl_external_platform.d), SDL now tries x11 before
wayland, and the log says so. SDL_VIDEO_DRIVER, when set, still decides.
OpenGL on llvmpipe or softpipe is also logged as drawn on the processor,
and port/linux/README.md has "NVIDIA on Wayland".
Only the 32-bit Linux build: the arm64 build's host is 64-bit.
Let hosts block optional mouse magnetism without overwriting local preferences or altering controller, touch or bullet autoaim. Replicate a validated reliable policy on ready and change; raise the network version to 27. Validation: 9 focused CPU tests, Windows compilation, native two-peer policy changes and Player Options persistence/Cancel.
Fixed read past a buffer issue and
untied previous solution from reverb.
Also added config variable audio.resampler
(but no UI implementation since currently
there is no more space for that option).
Zero credit: remove 51 guard-only headers, 50 project-list entries, four useless includes and two unused enum definitions. Preserve source assertion locations and both repositories existing READMEs. Clean rebuild, unchanged 8252-row strict snapshot, unchanged code/data credit and admissions, 1663 tests plus 159 subtests pass. Disclose 76 compiler-local label renumberings and two warning source-line shifts; runtime sections and relocations remain identical. No private assets or scratch tooling published.
Independently verified donor c694a43d4e3343eedebac48fe6f2b6052f679778 under Q-UP21 after the separately gated helper restoration. Fresh clean full gate against 09255f0e: cumulative +3 functions, +1238 meaningful / +1264 padded bytes, zero exact losses; 7483 of 7573 Halo functions, 1603071 of 1770166 code bytes, data 3311591 and objects 396 unchanged, parks 59 valid. Tools tests: 1663 passed, 5 skipped, 159 subtests. Full /W3 and unit /W4 multisets unchanged. All twenty cast ablations and layout controls passed; helper identity, real-call schedule and 284 ordered provider-link probes passed. The disclosed residual caller changes 4144 to 4128 bytes.
Retain the narrow per-function parentheses ruling, inferred scopes and collision_result placement, S6 correction, single-variable alternative and compiler-version caveat. No general hold, object admission, research candidate, scorer/verifier or compiler flag change. Fresh canonical receipts and source-policy boundaries are recorded in docs/object_matching_logs/biped_avoidance_canonical_reconciliation_20261002.md. Unrelated README changes and research preserved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independently reconciled donor 41d3b992a5e5c0fbbb5f76c476d27f9c68031e82 without candidate F. Fresh clean full gate: no additional gains or losses, all prior function sections identical, one new uncredited 48-byte SELECT_ANY point_from_line3d copy identical to January, unchanged real-call schedule, warnings, data and object credit. Current-compiler cast ablation and layout controls pass, 284 ordered provider-link probes pass, 1663 tools tests pass. Six inferred casts and three byte-inert per-site parentheses are disclosed; all other actor_moving holds stay in force.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independently reconciled donor 82ea0d5e9bcee8a1a48775aab6f0c351f5ee5e2a after the biped packet. Fresh clean full gate: zero additional gains or losses, all normalized objects unchanged, parks valid, warnings unchanged and 1663 tools tests passed. Current-compiler cast ablation is byte-inert; nine layout/alignment checks pass and a deliberately false control fails. Both current selected-provider link orders pass. Cast spelling remains inferred; no new parenthesis or general view-cast exception.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independently verified donor edbdbf729e9b7c0c09557f1ccb45f94ad6c8a06a against canonical 09255f0e: fresh clean full build, strict whole-board sweep with zero losses, parks 61 to 59, unchanged data/object credit and warnings, 1663 tools tests passed. Folded helper identity and all selected-provider pair links passed. Per-site parentheses, descriptive constants and coupled jumping form remain narrowly disclosed as inferred; no general hold lifted. Unrelated canonical biped_accelerate shape and README edit preserved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Approved coherent packet 41640b4d, independently clean-built after transport and plane-helper reconciliation. +538 meaningful / +544 padded bytes; main 95/95. Replace the invented packed aggregate with January's separate storage, add exactly eight private symbol rows and retire the frame-rate park. Names, six static scopes and exact main_game_render placement remain disclosed as inferred. Full stable sweep, parks, admission, fake scan and 1663 tools tests pass; no new warnings, data, objects or providers. Cumulative reconciliation +5 functions, +2121 meaningful / +2144 padded bytes, zero exact losses. Preserve dirty README and unrelated research. No PCH, scorer, verifier, status or held-source changes. Detailed independent receipts and limits recorded in docs/object_matching_logs/upstream_tmp_reconcile_20261002.md.
Approved coherent packet 78294dd3 (Q-UP1), independently gated on canonical. Remove only outer helper parentheses, preserving /Od argument order, together with the attested float argument repair in item_accelerate. Items object remains identical. +1126 meaningful / +1136 padded bytes; data/object/provider sets unchanged. Fresh full build, stable sweep, admission, fake scan (26), parks and tests (1663 passed, 5 skipped, 159 subtests) pass; no new warnings. Retire only two exact parks. Remeasure four residuals on canonical, preserving previous measurements/rejection history: frustum 88.8082->90.11173; pill 97.21691->98.086395; polygon clip 80.76382->82.37186; lens flares 99.29274->99.198944 (disclosed decrease). Do not import unrelated lane fidelity changes or held parentheses/DD2/DD3/casts.
Independently reconciled lane e191c0f1 under Q-UP1. Header and definition return long together; January startup-store and cleanup topology retained. Retire only the two verified parks. Fresh clean build and stable sweep: +457 meaningful / +464 padded bytes, 0 losses, unchanged data and object status. Affected /W4 warning counts unchanged; inherited WSA narrowing moves to initialization. Admission/parks/fake scan pass; tools 1663 passed, 5 skipped, 159 subtests. No new providers or section/data changes beyond the two intended functions. README and unrelated research preserved.
Preserve main README unchanged. Source/config/verification tree equals canonical 5e55d8f8; two exact Halo gains, zero regressions, +1998 meaningful bytes. No object or data-credit changes.
Cleaned punpck donor reconstructions; strict bytes and all relocations match January. Fresh cold whole-board gate: 2 gains, 0 losses; 1663 tests pass. Retire the two parks. Disclose six HUD /W3 C4244 narrowing warnings and six identical shared-inline helper copies; provider links pass both orders. No object or data credit changes; inherited README and research excluded.
2026-10-01 19:40:22 -07:00
539 changed files with 52107 additions and 8982 deletions
| `source/` | The game: 466 C files of the decompilation, compiled as they are with few changes (each marked, below) |
| `port/linux/src/` | The platform layer the three ports share: Direct3D 8 on OpenGL (`d3d8_gl.c`), DirectSound and XInput on SDL3, files, threads, memory, sockets, settings (`port_config.c`), internet play (`p2p*.c`) |
| `port/linux/game/` | The port's game-side code, compiled as the game's sources are: the distributed netcode (`network_*.c`), co-op, map sharing (`map_share*.c`), the menus (`menu_tags.c`, `menu_functions.c`), Custom Edition maps (`custom_edition_*.c`, `cache_file_formats.c`), the tag validator (`tag_validate.c`, `tag_schema_*.c`), touch, FOV and more |
| `port/linux/src/` | The platform layer the three ports share: Direct3D 8 on OpenGL (`d3d8_gl.c`), DirectSound and XInput on SDL3, files, threads, memory, sockets, settings (`port_config.c`), the mods folder (`port_mods.c`), internet play (`p2p*.c`) |
| `port/linux/game/` | The port's game-side code, compiled as the game's sources are: the distributed netcode (`network_*.c`), co-op, map sharing (`map_share*.c`), the menus (`menu_tags.c`, `menu_functions.c`), Custom Edition maps (`custom_edition_*.c`, `cache_file_formats.c`), the tag validator (`tag_validate.c`, `tag_schema_*.c`), mods (`mods.c`, `mod_*.c`), streamer mode's word filter (`text_filter*.c`), touch, FOV and more |
| `port/linux/include/` | The port's headers: the limits (`halo_port_limits.h`, with `HALO_PORT_NETWORK_VERSION`), capacities (`halo_port_capacity.h`), the prefix header, `halo_linux_source_fixups.h` (port functions the game's sources call) |
| `port/include/xdk/` | Stand-ins for the Xbox SDK's headers (declarations only) |
| `port/windows/` | The Windows build's own files (`win32_*.c`, headers) |
@@ -25,8 +25,8 @@ end.
| `port/assets/` | What the builds embed or ship: high-res HUD (`hud/`), fonts, titles, menus (`menus/`), icons, network brokers |
| `port/third_party/` | Vendored libraries, each with a README naming its upstream, version and checksum |
Ringfall: Mjolnir VR's own code is released under [CC0 1.0](LICENSE.md).
It includes or builds on the following, under their own licenses; each
license's full text is in the file named.
Ringfall: Mjolnir VR is released under the GNU Affero General Public
License, version 3 or later ([LICENSE](LICENSE)); releases before build 312
were released under [CC0 1.0](LICENSES/CC0-1.0.txt). It includes or builds
on the following, under their own licenses; each license's full text is in
the file named.
## Code
| Component | Used for | License | Text |
| --- | --- | --- | --- |
| [OpenCE](https://github.com/OpenCommunityEdition/OpenCE) and the decompilation ([bnunu/halo-1](https://github.com/bnunu/halo-1), [punpckhdq/halo](https://github.com/punpckhdq/halo)) | the game's code, the port | CC0 1.0 | [LICENSE.md](LICENSE.md) |
| [OpenCE](https://github.com/OpenCommunityEdition/OpenCE) and the decompilation ([bnunu/halo-1](https://github.com/bnunu/halo-1), [punpckhdq/halo](https://github.com/punpckhdq/halo)) | the game's code, the port | AGPL 3.0 or later (OpenCE from 19 October 2026, punpckhdq/halo from 17 October 2026; CC0 1.0 before); bnunu/halo-1: CC0 1.0 | [LICENSE](LICENSE), [LICENSES/CC0-1.0.txt](LICENSES/CC0-1.0.txt) |
| [HaloCE-Quest-VR](https://github.com/moistman42069/HaloCE-Quest-VR) | the arm IK, forearm twist and finger poses (`port/linux/game/vr_arms.c`) | CC0 1.0 | (its repository) |
@@ -22,11 +24,11 @@ license's full text is in the file named.
| [tomlc17](https://github.com/cktan/tomlc17) | reading `config.toml` | MIT | [port/third_party/tomlc17/LICENSE](port/third_party/tomlc17/LICENSE) |
| [stb](https://github.com/nothings/stb) | drawing the game's text sharply (stb_truetype) | MIT or public domain | [port/third_party/stb/LICENSE](port/third_party/stb/LICENSE) |
| [SMAA](https://www.iryoku.com/smaa/) | anti-aliasing (flat screens; off in VR) | MIT | [port/third_party/smaa/LICENSE](port/third_party/smaa/LICENSE) |
You do not need the Xbox SDK. The port supplies the SDK declarations that
the game uses. Refer to [port/include/xdk](../port/include/xdk/README.md).
the game uses. Refer to [port/include/xdk](port/include/xdk/README.md).
To build the game:
@@ -127,7 +140,7 @@ Give these options to `configure.py`:
| --- | --- |
| (none) | A debug build. A failed assertion stops the game. |
| `--release` | A release build. The game does not examine assertions, as in the retail game. |
| `--portable` | The Linux and Windows builds operate on all x86-64 processors. The Linux build also operates on older distributions and on SteamOS: refer to "Portable build" in [port/linux/README.md](../port/linux/README.md#portable-build). Use this option for builds that you give to other persons. |
| `--portable` | The Linux and Windows builds operate on all x86-64 processors. The Linux build also operates on older distributions and on SteamOS: refer to "Portable build" in [port/linux/README.md](port/linux/README.md#portable-build). Use this option for builds that you give to other persons. |
| `--lto=thin`, `--lto=off` | Less link-time optimization. The link is faster. |
| `--pgo=off` | No profile-guided optimization. |
| `--pgo=train` | Records a new optimization profile. Refer to "Optimization profiles". |
@@ -187,10 +200,41 @@ file.
`debug.profile_memory` sets the memory of a recording in MB (default 256,
from 4 to 1024). A recording has no length limit:
the game writes a part each time the memory is full. Refer to "Settings" in
[port/linux/README.md](../port/linux/README.md).
[port/linux/README.md](port/linux/README.md).
On Android, the files are in `/sdcard/Android/data/com.halo.decomp/files/profiles/`.
Enter `adb pull` to copy the recording parts to a computer.
A profiling build plays with normal builds. It does not use
`--pgo=train`.
## Licence
OpenCE is free software: you can give it to others and change it under the
terms of the GNU Affero General Public License as published by the Free
Software Foundation, version 3 of the License or (at your option) any later
version (`AGPL-3.0-or-later`). It comes with no warranty. The full text is in
[LICENSE](LICENSE).
- **From the change.** The AGPL applies from the commit that made the change,
and to each release built from that commit or a later one. Earlier
releases, and the source at every earlier commit, were released under
[CC0 1.0](LICENSES/CC0-1.0.txt) and stay that way.
- **What OpenCE builds on.** The decompilation of
[punpckhdq/halo](https://github.com/punpckhdq/halo) is under the AGPL 3.0
from 17 October 2026, and under CC0 1.0 before that date.
[bnunu/halo-1](https://github.com/bnunu/halo-1) is under CC0 1.0.
- **Contributions** are under `AGPL-3.0-or-later`. A change taken from a
project under GPL 3.0 keeps that licence and its notices; the GPL 3.0 and
the AGPL 3.0 let the two be combined (section 13 of each).
- **Hosts and servers.** If you change the game and let other persons play
with your version over a network (for example, as the host of an internet
game, or on a web site), the AGPL asks you to offer them the source of
your version (section 13). The game as released has its source here, and
each release's source is its tag (`build-<n>`).
- **Third-party code, fonts and art** keep their own licences, which are
next to them (`port/third_party`, `port/assets/fonts`,
`port/assets/icons`, `port/assets/text_filter`). The release downloads and
the web site's `licenses.txt` carry their notices.
- **Halo is not covered.** The licence covers the work of OpenCE's authors.
It gives no rights in Halo, its maps or anything else that Microsoft owns.
A mod is a folder of Halo PC tag files that the game plays over a map's own
tags on your machine, as a resource pack does: sounds (a high-res audio
pack), bitmaps (a texture pack, a HUD redraw), text (a translation) and
models (higher-detail weapons, vehicles and scenery). Only the machine that
has a mod plays it. Nothing of a mod is sent to another machine, and a mod
changes nothing that the machines of a network game must agree on, so you
can play with mods in any game, with players who have other mods or none. (A host's mod sounds of other lengths change how long
the host's scripts and AI wait for dialogue to end; the host decides those
for every machine, so they still agree.)
The game's code is `port/linux/game/mods.c` (with `mod_sounds.c`,
`mod_bitmaps.c`, `mod_strings.c` and `mod_models.c`, one for each kind of
tag) and `port/linux/src/port_mods.c` (the mods folder and its files).
## A mod's folder
The mods are in the `mods` folder of the data root (the folder with
`maps/`; on Android the app's data folder, on the web the browser's storage
for the site). Each mod is a folder in it, with a `mod.toml` and a `tags`
folder:
```
mods/
hd_audio/
mod.toml
tags/
sound/sfx/weapons/assault rifle/fire.sound
sound/dialog/...
german/
mod.toml
tags/
ui/hud/hud_item_messages.unicode_string_list
```
The files in `tags` have the paths of the tags they replace: a tag's name,
then its group's extension, as the Halo Editing Kit and Invader keep them.
`tags/sound/sfx/weapons/assault rifle/fire.sound` replaces the sound tag
`sound\sfx\weapons\assault rifle\fire` of every map that has one. Names are
not case-sensitive. The game only looks up the names of the map's tags among
a mod's files: a mod cannot add tags, and no tag name of a map makes a path
outside the mod.
`mod.toml` says what the mod is. Every key is optional:
```toml
name="High-res audio"# what the Mods menu shows (else the folder's name)
id="hd_audio"# what other mods name it by (else the folder's name)
version="1.0"
authors=["Someone","Someone else"]# or one string
description="The game's sounds at 44 kHz"
maps=["a10","bloodgulch"]# the maps it is for, by their files' names (else every map)
load_after=["hd_textures"]# mods it plays over: they load before it
load_before=["fixes"]# mods that play over it: they load after it
incompatible=["other_audio"]# mods it cannot be played with
format=1# the mods' format it is made for
```
`load_after`, `load_before` and `incompatible` name other mods by their `id`
or their folder's name, not case-sensitive. A mod they name that is not in
the mods folder is passed over. `maps = []` is every map.
A folder without `mod.toml` is not a mod. A mod made for a later `format`
than the build's, or whose `maps` names no map, is not played. The game
logs anything else wrong in `mod.toml` (an unknown key, a value of the
wrong type) and uses the rest; a `mod.toml` that is not TOML at all is
logged, and the mod played without what it says.
The game's log is its output: the terminal on Linux, `halo.log` beside
`halo.exe` on Windows, logcat on Android, and the page's log on the web.
Limits: 256 mods, folder names of 63 bytes at most, 262144 files in all, 24
folders deep under `tags`, and 64 maps in a mod's `maps`.
## The load order
Every mod in the mods folder is on until you turn it off. The mods load in
an order, and where two mods have a file for the same tag, the one loaded
later plays.
- The default order is the folders' names in alphabetical order (not
case-sensitive), except that the mods a mod must load after (those it
asks to load after, and those that ask to load before it) are taken to
just before it. Mods that ask in a ring (a after b, b after a) load
alphabetically, and the game's log says so.
- The main menu's **Mods** (in the PC version's menus, `display.menus =
"pc"`, the default) is laid out as the game lobby is: the mods on the
left as a resource pack list has them, the one at the top playing over
all those below it (the load order upside down: the mod loaded last is at
the top), and the chosen mod's details in the frame on the right (its
name, what is wrong with it, whether it is on, its priority, 1 the top,
and its version, authors, folder, maps and description). Choosing a mod
(A, or a click on the chosen one: the first click chooses it) turns it on
or off (ON, OFF); left and right, or **Move up** and **Move down**, move
the chosen mod (up: over one more mod). **OK** keeps the order and plays
it at once (on a map that is loaded, too); **Cancel** leaves it as it was.
- A warning sign beside a mod means it is on the wrong side of a mod it
asks to load before or after (both on): a mod that loads after another is
above it in the menu. A red cross means a mod that is on says it cannot be
played with this one, or that it cannot be played at all (made for a later
build, or its `maps` names none): it stays off. The details say which.
The menu keeps the order, and the mods turned off, in `mods/load_order.toml`,
by the mods' folders' names:
```toml
order = ["german", "hd_audio", "hd_textures"]
disabled = ["hd_textures"]
```
A mod added to the folder later is put where the default order puts it,
after the mod before it there (but on the side it asks for of the mods it
names), and is on. A mod gone from the folder is left out. When the file
cannot be written, the game says so in its log and keeps the order until it
quits.
### Mods that cannot be played together
When a mod that is on names another that is on as `incompatible`, the game
asks as it starts, before the window opens:
- **Play anyway** plays them all (closing the box does too);
- **Turn off ...** turns off the mods named as incompatible (if mod A says
it cannot be played with mod B, B is turned off), and keeps that in
`load_order.toml`;
- **Quit** closes the game.
The Mods menu turns them on again. On the web, **Quit** starts the page
again. A run nobody watches (`debug.hidden_window`, `debug.null_renderer`,
`debug.exit_after` or `debug.network_test`) is not asked: it plays them all,
and the game's log says which conflict.
At the console:
| Command | What it does |
| --- | --- |
| `mods_list` | Lists the mods in the mods folder in their load order, and which are on, then how many of the map's string lists, bitmaps, sounds and models the mods play. |
| `mods_reload` | Finds the mods again and reads their files again: a changed file is read again (one that now fails a check leaves what was played before), a new one is played, and the map's tag comes back for a file that is gone. If a sound changed, all sounds stop. It does nothing while `mods_enable false`. |
| `mods_enable false` | Plays the map's own tags until `mods_enable true` or the next map. |
These commands work in any game, a client's too: they change nothing of the
game.
### Getting a mod onto each platform
- **Linux, Windows**: put the mod's folder in `mods/` next to `maps/`.
- **Android**: put it in `mods/` in the app's data folder,
`/sdcard/Android/data/com.halo.decomp/files/mods`, for example with
@@ -98,6 +98,7 @@ To install the data from a computer:
| Item | Location in `/sdcard/Android/data/io.github.cesarus85.ringfall/files` |
| --- | --- |
| Saved games (`z:\` and `u:\`) | `save` |
| Mods (`docs/mods.md`) | `mods`: `adb push <mod folder> /sdcard/Android/data/com.halo.decomp/files/mods/`; the main menu's Mods orders them and turns them off |
| Log | `debug.txt` |
| Settings | `config.toml` |
@@ -160,7 +161,7 @@ controller for player 1. With the default buttons of the profile:
| Crouch, Zoom | left and right stick clicks |
| Light, Gren. type | white, black |
| Pause, Back | start, back |
| Up, Down, Left, Right | D-pad |
| Up, Down, Left, Right | D-pad (in a network game, Up opens the text chat's keyboard) |
The names on the buttons follow the profile's "Button layout" (Settings >
Gamepads): with "Swap triggers", the right trigger's button says "Grenade".
@@ -574,4 +575,7 @@ assembly of the port is necessary:
page contents once a frame instead ("write tracking: page hashes" in the
log). This is slower, and a write shows one frame later, so moving
geometry can glitch briefly. `debug.memory_watch = false` does the same
on a device.
on a device. The translation also has no scalar conversion from floating
point to an integer in a SIMD register (`fcvtzs s0, s1`, an illegal
instruction there), so `tools/android_asm_convert.py` writes each as the
vector form, which leaves the register and the exception flags the same.
@@ -18,15 +18,17 @@ named where code is taken over.
| --- | --- |
| Headsets | Quest 3; Steam Frame (Lepton): its controllers' layout, a Steam library tile that installs, starts, updates and quits the game, data kept in `~/Documents/RingfallMjolnirVR` |
| Rendering | The eyes drawn one by one (both at once, multiview, only with `debug.multiview`: its eyes did not always fuse, see the open points); lens flares at their depth in stereo; HUD, menus, scope, crosshair and names as layers of their own |
| Aiming | The weapon in the hand, its shots from the muzzle, laser and crosshair options |
| Aiming | The weapon in the hand, its shots from the muzzle, laser and crosshair options; each weapon held so that its model's hand lands on the real one, as the free hand's (it learns where each weapon's animations hold the hand; the pistol's had been 15 cm before the real hand, the assault rifle's 8 cm aside), the scope's disc moved with it; `vr.weapon_hand_*` to adjust (2026-10-17) |
| Body | Physical crouch (`vr.crouch_limit`) beside the crouch button, leaning (`vr.lean_limit`), melee by swinging, grenades thrown with the left hand, two-handed weapons |
| Vehicles | The Warthog's wheel turned with the hands, right trigger throttle, left brake; first-person seats; turrets with handles |
| Scores | The menu button held shows the scores on the Quest (a press pauses, as it is let go); the Steam Frame's View button as before; the text chat's lines where the desktop has them (2026-10-18) |
| Comfort | Snap or smooth turning, a comfort vignette in vehicles, VR options in the main and pause menus (Y), a page of every controller button |
| Menus | The PC version's menus in VR (`vr.menus`), clicked with a laser; text typed on the on-screen keyboard |
| Multiplayer | Up to date with OpenCE (network version 29, 2026-10-14): cross-play, its server browser, co-op campaign, password lobbies; join codes and the VR online list |
| Bots | Bots in multiplayer: on foot (paths, items, objectives), in vehicles and at turrets; they make room for a joining person |
| Multiplayer | Up to date with OpenCE (network version 32, 2026-10-20; HDR kept off in VR): cross-play, its server browser, co-op campaign, password lobbies; join codes and the VR online list |
| Bots | Bots in multiplayer (`host_bots.c`; OpenCE's own, `bots.c`, are local split screen games' and off in VR): on foot (paths, items, objectives), in vehicles and at turrets; they make room for a joining person, on the LAN too (2026-10-18); they climb ladders (Damnation's) and take paths as near their goal as a search reaches, as OpenCE's do: on Damnation 0 searches of 1747 failed, 1078 of 1605 before (2026-10-20). Later: OpenCE's bots' aiming (lead, bursts, taps for the pistol and sniper rifle, field of view), learnt obstacles and map fixes into ours |
| Voice chat | Push to talk on any button (`vr.button_talk`, BUTTONS page; the free hand's grip by default, sending nothing while it reaches for the foregrip or the wheel), open mic, off (2026-10-13) |
| Builds | The Android build's assembly conversion (`tools/android_asm_convert.py`) keeps string data as it is: strings and tables starting with "_" or holding \" lost a "_" (Opus's pulse cache among them: the Steam Frame's crashes in online games while talking), 2026-10-13 |
| Languages | The European disc in Spanish and Italian (a maps folder for each, `maps_es` and `maps_it`, none called `maps`) imported on the Quest and the Steam Frame: the headset's language's, its language the game's and the menus' (`maps/language.txt`, unless `game.language` is set), 2026-10-17; not yet tried with that disc |
| Controls | Left-handed mode (`vr.handedness`): the weapon in the left hand, the arms and grips mirrored, sticks and buttons mirrored on both controllers (`vr.mirror_controls`), the assault rifle's counter read the right way round (2026-10-06) |
| Graphics | Presets low / medium / high / max (`vr.graphics`), refresh rate and resolution in the VR options, all kept over a restart (2026-10-06) |
| Arms and hands | Both arms by IK from shoulders that follow the torso, the elbow led by the hand's roll; fingers from trigger, grip and touch; `vr.body` arms / hands / off, `vr.fingers` (2026-10-06) |
"note":"High-res HUD textures whose bitmap has the same layout in Halo Custom Edition (the same sprites, the same size or twice it), compared against the stock bitmaps.map and bloodgulch.map; hud_hires.c draws them for a Custom Edition map's stock bitmap of that name as well.",
<svgxmlns="http://www.w3.org/2000/svg"width="128"height="32"viewBox="0 0 128 32"><!-- Existing meter contour, aligned by the Xbox HUD tag offsets. --><pathtransform="translate(-1.25,0) scale(.25)"d="M328.14,53.64C327.98,53.7327.27,53.81326.57,53.89C324.82,54.06324.04,54.24323.36,54.57C323.04,54.74322.43,54.92322,54.99C321.54,55.06321,55.26320.64,55.47C320.32,55.69319.77,55.91319.41,56C319.06,56.07318.59,56.29318.35,56.46C318.12,56.62317.66,56.91317.31,57.07C316.4,57.52315.11,58.26314.55,58.66C313.98,59.07313.38,59.49312.69,59.95C311.82,60.54309.75,62.41308.05,64.14C305.23,67.03304.94,66.88313.35,66.88C320.88,66.88320.89,66.88321.69,67.78C322.69,68.88322.07,70.7320.11,72.45C318.82,73.6318.44,73.88317.94,74C317.66,74.08317.21,74.21316.94,74.31C316.51,74.47315.23,74.5309.1,74.5L301.76,74.5L301.41,74.8L301.06,75.1L301.06,80.78C301.06,86301.07,86.47301.29,86.76C301.4,86.92301.65,87.29301.84,87.56C302.36,88.36303.52,89.61303.99,89.91C304.23,90.05304.6,90.33304.8,90.51C305.01,90.69305.41,90.91305.69,91C305.96,91.09306.44,91.31306.75,91.51C307.14,91.75307.61,91.91308.25,92C308.76,92.08309.52,92.22309.95,92.33C310.85,92.56315.31,92.53315.9,92.28C316.09,92.2316.8,92.08317.5,92.01C318.32,91.92319.09,91.75319.73,91.51C320.25,91.31321.02,91.05321.44,90.94C321.85,90.83322.32,90.62322.5,90.51C322.68,90.39323.15,90.17323.56,90.04C323.98,89.9324.59,89.61324.93,89.39C325.27,89.17325.61,89325.7,89C325.77,89326.12,88.81326.46,88.58C326.79,88.35327.19,88.12327.35,88.08C327.5,88.03327.85,87.79328.14,87.55C328.43,87.31328.86,87.03329.1,86.94C329.34,86.84329.71,86.6329.94,86.41C330.16,86.21330.7,85.78331.12,85.44C331.55,85.09332.27,84.5332.73,84.11C333.19,83.72333.69,83.35333.84,83.28C334.29,83.03356.52,83.06357,83.31C357.52,83.59357.85,84.08357.94,84.72C358.05,85.56358.25,86.14358.56,86.56C358.73,86.76358.96,87.16359.1,87.44C359.59,88.41362.02,90.66362.93,90.96C363.2,91.06363.73,91.31364.07,91.51C364.48,91.74365.02,91.91365.59,92C366.06,92.06366.82,92.2367.26,92.31C368.39,92.58372.21,92.58373.09,92.31C373.45,92.2374.31,92.06375.01,92C375.98,91.91376.49,91.79377.11,91.5C377.56,91.3378.21,91.08378.56,91C378.9,90.92379.46,90.7379.81,90.5C380.15,90.31380.68,90.09380.95,90.01C381.24,89.94381.69,89.72381.95,89.54C382.23,89.36382.73,89.1383.06,88.97C383.41,88.84383.8,88.62383.94,88.5C384.07,88.38384.41,88.16384.69,88.03C385.29,87.72386.39,87.03387.06,86.5C387.34,86.29387.93,85.84388.38,85.5C390.16,84.12390.45,83.89391.68,82.69C392.38,82393.1,81.24393.27,81C393.45,80.76393.9,80.21394.26,79.79C395.91,77.88396.15,77.44395.73,77.16C395.64,77.11392.55,77.09388.88,77.1C380.81,77.15381.38,77.22379.48,75.75C378.15,74.72380.57,71.14383.59,69.69L384.75,69.12L392.12,69.12C400.81,69.12399.93,69.31400.06,67.42C400.29,64.56400,61.07399.49,60.36C399.36,60.19399.16,59.79399.04,59.49C398.38,57.86396.06,55.59394.45,54.96C394.1,54.84393.69,54.64393.51,54.51C393.05,54.19392.45,54.04391.35,53.94C390.81,53.9390.15,53.79389.86,53.69C389.12,53.42385.86,53.42385.11,53.69C384.81,53.8383.94,53.94383.15,54C382.01,54.09381.56,54.19380.9,54.49C380.44,54.69379.69,54.94379.23,55.04C378.76,55.15378.16,55.39377.88,55.56C377.6,55.74377.19,55.91376.96,55.96C376.5,56.05375.94,56.31375.51,56.64C375.35,56.77375,56.94374.74,57C374.48,57.07374.1,57.27373.9,57.44C373.7,57.61373.26,57.86372.94,58C372.6,58.14372.15,58.42371.91,58.62C371.69,58.84371.44,59371.38,59C371.3,59371.04,59.2370.76,59.44C370.5,59.67370.01,60369.68,60.15L369.06,60.44L355.8,60.47L342.54,60.5L342.31,60.24C342.18,60.1341.96,59.8341.81,59.56C341.15,58.47340.79,58340.07,57.26C339.18,56.34337.55,55337.31,55C337.23,55336.74,54.8336.24,54.56C335.55,54.24334.98,54.09334,53.95C333.27,53.85332.43,53.7332.12,53.64C331.43,53.47328.54,53.47328.14,53.64ZM327.19,57.01C326.71,57.07326.04,57.21325.69,57.3C323.85,57.82322.59,58.25322.23,58.47C322,58.61321.48,58.82321.06,58.94C320.65,59.06320,59.34319.62,59.56C319.25,59.79318.51,60.21318,60.5C315.26,62.07314.46,62.82314.99,63.3C315.18,63.48315.61,63.5317.89,63.5C321.77,63.5322.84,63.8324.21,65.22C325.81,66.88325.89,70.24324.4,72.69C323.9,73.5321.79,75.65321.19,75.96C320.99,76.08320.61,76.31320.38,76.5C320.14,76.69319.71,76.91319.44,77C319.16,77.09318.6,77.3318.19,77.49L317.44,77.81L310.88,77.84C304.71,77.86304.3,77.88304,78.1C302.6,79.12303.02,83.61304.74,85.89C305.91,87.45307.93,88.69309.79,89.01C311.21,89.25316.19,89.09317.31,88.78C319.38,88.19320.96,87.66321.25,87.46C321.43,87.35321.81,87.17322.12,87.06C322.43,86.95322.94,86.71323.25,86.51C323.56,86.33324.01,86.09324.25,86C324.49,85.9324.85,85.69325.05,85.53C325.26,85.36325.71,85.1326.07,84.94C326.43,84.79326.76,84.59326.81,84.51C326.85,84.42327.11,84Line truncated
<stringtext="Players can be hurt by weapons or explosives\nfired by the other players."/>
<stringtext="Damage from the other players will only reduce\nshields. Health will be unaffected."/>
<stringtext="Players can be hurt by damage from explosives\nfired by the other players."/>
<stringtext="Enemy squads grow with the players: by the value of\n'Per Player' for each player past the first."/>
<stringtext="Enemy squads use 'Multiplier', however many players\nthere are."/>
<stringtext="Enemy squads are as the campaign has them."/>
<stringtext="Enemy squads grow with the players: by the amount\nbelow for each player past the first."/>
<stringtext="Enemy squads are the amount below times as large,\nhowever many players there are."/>
<stringtext="For each player past the first, enemy squads get\nthis much more of themselves (100%: as many again)."/>
<stringtext="For each player past the first, enemy squads get\nthis much more of themselves (100%: as many again)."/>
<stringtext="For each player past the first, enemy squads get\nthis much more of themselves (100%: as many again)."/>
<stringtext="For each player past the first, enemy squads get\nthis much more of themselves (100%: as many again)."/>
<stringtext="For each player past the first, enemy squads get\nthis much more of themselves (100%: as many again)."/>
<stringtext="Each enemy squad is this many times as large."/>
<stringtext="Each enemy squad is this many times as large."/>
<stringtext="Each enemy squad is this many times as large."/>
<stringtext="Each enemy squad is this many times as large."/>
<stringtext="Each enemy squad is this many times as large."/>
<stringtext="Pickups grow with the players: by the value of\n'Per Player' for each player past the first."/>
<stringtext="Pickups use 'Multiplier', up to eight times as many,\nhowever many players there are."/>
<stringtext="Pickups are as the campaign has them."/>
<stringtext="Vehicles respawn after being driven a short distance\nbased on the number of players: by two thirds the value of\n'Per Player' for each player past the first."/>
<stringtext="Vehicles respawn after being driven a short distance,\nwith extra copies equal to ('Multiplier' - 1) * 2/3,\nrounded down, however many players there are."/>
<stringtext="Vehicles are as the campaign has them."/>
<stringtext="Static amounts for the options set to this mode.\nPickups are capped at eight times as many."/>
<stringtext="Static amounts for the options set to this mode.\nPickups are capped at eight times as many."/>
<stringtext="Static amounts for the options set to this mode.\nPickups are capped at eight times as many."/>
<stringtext="Static amounts for the options set to this mode.\nPickups are capped at eight times as many."/>
<stringtext="Static amounts for the options set to this mode.\nPickups are capped at eight times as many."/>
<stringtext="For each player past the first, options set to Per\nPlayer get this much more (100%: as many again)."/>
<stringtext="For each player past the first, options set to Per\nPlayer get this much more (100%: as many again)."/>
<stringtext="For each player past the first, options set to Per\nPlayer get this much more (100%: as many again)."/>
<stringtext="For each player past the first, options set to Per\nPlayer get this much more (100%: as many again)."/>
<stringtext="For each player past the first, options set to Per\nPlayer get this much more (100%: as many again)."/>
<stringtext="Players bump into each other, as in the campaign."/>
<stringtext="Players walk through each other, so that no one\nblocks a doorway. The AI's characters still block."/>
<widgetname="main_menu/settings_select/player_setup/player_profile_edit/gamepad_setup/button_preset_spinner"type="spinner"left="3"top="2"width="147"height="20"flags="pass_unhandled_to_focused_child left_right_tabs_items"strings="DEFAULT|SWAP TRIGGERS|SWAP A, L TRIGGER|SWAP B, L TRIGGER|SWAP B, R STICK"setting="profile.button_preset"values="0|1|2|3|4"font="ui\large_ui"color="#FF2896FF"align="center"text_y="1"header_bitmap="bitmaps/arrow_sm_left"footer_bitmap="bitmaps/arrow_sm_right"header_bounds="7 -6 19 0"footer_bounds="7 150 19 156">
<widgetname="main_menu/settings_select/player_setup/player_profile_edit/gamepad_setup/button_preset_spinner"type="spinner"left="3"top="2"width="147"height="20"flags="pass_unhandled_to_focused_child left_right_tabs_items"strings="DEFAULT|SWAP TRIGGERS|SWAP A, L TRIGGER|SWAP B, L TRIGGER|SWAP B, R STICK|BUMPER JUMPER"setting="profile.button_preset"values="0|1|2|3|4|5"font="ui\large_ui"color="#FF2896FF"align="center"text_y="1"header_bitmap="bitmaps/arrow_sm_left"footer_bitmap="bitmaps/arrow_sm_right"header_bounds="7 -6 19 0"footer_bounds="7 150 19 156">
Loaded 100 of 539 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.