Battlezone drops any UDP packet that doesn't arrive in exact sequential order, even by a millisecond. WiFi, wireless, international, anything with mild jitter — you're not losing those packets to the network, you're losing them to a sequencing rule that tolerates zero deviation.
This patch sits in front of the game as a DLL proxy. It buffers wayward packets briefly and releases them in order, forces bigger socket buffers, marks your traffic for router priority, and writes the game's own network tuning directly into memory. The game never knows it's there.
Everyone in the lobby should install it. The reorder fix is receive-side, so your install protects your inbound packets and nobody else's.
Current version: V4.7 — see CHANGELOG.md.
Paste into PowerShell:
powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/PiercingXX/battlezone-netcode-patch/master/install/install_windows.ps1 | iex"Finds your install, downloads the SHA256-verified winmm.dll, and installs the
tuned net.ini. No launch options to set. Launch the game normally.
Defender note: some users see
winmm.dllquarantined asProgram:Win32/Contebrew.A!ml— a heuristic detection common for unsigned DLL proxies. Restore it from Protection History and add an exception for that one file in the game folder. Don't disable AV globally.
Step 1 — paste into a terminal:
curl -fsSL https://raw.githubusercontent.com/PiercingXX/battlezone-netcode-patch/master/install/install_linux.sh | bashIt detects your install (native, Snap or Flatpak Steam), builds dsound.dll
from source, offers to raise the kernel UDP buffer limits, and installs the
tuned net.ini. Set BZNET_ASSUME_YES=1 to skip the prompts.
Step 2 — set Steam launch options for Battlezone 98 Redux:
WINEDLLOVERRIDES=dsound=n,b %command% -nointro
Without step 2 the DLL is never loaded. That's the whole install.
Prefer to do it by hand, or need a second install patched? See docs/MANUAL_INSTALL.md.
Play one multiplayer match, quit, then run the verify script.
Linux:
cd "/path/to/Battlezone 98 Redux"
/path/to/battlezone-netcode-patch/Linux/verify_net_patch.shWindows:
.\Microslop\verify_windows.ps1Expect VERIFY RESULT: PASS. If you'd rather eyeball it, open the proxy log
(dsound_proxy.log or winmm_proxy.log, next to the game exe) and look for:
| Line | Means |
|---|---|
reorder: enabled max_window_ms=100 … |
reorder buffer is live |
effective readback SO_SNDBUF=524288 … SO_RCVBUF=4194304 |
socket buffers applied |
net_patch: version confirmed |
the game build was recognised |
net_patch: MinBandwidth 4000 -> 16000 |
tuning is being written into the game |
reorder_stats: … (every 10s) |
counters are recording |
send_stats: … (every 10s) |
outbound traffic is being measured |
Not working? net_patch: … VETOED or 0 version signature matches means
the game updated and the memory addresses moved — the patch falls back to stock
behaviour rather than risking anything. Everything else still works. Open an
issue with the log.
The numbers that matter now are hold_ms, evicted and visible warps, not just the drop count. Collecting them takes two files per player per game.
- Everyone installs the patch and confirms
VERIFY RESULT: PASS. - Agree who is hosting. Auto-kick thresholds are enforced by the host only, so the host's install is what decides whether anyone gets kicked.
- If anyone is subscribed to the Steam Workshop mod "Auto-Kick Reduction
Patch" (
1895622040), unsubscribe. It ships its ownnet.inithat overrides the patch's and caps send rate at 32 KB/s. Disabling it in-game is not enough — you must unsubscribe.
Critical:
BZLogger.txtis overwritten every time the game launches. Copy it aside before anyone relaunches, or the game is lost.
From the game folder (…/steamapps/common/Battlezone 98 Redux/), copy both:
BZLogger.txt— the game's own log (drops, warps, kicks, governor)dsound_proxy.log(Linux) orwinmm_proxy.log(Windows) — the patch's counters
Rename them per player and per game, e.g. game3_piercingxx_BZLogger.txt.
Zip and attach the files. Pasted logs always truncate — these run to tens of megabytes.
python3 tools/analyze_drops.py game3_piercingxx_dsound_proxy.log game3_piercingxx_BZLogger.txt| What it reports | Healthy | What a bad value means |
|---|---|---|
| real drops/min, per peer | low | high on one link only = that peer's uplink |
hold_ms max |
well under 100 | latency this patch is adding to you — high here is warping and kick risk, not a win |
evicted |
0 | a queue filled and packets went out out-of-order; raise BZ_REORDER_DEPTH |
emsgsize |
0 | datagrams were too big for the drain buffer and the stack destroyed them — report this |
| visible warps/min (≥50 m) | low | the actual symptom, as opposed to the raw warp count, which is ~90% sub-metre noise |
governor range |
starts ≥16000 | still opening at 4000 means the cold-start fix didn't apply |
peak_pps / burst_seconds |
low | your machine is producing retransmit floods |
A drop-count improvement bought with a large hold_ms max is a regression, not
a win. That's the trap every earlier round of testing walked into.
make -C tests run # 30 cases, 506 assertions, no game requiredOnly needed for deep analysis — see logging_readme.md.
Receive side (helps you):
- Adaptive per-peer reorder buffer. Starts at a 5 ms floor so clean links
pay almost nothing, grows only when reordering is actually observed, and
decays back within seconds.
BZ_REORDER_MAX_HOLD_MScaps how long any packet can ever be held. - 4 MB receive / 512 KB send socket buffers, re-forced through a
setsockopthook so the game can't shrink them back. - Counters (
reorder_stats) reporting deliveries by kind, drops, and the latency the patch itself adds.
Send side (helps everyone receiving from you):
- The game's
[Net]tuning, written into memory every 100 ms:MinBandwidth16000,MaxBandwidth320000,UpCount100,DownCount50,MaxPing450. StockMaxPing = 300matters most for warping — a jitter spike makes the governor cut your send rate, so fewer updates arrive, so you warp more, and the cut sustains the spike.net.iniwas supposed to deliver these but has twice been caught found-but-not-applied. - Governor cold-start fix. The game opens every match at a hardcoded 4000 B/s trickle; this lifts it to 16000.
- DSCP priority marking (EF/46) so a WMM or SQM router serves your game traffic ahead of bulk downloads. Real effect under Proton; a no-op on stock Windows.
- Outbound burst measurement (
send_stats), always on.
Host only:
- Auto-kick relax. A player is ejected once their connection stays bad for 15 s (stock). Widened to 60 s with a 2 s ping ceiling, so a transient spike no longer kicks anyone.
All memory writes are data-only and DRM-safe — no game code is modified — and every address is sanity-checked before it's written.
Everything above has an environment variable, and the defaults are meant to be what you want. Full tables:
The ones worth knowing:
| Variable | Default | Effect |
|---|---|---|
BZ_NET_TUNE |
1 |
0 restores the game's stock governor behaviour |
BZ_AUTOKICK_RELAX |
1 |
0 restores stock auto-kicking |
BZ_GOV_START |
16000 |
0 restores the stock 4000 B/s cold start |
BZ_REORDER |
1 |
0 disables inbound reordering entirely |
BZ_REORDER_STATS |
1 |
0 silences the 10-second counter lines |
BZ_SEND_PACE |
0 |
bytes/sec outbound smoothing; off, measure first |
BZ_IOCP_SCAN |
0 |
Windows: log which completion API the game uses |
-
It fixes ordering, not loss or congestion. No receiver-side patch can fix a saturated uplink on the sending peer's end. That's a wired-ethernet or router-QoS problem for them.
-
Reordering is not free, and the drop counter hides the cost. Holding a packet to reorder it adds latency, and the game's drop count stops counting a packet the moment it's held — whether or not you're better off. V4.7 bounds the cost and measures it, but the old "65% fewer drops" headline was never the whole story.
-
Windows players get no inbound reordering yet. On real Windows the game receives via overlapped/IOCP, which the reorder hook deliberately bypasses — routing those through it froze the game at the loading screen in V4.1. Windows installs get the socket buffers, the
[Net]tuning, the governor fix and the auto-kick relax; reordering is fully active only under Proton.BZ_IOCP_REORDER=1exists but has never run on real Windows — leave it off unless you're deliberately testing it.BZ_IOCP_SCAN=1is read-only and safe, and answers the one question blocking this work. -
Ping packets aren't yet exempt from receive buffering. If ping replies ride the ordered queue, the buffer inflates the round-trip time a host measures against the kick threshold.
BZ_REORDER_MAX_HOLD_MScaps the damage; a proper fast lane needs one capture session to identify the packet type. -
The memory addresses are pinned to one game build. If Rebellion patches the game, expect
net_patch: … VETOEDand stock behaviour — not a crash, but not the fix either. -
A workshop mod shipping
net.inioverrides the patch's copy. Unsubscribe; disabling in-game isn't enough.
net.inionly loads through the mod system — and even then, found ≠ applied. A copy next to the exe is silently ignored. Delivered as a local mod the game logsMOD FOUND, yet a 2026-07-05 match proved the values still weren't used: the host ranAutoKickTime = 45000and kicked at the stock 15 s. Working theory is that only the session's active mod (the map's) is parsed. This is why V4.7 writes the values into memory instead.- The send governor runs on every machine, not just the host's — contrary to long-standing community wisdom. Only auto-kick is host-enforced.
- The raw warp count means nothing. ~90% of
Possible Large Warplines are sub-metre corrections. Filter by distance or you're measuring noise. - The game imports winsock functions by ordinal and sends all P2P traffic via
WSASendTo;sendtoisn't in its import table at all.
Full research notes: resources/ · test-logs/