You are viewing a potentially older version of this package. View all versions.
HelixDev-BeanheimNet-0.2.0 icon

BeanheimNet

Valheim 1.0 netcode fixes with telemetry: RTT-sized send window, Steam rate, fair priority, fauna throttle, deadbands, item merge/report. Server + clients.

Date uploaded 3 days ago
Version 0.2.0
Download link HelixDev-BeanheimNet-0.2.0.zip
Downloads 6
Dependency string HelixDev-BeanheimNet-0.2.0

This mod requires the following mods to function

denikson-BepInExPack_Valheim-5.4.2350 icon
denikson-BepInExPack_Valheim

BepInEx pack for Valheim. Preconfigured with the correct entry point for mods and preferred defaults for the community.

Preferred version: 5.4.2350

README

BeanheimNet

Valheim 1.0.x netcode improvements for the Beanheim dedicated server and its clients, with the telemetry to prove (or disprove) each one. One DLL, runs on the server and on clients. No ServerSync, no version lock: a client without it connects and gets vanilla behaviour, because nothing here changes the wire format.

The research behind it (decompiled 1.0.15 server assembly, prior-art survey, corrections to earlier assumptions) is in the profile repo at docs/networking.md.

What vanilla does, in one paragraph

Every ZDO update is sent reliable on one Steam lane. ZDOMan.SendZDOs keeps at most 10 KB in flight per peer (unacked bytes count), so throughput is 10 KB / round-trip-time — 68 KB/s at 150 ms — and Steam's send rate is pinned at 150 KB/s. The same limit applies to each client's uploads. Send order puts every building piece ahead of every creature, cart and drop. Owners write position on any exact change, so a bobbing or jittering object talks every frame, and ~95 % of relayed updates on this server were ambient fauna.

Features

Everything that is invisible to players ships on: F1, F2, F3, F4, F5, F6 and telemetry. The two that change what players see ship off: F8 Cleanup (deletes far-off floating junk) and F9 ItemMerge (merges nearby piles). Nothing here adds a version lock, so a player without the mod still connects; they simply keep vanilla behaviour on their side.

Toggles

Section What it does Where
F1 SendWindow Window per peer = RTT × target rate × BDP factor, clamped [MinWindowBytes, 65536]; falls back to vanilla when no reading exists. MinWindowBytes matters for LAN peers: a 0 ms ping gets no RTT sizing, and at this server's ~10 send rounds/s the vanilla 10 KB window is a ~100 KB/s cap on its own. Queue-drain compatibility for ServerSync/Jotunn-style waiters; raises Jotunn's MaximumSendQueueSize while on. Transpiler with an IL shape check that refuses if SendZDOs changed. both
F2 Scheduler Serve every connected peer each interval under a frame budget instead of one peer per frame with an idle arming frame. The dedicated server runs a hard 30 fps, so vanilla's lap is ~10 Hz per peer at two players; ServerTargetFrameRate (0 = vanilla 30) raises the frame cap at ~2× main-thread CPU. both
F3 SteamTransport SendRateMin and SendRateMax to SendRateKBps (Steam requires them equal), globally and on live connections, read back. Enable FIRST; without it F1 only builds queue. both
F4 RelayThrottle Per class (fish / creature / item) and distance band, cap how often Default-type objects are relayed. Distance is measured from the receiver on the server and from the nearest loaded player on the client uplink, so nothing anyone is looking at is slowed; defaults (near/far/beyond, Hz): fish 6/3/2, creature ∞/10/5, item ∞/5/3, bands at 40 m and 100 m. Receivers extrapolate motion between updates. Nothing is dropped; exempt: never-sent, owner changes, force-sends, players/ships/carts, non-Default types. both
F5 Priority Age-fair send order below the foreign-owned-Prioritized and Terrain tiers; live x/z reference position from the character ZDO. server
F6 Deadband Owner-side: skip position/rotation/velocity writes inside a deadband of the last written value; one exact zero velocity when an object comes to rest. Never players/ships/carts/Prioritized. clients
F7 Telemetry Per peer every 10 s: Steam RTT vs app RTT (the gap is bufferbloat), window, queue, in-flight, rounds, refused, candidates, sent, KB/s, deferred, throttled, ms/call, top prefabs; receive tally; boot census of every ZDO by prefab/type. Also appended to BepInEx/BeanheimNet-stats.log, which survives restarts. both
F8 Items Server: periodic ItemDrop report (by prefab, age, in water, far from players). Cleanup=true deletes items older than MaxAgeHours of played time, >ProtectRadiusMeters from every player, not near a base piece, (only in water by default), not protected/piece. Hard rule no setting relaxes: an item with custom data (every EpicLoot enchantment), a quality above 1, or a max stack of 1 (all gear) is never deleted. Every deletion is logged. server
F9 ItemMerge Client side: merge same-name, same-quality, same-variant plain stackable drops within MergeRadiusMeters (5) into one stack, every MergeIntervalSeconds (30). Vanilla's own auto-stack does this at 4 m, once per item, only while 200+ drops are loaded. Same hard exclusions as F8: enchanted, quality, variant or gear items are never merged. clients

Console: bnet stats | census | items | cleanup dryrun|run | reload | config (in-game console; the dedicated server binary never reads stdin, so on the server use the cfg instead: set one of [10. Triggers] Census / ItemReport / ItemCleanupOnce / Stats = true, it runs on the next reload (within 5 s) and resets itself to false).

Build and deploy

dotnet build BeanheimNet/BeanheimNet.csproj -c Release                          # build
dotnet build BeanheimNet/BeanheimNet.csproj -c Release -p:DeployToProfile=true  # + copy into the Gale profile

References resolve from the real game and Gale's BepInEx install; no binaries are committed. The profile's tools/vsync.sh diff then shows + BeanheimNet.dll; the server owner stops the server, runs apply, checks the RE-VERIFY block is all-zero, and restarts. Friends receive the DLL through the normal Gale profile sync.

Reading the numbers

  • deferred = candidates that did not fit the window this round. Sustained non-zero at 2+ peers means the window is the wall. At ≤ 1 peer it is structurally 0 (nothing to relay).
  • refused = rounds that sent nothing because the queue was above the window. High refused with high rtt is the 10 KB/RTT limit.
  • app RTT ≫ rtt (Steam) = data is queueing behind other data (bufferbloat). Raise F3 before F1.
  • throttled = F4 removals this interval (deferred by rate, not lost).
  • frame avg 33.3ms on the server = vanilla's 30 fps cap; sched frames/served/budget-breaks appear once F2 is on.
  • steam-status ok N not-ok N last <EResult> appears when Steam's connection-status call fails; without it F1 stays at vanilla for that peer and F3 cannot re-apply to live connections.
  • Vanilla's own Connections N ZDOS:X sent:Y recv:Z line stays as the cross-check; both counters are meaningless at ≤ 1 peer.

Measuring a change

To attribute a change, turn a toggle off in the cfg (live; no restart), play ≥ 10 min, compare the stats lines, turn it back on. The vanilla baseline for Beanheim was recorded on 2026-09-21 (see the profile's docs/networking.md): a LAN client pinned at ~130 KB/s upload with thousands of updates deferred per 10 s, and a base arrival relayed at 40–60 KB/s with 160,000+ deferred.

Rollback

[1. General] Enabled = false (live) makes every patch behave like vanilla on its next call. Full removal: delete BepInEx/plugins/BeanheimNet/ locally, vsync.sh diff shows - BeanheimNet.dll, stop, apply, restart. Nothing persists in the world except F8 deletions, and only when Cleanup = true.

Fail-soft rules

Every private vanilla member is bound once at startup (Bindings.cs); a missing member disables the features that need it with a logged reason. Every patch body is wrapped: an exception logs once per minute and that call falls back to vanilla. The plugin refuses to install anything but telemetry if Smoothbrain Network, ValheimTune, NetworkPerformanceSystem, ReturnToSender, BetterNetworking or VBNetTweaks is loaded, and lists every other Harmony owner of the methods it touches at boot ([owners] lines).

CHANGELOG

Changelog

0.2.2 — 2026-09-22

  • RelayThrottle: two new object classes. FloatingHz (default ∞/5/3) for floating objects that are neither items nor creatures (prefabs with a Floating component: logs, debris), which were unthrottled at any distance and the top uplink talker at a harbour; EffectHz (default 10/5/3) for networked sfx_/vfx_ prefabs (a sound that follows a bird). Floating objects use the item deadband.
  • Deadband: a ship with nobody aboard gets its own deadband (MooredShipPosMeters 0.05, MooredShipRotDegrees 2); a ship with a player aboard, players and carts stay untouched. Ship.HasPlayerOnboard is bound once at startup; if it is missing only this part is skipped.
  • Items: an item whose item data cannot be read (no data blob) is treated as valuable and never deleted, like enchanted, quality and gear items.
  • Telemetry: the stats line gains frames >50ms N >100ms N and gc N heap N MB cpu N% (gen-0 collections in the interval, managed heap, process-wide CPU as a share of one core).
  • Text: the Commands boot line names the 11 Triggers section; three leftover feature-key labels removed from a log line and two cfg descriptions.
  • README rewritten: short overview, before/after numbers from a live two-player A/B, settings and stats-line details under Advanced, build notes removed; manifest description shortened.

0.2.1 — 2026-09-21

  • cfg sections renamed to plain names (02 SendWindow, 03 Scheduler, 04 SteamTransport, 05 RelayThrottle, 06 Priority, 07 Deadband, 08 Telemetry, 09 Items, 10 ItemMerge, 11 Triggers); log tags match. Existing cfg files keep the old sections as orphans and the new ones start at defaults.
  • Scheduler credit carries across slow frames (a peer is still served at most once per frame), so a CPU-bound server reaches the configured rate.
  • README, comments and cfg text no longer refer to any particular installation.
  • MinWindowBytes defaults to 32768.

0.2.0 — 2026-09-21

  • Defaults: everything invisible to players is ON (window, scheduler, Steam rate, throttle, priority, deadbands, telemetry); Cleanup and ItemMerge stay OFF. A vanilla baseline was measured first.
  • Client uplink throttle measures distance from the nearest loaded player, not just the owner; gentler rates (fish 6/3/2, creature ∞/10/5, item ∞/5/3).
  • MinWindowBytes defaults to 16384.
  • Unwrap ServerSync's BufferingSocket wrapper to reach the real Steam socket (server-side status, window sizing and per-connection rate apply were inert without it).
  • Client-side item merge; hard rule that enchanted / quality / gear items are never deleted.
  • ServerTargetFrameRate; cfg Triggers; Steam status and per-connection diagnostics on the stats lines.
  • Thunderstore packaging (tools/package.sh).

0.1.0 — 2026-09-20

First build, against Valheim l-1.0.15 (network version 40), BepInEx 5.4.23.5, Harmony 2.9.