What the client is told
Verified against Minecraft 26.3 · Part IX · a creeper walks into view: everything the server decides to say, in order, and everything it decides not to.
A creeper three chunks away steps across a section boundary, and at the next tick’s tracking sweep, unless the player moves first, the server decides a player may know about it. One bundled packet goes out, and the position in that packet is not where the creeper is. It is where the creeper’s tracker last said it was — up to an update interval out of date. That is not a bug being tolerated. Every relative move the viewer is sent after it is a difference from the last position it was told, not a position, and the viewer adds each difference to its own copy of that base — so a viewer that started from anywhere else would be wrong by the gap until the next absolute position reset it. So the server sends the base rather than the truth, and the rest of this page is that trade made over and over: which chunks a player is sent and how fast, which entities they are told about, and what counts as a change worth a packet.
The cast
| class | what it decides | thread |
|---|---|---|
ChunkMap | which players can see which chunks and which entities — it keeps the tracked entities and moves each player’s chunk view | Server |
ChunkMap.TrackedEntity | one entity’s audience, ChunkMap.TrackedEntity.seenBy, and the range test that fills it | Server |
ServerEntity | the change detector: the baseline, the interval gate, the packet shape | Server |
ChunkTrackingView | which chunks a player is owed, and what a movement turns into | Server |
PlayerChunkSender | how many chunks leave for one player this tick | Server |
ChunkHolder | the per-chunk block-change batch, one flush a tick | Server |
EntityType | the per-type tracking range, update interval and delta exclusion | static, from the builder |
ChunkBatchSizeCalculator | the client’s answer — how many chunks a tick it wants | client Netty thread, and it never hops |
One entity’s tick, and the gates it does not pass
flowchart TD
TICK["ChunkMap.tick walks every tracked entity"]:::server --> SEC{"changed section"}
SEC -- yes --> UP["re-test every player in the level"]:::server
UP --> G1{"gate 1: range, hook, chunk"}
G1 -- "all three hold" --> IN["joins the audience: the introduction bundle"]:::server
G1 -- "any one false" --> OUT["leaves it: ClientboundRemoveEntitiesPacket"]:::server
SEC -- no --> G2{"gate 2: section, flag, or range"}
IN --> G2
OUT --> G2
G2 -- "any one is enough" --> SC["ServerEntity.sendChanges"]:::server
G2 -- "none of the three" --> MUTE["nothing, and the counter does not advance"]:::server
SC --> FREE["three feeds that skip gate 3"]:::server
SC --> G3{"gate 3: interval, flag, or dirty data"}
G3 -- "any one is enough" --> D["zero to five packets"]:::server
G3 -- "none of the three" --> WAIT["wait for a later call"]:::server
The three gates, each a three-term test: gate 1 is a conjunction and gates 2 and 3 are disjunctions. That is the asymmetry to carry away — one wrong answer at gate 1 keeps a viewer from seeing the entity at all, and one right answer at either of the others lets it speak, if it has anything to say.
The figure is the page. Three gates stand between an entity moving and a player hearing about it, and each is a three-term test: gate 1 is a conjunction, where all three must hold, and gates 2 and 3 are disjunctions, where any one term is enough. What comes out the bottom is not a description of the entity but a description of the difference between the entity and what this viewer was last told. The sections below are one per gate, then one per feed that does not go through them at all.
Gate 1: who is allowed to see it
ChunkMap.tick re-tests visibility only when something moved between
sections — the entity’s own section against the tracker’s last, and, for
players who changed section, every entity against that player. ChunkMap.move,
which runs on every move a player makes, re-tests every entity against that
player at once, and moves its chunk view when it crosses a boundary (tickets
and
loading).
The test itself is
three conjuncts.
The distance is horizontal. ChunkMap.TrackedEntity.updatePlayer compares
squared x/z distance against the smaller of the entity’s effective range and
the player’s view distance in blocks. Y is ignored entirely — an entity
directly above you, at any height, is in range.
The range is the maximum over the entity and everything riding it.
ChunkMap.TrackedEntity.getEffectiveRange takes the largest
EntityType.clientTrackingRange among the entity and all its indirect
passengers, then scales it by MinecraftServer.getScaledTrackingDistance,
which both server classes override: DedicatedServer applies the
entity-broadcast-range-percentage property, and IntegratedServer applies
the client’s own Entity Distance video option. In singleplayer a graphics
slider decides how far away mobs are tracked.
And the chunk must already be there. Entity.broadcastToPlayer looks like
a per-entity hiding hook and is not: it defaults to true and is overridden
exactly once, by ServerPlayer, to keep a spectator out of every
non-spectator’s view — and out of a fellow spectator’s while it is looking
through something else’s eyes. And
ChunkMap.isChunkTracked is false while the chunk is still queued in
PlayerChunkSender, so an entity is never sent before the ground it stands
on — the ordering is guaranteed rather than hoped for.
One case never reaches the test at all: ChunkMap.TrackedEntity.updatePlayer
returns immediately for the player’s own entity. You never track yourself.
That is why ServerEntity.Synchronizer has a self-directed verb,
ServerEntity.Synchronizer.sendToTrackingPlayersAndSelf, beside
ServerEntity.Synchronizer.sendToTrackingPlayers and
ServerEntity.Synchronizer.sendToTrackingPlayersFiltered, and why damage,
knockback and synched data must all reach for it.
The introduction is one bundle
What gate 1 opening costs, in packets, is one:
sequenceDiagram
participant CM as ChunkMap
participant CMTE as ChunkMap.<br/>TrackedEntity
participant SE as ServerEntity
participant CPL as ClientPacketListener
CM->>CMTE: the creeper changed section, so re-test every player
CMTE->>CMTE: all three conjuncts hold, so the audience gains this connection
CMTE->>SE: addPairing
SE->>SE: sendPairingData fills one list, in a fixed order
SE->>CPL: one ClientboundBundlePacket
Note over SE,CPL: add-entity at the tracker baseline, then synched data, attributes, equipment, ClientboundSetPassengersPacket, ClientboundSetEntityLinkPacket
CPL->>CPL: the bundle is applied inside one task, so nothing renders half-built
Gate 1 opening, and what the new viewer is sent the moment it does: one bundle, in one fixed order, applied on the client inside a single task. The position in it is the tracker’s baseline, not the creeper’s.
ServerEntity.sendPairingData produces the list and ServerEntity.addPairing
sends it as a single ClientboundBundlePacket, so the creeper can never be
seen half-initialised. The synched values are not read fresh: they come from
ServerEntity.trackedDataValues, the cached snapshot of the entity’s
non-default values, refreshed whenever dirty data is flushed
(synched entity
data).
Only the syncable
attributes go (attributes),
and equipment,
passengers and leash links go only if there are any.
That bundle is where the page’s claim first bites: what it carries is the
tracker’s baseline, not the creeper. It has three exceptions and two refusals. Paintings, item frames and leash knots build their own
ClientboundAddEntityPacket from their real position, bypassing
ServerEntity’s baseline — every concrete BlockAttachedEntity but the
cushion, which takes the default, and a block-attached entity has no moves
to add up.
EnderDragonPart refuses outright, and ChunkMap never asks it. Marker throws
outright if anyone asks — which nobody does, because its tracking range is
zero and ChunkMap never tracks it. Everything else reads its position from
ServerEntity.getPositionBase and its rotations and motion from the last-sent
fields, however old they are.
Gate 2: whether the detector is called at all
ChunkMap.tick calls ServerEntity.sendChanges when any of three things is
true: the entity changed section, Entity.needsSync is set, or its chunk is in
entity-ticking range. Two public fields on Entity do the forcing.
Entity.needsSync appears at this gate, again at gate 3 and again in the
velocity decision — three of the cascade’s terms are the same flag — and is
set by being pushed, by being loaded from disk, and by over twenty other
classes for their own reasons. Entity.syncPosition is subtler: it re-phases the call
counter to the next interval boundary, so a bounced entity syncs at once
rather than up to an interval late, unless it is a living entity other than a
shulker, whose SteppedInterpolationTracker takes the flag first and records
the bounce as a step.
The interval itself is per type and the page leans on it below, so it is
worth a number: EntityType.updateInterval defaults to three ticks, thirty
types give it another number and eight never let it open at all — and the
direction is the opposite of the obvious one. The three Display entities get one, because they move by
interpolation and a missed tick shows; an arrow, an experience orb, a dropped
item and a falling block get twenty, because a ballistic path is something
the client can extrapolate exactly. The thing moving fastest is told least
often.
The two counters inside ServerEntity are deliberately out of step.
ServerEntity.tickCount advances on every call, gate 3 open or shut, so
ServerEntity.FORCED_POS_UPDATE_PERIOD counts calls.
ServerEntity.teleportDelay advances only inside gate 3, so
ServerEntity.FORCED_TELEPORT_PERIOD counts gated calls — the forced
absolute sync is rarer than the forced position packet by however long the
interval gate stays shut.
This is also where a distant entity goes quiet, and it has nothing to say: a
mob outside entity-ticking range is not ticked either, so it does not move
itself, and the gate spares a call that would usually send nothing. A push from outside sets
Entity.needsSync and opens it again.
Gate 3, and the position it chooses
The first three rows are the four terms of the source’s one conjunction, in
its order, Entity.getRequiresPrecisePosition first. A relative packet must
pass all four, and only then is the delta’s size tested, which is why the
fourth row’s absolute sync, unlike the others, resets neither the teleport
count nor the ground flag. A passenger, the last row, is branched off before
any of them.
| condition | result |
|---|---|
Entity.getRequiresPrecisePosition | absolute sync |
ServerEntity.teleportDelay past ServerEntity.FORCED_TELEPORT_PERIOD — four hundred gated calls, so about 1,200 ticks for an entity tracked every tick on the three-tick default interval, fewer when data or a push opens the gate | absolute sync |
| the entity just dismounted, or its ground flag differs from the one the conjunction last recorded — so a landing or a step off a ledge that a gated call sees costs a full packet | absolute sync |
| delta beyond what a short can hold — about eight blocks — or, for a dropped item against a wall, floor or ceiling, one the encoding would round | absolute sync |
none of those, and the squared position delta is below ServerEntity.TOLERANCE_LEVEL_POSITION with rotation within ServerEntity.TOLERANCE_LEVEL_ROTATION | nothing sent |
| none of those, and something moved | ClientboundMoveEntityPacket.Pos, .Rot or .PosRot |
the call count is a multiple of ServerEntity.FORCED_POS_UPDATE_PERIOD | a position packet even if nothing moved — but only on a call that got through gate 3 |
| the entity is a passenger | rotation only — the base is silently re-set, and the first call to open gate 3 after it dismounts forces an absolute sync |
The gate covers more than the position. The same test that opens the table
above also opens the synched-data flush, which is why shearing a sheep runs
that sheep’s position branch in the same call (synched entity
data has
the data channel’s half); the ItemFrame branch — one section down, under the feeds that skip gate
3 — is the only path to that flush which skips the interval test, and
ServerEntity.handleMinecartPosRot reaches it from inside the gate rather
than around it.
Rotations are single bytes, so one unit is a little over a degree, and the
base — a VecDeltaCodec, which is the object holding the last-sent position
the whole page’s opening rests on — advances only when a position is sent, on
both sides alike: that is what keeps the two sides’ arithmetic identical, and
the two places that re-set the server’s without one, a passenger’s and a
new-behaviour minecart’s, are the two whose next position goes whole. An arrow never takes a partial
path — AbstractArrow is excluded from the position-only and rotation-only
branches, so every open gate sends it a full position-and-rotation packet. A
minecart on the new movement behaviour skips the table altogether:
ServerEntity.handleMinecartPosRot diverts it into
ClientboundMoveMinecartPacket, which carries the minecart’s own list of
steps.
That first term is set by three classes in the whole game.
Entity.setRequiresPrecisePosition is asked for by a happy ghast on its still
timeout — a large ridable platform, whose rounding error a player has to stand
on — and by a falling block or lit TNT while it is touching a block.
Velocity is the third decision and a separate channel. When the entity wants
deltas — it is in the EntityType.trackDeltas set, Entity.needsSync is set,
or it is a LivingEntity currently elytra-flying — ServerEntity compares the
current delta movement against ServerEntity.lastSentMovement and sends
ClientboundSetEntityMotionPacket, bundled with
ClientboundProjectilePowerPacket for a hurtling projectile.
EntityType.trackDeltas is a third tracking parameter, switched off on the
builder by EntityType.Builder.dontTrackDeltas: players, llama spit, the
wither, bats, item frames, leash knots, paintings, end crystals, evoker fangs
and cushions are out, everything else is in.
Three feeds that skip gate 3, and one that skips ServerEntity
ServerEntity.sendChanges opens with Entity.updateDataBeforeSync, the hook
LivingEntity overrides to reconcile its own effects — so a mob effect that
expired this tick can dirty the synched container and open gate 3 in the same
call that goes on to read it (synched entity
data).
Below it, three feeds ignore gate 3, a smaller exemption than it sounds:
all three live inside ServerEntity.sendChanges, so a mob that fails gate 2
gets none of them either. Entity.syncVelocity
sends a motion packet to the trackers, and to the entity itself when it is a
player, which is why a player feels knockback at once, its own client moving
it; a creeper, which a client only interpolates, shows its knockback with its
next position packet. A changed passenger list is diffed on every call and
goes out as a ClientboundSetPassengersPacket to every tracker, a riding
player included, though ServerPlayer.startRiding or
ServerPlayer.removeVehicle has already told that player directly. An
ItemFrame flushes its synched data every tenth call, and if it holds a map
with saved data it walks every player in the level — not its trackers — to
push map updates.
Equipment changes do not pass through ServerEntity.
ClientboundSetEquipmentPacket comes from
LivingEntity.handleEquipmentChanges, and it goes to the trackers without
the self-directed variant, so a player is never sent their own equipment. A
straight main-hand to off-hand swap does not even get that far:
LivingEntity.handleHandSwap compresses it into a one-byte entity event. And
damage crosses without a number — ClientboundDamageEventPacket carries the
source, not the amount, and the health bar moves because of a separate synched
value (damage and death).
Chunks arrive on a loop the client paces
Chunk visibility is the same kind of decision one level up: made per player rather than per entity, and paced by a loop whose set point the client supplies.
Which chunks enter and leave
ChunkMap.applyChunkTrackingView diffs the player’s old and new
ChunkTrackingView and turns the difference into a queue of pending sends and
a run of ClientboundForgetLevelChunkPackets. Which chunks are in that view,
what shape the view is — neither a disc nor a square, because
ChunkTrackingView.isWithinDistance shrinks each axis before it squares — and
what makes a chunk eligible to leave at all belong to tickets and
loading.
The one class the iteration needs and no other page names is
ChunkTrackingView.Positioned, which walks one chunk beyond the view distance
for exactly that reason.
The rate the client asks for
Then, once per tick, PlayerChunkSender.sendNextChunks runs per player from
the phase MinecraftServer.tickChildren reaches after every level has ticked
(the server
tick):
- it stops if too many batches are unacknowledged —
PlayerChunkSender.maxUnacknowledgedBatchesstarts at one and is raised toPlayerChunkSender.MAX_UNACKNOWLEDGED_BATCHES, which is ten, on the first reply. One batch in flight until the client answers, ten after it: the first batch of a login is a hard round-trip barrier, and it is why the world arrives in a pause and then a flood; - it accumulates
PlayerChunkSender.batchQuotaby the client’s desired rate and stops if it is below one; - it takes that many chunks nearest first from
PlayerChunkSender.pendingChunks— or, on a memory connection, or whenever fewer are pending than the budget allows, all of them at once; - and it brackets them with
ClientboundChunkBatchStartPacketandClientboundChunkBatchFinishedPacket.
The client times the bracket in ChunkBatchSizeCalculator, clamps the sample
against the running average by ChunkBatchSizeCalculator.CLAMP_COEFFICIENT,
folds it into a weighted mean against
ChunkBatchSizeCalculator.MAX_OLD_SAMPLES_WEIGHT, and reports a rate back in
ServerboundChunkBatchReceivedPacket. The server clamps that between
PlayerChunkSender.MIN_CHUNKS_PER_TICK and
PlayerChunkSender.MAX_CHUNKS_PER_TICK and uses it as next tick’s budget. It
is a closed control loop, and it is the client that sets the set point.
Seven milliseconds of client time per tick is what
ChunkBatchSizeCalculator.getDesiredChunksPerTick divides by its running
estimate of nanoseconds per chunk. It starts pessimistic, at two milliseconds
a chunk — three and a half chunks a tick against
PlayerChunkSender.START_CHUNKS_PER_TICK, nine. That opening figure is never
sent as it stands: the client folds the first real batch into the average
before it answers, so the first number the server hears is already half
measurement.
What is being measured is narrower than it looks.
ClientPacketListener.handleChunkBatchStart and
ClientPacketListener.handleChunkBatchFinished are two of the nine handlers on
the client’s play listener that never hop off the network thread, so the
loop is timing the batch’s arrival and decode on Netty, not mesh building (the nine are listed in
threads).
What a chunk packet carries
A chunk packet — ClientboundLevelChunkWithLightPacket — carries the
client-facing heightmaps, every section’s counts, paletted block states and
biomes, a
block-entity entry per block entity holding BlockEntity.getUpdateTag rather
than its save data, and the light layers. See
chunk anatomy and
lighting.
Block changes: one flush a tick, two audiences
ServerLevel.sendBlockUpdated sends nothing. It marks a section dirty on the
ChunkHolder — in ChunkHolder.changedBlocksPerSection, one short set per
section — and adds the holder to a set on ServerChunkCache, unless the chunk
is loaded but not ticking, in which case nothing is recorded and the change is
never broadcast to anyone. ServerChunkCache.broadcastChangedChunks drains
that set through
ChunkHolder.broadcastChanges once per level tick, inside
ServerChunkCache.tickChunks (on a frozen tick too, though never in a debug
world), early in the tick and before entities
move (the level
tick)
— so one broadcast
carries the block changes made so far this tick and the rest since the last
broadcast: the previous tick’s block events, entities and block entities, and
players’ actions.
Light goes first, and to a narrower audience — the border of each
player’s sent region rather than everyone tracking the chunk, through
ChunkMap.isChunkOnTrackedBorder. Why the border player needs it and the
player in the middle does not belongs to lighting,
and it is the one feed of this flush whose audience is narrower than the
chunk’s trackers.
Then blocks, to everyone tracking the chunk. Exactly one changed block in
a section becomes a ClientboundBlockUpdatePacket, two or more become a
ClientboundSectionBlocksUpdatePacket, and every change within the tick
collapses into at most one packet per section in this flush. What collapses them is that the
set holds positions, not values: ChunkHolder.broadcastChanges reads the
level again when it builds the packet, so a position written five times in one
tick is sent once, carrying the value it ended on and none of the four it
passed through. A whole redstone cascade can therefore run, settle and be
broadcast as a single state per position (signal and
dust).
And block entities alongside the blocks, not after them. The check runs
inside the same per-section loop, immediately after that section’s own update
packet — interleaved, not a third pass.
ChunkHolder.broadcastBlockEntityIfNeeded tests whether the state has a block
entity at all and delegates to ChunkHolder.broadcastBlockEntity, one of the
two places in the game that ask BlockEntity.getUpdatePacket (the other
resends the blocks under a player the server thinks is standing on air). Only overriding types produce one here, and the fallback is not “it
rides the chunk packet instead” — which is why chest contents are invisible
until the chest is opened. What the two sync hooks default to, and which types
override which, belong to block
entities.
The rest of the block-shaped traffic
It is small: ClientboundBlockEventPacket from the deferred event set
(pistons and block
events),
ClientboundBlockDestructionPacket for other players’ mining progress,
ClientboundChunksBiomesPacket when biomes are re-sent, and
ClientboundBlockChangedAckPacket, sent at most once per connection per tick
and on any tick where a loaded client sent a block action, a use-on or a use —
including an unsequenced abort or change of dig direction, which produces an ack of zero and settles nothing. The rules that receipt obeys — and the ordering rule that makes a receipt
without a verdict sufficient — belong to prediction and
acknowledgement.
The level’s own feeds
Entities and chunks are the two big feeds. The level itself has several small
ones, all bypassing the change detectors entirely — most on ServerLevel,
though time comes from MinecraftServer and the view distances from
PlayerList:
- Time, once a second.
MinecraftServer.forceGameTimeSynchronizationruns every twentieth tick, and the packet it broadcasts carries the overworld’s game time and an empty clock map: clock state travels only when a clock is changed, a player joins, respawns or changes dimension, or the rule that advances time changes (what crosses the wire). - Weather, on change.
ServerLevel.advanceWeatherCyclebroadcasts rain- and thunder-level changes and the start/stop pair asClientboundGameEventPackets, andPlayerListre-sends the same set to a joining player. - Sounds, level events, particles and entity events, each with its own helper and its own audience. Two radii are worth naming because they are not the tracking distance: another player’s mining progress reaches everyone within thirty-two blocks except the miner, and a block event reaches sixty-four.
- View distances, as
ClientboundSetChunkCacheRadiusPacketandClientboundSetSimulationDistancePacket— the two integers that are the client’s entire knowledge of the ticket system, and the only feed here the receiver acts on structurally rather than by storing a value: the first one rebuilds the client’s chunk storage array. - The debug feed, which the next section’s table names as an exception
more than once, and a narrow one.
ServerLevelowns a set of per-subscriber debug synchronizers that push neighbour updates, POI state, chunk sends and entity tracking to a client that has opted in — sixteen subscriptions in all, registered inDebugSubscriptions, covering brains, goal selectors and paths among them. Most of the next section has no subscription at all: not the seed, not the loot tables, not the game rules, not a scheduled tick.
What the client is never told
| what never crosses | the server-side owner | where it is explained |
|---|---|---|
| all AI — targets, goals, brains, paths — bar a few synched flags and target ids (the aggressive flag, a guardian’s beam target, the wither’s head targets), except through the debug channel | Mob.goalSelector, Mob.targetSelector, Mob.getTarget, Brain, Mob.navigation | AI, goals and brains |
| scheduled block and fluid ticks — the client’s equivalents are empty | ServerLevel.blockTicks, ServerLevel.fluidTicks | scheduled ticks |
| points of interest, except through the debug channel | ChunkMap.poiManager | points of interest |
| the ticket graph — the client gets a radius and a simulation distance, as two integers | TicketStorage, DistanceManager, ChunkHolder.ticketLevel, FullChunkStatus | tickets and loading |
worldgen, and the world seed — ClientLevel gets only a biome zoom seed; structures only through the debug channel | ChunkGenerator, RandomState, ServerLevel.structureManager, StructureStart | the generation pipeline |
the worldgen heightmaps, and any block-entity field outside BlockEntity.getUpdateTag, bar an operator’s query, a command block’s screen, a creative player’s pick-block and an opened container’s slots | BlockEntity | block entities |
| non-syncable attributes, loot tables and loot seeds, the natural spawn state, raids and the dragon fight beyond their boss bars, except through the debug channel | AttributeMap, LootTable, NaturalSpawner | attributes |
| game rules — the full set reaches the client only on request, and only for a player with the command permission; the few that change how the client behaves are pushed | GameRules | level data and rules |
| the creeper’s fuse length and its swell counter — of its three synched values, none is the counter | Creeper | synched entity data |
| everything out of range: entities past tracking range, chunks past the view, and every other level on the server | ChunkMap, MinecraftServer.levels | — |
Choosing what the client may be wrong about
Every gate above is a decision not to say something, and the server can
afford them because the receiver is not passive. ClientLevel never admits a
chunk is missing, runs its own light engine and its own clock, ticks entities
it does not own — Creeper.tick runs locally, swell counter and all — and
guesses at block placements through a sequence-numbered ledger. That half of
the story is Part X’s: the client
level for
what the receiver fakes and simulates,
prediction and
acknowledgement
for what it
guesses, and
authority
for which side is allowed
to move what. The client also applies a whole burst of these packets once per
frame, before that frame’s ticks, so at a high frame rate it takes the
server’s updates far more often than it ticks — the two-loops figure in
anatomy is the shape,
the client loop the detail.
Which is the design constraint under all of it: because the client will happily simulate in the absence of data, the server’s job is not to keep the client correct — it is to choose what the client is allowed to be wrong about.
For a 1.21-era reader. Routine movement no longer travels as
ClientboundTeleportEntityPacket: that packet survives, butServerEntitynever touches it, and an absolute position isClientboundEntityPositionSyncPacket. And in 26.3 the passenger list goes to every tracker, where 1.21 skipped a player whose own riding had changed.
Where to look
ServerEntity.sendChanges is the page. It is one long method, and gate 3,
the head-yaw branch and the velocity branch are all visible in it at once,
with the relative-or-absolute conjunction one call away in
ServerEntity.createMovePacket; read it before anything else
here, and read it twice.
Then the two callers that decide who hears it: ChunkMap.tick for the
sweep that runs it, and ChunkMap.TrackedEntity.updatePlayer for gate 1,
which is short and the whole visibility policy. ServerEntity.addPairing
and ServerEntity.sendPairingData are the introduction bundle, in the
order it is built.
For the other two feeds: PlayerChunkSender.sendNextChunks is the control
loop from the server’s end and ChunkBatchSizeCalculator is the client’s
half of it — open them together or neither. ChunkHolder.broadcastChanges
is the block flush, and the line in it that re-reads the level is why a
redstone cascade costs one state per position.
One door this page only points at: VecDeltaCodec, the base itself and
the reason the whole policy works.
Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes tools/verify_names.py.