Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
BeanheimNet
Networking fixes for Valheim dedicated servers and clients: a bigger send window, a higher Steam send rate, a fairer send order and fewer wasted updates, plus per-player network stats.
By HelixDev
| Date uploaded | a day ago |
| Version | 0.3.1 |
| Download link | HelixDev-BeanheimNet-0.3.1.zip |
| Downloads | 369 |
| Dependency string | HelixDev-BeanheimNet-0.3.1 |
This mod requires the following mods to function
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.2350README
BeanheimNet
Networking fixes for Valheim dedicated servers. Install it on the server and on every player's game. It sends more of what players need, sooner, and stops sending updates nobody needs. It also writes network stats to a log file so you can see what your server is actually doing.
Why
Vanilla Valheim keeps at most 10 KB of updates in flight per player and caps Steam's send rate at 150 KB/s. A player 150 ms away can receive about 68 KB/s at most, and the same limit caps what each player uploads. Building pieces always go out before creatures, carts and dropped items. Fish and ambient animals send an update every frame they move. On a busy server you see creatures that stutter or teleport, carts that freeze and snap, drops that show up seconds late, and hits that land late.
What it does
| Feature | What it does | Default |
|---|---|---|
| SendWindow | Sizes each player's window from their ping instead of the fixed 10 KB | on |
| SteamTransport | Raises Steam's per-connection send rate from 150 KB/s to 1 MB/s | on |
| Scheduler | Sends to every player each round instead of one player per frame | on |
| RelayThrottle | Sends fish, far creatures, far items, floating logs and sound effects less often. Near a player only fish (6 updates/s) and sound effects (10/s) are slowed | on |
| Priority | Creatures, carts and drops next to a player no longer wait behind a distant building backlog | on |
| Deadband | Whoever simulates an object skips updates for movement under a centimetre or a degree, and sends one exact stop when it comes to rest | on |
| Telemetry | Network stats every 10 s in the BepInEx log and in BepInEx/BeanheimNet-stats.log |
on |
| Items | Reports dropped items on the server. Can also delete old floating junk | report on, delete off |
| ItemMerge | Merges nearby stacks of the same plain item | off |
| Diagnostics | Frame profiler and reflection sampler, for chasing long frames | off |
A dedicated server's frame cap also goes from 30 to 60 fps (Scheduler.ServerTargetFrameRate).
That setting follows the master switch, not the Scheduler switch.
What you might want to change
Most servers need nothing changed. The server and the clients pick their own defaults on first start.
- Server short on CPU: set
[99 Advanced] Scheduler.ServerTargetFrameRate = 0to go back to vanilla's 30 fps. - Too many dropped items lying around: set
[01 General] RunItemReport = trueto see what is out there, thenItemCleanup = trueto delete items that have sat for 6 hours of play more than 200 m from everyone. By default only items in the water go. Enchanted items, gear, anything above quality 1 and anything near a base are never touched. - Piles of small stacks:
[01 General] ItemMerge = truemerges nearby stacks of the same plain item. - Less log output:
[01 General] Telemetry = false, or switch off just the extra lines (PeerExtras,ClientExtras,DesyncCounters). - Something seems wrong:
[01 General] Enabled = falseputs every patch back to vanilla behaviour within 5 s, without a restart.
Install
- Install with your mod manager on the server and on each player's game. A player who doesn't have it can still join; they just don't get the client side of it.
- The config is
BepInEx/config/helix.beanheim.net.cfg. Changes are picked up within 5 s, no restart. Every on/off switch is in[01 General](the General group in ConfigurationManager). The tuning values are in[99 Advanced], namedSection.Key(for exampleScheduler.SendIntervalSeconds), and the diagnostics in[12 Diagnostics]. - Some defaults differ between server and client: a dedicated server gets a 60 fps frame cap,
Scheduler.SendIntervalSeconds0.033 andRelayThrottle.OtherHz0,5,3, while clients keep 0.05 and 0,0,0. The values are written into the file, so don't copy a server cfg to a client or the other way. - Coming from 0.2.1, 0.2.2 or 0.3.0, your settings move on the first start (switches into
[01 General], tuning values into[99 Advanced]) and anything you had changed is kept. A 0.2.0 cfg uses older section names and starts at the defaults. Older versions can't read the new layout, so if you go back, put back your old cfg too. - A dedicated server has no console. Use the one-shot keys in
[01 General](RunCensus,RunItemReport,RunItemCleanupOnce,RunStats): set one to true, it runs and resets itself. - On a client the console command is
bnet stats | census | items | cleanup dryrun|run | reload | config. - To uninstall, remove it in your mod manager. Nothing stays in the world except items deleted by
ItemCleanupor merged byItemMerge, if you turned those on.
Compatibility
BeanheimNet is not compatible with other mods that rewrite Valheim's network send code: Smoothbrain Network, ValheimTune, NetworkPerformanceSystem, ReturnToSender, BetterNetworking, BetterNetworking_Valheim, VBNetTweaks, FiresGhettoNetworking, SkadiNet, WarheimNetwork and NetworkTweaks. Use one or the other. If one of them is loaded, BeanheimNet logs a warning naming it and keeps running anyway.
Mods that only touch the same methods in passing are fine. At startup BeanheimNet lists every other
mod that patches the methods it uses ([owners] lines in the log). If a game update changes
something it relies on, it switches off only the parts that need it and logs why. An error inside
one of its patches is logged at most once a minute, and that call falls back to vanilla.
Measured
Version 0.3.0, two players, one on LAN and one at 48 ms ping, at a large base on a heavily modded server. All features were switched off for ten minutes and back on for ten, live, with the stats running the whole time. The server and the LAN player's game were switched; the other player stayed on 0.2.2 throughout.
| Features off | Features on | |
|---|---|---|
| Server frame time | 47 ms (frame cap 30 fps) | 29 ms (frame cap 60 fps) |
| Server CPU, share of one core | 61 % | 69 % |
| Updates of moving objects near you arriving within 100 ms of the last | 6 % | 75 % |
| Time a moving object near a player waits before it is relayed, 95th percentile | 114–121 ms | 75–96 ms |
| Longest single wait for such an object | 13.7 s | 1.7 s |
| Deferred updates in the worst 10 s interval, remote player | 440,227 | 131,635 |
| Remote player's receive rate while entering an area | median 60 KB/s, max 68 (the vanilla ceiling) | median 91 KB/s, max 154 |
| The LAN player's upload | 121 KB/s against the 150 KB/s Steam cap, 1.2 million deferred | 57 KB/s, 7,247 deferred |
| Updates the server receives at rest | 759/s | 384/s |
| Client frame time | 13.9 ms | 13.7 ms |
Deferred counts an update once for every send round it waits.
Some numbers got worse. The server used 8 points more of one core. The remote player's app ping
(a round trip through the game's own send queue; Steam's ping was 47 ms) went from 106 ms to 131 ms
while entering an area, because a bigger window queues more. At rest it was 108 ms off and 106 ms on.
The LAN player's went from 33 ms to 16 ms, which is one server frame. The LAN player's game also saw
more position corrections of 2 m or more with features on (805 against 644), but it was receiving
far more updates of nearby moving objects in that window.
Advanced
Settings
The tuning values below are in [99 Advanced] under the section name shown.
SendWindow: window = ping ×TargetRateKBps(300) ×BdpFactor(1.25), clamped betweenMinWindowBytes(32768) andMaxWindowBytes(65536); vanilla 10240 when there is no ping reading. Includes queue-drain compatibility for mods that wait on the socket queue (ServerSync, Jotunn, ConditionalConfigSync) and raises Jotunn'sMaximumSendQueueSizewhile on. It doesn't install ifZDOMan.SendZDOsdoesn't look the way it expects.Scheduler:SendIntervalSeconds(0.033 on a dedicated server, 0.05 on clients) caps send rounds per player per second;FrameBudgetMs(4) bounds the time spent per frame.ServerTargetFrameRate(60; 0 = vanilla 30) applies to a dedicated server only and follows the master switch, not the Scheduler switch. In the measurement above it was switched together with the other features, so the frame time and CPU there are for everything at once.SteamTransport: SendRateMin and SendRateMax set toSendRateKBps(1024). Steam requires them equal. Applied globally and to live connections, and read back.SendBufferBytes(0 = Steam's default) sets Steam's send buffer; the original size is put back when it returns to 0.RelayThrottle: rates in updates per second as near/far/beyond: fish 6/3/2, creature ∞/10/5, item ∞/5/3, floating ∞/5/3, effect 10/5/3, other ∞/5/3 on a dedicated server and ∞/∞/∞ on clients. Bands atNearMeters(40) andFarMeters(100). Distance is measured from the receiving player on the server and from the nearest loaded player on the client upload. Never throttled: never-sent objects, ownership changes, force-sends, players, ships, carts and non-Default object types. Nothing is dropped; a held update goes in the next round.Priority: score = distance −AgeWeight(1.5) × seconds since the last sync −SolidBiasMeters(64) for building pieces −NewNearBonusMeters(200) for never-sent objects insideNearMeters. Foreign-owned Prioritized objects and terrain keep their top tiers. Uses the live character position instead of the one the client reports every 2 s.Deadband: item 0.02 m / 2°, creature 0.01 m / 1°, other 0.01 m / 1°, velocity 0.05 m/s. Measured against the last written value, not the last frame. When an object comes to rest its velocity, and its body velocity, is written as exactly zero once, so other players' games stop moving it. Never players, carts or Prioritized objects. A ship with nobody aboard gets its own band, 0.05 m / 2°, while it is still; vanilla ships are Prioritized, so this reaches only modded ships of the default object type.Items:ItemCleanupdeletes items older thanMaxAgeHours(6) of played time, farther thanProtectRadiusMeters(200) from every player, not withinBaseProxyRadiusMetersof aBaseProxyPrefabspiece, in water whenOnlyInWateris set, and not matchingProtectedPatterns. An item with custom data (every EpicLoot enchantment), a quality above 1, a max stack of 1 (all gear) or unreadable item data is never deleted. Every condition is checked again at the moment of deletion. Every deletion is logged.ItemMerge: same name, quality 1, variant 0, no custom data, withinMergeRadiusMeters(5), checked everyMergeIntervalSeconds(30). Vanilla auto-stacks at 4 m, once per item and only while more than 200 drops are loaded.
Reading the stats line
deferred: updates that did not fit the window this round. Sustained non-zero with two or more players means the window is the limit. With one player it is always 0.refused: rounds that sent nothing because the queue was above the window.rttis Steam's ping;appis a ping through the game's own send queue.appincludes one server frame and one client frame (about 16 ms with the server at 60 fps, 33–76 ms at 30), so the queueing delay isapp − rtt − floor.throttled: updates held back by RelayThrottle this interval. They are sent later, not dropped.frame avgon a server line is the server's frame time;frames >50ms N >100ms Ncounts long frames in the interval.sched frames/served/budget-breaksappear when Scheduler is on.gc N heap N MB cpu N%: garbage collections of any generation in the interval, managed heap size, and process-wide CPU as a share of one core. A long frame with lowcpuis waiting, not working.steam-status ok N not-ok N last <EResult>appears when Steam's connection-status call fails; that player's window stays vanilla until it works.- Vanilla's own
Connections N ZDOS:X sent:Y recv:Zline is the cross-check. Both counters are 0 with one player. - To measure a change: turn one toggle off in the cfg, play ten minutes, compare the lines, turn it back on.
Telemetry
These lines only read; nothing is sent or changed. Each has a switch in [01 General], on by
default.
peerx <player>(server, after eachpeerline;PeerExtras):qtimeis Steam's queue time andlossthe local and remote packet loss.relay delay nearis how long a moving object near that player waited before it was relayed (95th percentile and max);pending>250counts each time an object near them went more than 250 ms without being relayed.owners near self/other/noneis who owns the creatures within 40 m of them.owner changes N p2p Ncounts creature ownership changes involving them, and how many moved straight between two players.helm!=ownerreads the whole interval when, at the check, this player holds the helm of a ship somebody else owns, and 0 otherwise (-1 if that part could not be installed).silence maxis the longest gap between pings from that player.client(client, after thestatsline;ClientExtras):gaps nearis the time between updates of moving objects near you, bucketed <100/<250/<500/<1000/≥1000 ms.jumpis how far an update moved an object from where your game expected it (≥0.5 m, ≥2 m), andsnapsan object moving 4.9 m or more in one step, split by creature, player and ship; other objects count in the totals only. A move of 100 m or more is a teleport and is not counted.nettime corris the clock corrections from the server,inst pendingandcreated/sthe objects waiting to be created,zdodatathe object data received.desync(server;DesyncCounters):ghost-misscounts copies left out of date after an object crossed a zone border.misroutedcounts damage aimed at a creature, fish or player that reached nobody able to apply it: another player who does not own the target (peer), the server (server) or nobody (none).rpc toplists routed RPCs by name with their recipient counts.census ghostsandcensus types(server, at boot and on theRunCensustrigger): planned (*_planned) pieces by zone and age, and every object counted by class and network type.
Diagnostics
All off by default, in [12 Diagnostics], and live.
Profiler(both sides): aprofline per interval with the time per frame by phase, the busiest update methods, BeanheimNet's own hooks and its own overhead (self). Its hooks are installed a few per frame when it turns on and removed when it turns off.ReflectionSampler(both sides): areflline counting reflection calls on the main thread and naming the code that makes them in long frames. It captures a call stack for a small sample of calls, so leave it off unless you are chasing long frames.GcTimeSliceMs(dedicated server): Unity's incremental garbage-collector time slice in milliseconds, for measuring long frames. 0 leaves Unity's value alone.
Credits
Some ideas come from Network Performance System, FiresGhettoNetworking, LeanNet and Smoothbrain Network. Ideas only; no code was copied.
CHANGELOG
Changelog
0.3.1 — 2026-09-27
- Every on/off switch is now in
[01 General]under its own name, listed in a fixed order in ConfigurationManager:Enabled, thenSendWindow,SteamTransport,Scheduler,RelayThrottle,Priority,Deadband,Telemetry,StatsFile,PeerExtras,ClientExtras,DesyncCounters,CensusOnBoot,ItemReport(was[09 Items] ReportEnabled),ItemCleanup(wasCleanup),ItemMerge, and the one-shotRunCensus,RunItemReport,RunItemCleanupOnce,RunStats(were[11 Triggers]). Tuning values stay in[99 Advanced]and diagnostics in[12 Diagnostics]. The first start moves the switches once, keeps anything you had changed, and logsconfig: moved N switches to [01 General]: .... - BeanheimNet no longer switches itself to telemetry only when another networking mod is loaded. It logs a warning naming that mod and keeps running. The two are not compatible; remove one of them. The stats file's boot line says
conflicts=instead ofrefused=. - README rewritten: what you might want to change, installing with a mod manager, and a plain compatibility note. The package description no longer says "No version lock"; a player without the mod can still join, as before.
0.3.0 — 2026-09-27
Settings
- The on/off switches stay in the numbered sections. The 51 tuning values moved into one
[99 Advanced]section, each named after its old section (Scheduler.SendIntervalSeconds,RelayThrottle.OtherHz, ...). - The first 0.3.0 start carries values over once from a 0.2.1 or 0.2.2 cfg (a 0.2.0 cfg uses older section names and starts at the defaults) and logs
config: moved 51 settings to [99 Advanced]: kept N changed values .... A value you changed is kept. A value equal to the old default counts as a default, so the new default applies. If a[99 Advanced]key is already in the file, it wins, and a differing old value is named in a warning. - New defaults on a dedicated server:
Scheduler.ServerTargetFrameRate60,Scheduler.SendIntervalSeconds0.033,RelayThrottle.OtherHz0,5,3. Clients keep 0.05 and 0,0,0; the frame-rate setting does nothing on a client. - A cfg copied from a server to a client (or back) carries the other side's values, because they are written into the file.
- Downgrading to 0.2.2 ignores
[99 Advanced]and runs 0.2.2's defaults. Keep a copy of the pre-0.3.0 cfg if you may roll back.
Fixes
- Items: cleanup re-checks every condition for each candidate at the moment of deletion. The game recycles object records, so a record freed between the scan and the delete could belong to an unrelated object by then. Skips are logged as
items: recheck skipped N at delete. - Deadband: when a body comes to rest, its body velocity and angular velocity are written as exactly zero once, and the game's own write of the small leftover velocity is skipped that frame, so other players' games put the body to sleep. This matters for tree logs and some creatures; no item prefab syncs body velocity.
- Deadband: the moored-ship band applies only while the hull is still (horizontal speed at most 0.25 m/s, turning at most 0.1 rad/s). Vanilla ships are Prioritized objects, which the deadband never touches, so in 0.2.2 and 0.3.0 the moored-ship band only reaches modded ships of the default object type.
- When another networking mod is detected, the frame-rate setting, ItemMerge and item cleanup now also stand down; reports, census and stats continue.
- SteamTransport: reads Steam's send buffer size before changing it and restores it when
SendBufferBytesgoes back to 0 or the feature is switched off.SendBufferBytesis now applied at aSendRateKBpsof 150 too. If the original size cannot be read, the buffer is left alone. - A prefab that is not loaded yet is no longer classed as "other" for the rest of the session; it is looked up again after 30 s.
- Five more networking mods switch BeanheimNet to telemetry only:
DIT.BetterNetworking10,com.Fire.FiresGhettoNetworkMod,sighsorry.SkadiNet,dzk.warheimnetwork,Searica.Valheim.NetworkTweaks. - 0.2.1 said the mod no longer referred to any particular installation. That was incomplete: some comments and descriptions still did, and the DLL carried a local build path. Both are removed.
Telemetry (every new line reads only; nothing is sent or changed)
peerx(server, after eachpeerline): Steam queue time and packet loss, how long a moving object near that player waited before it was relayed, who owns the creatures near them, ownership changes (and how many moved straight between two players), helm holder vs ship owner, the longest silence.client(client, after thestatsline): arrival gaps of moving objects near you, position corrections (jumps and snaps) by creature/player/ship, clock corrections from the server, objects waiting to be created, received object data.desync(server): copies left out of date after an object crossed a zone border, damage sent to a player who does not own the target, routed RPCs by name.census(server): PlanBuild ghost counts by zone and age, and object counts by class and type.gcon the stats line counts every garbage collection in the interval; it was described as gen-0 only.
Diagnostics ([12 Diagnostics], all off by default, live)
Profiler: aprofline with per-frame time by game phase, the slowest named systems, BeanheimNet's own hooks and its own overhead.ReflectionSampler: areflline naming which code makes reflection calls in long frames.GcTimeSliceMs(dedicated server): sets Unity's incremental garbage-collector time slice, for measuring long frames. 0 leaves Unity's value alone.
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 networkedsfx_/vfx_prefabs (a sound that follows a bird). Floating objects use the item deadband. - Deadband: a ship with nobody aboard gets its own deadband (
MooredShipPosMeters0.05,MooredShipRotDegrees2); a ship with a player aboard, players and carts stay untouched.Ship.HasPlayerOnboardis 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 Nandgc 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 Triggerssection; 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.
MinWindowBytesdefaults to 32768.
0.2.0 — 2026-09-21
- Defaults: everything invisible to players is ON (window, scheduler, Steam rate, throttle, priority, deadbands, telemetry);
CleanupandItemMergestay 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).
MinWindowBytesdefaults to 16384.- Unwrap ServerSync's
BufferingSocketwrapper 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; cfgTriggers; 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.