Server vs client authority
Which machine owns each piece of game state, why a verb on a player is a request, and how to confirm that it worked.
Each player’s own client owns their body: health, stamina, clothes, buffs and
where they stand. The server owns everything shared between players, and each
player’s inventory. Reading a player’s body gives you its latest snapshot,
and most body-changing verbs are requests to its client. The server also
rules on player combat and NPC melee against players before health is taken;
playerHit lets the game mode refuse or change those hits.
// serverEvents.on("playerCommand", (player, command) => { if (command !== "lift") return;
const above = player.position.clone(); above.z += 20;
const sent = player.teleport(above); // `sent` means the request went out. player.position still reads the old // spot here; the new one arrives with the player's next update. Chat.sendToPlayer(player, sent ? "Up you go." : "Your client could not be reached.");});Who owns what
Section titled “Who owns what”| Thing | Owner | What you can do |
|---|---|---|
| A player’s body: pose, health, stamina, stats, appearance, buffs | That player’s client | Read its last report; send it requests. |
| A player’s inventory | The server | Read and change it directly; their game only shows it. See Inventories. |
| A player’s skills, XP and perks | The server rules, their client applies | Every gain is asked for and granted as an order. See Progression. |
| A horse standing idle | The server | Spawn, move and change it directly. |
| A horse with a rider | The rider’s client | Handed over on horseMount, back on horseDismount. |
| An NPC | The server; a nearby client runs the body | Identity, orders and health are the server’s. Writes apply at once. |
| NPC inventory and harvest state | The server | Set stock with Inventory.set, or wait for the client to report the starting inventory. See Corpse loot. |
| Food and potion use | Server decision, native client execution | Approve the serving and effects; react to confirmed use. See Food and potions. |
| Props, markers, particle effects, ground items, stashes, quests | The server | Spawn, change and destroy directly. |
| World clock and weather | The server | Every client adopts the server’s values. |
| Entity state bags | The server | Only the server writes; clients read. See State bags. |
Read a player: it is a snapshot
Section titled “Read a player: it is a snapshot”Every player property is the value their client last published, not one the
server keeps. Until the first report arrives, fields hold placeholders:
health reads 0 and position is the world origin.
player.ready turns true
once the first pose and character state have arrived. Check it before a
position matters:
// serverfunction nearEnough(a: Player, b: Player, metres: number): boolean { if (!a.ready || !b.ready) return false; return a.position.distance(b.position) <= metres;}Act on a player: the return value means “sent”
Section titled “Act on a player: the return value means “sent””A verb on a player returns whether the request went out, not whether it worked. The effect appears a moment later, once their client has applied it and reported back. Their game can still refuse: a buff that conflicts with one already there, or a beard the face was never modelled with.
| Verb | Returns | How you learn it happened |
|---|---|---|
teleport, spawn |
true when sent |
player.position changes on a later update. |
setAppearance |
true when sent |
player.appearance reads back the new look. |
addBuff, removeBuff |
true when sent |
playerBuffAdded or playerBuffRemoved fires. hasBuff straight after addBuff is still false. |
heal |
true when sent |
player.health rises; playerInjuryHealed fires per limb. |
setStat, setHealth, setHunger |
true when sent to a ready, living body |
A later snapshot reports the applied value. |
mount |
true when requested |
horseMounting can refuse; horseMount confirms the seat. |
revive |
true when sent |
The player stands up and player.alive turns true. |
addXp, setLevel, addPerk |
true when sent |
playerXpGained, playerLevelUp or playerPerkAdded fires. |
Item verbs are the exception. giveItem, takeItem and the Inventory calls
change the inventory the server holds, so their result is final: the player’s
game shows it on the next tick.
Why use a method instead of assigning a player property?
A local property assignment does not instruct the player’s game to change.
Use setHealth or setStat for writable stats. player.position is inherited from
Entity and can be assigned, but
the owner’s next pose replaces it. Use teleport or spawn to move them.
Confirm an outcome with an event
Section titled “Confirm an outcome with an event”When you need to know, listen for the event, or read the value back later.
// serverEvents.on("playerCommand", (player, command, args) => { if (command !== "buff" || !args[0]) return; const info = Buffs.find(args[0]); if (!info) return; player.addBuff(info.name); // hasBuff is still false here});
Events.on("playerBuffAdded", (player, buff, source) => { if (source === "server") Chat.sendToPlayer(player, `${buff} is on.`);});Write what the server owns
Section titled “Write what the server owns”Writes on server-owned things apply at once. npc.teleport moves the body
whoever simulates it; a pose the simulating client already sent cannot put it
back. npc.setAppearance and assigning horse.name are writes too.
The client running an NPC’s body changes as players move (npcSimulatorChange),
and the NPC does not: the new simulator picks up from the server’s copy. See
Spawn NPCs.
What the server validates for you
Section titled “What the server validates for you”| Event | Checked how |
|---|---|
markerEnter |
Client detects it; server confirms against the position it replicates. |
npcInteract |
Server confirms the distance. |
npcDamage |
Attacker’s client resolves the hit; server agrees, so amount is what was taken. |
groundItemPickup |
Reports a pickup the server already granted. |
doorInteract |
Server has already refused what the game’s rules forbid (out of reach, locked by the server). |
Some events are just reported, because the client decides: questTrackingChanged,
playerDamage, and buffs the game applies itself (playerBuffAdded with
source "native"). To overrule native buffs, Buffs.claim turns them into
playerBuffBlocked events for you to decide on.
Decide on the server and let the client do what it is told. A client resource is for input, UI and effects on one machine; never enforce a rule there, because the player controls it.
Related
Section titled “Related”- Teleport, kick and other player actions: the verbs in the table above
- Read health, stats and skills: what a snapshot holds
- Spawn and manage horses: authority changing hands on mount
- Send data between server and client: your own events, and validating them