90b711d12a
Control-state catalogs (handshake/status/login/config), categorized Play catalog (~182 packets), and deep-dives on the four hard formats: chunk data (paletted containers + light), entity metadata (type registry), slot/structured components, command graph (Brigadier). Sourced from minecraft-data + ViaVersion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
344 lines
17 KiB
Markdown
344 lines
17 KiB
Markdown
# 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:34–38`; `minecraft-data` `data/pc/1.9/protocol.json` (and all later versions).
|
||
|
||
### 2.1 Frame structure
|
||
|
||
```
|
||
EntityMetadata = (Entry)* Terminator
|
||
|
||
Entry = {
|
||
Index : u8 -- unsigned byte; 0x00–0xFE 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.9–1.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 0–12 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 33–63) + `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.9–1.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.9–1.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 (0–30) 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.9–1.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.13–1.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.9–1.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 5–13 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 0–15):
|
||
`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 19–22:
|
||
- 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; previously only Optional Block State existed — no bare Block State type ID was present in 1.14–1.19.3) <!-- UNCONFIRMED: minecraft-data 1.19 shows blockStateType=id 13 in ViaVersion but only Optional exists; ViaVersion 1.19.4 file explicitly separates them at 14 and 15. -->
|
||
- 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 0–30).
|
||
|
||
---
|
||
|
||
## 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 16–18); Position bit-layout changed (`[x:26][z:26][y:12]`) |
|
||
| 1.19 | Cat Variant, Frog Variant, Optional Global Position, Painting Variant added (IDs 19–22); 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:36–50`; `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 0–31 (5 bits) and type to 0–7 (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
|
||
|
||
- `<!-- UNCONFIRMED -->` — Whether `Block State` (ID 14, non-optional) already existed in 1.14–1.19.2 or was truly new in 1.19.4: ViaVersion's 1.14 class omits it (only `optionalBlockState` at 13) but 1.19.4 explicitly has both 14 and 15. This implies the split was at 1.19.4, but no direct minecraft-data diff or wiki changelog entry in the available sources explicitly names this change.
|
||
- The `optional_global_pos` value in 1.21.1 (`minecraft-data` encodes it as `['option', 'string']`) — the string appears to be the dimension name key only, not a full ResourceLocation + Position pair as ViaVersion's `OPTIONAL_GLOBAL_POSITION` type implies. <!-- UNCONFIRMED: need to cross-check against the Minecraft wiki Optional Global Position definition. -->
|
||
- Pose enum values beyond the 1.14 original (e.g. `Croaking`, `UsingTongue`, `Roaring`, `Sniffing`, `Emerging`, `Digging` added for 1.17–1.20 mobs) — not enumerated here; the authoritative list is at `minecraft.wiki/w/Entity_format#Pose`.
|