Loading...
Loading...
Use when a Godot 4 game goes online or co-op: host and join with ENet, WebSocket for a web build, RPCs (@rpc, rpc_id, any_peer), MultiplayerSpawner and MultiplayerSynchronizer, player authority, lobbies and login, a dedicated or headless server export, lag, rubber-banding, prediction or netfox, cheating, bandwidth, Steam, Nakama or WebRTC. Symptoms: players do not appear on the client, 'rpc node checksum failed', 'node not found' on an RPC, desync, the server build is huge, a client never connects.
npx skill4agent add scenario-labs/skills scenario-godot-multiplayernpx skills add scenario-labs/skills --skill <name>gd_runtests/live_evidence/godot-multiplayer/_enter_tree_enter_treeset_multiplayer_authority(name.to_int())_readypeer_id_enter_treeany_peermultiplayer.get_remote_sender_id()await@rpcbacall_localgd_net.UdpLagProxy--max-fpsEngine.max_fpsreplication_intervalpeer_connectedconnected_to_serverpeer_connectedget_unique_id()dedicated_server--headlesswss://@rpcgd_net.install_netkit(P)net_build.gdmake_syncmake_spawnerSceneReplicationConfigreferences/gui-paths.mdadd_child(node, true)_enter_treenet_audit.gd:scene@Node@2gd_net.session(P, roles)auth_callbackcomplete_authUdpLagProxyreplication_delay()bandwidthtransport="websocket"wss://_processgd_net.server_preset()pack_report--headlessapplication/run/flush_stdout_on_print=truededicated_serverheadlesscapture_atimport sys; sys.path[:0] = ["<skills>/scenario-godot-expert/scripts", "<skills>/scenario-godot-multiplayer/scripts"]
import gd_net
gd_net.install_netkit(P); gd_net.build_scenes(P) # res://net/world.tscn, player.tscn, props
roles = [gd_net.role("server", role="server", port=24010, lobby_size=2),
gd_net.role("c1", role="client", port=24011, index=0, delay_s=0.3, cheat_at=2.0),
gd_net.role("c2", role="client", port=24011, index=1, delay_s=0.3)]
with gd_net.UdpLagProxy(24011, 24010, rtt_ms=100, jitter_ms=0, loss=0.0):
s = gd_net.session(P, roles) # one APFS clone and one Godot per role
s["roles"]["c1"]["result"]["rtt_median_ms"] # every NetLab number is in AGENT_RESULTrole(name, script=...)--AGENT_RESULT_rollback_tickNetworkTime.physics_factormove_and_slideTickInterpolator| Value | Measured here (2026-10-02, M-series Mac, localhost) | Relative to |
|---|---|---|
| RPC round trip, no proxy | 16 to 19 ms | 60 fps both ends; one frame each way dominates |
| ENet RTT statistic | 15 to 23 ms | same |
| Own input to visible motion, server authority | 47 to 52 ms | about 3 frames |
| Remote view behind server truth | 20 to 30 ms, 0.07 m error at 3 m/s | no proxy |
| Same at 100 / 250 ms RTT | 70 to 80 / 150 to 155 ms; 0.22 / 0.44 m | |
| 50 props, ALWAYS, 60 fps | 72.6 KiB/s, moving or idle | 1 client |
| ON_CHANGE moving / idle | 98 to 101 / 2.3 KiB/s | same |
ALWAYS, | 21 to 22 KiB/s | same |
| ALWAYS, uncapped headless server (145 fps) | 160 to 175 KiB/s | same |
| 3.0 s; 1.50 s after setting 1.5 | server |
| Web export, single-thread | 40 MB, wasm 39.5 MB (10.1 MB gzip) | handoff to scenario-godot-performance-export |
| Dedicated server pack / client pack | 0.11 / 3.27 MB (3.1 MB texture stripped) | same project |
| 0 = unlimited (default); limit 2: third | M14; raise it to max players (jgJuX04cq7k [00:18:34]) |
net_auditokplayers_seen_maxreplication_delayrpc_auditrpc_pair_check| Mistake | What it looks like | Fix |
|---|---|---|
Players spawned only on the server without a spawner, or spawner | client sees nobody | |
Authority read from a synced property in | authority 1 everywhere on clients | authority from |
Runtime node without | | readable names |
| sender 0, validation passes or fails wrongly | read the sender first |
| RPC added on the server script only | checksum error, the wrong method runs | one script, or |
| Object or Resource as RPC argument | no error; receiver gets | send ids, paths or plain data |
| Client owns its body in a competitive game | speed and teleport hacks stick | input-only authority plus server simulation |
| Headless server not capped | 2.4 times the bandwidth | |
| "Attempt to call RPC with unknown peer ID" | keep relay on, or route through the server |
| Freeing a networked branch in the wrong order | "isn't currently active", "Node not found" | offline peer, free contents, drop custom API, free branch |
| Server export in normal mode | full-size pack, window opens | dedicated server export mode, Strip Visuals |
Server start inside | works from the editor; the release export never listens | |
Client info sent from | duplicate nodes on late joiners, a phantom player | |
| Client and server on different Godot versions | connection or protocol failures | same 4.7.2 build (protocol changed in 4.3) |
get_rpc_configget_node_rpc_config@rpcScript.get_rpc_config()auth_timeoutauth_timoutNetworkedMultiplayerENetremotersetget_rpc_sender_idgd_net.rpc_auditinitialize()NetUtil.enet_throttle()ready_state != STATE_OPENreferences/procedures.mdreferences/expert-notes.mdreferences/critique.mdreferences/gui-paths.mdreferences/sources.mdscripts/gd_net.pyscripts/agentkit/multiplayer/