<!-- https://minecraftdocs.dev/systems/server/server-level-tick — this page as markdown. The book's index: https://minecraftdocs.dev/llms.txt; the whole book in one file: https://minecraftdocs.dev/llms-full.txt -->

# The level tick

> Verified against **Minecraft 26.3** · Part III · A player stands still for one twentieth of a second while a whole dimension advances — weather, scheduled ticks, spawns, every entity, every block entity.

Nobody moves. A wheat crop is one random tick short of ripe, a creeper is
walking a fence line, rain has been falling for four minutes and a piston
somewhere is halfway through extending. Then `ServerLevel.tick` runs, once,
for this dimension, and all of it advances: the border interpolates, the
weather counts down, the scheduled block ticks fire, mobs are counted and
spawned, chunks near players get their random ticks, every entity in range
runs its `Entity.tick`, block entities run their tickers, and the block
changes that all of that produced are handed to the clients. It is one
method on the Server thread, called once per dimension from
`MinecraftServer.tickChildren` ([the server
tick](server-tick#what-minecraftservertickchildren-runs-and-in-what-order)),
overworld first. The order inside it is the whole lecture, because one of the steps is
in a place nobody expects: **the block-change broadcast runs before the
entities tick**. `ServerChunkCache.broadcastChangedChunks` comes before
`ChunkMap.tick` and long before `EntityTickList.forEach`, so a block a
command changed between two ticks reaches your screen in the tick that
follows, and a block a piston changed during that tick reaches it a tick
later.

## Three ranges, before we need them

Three phrases run through everything below and they are all one number line,
which counts *outwards*: a low level is a chunk somebody is standing in and a
high one is a chunk at the edge of what the server bothers with, up to
`ChunkLevel.MAX_LEVEL`. A chunk is *loaded* when it has a `ChunkHolder` at
all; it is
**block-ticking** at level 32 or below (`ChunkLevel.BLOCK_TICKING_LEVEL`) and
**entity-ticking** at 31 or below (`ChunkLevel.ENTITY_TICKING_LEVEL`), and
those last two answers come from the simulation graph, through
`DistanceManager.inBlockTickingRange` and
`DistanceManager.inEntityTickingRange`. How a chunk gets its level — which
tickets put it there and which graph they feed — is Part IV's
[tickets and loading](../world/tickets-and-loading#the-number-line); for this page it is
enough that block-ticking reaches one chunk further out than entity-ticking,
and that both are brought up to date inside this tick, before anything but the
scheduled ticks and the raids reads them.

## The cast

| class | what it decides | thread |
|---|---|---|
| `ServerLevel` | the order of the tick, and the gates on its own steps — one instance per dimension | Server |
| `ServerChunkCache` | the chunk half of the tick: ticket purging, distance updates, spawning, random ticks, the broadcast | Server |
| `ChunkMap` | which chunks are candidates for spawning, which are entity-ticking, and every player's view of every entity | Server |
| `ChunkHolder` | one chunk's pending block and light changes, and which packet shape they become | Server |
| `LevelTicks` | the two scheduled-tick queues, one `LevelChunkTicks` per chunk, drained by priority | Server |
| `EntityTickList` | which entities are ticked, and a stable view of that set while it is being walked | Server |
| `PersistentEntitySectionManager` | which entities exist and which of them are ticking — the tick list's only editor | Server; its inbox is filled by IO threads |
| `TickRateManager` | whether this is a normal tick at all, through `TickRateManager.runsNormally` | Server |

`ServerLevel` is one of the two subclasses of the abstract `Level`, and the
split shows in this page's own names. `Level` holds what a world has on
either side — the block-entity ticker list, the `CollectingNeighborUpdater`,
the two weather floats, `Level.random` and the level data — and leaves the
questions only an authority can answer abstract, `Level.sendBlockUpdated` and
`Level.tickRateManager` among them. `ServerLevel` answers those and adds the
authority itself: the chunk source, the two scheduled-tick queues, the entity
manager, the list of players. So where a step below is spelled with
`Level` rather than `ServerLevel` the client inherits it too, and [the client
level](../client/the-client-level#where-the-two-levels-differ) is the list
of the ones it has hollowed out.

## The whole tick, and its three gates

The tick is one method calling its steps one after another, so its shape is
not a conversation — it is a column with guards down the side. There are three
guards, and every step is behind one of them, a combination of them, or
nothing: *running* (`TickRateManager.runsNormally`, false while
`/tick freeze` holds and no step is pending), *not a debug world* (`Level.isDebug`,
true only in the *Debug Mode* world type), and *the dimension is not empty* (`ServerLevel.emptyTime`
below `ServerLevel.EMPTY_TIME_NO_TICK`, 300).

```mermaid
flowchart TD
    START["MinecraftServer.tickChildren calls ServerLevel.tick, overworld first"]
    ENV["ServerLevel.handlingTick goes true, the attribute cache dropped"]
    WB["WorldBorder.tick, then ServerLevel.advanceWeatherCycle"]
    SLEEP["the sleep check: the clock, the players, the weather"]
    SKY["Level.updateSkyBrightness"]
    TIME["ServerLevel.tickTime, overworld only"]
    SCHED["ServerLevel.blockTicks, then ServerLevel.fluidTicks"]
    RAID["Raids.tick"]
    subgraph SCC["ServerChunkCache.tick"]
        direction TB
        PURGE["TicketStorage.purgeStaleTickets"]
        DIST["ServerChunkCache.runDistanceManagerUpdates"]
        CHUNKS["spawns, random ticks, thunder, custom spawners"]
        CAST["ServerChunkCache.broadcastChangedChunks"]
        TRACK["ChunkMap.tick: entity tracking"]
        UNLOAD["ChunkMap.tick with the budget: POI saves and unloads"]
        PURGE --> DIST --> CHUNKS --> CAST --> TRACK --> UNLOAD
    end
    EVENTS["ServerLevel.runBlockEvents, then ServerLevel.handlingTick false"]
    EMPTY["ServerLevel.emptyTime: reset by a ticket, otherwise rising"]
    DRAGON["EnderDragonFight.tick, the End only"]
    ENT["EntityTickList.forEach: each entity, then its riders"]
    BE["Level.tickBlockEntities"]
    EM["PersistentEntitySectionManager.tick: the inbox, then the unload set"]
    DBG["LevelDebugSynchronizers.tick"]
    GC["RandomState.garbageCollect"]
    START --> ENV --> WB --> SLEEP --> SKY --> TIME --> SCHED --> RAID --> PURGE
    UNLOAD --> EVENTS --> EMPTY --> DRAGON --> ENT --> BE --> EM --> DBG --> GC
```

*The order, and the one containment in it: the six steps in the box are not
siblings of the steps around them, they are the insides of a single call, and
a later section counts them as five things.*

The gates are the other half of the tick and they do not fit in the boxes. Read
this table down the middle instead: a step with three blank cells runs on a
frozen, empty, debug world.

| the step | *running* | *not a debug world* | *the dimension is not empty* |
|---|:-:|:-:|:-:|
| `ServerLevel.handlingTick` true, the attribute cache dropped | | | |
| `WorldBorder.tick`, `ServerLevel.advanceWeatherCycle` | • | | |
| the sleep check | | | |
| `Level.updateSkyBrightness` | | | |
| `ServerLevel.tickTime` | • | | |
| `ServerLevel.blockTicks`, `ServerLevel.fluidTicks` | • | • | |
| `Raids.tick` | • | | |
| `TicketStorage.purgeStaleTickets` | • | | |
| `ServerChunkCache.runDistanceManagerUpdates` | | | |
| spawns, random ticks, thunder, custom spawners | • | • | |
| `ServerChunkCache.broadcastChangedChunks` | | • | |
| `ChunkMap.tick`, entity tracking | | | |
| `ChunkMap.tick` with the budget | | | |
| `ServerLevel.runBlockEvents` | • | | |
| `ServerLevel.handlingTick` false | | | |
| `ServerLevel.emptyTime` rises | • | | |
| `EnderDragonFight.tick` | • | | • |
| `EntityTickList.forEach` | | | • |
| `Level.tickBlockEntities` | per ticker | | • |
| `PersistentEntitySectionManager.tick` | | | |
| `LevelDebugSynchronizers.tick` | | | |
| `RandomState.garbageCollect` | | | |

All but the last of those steps have names, and they are the names a
profiler reports: *world
border* and *weather*, *tickPending* (holding *blockTicks* and *fluidTicks*),
*raid*, *chunkSource*, *blockEvents*, *entities* (holding *dragonFight*,
*checkDespawn* and the per-entity *tick*), *blockEntities*,
*entityManagement* and *debugSynchronizers*. Half a dozen pages elsewhere in
the book place a mechanism by saying it runs *after tickPending* or *in the
chunk source phase*, and they mean these; so does a `/debug` profiling
report.

Read the gate columns and most of the page's surprises fall out of the table.
Sleeping through the night works with the game frozen. A frozen world still
loads, sends and unloads chunks — and stops expiring its tickets. A debug
world keeps its entities and drops its block updates. And the last three
steps run on a dimension with nobody in it.

## What the tick does before anything can move

### The cache that is dropped before the border

The first thing the tick does to the world, before the border and before
the weather, is `EnvironmentAttributeSystem.invalidateTickCache`: last tick's
resolved environment attributes are thrown away. That system
([environment attributes](../world/environment-attributes-and-timelines#the-stack-a-value-falls-through))
is where the old per-dimension and per-biome constants went, and
`Level.updateSkyBrightness` later in this same tick reads
`EnvironmentAttributes.SKY_LIGHT_LEVEL` out of it rather than deriving sky
light from the time of day. `ServerClockManager` invalidates the same cache
on every level whenever a clock is set, moved, paused or given a new rate, so
the level is not its only owner —
it is the first reader of the tick, and it starts clean.

### The weather is the server's; only the fade is the level's

Then `WorldBorder.tick` advances the interpolated extent, and
`ServerLevel.advanceWeatherCycle` counts the clear, rain and thunder timers
down under `GameRules.ADVANCE_WEATHER`, resampling the rain and thunder
timers from `ServerLevel.RAIN_DELAY`, `ServerLevel.RAIN_DURATION`,
`ServerLevel.THUNDER_DELAY` and `ServerLevel.THUNDER_DURATION` as each
expires, and fading `Level.rainLevel` and `Level.thunderLevel` by 0.01 a
tick, which is why a downpour arrives as a five-second ramp. The countdowns
belong to the *server*: `ServerLevel.getWeatherData` delegates to
`MinecraftServer.getWeatherData`, one `WeatherData` shared by every
dimension, and the only per-level parts are those two floats and the
`Level.canHaveWeather` test that decides whether this dimension acts on any
of it. Every move of a float is a `ClientboundGameEventPacket`
(`ClientboundGameEventPacket.RAIN_LEVEL_CHANGE`,
`ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE`) to this dimension's
players, and a start or a stop goes, with both levels, to every player in
every dimension.

## A freeze stops the clock and not the sleep check

`SleepStatus.areEnoughSleeping` and `SleepStatus.areEnoughDeepSleeping`
(against `GameRules.PLAYERS_SLEEPING_PERCENTAGE`) decide the night skip, and
they are checked outside every gate. If the dimension type has a default
clock and `GameRules.ADVANCE_TIME` is on,
`ServerClockManager.moveToTimeMarker` jumps that clock to
`ClockTimeMarkers.WAKE_UP_FROM_SLEEP`; `ServerLevel.wakeUpAllPlayers` gets
everyone out of bed, and, if it is raining and `GameRules.ADVANCE_WEATHER` is
on, `ServerLevel.resetWeatherCycle` clears the storm.
The `ClientboundSetTimePacket` that follows is sent by the clock manager,
not by the level — day time is not the level's state at all.

What the level does own is *gameTime*, and only in the overworld:
`ServerLevel.tickTime` advances it when the level was built with its
`ServerLevel.tickTime` flag set, which `MinecraftServer` does for the
overworld alone, and every other dimension reads that number. The same call
advances the server-wide `TimerQueue` behind `/schedule`
(`MinecraftServer.getScheduledEvents`), so a scheduled function is timed off
overworld *gameTime* and stands still while the world is frozen.

## Scheduled ticks, twice, and a promise to the same block

`LevelTicks.tick` is called twice — `ServerLevel.blockTicks`, then
`ServerLevel.fluidTicks` — each with the current *gameTime* and a budget of
`ServerLevel.MAX_SCHEDULED_TICKS_PER_TICK`, 65536, and both skipped entirely
in a debug world. Each call collects every `LevelChunkTicks` container whose
head is due and whose chunk passes
`ServerLevel.isPositionTickingWithEntitiesLoaded`, drains what the budget
allows, and hands each drained tick to `ServerLevel.tickBlock` or
`ServerLevel.tickFluid` — both of which check that what is at the position is
*still* what the tick named before running it. The order the drain uses, the
dedup rule that decides what was ever queued, and why that re-check is the
whole of cancellation belong to [scheduled
ticks](../world/scheduled-ticks#what-one-drain-actually-does); what
belongs to this page is that the two calls happen here, in this order, under
this budget.

## The chunk source does five things in one call

`ServerChunkCache.tick` receives the server's `MinecraftServer.haveTime`
supplier (or, while sprinting, a constant *false* in its place) — the level never looks at it, it only passes it on — and does five
things in order.

1. **Purge stale tickets** — while running only, so a frozen world holds on
   to expired portal and pearl tickets indefinitely.
2. **Run `ServerChunkCache.runDistanceManagerUpdates`** — ungated, and the
   tick's own run of the updates that change a chunk's ticking state. The reason an entity starts or stops
   ticking this tick is decided here, several steps before the entity loop
   reads it.
3. **Tick the chunks, then broadcast** — one call holding two halves, both
   skipped in a debug world: the spawning and random-ticking work below,
   and then the block changes turned into packets. Each half has a section
   of its own.
4. **Update entity tracking** — `ChunkMap.tick`, ungated, where last tick's
   movement becomes packets.
5. **Spend whatever time is left** on POI saving and chunk unloads.

That last step is the only part of the whole level tick that yields to the
clock, and even it does not yield completely: `ChunkMap.processUnloads`
force-drains anything over two thousand queued unloads whatever the budget
says.

### Two chunk sets, and two different mob caps

`NaturalSpawner.createState` walks `ServerLevel.getAllEntities` — every
entity in the dimension except the ones in `MobCategory.MISC` (items,
projectiles and armour stands, but villagers, golems and minecarts too), and
except mobs that require persistence — and
counts the rest per `MobCategory`, using the chunk each one stands in to charge a
spawn-potential field and to feed a `LocalMobCapCalculator`. Walking every
entity in the dimension is what this step costs, once a tick, before any
spawn is attempted; the two caps that census feeds — a server-wide one
scaled by the spawnable-chunk count and a per-player one that is not — belong to
[entity lifecycle](../entities/entity-lifecycle#the-two-caps-and-where-289-comes-from).
The categories marked persistent — the animals — are
considered only on a tick where *gameTime* divides by 400, and
`GameRules.SPAWN_MOBS` stops the spawns themselves, not the census before them
or the thunder beside them.

Two chunk sets are then walked, and they are not the same set.
`ChunkMap.collectSpawningChunks` gathers the **spawning chunks**: the chunks within eight of a player (the radius
`DistanceManager.naturalSpawnChunkCounter` counts) that have a ticking chunk and at least one
non-spectating player within 128 blocks
(`ChunkMap.playerIsCloseEnoughForSpawning`). They are shuffled, and each
then gets `ChunkAccess.incrementInhabitedTime`, then `ServerLevel.tickThunder`
*if it is also entity-ticking*, and then `NaturalSpawner.spawnForChunk` if
`ServerLevel.canSpawnEntitiesInChunk`. The thunder is a 1-in-100000 roll per
chunk per tick while raining and thundering, whose bolt prefers a lightning
rod, then a mob that can see the sky, then the heightmap, and which brings a
trap skeleton horse along at effective difficulty × 1 % when
`GameRules.SPAWN_MOBS` is on and no lightning rod is under the strike.

The second set is `ChunkMap.forEachBlockTickingChunk`, which despite its
name walks `DistanceManager.forEachEntityTickingChunk` — the entity-ticking
set, level 31 and below — and keeps only those whose `ChunkHolder` has a live
`ChunkHolder.getTickingChunk`. Each survivor gets `ServerLevel.tickChunk`.

### Random ticks are counted per section, and empty sections are free

`ServerChunkCache.tickChunks` reads `GameRules.RANDOM_TICK_SPEED` (default 3,
minimum 0) **once per level tick** and hands it down, and
`ServerLevel.tickChunk` does two things with it. It rolls that many 1-in-48
chances at `ServerLevel.tickPrecipitation` — the ice and snow layer, using
`Biome.shouldFreeze`, `Biome.shouldSnow` and
`GameRules.MAX_SNOW_ACCUMULATION_HEIGHT` — and then, for every
`LevelChunkSection` in the chunk that reports
`LevelChunkSection.isRandomlyTicking`, picks that many random positions and
rolls both `BlockBehaviour.BlockStateBase.randomTick` and the fluid's at
each. `LevelChunkSection.isRandomlyTicking` is a counter maintained on every
block change, so a section of solid stone costs nothing at all: the loop
over sections is a loop over the chunk's *interesting* height. Set the rule
to zero and both halves stop, ice and snow included.

The position itself does not come from `Level.random`.
`Level.getBlockRandomPos` advances `Level.randValue`, a plain linear
congruential generator, and unpacks x, y and z out of one integer.
Everything the block then *does* with that position — the crop-growth roll,
fire spreading, every implementation of
`BlockBehaviour.BlockStateBase.randomTick` — takes `Level.random`, which is
also what the ice and snow rolls, the lightning roll and the spawning-chunk
shuffle use.

The custom spawners come last inside this step.
`ServerLevel.tickCustomSpawners` runs the five a level was constructed with,
which in practice means the overworld's ([the other ways
in](../entities/entity-lifecycle#the-other-ways-in) names them). Three of
the five carry a game rule of their own (`GameRules.SPAWN_PHANTOMS`,
`GameRules.SPAWN_PATROLS`, `GameRules.SPAWN_WANDERING_TRADERS`); the other two carry none, and `GameRules.SPAWN_MOBS` gates the whole call — though `VillageSiege`, like the phantom and patrol spawners, also stops when `GameRules.SPAWN_MONSTERS` is off.

### The broadcast, which is why entities are a tick behind

Almost nothing in the game sends a block update at the moment a block
changes — a landing `FallingBlockEntity` is the exception worth knowing, and
it is dealt with at the end of this section.
`ServerLevel.sendBlockUpdated` calls `ServerChunkCache.blockChanged`, which
finds the `ChunkHolder`, records the position in that holder's per-section
set, and — the first time a holder gains a changed section — adds it to
`ServerChunkCache.chunkHoldersToBroadcast`. Then `ServerChunkCache.blockChanged` returns. The packets are
built once, later, in the chunk source's step.

```mermaid
sequenceDiagram
    participant SL as ServerLevel
    participant SCC as ServerChunkCache
    participant CH as ChunkHolder
    participant Wire as the network

    rect rgba(0, 0, 0, 0.04)
        Note over SL,Wire: before the tick — a command changed a hundred blocks
        SL->>SCC: blockChanged, once per block
        SCC->>CH: blockChanged, one entry in that section's set
        CH-->>SCC: nothing leaves, the holder is only marked
    end
    rect rgba(0, 0, 0, 0.04)
        Note over SL,Wire: this tick, inside ServerChunkCache.tick
        SCC->>CH: broadcastChanges, on every marked holder
        CH->>Wire: ClientboundLightUpdatePacket, if either light filter has anything
        CH->>Wire: one ClientboundBlockUpdatePacket per single-change section
        CH->>Wire: one ClientboundSectionBlocks<br/>UpdatePacket per busier section
        CH->>Wire: BlockEntity.getUpdatePacket beside any changed block entity
        Note over SL,Wire: ChunkMap.tick then turns last tick's movement into packets
    end
    rect rgba(0, 0, 0, 0.04)
        Note over SL,Wire: still this tick, several steps later
        SL->>SL: tickBlockEntities: a piston head finishes its push
        SL->>SCC: blockChanged, marked, and it waits for the next tick
    end
```

*Three bands: the write before the tick, the send inside it, and a write that
arrives too late for the send. The dotted arrow in the first band is the hook — a block
change leaves nothing on the wire at the moment it happens.*

`ChunkHolder.broadcastChanges` asks two different questions of two
different audiences, and it asks the light one first: if either light filter
has anything in it, `ChunkHolder.PlayerProvider.getPlayers` is called with
the border-only flag set and one `ClientboundLightUpdatePacket` goes to the
players on the edge of their tracked area, before a single block packet is
built. The blocks are then emitted **per 16³ section**, to everyone tracking
the chunk, and the shape depends on how many blocks in that section changed: one
`ClientboundBlockUpdatePacket` for a single change, one
`ClientboundSectionBlocksUpdatePacket` for several, plus
`BlockEntity.getUpdatePacket` for any changed position that carries a block
entity. A hundred blocks changed by one command are therefore a handful of
packets, one per affected section — not a hundred.

Then `ChunkMap.tick` runs — chunk tracking for each player, and
`ChunkMap.TrackedEntity` for each entity, which is where
`ServerEntity.sendChanges` turns last tick's movement into packets. Blocks
first, entities second, and the entity loop only after both. The ordering is
visible from a client: a player's `/setblock` lands in the tick right after it
runs, because a player's command runs as a task between two ticks, before
`MinecraftServer.tickChildren` starts, while a piston head, moved inside a tick
after that tick's broadcast, lands in the tick after the one that moved it. A command typed at the *console* is as late
as the piston, because it too changes blocks after the broadcast: it is
drained in the connection phase,
which `MinecraftServer.tickChildren` runs after every level has already
broadcast.

Falling sand is the exception that proves the rule, and it is deliberate.
`FallingBlockEntity` calls `LevelWriter.setBlockAndUpdate` and then, straight
after, `ChunkMap.sendToTrackingPlayers` twice: a
`ClientboundBlockUpdatePacket` of its own, and a
`ClientboundAddTransientBlockPacket` the client hands its renderer as a
`TransientBlock` — so the block the client sees appear is sent in the same tick the entity
that placed it vanished, rather than a tick behind it. A handful of other
places do the same thing to one player rather than to everyone tracking the
chunk. What the client does with all of this is
[what the client is told](../networking/what-the-client-is-told).

## Block events close the handlingTick window

`ServerLevel.runBlockEvents` drains `ServerLevel.blockEvents` — the
note-block plays, piston pushes and chest-lid counts queued since the last
drain, and any the drain itself raises — running every event inside the
block-ticking range and putting the rest back for the next one, and this is the only point in the tick at which it happens.
What the queue promises the blocks in it, and why a piston told about a change
by a packet handler still moves in the same tick this phase belongs to, is
[pistons and block
events](../blocks/pistons-and-block-events#the-queue-and-which-tick-it-drains-in).

`ServerLevel.handlingTick` went true at the very top of the tick and goes
false here, so the whole entity half runs outside that window. Its one
reader in the game is `PistonBaseBlock`, which uses it to tell a piston
update raised inside the tick from one raised outside it.

## An empty dimension skips exactly three things

`ServerChunkCache.hasActiveTickets` — really
`TicketStorage.shouldKeepDimensionActive`, which is the players' *simulation*
tickets and the forced, portal and ender-pearl tickets — resets `ServerLevel.emptyTime` at this point in the tick, and two other calls reset it: `Entity.teleportCrossDimension` for the level a non-player entity arrives in, and `ServerPlayer.registerAndUpdateEnderPearlTicket`, from a thrown pearl's own tick in the entity loop, for the pearl's level. Otherwise
the counter rises, and it rises only while running, so a frozen dimension
never falls asleep. Past `ServerLevel.EMPTY_TIME_NO_TICK`, 300 ticks, the
level skips the dragon fight, the entity loop and the block entities. That
is the entire skip: the weather, the scheduled ticks, the chunk source, the
block events, the entity manager's load and unload drain and the debug feed
all keep running on a dimension nobody has visited for fifteen seconds.

## Every entity, and then its riders

`EntityTickList.forEach` walks the tick list. An entity is skipped if it has
been removed, or if `TickRateManager.isEntityFrozen` says so — frozen, and
neither a player nor something carrying one. Otherwise it gets
`Entity.checkDespawn`, and then ticks only if it is a `ServerPlayer` or its
chunk answers `DistanceManager.inEntityTickingRange`. A passenger whose
vehicle is alive and still lists it is passed over here and ticked by the
vehicle instead; a stale link is broken with `Entity.stopRiding`.
`ServerLevel.tickNonPassenger` calls `Entity.commonTick`, which counts
`Entity.invulnerableTime` down, records the old position and bumps
`Entity.tickCount`, and then `Entity.tick`; `ServerLevel.tickPassenger`
runs `Entity.rideTick` for each rider that is a `Player` or in the tick
list, recursively down the stack. `Level.guardEntityTick` wraps each one in
a crash report titled *Ticking entity*, so a mob that throws names itself.

The list stays stable under all of that by construction. Membership changes
during the loop — a bred baby, a fired arrow, a bolt a trident calls down —
arrive
through `ServerLevel.EntityCallbacks.onTickingStart` immediately.
`EntityTickList` allows exactly one `EntityTickList.forEach` at a time and,
on the first add or remove while it is iterating, copies into its
`EntityTickList.passive` map and swaps that with `EntityTickList.active`:
the running loop keeps walking the view it started with, and the new entity
waits for the next tick. Which entities are in the list at all is
`PersistentEntitySectionManager`'s answer, through
`Visibility.fromFullChunkStatus` — only `FullChunkStatus.ENTITY_TICKING`
maps to a ticking visibility — with one override, `Player.isAlwaysTicking`,
which keeps a player in the list whatever its chunk is doing; the
`ServerPlayer` test inside the loop is the other half of that exemption,
letting the player past the range check. Entities are
Part VI's subject: [entity
lifecycle](../entities/entity-lifecycle#findable-ticking-or-neither).

## Block entities reach one chunk further than mobs

`Level.tickBlockEntities` walks `Level.blockEntityTickers`, dropping removed
tickers as it goes and running the rest — but only those whose position
passes `ServerLevel.shouldTickBlocksAt`, which is the **block**-ticking
range, and only while `TickRateManager.runsNormally`. That single condition
is why a furnace keeps smelting one chunk further out than a zombie keeps
walking. A block entity created during the walk — a sculk sensor a catalyst's
spread has just grown — lands on `Level.pendingBlockEntityTickers` instead,
because `Level.tickingBlockEntities` is true, and is merged in when the next
tick's walk begins. [Block
entities](../blocks/block-entities#loaded-is-not-enough-to-tick) has the
rest.

## After the entities: the manager's drain and the debug feed

`PersistentEntitySectionManager.tick` drains
`PersistentEntitySectionManager.loadingInbox`, a concurrent queue that chunk storage fills on the Server thread once an IO read returns — entities from freshly loaded chunks join the
world here — and then processes
`PersistentEntitySectionManager.chunksToUnload`. The
`ServerLevel.EntityCallbacks` it fires
(`ServerLevel.EntityCallbacks.onTickingStart`,
`ServerLevel.EntityCallbacks.onTickingEnd`) are exactly what add and remove
entries in the tick list the previous step walked.

Then `LevelDebugSynchronizers.tick` pushes this tick's changed brains, paths and raids to any client subscribed through `DebugSubscriptions` — and, just
before it, arms or clears the neighbour listener on `CollectingNeighborUpdater`,
which sends each neighbour update the moment it happens, depending on whether
anyone is subscribed to `DebugSubscriptions.NEIGHBOR_UPDATES` at all. It is the
one step of the level tick whose entire output is diagnostic, and it is outside
every gate. The last step is `RandomState.garbageCollect`, which drops the
density buffers world generation pooled and no longer uses.

## What leaves the level, and when

| the packet | the step that sends it | to whom |
|---|---|---|
| `ClientboundGameEventPacket`, rain and thunder levels | `ServerLevel.advanceWeatherCycle` | this dimension's players, and everyone on a start or a stop |
| `ClientboundGameEventPacket`, start and stop raining | the same step, on a transition | every player in every dimension |
| `ClientboundSetTimePacket` | `ServerClockManager.moveToTimeMarker`, from the sleep skip | everyone, sent by the clock manager |
| `ClientboundBlockUpdatePacket` · `ClientboundSectionBlocksUpdatePacket` | `ChunkHolder.broadcastChanges`, per 16³ section | everyone tracking the chunk |
| `BlockEntity.getUpdatePacket` | the same walk, beside a changed position that has one | everyone tracking the chunk |
| `ClientboundLightUpdatePacket` | the same walk, before the blocks | the players at the edge of their tracked area |
| entity moves, and the adds and removes a move causes | `ChunkMap.TrackedEntity`, inside `ChunkMap.tick` | whoever tracks that entity |
| `ClientboundBlockEventPacket` | `ServerLevel.runBlockEvents` | players within 64 blocks |

None of them go out when they are written. Every one is queued behind the
suspended flush that `MinecraftServer.tickChildren` opens around all the
levels, and leaves on the wire when `Connection.tick` flushes the channel in
the connection phase, after every level has ticked
([the server tick](server-tick#the-two-writes-each-client-gets)).

## Questions players ask

**Does `/tick freeze` stop the server doing work?** Barely, on the chunk
side. Distance updates, the block-change broadcast, entity tracking and
chunk unloads all run every tick regardless. Two things inside the chunk
source *are* frozen and are easy to miss: the spawning and random-ticking
step, and `TicketStorage.purgeStaleTickets` — so a frozen world accumulates
expired tickets and never releases the chunks they hold.

**Why did replacing a block cancel its scheduled tick?** Because a scheduled
tick is a promise to *that* block: `ServerLevel.tickBlock` compares the
`Block` at the position with the one scheduled, and a mismatch runs nothing.

**Why does `/weather rain` in the Nether change the overworld?** There is one
`WeatherData` on the `MinecraftServer`, and the command writes it from whatever
dimension it runs in; only a dimension that `Level.canHaveWeather` counts it
down. The fade of `Level.rainLevel` and `Level.thunderLevel` is per level, and
only there.

**Why does an arrow a skeleton fires not move until the next tick?**
`EntityTickList` swapped its maps the moment the arrow was added, so the loop
that is running kept the view it started with. The arrow is in the list — it
is just not in *this* walk of it. A mob that spawned naturally is ticked at
once: the chunk source added it before the walk began.

> **For a 1.21-era reader.** The day–night cycle is out of the level. Time is
> a set of `WorldClock`s owned by `ServerClockManager` and ticked by the
> server, and the `ClientboundSetTimePacket` after a night skip is sent by the
> clock manager rather than by the level. What `ServerLevel` still owns is
> *gameTime*, advanced by `ServerLevel.tickTime` in the overworld alone, which
> every other dimension reads. The weather countdowns went the same way, into
> one `WeatherData` on the `MinecraftServer`.

## Where to look

In the order the tick runs them. `ServerLevel.tick` ·
`EnvironmentAttributeSystem.invalidateTickCache` ·
`ServerLevel.advanceWeatherCycle` · `SleepStatus.areEnoughSleeping` ·
`ServerClockManager.moveToTimeMarker` · `ServerLevel.tickTime` ·
`LevelTicks.tick` · `ServerChunkCache.tick` · `ServerChunkCache.tickChunks` ·
`NaturalSpawner.createState` · `ServerLevel.tickChunk` ·
`ServerChunkCache.broadcastChangedChunks` · `ChunkHolder.broadcastChanges` ·
`ChunkMap.tick` · `ServerLevel.runBlockEvents` · `EntityTickList.forEach` ·
`Level.tickBlockEntities` · `PersistentEntitySectionManager.tick` ·
`LevelDebugSynchronizers.tick` · `RandomState.garbageCollect`

---

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