Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The two-phase tick

Verified against Minecraft 26.3 · Part VIII · One server tick of one player — which happens twice, from two different callers, and the second half throws its own answer away.

Every other entity on the server is ticked once, by the level it stands in. A player is ticked twice: once from the level’s entity loop, and once from its own connection, after every level in the game has finished. Keep the inheritance chain in view for the whole page, because the halves are told apart by how far up it they reach: a ServerPlayer is a Player, which is an Avatar, which is a LivingEntity, which is an Entity, and every one of them but Avatar declares its own tick. The first half never reaches Player.tick: what the level runs besides ServerPlayer.tick is Entity.commonTick, the bookkeeping it gives every entity first, and for a rider the chain of rideTick overrides that ends in the Entity.rideTick calling it — Player.rideTick among them, which dismounts a sneaking rider instead. The one block the two halves have in common is the container-validity check — and the second half is stranger still. The connection records where the player is, runs the entire physics pipeline, and then puts the player back where it found them. The server simulates your movement in full, every tick, and deletes the result: what it keeps is the velocity, and that is the number the anti-cheat allows for when it checks your reported motion.

The cast

classwhat it decidesthread
ServerLevelphase one: ticks the player in entity order, inside the level tickServer
ServerPlayerboth halves — ServerPlayer.tick and ServerPlayer.doTick overlap in one block onlyServer
ServerGamePacketListenerImplphase two: the record–simulate–snap-back bracketServer
PlayerPlayer.tick and Player.aiStep — on the server, reached from phase two only; on the client, from LocalPlayer.tick (and Player.tick from RemotePlayer.tick)both
Inventorythe thirty-six ordinary slots’ per-tick item hookboth
FoodDatahunger, regeneration and starvation, ticked after the physicsServer
AbstractContainerMenuthe open window, diffed against what the client was toldServer
LocalPlayerthe client’s own single tick, gated on the level having loadedRender

Phase one: what the world does to this player

ServerPlayer.tick is called by the level’s entity loop — through ServerLevel.tickNonPassenger when the player is walking, and through ServerLevel.tickPassenger and Entity.rideTick when mounted, each time after Entity.commonTick has counted down Entity.invulnerableTime, saved last tick’s position and advanced the tick count — and players are ticked there whether or not their chunk is entity-ticking (the level tick). It runs late in ServerLevel.tick: after the block ticks and the chunk source, before the block entities.

It does not call Player.tick. What it does instead is the outside world’s business with the player: ServerPlayerGameMode.tick for block-breaking progress and the delayed destroy (block breaking), the countdown of LivingEntity.damageCooldownTime, the hurt cooldown, AbstractContainerMenu.broadcastChanges on the open menu followed by closing it if it is no longer valid (containers and menus), snapping the player to the camera entity when one is set, the per-tick advancement criteria and a flush of the dirty ones, the warden spawn tracker, and ServerPlayer.updatePlayerAttributes. It is not quite connection-free: its very first statement is the connection’s client-load timeout (players and sessions).

Phase two: what this player would do if it simulated itself

ServerPlayer.doTick is called by ServerGamePacketListenerImpl.tickPlayer, from the connection tick, after every level has ticked. This is the half that calls up into Player.tick and LivingEntity.tick, so the player’s physics are simulated here. It then ticks FoodData.tick, the play-time statistics, ServerPlayer.synchronizeSpecialItemUpdates over all forty-three slots (player anatomy), and every has this changed since I last sent it comparison that produces ClientboundSetHealthPacket and ClientboundSetExperiencePacket (hunger and experience). This is also where the shared block turns up: ServerPlayer.doTick re-runs the same AbstractContainerMenu.stillValid check phase one ran, and closes the menu if it now fails — the only work both halves do.

The first group of those is gated, and the gate is a single condition: a spectator that is touching an unloaded chunk skips it. Behind that gate sit Player.tick, the container check, FoodData.tick and the play-time statistics; the forty-three-slot sweep and every last-sent comparison run regardless. So a spectator drifting out of the loaded world stops being simulated and keeps being reported on.

Player.aiStep, reached from inside that, is where the two item-tick calls happen and in which order: Inventory.tick over the thirty-six ordinary slots, before EntityEquipment.tick covers the other seven from LivingEntity.aiStep — why there are two of them is the forty-three slots (player anatomy). It is also the item and orb pickup sweep, gated on being alive and not a spectator; what the sweep does with an orb once it touches one is hunger and experience.

The division is clean enough to use as a rule while reading the rest of this part. If the thing you are looking for is the world acting on the player — the menu’s change broadcast, the breaking timer, the spectator camera, the advancement criteria — it is in phase one. If it is the player acting — physics, hunger, effects, item ticking, the health, food and experience packets — it is in phase two.

Both halves, in the order they run

One object, one tick, two callers and two bands — and the one call that appears in both of them is the container check.

sequenceDiagram
    participant SL as ServerLevel
    participant SP as ServerPlayer
    participant SPGM as ServerPlayer<br/>GameMode
    participant ACM as Abstract<br/>ContainerMenu
    participant SGPL as ServerGamePacket<br/>ListenerImpl
    participant FD as FoodData

    rect rgba(0, 0, 0, 0.04)
        Note over SL,FD: phase 1 — the entity loop, inside the level tick
        SL->>SP: commonTick, then tick — never up to Player.tick
        SP->>SPGM: tick — the breaking timer and the delayed destroy
        SP->>ACM: broadcastChanges, then stillValid
        SP->>SP: updatePlayerAttributes — the reach and waypoint modifiers
    end
    rect rgba(0, 0, 0, 0.04)
        Note over SL,FD: phase 2 — the connection tick, after every level
        SGPL->>SGPL: resetPosition — this position becomes firstGood
        SGPL->>SP: doTick, inside the bracket
        SP->>SP: Player.tick, then LivingEntity.tick — the physics
        SP->>ACM: stillValid — the only work both halves do
        SP->>FD: tick — hunger, regeneration, starvation
        SP->>SGPL: ClientboundSetHealthPacket, if a watched field differs
        SGPL->>SP: absSnapTo — back to firstGood, keeping the rotation
    end

The two bands are the two callers, and ServerPlayer is one lane in both because it is one object: the physics the page is about happen inside the second band and their position is undone by its last arrow, and AbstractContainerMenu is the only other lane both bands touch.

The bracket, and what survives it

ServerGamePacketListenerImpl.tickPlayer is a bracket around one call. ServerGamePacketListenerImpl.resetPosition records the player’s current position into the firstGood… and lastGood… fields — good meaning this is the last place the server was willing to agree you were — then ServerPlayer.doTick runs, and then the player is snapped back to the recorded position with Entity.absSnapTo, keeping only the rotation. What runs inside it is the whole of LivingEntity.aiStep, LivingEntity.travel and Entity.move. The rest of the method is the anti-cheat that rides along in the same bracket: the floating too long kick, and the same record-and-check done again for the vehicle the player is steering. On foot the authoritative position moves in ServerGamePacketListenerImpl.handleMovePlayer or in a teleport, never here. Riding is one exception, and it is phase one that makes it one: Entity.rideTick ticks the passenger and then has the vehicle reposition it, so a rider’s real position is written by the level’s loop before the bracket ever opens, and the snap-back at the end of the bracket puts it back there. A spectator looking through another entity is the other, for the same reason: phase one snaps them to it.

What survives the snap-back is Entity.getDeltaMovement — the velocity the anti-cheat allows for when it measures the client’s reported displacement (input to movement) — plus everything non-positional the tick did: drowning, burning, effects, hunger, the last-sent diffs. Both halves run every tick whether or not a packet arrived, and packets are drained before either of them. What phase two tells the client about its own player leaves at once: Connection.tick flushes the channel right after it has run the listener that called ServerPlayer.doTick (the server tick).

Not all of a player’s state is written inside the two halves: the handlers drained before the tick write it too, on the server thread — the position above is one — and the chat and command handlers write one field of it off that thread altogether. Almost every game handler defers to the owning thread before it touches anything (the server tick owns the rule and counts the exceptions); the chat handlers are among the ten that do not. ServerGamePacketListenerImpl.tryHandleChat reads ServerPlayer.getChatVisibility and calls ServerPlayer.resetLastActionTime on the Netty thread, before it hands the rest over (chat and signing) — one field on a ServerPlayer read and another written outside both halves and outside the server thread.

The pairing that makes the bracket necessary is Part VI’s authority: Player.isClientAuthoritative is an unconditional yes on both sides, which denies a ServerPlayer local-instance authority, while Player’s move-simulation type and its Entity.isEffectiveAi override make Entity.canSimulateMovement and Entity.isEffectiveAi true on the server anyway. So the pipeline runs and its answer is not believed.

The client ticks its player once

LocalPlayer.tick runs from ClientLevel’s entity tick on the Render thread, with its entire body gated on the connection reporting that the level has loaded. Minecraft.gameMode is ticked separately, and earlier in Minecraft.tick than the entity tick. ClientInput.tick is called from inside LocalPlayer.aiStep, so input is sampled inside the tick, not pushed from the key callback — though the method doing the sampling is KeyboardInput.tick; ClientInput.tick itself is empty.

Questions players ask

If the server ticks my player from the connection, does a silent client stop being ticked? No. ServerPlayer.doTick runs every tick regardless of traffic, and so does ServerPlayer.tick. Only two things hold phase two back, and neither is silence: MinecraftServer.isPaused — which only an integrated server ever reports true — short-circuits the whole bracket before ServerPlayer.doTick is called at all, and the spectator gate above skips its first half. What stops a silent client moving is neither: it is the snap-back, which undoes every position the simulation produced.

Why does fall damage come from the packet handler? Because the branch inside Entity.move is the one gate there that reads local-instance authority alone, which a ServerPlayer fails (authority). Entity.doCheckFallDamage on the movement-packet path does it instead, with the client’s own reported delta — which is the two-phase split showing up as a damage number.

Is a mounted player different? In phase one, where Entity.rideTick ticks the player and then has the vehicle place it; and in the bracket, which skips the player’s floating check and, for a player steering, runs the vehicle’s record-and-check instead. The movement packets a passenger sends are judged by an almost entirely separate piece of code too — see input to movement.

Where to look

Read the two halves side by side: ServerPlayer.tick and ServerPlayer.doTick are both short, and the second is the one with the spectator gate at the top of it. Then ServerGamePacketListenerImpl.tickPlayer for the bracket itself — the record, the call, the Entity.absSnapTo, and the two floating checks that ride along after it. Player.aiStep is where the item ticks and the pickup sweep happen, and LocalPlayer.tick is the client’s whole one-pass answer to all of it, worth reading immediately afterwards for the contrast. If you want the callers rather than the callees, ServerLevel.tickNonPassenger is phase one’s.


Rules: names, never code · how the system works, not how the code reads · newest version only · every backticked name passes tools/verify_names.py.