Baseline: GitHub main 3d0fcc7c075c2112946ec8d0299695a923d4ef83, verified against local files before editing. Engine, editor, scripts and native sources matched; 46 third-party example/project files differed only in newline representation. There were no missing files. The baseline source can be retrieved from the commit above.
All new behavior is opt-in. Existing scenes keep their legacy snapshot format and rates. Owner prediction always receives the full exact BodyState. Inputs already use packed button bits and quantized controls.
NetworkSession exposes the corresponding session options in the editor (all off/unlimited by default). For code-created sessions, set NetworkManager.OptimizeSnapshots=true on the server to use individually decodable snapshot packets (default payload ceiling 1100 bytes, configurable 256..1200). Packets from the same tick are accepted independently; delayed reliable final keys are checked by object tick, rather than discarded by the newest global tick. Remote transforms no longer share one enormous fragmented snapshot. Owned prediction corrections are kept together so multi-object prediction replays only once: an exceptional ownership group larger than the packet limit retains legacy fragmentation. Ordinary one-aircraft ownership fits one packet. Optimized packets use a new message type: all peers must run this engine version before enabling them.
Per NetworkIdentity:
remoteFormat=Legacy: retain existing compact/fullPrecision selection.remoteFormat=ExactTransform: exact position/quaternion only for nonowners (29 bytes including flags, plus object ID). No numerical precision loss.remoteFormat=QuantizedTransform: signed 24-bit positions at 10,000 steps/world unit, and the existing smallest-three quaternion (14 bytes including flags, plus ID). In metre-based worlds the position step is 0.1mm, with a conservative 0.12mm tested vector error bound. Positions outside +/-838 world units fall back to exact floats. Quaternion quantization has a separate bounded angular error. These affect remote interpolation, never host physics or owner prediction.remoteFormat=QuantizedBody: for physics bodies that clients only watch. Position at 65,536 steps/world unit (about 15 micrometres in metre-based worlds, exact floats beyond +/-128 units), the smallest-three quaternion, and linear and angular velocity as three 16-bit numbers sharing a power-of-two scale (one byte for a zero velocity). 28 bytes including flags for a moving body, 16 at rest, against 85 for a fullfullPrecisionrecord. Clients keep the real velocities for interpolation across impacts, as a full record does. KeepfullPrecisionon as well if small movements must still count as changes. A body withphysicsPrediction=Onis predicted whatever its format (before this,fullPrecisionwithLegacysilently overrode it).remoteIntervalTicks: zero uses the global interval; e.g. 6 gives at most 10Hz at 60 server ticks. Lower rates can reduce visual responsiveness.dormantWhenSleeping: send a reliable final transform, then omit refreshes until wake/change. New clients get the sleeping state reliably. Sleep keys stop extrapolation; wake/change resumes updates. An unchanged transform never relies on an unacknowledged delta baseline.distanceFiltered: allow the session relevance radius to omit this object’s remote transforms. Owned state is exempt. Re-entry forces updates even if stationary. This filters transforms only, not spawns, visibility or gameplay events.snapshotPriority: larger values are serviced first under budget, then oldest-delivered first within equal priority. Persistent high-priority traffic can delay lower-priority objects.
Per NetworkManager:
SnapshotBytesPerSecond: optional per-client snapshot application-payload budget. Zero is unlimited. Reserves exact own prediction first; owner traffic can exceed an impossibly small budget. Integer scheduling uses a minimum 128 bytes per snapshot, so configured rates below that floor cannot be enforced. Does not cap reliable spawns, gameplay messages or transport overhead/retransmissions. Those must be included separately in hosting estimates.SnapshotRelevanceRadius: world-unit distance from the client’s player, only for opted-in identities. Zero disables; no anchor means no filtering.LosslessCompression: lossless range coding. Direct uses ENet’s built-in packet compressor (enable on BOTH endpoints before starting). Steam/WebRTC use transport-independent message frames, decoded automatically by this engine version. Frames are sent only when smaller, preserve every float/NaN payload bit, and bound decompression output to 2MiB. Allocation failure falls back to plaintext for message framing; Direct setup failure fails session startup. This is a standalone codec on Steam, not ENet networking.MessageCompression: force the same message-framing path on any transport. Thesteamfixture profile uses this over Direct UDP so lag/loss and multiplayer tests exercise Steam-compatible application framing without an account or any claim of live Steam verification.TransportBytesSentPerSecond: encoded application bytes offered to the transport before simulated loss. This includes message-framing compression but not Direct ENet’s subsequent built-in compression or physical transport overhead. Received message counters count encoded delivered bytes before application-frame decompression.ProfileSnapshots: opt-in CPU timing for snapshot encode/decode; exposes cumulative time, call counts and worst-call time. These are distinct from elapsed frame intervals.WireBytesSent/Received: ENet UDP payload totals including protocol headers, compression and retransmissions. They exclude IP/UDP headers. Application counters still measure messages before simulated packet loss and compression. Steam/WebRTC return zero (unsupported measurement), not a wire estimate.
The test fixture profiles are baseline, exact, quantized, sleep, rate, budget, compressed, all and steam. They do not change normal game settings. all uses quantized transforms at 10Hz, dormancy and ENet compression; it does not enable relevance or a budget, keeping all 4096 props in the scaling workload. The steam profile uses the same quantized 10Hz/dormant policy with transport-independent compression instead of ENet transport compression. The budget profile exercises a 500,000 B/s per-client application snapshot allowance. Dedicated field-by-field deltas (position vs rotation etc.) are deliberately deferred at the user’s request.
Tests: python tools/run_network_bandwidth_tests.py --engine <engine-root>. These exercise serialization, quantization error/fallback/truncation, ownership, same-tick batching, delayed final keys, rate limits, relevance/re-entry, destruction cleanup, actual native-body dormancy/wake, budget fairness and stopped extrapolation without requiring sockets. The game runner uses actual UDP host/client processes and checks each client’s prediction, crash recovery and all-prop convergence. No headless test establishes visual quality or real internet/Steam behavior.
Root-cause check: the bundled ENet peer.c converts a large flags=0 packet into acknowledged reliable fragments. A socket-free native probe queued 260 reliable fragments for 356500 bytes, versus one unreliable command for 1100 bytes. This supports the observed client backlog even while the server maintained 60 ticks/second.
Steam: Steam holds each connection to 256 KB/s unless told otherwise, and anything sent past that waits in its queue, delaying later messages too. The engine now raises the cap to Steam.SendRate (1 MiB/s by default; set it before Steam starts). Tumble Toys measured about 220 KB/s per player with full prop records in a busy room, and about 88 KB/s with QuantizedBody.
This guide is docs/NETWORK_BANDWIDTH_OPTIONS.md in the engine’s repository. Spotted a mistake? Open an issue.