The testing branch is at version 28, whose changes (holding back the
players of a machine still downloading the map, the multiplayer scripted
BSP, lights past the Xbox limits, readying a second weapon) change no
message, kind, bit or struct the record holds; only its version changes.
Network version 28: the host now holds a downloading machine's players out
of the game, and keeps a multiplayer map's first scripted BSP. Dynamic lights
and the flashlight stay on past the Xbox limits, and a second weapon picked
up while firing is readied. Machines of version 27 would disagree on these.
- A large co-op game's respawn fallback no longer picks a teammate who is in
the air, outside the BSP, or riding an AI vehicle.
- A machine still downloading the map times out when it goes silent.
- Held players join through the late-join queue, so they get the same checks.
A machine with no held players is started instead of being refused.
- The co-op teleport uses world positions, which are right for seated players.
- Overshield drain no longer cuts off a hit's flash, and hits over 0.02 flash.
- The menu XML is the generator's (Custom Edition's layout) again.
- port: markers on two new declarations.
Checked: ninja linux, android and web build; tools/harness,
test_touch_menu and test_web_build pass.
rasterizer_debug_model_vertices declares 2048 debug vertices of 64 bytes
on the stack. render_model calls it for each part it draws, and the
release build inlines it there, so render_model's frame was 136,304
bytes, and the Windows build probed it a page at a time (_chkstk, 33
pages) for every model drawn, although the debug display is off. Its
vertices are static now: only the vertices below debug_vertex_count are
read, each written when it is added, and the function runs on the
render thread only, so nothing it draws changes.
Measured on Windows (7800X3D, RTX 4080, release build, vsync off), b30
co-op with 32 times the enemies and a bot, 75 s a run, five runs of each
build alternated: render_model's frame is 5,360 bytes (the PDB's
S_FRAMEPROC); _chkstk fell from 2.0% to 0.5% of the game thread's
samples; fps over windows with 2000 draws a frame or more, origin/main
128, 141, 139, 134, 107 (median 134), this change 140, 139, 144, 141,
132 (median 140). Linux, Android and the web build do not probe the
stack, so the change only shortens their frame. They were not built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AGENTS.md asks for HALO_PORT_NETWORK_VERSION to be raised with any change
to what the machines send each other; nothing checked it, and #95, #99 and
and 18 caught up after them).
tools/network_format.py takes the wire format from the sources: the game's
message numbers, sizes and fields, the distributed netcode's message kinds
and flag bits, its DISTRIBUTED_ constants and the structs it sends whole, as
hashes. port/linux/network_format.txt records them for the version, and
test_linux_port.py fails when the sources differ: an item changed or removed
asks for the version to be raised (--record refuses it until then), an item
added (a new kind or bit, which older builds may ignore) only to be recorded.
It also fails when NETCODE.md says nothing of the version.
Checked by recording each merge's parent and checking the merge: #95, #99
and #106 fail as changed structs; #91, votes to kick, voice chat and the
integrated light bits as additions; #101, which raised the version, passes
once recorded. A comment, or a struct the netcode keeps to itself, changes
nothing.
The versions' list stopped at 21. 22 to 25 as their commits give them:
Custom Edition maps named custom_maps\<name> (4e8ed2f1), their blocks kept
past the Xbox tools' limits (34e2d4fd), the gametype's PC vehicle set
(11117d7c) and a unit's integrated light in its object state (6b55f5cc).
Every transparent thing a frame draws takes a group: each transparent
part of a model, each widget (light volumes, antennas, flags, glows),
each batch of dynamic geometry (particles, contrails), each transparent
surface of the structure. The Xbox's 384 were a limit the native builds'
larger pools of objects, widgets, effects and particles could pass: a
frame past it was drawn without the rest, and debug.txt said "too many
transparent geometry groups". A part refused a group has no sort record
(models.c checks the group's index), so nothing else changes.
Checked on Windows in b30 co-op with eight times the enemies and a bot,
75 s a run: origin/main overflowed in 3 runs of 4; a build counting the
groups asked for showed up to 464 in a frame, while the dynamic triangle
and vertex buffers peaked at 123 and 328 of their 1024 and the dynamic
triangles at 10272 of 49152, so they are left as they are. With this
change, the same count showed 391 in a frame and no overflow; two more
runs had none. The pool is 2048 * 160 bytes, malloc'd; the model order
pass keeps 14 bytes a group on the stack (28 KB). Harness: the
transparent_plane_order tests pass at the new size (natively on Windows;
the other 44 failures there are the same on origin/main, POSIX headers).
Android and web builds not built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every draw compares its pixel shader key (256 bytes) with the recent and
cached keys, and its uniforms' inputs (77 words) with the last draw's.
The Windows build's memcmp (vcruntime) compares them a byte or a word at
a time and was the game thread's largest single function: 7.9% of its
samples in a b30 co-op game with 32 times the enemies (about 3900 draws
a frame), 5.3% from fragment_shader_get and 2.2% from the uniforms.
words_equal has no early exit, so that clang compares several words at
once (four 512-bit compares a key with -march=native).
Measured on Windows (7800X3D, RTX 4080, release build, vsync off), five
runs of each build alternated, 75 s each, b30 co-op x32 with a bot:
fps over windows with 2000 draws a frame or more, origin/main 123, 139,
135, 123, 122 (median 123); this change 142, 147, 138, 149, 131 (median
142). A sampled run of this change shows memcmp gone and words_equal at
1.2%. The keys are memset before they are filled and have no padding,
so word equality is byte equality. Android and web builds not built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wait only counted teammates the map called grounded, so jumping, riding, or standing on a beach left it with nobody to use. A late joiner after a checkpoint uses the same wait when nobody is safe to spawn beside.
The high-res text sizes Overpass by the font tag's capitals and centres
each glyph on the tag's character width, and Overpass was chosen for the
Xbox maps' widths. A Custom Edition loc.map's fonts are narrower: the
Italian one's large_ui and small_ui are the same font, its capitals 10
units tall and its letters 87% as wide as Overpass's at that height
(against 95 to 98% for the Xbox maps' interstate, large_ui and small_ui),
so the menus' and HUD's letters ran into each other. Each font is now
narrowed until its letters A to Z and a to z are, summed, as wide as the
tag's; the Xbox maps' fonts are narrowed by 2 to 5 hundredths.
The fonts were also remembered by the tag's name, and the Xbox maps and a
loc.map have tags of the same names with other measures, and by the font
tag's address, which the next map's tags may reuse: they are remembered
by their measures too, and forgotten with the map's tags.
Checked: the fonts' widths measured from the Italian loc.map and the Xbox
ui.map against Overpass's advances; the Windows build (clang 23.1.3, full
LTO) without warnings; Extinction from the Italian Custom Edition, whose
large_ui is now drawn 87% wide, its letters apart, and the main menu's
small_ui 98% wide.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hosts send players who join without the game's Custom Edition map the map
and the resource maps it needs; the other players help, torrent-style.
Network version 27: new map sharing messages, and players of an internet
game now reach each other.
WebGL on Metal (ANGLE, which Chrome and Safari use on a Mac) waits for the
GPU to finish everything queued before it writes a buffer that a queued
draw reads, unless its shadow buffers are on, and Chrome leaves them off.
The renderer wrote its stream and index buffers after each draw, some 2000
times a frame, so each frame waited for the GPU as many times. On an M1
Pro in Chrome the Silent Cartographer ran at 7 to 9 frames each second and
Blood Gulch at about 22, at any resolution (640x480 too): Chrome's GPU
process spent half its time in -[MTLCommandBuffer waitUntilCompleted]
(macOS sample), and --enable-angle-features=useShadowBuffersWhenAppropriate
alone tripled the rate.
Now, on the web, each draw's vertices and indices go into a buffer that no
queued draw reads: web_chunk_get takes one from pools of power-of-two sizes
(4 KB to 32 MB), one set for each frame of the ring, and a buffer is written
again only when its frame comes round again. A draw counter (draw_serial)
tells stream_reserve and index_upload when the current buffer may be read
by a draw. Android is unchanged.
Checked on an M1 Pro, Chrome 155, 3024x1626, uncapped, A/B against
e0b581b1: b30 6.1 to 170 frames each second, Blood Gulch 57 to 595; with
vsync, 60 throughout. Screenshots of Blood Gulch before and after are the
same. ninja web builds without warnings; tools/test_web_build.py and
port/web/tests/*.test.js pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fast setup and the network test's join opened ui.map's pregame screen by name, where ui_widget.c opens the port's lobby; a ui.map of only the menus' tags hosts and joins with both.
msvc_comdat.c defines __inline as weak on Windows, which a static function cannot be; the accessor is __inline__. Checked by compiling msvc_comdat.c for i686-pc-windows-msvc.