Files
minecraft_protocol/packets/format-entity-metadata.md
claude-timemachine 02a249a379 verify pass 2: resolve markers via ViaBackwards/ViaRewind/ViaLegacy
Closed most remaining UNCONFIRMED items + caught corrections: 1.7 data[]=proto5,
1.8 multi-block nibble order (z/x), stance y+1.62, 1.15 wolf idx18=begging,
1.18 below-zero sections NOT stripped (claim was wrong), CONTAINER_SLOT_STATE
1.20.3, + pinned first-appeared versions (report details/server links=1.21,
fluidCount=26.1, block-state id14=1.19.4, itemCount VarInt=1.20.5).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 19:13:19 +02:00

344 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Entity Metadata Wire Format
> **Scope:** Java Edition entity metadata (a.k.a. "entity data") as carried by `Set Entity Metadata` and (historically) inside spawn packets.
> **Primary sources:**
> - `/tmp/mcproto-refs/minecraft-data/data/pc/*/protocol.json` — `entityMetadata`, `entityMetadataEntry`, `entityMetadataItem` type defs
> - `/tmp/mcproto-refs/ViaVersion/api/src/main/java/com/viaversion/viaversion/api/type/types/entitydata/OldEntityDataType.java` — legacy frame reader
> - `/tmp/mcproto-refs/ViaVersion/api/src/main/java/com/viaversion/viaversion/api/type/types/entitydata/ModernEntityDataType.java` — modern frame reader
> - `/tmp/mcproto-refs/ViaVersion/api/src/main/java/com/viaversion/viaversion/api/minecraft/entitydata/types/EntityDataTypes*.java` — per-version type registries
> - Cross-links: [../versions/1.9.md](../versions/1.9.md) · [../versions/1.14.md](../versions/1.14.md) · [../01-data-types.md](../01-data-types.md) (VarInt / NBT / Slot)
---
## 1. Overview
Entity metadata is an indexed key/value list sent by the server to describe the current state of an entity: health flags, custom names, riding status, arm poses, and hundreds of entity-class-specific attributes. It travels in two contexts:
1. **`Set Entity Metadata` (Play CB)** — differential update for an already-spawned entity. Only the changed indices are sent.
2. **Spawn packets (historical, ≤ 1.8)** — the full metadata list was embedded in `Spawn Mob` and `Spawn Object`.
The format has two incompatible eras:
| Era | Versions | Index encoding | Type encoding | Terminator |
|-----|----------|----------------|---------------|------------|
| **Legacy** | ≤ 1.8 (protocol ≤ 47) | low 5 bits of a single byte | high 3 bits of the same byte | `0x7F` (`127`) |
| **Modern** | ≥ 1.9 (protocol ≥ 107) | separate unsigned byte | separate VarInt | `0xFF` (`255`) |
---
## 2. Modern Format (1.9+)
Source: `ModernEntityDataType.java:3438`; `minecraft-data` `data/pc/1.9/protocol.json` (and all later versions).
### 2.1 Frame structure
```
EntityMetadata = (Entry)* Terminator
Entry = {
Index : u8 -- unsigned byte; 0x000xFE are valid indices
Type : VarInt -- type ID from the per-version registry (see §3)
Value : <depends on Type>
}
Terminator = 0xFF -- single byte; signals end of list
```
Read loop (pseudocode matching `ModernEntityDataType.java`):
```
loop:
index = readUnsignedByte()
if index == 0xFF: break
typeId = readVarInt()
type = registry.byId(typeId)
value = type.read(buffer)
entries.add(Entry(index, type, value))
```
**Key points:**
- Index and Type are always present together; neither is omitted.
- The list is unordered within the packet (indices need not be ascending).
- An empty metadata list is a legal single byte: `FF`.
- The `Type` VarInt is always small (≤ 2 bytes on the wire) but must be read as a full VarInt.
> **Note on minecraft-data 1.91.12.2:** `minecraft-data` records the `type` field as `i8` (signed byte) in those versions rather than `varint`. This is an artifact of minecraft-data's protocol description — since values 012 all fit in one byte, both readings are wire-compatible. ViaVersion's `ModernEntityDataType` reads it as VarInt from 1.9 onward, and the Minecraft wiki specifies VarInt. The `varint` encoding is authoritative; `i8` in minecraft-data is a pragmatic approximation. The minecraft-data source confirms the change to `varint` in its 1.13 JSON: `/tmp/mcproto-refs/minecraft-data/data/pc/1.13/protocol.json`.
---
## 3. Type Registry — 1.21.1
Source: `EntityDataTypes1_21.java` (lines 3363) + `minecraft-data` `data/pc/1.21.1/protocol.json` `entityMetadataEntry` type mapper.
| ID | Name | Wire encoding |
|----|------|---------------|
| 0 | Byte | `i8` — signed byte |
| 1 | VarInt | VarInt |
| 2 | VarLong | VarLong (added 1.19.3; see §5) |
| 3 | Float | `f32` — big-endian IEEE 754 single |
| 4 | String | VarInt length + UTF-8 bytes |
| 5 | Chat (TextComponent) | Anonymous NBT tag (1.20.3+); `string` (JSON text) in 1.91.20.2 |
| 6 | Optional Chat | `bool` present-flag + Chat (if present) |
| 7 | Slot | Item stack — see [../01-data-types.md](../01-data-types.md) §Slot; encoding changed at 1.9, 1.13, 1.13.2, 1.20.2, 1.20.5 |
| 8 | Boolean | `bool` — single byte `0x00`/`0x01` |
| 9 | Rotations | `f32` pitch + `f32` yaw + `f32` roll (3 × 4 bytes) |
| 10 | Position | 64-bit packed `Position``[x:26][z:26][y:12]` since 1.14; `[x:26][y:12][z:26]` in 1.91.13 |
| 11 | Optional Position | `bool` present-flag + Position (if present) |
| 12 | Direction | VarInt enum: `0`=Down `1`=Up `2`=North `3`=South `4`=West `5`=East |
| 13 | Optional UUID | `bool` present-flag + 128-bit UUID big-endian (if present) |
| 14 | Block State | VarInt flat block-state ID (0 = air) |
| 15 | Optional Block State | VarInt: `0` = absent; non-zero = block-state ID |
| 16 | NBT | Anonymous compound NBT (no leading name) — see [../01-data-types.md](../01-data-types.md) §NBT |
| 17 | Particle | Typed `Particle` compound (added 1.13) |
| 18 | Particles | VarInt count + Particle\[\] (added 1.20.5) |
| 19 | Villager Data | VarInt villagerType + VarInt villagerProfession + VarInt level (added 1.14) |
| 20 | Optional VarInt | VarInt: `0` = absent; value = stored\_value + 1 |
| 21 | Pose | VarInt enum — see §4.3 (added 1.14) |
| 22 | Cat Variant | VarInt registry ID (added 1.19) |
| 23 | Wolf Variant | `registryEntryHolder`: VarInt registry ID or inline `WolfVariant` struct (added 1.21.2; was plain VarInt in 1.21) |
| 24 | Frog Variant | VarInt registry ID (added 1.19) |
| 25 | Optional Global Position | `bool` present-flag + dimension String + Position (if present) |
| 26 | Painting Variant | `registryEntryHolder`: VarInt registry ID or inline `PaintingVariant` struct (added 1.21; struct encoding differs 1.21 vs 1.21.2+) |
| 27 | Sniffer State | VarInt enum (added 1.20) |
| 28 | Armadillo State | VarInt enum (added 1.20.5) |
| 29 | Vector3 | `f32` x + `f32` y + `f32` z (added 1.20) |
| 30 | Quaternion | `f32` x + `f32` y + `f32` z + `f32` w (added 1.20) |
**Total: 31 type IDs (030) in 1.21.x.**
Source: `EntityDataTypes1_21.java` constructor argument `super(31)`.
### 3.1 `registryEntryHolder` encoding (types 23, 26)
Used since 1.21 for Wolf Variant and Painting Variant.
```
RegistryEntryHolder = {
hasInlineData : bool
if hasInlineData:
inlineValue : <variant-specific struct>
else:
registryId : VarInt -- numeric ID within the server's registry
}
```
`WolfVariant` inline struct (1.21): wildTexture String + tameTexture String + angryTexture String + biome IDSet.
`PaintingVariant` inline struct (1.21): i32 width + i32 height + String assetId + Optional Anonymous NBT title + Optional Anonymous NBT author.
Source: `EntityMetadataWolfVariant` / `EntityMetadataPaintingVariant` in `minecraft-data` `data/pc/1.21.1/protocol.json`.
---
## 4. Type Registry History (selected versions)
The per-version type classes in ViaVersion are authoritative. Below are the salient snapshots.
### 4.1 1.91.12.2 (EntityDataTypes1_9 / EntityDataTypes1_12)
Source: `EntityDataTypes1_9.java` (ordinal = ID), `EntityDataTypes1_12.java`.
| ID | 1.9 | 1.12 (added) |
|----|-----|--------------|
| 0 | Byte | same |
| 1 | VarInt | same |
| 2 | Float | same |
| 3 | String | same |
| 4 | Component (JSON text) | same |
| 5 | Slot (`ITEM1_8`) | same |
| 6 | Boolean | same |
| 7 | Rotations | same |
| 8 | Position (`BLOCK_POSITION1_8`) | same |
| 9 | Optional Position | same |
| 10 | Direction | same |
| 11 | Optional UUID | same |
| 12 | Optional Block State | same |
| 13 | — | NBT (`NAMED_COMPOUND_TAG`) |
Note: no VarLong, no Particle, no Villager Data, no Pose in this era.
### 4.2 1.131.13.2 (EntityDataTypes1_13 / EntityDataTypes1_13_2)
Source: `EntityDataTypes1_13.java`, `EntityDataTypes1_13_2.java`.
Key changes:
- **Type field becomes VarInt** (was `i8`-compatible byte in 1.91.12.2; explicitly `varint` in minecraft-data from 1.13).
- ID 5 becomes `Optional Component` (was absent; Component shifts from id 4 to id 4, Optional Component added at 5; ids 513 shift up by 1).
- Particle added at ID 15.
- Slot encoding updated to `ITEM1_13` / `ITEM1_13_2`.
Full 1.13.2 registry (16 entries, IDs 015):
`Byte · VarInt · Float · String · Component · OptComponent · Slot · Boolean · Rotations · Position · OptPosition · Direction · OptUUID · OptBlockState · NBT · Particle`
### 4.3 1.14 (EntityDataTypes1_14)
Source: `EntityDataTypes1_14.java`.
Added IDs (shifts all subsequent IDs by +1 relative to 1.12, due to Optional Component insertion in 1.13 and now Villager Data + Opt VarInt + Pose):
| New ID | Type | Notes |
|--------|------|-------|
| 16 | Villager Data | VarInt×3: villagerType, villagerProfession, level |
| 17 | Optional VarInt | 0 = absent; otherwise stored\_value + 1 |
| 18 | Pose | VarInt enum |
**Pose values (1.14):** `0`=Standing `1`=FallFlying `2`=Sleeping `3`=Swimming `4`=SpinAttack `5`=Sneaking _(1.14 original list)_
Position encoding also changes: `BLOCK_POSITION1_14` (`[x:26][z:26][y:12]` bit layout) replaces `BLOCK_POSITION1_8`. See [../versions/1.14.md](../versions/1.14.md) §Position.
### 4.4 1.19 (EntityDataTypes1_19)
Source: `EntityDataTypes1_19.java`.
Added IDs 1922:
- 19: Cat Variant (VarInt)
- 20: Frog Variant (VarInt)
- 21: Optional Global Position (`bool` + dimension String + Position)
- 22: Painting Variant (VarInt)
### 4.5 1.19.3 (EntityDataTypes1_19_3)
Source: `EntityDataTypes1_19_3.java`; confirmed by `minecraft-data` `data/pc/1.19.3/protocol.json`.
**VarLong inserted at ID 2.** All existing IDs from 2 onward shift up by 1 (Float moves to 3, String to 4, etc.). This was the largest single-version numbering shift since 1.13.
Registry after insertion (24 entries):
`Byte(0) · VarInt(1) · VarLong(2) · Float(3) · String(4) · Component(5) · OptComponent(6) · Slot(7) · Boolean(8) · Rotations(9) · Position(10) · OptPosition(11) · Direction(12) · OptUUID(13) · OptBlockState(14) · NBT(15) · Particle(16) · VillagerData(17) · OptVarInt(18) · Pose(19) · CatVariant(20) · FrogVariant(21) · OptGlobalPos(22) · PaintingVariant(23)`
### 4.6 1.19.4 (EntityDataTypes1_19_4)
Source: `EntityDataTypes1_19_4.java`.
Added:
- ID 14: Block State (non-optional, explicit; confirmed new in 1.19.4). ViaVersion `EntityDataTypes1_9` through `EntityDataTypes1_19_3` have only `optionalBlockStateType` (never a separate `blockStateType`); `EntityDataTypes1_19_4.java:45` is the first to add `blockStateType = add(14, Types.VAR_INT)` alongside `optionalBlockStateType = add(15, Types.VAR_INT)`. Source: grep across `EntityDataTypes1_9.java`, `EntityDataTypes1_14.java`, `EntityDataTypes1_19.java`, `EntityDataTypes1_19_3.java`, `EntityDataTypes1_19_4.java`.
- ID 25: Sniffer State (VarInt enum, new in 1.20 — but the type ID slot is first defined in ViaVersion's 1.19.4 class)
- ID 26: Vector3F (`f32`×3)
- ID 27: Quaternion (`f32`×4)
### 4.7 1.20.5 (EntityDataTypes1_20_5) and 1.21 (EntityDataTypes1_21)
Source: `EntityDataTypes1_20_5.java`, `EntityDataTypes1_21.java`.
Changes vs 1.19.4:
- **Particles (plural) added at ID 18.** Particle (singular) stays at 17. All subsequent IDs shift +1.
- **Chat/Component now uses anonymous NBT tag** (`TRUSTED_TAG`) instead of JSON string. Source: `Types.TRUSTED_TAG` / `Types.TRUSTED_OPTIONAL_TAG` in both files.
- **Wolf Variant (ID 23)**: 1.21 uses `WolfVariant.TYPE` (inline struct holder); 1.20.5 used plain VarInt.
- **Armadillo State (ID 28)** added.
- **Wolf Variant, Painting Variant** use `registryEntryHolder` shape from 1.21.2 onward.
Full 1.21.x registry: see §3 above (31 entries, IDs 030).
---
## 5. Evolution Timeline
| Version | Change |
|---------|--------|
| ≤ 1.8 | Legacy single-byte packed format (§6); terminator `0x7F` |
| **1.9** | Modern format: separate u8 index + VarInt type + 0xFF terminator; 13 type IDs |
| 1.9 | Dual-hand: `HAND_ACTIVE` (Byte, index 5 on LivingEntity) added; off-hand Slot carried in Slot type; old Block/Int types removed |
| 1.12 | NBT type added (ID 13); total 14 type IDs |
| **1.13** | Type field officially becomes VarInt (was byte-range compatible); Optional Component added (ID 5); Particle added (ID 15); Slot encoding updated; 16 type IDs |
| **1.14** | Villager Data, Optional VarInt, Pose added (IDs 1618); Position bit-layout changed (`[x:26][z:26][y:12]`) |
| 1.19 | Cat Variant, Frog Variant, Optional Global Position, Painting Variant added (IDs 1922); 23 type IDs |
| **1.19.3** | VarLong inserted at ID 2 — all subsequent IDs +1; 24 type IDs |
| 1.19.4 | Block State (explicit non-optional) ID 14 split from Optional Block State; Sniffer State, Vector3, Quaternion added; 28 type IDs |
| 1.20.5 | Particles (plural) added at ID 18 (+1 shift); Chat type switches from JSON string to anonymous NBT; Armadillo State added; Wolf Variant expanded; 31 type IDs |
| 1.21 | Wolf Variant becomes `registryEntryHolder`; Painting Variant becomes `registryEntryHolder` |
| 1.21.2 | Painting Variant inline struct encoding updated |
---
## 6. Legacy Format (≤ 1.8)
Source: `OldEntityDataType.java:3650`; `minecraft-data` `data/pc/1.8/protocol.json` `entityMetadata`.
### 6.1 Frame structure
```
EntityMetadata = (Entry)* Terminator
Entry = {
header : u8 -- bits [7:5] = type ID (3 bits); bits [4:0] = index (5 bits)
Value : <depends on type ID>
}
Terminator = 0x7F -- single byte (127); index field would be 31, type 3 = Float, but 0x7F is the sentinel
```
The header byte encoding:
```
header = (typeId << 5) | (index & 0x1F)
```
This limits index to 031 (5 bits) and type to 07 (3 bits).
### 6.2 1.8 type registry (8 entries)
Source: `EntityDataTypes1_8.java` (ordinal = ID); `minecraft-data` `data/pc/1.8/protocol.json` `entityMetadataItem`.
| ID | Name | Wire encoding |
|----|------|---------------|
| 0 | Byte | `i8` |
| 1 | Short | `i16` big-endian |
| 2 | Int | `i32` big-endian |
| 3 | Float | `f32` IEEE 754 |
| 4 | String | VarInt length + UTF-8 |
| 5 | Slot | `ITEM1_8` — item id (i16) + count (i8) + damage (i16) + NBT |
| 6 | Block Position | `i32` x + `i32` y + `i32` z (three separate ints, NOT the packed Position) |
| 7 | Rotations | `f32` pitch + `f32` yaw + `f32` roll |
**Key differences from 1.9+:**
- No Boolean type (boolean flags used Byte 0/1).
- No Optional types (UUID, Position, Block State).
- Short (i16) and Int (i32) exist; both removed in 1.9 (replaced by VarInt).
- Block Position is three plain `i32` fields, not a packed 64-bit value.
- No Component / NBT / Direction / Particle.
---
## 7. Index Assignments Are Per-Entity-Class and Per-Version
**The type registry (§3) defines only the wire encoding per type ID. Which type ID and which index value mean what for a given entity class is entirely version-specific and entity-class-specific.**
Examples (from `EntityDataIndex1_9.java`):
| Entity class | Index (1.8) | Index (1.9) | Type (1.8) | Type (1.9) | Meaning |
|---|---|---|---|---|---|
| Entity | 0 | 0 | Byte | Byte | Status flags (on fire, crouching, …) |
| Entity | 1 | 1 | Short | VarInt | Air supply |
| Entity | 2 | 2 | String | String | Custom name |
| LivingEntity | 6 | 6 | Float | Float | Health |
| LivingEntity | 7 | 7 | Int | VarInt | Potion effect colour |
| Player | 10 | 12 | Byte | Byte | Skin flags |
| LivingEntity | — | 5 | — | Byte | Hand state (new in 1.9, dual-hand) |
Per-version index tables live in the ViaVersion EntityDataIndex files under
`/tmp/mcproto-refs/ViaVersion/common/src/main/java/com/viaversion/viaversion/protocols/v1_8to1_9/data/EntityDataIndex1_9.java`
and in `EntityTypes*` classes for later versions.
For 1.21.1 entity data index assignments, refer to [../versions/1.21.md](../versions/1.21.md) or the Minecraft wiki [Entity format](https://minecraft.wiki/w/Entity_format) page which tabulates per-class indices.
---
## 8. Carrying Packet
In 1.21.1 (Play state, client-bound):
```
Set Entity Metadata (0x52) = {
entityId : VarInt
metadata : EntityMetadata -- zero or more entries + 0xFF
}
```
Source: `minecraft-data` `data/pc/1.21.1/protocol.json` `play.toClient.types.packet_entity_metadata`.
Historically (≤ 1.8) the full metadata list was also embedded in `Spawn Mob` (CB 0x0F) and `Spawn Object` (CB 0x0E).
---
## 9. Open Items
- **Block State (ID 14, non-optional) first appeared in 1.19.4**: confirmed — ViaVersion's 1.14, 1.19, and 1.19.3 classes have only `optionalBlockStateType`; 1.19.4 adds both `blockStateType` (14) and `optionalBlockStateType` (15). See §4.6.
- **`optional_global_pos` wire encoding**: confirmed — `GlobalBlockPositionType.java` reads `String dimension` (the dimension ResourceLocation, e.g. `minecraft:overworld`) + `BLOCK_POSITION1_14` (packed i64 position). The `minecraft-data` `['option', 'string']` representation is a schema approximation; the actual wire format is `bool` + (if present) String + packed Position. Source: `ViaVersion/.../api/type/types/math/GlobalBlockPositionType.java:41-43`.
- Pose enum values beyond the 1.14 original (e.g. `Croaking`, `UsingTongue`, `Roaring`, `Sniffing`, `Emerging`, `Digging` added for 1.171.20 mobs) — not enumerated here; the authoritative list is at `minecraft.wiki/w/Entity_format#Pose`.