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
+1
View File
@@ -18,6 +18,7 @@ A from-scratch, version-aware study of the Minecraft: Java Edition network proto
| [07-version-differences.md](07-version-differences.md) | Protocol-number table 1.7.10→latest, the major breaking changes per era, and how ViaVersion translates between them |
| [proxy-forwarding/](proxy-forwarding/) | What a proxy does; online vs offline mode; BungeeCord legacy forwarding; Velocity modern forwarding; BungeeGuard; Forge/FML |
| [versions/](versions/INDEX.md) | **Per-version deep-dives** — one doc per release line (1.7.10→26.2), what changed in each protocol bump, sourced from release notes + ViaVersion commits + wiki |
| [packets/](packets/README.md) | **Packet reference** — the packet model, complete control-state catalogs, the categorized Play catalog (~182 packets), + wire-format deep-dives: chunk data (paletted containers), entity metadata, slot/components, command graph |
| [references.md](references.md) | Every source: spec pages + which reference repo to read for what |
| [PLAN.md](PLAN.md) | How this repo was built — orchestration + per-agent research spec |
+111
View File
@@ -0,0 +1,111 @@
# Packets — Model & Navigation
> **Scope:** Java Edition, 1.21.1 baseline (protocol 773) unless noted.
> **Sources:** `minecraft-data/data/pc/1.21.1/protocol.json` · minecraft.wiki/w/Java_Edition_protocol/Packets
---
## 1. Packet Framing Recap
Every packet is **length-prefixed over TCP**. Two modes exist:
**Uncompressed** (before `Set Compression` arrives):
```
[Length: VarInt] [Packet ID: VarInt] [Payload: bytes...]
```
**Compressed** (after `Set Compression`; applies for rest of session):
```
[Packet Length: VarInt] [Data Length: VarInt] [Packet ID + Payload: zlib or raw]
```
`Data Length = 0` means the inner payload was not compressed (below threshold). When `Data Length > 0` it is the uncompressed size of `(Packet ID + Payload)`.
Full framing spec with code citations: [../00-overview.md](../00-overview.md) §2.
---
## 2. Packet ID Scoping
**Packet IDs are scoped by BOTH connection state AND traffic direction.**
The same byte value `0x00` identifies a completely different packet in each slot:
| State | Direction | 0x00 packet |
|---------------|-------------|------------------------|
| Handshaking | Serverbound | Handshake |
| Status | Serverbound | Status Request |
| Status | Clientbound | Status Response |
| Login | Serverbound | Login Start |
| Login | Clientbound | Disconnect (Login) |
| Configuration | Serverbound | Client Information |
| Configuration | Clientbound | Cookie Request |
| Play | Serverbound | Confirm Teleportation |
| Play | Clientbound | Bundle Delimiter |
A parser **must** track the current state and which peer sent the packet before it can decode the ID. State transitions are driven by specific packets (Handshake → Status/Login, Login Acknowledged → Configuration, Finish Configuration → Play).
---
## 3. ID Instability Across Versions
Packet IDs within a state/direction namespace are **not stable between protocol versions**. When Mojang adds or removes packets, every subsequent ID in that list shifts.
Examples:
- Configuration state did not exist before 1.20.2 (protocol 764). All its IDs are new.
- In 1.20.5 (protocol 766), Cookie/Transfer/Store-Cookie/Resource-Pack-Pop/Resource-Pack-Push were added to Configuration, shifting existing IDs upward.
- Login gained `Cookie Request` (CB 0x05) and `Cookie Response` (SB 0x04) in 1.20.5.
- `Login Acknowledged` (SB 0x03) was added in 1.20.2.
For per-version ID tables, see [../versions/](../versions/) and the authoritative machine-readable source below.
---
## 4. Authoritative Per-Version Packet Definitions
`minecraft-data` provides a complete, machine-readable protocol schema for every supported version:
```
/tmp/mcproto-refs/minecraft-data/data/pc/<version>/protocol.json
```
Structure per state/direction:
```json
{
"<state>": {
"toClient": {
"types": {
"packet": ["container", [
{ "name": "name", "type": ["mapper", { "type": "varint", "mappings": { "0x00": "..." } }] },
{ "name": "params", "type": ["switch", { "compareTo": "name", "fields": { "...": "packet_..." } }] }
]],
"packet_<name>": ["container", [ ... field defs ... ]]
}
}
}
}
```
The `mappings` object is the ID→name table. The `packet_<name>` types give field-level detail.
---
## 5. Navigation
| Document | Contents |
|----------|----------|
| [catalog-control-states.md](catalog-control-states.md) | Complete packet tables for Handshaking, Status, Login, Configuration (1.21.1) |
| [catalog-play.md](catalog-play.md) | Play-state packet index (high count — separate file) |
| [format-chunk-data.md](format-chunk-data.md) | Chunk Data & Update Light packet payload format |
| [format-entity-metadata.md](format-entity-metadata.md) | Entity metadata encoding, type IDs, value formats |
| [format-slot-and-components.md](format-slot-and-components.md) | Item slot encoding; data component system (1.20.5+) |
| [format-command-graph.md](format-command-graph.md) | Declare Commands packet graph encoding |
| [../versions/](../versions/) | Per-version delta notes; ID shift history |
| [../00-overview.md](../00-overview.md) | Transport, framing, compression, encryption |
| [../03-handshake.md](../03-handshake.md) | Handshake packet deep-dive |
| [../04-status-ping.md](../04-status-ping.md) | Status/ping flow detail |
| [../05-login-encryption.md](../05-login-encryption.md) | Login + encryption flow detail |
| [../06-configuration.md](../06-configuration.md) | Configuration state detail |
+179
View File
@@ -0,0 +1,179 @@
# Packet Catalog — Control States
Complete packet tables for the small, fully-enumerable states: **Handshaking, Status, Login, Configuration**.
> **Baseline:** 1.21.1 (protocol 773). Version deltas noted inline.
> **Sources:**
> - `minecraft-data/data/pc/1.21.1/protocol.json` — IDs and field types (machine-readable ground truth)
> - ViaVersion `ClientboundLoginPackets.java`, `ServerboundLoginPackets.java` — enum ordinal cross-check
> - ViaVersion `ClientboundConfigurationPackets1_20_2.java`, `1_20_5.java`, `ServerboundConfigurationPackets1_20_2.java`, `1_20_5.java` — version delta cross-check
> - minecraft.wiki/w/Java_Edition_protocol/Packets — field names and semantic descriptions
>
> **See also:** [../03-handshake.md](../03-handshake.md) · [../04-status-ping.md](../04-status-ping.md) · [../05-login-encryption.md](../05-login-encryption.md) · [../06-configuration.md](../06-configuration.md)
---
## Handshaking State
The handshaking state has **no clientbound packets**. There is exactly one modern serverbound packet; the legacy ping byte is a legacy artifact that predates the formal state machine.
### Serverbound
| ID | Name | Direction | Field summary |
|--------|------------------------|-----------|---------------|
| `0x00` | Handshake | C→S | `protocolVersion` (VarInt), `serverHost` (String ≤255), `serverPort` (u16), `nextState` (VarInt: 1=Status, 2=Login, 3=Transfer¹) |
| `0xFE` | Legacy Server List Ping | C→S | `payload` (u8, always `0x01`); pre-1.7 clients only — modern servers recognize and respond with legacy format² |
¹ `nextState=3` (Transfer) added in 1.20.5 (protocol 766).
² `0xFE` is outside the normal VarInt packet-ID space; servers detect it by the raw byte value.
Source: `minecraft-data/data/pc/1.21.1/protocol.json``handshaking.toServer.types` mappings `{"0x00": "set_protocol", "0xfe": "legacy_server_list_ping"}`.
---
## Status State
Two packets in each direction. IDs are stable since 1.7.
### Serverbound
| ID | Name | Direction | Field summary |
|--------|----------------|-----------|---------------|
| `0x00` | Status Request | C→S | No fields; triggers Status Response |
| `0x01` | Ping Request | C→S | `time` (i64, ms timestamp) |
### Clientbound
| ID | Name | Direction | Field summary |
|--------|-----------------|-----------|---------------|
| `0x00` | Status Response | S→C | `response` (String; JSON with version, players, description, favicon) |
| `0x01` | Pong Response | S→C | `time` (i64; echo of Ping Request's timestamp) |
Source: `minecraft-data/data/pc/1.21.1/protocol.json``status.toServer` / `status.toClient` mappings.
---
## Login State
Login was extended in **1.20.2** (Login Acknowledged) and **1.20.5** (Cookie Request/Response).
### Serverbound
| ID | Name | Direction | Since | Field summary |
|--------|------------------------|-----------|---------|---------------|
| `0x00` | Login Start | C→S | 1.7 | `username` (String ≤16), `playerUUID` (UUID) |
| `0x01` | Encryption Response | C→S | 1.7 | `sharedSecret` (ByteArray, VarInt-length-prefixed; RSA-encrypted), `verifyToken` (ByteArray, VarInt-length-prefixed; RSA-encrypted) |
| `0x02` | Login Plugin Response | C→S | 1.13 | `messageId` (VarInt; echoes request), `data` (optional restBuffer; absent if unhandled) |
| `0x03` | Login Acknowledged | C→S | 1.20.2 | No fields; transitions server to Configuration state |
| `0x04` | Cookie Response | C→S | 1.20.5 | `key` (String; resource location), `value` (optional ByteArray ≤5120 bytes) |
### Clientbound
| ID | Name | Direction | Since | Field summary |
|--------|------------------------|-----------|---------|---------------|
| `0x00` | Disconnect (Login) | S→C | 1.7 | `reason` (String; JSON text component) |
| `0x01` | Encryption Request | S→C | 1.7 | `serverId` (String, always `""` in modern protocol¹), `publicKey` (ByteArray; DER-encoded RSA), `verifyToken` (ByteArray; random nonce), `shouldAuthenticate` (bool; false = offline-mode) |
| `0x02` | Login Success | S→C | 1.7 | `uuid` (UUID), `username` (String ≤16), `properties` (Array of {name:String, value:String, signature:Optional\<String\>}), `strictErrorHandling` (bool; added 1.20.5²) |
| `0x03` | Set Compression | S→C | 1.8 | `threshold` (VarInt; packets ≥ this size get compressed; 1 = disable) |
| `0x04` | Login Plugin Request | S→C | 1.13 | `messageId` (VarInt; unique per request), `channel` (String; resource location), `data` (restBuffer; plugin-defined payload) |
| `0x05` | Cookie Request (Login) | S→C | 1.20.5 | `cookie` (String; resource location key) |
¹ Server ID was used for session-server auth in very old versions; now always empty string.
² `strictErrorHandling` field added in 1.20.5 — older clients receive a shorter Login Success.
Source: `minecraft-data/data/pc/1.21.1/protocol.json``login.toServer` / `login.toClient` mappings.
ViaVersion cross-check: `ClientboundLoginPackets.java` ordinals 05 match; `ServerboundLoginPackets.java` ordinals 04 match.
---
## Configuration State
**Added in 1.20.2 (protocol 764).** Not present in 1.20 or earlier.
The Configuration state runs between Login Acknowledged and Finish Configuration. Its purpose: push registry data, feature flags, resource packs, and tags before the client enters Play. The state can be re-entered from Play (server sends `Start Configuration`).
The packet set expanded again in **1.20.5 (protocol 766)**: Cookie, Transfer, Store Cookie, resource-pack split (push/pop vs. unified), Select Known Packs, Custom Report Details, Server Links were added, shifting most IDs.
### Serverbound
| ID | Name | Direction | Since | Field summary |
|--------|------------------------------|-----------|---------|---------------|
| `0x00` | Client Information | C→S | 1.20.2 | `locale` (String ≤16), `viewDistance` (i8), `chatFlags` (VarInt), `chatColors` (bool), `skinParts` (u8 bitmask), `mainHand` (VarInt: 0=left,1=right), `enableTextFiltering` (bool), `allowServerListings` (bool) |
| `0x01` | Cookie Response | C→S | 1.20.5 | `key` (String; resource location), `value` (optional ByteArray ≤5120 bytes) |
| `0x02` | Plugin Message | C→S | 1.20.2 | `channel` (String; resource location), `data` (restBuffer; `minecraft:brand` → UTF-8 VarInt-prefixed client brand string) |
| `0x03` | Acknowledge Finish Configuration | C→S | 1.20.2 | No fields; transitions to Play state |
| `0x04` | Keep Alive | C→S | 1.20.2 | `keepAliveId` (i64; echoes server's value) |
| `0x05` | Pong | C→S | 1.20.2 | `id` (i32; echoes server's Ping id) |
| `0x06` | Resource Pack Response | C→S | 1.20.2 | `uuid` (UUID; 1.20.5+¹), `result` (VarInt: 0=Success,1=Declined,2=Failed,3=Accepted,4=Downloaded,5=InvalidUrl,6=FailedReload,7=Discarded) |
| `0x07` | Select Known Packs | C→S | 1.20.5 | `packs` (Array of {namespace:String, id:String, version:String}) |
| `0x08` | Custom Report Details | C→S | 1.21 | `details` (Array of {key:String, value:String}; debug crash info)² |
| `0x09` | Server Links Response | C→S | 1.21 | `links` (Array; echoes known-type or custom-text link list)² |
¹ In 1.20.21.20.4 the resource pack response had no UUID field; UUID was added when resource-pack-push/pop split was introduced in 1.20.5.
² IDs `0x08` and `0x09` are present in 1.21.1 per `protocol.json` (`custom_report_details`, `server_links`) but absent from earlier ViaVersion 1.20.5 enum — confirm exact introduction version.
<!-- UNCONFIRMED: Exact first version for SB Custom Report Details (0x08) and Server Links Response (0x09) — protocol.json shows them in 1.21.1; ViaVersion 1_20_5 SB enum only lists 8 entries (0x000x07). Likely 1.21.0 or 1.21.1. -->
**ID shift note (1.20.2 → 1.20.5 serverbound):**
| ID (1.20.2) | Name | ID (1.20.5+) |
|-------------|------------------|--------------|
| `0x00` | Client Information | `0x00` (no change) |
| `0x01` | Plugin Message | `0x02` (+1) |
| `0x02` | Finish Configuration | `0x03` (+1) |
| `0x03` | Keep Alive | `0x04` (+1) |
| `0x04` | Pong | `0x05` (+1) |
| `0x05` | Resource Pack | `0x06` (+1) |
| — | Cookie Response | `0x01` (new) |
| — | Select Known Packs | `0x07` (new) |
Source: `minecraft-data/data/pc/1.21.1/protocol.json``configuration.toServer`; `ViaVersion/.../ServerboundConfigurationPackets1_20_2.java` vs `ServerboundConfigurationPackets1_20_5.java`.
---
### Clientbound
| ID | Name | Direction | Since | Field summary |
|--------|------------------------|-----------|---------|---------------|
| `0x00` | Cookie Request | S→C | 1.20.5 | `cookie` (String; resource location key to retrieve) |
| `0x01` | Plugin Message | S→C | 1.20.2 | `channel` (String), `data` (restBuffer; `minecraft:brand` sends server brand) |
| `0x02` | Disconnect | S→C | 1.20.2 | `reason` (anonymousNbt; text component as NBT, not JSON string) |
| `0x03` | Finish Configuration | S→C | 1.20.2 | No fields; server signals config complete, waits for SB Acknowledge |
| `0x04` | Keep Alive | S→C | 1.20.2 | `keepAliveId` (i64; client must echo within ~30s or be kicked) |
| `0x05` | Ping | S→C | 1.20.2 | `id` (i32; client echoes in Pong) |
| `0x06` | Reset Chat | S→C | 1.20.2 | No fields; clears client chat session state |
| `0x07` | Registry Data | S→C | 1.20.2 | `id` (String; registry resource location), `entries` (Array of {key:String, value:Optional\<anonymousNbt\>}); one packet per registry |
| `0x08` | Remove Resource Pack | S→C | 1.20.5 | `uuid` (optional UUID; absent = remove all) |
| `0x09` | Add Resource Pack | S→C | 1.20.5 | `uuid` (UUID), `url` (String), `hash` (String, SHA-1 hex), `forced` (bool), `promptMessage` (Optional\<anonymousNbt\> text component) |
| `0x0A` | Store Cookie | S→C | 1.20.5 | `key` (String; resource location), `value` (ByteArray ≤5120 bytes) |
| `0x0B` | Transfer | S→C | 1.20.5 | `host` (String), `port` (VarInt); redirects client to another server |
| `0x0C` | Feature Flags | S→C | 1.20.2 | `features` (Array\<String\>; resource location list of active experimental features) |
| `0x0D` | Update Tags | S→C | 1.20.2 | `tags` (Array of {tagType:String, tags:Array of {tagName:String, entries:Array\<VarInt\>}}) |
| `0x0E` | Select Known Packs | S→C | 1.20.5 | `packs` (Array of {namespace:String, id:String, version:String}); client replies SB Select Known Packs |
| `0x0F` | Custom Report Details | S→C | 1.21 | `details` (Array of {key:String, value:String}); appears in crash reports¹ |
| `0x10` | Server Links | S→C | 1.21 | `links` (Array of {hasKnownType:bool, knownType:ServerLinkType (if known), unknownType:NbtComponent (if custom), url:String})¹ |
¹ `0x0F` and `0x10` present in 1.21.1 `protocol.json`; not in ViaVersion 1.20.5 CB enum (15 entries, 0x000x0E). Likely introduced 1.21.0 or 1.21.1.
<!-- UNCONFIRMED: Exact first version for CB Custom Report Details (0x0F) and Server Links (0x10) — same tracking note as SB counterparts above. -->
**ID shift note (1.20.2 → 1.20.5 clientbound):**
| ID (1.20.2) | Name | ID (1.20.5+) |
|-------------|-------------------|--------------|
| `0x00` | Plugin Message | `0x01` (+1) |
| `0x01` | Disconnect | `0x02` (+1) |
| `0x02` | Finish Configuration | `0x03` (+1) |
| `0x03` | Keep Alive | `0x04` (+1) |
| `0x04` | Ping | `0x05` (+1) |
| `0x05` | Registry Data | `0x07` (+2) |
| `0x06` | Resource Pack | split: `0x08`/`0x09` |
| `0x07` | Feature Flags | `0x0C` (+5) |
| `0x08` | Update Tags | `0x0D` (+5) |
| — | Cookie Request | `0x00` (new) |
| — | Reset Chat | `0x06` (new) |
| — | Store Cookie | `0x0A` (new) |
| — | Transfer | `0x0B` (new) |
| — | Select Known Packs | `0x0E` (new) |
Source: `minecraft-data/data/pc/1.21.1/protocol.json``configuration.toClient`; `ViaVersion/.../ClientboundConfigurationPackets1_20_2.java` vs `ClientboundConfigurationPackets1_20_5.java`.
+453
View File
@@ -0,0 +1,453 @@
# Play-State Packet Catalog — Java Edition 1.21.1 (protocol 767)
**Version lock**: all IDs on this page are 1.21.1 (protocol 767). Play-state IDs shift between versions — sometimes heavily. See [`../versions/1.21.md`](../versions/1.21.md) for the delta from 1.20.x, and [`../versions/INDEX.md`](../versions/INDEX.md) for the full version map.
**Sources**:
- `minecraft-data`: `/tmp/mcproto-refs/minecraft-data/data/pc/1.21.1/protocol.json` (packet IDs + field types, authoritative for 1.21.1)
- `ViaVersion CB`: `/tmp/mcproto-refs/ViaVersion/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/packet/ClientboundPackets1_21.java` (ordered enum — ordinal = wire ID)
- `ViaVersion SB`: `/tmp/mcproto-refs/ViaVersion/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_3to1_20_5/packet/ServerboundPackets1_20_5.java` (serverbound IDs unchanged from 1.20.5→1.21.1; no new SB enum file in the `v1_20_5to1_21` package)
**Totals** (minecraft-data count): **124 clientbound**, **58 serverbound** = 182 play-state packets.
**Notation**:
- `CB` = Server→Client (clientbound)
- `SB` = Client→Server (serverbound)
- `→ format-*.md` = that packet's binary layout has its own deep-dive doc in this directory
- `<!-- UNCONFIRMED -->` = claim not verified against a second source
---
## 1. Connection / System
Core session-maintenance and state-transition packets.
### Clientbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x00 | `BUNDLE_DELIMITER` | `bundle_delimiter` | Frame boundary: server wraps a group of CB packets between two 0x00 delimiters; client applies them atomically (added 1.19.4) |
| 0x26 | `KEEP_ALIVE` | `keep_alive` | Heartbeat: server sends random `i64` ID; client must echo it within timeout or be kicked |
| 0x35 | `PING` | `ping` | Echoes an `i32` ID back from client's `PONG` (not the status-ping; used for latency measurement mid-session) |
| 0x36 | `PONG_RESPONSE` | `ping_response` | Server response to client's `PING_REQUEST` (0x21 SB); carries `i64` payload |
| 0x1D | `DISCONNECT` | `kick_disconnect` | Kick with chat-component reason (Text component, NBT-encoded in 1.20.3+) |
| 0x2B | `LOGIN` | `login` | **Join Game** — first play-state packet after configuration; see detail below |
| 0x47 | `RESPAWN` | `respawn` | Sent on dimension change or respawn; see detail below |
| 0x53 | `SET_CARRIED_ITEM` | `held_item_slot` | Sets the client's active hotbar slot (08) |
| 0x69 | `START_CONFIGURATION` | `start_configuration` | Transitions back to Configuration state for resource-pack / registry updates (1.20.2+) |
| 0x19 | `CUSTOM_PAYLOAD` | `custom_payload` | Plugin channel message (`Identifier` channel + byte blob) |
| 0x16 | `COOKIE_REQUEST` | `cookie_request` | Server requests a previously stored cookie value from the client (1.20.5+) |
| 0x6B | `STORE_COOKIE` | `store_cookie` | Server pushes a key/value cookie to the client for later retrieval (1.20.5+) |
| 0x73 | `TRANSFER` | `transfer` | Transfers the client to a different server (host + port); 1.20.5+ |
### Serverbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x18 | `KEEP_ALIVE` | `keep_alive` | Echo of server's `i64` keep-alive ID |
| 0x27 | `PONG` | `pong` | Echo of server's `PING` `i32` ID |
| 0x21 | `PING_REQUEST` | `ping_request` | Client-initiated latency probe; carries `i64` payload; server echoes via `PONG_RESPONSE` |
| 0x00 | `ACCEPT_TELEPORTATION` | `teleport_confirm` | Client acks a `PLAYER_POSITION` teleport by echoing its `varint teleportId` |
| 0x0C | `CONFIGURATION_ACKNOWLEDGED` | `configuration_acknowledged` | Client acks `START_CONFIGURATION`, triggering transition back to Configuration state |
| 0x11 | `COOKIE_RESPONSE` | `cookie_response` | Response to `COOKIE_REQUEST`; carries key + optional value |
| 0x12 | `CUSTOM_PAYLOAD` | `custom_payload` | Plugin channel message (SB direction) |
| 0x2B | `RESOURCE_PACK` | `resource_pack_receive` | Client reports resource-pack download status (accepted/declined/downloaded/failed) |
---
### Detail: LOGIN (0x2B CB) — Join Game
First play-state packet sent by the server (after Configuration → Play transition). Sets up the client's world context.
Fields (from `protocol.json` `packet_login`):
| Field | Type | Notes |
|-------|------|-------|
| `entityId` | i32 | Client's entity ID |
| `isHardcore` | bool | |
| `worldNames` | varint-prefixed string[] | All dimension names known to the server |
| `maxPlayers` | varint | (informational, not enforced client-side) |
| `viewDistance` | varint | Server's configured view distance |
| `simulationDistance` | varint | |
| `reducedDebugInfo` | bool | Hides debug overlay |
| `enableRespawnScreen` | bool | |
| `doLimitedCrafting` | bool | |
| `worldState` | SpawnInfo | Composite: dimension type ID, dimension name, hashed seed, gamemode, prev gamemode, debug/flat flags, death location (optional), portal cooldown |
| `enforcesSecureChat` | bool | If true, server requires signed chat messages |
### Detail: RESPAWN (0x47 CB)
Sent on dimension switch, death respawn, or `/respawn`. Resets world context without a full Login.
Fields (`packet_respawn`):
| Field | Type | Notes |
|-------|------|-------|
| `worldState` | SpawnInfo | Same composite type as Login |
| `copyMetadata` | u8 | Bitmask: bit 0 = keep attributes, bit 1 = keep entity data |
---
## 2. Entity
Spawning, movement, metadata, equipment, effects, and removal.
### Clientbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x01 | `ADD_ENTITY` | `spawn_entity` | Spawn any non-XP entity; carries entity ID (varint), UUID, type (varint), position (f64×3), angles, data (varint, type-dependent), velocity |
| 0x02 | `ADD_EXPERIENCE_ORB` | `spawn_entity_experience_orb` | Spawn XP orb; entity ID + position + count (i16) |
| 0x03 | `ANIMATE` | `animation` | Play entity animation (swing arm, take damage, leave bed, etc.); entity ID + animation byte |
| 0x2E | `MOVE_ENTITY_POS` | `rel_entity_move` | Delta position only (fixed-point i16×3); entity moved < 8 blocks |
| 0x2F | `MOVE_ENTITY_POS_ROT` | `entity_move_look` | Delta position + rotation |
| 0x30 | `MOVE_ENTITY_ROT` | `entity_look` | Rotation only |
| 0x48 | `ROTATE_HEAD` | `entity_head_rotation` | Yaw of entity head (separate from body) |
| 0x31 | `MOVE_VEHICLE` | `vehicle_move` | Absolute position + rotation for mounted vehicle |
| 0x70 | `TELEPORT_ENTITY` | `entity_teleport` | Absolute position + rotation for entities beyond delta range (>8 blocks) |
| 0x5A | `SET_ENTITY_MOTION` | `entity_velocity` | Velocity in fixed-point i16 units (1/8000 m/tick) |
| 0x58 | `SET_ENTITY_DATA` | `entity_metadata` | Entity metadata key-value stream; **`format-entity-metadata.md`** |
| 0x5B | `SET_EQUIPMENT` | `entity_equipment` | Equipment slots (main/off hand, 4 armor slots); slot bitmask + Slot data |
| 0x76 | `UPDATE_MOB_EFFECT` | `entity_effect` | Apply/update a potion effect (entity ID, effect ID, amplifier, duration, flags) |
| 0x43 | `REMOVE_MOB_EFFECT` | `remove_entity_effect` | Remove a specific effect from entity |
| 0x59 | `SET_ENTITY_LINK` | `attach_entity` | Attach entity A to entity B (leash); entity ID + vehicle ID (-1 to detach) |
| 0x5F | `SET_PASSENGERS` | `set_passengers` | Set which entities are riding on a vehicle; vehicle entity ID + array of passenger IDs |
| 0x42 | `REMOVE_ENTITIES` | `entity_destroy` | Remove one or more entities by ID (varint array) |
| 0x1F | `ENTITY_EVENT` | `entity_status` | Entity-specific status byte (e.g. death animation, tipped arrow hit, villager anger) |
| 0x1A | `DAMAGE_EVENT` | `damage_event` | Entity took damage: entity ID, damage type, source entity/position (1.19.4+) |
| 0x24 | `HURT_ANIMATION` | `hurt_animation` | Visual-only hurt swing (entity ID + yaw); added 1.19.4 |
| 0x75 | `UPDATE_ATTRIBUTES` | `entity_update_attributes` | Update base attribute values (movement speed, attack damage, etc.) |
| 0x6F | `TAKE_ITEM_ENTITY` | `collect` | Entity picked up an item (item entity ID + collector entity ID + pickup count) |
| 0x3F | `PLAYER_LOOK_AT` | `face_player` | Make entity/player face a position or another entity |
| 0x79 | `PROJECTILE_POWER` | `set_projectile_power` | Projectile acceleration vector <!-- UNCONFIRMED exact semantics --> |
### Serverbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x1A | `MOVE_PLAYER_POS` | `position` | Player position update (`f64` x/y/z + `bool` onGround) |
| 0x1B | `MOVE_PLAYER_POS_ROT` | `position_look` | Position + yaw/pitch |
| 0x1C | `MOVE_PLAYER_ROT` | `look` | Rotation only |
| 0x1D | `MOVE_PLAYER_STATUS_ONLY` | `flying` | Ground status only (used when no other movement to report) |
| 0x1E | `MOVE_VEHICLE` | `vehicle_move` | Absolute position + rotation while controlling a vehicle |
| 0x16 | `INTERACT` | `use_entity` | Interact with entity: entity ID + action (attack/interact/interact-at) + hand + position (for interact-at) |
| 0x24 | `PLAYER_ACTION` | `block_dig` | Block dig/place actions (start dig, cancel, finish, drop item, swap hands, shoot arrow) + location + face + sequence |
| 0x25 | `PLAYER_COMMAND` | `entity_action` | Player-state commands: sneak start/stop, sprint start/stop, leave bed, start/stop elytra, horse jump |
| 0x36 | `SWING` | `arm_animation` | Arm swing animation (main or off hand) |
| 0x37 | `TELEPORT_TO_ENTITY` | `spectate` | Spectator teleport to entity UUID |
| 0x26 | `PLAYER_INPUT` | `steer_vehicle` | Vehicle steering input (sideways/forward + flags for jump/unmount) |
| 0x1F | `PADDLE_BOAT` | `steer_boat` | Boat paddle state (left + right `bool`) |
| 0x20 | `PICK_ITEM` | `pick_item` | Pick Block (middle-click); carries hotbar slot index |
| 0x15 | `ENTITY_TAG_QUERY` | `query_entity_nbt` | Debug: request entity NBT (debug build) |
---
## 3. World
Chunks, blocks, lighting, world events, and world border.
### Clientbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x27 | `LEVEL_CHUNK_WITH_LIGHT` | `map_chunk` | Full chunk column + light data bundled; **`format-chunk-data.md`** |
| 0x2A | `LIGHT_UPDATE` | `update_light` | Update lighting only (no block data); same sky/block light structure as above; **`format-chunk-data.md`** |
| 0x0C | `CHUNK_BATCH_FINISHED` | `chunk_batch_finished` | Signals end of chunk batch; carries batch size (varint); client responds with `CHUNK_BATCH_RECEIVED` (1.20.2+) |
| 0x0D | `CHUNK_BATCH_START` | `chunk_batch_start` | Signals start of chunk batch (1.20.2+) |
| 0x0E | `CHUNKS_BIOMES` | `chunk_biomes` | Update biome data for existing chunks without re-sending block data (1.20+) |
| 0x21 | `FORGET_LEVEL_CHUNK` | `unload_chunk` | Unload a chunk column; `i32` chunk X + Z |
| 0x54 | `SET_CHUNK_CACHE_CENTER` | `update_view_position` | Shift the client's chunk center (sent on player movement between chunks) |
| 0x55 | `SET_CHUNK_CACHE_RADIUS` | `update_view_distance` | Update render distance |
| 0x09 | `BLOCK_UPDATE` | `block_change` | Single block change: `Position` + block state ID (varint) |
| 0x49 | `SECTION_BLOCKS_UPDATE` | `multi_block_change` | Multiple block changes in one chunk section; section coords + array of `(localPos << 12 | stateId)` i64 |
| 0x07 | `BLOCK_ENTITY_DATA` | `tile_entity_data` | Update a block entity's NBT (chest, furnace, sign, etc.) |
| 0x08 | `BLOCK_EVENT` | `block_action` | Block action event (e.g. chest open/close sound, note block pitch, piston extension); location + action byte + action param + block type |
| 0x06 | `BLOCK_DESTRUCTION` | `block_break_animation` | Show block breaking animation progress (09) for a given entity ID at a position |
| 0x05 | `BLOCK_CHANGED_ACK` | `acknowledge_player_digging` | Server acknowledges a block-dig sequence number; carries sequence ID + position + block state + status + successful bool |
| 0x28 | `LEVEL_EVENT` | `world_event` | World event/sound (door open, explosion sound, portal, block break sound, etc.); event ID + position + data + global flag |
| 0x20 | `EXPLODE` | `explosion` | Explosion: position (f64), radius (f32), array of affected block offsets, player knockback velocity, interaction type, particles, sound |
| 0x22 | `GAME_EVENT` | `game_state_change` | Game state notifications: start/stop rain, change gamemode, win game, demo events, bed/respawn prompts, guardian elder effect, etc. Carries `u8` reason + `f32` value |
| 0x56 | `SET_DEFAULT_SPAWN_POSITION` | `spawn_position` | World spawn point (used for compass direction) |
| 0x25 | `INITIALIZE_BORDER` | `initialize_world_border` | Full world border state (center, size, lerp target, lerp time, warning blocks/time) |
| 0x4D | `SET_BORDER_CENTER` | `world_border_center` | Update border center |
| 0x4E | `SET_BORDER_LERP_SIZE` | `world_border_lerp_size` | Animate border size change |
| 0x4F | `SET_BORDER_SIZE` | `world_border_size` | Set border size instantly |
| 0x50 | `SET_BORDER_WARNING_DELAY` | `world_border_warning_delay` | Warning time (seconds) |
| 0x51 | `SET_BORDER_WARNING_DISTANCE` | `world_border_warning_reach` | Warning distance (blocks) |
| 0x64 | `SET_TIME` | `update_time` | World age (i64, always incrementing) + time of day (i64, 024000; negative = frozen) |
| 0x62 | `SET_SIMULATION_DISTANCE` | `simulation_distance` | Server's entity simulation radius |
| 0x71 | `TICKING_STATE` | `set_ticking_state` | Tick rate + whether frozen (1.20.3+) |
| 0x72 | `TICKING_STEP` | `step_tick` | Advance frozen world by N ticks (1.20.3+) |
| 0x0B | `CHANGE_DIFFICULTY` | `difficulty` | Current difficulty + locked flag |
### Serverbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x08 | `CHUNK_BATCH_RECEIVED` | `chunk_batch_received` | Client reports desired chunks/tick back to server after batch (throttle feedback) |
| 0x01 | `BLOCK_ENTITY_TAG_QUERY` | `query_block_nbt` | Debug: request block entity NBT at position |
| 0x02 | `CHANGE_DIFFICULTY` | `set_difficulty` | Op changes world difficulty (op-only) |
| 0x19 | `LOCK_DIFFICULTY` | `lock_difficulty` | Op locks/unlocks difficulty |
| 0x38 | `USE_ITEM_ON` | `block_place` | Place/use block: hand + block position + face + cursor position + head-inside-block flag + sequence |
| 0x39 | `USE_ITEM` | `use_item` | Use item in hand (right-click in air): hand + sequence |
| 0x17 | `JIGSAW_GENERATE` | `generate_structure` | Generate jigsaw structure (op/debug) |
| 0x30 | `SET_COMMAND_BLOCK` | `update_command_block` | Update command block: position + command + mode + flags |
| 0x31 | `SET_COMMAND_MINECART` | `update_command_block_minecart` | Update command block in minecart |
| 0x33 | `SET_JIGSAW_BLOCK` | `update_jigsaw_block` | Update jigsaw block settings |
| 0x34 | `SET_STRUCTURE_BLOCK` | `update_structure_block` | Update structure block settings |
| 0x35 | `SIGN_UPDATE` | `update_sign` | Submit sign text (front/back): position + is-front-side + 4 lines |
---
## 4. Inventory / Container
Opening, populating, and interacting with containers and the player inventory.
### Clientbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x33 | `OPEN_SCREEN` | `open_window` | Open a container UI; window ID + menu type (varint) + title (NBT text) |
| 0x23 | `HORSE_SCREEN_OPEN` | `open_horse_window` | Open horse inventory UI (separate from generic containers) |
| 0x12 | `CONTAINER_CLOSE` | `close_window` | Server closes a container (window ID) |
| 0x13 | `CONTAINER_SET_CONTENT` | `window_items` | Full slot sync for open container; window ID + `stateId` + Slot array + carried item; **Slot type → `format-slot-and-components.md`** |
| 0x15 | `CONTAINER_SET_SLOT` | `set_slot` | Update single slot; window ID + `stateId` + slot index (i16) + Slot; **`format-slot-and-components.md`** |
| 0x14 | `CONTAINER_SET_DATA` | `craft_progress_bar` | Update a container "property" (furnace progress, enchant seed, etc.); window ID + property ID (i16) + value (i16) |
| 0x32 | `OPEN_BOOK` | `open_book` | Open a book UI (hand: main/off) |
| 0x34 | `OPEN_SIGN_EDITOR` | `open_sign_entity` | Open sign editor UI for a position; bool for front/back side |
| 0x37 | `PLACE_GHOST_RECIPE` | `craft_recipe_response` | Show recipe in crafting grid (response to `PLACE_RECIPE` SB) |
| 0x17 | `COOLDOWN` | `set_cooldown` | Start item use cooldown (item ID + ticks) |
| 0x2D | `MERCHANT_OFFERS` | `trade_list` | Villager trade list (window ID + trades array + villager level/XP + regular villager + can restock) |
| 0x2C | `MAP_ITEM_DATA` | `map` | Map item data (map ID + scale + locked + icons + patch data) |
### Serverbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x0F | `CONTAINER_CLOSE` | `close_window` | Client closed a container (window ID) |
| 0x0E | `CONTAINER_CLICK` | `window_click` | Inventory click; window ID + `stateId` + slot (i16) + button + mode + changed slots array + cursor Slot |
| 0x0D | `CONTAINER_BUTTON_CLICK` | `enchant_item` | Click a container button (enchanting table slot select, trade button, etc.) |
| 0x10 | `CONTAINER_SLOT_STATE_CHANGED` | `set_slot_state` | Toggle a slot state (e.g. toggle a crafting grid slot on/off — added 1.20.2) <!-- UNCONFIRMED exact usage --> |
| 0x22 | `PLACE_RECIPE` | `craft_recipe_request` | Request to auto-fill a recipe in crafting grid; window ID + recipe ID + make-all flag |
| 0x32 | `SET_CREATIVE_MODE_SLOT` | `set_creative_slot` | Creative-mode direct slot set; slot (i16) + Slot data |
| 0x29 | `RECIPE_BOOK_SEEN_RECIPE` | `displayed_recipe` | Mark a recipe as seen in the recipe book |
| 0x28 | `RECIPE_BOOK_CHANGE_SETTINGS` | `recipe_book` | Toggle recipe book filter/open state |
| 0x2A | `RENAME_ITEM` | `name_item` | Submit anvil rename string |
| 0x2E | `SET_BEACON` | `set_beacon_effect` | Set beacon effect (primary + secondary effect IDs) |
| 0x2D | `SELECT_TRADE` | `select_trade` | Select villager trade by index |
| 0x14 | `EDIT_BOOK` | `edit_book` | Submit book contents (hand + pages array + optional title) |
---
## 5. Player
Position synchronization, health, XP, abilities, and player list.
### Clientbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x40 | `PLAYER_POSITION` | `position` | **Synchronize player position**: server-authoritative teleport; fields: x/y/z (f64), yaw/pitch (f32), flags (PositionUpdateRelatives bitmask — absolute vs relative per axis), `teleportId` (varint); client must confirm with `ACCEPT_TELEPORTATION`; see field detail below |
| 0x38 | `PLAYER_ABILITIES` | `abilities` | Player ability flags (invulnerable, flying, allow-fly, creative-mode) + fly speed + field-of-view modifier |
| 0x5D | `SET_HEALTH` | `update_health` | Health (f32), food (varint), saturation (f32) |
| 0x5C | `SET_EXPERIENCE` | `experience` | XP bar progress (f32 01) + total XP levels (varint) + total XP points (varint) |
| 0x3E | `PLAYER_INFO_UPDATE` | `player_info` | Add/update player list entries; bitmask of actions (add player, initialize chat, update gamemode, update listed, update latency, update display name) + array of UUID + per-action data |
| 0x3D | `PLAYER_INFO_REMOVE` | `player_remove` | Remove player list entries by UUID array |
| 0x3A | `PLAYER_COMBAT_END` | `end_combat_event` | Player exited combat (duration + entity ID) |
| 0x3B | `PLAYER_COMBAT_ENTER` | `enter_combat_event` | Player entered combat |
| 0x3C | `PLAYER_COMBAT_KILL` | `death_combat_event` | Player killed in combat (player entity ID + death message) |
| 0x39 | `PLAYER_CHAT` | `player_chat` | Signed player chat message; see field detail below |
| 0x09 | `CLIENT_COMMAND` (mismap) | — | See SB below |
| 0x52 | `SET_CAMERA` | `camera` | Set which entity the player's camera follows |
| 0x31 | `MOVE_VEHICLE` | `vehicle_move` | Sync vehicle position to client |
### Serverbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x09 | `CLIENT_COMMAND` | `client_command` | Perform client action: 0 = respawn, 1 = open inventory stats |
| 0x0A | `CLIENT_INFORMATION` | `settings` | Client settings: locale, view distance, chat mode, colors, skin parts, main hand, filter text, allow server listings |
| 0x23 | `PLAYER_ABILITIES` | `abilities` | Client reports flying flag (bit 1); other flags ignored by server |
| 0x2F | `SET_CARRIED_ITEM` | `held_item_slot` | Change held hotbar slot (08) |
| 0x0B | `COMMAND_SUGGESTION` | `tab_complete` | Tab-complete request; `transactionId` (varint) + partial command string |
| 0x13 | `DEBUG_SAMPLE_SUBSCRIPTION` | `debug_sample_subscription` | Subscribe to debug performance samples (tick duration, ping, etc.) |
---
### Detail: PLAYER_POSITION (0x40 CB) — Synchronize Player Position
The server asserts the player's canonical position. Client must respond with `ACCEPT_TELEPORTATION` (0x00 SB) carrying the same `teleportId` before sending movement packets will be trusted again.
Fields (from `protocol.json` `packet_position`):
| Field | Type | Notes |
|-------|------|-------|
| `x` | f64 | |
| `y` | f64 | |
| `z` | f64 | |
| `yaw` | f32 | |
| `pitch` | f32 | |
| `flags` | `PositionUpdateRelatives` (u8 bitmask) | Bit 0=X, 1=Y, 2=Z, 3=pitch, 4=yaw: if set, the value is *relative* to current |
| `teleportId` | varint | Client echoes in `ACCEPT_TELEPORTATION` |
---
## 6. UI / HUD
Chat messages (all three kinds), titles, scoreboard, boss bar, tab list.
### Clientbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x6C | `SYSTEM_CHAT` | `system_chat` | System message (NBT text component + `isActionBar` bool); used for `/say`, `/tell`, server announcements |
| 0x39 | `PLAYER_CHAT` | `player_chat` | Signed player chat; carries sender UUID, message, timestamp, signature (256-byte optional), chat type decoration reference, optional unsigned fallback; see detail below |
| 0x1E | `DISGUISED_CHAT` | `profileless_chat` | System-style chat that mimics a player message without a UUID (no signature); added 1.19.3 |
| 0x1C | `DELETE_CHAT` | `hide_message` | Delete a previously sent signed chat message by signature hash |
| 0x65 | `SET_TITLE_TEXT` | `set_title_text` | Show title (NBT text) |
| 0x63 | `SET_SUBTITLE_TEXT` | `set_title_subtitle` | Show subtitle (NBT text) |
| 0x4C | `SET_ACTION_BAR_TEXT` | `action_bar` | Action bar message (NBT text) |
| 0x66 | `SET_TITLES_ANIMATION` | `set_title_time` | Title timing: fade-in, stay, fade-out (all i32 ticks) |
| 0x0F | `CLEAR_TITLES` | `clear_titles` | Clear title/subtitle/action bar; `bool` reset (resets timing) |
| 0x0A | `BOSS_EVENT` | `boss_bar` | Boss bar operation: ADD, REMOVE, UPDATE_HEALTH, UPDATE_TITLE, UPDATE_STYLE, UPDATE_FLAGS; UUID + per-action data |
| 0x6D | `TAB_LIST` | `playerlist_header` | Tab list header + footer (both NBT text) |
| 0x5E | `SET_OBJECTIVE` | `scoreboard_objective` | Create/remove/update scoreboard objective; name + mode (0=create,1=remove,2=update) + display name + type |
| 0x61 | `SET_SCORE` | `scoreboard_score` | Set an entity's score for an objective; entity name + objective name + score (varint) + optional display name + optional number format |
| 0x44 | `RESET_SCORE` | `reset_score` | Remove a score entry; entity name + optional objective name (1.20.3+) |
| 0x57 | `SET_DISPLAY_OBJECTIVE` | `scoreboard_display_objective` | Set which objective appears in a display slot (sidebar, list, below-name) |
| 0x60 | `SET_PLAYER_TEAM` | `teams` | Scoreboard team operations: CREATE, REMOVE, INFO_UPDATE, ADD_PLAYERS, REMOVE_PLAYERS |
| 0x18 | `CUSTOM_CHAT_COMPLETIONS` | `chat_suggestions` | Add/remove/set chat autocomplete suggestions (1.19.1+) |
| 0x0B | `CHANGE_DIFFICULTY` (mismap) | `difficulty` | See World § above |
| 0x1B | `DEBUG_SAMPLE` | `debug_sample` | Performance debug sample push (tick durations, etc.) |
### Serverbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x06 | `CHAT` | `chat_message` | Player chat message; text + timestamp + salt + optional signature + ack bitset |
| 0x04 | `CHAT_COMMAND` | `chat_command` | Run command (unsigned); command string |
| 0x05 | `CHAT_COMMAND_SIGNED` | `chat_command_signed` | Run command with signed arguments (1.19.1+) |
| 0x07 | `CHAT_SESSION_UPDATE` | `chat_session_update` | Update session signing key (public key + expiry + signature) |
| 0x03 | `CHAT_ACK` | `message_acknowledgement` | Acknowledge received signed chat messages (cumulative offset into message history) |
---
### Detail: PLAYER_CHAT (0x39 CB) — Signed Chat
Fields (`protocol.json` `packet_player_chat`):
| Field | Type | Notes |
|-------|------|-------|
| `senderUuid` | UUID | |
| `index` | varint | Position in sender's message sequence |
| `signature` | optional byte[256] | RSA-signed message hash; absent for unsigned messages |
| `plainMessage` | string | Raw text |
| `timestamp` | i64 | Unix ms |
| `salt` | i64 | |
| `previousMessages` | previousMessages | Compact acknowledgment of prior messages (used for signature chaining) |
| `unsignedChatContent` | optional NBT | Decorated/filtered version if different from plain |
| `filterType` | varint | 0=pass-through, 1=fully-filtered, 2=partially-filtered |
| `filterTypeMask` | conditional | Bitmask when `filterType=2` |
| `type` | ChatTypesHolder | Reference into registry for formatting (chat, say-command, etc.) |
| `networkName` | NBT | Sender's display name |
| `networkTargetName` | optional NBT | Target for directed messages (whisper, /tell) |
---
## 7. Commands / Recipes
The command graph, tab-complete, and recipe synchronization.
### Clientbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x11 | `COMMANDS` | `declare_commands` | Full command graph as a node tree; **`format-command-graph.md`** |
| 0x10 | `COMMAND_SUGGESTIONS` | `tab_complete` | Tab-complete response; `transactionId` + match start + length + array of (text, optional tooltip) |
| 0x77 | `UPDATE_RECIPES` | `declare_recipes` | Full recipe list (crafting, smelting, stonecutting, etc.) |
| 0x41 | `RECIPE` | `unlock_recipes` | Recipe book state: action (init/add/remove) + recipe IDs shown + recipe IDs highlighted + display settings |
| 0x37 | `PLACE_GHOST_RECIPE` | `craft_recipe_response` | (Repeat from Inventory §) Display recipe in crafting grid |
| 0x4A | `SELECT_ADVANCEMENTS_TAB` | `select_advancement_tab` | Open advancement tab (optional identifier) |
| 0x74 | `UPDATE_ADVANCEMENTS` | `advancements` | Advancement data: reset flag + added map + removed IDs + progress map |
### Serverbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x0B | `COMMAND_SUGGESTION` | `tab_complete` | Tab-complete request (see Player §5 SB) |
| 0x22 | `PLACE_RECIPE` | `craft_recipe_request` | (Repeat from Inventory §) |
| 0x28 | `RECIPE_BOOK_CHANGE_SETTINGS` | `recipe_book` | Recipe book toggle |
| 0x29 | `RECIPE_BOOK_SEEN_RECIPE` | `displayed_recipe` | Mark recipe seen |
| 0x2C | `SEEN_ADVANCEMENTS` | `advancement_tab` | Open/close an advancement tab |
---
## 8. Sound / Particle
### Clientbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x68 | `SOUND` | `sound_effect` | Named sound at fixed position (sound ID varint, category, x/y/z as fixed-point i32, volume f32, pitch f32, seed i64) |
| 0x67 | `SOUND_ENTITY` | `entity_sound_effect` | Same but position follows an entity ID |
| 0x6A | `STOP_SOUND` | `stop_sound` | Stop sounds matching a category (optional) and/or sound ID (optional) |
| 0x29 | `LEVEL_PARTICLES` | `world_particles` | Spawn particle effect: particle type + data + position (f64) + offset (f32×3) + max speed + count + long-range flag |
| 0x28 | `LEVEL_EVENT` | `world_event` | (See World §) Also carries many sound/effect IDs |
### Serverbound
_(No SB packets for sound/particle — client never requests specific sounds.)_
---
## 9. Misc
Tags, resource packs, server info, and debug/telemetry.
### Clientbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x78 | `UPDATE_TAGS` | `tags` | Registry tag lists (block, item, fluid, entity type, etc.); map of registry → array of (tag ID + value IDs) |
| 0x46 | `RESOURCE_PACK_PUSH` | `add_resource_pack` | Push a resource pack to client: UUID + URL + hash + forced + optional prompt message (1.20.3: UUID-keyed; 1.20.5: stacks) |
| 0x45 | `RESOURCE_PACK_POP` | `remove_resource_pack` | Remove a previously pushed resource pack by UUID (optional; clear all if absent) |
| 0x4B | `SERVER_DATA` | `server_data` | Server icon (optional PNG bytes) + enforces-secure-chat flag (sent once at login) |
| 0x6E | `TAG_QUERY` | `nbt_query_response` | Response to block/entity NBT query; transaction ID + NBT |
| 0x04 | `AWARD_STATS` | `statistics` | Statistics update (category + statistic + value; varint array) |
| 0x2C | `MAP_ITEM_DATA` | `map` | (See Inventory §) |
| 0x3C | `PLAYER_COMBAT_KILL` | `death_combat_event` | (See Player §) |
| 0x7A | `CUSTOM_REPORT_DETAILS` | `custom_report_details` | Key-value map sent to crash reports (1.20.5+) |
| 0x7B | `SERVER_LINKS` | `server_links` | List of labeled URLs (bug report, feedback, support, etc.) shown in pause menu (1.20.5+) |
### Serverbound
| ID | ViaVersion name | mc-data name | Purpose |
|----|-----------------|--------------|---------|
| 0x2B | `RESOURCE_PACK` | `resource_pack_receive` | Client status for a resource pack UUID: accepted/declined/success/failed |
---
## Cross-links
- **Slot type** (used in container packets): [`format-slot-and-components.md`](format-slot-and-components.md) — Slot encoding, item component NBT, 1.20.5 data-driven component format
- **Entity metadata**: [`format-entity-metadata.md`](format-entity-metadata.md) — per-type metadata keys, value encoding
- **Chunk data + lighting**: [`format-chunk-data.md`](format-chunk-data.md) — section encoding, palette, heightmaps, block entity list, sky/block light arrays
- **Command graph**: [`format-command-graph.md`](format-command-graph.md) — node types, flags, parser IDs, redirect mechanism
- **Version deltas**: [`../versions/1.21.md`](../versions/1.21.md) — what changed 1.20.5→1.21; [`../versions/INDEX.md`](../versions/INDEX.md) — full version map
> **Note**: The `format-*.md` cross-link targets do not exist yet in this directory — they are planned deep-dive docs. Links will resolve when those docs are written.
---
## Version caveat
Play-state packet IDs change **frequently** between versions. Examples of large shifts:
| Version range | Notable shift |
|---|---|
| 1.19 → 1.19.1 | Chat signing added; PLAYER_CHAT restructured |
| 1.20.2 | Configuration state added; START_CONFIGURATION 0x69 is new |
| 1.20.3 | NBT chat components replace string; SET_SCORE/RESET_SCORE split |
| 1.20.5 | Item components (data-driven Slot); cookies; resource-pack UUID stacking; SERVER_LINKS; CUSTOM_REPORT_DETAILS |
| 1.21 (767) | No major new play packets vs 1.20.5; PROJECTILE_POWER (0x79) added |
Always cross-check IDs against `minecraft-data/data/pc/<version>/protocol.json` or the ViaVersion `ClientboundPackets<version>` enum for the target version.
+333
View File
@@ -0,0 +1,333 @@
# Packet deep-dive — Chunk Data wire format & its evolution
The single hardest wire format in the Java protocol. This doc nails the **current (1.18+) "Chunk Data and Update Light"** packet byte-for-byte — including the **paletted container**, the format's crux — then walks the evolution back through 1.17, 1.14, 1.9, and 1.8.
**Cross-links:** [1.8](../versions/1.8.md) · [1.14](../versions/1.14.md) · [1.17](../versions/1.17.md) · [1.18](../versions/1.18.md) · data-type primitives in [01-data-types.md](../01-data-types.md) (NBT §11, BitSet §16, VarInt, Long Array).
**Primary sources** (cited inline as `path:line`):
- minecraft-data `data/pc/{1.8,1.14,1.17,1.18,1.21.1}/protocol.json``packet_map_chunk` / `packet_update_light` defs, diffed for evolution.
- ViaVersion (the authoritative paletted-container + section impl): `api/.../type/types/chunk/ChunkType1_18.java`, `ChunkSectionType1_18.java`, `PaletteType1_18.java`, `ChunkType1_8.java`; `api/.../minecraft/chunks/PaletteType.java`, `ChunkSection.java`; `util/CompactArrayUtil.java`; `api/.../type/types/block/BlockEntityType1_18.java`; `common/.../protocols/v1_17_1to1_18/rewriter/WorldPacketRewriter1_18.java`, `.../storage/ChunkLightStorage.java`.
- minecraft.wiki [Java Edition protocol / Chunk format](https://minecraft.wiki/w/Java_Edition_protocol/Chunk_format) and [Packets](https://minecraft.wiki/w/Java_Edition_protocol/Packets) (fetched 2026-06-19).
| Version | Protocol # | minecraft-data dir | Chunk packet name |
|---|---|---|---|
| 1.8.x | 47 | `1.8` | `packet_map_chunk` (`0x21`) — *light embedded* |
| 1.14.x | 477 | `1.14` | `packet_map_chunk` (`0x22`) + separate `packet_update_light` |
| 1.17.x | 755 | `1.17` | `packet_map_chunk` + `packet_update_light` (masks now Long-array BitSets) |
| 1.18.x | 757 | `1.18` | `LEVEL_CHUNK_WITH_LIGHT` (`0x22`) — *light re-merged* |
| 1.21.1 | 767 | `1.21.1` | `level_chunk_with_light` (`0x27`) — `trustEdges` dropped |
Protocol numbers from `data/pc/<ver>/version.json`.
---
## 1. Current packet — "Chunk Data and Update Light" (1.18+)
Clientbound, **Play** state. Named `LEVEL_CHUNK_WITH_LIGHT` in ViaVersion / `packet_map_chunk` in minecraft-data. Packet ID is version-dependent (1.18: `0x22`; 1.21.1: `0x27``data/pc/1.21.1/protocol.json` `play.toClient.types.packet`).
Three logical blocks in one packet: **(A) Chunk Data**, **(B) Block Entities**, **(C) Light Data**. The 1.18 merge is exactly: take the old `LEVEL_CHUNK` (A + B) and append the old `LIGHT_UPDATE` body (C) with no separator.
### 1.0 Top-level layout
```
LEVEL_CHUNK_WITH_LIGHT (clientbound, Play)
┌──────────────────────────────────────────────────────────────────────────┐
│ Int chunkX │ ── A
│ Int chunkZ │
│ NBT heightmaps (named compound; 1.21.1+ anonymous compound) │
│ VarInt dataSize (byte length of the Data field below) │
│ Byte[dataSize] data = ySectionCount × Chunk Section (see §1.2) │
│ VarInt blockEntityCount │ ── B
│ BlockEntity[] blockEntities (count above; struct in §1.3) │
│ ── light (was the separate LIGHT_UPDATE packet pre-1.18) ── │ ── C
│ Boolean trustEdges (PRESENT 1.181.20; REMOVED 1.21.1+) │
│ BitSet skyLightMask │
│ BitSet blockLightMask │
│ BitSet emptySkyLightMask │
│ BitSet emptyBlockLightMask │
│ VarInt skyLightArrayCount │
│ ByteArray[] skyLightArrays (each: VarInt len(=2048) + 2048 bytes) │
│ VarInt blockLightArrayCount │
│ ByteArray[] blockLightArrays(each: VarInt len(=2048) + 2048 bytes) │
└──────────────────────────────────────────────────────────────────────────┘
```
Field order/types confirmed: `ChunkType1_18.java:48-86` (read/write of chunkX, chunkZ, heightMap, dataSize, sections, blockEntityCount, blockEntities) and `WorldPacketRewriter1_18.java:166-196` (the appended light block). minecraft-data `packet_map_chunk` for 1.18 lists exactly: `x i32, z i32, heightmaps nbt, chunkData buffer(varint), blockEntities array(chunkBlockEntity), trustEdges bool, skyLightMask i64[], blockLightMask i64[], emptySkyLightMask i64[], emptyBlockLightMask i64[], skyLight array(array(u8)), blockLight array(array(u8))`.
> **`trustEdges` lifetime.** 1.18 keeps the `trustEdges` boolean from the old `LIGHT_UPDATE` (`WorldPacketRewriter1_18.java:81,183`; minecraft-data 1.18 `packet_map_chunk`). It was **removed in 1.21.x** — minecraft-data `1.21.1/packet_map_chunk` has no `trustEdges` field. Use the per-version `packet_map_chunk` to be exact.
### 1.1 `ySectionCount` — how many sections in `data`
`data` is a **flat concatenation of Chunk Sections, one per 16-block-tall Y layer, with no count prefix of its own** — the receiver must already know how many to read. The count is the dimension's section count, derived from the dimension-type NBT (`height >> 4`):
| Dimension | World Y | Block height | Sections (`height>>4`) |
|---|---|---|---|
| Overworld (1.18+) | 64 … 319 | 384 | **24** |
| Pre-1.18 overworld | 0 … 255 | 256 | 16 |
| Nether / End | 0 … 255 | 256 | 16 |
ViaVersion reads `height` from the registry NBT in `LOGIN`/`RESPAWN` and stores `height >> 4` as `currentWorldSectionHeight()`, then passes it to `new ChunkType1_18(ySectionCount, …)` so decoding is dimension-aware, **not hardcoded to 24** (`ChunkType1_18.java:41-46,55-59`; tracker plumbing in `versions/1.18.md` §"World height in the dimension type NBT"). Every section is **always serialised**, even empty ones (single-value air palette) — there is no longer a presence bitmask (see §3.4).
> Light arrays index a **larger range than `ySectionCount`**: there are `ySectionCount + 2` light layers (one extra below the world and one above, for edge lighting). ViaVersion's empty-light fallback sets the mask over `currentWorldSectionHeight() + 2` bits (`WorldPacketRewriter1_18.java:173-174`). For the overworld that is **26** light layers around **24** block sections.
### 1.2 Chunk Section (the unit inside `data`)
```
Chunk Section
┌────────────────────────────────────────────────────────────┐
│ Short blockCount (non-air block count) │
│ PalettedContainer blockStates (4096 entries = 16×16×16) │
│ PalettedContainer biomes (64 entries = 4×4×4) ◄ NEW 1.18│
└────────────────────────────────────────────────────────────┘
```
Source: `ChunkSectionType1_18.java:49-63` — reads `Short` non-air count, then the BLOCKS paletted container, then the BIOMES paletted container. Sizes: `ChunkSection.java:32` `SIZE = 16*16*16 = 4096`; `ChunkSection.java:37` `BIOME_SIZE = 4*4*4 = 64`.
- **`blockCount`** — number of non-air blocks; the client uses it to skip rendering / mark a section empty. Air-only sections have `blockCount = 0`.
- **`blockStates`** — paletted container over **4096** entries (one per block in the 16³ section). Entry = a **block-state** global-registry ID, not a block ID. Cell index ordering is **YZX**: `index = (y<<8) | (z<<4) | x` (`ChunkSection.java:39`).
- **`biomes`** — paletted container over **64** entries (4×4×4 sub-grid; one biome per 4³ cell). Entry = a biome registry ID. *This container did not exist before 1.18* — biomes lived in a flat chunk-level array (§3).
<!-- UNCONFIRMED: the latest wiki (1.21.9-era) shows a second `Short fluidCount` field after `blockCount` in the Chunk Section — ViaVersion exposes ChunkSection.getFluidCount() "Available with versions 26.1+" (ChunkSection.java:67-73). For 1.181.21.x there is exactly ONE Short (blockCount) before the block-states container. -->
### 1.3 Block Entity (the array after `data`)
```
VarInt blockEntityCount
repeated:
┌──────────────────────────────────────────────────┐
│ Byte packedXZ = ((x & 15) << 4) | (z & 15) │
│ Short y (absolute world Y, signed i16) │
│ VarInt type (block-entity registry type ID) │
│ NBT data (optional NBT; may be TAG_End) │
└──────────────────────────────────────────────────┘
```
Source: `BlockEntityType1_18.java:38-53` (byte xz, short y, varint typeId, named-compound NBT) and `ChunkType1_18.java:61-65`. minecraft-data top-level type `chunkBlockEntity` for 1.18 decomposes `packedXZ` as a bitfield `{x: u4 (high nibble), z: u4 (low nibble)}`, then `y i16`, `type varint`, `nbtData optionalNbt`. `x`/`z` are **section-local** (015); the section is identified by the packet's `chunkX/chunkZ`. ViaVersion packs it as `(byte)((x & 15) << 4 | (z & 15))` when up-converting (`WorldPacketRewriter1_18.java:127`).
This packed form is **new in 1.18** — pre-1.18 block entities were raw NBT compounds carrying their own `x`/`y`/`z`/`id` fields (§3.3, §4).
### 1.4 Light Data (block C)
Re-merged into this packet in 1.18 (was the standalone `LIGHT_UPDATE` since 1.14). Layout (`WorldPacketRewriter1_18.java:182-196`, write side):
```
Boolean trustEdges (1.181.20 only; gone 1.21.1+)
BitSet skyLightMask \
BitSet blockLightMask | each = VarInt length + that many i64 (longs)
BitSet emptySkyLightMask | → see 01-data-types.md §16 (prefixed BitSet)
BitSet emptyBlockLightMask /
VarInt skyLightArrayCount
repeated skyLightArrayCount×: ByteArray (VarInt length = 2048, then 2048 bytes)
VarInt blockLightArrayCount
repeated blockLightArrayCount×: ByteArray (VarInt length = 2048, then 2048 bytes)
```
- **Each BitSet** is wire-encoded as a **Long Array**: a `VarInt` element-count followed by that many `i64` (little-endian-within-long bit indexing per the BitSet spec). ViaVersion writes them with `Types.LONG_ARRAY_PRIMITIVE` (`WorldPacketRewriter1_18.java:184-187`); minecraft-data types them `array(countType: varint, type: i64)`. The mask bit positions correspond to the `ySectionCount + 2` light layers (bit 0 = the layer below the world).
- **Sky/Block Light masks** mark which light layers are *present* in the following arrays; **Empty** masks mark layers that are explicitly all-zero (so no array is sent for them). A layer's bit set in the mask ⇒ one 2048-byte array follows, in mask-bit order.
- **Each light array is exactly 2048 bytes** = 4096 nibbles = one 4-bit light level per block in a 16³ layer. The wire form is still a length-prefixed byte array (`VarInt 2048` then the bytes — `Types.BYTE_ARRAY_PRIMITIVE`, `WorldPacketRewriter1_18.java:189-194`); minecraft-data types it `array(varint, array(varint, u8))`.
---
## 2. PALETTED CONTAINER — the crux (1.18 impl, format introduced 1.9)
A paletted container compresses a fixed number of entries (4096 for blocks, 64 for biomes) by choosing, per container, the **smallest practical bits-per-entry** and an optional local palette. Authoritative impl: `PaletteType1_18.java:43-129` (read/write); thresholds in `PaletteType.java:25-27`.
```
PalettedContainer
┌──────────────────────────────────────────────────────────────────────┐
│ Unsigned Byte bitsPerEntry │
│ <palette> ← format SELECTED BY bitsPerEntry (see below) │
│ <data> ← Long Array: VarInt length + that many i64 │
└──────────────────────────────────────────────────────────────────────┘
```
`bitsPerEntry` is read as a single byte (`PaletteType1_18.java:45`). The three formats:
### 2.1 Single-valued palette — `bitsPerEntry == 0`
The entire container is one value (e.g. a whole section of air, or a whole biome).
```
Byte 0
VarInt value ← the one global-registry ID for every entry
VarInt dataArrayLen = 0 (empty data array — NO long data)
```
Read: `PaletteType1_18.java:47-53` — on `bitsPerEntry == 0`, read one VarInt id, then `readValues` returns immediately because `bitsPerValue == 0`. Write: lines 92-100 + 116-121 — write byte `0`, the single id, then VarInt `0` for the (empty) data-array length. **Note the empty long array still has its VarInt length prefix (= 0).**
### 2.2 Indirect palette
A local palette lists the global IDs actually used; the data array stores **indices into that local palette**.
```
Byte bitsPerEntry (blocks: 48 ; biomes: 13)
VarInt paletteLength
VarInt[paletteLength] palette ← global IDs; data entries index THIS array
VarInt dataArrayLen
i64[dataArrayLen] data ← packed paletteLength-indices, bitsPerEntry wide
```
Read: `PaletteType1_18.java:62-73` — when `bitsPerValue != globalPaletteBits`, read `paletteLength` then that many VarInt ids into the local palette, then the packed values (interpreted via `setPaletteIndexAt`). Write: lines 105-113.
**Bits-per-entry ranges (the load-bearing numbers):**
| Container | Single | Indirect | Direct (global) |
|---|---|---|---|
| **Block states** | bpe `0` | bpe **48** | bpe **≥ 9** → global |
| **Biomes** | bpe `0` | bpe **13** | bpe **≥ 4** → global |
- The **indirect ceiling** is `PaletteType.highestBitsPerValue()`: **8 for `BLOCKS`**, **3 for `BIOMES`** (`PaletteType.java:26-27``BLOCKS(ChunkSection.SIZE, 8)`, `BIOMES(ChunkSection.BIOME_SIZE, 3)`). Above the ceiling the reader switches to the global/direct palette: `if (bitsPerValue < 0 || bitsPerValue > type.highestBitsPerValue()) bitsPerValue = globalPaletteBits` (`PaletteType1_18.java:55-56`).
- **Block-palette floor = 4 bits.** Linear (indirect) block palettes of 1/2/3 bits "can't be read by the client", so the writer clamps to 4 (`PaletteType1_18.java:57-59,133` and the comment at 132) and the reader bumps any `1 ≤ bpe < 4` up to 4 (`:57-59`). Biomes have **no** floor — they legitimately use 1, 2, 3.
- The wiki gives the same ranges from the client's view: blocks "Indirect (BPE 4-8)", "Direct (BPE ≥15)"; biomes "Indirect (1-3)", "Direct (≥7)" ([Chunk format](https://minecraft.wiki/w/Java_Edition_protocol/Chunk_format), fetched 2026-06-19). The wiki's "≥15"/"≥7" are the **actual global-palette bit widths** vanilla emits (≈`ceil(log2(registry_size))`), whereas ViaVersion's threshold is "anything above the indirect ceiling (8 / 3) ⇒ use `globalPaletteBits`". Both describe the same switch: indirect tops out at 8 (blocks) / 3 (biomes); everything wider is direct.
### 2.3 Direct palette — `bitsPerEntry == globalPaletteBits`
No local palette. Data entries are **global-registry IDs packed directly**.
```
Byte bitsPerEntry = globalPaletteBits (blocks ~15, biomes ~6/7)
VarInt dataArrayLen
i64[dataArrayLen] data ← packed global IDs, bitsPerEntry wide
```
Read: `PaletteType1_18.java:68-70` — when `bitsPerValue == globalPaletteBits`, **skip the palette** and read values straight via `setIdAt`. `globalPaletteBits` is passed into the type at construction. ViaVersion computes it as `ceilLog2(blockStateMappings.mappedSize())` for blocks and `ceilLog2(tracker.biomesSent())` for biomes (`WorldPacketRewriter1_18.java:159-161`) — i.e. enough bits to index the whole registry.
### 2.4 Data-array bit packing — **no entry spans two longs**
This is the detail people get wrong. In the **modern (1.16+) format** entries are packed so that **each entry stays inside one 64-bit long**; leftover high bits at the top of each long are **padding** (zero), not the start of the next entry.
```
valuesPerLong = floor(64 / bitsPerEntry) ← truncating division
longCount = ceil(entries / valuesPerLong) ← entries = 4096 (blocks) or 64 (biomes)
within a long: entry0 in the LOW bits, then entry1 above it, …; top (64 mod bpe) bits = padding
```
Source: `CompactArrayUtil.createCompactArrayWithPadding:55-73` (write) and `iterateCompactArrayWithPadding:75-89` (read) — `valuesPerLong = (char)(64 / bitsPerEntry)`, `size = (entries + valuesPerLong - 1) / valuesPerLong`, each value shifted by `bitIndex += bitsPerEntry` and the loop bounded by `min(i + valuesPerLong, entries)` so it never crosses into the next long. `PaletteType1_18.readValues:77-89` computes the *expected* long count the same way and only iterates if `values.length == expectedLength`. The wiki states it identically: entries "cannot span across multiple longs; instead, padding is inserted… starting from the most significant bits", "tightly packed within the long, with the first entry on the least significant bits".
> **Contrast — the OLD tightly-packed format (pre-1.16).** `CompactArrayUtil.createCompactArray:91-107` / `iterateCompactArray:109-125` pack entries with **no padding**, so an entry **can straddle a long boundary** (`startIndex != endIndex` branch at `:101-104` / `:119-122`). 1.91.15 used this; 1.16 switched to the padded form above. A decoder must pick the packing by version — same palette framing, different long math.
**Worked sizes** (modern padded format):
- Blocks, bpe = 4: `valuesPerLong = 16`, `longCount = ceil(4096/16) = 256` longs.
- Blocks, bpe = 8: `valuesPerLong = 8`, `longCount = ceil(4096/8) = 512` longs.
- Blocks, bpe = 15 (direct): `valuesPerLong = 4`, `longCount = ceil(4096/4) = 1024` longs.
- Biomes, bpe = 1: `valuesPerLong = 64`, `longCount = ceil(64/64) = 1` long.
- Biomes, bpe = 3: `valuesPerLong = 21`, `longCount = ceil(64/21) = 4` longs.
---
## 3. Evolution — what changed at each step
Diff source: `data/pc/<ver>/protocol.json` `packet_map_chunk` (+ `packet_update_light`). The five steps below are the structural inflection points.
### 3.1 — 1.8 (proto 47): per-section palette is born; presence bitmask; light embedded
`packet_map_chunk` (1.8) = `{ x i32, z i32, groundUp bool, bitMap u16, chunkData buffer(varint) }`. Inside `chunkData` (decoded by `ChunkType1_8.deserialize:88-124`):
```
for each of 16 sections, IF (bitMap >> i) & 1: block data (per-section palette)
for each present section: block light (2048 bytes)
if NORMAL dimension, for each present section: sky light (2048 bytes)
if groundUp (full chunk): biome array = 256 bytes (one per X,Z column)
```
Key facts of this era:
- **Per-section block palette introduced** (the ancestor of today's paletted container) — sections carry their own palette + packed data.
- **`bitMap` (u16) selects which of the 16 sections are present** (`ChunkType1_8.read:58`, decode loop `:95-98`). `groundUp`/full-chunk (`:57`) means "replace the whole column, biomes included".
- **Light is INSIDE the chunk packet**, interleaved per section, block-light then sky-light (`deserialize:100-112`). There is no separate light packet yet.
- **Biomes = a flat 256-byte array** (one biome byte per horizontal column, 2-D), only on full chunks (`:115-120`). No per-section, no 3-D biomes.
- No heightmaps; no block-entity array (block entities came via separate `update_block_entity` packets / chunk-bulk).
Source: `ChunkType1_8.java:53-160`; minecraft-data `1.8/packet_map_chunk` (`groundUp bool`, `bitMap u16`).
Cross-link: [versions/1.8.md](../versions/1.8.md).
### 3.2 — 1.9 (proto 107+): block-entity NBT array appended
1.9.3+ appends a **block-entities array** to the chunk: count + that many full **NBT compounds**, each self-describing via its own `x`/`y`/`z`/`id` tags. This is why pre-1.18 the block entity is "just NBT" (contrast §1.3's packed struct). The 1.9 line also moved block IDs to the global block-state registry that the palette indexes.
(minecraft-data carries the array on `packet_map_chunk` from 1.14 onward in the dirs we diffed; the 1.9-era addition is documented in [versions/1.9.md](../versions/1.9.md). The flat-packed long arithmetic of this era is the `createCompactArray`/`iterateCompactArray` "entries CAN span longs" form — see §2.4.)
### 3.3 — 1.14 (proto 477): light SPLIT OUT; heightmaps NBT added
`packet_map_chunk` (1.14) = `{ x i32, z i32, groundUp bool, bitMap varint, heightmaps nbt, chunkData buffer(varint), blockEntities array(nbt) }`. New `packet_update_light` = `{ chunkX varint, chunkZ varint, skyLightMask varint, blockLightMask varint, emptySkyLightMask varint, emptyBlockLightMask varint, data restBuffer }`.
Changes vs 1.8/1.9:
- **Light removed from the chunk packet** and moved to a **separate `Update Light` packet** (client now receives chunk and light as two packets). The chunk packet no longer carries the per-section light arrays.
- **`heightmaps` NBT added** to the chunk (`MOTION_BLOCKING`, `WORLD_SURFACE`, etc. — packed long arrays inside an NBT compound), supporting the new lighting engine.
- `bitMap` widened **u16 → VarInt** (still a section-presence bitmask).
- Block entities are an explicit `array(nbt)` field on the packet (full NBT each).
- In 1.14 the `update_light` masks are still **single VarInts** (≤32 sections fit in 32 bits).
Source: minecraft-data `1.14/packet_map_chunk` + `1.14/packet_update_light`.
Cross-link: [versions/1.14.md](../versions/1.14.md).
### 3.4 — 1.17 (proto 755): full-chunks-only; masks become Long-array BitSets; biomes go 3-D-flat
`packet_map_chunk` (1.17) = `{ x i32, z i32, bitMap i64[], heightmaps nbt, biomes array(varint), chunkData buffer(varint), blockEntities array(nbt) }`. `packet_update_light` (1.17) = `{ chunkX, chunkZ, trustEdges bool, skyLightMask i64[], blockLightMask i64[], emptySkyLightMask i64[], emptyBlockLightMask i64[], skyLight array(array(u8)), blockLight array(array(u8)) }`.
Changes vs 1.14:
- **`groundUp` / partial-chunk dropped** — 1.17 only sends **full chunks**; there is no longer a "groundUp" boolean (the field is gone from `packet_map_chunk`). The world-height growth to come needed a clean full-column model.
- **`bitMap` became a Long-array BitSet** (`array(varint, i64)`) instead of a VarInt, because section counts could exceed 32. The light masks in `update_light` likewise became Long-array BitSets, and `trustEdges` + explicit `skyLight`/`blockLight` array-of-arrays appeared (the form §1.4 later inherits).
- **Biomes promoted to a chunk-level `VarInt[]`** = 1024 entries (4×4×4 per section × 16 sections) — full 3-D biomes, but still a *flat array at chunk level*, not per-section. ViaVersion reads this flat array and re-slices it into per-section palettes when up-converting to 1.18 (`WorldPacketRewriter1_18.java:131,146-155`).
- World height **still 0…255 (16 sections)**.
Source: minecraft-data `1.17/packet_map_chunk` + `1.17/packet_update_light`; `ChunkType1_17` (read) referenced from `WorldPacketRewriter1_18.java:107`.
Cross-link: [versions/1.17.md](../versions/1.17.md).
### 3.5 — 1.18 (proto 757): height 64…319 (24 sections); biomes → per-section container; light RE-MERGED
`packet_map_chunk` (1.18) drops `bitMap` and the chunk-level `biomes` array, and **appends the whole `update_light` body**: `{ x i32, z i32, heightmaps nbt, chunkData buffer(varint), blockEntities array(chunkBlockEntity), trustEdges bool, skyLightMask i64[], blockLightMask i64[], emptySkyLightMask i64[], emptyBlockLightMask i64[], skyLight array(array(u8)), blockLight array(array(u8)) }`.
Changes vs 1.17 (the heavy ones):
- **World height 64…319 ⇒ 24 sections** (was 16). Section count is dimension-driven (`height >> 4`), not constant (§1.1).
- **Section presence bitmask REMOVED** — *every* section is serialised, empty ones using the single-value air palette. ViaVersion synthesises an air section for previously-absent ones when up-converting: `new ChunkSectionImpl()` + a single-id palette of `0` (`WorldPacketRewriter1_18.java:135-144`).
- **Biomes moved INTO each section as a paletted container** (4×4×4, §1.2/§2). The flat chunk-level `biomes VarInt[]` is gone; ViaVersion fills the per-section biome palettes from the old flat array (`WorldPacketRewriter1_18.java:146-155`).
- **Light RE-MERGED** into "Chunk Data and Update Light" — the separate `LIGHT_UPDATE` body is appended directly (§1.4). Proxies bridging 1.17↔1.18 must buffer one packet until the other arrives (`ChunkLightStorage.java`; `WorldPacketRewriter1_18.java:66-103,163-196`).
- **Block entities packed** into `chunkBlockEntity` structs (`packedXZ`/`y`/`type`/`optNBT`) instead of full self-describing NBT (§1.3).
Source: minecraft-data `1.17` vs `1.18` `packet_map_chunk`; ViaVersion `ChunkType1_18.java`, `ChunkSectionType1_18.java`, `WorldPacketRewriter1_18.java`. Full prose in [versions/1.18.md](../versions/1.18.md) §"Chunk packet restructure".
### 3.6 — after 1.18 (context)
- **1.21.x**: `trustEdges` removed from the chunk packet (minecraft-data `1.21.1/packet_map_chunk` has no `trustEdges`); packet ID shifted to `0x27`. heightmaps switched to **anonymous** (network/unnamed) NBT (`anonymousNbt`).
- **1.16**: the data-array packing changed from "entries-can-span-longs" to the **padded "no entry spans two longs"** form (§2.4) — a silent but decoder-breaking change with no field-list delta.
- **26.1+**: a second `Short fluidCount` is added to each Chunk Section (ViaVersion `ChunkSection.getFluidCount` "Available with versions 26.1+", `ChunkSection.java:67-73`). <!-- UNCONFIRMED for exact protocol/version number; flagged in §1.2 -->
---
## 4. Quick comparison matrix
| Aspect | 1.8 (47) | 1.14 (477) | 1.17 (755) | 1.18+ (757) |
|---|---|---|---|---|
| Sections in `data` | 16, **bitmask-selected** | 16, bitmask-selected | 16, full-chunk only | **24** (dim-driven), all present |
| Section presence | `bitMap` u16 | `bitMap` VarInt | `bitMap` i64[] BitSet | **no mask** (air palette) |
| Block palette | per-section (spanning longs) | per-section | per-section | per-section, **padded longs** (§2.4) |
| Biomes | 256-byte 2-D array | 256-byte (full chunk) | **flat VarInt[1024]** 3-D | **per-section paletted container** |
| Heightmaps | none | **NBT added** | NBT | NBT (anon NBT in 1.21+) |
| Block entities | none (separate pkts) | **NBT array** | NBT array | **packed `chunkBlockEntity`** |
| Light | **embedded in chunk** | **separate `Update Light`** | separate `Update Light` | **re-merged** into chunk pkt |
| Light masks | n/a (interleaved) | single VarInt | i64[] BitSet | i64[] BitSet |
---
## 5. Paletted-container cheat-sheet (decoder pseudocode, 1.18+)
```
read_paletted_container(buf, entries, indirectCeiling, globalBits): # blocks: 4096,8,~15 biomes: 64,3,~6
bpe = buf.read_u8()
if bpe == 0: # single-valued
value = buf.read_varint()
_len = buf.read_varint() # == 0, empty data array
return uniform(entries, value)
if bpe > indirectCeiling: # direct / global
bpe = globalBits
palette = None # data holds global IDs
else: # indirect
if blocks and bpe < 4: bpe = 4 # block-palette floor (biomes: no floor)
n = buf.read_varint(); palette = [buf.read_varint() for _ in range(n)]
longs = buf.read_varint(); data = [buf.read_i64() for _ in range(longs)]
# unpack: valuesPerLong = 64 // bpe ; entry i is in long (i // valuesPerLong),
# at bit ((i % valuesPerLong) * bpe), masked to bpe bits. NO entry spans two longs.
return [ palette[idx] if palette else idx for idx in unpack(data, bpe, entries) ]
```
Matches `PaletteType1_18.read:43-89` + `CompactArrayUtil.iterateCompactArrayWithPadding:75-89` exactly.
+442
View File
@@ -0,0 +1,442 @@
# Commands (Declare Commands) Packet
Clientbound · Play state
Added: **1.13** (protocol 393)
Sends the server's Brigadier command graph to the client so it can populate tab-completion and validate syntax client-side before sending the command.
Packet IDs by version:
| Version | Packet ID |
|---------|-----------|
| 1.13 | `0x11` |
| 1.19 | `0x0F` |
| 1.21.1 | `0x11` |
Sources:
- `minecraft-data/data/pc/1.13/protocol.json` — top-level `types.command_node` + `play.toClient` packet list
- `minecraft-data/data/pc/1.19/protocol.json` — same, post-VarInt-parser change
- `minecraft-data/data/pc/1.21.1/protocol.json` — same, 1.21.1 parser table
- `ViaVersion/common/src/main/java/com/viaversion/viaversion/rewriter/CommandRewriter.java``registerDeclareCommands` (String era) and `handle1_19` / `registerDeclareCommands1_19` (VarInt era)
- `ViaVersion/…/protocols/v1_18_2to1_19/Protocol1_18_2To1_19.java` lines 178210 — transition handler (reads `Types.STRING`, writes `Types.VAR_INT`)
See also: [../versions/1.13.md](../versions/1.13.md) · [../versions/1.19.md](../versions/1.19.md)
---
## Packet Layout
```
Count VarInt number of nodes in the flat node array
Nodes[] command_node[] one record per node (see below)
Root Index VarInt index into Nodes[] of the root node
```
The node array is **flat** — nodes reference each other by index, not by nesting. The root node is always type 0 (root); its index is given by `Root Index` at the end of the packet.
---
## Node Record (`command_node`)
Fields are conditional on the `flags` byte:
```
Flags byte (bitfield)
Children VarInt count, then VarInt[] of child node indices
Redirect Node VarInt — only if flags bit 3 (0x08) set
Name String — only for literal (type 1) and argument (type 2) nodes
Parser ID String|VarInt — only for argument nodes (type 2); encoding changed in 1.19
Properties (variable) — only for argument nodes; depends on Parser ID
Suggestions String/Identifier — only if flags bit 4 (0x10) set
```
### Flags Byte (bitfield)
The byte is parsed from **LSB to MSB**:
```
Bit(s) Mask Field
------ ----- -------------------------------------------
01 0x03 node type: 0 = root, 1 = literal, 2 = argument
2 0x04 is_executable — this node itself is a valid end-of-input
3 0x08 has_redirect — a Redirect Node index follows Children
4 0x10 has_suggestions — a Suggestions Type identifier follows Properties
(argument nodes only; ignored on other types)
57 0xE0 unused
```
Note: `minecraft-data` names these `command_node_type` (bits 01), `has_command` (bit 2), `has_redirect_node` (bit 3), `has_custom_suggestions` (bit 4).
### Children
```
ChildCount VarInt number of children
Children[] VarInt[] indices into the flat node array
```
### Redirect Node
Present only when `flags & 0x08 != 0`. A single VarInt index into the flat node array. Used for command aliases — e.g. `/tp` and `/teleport` can share the same subgraph via redirect.
### Name
Present for node types **literal** (1) and **argument** (2). Not present for root (0). A length-prefixed UTF-8 String (max 32767 chars per the standard String encoding).
### Parser ID + Properties
Present only for argument nodes (type 2). The parser encoding **changed in 1.19**.
---
## Parser Encoding: String (1.13) vs VarInt (1.19+)
### Pre-1.19 (1.13 through 1.18.x)
Parser is a plain **String** (identifier, e.g. `"brigadier:string"`, `"minecraft:entity"`).
```
Parser String e.g. "brigadier:integer"
```
Source: `minecraft-data/data/pc/1.13/protocol.json` `types.command_node` extraNodeData type=2 → `parser: "string"`.
ViaVersion `CommandRewriter.registerDeclareCommands()` reads `Types.STRING` for the parser name.
### 1.19+ (protocol 759 onwards)
Parser is a **VarInt** registry index (a compact enum of all known parser names).
```
Parser VarInt e.g. 5 = "brigadier:string"
```
Source: `minecraft-data/data/pc/1.19/protocol.json` `types.command_node` extraNodeData type=2 → `parser: ["mapper", {"type": "varint", ...}]`.
ViaVersion `Protocol1_18_2To1_19.java` lines 194200: reads `Types.STRING`, looks up `MAPPINGS.getArgumentTypeMappings().mappedId(argumentType)`, writes `Types.VAR_INT`.
ViaVersion `CommandRewriter.handle1_19()` line 131: reads `Types.VAR_INT` and resolves it to a name via `argumentType(argumentTypeId)`.
The property format **did not change** — only the parser identifier encoding changed. Properties are still parsed by the same per-parser logic after resolving the VarInt to a name.
---
## Common Parsers and Their Properties
All parsers with no listed properties send nothing (`void`).
### Brigadier parsers (no namespace qualifier needed for these 6)
| Parser name | VarInt ID (1.19+) | Properties |
|---------------------|:-----------------:|------------|
| `brigadier:bool` | 0 | none |
| `brigadier:float` | 1 | flags byte (bit 0 = min present, bit 1 = max present), then optional f32 min, optional f32 max |
| `brigadier:double` | 2 | flags byte (bit 0 = min present, bit 1 = max present), then optional f64 min, optional f64 max |
| `brigadier:integer` | 3 | flags byte (bit 0 = min present, bit 1 = max present), then optional i32 min, optional i32 max |
| `brigadier:long` | 4 | flags byte (bit 0 = min present, bit 1 = max present), then optional i64 min, optional i64 max |
| `brigadier:string` | 5 | VarInt mode: 0 = SINGLE_WORD, 1 = QUOTABLE_PHRASE, 2 = GREEDY_PHRASE |
Numeric range flags detail (same pattern for float/double/integer/long):
```
Bit 0 (0x01) — minimum bound present → read min value
Bit 1 (0x02) — maximum bound present → read max value
Bits 27 — unused
```
### Minecraft parsers (selected; full table below)
| Parser name | VarInt ID (1.19+) | Properties |
|------------------------------|:-----------------:|------------|
| `minecraft:entity` | 6 | flags byte: bit 0 (0x01) = only allow entities (not players), bit 1 (0x02) = only allow players |
| `minecraft:game_profile` | 7 | none |
| `minecraft:block_pos` | 8 | none |
| `minecraft:column_pos` | 9 | none |
| `minecraft:vec3` | 10 | none |
| `minecraft:vec2` | 11 | none |
| `minecraft:block_state` | 12 | none |
| `minecraft:block_predicate` | 13 | none |
| `minecraft:item_stack` | 14 | none |
| `minecraft:item_predicate` | 15 | none |
| `minecraft:color` | 16 | none |
| `minecraft:component` | 17 | none |
| `minecraft:message` | 18 (1.19) / 19 (1.21.1) | none |
| `minecraft:nbt` | 19 (1.19) / 20 (1.21.1) | none |
| `minecraft:nbt_path` | 21 (1.19) / 22 (1.21.1) | none |
| `minecraft:objective` | 22 (1.19) / 23 (1.21.1) | none |
| `minecraft:scoreboard_slot` | 28 (1.19) / 29 (1.21.1) | none |
| `minecraft:score_holder` | 29 (1.19) / 30 (1.21.1) | flags byte: bit 0 (0x01) = allow multiple |
| `minecraft:swizzle` | 30 (1.19) | none |
| `minecraft:team` | 31 (1.19) | none |
| `minecraft:resource_location`| 33 (1.19) | none |
| `minecraft:resource_or_tag` | 43 (1.19) | String registry name |
| `minecraft:resource` | 44 (1.19) | String registry name |
| `minecraft:uuid` | 47 (1.19) / 53 (1.21.1) | none |
Note: IDs shift across versions as new parsers are inserted. The VarInt ID is version-specific. Use the `minecraft-data` protocol.json for the authoritative table for a given protocol version.
#### `minecraft:entity` flags detail
```
Bit 0 (0x01) — onlyAllowEntities (non-player entities only)
Bit 1 (0x02) — onlyAllowPlayers
Bits 27 — unused
```
Both bits 0 and 1 can be 0 simultaneously (allow any selector).
#### `minecraft:score_holder` flags detail
```
Bit 0 (0x01) — allowMultiple (accept `*` and selectors matching multiple holders)
Bits 17 — unused
```
### Full 1.19 VarInt Parser Table
```
0 brigadier:bool
1 brigadier:float
2 brigadier:double
3 brigadier:integer
4 brigadier:long
5 brigadier:string
6 minecraft:entity
7 minecraft:game_profile
8 minecraft:block_pos
9 minecraft:column_pos
10 minecraft:vec3
11 minecraft:vec2
12 minecraft:block_state
13 minecraft:block_predicate
14 minecraft:item_stack
15 minecraft:item_predicate
16 minecraft:color
17 minecraft:component
18 minecraft:message
19 minecraft:nbt
20 minecraft:nbt_tag
21 minecraft:nbt_path
22 minecraft:objective
23 minecraft:objective_criteria
24 minecraft:operation
25 minecraft:particle
26 minecraft:angle
27 minecraft:rotation
28 minecraft:scoreboard_slot
29 minecraft:score_holder
30 minecraft:swizzle
31 minecraft:team
32 minecraft:item_slot
33 minecraft:resource_location
34 minecraft:mob_effect
35 minecraft:function
36 minecraft:entity_anchor
37 minecraft:int_range
38 minecraft:float_range
39 minecraft:item_enchantment
40 minecraft:entity_summon
41 minecraft:dimension
42 minecraft:time
43 minecraft:resource_or_tag
44 minecraft:resource
45 minecraft:template_mirror
46 minecraft:template_rotation
47 minecraft:uuid
```
Source: `minecraft-data/data/pc/1.19/protocol.json` `types.command_node` parser mapper mappings.
### Full 1.21.1 VarInt Parser Table (additions highlighted)
```
0 brigadier:bool
1 brigadier:float
2 brigadier:double
3 brigadier:integer
4 brigadier:long
5 brigadier:string
6 minecraft:entity
7 minecraft:game_profile
8 minecraft:block_pos
9 minecraft:column_pos
10 minecraft:vec3
11 minecraft:vec2
12 minecraft:block_state
13 minecraft:block_predicate
14 minecraft:item_stack
15 minecraft:item_predicate
16 minecraft:color
17 minecraft:component
18 minecraft:style ← new in 1.21.x era
19 minecraft:message
20 minecraft:nbt
21 minecraft:nbt_tag
22 minecraft:nbt_path
23 minecraft:objective
24 minecraft:objective_criteria
25 minecraft:operation
26 minecraft:particle
27 minecraft:angle
28 minecraft:rotation
29 minecraft:scoreboard_slot
30 minecraft:score_holder
31 minecraft:swizzle
32 minecraft:team
33 minecraft:item_slot
34 minecraft:item_slots ← new
35 minecraft:resource_location
36 minecraft:function
37 minecraft:entity_anchor
38 minecraft:int_range
39 minecraft:float_range
40 minecraft:dimension
41 minecraft:gamemode ← new
42 minecraft:time
43 minecraft:resource_or_tag
44 minecraft:resource_or_tag_key ← new
45 minecraft:resource
46 minecraft:resource_key ← new
47 minecraft:template_mirror
48 minecraft:template_rotation
49 minecraft:heightmap ← new
50 minecraft:loot_table ← new
51 minecraft:loot_predicate ← new
52 minecraft:loot_modifier ← new
53 minecraft:uuid
```
Source: `minecraft-data/data/pc/1.21.1/protocol.json` `types.command_node` parser mapper mappings.
---
## Suggestions Type
When flags bit 4 (`0x10`) is set, an extra field follows Properties:
```
Suggestions String an Identifier naming the suggestion provider
```
Known values:
- `minecraft:ask_server` — client sends a Tab-Complete request to the server for this argument
- `minecraft:all_recipes` — client fills from the known recipe list
- `minecraft:available_sounds` — client fills from the sound registry
- `minecraft:summonable_entities` — client fills from entity type registry
<!-- UNCONFIRMED: whether additional built-in suggestion providers exist beyond these four; wiki.vg lists these as the documented set -->
---
## Example: `/teleport <target>`
A minimal `/teleport <target>` command with one argument. Three nodes total:
```
Node 0: type=root, executable=false, children=[1]
Node 1: type=literal, executable=false, name="teleport", children=[2]
Node 2: type=argument, executable=true, name="target",
parser="minecraft:entity" (VarInt 6 in 1.19+),
properties=flags(0x02 = onlyAllowPlayers),
children=[]
Root Index = 0
```
Wire layout (hex, showing field-by-field):
```
Count 03 (3 nodes)
Node 0:
flags 00 (type=root, not executable, no redirect)
childCount 01 (1 child)
children 01 (index 1)
[no name — root node]
Node 1:
flags 04 (type=literal 0x01 | executable=false; wait — not executable here, so 0x01)
-- actually flags = 0x01 (type=literal, not executable)
childCount 01
children 02
name 08 74 65 6c 65 70 6f 72 74 ("teleport", length-prefixed string)
Node 2:
flags 06 (type=argument 0x02 | is_executable 0x04)
childCount 00
name 06 74 61 72 67 65 74 ("target")
parser 06 (VarInt 6 = minecraft:entity, in 1.19+)
properties 02 (onlyAllowPlayers flag)
Root Index 00
```
ASCII graph:
```
[ROOT node=0]
|
v
[LITERAL "teleport" node=1]
|
v
[ARGUMENT "target" node=2] parser=minecraft:entity executable
```
---
## Redirect Example
`/tp` can alias `/teleport` by using a redirect:
```
Node 3: type=literal, executable=false, flags=0x09 (literal | has_redirect),
name="tp", children=[], redirect=1
```
`flags = 0x01 | 0x08 = 0x09`. The redirect points to node 1 (the `teleport` literal), so the client treats `/tp` as if it navigated into the `teleport` subgraph.
---
## Node-Record Pseudocode
```python
def read_command_node(buf):
flags = buf.read_byte()
node_type = flags & 0x03 # 0=root, 1=literal, 2=argument
is_executable = bool(flags & 0x04)
has_redirect = bool(flags & 0x08)
has_suggestions = bool(flags & 0x10)
child_count = buf.read_varint()
children = [buf.read_varint() for _ in range(child_count)]
redirect = buf.read_varint() if has_redirect else None
name = None
if node_type in (1, 2): # literal or argument
name = buf.read_string()
parser = None
properties = None
if node_type == 2: # argument
# Pre-1.19: parser = buf.read_string()
# 1.19+: parser = PARSER_TABLE[buf.read_varint()]
parser = read_parser_id(buf)
properties = read_parser_properties(buf, parser)
suggestions = None
if has_suggestions: # argument nodes only in practice
suggestions = buf.read_string() # identifier
return CommandNode(node_type, is_executable, children, redirect,
name, parser, properties, suggestions)
```
---
## Version History
| Version | Protocol | Change |
|------------|----------|--------|
| 1.13 | 393 | Packet added; parser = String identifier |
| 1.14 | 477 | New argument parsers added (e.g. `minecraft:uuid`, `minecraft:nbt_compound_tag`) |
| 1.19 | 759 | Parser identifier changed from String to VarInt registry index; new parsers added |
| 1.19.3 | 761 | Additional parser entries added |
| 1.21.1 | 767 | `minecraft:style`, `minecraft:item_slots`, `minecraft:gamemode`, `minecraft:resource_or_tag_key`, `minecraft:resource_key`, `minecraft:heightmap`, `minecraft:loot_*` parsers added |
The `minecraft-data` `types.command_node.extraNodeData[type=2].parser` field is `"string"` for 1.131.18, and `["mapper", {"type": "varint", ...}]` for 1.19+.
ViaVersion `Protocol1_18_2To1_19.java:194200` is the canonical protocol transition point: it reads `Types.STRING` from the 1.18 upstream and writes `Types.VAR_INT` to the 1.19 client.
<!-- UNCONFIRMED: whether 1.13-pre versions had a slightly different flag layout before 1.13 release; only release 1.13 data confirmed via minecraft-data -->
+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`.
+297
View File
@@ -0,0 +1,297 @@
# Slot (Item Stack) Wire Format and Component Evolution
> Cross-references: [`../01-data-types.md`](../01-data-types.md) · [`../versions/1.13.md`](../versions/1.13.md) · [`../versions/1.20.md`](../versions/1.20.md)
A **Slot** is the on-wire representation of an item stack. It appears in dozens of
play-phase packets (Set Slot, Window Items, Creative Inventory Action, Entity
Equipment, …). Its layout has changed three times at major protocol boundaries.
---
## Era 1 — Pre-1.13 (≤ protocol 340)
**Source:** `minecraft-data/data/pc/1.12.2/protocol.json``types.slot`; `ViaVersion` `ItemType1_8.java`
```
Slot {
blockId : i16 // item numeric ID; -1 = empty slot
if blockId != -1 {
itemCount : i8 // stack size (164)
itemDamage : i16 // damage / metadata value
nbtData : NBT // TAG_Compound or 0x00 byte (no tag)
}
}
```
### Notes
- `blockId` was the numeric item registry ID assigned by Mojang and exposed via
`ids.json`. Negative values (`blockId < 0`, conventionally `-1`) signal an
empty slot.
- `itemDamage` (also called *metadata* or *aux*) served double duty:
- For tools/weapons: the amount of durability consumed.
- For items like wool, dye, or spawn eggs: a sub-type discriminator
(e.g. wool damage=14 → red wool).
- Together, `(blockId, itemDamage)` formed the full **item identity**.
- `nbtData` is a **named compound tag** serialised directly into the packet
without a length prefix. The "named" form means the stream contains:
`0x0A` (TAG_Compound type byte) · UTF-16BE length-prefixed name (empty string
`0x00 0x00` for anonymous root) · tag payload.
A leading `0x00` (TAG_End type byte) signals no tag.
This is **uncompressed** network NBT — not gzip-wrapped.
The `optionalNbt` minecraft-data abstract type maps to this idiom; its value
is `"native"` meaning the codec is supplied by the runtime
(`NamedCompoundTagType`, ViaVersion `api/.../misc/NamedCompoundTagType.java:62`).
---
## Era 2 — 1.13 / The Flattening (protocol 393)
**Source:** `minecraft-data/data/pc/1.13/protocol.json``types.slot`; `ViaVersion` `ItemType1_13.java`
```
Slot {
itemId : i16 // flat numeric ID; -1 = empty
if itemId != -1 {
itemCount : i8
nbtData : NBT // same named-compound encoding as pre-1.13
}
}
```
### What changed
- **The Flattening** (MC-Java 1.13) merged the `(id, damage)` identity pair into
a single flat item ID. Each old `(blockId, damage)` pair that was a distinct
item became its own registry entry (e.g. the 16 wool colours became 16 separate
item IDs). The `itemDamage` field was **removed from the Slot wire format**.
- The sentinel is still `i16 == -1` (not a boolean).
- NBT encoding unchanged.
> **ViaVersion evidence** — `ItemType1_13.java:39`: `short id = buffer.readShort();`
> then `item.setAmount(buffer.readByte())` and `Types.NAMED_COMPOUND_TAG.read(buffer)`.
> No damage field read.
---
## Era 3 — 1.13.2 → 1.20.4 (protocols 404 765)
**Source:** `minecraft-data/data/pc/1.13.2/protocol.json``types.slot`; `ViaVersion` `ItemType1_13_2.java`, `ItemType1_20_2.java`
```
Slot {
present : bool // false = empty slot
if present {
itemId : VarInt // flat numeric ID
itemCount : i8 // stack size
nbtData : NBT // named compound tag or 0x00
}
}
```
### What changed
- The empty-slot sentinel flipped from `i16 == -1` to a dedicated **boolean**
`present` byte (`0x00` / `0x01`).
- `itemId` widened from `i16` to **VarInt** in the same change (1.13.2-pre1).
- `itemCount` remained `i8` throughout this era.
- 1.20.2 (protocol 764) switched the `nbtData` from *named* to *anonymous* NBT
(no name prefix in the stream; `Types.COMPOUND_TAG` vs `Types.NAMED_COMPOUND_TAG`
in ViaVersion `ItemType1_20_2.java:47`) — but the field is otherwise identical.
> **ViaVersion evidence** — `ItemType1_13_2.java:39`: `boolean present = buffer.readBoolean();`
> then `Types.VAR_INT.readPrimitive(buffer)` + `buffer.readByte()` + `Types.NAMED_COMPOUND_TAG.read(buffer)`.
This era spans 1.13.2 through 1.20.3 with only NBT-encoding tweaks. The logical
layout (present + id + count + tag) was stable for ~5 years.
---
## Era 4 — 1.20.5+ Structured Components (protocol 766+)
**Sources:**
- `minecraft-data/data/pc/1.20.5/protocol.json``types.{Slot,SlotComponent,SlotComponentType}`
- `minecraft-data/data/pc/1.21.1/protocol.json` — same keys
- `ViaVersion` `ItemType1_20_5.java`, `StructuredDataType.java`, `StructuredDataKey.java`
### Wire layout
```
Slot {
itemCount : VarInt // 0 = empty slot
if itemCount > 0 {
itemId : VarInt
numComponentsToAdd : VarInt
numComponentsToRemove : VarInt
// Add array — length = numComponentsToAdd
components[] {
componentType : VarInt // SlotComponentType id
data : <typed> // depends on componentType
}
// Remove array — length = numComponentsToRemove
removeComponents[] {
componentType : VarInt // marks this component absent
}
}
}
```
### What changed
- The `present` boolean + `nbtData` tag are **gone**. Empty is now signalled by
`itemCount == 0`.
- The single freeform NBT `tag` key was replaced by **typed structured components**:
a registry of named data shapes. Each component is identified by a VarInt ID and
has its own typed encoding.
- `numComponentsToAdd` / `numComponentsToRemove` are both typically zero for items
that carry only default properties; a non-zero count means the item overrides or
explicitly clears default component values.
- The "remove" array carries only the component type IDs (no data), marking those
components explicitly absent (overrides the item type's defaults).
### `itemCount` type discrepancy
`minecraft-data` 1.20.5 records `itemCount` as `i8`. ViaVersion's `ItemType1_20_5.java:51`
reads it as `VAR_INT`. ViaVersion is tested against live servers; the minecraft-data
entry is believed to be an artifact of the dataset's snapshot timing.
From 1.21.2 onwards minecraft-data also records `varint`, confirming the ViaVersion
reading. <!-- UNCONFIRMED: exact snapshot commit when Mojang changed the wire type -->
---
## SlotComponentType Registry
The component type ID is a **VarInt** whose mapping to named components is
version-specific. The tables below show the 1.20.5 and 1.21.1 registries.
**1.20.5** (`minecraft-data/data/pc/1.20.5/protocol.json``types.SlotComponentType.mappings`)
**1.21.1** (`minecraft-data/data/pc/1.21.1/protocol.json``types.SlotComponentType.mappings`)
| ID (1.20.5) | ID (1.21.1) | Component name | Wire encoding (Add payload) |
|:-----------:|:-----------:|----------------|----------------------------|
| 0 | 0 | `custom_data` | anonymousNbt (TAG_Compound) |
| 1 | 1 | `max_stack_size` | VarInt |
| 2 | 2 | `max_damage` | VarInt |
| 3 | 3 | `damage` | VarInt |
| 4 | 4 | `unbreakable` | bool (show tooltip flag) |
| 5 | 5 | `custom_name` | anonymousNbt (text component) |
| 6 | 6 | `item_name` | anonymousNbt (text component) |
| 7 | 7 | `lore` | VarInt count + anonymousNbt[] |
| 8 | 8 | `rarity` | VarInt (0=common 1=uncommon 2=rare 3=epic) |
| 9 | 9 | `enchantments` | VarInt count + {id:VarInt, level:VarInt}[] + bool showTooltip |
| 10 | 10 | `can_place_on` | VarInt count + ItemBlockPredicate[] + bool showTooltip |
| 11 | 11 | `can_break` | VarInt count + ItemBlockPredicate[] + bool showTooltip |
| 12 | 12 | `attribute_modifiers` | VarInt count + {typeId, uuid¹, name, value:f64, operation, slot}[] + bool showTooltip |
| 13 | 13 | `custom_model_data` | VarInt |
| 14 | 14 | `hide_additional_tooltip` | void (presence-only) |
| 15 | 15 | `hide_tooltip` | void |
| 16 | 16 | `repair_cost` | VarInt |
| 17 | 17 | `creative_slot_lock` | void |
| 18 | 18 | `enchantment_glint_override` | bool |
| 19 | 19 | `intangible_projectile` | anonymousNbt |
| 20 | 20 | `food` | {nutrition:VarInt, saturationModifier:f32, canAlwaysEat:bool, secondsToEat:f32, usingConvertsTo:Slot, effects:[]} |
| 21 | 21 | `fire_resistant` | void |
| 22 | 22 | `tool` | {rules:[], defaultMiningSpeed:f32, damagePerBlock:VarInt} |
| 23 | 23 | `stored_enchantments` | same as `enchantments` |
| 24 | 24 | `dyed_color` | {color:i32, showTooltip:bool} |
| 25 | 25 | `map_color` | i32 (RGB) |
| 26 | 26 | `map_id` | VarInt |
| 27 | 27 | `map_decorations` | anonymousNbt |
| 28 | 28 | `map_post_processing` | VarInt |
| 29 | 29 | `charged_projectiles` | VarInt count + Slot[] |
| 30 | 30 | `bundle_contents` | VarInt count + Slot[] |
| 31 | 31 | `potion_contents` | {potionId:VarInt?, customColor:i32?, customEffects:[], customName:string?} |
| 32 | 32 | `suspicious_stew_effects` | VarInt count + {effect:VarInt, duration:VarInt}[] |
| 33 | 33 | `writable_book_content` | VarInt count + ItemBookPage[] |
| 34 | 34 | `written_book_content` | {rawTitle, filteredTitle?, author, generation:VarInt, pages[], resolved:bool} |
| 35 | 35 | `trim` | {material:RegistryEntry, pattern:RegistryEntry, showInTooltip:bool} |
| 36 | 36 | `debug_stick_state` | anonymousNbt |
| 37 | 37 | `entity_data` | anonymousNbt |
| 38 | 38 | `bucket_entity_data` | anonymousNbt |
| 39 | 39 | `block_entity_data` | anonymousNbt |
| 40 | 40 | `instrument` | RegistryEntryHolder (inline or id ref) |
| 41 | 41 | `ominous_bottle_amplifier` | VarInt |
| 42 | — | `recipes` *(1.20.5 only)* | anonymousNbt |
| — | 42 | `jukebox_playable` *(1.21+ only)* | {hasHolder:bool, song, showInTooltip:bool} |
| 43 | 42 | `jukebox_playable` / `recipes` | *(see above — IDs shift by 1 after id 41 in 1.21)* |
| 44 | 43 | `lodestone_tracker` | {globalPosition:optional {dimension:string, position:Position}, tracked:bool} |
| 45 | 44 | `firework_explosion` | ItemFireworkExplosion |
| 46 | 45 | `fireworks` | {flightDuration:VarInt, explosions:[]} |
| 47 | 46 | `profile` | {name:string?, uuid:UUID?, properties:[{name, value, signature?}]} |
| 48 | 47 | `note_block_sound` | string (resource location) |
| 49 | 48 | `banner_patterns` | VarInt count + BannerPatternLayer[] |
| 50 | 49 | `base_color` | VarInt (DyeColor) |
| 51 | 50 | `pot_decorations` | VarInt count + VarInt[] (item ids) |
| 52 | 51 | `container` | VarInt count + Slot[] |
| 53 | 52 | `block_state` | VarInt count + {property:string, value:string}[] |
| 54 | 53 | `bees` | VarInt count + {nbtData:anonymousNbt, ticksInHive:VarInt, minTicksInHive:VarInt}[] |
| 55 | 54 | `lock` | anonymousNbt |
| 56 | 55 | `container_loot` | anonymousNbt (CompoundTag with loot table + seed) |
¹ `attribute_modifiers` in 1.20.5 includes a UUID field that was removed in 1.21.
### ID shift: 1.20.5 → 1.21
In 1.20.5 `recipes` occupies id 42 and `jukebox_playable` is absent (it was
added in MC 1.21). In 1.21+, `jukebox_playable` is inserted at id 42 and
`recipes` shifts to 43. All subsequent IDs shift by +1. **Parsers must never
hardcode component IDs across versions** — always consult the per-version registry.
---
## NBT → Components: `custom_data` and backward compatibility
Before 1.20.5, all item metadata lived in the free-form NBT `tag` compound.
ViaVersion's `StructuredDataConverter` (path:
`common/.../protocols/v1_20_3to1_20_5/rewriter/StructuredDataConverter.java`)
translates the old NBT keys into the appropriate components during downgrade:
- `tag.display.Name``custom_name` component
- `tag.display.Lore``lore` component
- `tag.Enchantments``enchantments` component
- `tag.Unbreakable``unbreakable` component
- `tag.CustomModelData``custom_model_data` component
- `tag.AttributeModifiers``attribute_modifiers` component
- `tag.CanPlaceOn``can_place_on` component
- `tag.CanDestroy``can_break` component
- Data not expressible in any known component is preserved in `custom_data`
under a `VV|DataComponents` backup key.
The `StructuredDataConverter` constants for the old hide-flags bitfield (`HIDE_ENCHANTMENTS=1`,
`HIDE_ATTRIBUTES=2`, `HIDE_UNBREAKABLE=4`, …) appear at lines 7683 of that file,
confirming how the `HideFlags` NBT int mapped to the old tooltip-suppression system
that components replace with per-component `showTooltip` booleans.
---
## Summary of format evolution
| Era | Versions | Empty sentinel | Item ID | Count | Sub-type | Metadata |
|-----|----------|---------------|---------|-------|----------|----------|
| Pre-1.13 | ≤ 1.12.x | `i16 < 0` | `i16` | `i8` | `i16` damage | freeform NBT |
| 1.13 | 1.13 1.13.1 | `i16 < 0` | `i16` (flat) | `i8` | — | freeform NBT |
| 1.13.2 1.20.4 | 1.13.2 1.20.3 | `bool false` | `VarInt` | `i8` | — | freeform NBT |
| 1.20.5+ | 1.20.5+ | `VarInt == 0` | `VarInt` | `VarInt` | — | typed components |
---
## Open items
- **`itemCount` i8 vs VarInt in 1.20.5**: minecraft-data records `i8`; ViaVersion
`ItemType1_20_5.java` reads `VAR_INT`. The ViaVersion implementation is tested
against live traffic and is treated as authoritative here, but the exact protocol
snapshot where Mojang switched is unconfirmed. <!-- UNCONFIRMED -->
- **1.20.2 NBT format**: ViaVersion `ItemType1_20_2.java` uses `Types.COMPOUND_TAG`
(anonymous, no name prefix) while earlier versions used `Types.NAMED_COMPOUND_TAG`.
The exact protocol version of that sub-change within the 1.13.21.20.4 era is
confirmed as 1.20.2 by the class name but not independently verified against
protocol.json (1.20.2 and 1.20.3 files share the same `present+id+count+nbt`
shape in minecraft-data). <!-- UNCONFIRMED: exact NBT name-prefix removal commit -->
- **`jukebox_playable` wire shape in 1.20.5**: present in the component type enum
at id 42 per minecraft-data 1.20.5? The diff above shows `recipes` at 42 in 1.20.5
and `jukebox_playable` absent — consistent with Mojang adding `jukebox_playable`
in the 1.21 pre-releases. If a 1.20.5 release-candidate snapshot introduced it
mid-stream, that is not captured in the current minecraft-data snapshot.
<!-- UNCONFIRMED -->