Repository navigation
System link games of up to 128 players on up to 128 machines - #2
Merged
cybersecurity merged 12 commits intoSep 27, 2026
Merged
cybersecurity merged 12 commits into
cybersecurity merged 12 commits into
Conversation
halo_port_limits.h (force-included by the Linux and Windows prefix headers) defines the native builds' multiplayer limits, 128 players on up to 128 machines, and the struct network_game layout that follows from them. Game sources use it only under HALO_LINUX, so the matching build keeps the Xbox limits (16 players, 4 machines). xnet.c gains two settings: HALO_NET_ADDRESS binds the game's sockets to one local address and reports it as the machine's system link address, so several instances can share one computer on different loopback addresses; HALO_NET_BROADCAST sends the game's broadcasts to a list of addresses instead, for machines broadcasts do not reach. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With HALO_NET_ADDRESS set, connections and datagrams to 127.0.0.1 go to that address, and traffic from it is reported as coming from 127.0.0.1. The game uses 127.0.0.1 for itself (a host joins its own game through it, and admits only it to a split screen game), so an instance bound to another address can still host. HALO_NET_BROADCAST now takes a list of addresses: a host's game advertisement is a broadcast too, and several instances on one computer each need a copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Xbox game allows 16 players on 4 machines (4 split screen players each). The native builds raise both limits to 128, keeping 4 players per machine, and leave the byte-matching build unchanged: every change is under HALO_LINUX (the MSVC objects compare identical). - Limits: every local copy of the player and machine limits, and the bare 4s and 16s that stood for them, follow halo_port_limits.h. The player data array, the machine-to-player table (and its reset) and the per-tick update arrays grow with them. - Records: the copies of struct network_game agree on the larger layout and check it against halo_port_limits.h; maximum_player_count becomes a byte (128 does not fit a signed char); network_player_is_valid checks the machine against the machine limit, not the split screen one; a machine's input carries MAXIMUM_LOCAL_PLAYERS actions (16/4 would have become 128/128). - Protocol: a message holds at most 4095 bytes (its header has a 12-bit length), so the 13 KB game settings record is sent in 3.5 KB pieces that clients reassemble; the per-tick update of 128 players (3,857 bytes) fits. Buffers and the packet codec's limits grow to match, and the game search uses protocol version 2 so these builds and the Xbox game never see each other's games. - Connections: the host accepts a client connection per machine (the split screen count stood in for it), FD_SETSIZE is 256, stream writes send all of a message or drop a peer that stops reading instead of corrupting the stream, and the input and update queues are larger. - Host: lobby changes are sent at most every 250 ms instead of on every change (a full lobby would otherwise send ~650 MB), in-game joins wait in a queue instead of being dropped, the random name and colour pickers stop looping forever once names or colours run out, machines get 60 s to load a map, and the debug dump lists only the slots in use (128 empty slots took seconds to log). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Native builds (HALO_LINUX) size the multiplayer game engine for HALO_PORT_MAXIMUM_NETWORK_PLAYERS; the matching build keeps the January text in #else branches and stays byte-identical. - game_engine.c: MULTIPLAYER_MAXIMUM_PLAYERS follows the port limit; the postgame statistic buffers hold an entry per player; global_goal has a slot per player (slayer kill-in-order uses the absolute player index) and the goal loops are bounded by the array instead of the address of global_variant; game_engine_get_place uses a 128-bit team mask; places past the 16th get code-generated English ordinals in the HUD score messages, scoreboard and postgame instead of asserting or clamping to "16th"; a free-for-all team is the player's absolute index. - slayer, oddball, king, race: per-team and per-player score arrays follow the port limit (oddball's ball arrays stay at 16); January size asserts are kept for the matching build. Race reads the lap flags of the player rather than of the player's team index. - game_allegiance.c: a team past the 10-team table is not its own enemy, like teams 0..9, so a free-for-all suicide no longer scores as a kill. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Native builds only (#ifdef HALO_LINUX); the matching MSVC build keeps the original text and stays byte-identical. - ui_widget_game_data_input_functions.c: the local machine limit follows HALO_PORT_MAXIMUM_NETWORK_MACHINES and the struct network_game copy holds HALO_PORT_MAXIMUM_NETWORK_PLAYERS players; its layout is checked against halo_port_limits.h. The pregame screen keeps its three remote machine panels and shows the first three remote machines instead of asserting (sizing its arrays by the machine limit read uninitialized widget pointers). netgame_prejoin_players scans every player slot, and the player count texts have room for three digits. network_machine_is_valid widens the char index before comparing it with 128. - ui_widget_event_handler_functions.c: the join/unjoin, team swap and countdown handlers use the port's players offset and player count instead of 0x226/0x242/0x243 and 16. - hud.c: hud_draw_players sizes its teammate buffer by the port's player limit and bounds the fill (more than 16 teammates overflowed the stack). - motion_sensor.c: multiplayer tests the (horizontal) range before a unit takes a blip slot, so distant units no longer crowd nearby ones out of the 16 slots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 128-bit team mask is indexed by team; a player whose team is not set (NONE) would read and write outside it, where the 32-bit mask only shifted. Such a player now counts on its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
halo_ws_sendto rewrote a destination of 127.0.0.1 to the HALO_NET_ADDRESS address in a structure local to the else block, then sent to it after the block had ended. At -O2 clang drops the store, so datagrams went to a garbage address. It hit the host's own machine: its client reaches the local server as 127.0.0.1, so with HALO_NET_ADDRESS set the server never got the host's input and dropped the host after the stall timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Xbox game state (0x345000 bytes at 0x80061000) ends exactly where the map-linked tag cache begins and has 948 bytes free, so a 128-player session cannot even allocate its player datums. The native builds now place a 16 MB game state (CPU 0xFC0000, GPU 0x40000) at 0x81A00000, above the tag cache and below everything the Xbox memory window hands out top-down, and grow the pools that scale with the number of players: - objects: 2048 -> 8192 headers, 1 MB -> 8 MB pool; the garbage limits keep January's proportions (5/10/20 % of the pool, 2.5/5/10 % of the headers, 75/50/30 active garbage objects per 16 players) - cluster partition reference lists 2048 -> 8192 (collideable and noncollideable objects, lights); a full list drops objects out of rendering, collision and sphere queries - effects 256 -> 2048, effect locations 512 -> 4096 - particles 1024 -> 8192 (render_particles' visible list, filled without a bound check, follows), particle systems 64 -> 256, their particles 512 -> 4096 - contrails 256 -> 1024, contrail points 1024 -> 8192 (MAXIMUM_CONTRAIL_POINTS stays the pool size for the same reason) - lights 896 -> 4096, object looping sounds 1024 -> 4096 - cached object render states and rendered objects 256 -> 1024 - script object-list references 128 -> 1024, objects one explosion can damage 64 -> 256 The values live in port/linux/include/halo_port_capacity.h, included by halo_port_limits.h. With 128 player datums the CPU part holds 14,256,908 bytes (2,258,164 free). Saved games and cores grow to 16 MB. Every change is under HALO_LINUX; the MSVC objects are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by running a 128-machine session with tools/system_link_bots.py: - The listen backlog was 32 connections, so machines joining at once were refused; it is now the machine limit. - Socket buffers are at least 1 MB. A host's connection to a machine must hold every update the machine has not acknowledged, up to 128 ticks of 3.9 KB with 128 players; a full buffer blocked the host for the write timeout. The host's connection to its own client must also hold all it sends in one frame. - Each machine that finished loading logged a line for every machine still loading, 8,000 lines as 128 machines load; it now logs a count. - Removing a machine dumped the whole server state to the log; when many machines left at once the host wrote dumps for minutes. - Machines that had loaded heard nothing while the others loaded, and a client drops a connection that is silent for 15 seconds, less than the 60 seconds the host waits for slow machines. The host now sends its pregame keep-alive every 5 seconds while machines load; a client in game ignores it. All under HALO_LINUX; the MSVC build is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
write_to_error_file opened and closed debug.txt for every line, which takes milliseconds on Windows. A host logs thousands of lines when a hundred machines join, load or leave at once, and while it writes them its own client and every other machine wait. The native builds now keep the file open and flush each line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/system_link_bots.py joins a native-build host with many lightweight
machines, each with one player, speaking the system link protocol directly:
join, settings (reassembling the fragmented record), player, map precached,
loaded, then a UDP input message each tick that acknowledges the host's
updates, as a real client keeps a lockstep game running. Each machine binds
its own loopback address (127.0.0.2 and up), since the host tells machines
apart by address.
python tools/system_link_bots.py --machines 127 --start
fills a 128-machine session on the host at 127.0.0.1 and starts it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
port/linux/README.md (which the Windows and Android READMEs refer to for settings) describes the native builds' session limits, what a session of 128 machines costs the host, the lobby and scoreboard behaviour, running several copies on one computer with HALO_NET_ADDRESS and HALO_NET_BROADCAST, and tools/system_link_bots.py, and lists the game sources the change touches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bnunu
force-pushed
the
halo-ce-universal/players-128
branch
from
September 26, 2026 17:24
f2fa457 to
7fc08be
Compare
damiantw
referenced
this pull request
in damiantw/halo-ce-web-multiplayer
Sep 30, 2026
Dedicated server: control channel for a supervising process (JSON events on stdout, commands on stdin)
isifire
pushed a commit
to isifire/halo-ce-vita
that referenced
this pull request
Oct 2, 2026
Issue OpenCommunityEdition#2 (v1.0, a10): the game thread crashed in game_sound_restore after a game state load. The registers show the looping sound it was on, 0x00010012, carries identifier 0x0001, which no datum of the looping sound array can have (identifiers keep bit 15 set), and that tag_get on its definition index returned NULL. The state that was loaded was already bad in that array; every revert runs at the top of the main loop with the tick joined, so it is not the tick racing the restore. The partial dump does not show where the bad data came from. Defensive only: such a datum is deleted and named in the log instead of being read through NULL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ucYZ9cvWn3nrnk7kK3S5X
xshxdex98
referenced
this pull request
in xshxdex98/DamnationCE
Oct 3, 2026
Upstream has merged the 128-player system link work (PR #2), rebased onto its main. This takes upstream's tree as it is, so the fork matches upstream; that drops the colour clamps this branch still carried, which upstream's return to SSE maths (13d1ae2) made unnecessary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xshxdex98
referenced
this pull request
in xshxdex98/DamnationCE
Oct 3, 2026
…e fix Network version 10 (OpenCE build-73), joins 9 and 10; probe/Online Games invite fix
xshxdex98
referenced
this pull request
in xshxdex98/DamnationCE
Oct 5, 2026
The co-op player's unit was given its owner only after it was made, so a player's Elite chose its first animation slots from the Elite's own graph and then used them against the Spartan's, which it switches to once owned: a slot past the Spartan's graph halted a debug build as the player respawned (tag_groups.c, "#2 is not a valid index in [#0,#1)"). The unit is now made already owned.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The native builds (Windows, Linux, Android) now host system link games of up to 128 players on up to 128 machines. The Xbox game allows 16 players on up to 4. Split screen stays at 4 players per machine.
port/linux/include/halo_port_limits.h. The memory they need is set inport/linux/include/halo_port_capacity.h.#ifdef HALO_LINUX, so the byte-matching MSVC build keeps the Xbox limits. All 621 objects compare identical tomain's.This PR is rebased onto the current
main(13d1ae2) and no longer depends on #1. That PR clamped colours that the x87 maths pushed a hair past 1. Now that 13d1ae2 has put Windows back on SSE maths, those asserts don't fire in a 128-machine game on Blood Gulch, so #1 is closed.What changed
halo_port_limits.h.FD_SETSIZEare larger.debug.txtstays open between lines.0x81A00000(3.3 MB on the Xbox).port/linux/src/xnet.c)HALO_NET_ADDRESSgives each copy of the game its own address, so several copies can play together on one computer.HALO_NET_BROADCASTsends the game search to a list of addresses.tools/system_link_bots.pyport/linux/README.md, and new rows in the settings andHALO_LINUXedits tables.Testing
All on one Windows computer, with the Windows build of this branch on the current
main(13d1ae2):HALO_NET_ADDRESS=127.0.0.200), a second copy of the game as a real client (127.0.0.201), and 126 stand-ins played for about 90 seconds.main(a06ef74).main(sections, relocations, symbols) found 0 differences.Not covered:
HALO_LINUXplus the sharedxnet.c. A Linux or Android build check would be welcome.🤖 Generated with Claude Code