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

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

classwhat it decidesthread
ChunkMapwhich players can see which chunks and which entities — it keeps the tracked entities and moves each player’s chunk viewServer
ChunkMap.TrackedEntityone entity’s audience, ChunkMap.TrackedEntity.seenBy, and the range test that fills itServer
ServerEntitythe change detector: the baseline, the interval gate, the packet shapeServer
ChunkTrackingViewwhich chunks a player is owed, and what a movement turns intoServer
PlayerChunkSenderhow many chunks leave for one player this tickServer
ChunkHolderthe per-chunk block-change batch, one flush a tickServer
EntityTypethe per-type tracking range, update interval and delta exclusionstatic, from the builder
ChunkBatchSizeCalculatorthe client’s answer — how many chunks a tick it wantsclient 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.

conditionresult
Entity.getRequiresPrecisePositionabsolute 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 gateabsolute 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 packetabsolute 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 roundabsolute sync
none of those, and the squared position delta is below ServerEntity.TOLERANCE_LEVEL_POSITION with rotation within ServerEntity.TOLERANCE_LEVEL_ROTATIONnothing sent
none of those, and something movedClientboundMoveEntityPacket.Pos, .Rot or .PosRot
the call count is a multiple of ServerEntity.FORCED_POS_UPDATE_PERIODa position packet even if nothing moved — but only on a call that got through gate 3
the entity is a passengerrotation 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.maxUnacknowledgedBatches starts at one and is raised to PlayerChunkSender.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.batchQuota by 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 ClientboundChunkBatchStartPacket and ClientboundChunkBatchFinishedPacket.

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.forceGameTimeSynchronization runs 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.advanceWeatherCycle broadcasts rain- and thunder-level changes and the start/stop pair as ClientboundGameEventPackets, and PlayerList re-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 ClientboundSetChunkCacheRadiusPacket and ClientboundSetSimulationDistancePacket — 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. ServerLevel owns 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 in DebugSubscriptions, 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 crossesthe server-side ownerwhere 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 channelMob.goalSelector, Mob.targetSelector, Mob.getTarget, Brain, Mob.navigationAI, goals and brains
scheduled block and fluid ticks — the client’s equivalents are emptyServerLevel.blockTicks, ServerLevel.fluidTicksscheduled ticks
points of interest, except through the debug channelChunkMap.poiManagerpoints of interest
the ticket graph — the client gets a radius and a simulation distance, as two integersTicketStorage, DistanceManager, ChunkHolder.ticketLevel, FullChunkStatustickets and loading
worldgen, and the world seed — ClientLevel gets only a biome zoom seed; structures only through the debug channelChunkGenerator, RandomState, ServerLevel.structureManager, StructureStartthe 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 slotsBlockEntityblock 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 channelAttributeMap, LootTable, NaturalSpawnerattributes
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 pushedGameRuleslevel data and rules
the creeper’s fuse length and its swell counter — of its three synched values, none is the counterCreepersynched entity data
everything out of range: entities past tracking range, chunks past the view, and every other level on the serverChunkMap, 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, but ServerEntity never touches it, and an absolute position is ClientboundEntityPositionSyncPacket. 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.