packets/: packet model + catalogs + wire-format deep-dives

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>
This commit is contained in:
claude-timemachine
2026-06-19 15:15:26 +02:00
parent a3d5f64ef5
commit 90b711d12a
8 changed files with 2159 additions and 0 deletions
+343
View File
@@ -0,0 +1,343 @@
# 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; previously only Optional Block State existed — no bare Block State type ID was present in 1.141.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 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
- `<!-- UNCONFIRMED -->` — Whether `Block State` (ID 14, non-optional) already existed in 1.141.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.171.20 mobs) — not enumerated here; the authoritative list is at `minecraft.wiki/w/Entity_format#Pose`.