Authority
Authority
Section titled “Authority”When a player sends a command that targets an entity—move their character, pick up an item, fire a weapon—the server needs to verify that the sender actually controls that entity and isn’t manipulating someone else’s. Authority is how golem-engine enforces that boundary.
Assigning ownership
Section titled “Assigning ownership”Ownership is tied to a session. When you create a player’s entity on connect, pass the session ID as the second argument to CreateEntity:
srv.OnConnect(func(sess *golem.Session) { player := synced.NewSyncedPlayer(nextID(), 0, 0, "Aria") _ = srv.CreateEntity(player, sess.ID) // player is now owned by sess.ID})OnConnect runs on the tick goroutine, so CreateEntity is safe to call directly here.
Without the second argument, the entity is unowned—world NPCs, pickups, and environmental objects typically work this way. Unowned entities cannot be targeted by entity-scoped commands.
How CommandRouter uses it
Section titled “How CommandRouter uses it”When a client sends an entity-targeted command, CommandRouter.Dispatch checks that senderID matches the owning session for the target entity. If it does, the handler fires. If not, Dispatch returns an error and the handler is skipped entirely—no game code runs.
This means you don’t need to write ownership checks inside handlers. Since OnMessage (and therefore Dispatch) runs on the tick goroutine, entity mutations inside command handlers are also safe without additional locking:
router.OnMove(func(senderID int64, ent *synced.SyncedPlayer, cmd *synced.MoveCommand) { // only reached when senderID owns ent — safe to apply ent.SetPosition(ent.PositionX()+cmd.Dx, ent.PositionY()+cmd.Dy)})Transferring ownership
Section titled “Transferring ownership”Sometimes a player reconnects with a new session (their WebSocket was dropped and re-established). Use srv.SetOwner to move the entity to the new session without recreating it:
srv.OnConnect(func(sess *golem.Session) { if existingEntityID, ok := lookupPlayerEntity(sess.PlayerToken); ok { // Reconnect: transfer the existing entity to the new session srv.SetOwner(existingEntityID, sess.ID) } else { // First connect: create a new entity player := synced.NewSyncedPlayer(nextID(), 0, 0, "Aria") _ = srv.CreateEntity(player, sess.ID) }})SetOwner returns false if the entity ID doesn’t exist; ownership is unchanged in that case.
Querying ownership
Section titled “Querying ownership”Use srv.Owner(entityID) to find out which session currently owns an entity:
if sessionID, ok := srv.Owner(player.EntityID()); ok { // sessionID is the current owner}This is occasionally useful for server-side logic that needs to know which session is responsible for a given entity—for example, to send a targeted message to a specific client with srv.Send.
Cleanup on disconnect
Section titled “Cleanup on disconnect”Remove the entity (or transfer it) when the owning session disconnects. OnDisconnect runs on the tick goroutine, so DeleteEntity is safe to call directly:
srv.OnDisconnect(func(sess *golem.Session) { srv.DeleteEntity(playerEntityFor(sess.ID))})Once deleted, any command targeting that entity ID will fail the ownership check even if the same session reconnects—because the entity no longer exists.
See Client commands for the full command dispatch flow and Command schemas for how target: entity is declared in YAML.