Entity anatomy
Verified against Minecraft 26.3 · Part VI · What an entity is: one
EntityTypefrom the registry, through a factory, to a live object the level ticks.
You type /summon pig, and by the next tick there is a pig standing where
you are. Between the command and the animal are three objects and one factory
call: a ResourceKey in EntityTypeIds, the EntityType that EntityTypes
built from it before any world existed, and the Entity that the type’s
EntityType.EntityFactory returns. This page is the vocabulary of that chain,
and the rest of Part VI is built on it. The surprise is what happens when the
kind is one nobody has heard of, because that depends entirely on which of two
doors it came through. An entity arriving over the network is named by a
number, the registry index inside a ClientboundAddEntityPacket; an entity
read back off disk is named by a string, the id field in the region file.
Those two doors give opposite answers to the same bad name.
Registries.ENTITY_TYPE is one of the few defaulted registries, its
default is pig, and DefaultedMappedRegistry overrides nine methods, seven
of which hand that default back — five for a miss, among them
DefaultedMappedRegistry.byId and DefaultedMappedRegistry.getValue, and
DefaultedMappedRegistry.getAny and DefaultedMappedRegistry.getDefaultKey
always.
So the numeric door cannot fail: ByteBufCodecs.registry decodes through
IdMap.byIdOrThrow, which cannot throw here because
DefaultedMappedRegistry.byId never returns null, and a pig walks out of the
packet. The string door is not defaulted at all, because EntityType.CODEC is
Registry.byNameCodec and resolves through a lookup the subclass leaves alone
— as does DefaultedMappedRegistry.getOptional, the one override that
deliberately answers empty. So the same unknown name in a region file yields
nothing: EntityType.create logs Skipping Entity with id … and leaves a hole
where the entity was. The default reaches the network and never reaches your
save file.
The cast
| class | what it decides | thread |
|---|---|---|
EntityType | one registered kind: its factory, category, frozen dimensions, feature flags, and the two numbers that decide how it reaches clients | built in a class initialiser, read from both game threads and the worldgen executor after |
EntityTypes | which 161 kinds exist and what every one of them is sized like — the most useful single table in the package | class initialiser, once |
Entity | position, box, network id, synched values, vehicle, removal reason. Deliberately thin on behaviour | the tick thread of whichever level owns it, or the worldgen executor before its chunk is live |
EntityDimensions | width, height, eye height, attachment points, and whether Attributes.SCALE may touch them | immutable record; the type’s is shared by every entity of the type |
SynchedEntityData | which of the entity’s fields the other side is told about (synched entity data) | one container per side; only the server’s writes travel |
EntityInLevelCallback | whether the object is in a world or merely on the heap | installed by the level’s entity manager |
ServerEntity | when a tracked entity’s state becomes packets (what the client is told) | the Server thread |
EntityReference | how one entity remembers another across a chunk unload | wherever it is resolved |
Only one packet is this page’s own: ClientboundAddEntityPacket, the birth
announcement. Everything after it — ClientboundSetEntityDataPacket,
ClientboundEntityPositionSyncPacket, ClientboundEntityEventPacket,
ClientboundRemoveEntitiesPacket — belongs to a sibling page.
The type is frozen, the entity is not
An entity is anything in the world that is not a block: mobs, players, items
on the ground, arrows, boats, item frames, experience orbs, the invisible
markers a data pack uses as bookmarks. The kind is an EntityType, built once
and never changed. The individual is an Entity, and nearly all of its state
is mutable by design.
classDiagram
class EntityType {
one object per registered kind, 161 of them
EntityType.EntityFactory, usually the constructor
MobCategory, the spawn cap and despawn distance
clientTrackingRange in chunks, updateInterval in ticks
frozen by EntityType.Builder.build
}
class EntityDimensions {
width, height, eye height
EntityAttachments, and a fixed flag
}
class Entity {
SynchedEntityData, eight accessors and the chain's
Entity.position at the feet, Entity.bb a stored box
Entity.dimensions and Entity.eyeHeight, both caches
Entity.levelCallback, a NULL sentinel until a level takes it
Entity.removalReason, the whole am-I-alive bit
Entity.vehicle and Entity.passengers
}
EntityType --> EntityDimensions : holds one, built once
EntityType ..> Entity : EntityType.create calls the factory
EntityDimensions ..> Entity : copied into the two caches, and recopied on a pose change
What each object holds, and the line the page turns on: everything in the top two boxes is frozen at class-initialiser time and shared by every individual of that kind, and everything in the bottom one belongs to one individual and changes.
Entity implements nine interfaces — Nameable, EntityAccess,
ScoreHolder, SyncedDataHolder, DataComponentGetter, ItemOwner,
SlotProvider, DebugValueSource and TypedInstance over EntityType —
which is a fair measure of how many systems reach into it. Health, AI, the
damage pipeline and inventory are all further down the tree.
In 26.3 the type constants are not on EntityType. They live in two
parallel files: EntityTypeIds, 161 ResourceKeys with no reference to any
entity class, and EntityTypes, the 161 matching objects that
EntityType.Builder produced from them. MobCategory — MobCategory.MONSTER,
MobCategory.CREATURE, MobCategory.AMBIENT, MobCategory.AXOLOTLS,
MobCategory.UNDERGROUND_WATER_CREATURE, MobCategory.WATER_CREATURE,
MobCategory.WATER_AMBIENT, MobCategory.MISC — carries the spawn cap
(entity lifecycle)
and the despawn distance
(entity lifecycle). None of
this is data-driven: Registries.ENTITY_TYPE is code-registered
(the data-driven type pattern
is what it is not), and what a
data pack can reach is the tags in EntityTypeTags, the loot table at
entities/<id>, DataComponents.ENTITY_DATA on a spawn egg, and the
per-species variant registries (Registries.WOLF_VARIANT,
Registries.CAT_VARIANT, Registries.PAINTING_VARIANT and the rest).
Dimensions, attachments and pose
EntityDimensions is a record — width, height, eye height, an
EntityAttachments map and a fixed flag — and
EntityDimensions.makeBoundingBox centres the box in X and Z on
Entity.position and grows it upward, because that position is the bottom
centre, the feet, and not the middle of the entity.
EntityDimensions.scale returns the record unchanged when it is fixed, and
also when both factors are 1. The flag is not the only way to escape Attributes.SCALE,
either: plain Entity.getDimensions never scales at all, so every non-living
entity ignores scale for free, and LivingEntity.getDimensions — which is
final, the overridable hook being LivingEntity.getDefaultDimensions —
short-circuits a sleeping entity to LivingEntity.SLEEPING_DIMENSIONS before
any scaling happens.
EntityAttachments answers where a passenger sits, where the entity itself
sits on a vehicle, where the name tag floats: EntityAttachment.PASSENGER,
EntityAttachment.VEHICLE, EntityAttachment.NAME_TAG and
EntityAttachment.WARDEN_CHEST, each with a fallback such as
EntityAttachment.Fallback.AT_HEIGHT that EntityAttachments.Builder.build
fills in from the width and height.
Pose is eighteen constants with explicit wire ids, not ordinals — the
ones that matter are Pose.STANDING, Pose.CROUCHING, Pose.SWIMMING,
Pose.FALL_FLYING, Pose.SLEEPING, Pose.SPIN_ATTACK and Pose.DYING, and
the rest are animation states of one mob or a few, such as Pose.EMERGING and
Pose.DIGGING. Pose.BY_ID is built with
ByIdMap.OutOfBoundsStrategy.ZERO, so a pose id outside the range decodes
silently to Pose.STANDING rather than failing the connection. Pose is the
synched value on the base class that changes the box — a dozen subclasses
have their own, from a pufferfish’s puff state to a slime’s size — and the
loop is worth stating
because everything else on this page hangs off it: Entity.setPose writes the
value, SynchedEntityData.set calls Entity.onSyncedDataUpdated
inside the setter, before anything is marked dirty, and that sees
Entity.DATA_POSE and calls Entity.refreshDimensions, which asks
Entity.getDimensions for the new record, overwrites both caches, and calls
Entity.reapplyPosition to rebuild the box. The side that set the pose
resizes immediately, the other resizes when the value lands. If the box grew,
Entity.fudgePositionAfterSizeChange nudges the entity out of whatever it now
overlaps — but only on the server, only after the first tick, only with
physics on, only when the entity is not a Player, and only when the new box
is at most four blocks in both width and height.
That loop is also the answer to why did the hitbox not change when I changed
the size. Entity.dimensions, Entity.eyeHeight and Entity.bb are caches,
and two things in the base classes refresh them unasked: a pose change, in
Entity, and LivingEntity.onAttributeUpdated on Attributes.SCALE. The dozen
subclasses with a box-changing value of their own — AgeableMob on the
baby bit among them — each call Entity.refreshDimensions for it, and
everything else has to call it by hand. While a cache is stale the two
eye-height accessors disagree: Entity.getEyeHeight with no argument reads the
cache, and the Pose overload recomputes.
The tree, and the class that was inserted into it
Entity has 18 direct subclasses and 192 descendants. LivingEntity and
its own 124 make 125 of them — two thirds of the tree in one branch — and the
seventeen non-living branches hold the other 67.
Entity tree to three levels, generated from the decompile: a name in brackets carries how many types descend from it, and a grey line folds a node's childless children when there are two or more of them. Click to enlarge.The full drawing, with the block, item and screen trees beside it, is in
what extends what. What matters here is the shape:
a long spine and a scattering. The spine is LivingEntity, and for all that it
carries it has exactly three direct subclasses — Avatar, ArmorStand and
Mob —
which is worth saying plainly, because it means an armour stand is a living
entity with no AI at all: no GoalSelector, no PathNavigation, both of
those being Mob’s. The Brain is not Mob’s, though. It is declared on
LivingEntity, built in its constructor and written under a Brain tag by
every living entity, so an armour stand carries an empty one.
PathfinderMob is a small class that scores walk targets and adds no way of
moving, which is why Ghast and Phantom navigate without ever being one.
AgeableMob adds babies, TamableAnimal an owner reference and a tame bit,
Animal and Monster split by disposition, and Monster implements Enemy,
a marker interface carrying nothing but XP-reward constants.
The 67 outside LivingEntity’s branch are shallow, and the atlas draws them as
four families and a scattering: Projectile with 26 descendants,
VehicleEntity with 15, BlockAttachedEntity with 6, Display with 3, and
thirteen direct subclasses of Entity with no
children of their own, from ItemEntity to LightningBolt. Sharing a base class that thin is what lets
them disagree so completely about being hit
(damage and death).
Avatar sits between LivingEntity and Player. It is a small class
carrying the player-shaped Avatar.POSES dimension map, Avatar.DEFAULT_EYE_HEIGHT
of 1.62 and one abstract method, Avatar.getProfile, so anything written
against “Player extends LivingEntity” is wrong by one level. Its point
is Mannequin, a posable, profile-skinned, player-looking entity in the
decoration package that is not a Player and carries none of the
inventory, abilities or hunger. What the rung holds and who draws it belong to
player anatomy;
what matters here is that the tree has that level.
Cutting across the tree are the capability interfaces, where most of the
shared behaviour lives: Leashable is the fattest, with
Leashable.tickLeash called from Entity.baseTick, and beside it
Bucketable, EquipmentUser, NeutralMob, Attackable, Targeting,
TraceableEntity, OwnableEntity, Shearable, PlayerRideableJumping,
ItemSteerable, and ContainerUser, with exactly two implementors: Player
and CopperGolem.
EntityReference deserves a name of its own. It stores either a UUID or the
live object, and EntityReference.getEntity resolves lazily and decays back
to the UUID the first time it is resolved after the target reports itself
removed. It is how “who last hurt me”, “who owns this pet” and “who shot this
arrow” survive a chunk unload.
Where the 727 files are
| subpackage | files | what |
|---|---|---|
entity/ai | 277 | goals, brains, navigation, attributes, sensors |
entity/animal | 130 | one subpackage per species or family now |
entity/monster | 84 | mostly likewise |
world/entity itself | 85 | Entity, LivingEntity, Mob, Avatar, EntityType, EntityTypes, the capability interfaces |
entity/projectile | 37 | arrows, fireballs, thrown items |
entity/boss | 24 | dragon and wither |
entity/vehicle | 24 | boats and minecarts |
entity/npc | 15 | villagers and traders |
entity/player | 14 | Player, Inventory, Abilities |
entity/decoration | 13 | armour stands, frames, paintings, Mannequin |
entity/variant | 11 | the machinery that picks a data-driven mob variant |
entity/item, entity/raid, entity/ambient, entity/schedule | 13 | items, raids, bats, and Activity — which is all entity/schedule still holds |
From a registry entry to a live object
EntityTypes runs EntityType.Builder.build for each of the 161 keys in
EntityTypeIds and registers the result, and it is that call which freezes
the EntityDimensions and its attachment points for the life of the type.
Everything below happens per entity, long afterwards.
sequenceDiagram
participant SumC as SummonCommand
participant ET as EntityType
participant Entity as Entity
participant SL as ServerLevel
participant PESM as PersistentEntity<br/>SectionManager
SumC->>SumC: rejects out of bounds, then on peaceful
SumC->>ET: loadEntityRecursive, a tag with an id string
ET->>ET: EntityType.by reads the id through EntityType.CODEC
ET->>ET: EntityType.create checks canSpawn, then the factory
ET->>Entity: the constructor: the next id, a new UUID, the type's dimensions
Entity->>Entity: eight base accessors, then defineSynchedData, then setPos
ET->>Entity: load, then readAdditionalSaveData
SumC->>Entity: snapTo, the postLoad processor, before any passenger exists
ET->>Entity: each Passengers child the same way, then startRiding
SumC->>SL: tryAddFreshEntityWithPassengers, one body at a time
SL->>PESM: addNewEntity
PESM->>Entity: setLevelCallback, and only now is it in a world
Note over SL,Entity: the next server tick
SL->>Entity: commonTick, then tick
Thirteen steps between a registry entry and a live object, and the one that
matters is the second from last: everything above Entity.setLevelCallback
happens
to an object no level knows about.
The client builds its own copy of the same object, from a packet, whenever a player comes into range — which is the same journey a second time, in five steps instead of thirteen:
sequenceDiagram
participant Entity as Entity
participant SE as ServerEntity
participant CPL as ClientPacketListener
SE->>Entity: getAddEntityPacket
SE->>CPL: ClientboundAddEntityPacket, with data, attributes and equipment
CPL->>CPL: createEntityFromPacket, a second object
CPL->>CPL: Entity.recreateFrom<br/>Packet
CPL->>CPL: ClientLevel.addEntity
The client’s Entity is not the server’s: it is a separate object, of the
same class for all but a player or a mannequin, built from a packet rather than from a tag, and the two are only
ever as alike as the channels in the rest of this part keep them.
Name to type. EntityType.by reads the id field through
EntityType.CODEC. An unknown id yields nothing, and the entity is dropped
with the log line the opening quoted — this is the save-file half of the hook.
Type to object. EntityType.create checks EntityType.canSpawn: the
feature flags, plus a peaceful-difficulty test gated on the type’s own
EntityType.isAllowedInPeaceful flag, which is a declared property and not a
synonym for hostile. EntitySpawnRequest.ignoreChecks skips both.
SummonCommand tests the same peaceful rule itself, a few lines earlier, purely
to fail with a better message; the command does not set the skip flag, so both
run. One type fails EntityType.create always: EntityTypes.PLAYER was built
with EntityType.Builder.createNothing, so its factory returns null, which
is why ClientPacketListener.createEntityFromPacket special-cases it and
hand-builds a RemotePlayer from the player info it already has; it is
EntityType.Builder.noSave and EntityType.Builder.noSummon besides. Otherwise the
factory runs. The Entity constructor takes the next id from the level,
invents a UUID with Mth.createInsecureUUID, copies the type’s dimensions
into its cache, and builds the synched-data container — its own eight
accessors, then the abstract Entity.defineSynchedData, which contributes
nothing on the base class and exists only for the subclasses to chain up
through (synched entity
data
names the eight and says why their order is the whole numbering
scheme). It then calls Entity.setPos at the origin,
so a fresh entity already has a full-size box rather than the zero-size
Entity.INITIAL_AABB the field initialiser gave it.
Tag to state. Entity.load reads position (clamped to ±3.0000512E7
horizontally), motion, rotation and UUID, then calls the abstract
Entity.readAdditionalSaveData, then Entity.reapplyPosition again if
Entity.repositionEntityAfterLoad says so — which everything says except
BlockAttachedEntity, the corpus’s one override, so paintings, item frames,
leash knots and cushions are precisely the entities that skip it and keep the
position their own load computed. The save twin is Entity.addAdditionalSaveData, and
passengers save inside their vehicle: Entity.save returns false for anything
currently riding, and the vehicle writes them into its Passengers list
through Entity.saveAsPassenger, using Entity.getEncodeId, which is null
for types that never serialise.
Object to level. ServerLevel.tryAddFreshEntityWithPassengers refuses if
any UUID in the stack is already loaded, then
ServerLevelAccessor.addFreshEntityWithPassengers calls
ServerLevel.addFreshEntity once per body.
PersistentEntitySectionManager.addNewEntity files it into a section and
replaces EntityInLevelCallback.NULL with a real callback. That is the
moment it stops being an object on the heap;
entity lifecycle
takes it from here.
Level to client. ServerEntity.addPairing sends
ServerEntity.sendPairingData as one bundle: the packet
Entity.getAddEntityPacket returns — id, UUID, type, position, velocity,
three rotation bytes and one varint of type-specific data — then the
non-default synched values, then attributes and equipment. On the client,
ClientPacketListener.handleAddEntity builds the object, calls
Entity.recreateFromPacket, and only then adds it to the level.
The spawn-egg path is a different pipeline that meets this one at the end.
EntityType.spawn snaps the new entity out of collision with
EntityType.getYOffset, aligns head and body rotation, runs
Mob.finalizeSpawn, applies a PostSpawnProcessor, adds it through
ServerLevelAccessor.addFreshEntityWithPassengers and plays an ambient
sound. SummonCommand also calls Mob.finalizeSpawn, but only when the
command asks for it, and it never touches the Y offset.
The tick both sides share
ServerLevel.tickNonPassenger opens a profiler section named after the entity
type, calls Entity.commonTick — which counts Entity.invulnerableTime down,
calls Entity.setOldPosAndRot, steps the interpolation on a client and
increments Entity.tickCount — and then calls Entity.tick, all of it
through Level.guardEntityTick, which turns any exception into a crash report
with the entity’s details attached.
ClientLevel.tickEntities does the same, through the same guard, having first
skipped anything removed, riding or frozen by the tick-rate manager. Which
entities the server’s loop reaches, and in what order, belongs to the level
tick. No
entity is ever ticked on a worker pool, and Entity is not thread-safe —
though entities are constructed on one: ChunkStatus.SPAWN runs
NaturalSpawner.spawnMobsForChunkGeneration on the worldgen executor, so a
chunk’s first animals are built and finalised off the Server thread before the
chunk ever becomes live.
Entity.tick on the base class is one line: call Entity.baseTick.
Everything else readers remember happening “in tick” is in Entity.baseTick — the
dead-vehicle check, the boarding cooldown, the portal handling, the fluid
snapshot, swimming, fire ticking, the lava halving of fall distance, the
below-world check, the leash — or in an override. LivingEntity.tick calls up
and then runs LivingEntity.aiStep, and Mob.tick calls up and refreshes its
goal-control flags through Mob.updateControlFlags every five ticks, on the
server only — as the portal handling, the burning and the leash inside
Entity.baseTick are too.
What differs between the sides is not the tick but what the tick is allowed to do, and that is exactly the subject of the next page, authority.
The id, and what compares equal
Entity.equals compares the network id and nothing else — not the UUID, not
identity — and Entity.hashCode is the id. Ids are handed out by
ServerLevel.getNextEntityId from a static AtomicInteger on ServerLevel,
so they are process-global, which keeps two server levels’ ids apart, and the
level is consulted only to avoid a collision. Equal ids still meet across
levels: in singleplayer the client’s copy of an entity and the integrated
server’s share one, which is why entities from two levels must never go in one
set.
On the client there is no id at all until a packet supplies one.
Level.getNextEntityId returns a literal zero and ClientLevel does not
override it, while zero is the reserved invalid id and Entity.getId throws
Tried to access entity ID before ID assignment on it. Between construction
and the Entity.setId that Entity.recreateFromPacket performs, a
client-side entity has no id, and therefore no equality and no hash.
ServerLevel.getNextEntityId skips zero deliberately.
The two numbers frozen onto the type
The cast promised two numbers that decide how an entity reaches clients.
EntityType.clientTrackingRange is in chunks and
EntityType.updateInterval in ticks — 5 and 3 unless a builder says
otherwise — and between them they decide who is told about the entity and how
often its movement is sent, never mind what the packet says (what the client is
told
owns the asking, and a third parameter, EntityType.trackDeltas, which is
true of every type but eleven).
Thirty-eight of the 161 types set an interval explicitly, most of them at 10
or 20; eight set Integer.MAX_VALUE (most through
EntityType.Builder.noUpdateInterval), which ChunkMap turns into
UpdateInterval.NEVER, so their interval branch never fires at all —
EntityTypes.ITEM_FRAME and EntityTypes.GLOW_ITEM_FRAME,
EntityTypes.PAINTING, EntityTypes.LEASH_KNOT, EntityTypes.END_CRYSTAL,
EntityTypes.LIGHTNING_BOLT, EntityTypes.AREA_EFFECT_CLOUD and
EntityTypes.CUSHION. At the
other end EntityTypes.MARKER has a tracking range of 0 and is never sent to
anyone at all.
The client holds a lever over a distance of its own, how far away an entity is
still drawn (in singleplayer the same option also scales how far the integrated
server tracks), and it is process-global in the same way the ids are. Entity.viewScale is a static field on Entity,
and LevelExtractor writes it from the effective render distance and the
entity-distance option together, not from the option alone — which is why
entity render distance moves when you touch a slider that does not name
entities.
Where to look
Start at EntityTypes, which builds all 161 types and is the most useful
single table in the package, and read EntityType.Builder.build beside it for
what gets frozen. Then EntityType.create and EntityType.loadEntityRecursive
for the chain this page traces, and Entity itself — its constructor,
Entity.defineSynchedData and Entity.load — for what comes out of it.
Entity.refreshDimensions is the way into the dimension caches and
EntityDimensions.makeBoundingBox the arithmetic under them.
PersistentEntitySectionManager.addNewEntity is where an object becomes part
of a world. Two doors the page opens only a crack: EntityReference, for how
one entity remembers another, and EntityType.spawn, the spawn-egg path that
meets the trace at its end.
Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes tools/verify_names.py.