08 — Authority¶
Authority is who gets to decide. Lattice distinguishes two kinds:
- State authority — the peer that owns and writes the simulation. In
Server/Hostmodes this is the server; it's the only peer that maySpawn,WriteState, andMarkDirty. - Input authority — the peer whose input drives a particular object (its
Owner). A client can have input authority over its own avatar without having state authority over the world.
Verified against: bindings/lattice-unity/com.lattice.netcode/Runtime/NetworkObject.cs:56-72,
NetworkRunner.cs:98-99.
obj.HasStateAuthority // true if the local runner is server/host
obj.HasInputAuthority // true if obj.Owner == runner.LocalPlayer
obj.Owner // == obj.InputAuthority — the participant that owns it
runner.IsAuthority // true for Server / Host / SharedHost
Verified against: NetworkObject.cs:62-72, NetworkRunner.cs:98-99.
What LocalPlayer is, and when it is not known
HasInputAuthority is only as good as LocalPlayer. An authority is peer 0; a client is
told its id during the secure handshake and reports
LatticeNative.LocalPeerUnknown until then, so HasInputAuthority is simply never true on a
plaintext client. Check runner.IsLocalPlayerKnown before relying on it — see
Peer identity & local player.
Host-authoritative validation (the sample's model)¶
The most important authority pattern — and the one the sample verifies end to end — is server-authoritative validation: clients request changes; the authority validates and applies them; illegal requests change nothing.
In tic-tac-toe the host is the sole writer of the board. A client never writes the board directly —
it sends a Move event, and the host decides:
public void RequestMove(int cell)
{
if (_board == null) return;
if (IsHost)
TryApplyMove(_runner.LocalPlayer, LocalMark, cell); // host applies its own move directly
else
_runner.SendEvent(Net.EventMove, LatticeEventTarget.Server, // client asks the host
Payloads.Move(cell), reliable: true);
}
Verified against: TicTacToeSession.cs:106-113.
The host's authoritative handler validates against the pure rules, and only on success writes
the state, flips the turn, re-evaluates the result, and marks the changed fields dirty so they
replicate. An illegal move changes no state and is surfaced via OnMoveRejected:
private bool TryApplyMove(ulong fromPeer, Mark mover, int cell)
{
var state = _board.ReadState<BoardState>();
byte[] cells = state.ToCells();
if (!TicTacToeRules.IsLegalMove(cells, (Mark)state.turn, (GameResult)state.result, mover, cell))
{
OnMoveRejected?.Invoke(fromPeer, cell); // wrong turn / taken cell / game over
return false;
}
state.SetCell(cell, (byte)mover);
cells[cell] = (byte)mover;
GameResult result = TicTacToeRules.Evaluate(cells);
state.result = (byte)result;
state.turn = (byte)(TicTacToeRules.IsOver(result) ? Mark.Empty : TicTacToeRules.Other(mover));
_board.WriteState(state);
_board.MarkDirty(BoardField.ForCell(cell));
_board.MarkDirty(BoardField.Turn);
_board.MarkDirty(BoardField.Result);
OnBoardChanged?.Invoke(state); // host's own view updates immediately
return true;
}
Verified against: TicTacToeSession.cs:141-165; the rules at TicTacToeRules.cs:51-58,64-78.
Crucially, the mover's mark is the one the host assigned to that peer — not one the client
claims — using the sender id from the event (chapter 07):
private void HandleMoveEvent(ulong sender, byte[] payload)
{
if (_board == null) return;
AssignMarkIfNeeded(sender); // tolerate move-before-join
Mark mover = _peerMarks.TryGetValue(sender, out var m) ? m : Mark.Empty;
TryApplyMove(sender, mover, Payloads.ReadMoveCell(payload)); // authority decides
}
Verified against: TicTacToeSession.cs:218-224.
This is verified
The headless harness asserts the authority rejects moving out of turn, taking an occupied
cell, and moving after the game is over, leaving the board unchanged on both peers — while
legal moves replicate. This is the core guarantee of a server-authoritative game.
Verified against: samples/unity-tictactoe/headless/Program.cs:84-105,134-138;
samples/unity-tictactoe/README.md ("an illegal move is rejected by the authority").
The recipe¶
- Client sends a request (event or RPC to
Server). - Authority validates against its rules using the trusted
senderidentity. - On success:
WriteState+MarkDirty→ it replicates. On failure: change nothing, optionally notify. - Clients only ever read replicated state and render it.
This keeps a cheating client from writing illegal state: it can only ask, and the authority is the single writer.
Distributed / shared authority¶
For games where authority over an object should move between peers (e.g. whoever grabbed the ball owns it), Lattice has a shared / distributed authority model. The API:
runner.RequestAuthority(ulong netid); // ask the host arbiter for state authority over an object
runner.ObjectOwner(ulong netid); // who currently holds authority (0 if unknown)
runner.ObjectAuthorityTick(ulong netid);// the token's claim counter
// object-scoped equivalents:
obj.RequestAuthority();
obj.CurrentOwner;
obj.AuthorityTick;
Verified against: NetworkRunner.cs:354-365, NetworkObject.cs:149-164.
RequestAuthority is sent reliably to the host arbiter, which mints a new ownership token and
broadcasts the grant. Once observed, OnAuthorityChanged fires on every peer the tick it adopts
the new token, and the requester's writes win:
runner.OnAuthorityChanged += (netid, newOwner, authorityTick) =>
{
// newOwner now holds state authority over netid, as of authorityTick.
};
Verified against: NetworkRunner.cs:35-37 (event + doc), NetworkRunner.cs:160-161 (trampoline),
LatticeNative.cs:196-200.
This path is intended for SharedHost (LatticeGameMode.SharedHost) sessions.
Real ABI, but not exercised by the sample or the headless test
RequestAuthority / OnAuthorityChanged / ObjectOwner / ObjectAuthorityTick are genuine
wrappers over the core's lattice_request_authority / on_authority_changed /
lattice_object_owner / lattice_object_authority_tick. The tic-tac-toe sample is
single-authority (host-only), so it does not call them, and the binding's headless test
covers spawn/replicate/RPC/despawn but not an authority transfer. The API surface and its
documented semantics are accurate; end-to-end transfer isn't demonstrated in this tutorial's
verified sources. Design against it for shared-authority games, and test the transfer in your own
integration.
Honesty note: AssignInputAuthority is a stub¶
The Fusion-parity method NetworkObject.AssignInputAuthority(player) does not replicate. It
updates the local Owner view only — the core has no input-authority transfer ABI (only state
authority via RequestAuthority). Don't rely on it to move ownership across the network.
public void AssignInputAuthority(ulong player) => Owner = player; // local only; does not replicate
Verified against: NetworkObject.cs:86-91 (documented as a compile-stub).
Next: 09 — Full walkthrough, where all of this comes together into the complete game.