Quick answer: Only one peer should be the authority for each node, and all peers must agree on who that is. Set authority on the server and use @rpc("authority", "call_local", "reliable") for state-changing calls. Verify with is_multiplayer_authority() before writing replicated values.

Here is how to fix Godot multiplayer authority desync. You have a shooter prototype: server plus two clients. Everyone spawns a player. They see each other move. Then player A shoots player B, and on A’s screen B dies; on B’s screen A dies; on C’s screen both are still alive. The shots registered, but the damage applied differently on each peer. Authority in Godot 4 is per-node, and disagreement about authority is the root of most multiplayer desync.

The Symptom

Clients display different game states. Possible manifestations:

Logs on different peers show different current values. No RPC errors in the console.

What Causes This

Authority not explicitly set. New nodes default to authority = 1 (server). If your peer IDs are not what you expect, or if a client-spawned node assumes authority it should not have, writes happen on the wrong peer.

Multiple peers claim authority. If client A calls set_multiplayer_authority(A_id) on a shared node and client B does the same with B_id, both now try to drive the same node. Whoever sends last wins on each receiver — inconsistent state emerges.

MultiplayerSynchronizer replication paths mismatched. A Synchronizer replicates specific properties. If the property path is typoed or refers to a node that differs between peers (e.g. different scene instance structure), replication silently fails for that property.

RPC without call_local. @rpc("authority") without "call_local" only runs on remote peers. If the authority needs to execute the same logic locally, it skipped. Add "call_local" for write calls that affect all peers including the caller.

Scene spawned only on some peers. If you add_child a scene on one peer without MultiplayerSpawner or equivalent replication, other peers do not have that node. Calls referencing it fail silently.

The Fix

Step 1: Use MultiplayerSpawner for networked scenes. A MultiplayerSpawner node handles spawning scenes across all peers automatically:

extends Node # Game root

@onready var spawner = $MultiplayerSpawner

func _ready():
    spawner.spawn_path = ^"Players"
    spawner.spawn_limit = 16
    # Register player scene for replication
    spawner.add_spawnable_scene("res://player.tscn")

func spawn_player(peer_id: int):
    if not multiplayer.is_server():
        return
    var p = preload("res://player.tscn").instantiate()
    p.name = str(peer_id)
    p.set_multiplayer_authority(peer_id) # peer owns their player
    $Players.add_child(p, true)

Spawning only on the server with add_child propagates to all peers automatically via the spawner. Authority is set before replication so all peers receive the correct ownership info.

Step 2: Guard writes with is_multiplayer_authority. Always check authority before writing to replicated state:

func take_damage(amount: int):
    if not is_multiplayer_authority():
        return # only authority can change health
    health -= amount
    if health <= 0:
        die.rpc() # broadcast death to all

@rpc("authority", "call_local", "reliable")
func die():
    queue_free()

Non-authority peers early-return. The authority applies changes, then RPC-broadcasts events. All peers run the RPC (including the authority via call_local), so die() runs everywhere.

Step 3: Use MultiplayerSynchronizer for continuous state. Add a MultiplayerSynchronizer child to your player. Configure Replication Config with paths to properties you want synced:

# In editor: MultiplayerSynchronizer > Replication Config
# Add properties:
#   position (spawn + sync)
#   rotation (sync only)
#   health (sync only)

# At runtime, only the authority writes these values
func _physics_process(delta):
    if not is_multiplayer_authority():
        return

    # authority applies input and physics
    var input_vec = Input.get_vector(...)
    velocity = input_vec * speed
    move_and_slide()

The Synchronizer at network rate (default 30 Hz) pushes authority’s property values to all peers. Non-authority peers just display what they receive.

Step 4: Diagnose with debug prints. On every peer, log authority state:

func _ready():
    print("Peer ", multiplayer.get_unique_id(),
        " says: this node authority = ", get_multiplayer_authority())

Run with three peers (server + 2 clients). All three should report the same authority for the same node. If they disagree, authority was set inconsistently — usually because clients set it locally without waiting for server.

High-Latency Smoothing

For smooth motion despite network jitter, enable interpolation on MultiplayerSynchronizer via its replication config. Or implement client-side prediction: authority sets “target position,” non-authority lerps current position toward target over multiple frames.

“One authority per node. All peers agree. Set by the server, never by individual clients. Multiplayer desync dies.”

Related Issues

For signal handling in multiplayer, see Godot Await Signal. For general multiplayer testing, Testing Cross-Platform Multiplayer covers related debug techniques.

Server spawns, sets authority. is_multiplayer_authority() guards writes. Synchronizer for state, RPCs for events.