Skip to content

Networking

TL;DR: From a WASM script you can send raw byte payloads to specific players (or broadcast) via CVR_Networking_SendMessage with one of three delivery modes. CVR batches your sends on the underlying DarkRift transport. Use WillMessageBeDropped before enqueueing large messages and back off when NetworkCloggedPercentage stays high.

Source: CVR-GameFiles/WasmScripting/SendType.cs.

public enum SendType {
Unreliable = 0, // fire and forget, possibly dropped, possibly reordered
UnreliableSequenced = 1, // possibly dropped, but never reordered (old messages discarded)
Reliable = 2, // guaranteed delivery and order; higher latency if packets are lost
}

Pick Unreliable for frequent state ticks where only the latest matters (positions). UnreliableSequenced for streams where gaps are fine but order is not (telemetry with sequence numbers). Reliable for discrete events (someone completed a lap, a button was pressed).

Host binding signature (NetworkingBindings.cs):

CVR_Networking_SendMessage(
i32 messagePtr, i32 messageLength,
i32 playerIdsPtr, i32 playerIdsLength, // array of i16 player IDs
i32 sendType)
  • message — the raw bytes to ship. Host copies out of guest linear memory immediately, so you can reuse the buffer after the call.
  • playerIds — array of 16-bit player IDs. Pass length 0 to broadcast to all other players in the instance.
  • sendType — integer value of SendType above.

The host-side routing lives in CVR-GameFiles/WasmScripting/WasmNetworkManager.cs. Messages are buffered per-VM (Sender struct) and flushed on DarkRift’s tick.

Incoming messages arrive as a game-level event. The exact guest-side entry point depends on what the CCK WasmModule exposes; at the host boundary the dispatch uses the scripting_call_trigger_script_event_object mechanism with a wrapped message object.

Practically: define a method on your behaviour following the CCK WasmModule’s documented contract (typically a public method receiving a message object that wraps sender + byte payload). Consult your CCKWasmModule version — the private module currently at version 0.0.65 owns the exact API surface for parsing messages.

BindingSignaturePurpose
CVR_Networking_WillMessageBeDropped(i32 size) -> i32Non-zero if queuedBytes + size > 65536 (MaxBytesQueued). Check before sending large Unreliable* payloads.
CVR_Networking_NetworkCloggedPercentage() -> f32Returns bytesSent / 16384f — a fraction of the 16 KB spike budget (MaxBytesSendSpike). Not clamped: it may exceed 1.0 if the sustained budget drain hasn’t caught up. Back off writes when persistently above ~0.7.
CVR_Networking_GetInstanceOwner() -> i64Wrapped handle for the current instance owner Player. Useful for authoritative-owner patterns.
CVR_Networking_GetPing() -> i32NetworkManager.Instance.GameNetworkPing in ms.
CVR_Networking_GetServerStartTime() -> i64Unix timestamp (ms) the game server started. CCK source only — not in the shipped decompile yet.
CVR_Networking_GetServerUptime() -> i64Game server uptime in ms. CCK source only.

Players are referenced by 16-bit (short) instance-local IDs. Enumerate them via CVR_Player_GetAllPlayers / CVR_Player_GetRemotePlayers (returning wrapped Player handles). Map each Player handle through the CCK WasmModule’s shim to get the short network ID for the playerIds array.

Passing an empty playerIds slice broadcasts to everyone in the instance except the sender. For “send to me too” patterns, invoke the handler directly on the local side in addition to sending.

CVR instances have a notional instance owner (the player who created the instance, or the one who inherited on disconnect). Common networking patterns:

  • Owner-only writes — each client checks CVR_Networking_GetInstanceOwner against its own player; only the owner sends authoritative updates. Subscribe to OnInstanceOwnerChange to handle handoff.
  • Leaderless — every client sends its own state. Cheap but racey; use UnreliableSequenced and include timestamps.
  • RPC over reliable — send discrete events reliably; each client integrates them into local state.

Concrete constants (CVR-GameFiles/WasmScripting/WasmNetworkManager.cs):

NameValueSemantics
MaxBytesPerSecond4096Sustained per-VM send rate, drained per second of wall clock.
MaxBytesSendSpike16384In-flight cap. The flush loop refuses to dispatch a message whose size would push the running total above this.
MaxBytesQueued65536Upper bound on bytes queued but not yet flushed. WillMessageBeDropped(size) compares queuedBytes + size to this.

Any outbound message larger than MaxBytesSendSpike (16 KB incl. its 22+playerIds.Length*4+"CVR.TN:1.0.0".Length*2 header) is dropped silently by WasmNetworkManager.SendMessage without touching the queue. Chunk payloads into pieces below that cap.

Pragmatic rules of thumb:

  1. Small messages (< ~200 bytes) — send freely.
  2. Medium messages (~200 bytes – 1 KB) — call WillMessageBeDropped(size) first when the queue may already be busy.
  3. Large payloads — stay well under 16 KB per call. Split larger payloads across frames and stitch them on the receiver with a tag byte + sequence number.

NetworkingBindings.cs does not call CheckAccess explicitly on any of these functions, so they are reachable from any ObjectContext (Avatar, Prop, World). In practice networking from an avatar script is still per-instance and won’t cross to other worlds or players outside the instance — CVR’s networking isolation is enforced at the transport layer.

World-permissioned external HTTP is entirely separate — see World Permissions.

  • CVR-GameFiles/WasmScripting/NetworkingBindings.csCVR_Networking_* host bindings.
  • CVR-GameFiles/WasmScripting/WasmNetworkManager.cs — per-VM Sender queue, budget constants, SendMessage, OnReceiveMessage, FlushMessages runs in the Initialization PlayerLoopSystem.
  • CVR-GameFiles/WasmScripting/SendType.cs — three-value enum.
  • World Permissions — HTTP allowed-domains flag.
  • EventsOnInstanceOwnerChange hook for authority handoff.
  • Limits — epoch deadline considerations when composing messages.