# 06 — Configuration State > **Version gate**: the Configuration state does **not exist** before 1.20.2 (protocol 764). > Nothing in this document applies to 1.20.1 or earlier. > Known Packs (§4) requires 1.20.5 (protocol 766) or later. --- ## 1. Why it was added Before 1.20.2, the `Join Game` / `Login (play)` packet was the kitchen sink: it carried dimension codec NBT, dimension type, feature flags, and more. Tags arrived as a separate `Update Tags` play packet immediately after. This meant: - Registry data (codec NBT) had to be resent in full every time the client changed servers or re-entered a world. - There was no clean hook for sending resource packs or feature flags before the client started rendering the world. - Server-to-server transfers (dimension changes, datapack reloads) required hacky re-use of the play state with no protocol-level guarantee the client was in a clean state. 1.20.2 introduced a dedicated **Configuration** phase that sits between Login and Play. The server can keep the client in Configuration as long as needed, push all setup data (registries, resource packs, feature flags, tags), then release it into Play. Crucially, the server can **pull the client back** from Play into Configuration at any time (`Start Configuration` → `Finish Configuration` handshake), enabling clean dimension/datapack changes without a full disconnect. Source: minecraft.wiki Java Edition protocol (Configuration) [primary]; node-minecraft-protocol `src/client/play.js:39-68`; Velocity `ConfigSessionHandler.java`, `ClientConfigSessionHandler.java`. --- ## 2. Connection-state machine ``` HANDSHAKING ──► STATUS (ping/list) └──► LOGIN │ │ Login Success (CB 0x02) │ Login Acknowledged (SB 0x03, added 1.20.2) ▼ CONFIGURATION ◄────────────────────────────────┐ │ │ │ Finish Configuration (CB 0x03) │ │ Ack Finish Configuration (SB 0x03) │ ▼ │ PLAY ────── Start Configuration (CB) ───────┘ ``` State enum source: `node-minecraft-protocol/src/states.js:3-9` — defines `HANDSHAKING`, `STATUS`, `LOGIN`, `CONFIGURATION`, `PLAY`. Transition packet source: `Velocity/StateRegistry.java:853-854` — `LoginAcknowledgedPacket` registered at `0x03` from `MINECRAFT_1_20_2`. --- ## 3. Configuration flow ### 3.1 Initial entry (Login → Configuration) ```mermaid sequenceDiagram participant C as Client participant S as Server Note over C,S: LOGIN state S->>C: Login Success (Login CB 0x02) C->>S: Login Acknowledged (Login SB 0x03) Note over C,S: CONFIGURATION state begins C->>S: Plugin Message minecraft:brand (Config SB 0x02 / 0x01 pre-1.20.5) Note right of S: client brand string e.g. "vanilla" C->>S: Client Information (Config SB 0x00) Note right of S: locale, render distance, chat mode, skin parts, main hand alt 1.20.5 and later (protocol 766+) S->>C: Known Packs (Config CB 0x0E) C->>S: Known Packs (Config SB 0x07) Note over C,S: server computes registry diff end S->>C: Feature Flags (Config CB 0x0C / 0x07 pre-1.20.5) Note left of C: enabled experiment identifiers S->>C: Registry Data x N (Config CB 0x07 / 0x05 pre-1.20.5) Note left of C: one packet per registry; delta if Known Packs matched S->>C: Update Tags (Config CB 0x0D / 0x08 pre-1.20.5) opt Resource packs S->>C: Add Resource Pack (Config CB 0x09) C->>S: Resource Pack Response (Config SB 0x06) end S->>C: Finish Configuration (Config CB 0x03) C->>S: Acknowledge Finish Configuration (Config SB 0x03) Note over C,S: PLAY state begins S->>C: Login Play (Play CB 0x29 in 1.20.2) ``` The server controls ordering within Configuration. The sequence above reflects Vanilla ordering per minecraft.wiki/w/Java_Edition_protocol/FAQ (steps 11–20). The client **must not** transition to Play until it receives `Finish Configuration`. Note: brand (step 11) is sent before Client Information (step 12) per the wiki; the server does not enforce this ordering. Source: minecraft.wiki/w/Java_Edition_protocol/FAQ steps 11–20 (authoritative ordering); `node-minecraft-protocol/src/server/login.js:224-239` (server sends registry_data then finish_configuration); `node-minecraft-protocol/src/client/play.js:49-68` (client handles select_known_packs → finish_configuration → state=PLAY). ### 3.2 Packet IDs at a glance (Configuration state) Packet IDs shifted at 1.20.5 when `Cookie Request/Response`, `Store Cookie`, and `Transfer` were inserted. The table below shows the two main stable points. For 1.21.x+ IDs see `StateRegistry.java:163-261` directly. #### Clientbound (server → client) | Packet name | 1.20.2–1.20.4 | 1.20.5+ | Official name | |--------------------------|:-------------:|:--------:|---------------------------| | Cookie Request | — | 0x00 | `cookie_request` | | Plugin Message | 0x00 | 0x01 | `custom_payload` | | Disconnect | 0x01 | 0x02 | `disconnect` | | Finish Configuration | 0x02 | 0x03 | `finish_configuration` | | Keep Alive | 0x03 | 0x04 | `keep_alive` | | Ping | 0x04 | 0x05 | `ping` | | Reset Chat | — | 0x06 | `reset_chat` | | Registry Data | 0x05 | 0x07 | `registry_data` | | Remove Resource Pack | 0x06 (1.20.3) | 0x08 | `resource_pack_pop` | | Add Resource Pack | 0x06 | 0x09 | `resource_pack_push` | | Store Cookie | — | 0x0A | `store_cookie` | | Transfer | — | 0x0B | `transfer` | | Feature Flags | 0x07 | 0x0C | `update_enabled_features` | | Update Tags | 0x08 | 0x0D | `update_tags` | | Known Packs (CB) | — | 0x0E | `select_known_packs` | Source: `StateRegistry.java:202-261` (CONFIG clientbound block). #### Serverbound (client → server) | Packet name | 1.20.2–1.20.4 | 1.20.5+ | Official name | |----------------------------------|:-------------:|:--------:|---------------------------| | Client Information | 0x00 | 0x00 | `client_information` | | Cookie Response | — | 0x01 | `cookie_response` | | Plugin Message | 0x01 | 0x02 | `custom_payload` | | Acknowledge Finish Configuration | 0x02 | 0x03 | `finish_configuration` | | Keep Alive | 0x03 | 0x04 | `keep_alive` | | Pong | 0x04 | 0x05 | `pong` | | Resource Pack Response | 0x05 | 0x06 | `resource_pack` | | Known Packs (SB) | — | 0x07 | `select_known_packs` | Source: `StateRegistry.java:165-200` (CONFIG serverbound block). --- ## 4. Known Packs handshake (1.20.5 / protocol 766) **Added in 1.20.5. Not present in 1.20.2–1.20.4.** Registry data can be large. If the client already has the vanilla datapack's registry entries (shipped with the client JAR), the server need not resend them. The Known Packs exchange lets server and client negotiate which packs each side knows, so Registry Data only sends the diff. ```mermaid sequenceDiagram participant C as Client participant S as Server Note over C,S: CONFIGURATION state (1.20.5+) S->>C: Known Packs CB (0x0E) — server's list of packs C->>S: Known Packs SB (0x07) — subset client already has locally Note over C,S: server computes intersection S->>C: Registry Data — only entries NOT covered by client's known packs ``` **Wire format** — each `KnownPack` entry is three length-prefixed strings read sequentially: `namespace`, `id`, `version`. VarInt count prefix. Serverbound capped at 64 entries by default. Source: `KnownPacksPacket.java:68-78` (full record and read/write methods). ```java // KnownPacksPacket.java:68 public record KnownPack(String namespace, String id, String version) { private static KnownPack read(ByteBuf buf) { return new KnownPack( ProtocolUtils.readString(buf), ProtocolUtils.readString(buf), ProtocolUtils.readString(buf)); } ``` ### How Velocity uses the Known Packs boundary `ClientConfigSessionHandler.callConfigurationEvent()` javadoc (lines 305-320): > "For 1.20.5+ backends this is done when the client responds to the known packs request. > The response is delayed until the event has been called. For 1.20.2–1.20.4 servers this > is done when the client acknowledges the end of the configuration. This is handled > differently because for 1.20.5+ servers can't keep their connection alive between states > and older servers don't have the known packs transaction." `ClientConfigSessionHandler.handle(KnownPacksPacket)` (line 176) fires `PlayerConfigurationEvent` then forwards the client's response to the backend. `node-minecraft-protocol/src/client/play.js:56-58`: ```js client.once('select_known_packs', () => { client.write('select_known_packs', { packs: [] }) // sends empty list → server sends full registry }) ``` --- ## 5. Re-configuration: Play → Configuration loop The server can return the client to Configuration from Play at any time. Vanilla uses this for dimension changes, datapack reloads (`/reload`), and server transfers (1.20.5+). ```mermaid sequenceDiagram participant C as Client participant S as Server Note over C,S: PLAY state (mid-session) S->>C: Start Configuration (Play CB — see ID table below) C->>S: Acknowledge Configuration (Play SB 0x0B in 1.20.2) Note over C,S: CONFIGURATION state (re-entry) S->>C: Known Packs CB (1.20.5+) C->>S: Known Packs SB (1.20.5+) S->>C: Registry Data / Feature Flags / Update Tags / Resource Packs as needed S->>C: Finish Configuration (Config CB 0x03) C->>S: Acknowledge Finish Configuration (Config SB 0x03) Note over C,S: PLAY state resumes S->>C: Login Play or Respawn ``` **Start Configuration** Play-state clientbound packet IDs: | Version range | ID | |-----------------|-------| | 1.20.2 | 0x65 | | 1.20.3–1.20.4 | 0x67 | | 1.20.5–1.21.1 | 0x69 | | 1.21.2–1.21.4 | 0x70 | | 1.21.5–1.21.8 | 0x6F | Source: `StateRegistry.java:804-813` (PLAY clientbound `StartUpdatePacket` mappings). `StartUpdatePacket` has zero fields — it is a signal-only packet. Source: `StartUpdatePacket.java:35-38` — empty `decode`/`encode` bodies. **node-minecraft-protocol re-entry code** (`src/client/play.js:42-54`): ```js // Server can tell client to re-enter config state client.on('start_configuration', () => enterConfigState()) function enterConfigState(finishCb) { if (client.state === states.CONFIGURATION) return // If we are returning from the play state, acknowledge it if (client.state === states.PLAY) { client.write('configuration_acknowledged', {}) } client.state = states.CONFIGURATION // ... } ``` Players in Configuration are not visible on the tab list. ### How Velocity bridges re-configuration Two session handler classes manage each side: - **`ConfigSessionHandler`** (backend side, `connection/backend/`) — forwards `RegistrySyncPacket`, `TagsUpdatePacket`, `ActiveFeaturesPacket` directly to the client. Intercepts `FinishedUpdatePacket` to orchestrate the pipeline switch. - **`ClientConfigSessionHandler`** (player side, `connection/client/`) — handles packets from the client during Configuration. `handle(FinishedUpdatePacket)` (line 118) switches the client connection's active handler to `ClientPlaySessionHandler` and completes `configSwitchFuture`. The decoder pipeline is explicitly rewound to `StateRegistry.PLAY` on the backend side **before** writing `FinishedUpdatePacket` back, ensuring the Netty decoder expects Play-state packets from that point: ```java // ConfigSessionHandler.java:240-241 — handle(FinishedUpdatePacket) smc.getChannel().pipeline().get(MinecraftVarintFrameDecoder.class).setState(StateRegistry.PLAY); smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.PLAY); ``` --- ## 6. What moved out of Join Game / Login Play Before 1.20.2, `Join Game` (Play CB) carried a `registryCodec` NBT compound containing all dimension types, biome registries, damage types, etc. In 1.20.2+ the field is **absent** from the Join Game packet entirely. `RegistrySyncPacket.java:34` notes: ```java // NBT change in 1.20.2 makes it difficult to parse this packet. ``` Velocity treats it as an opaque byte slice and forwards verbatim. **node-minecraft-protocol** shows the split (`src/server/login.js:226-233`): ```js if (client.supportFeature('segmentedRegistryCodecData')) { for (const key in options.registryCodec) { client.write('registry_data', entry) // one packet per registry } } else { client.write('registry_data', { codec: options.registryCodec || {} }) // pre-1.20.5 monolithic } ``` Confirmed 1.20.5: `minecraft-data/data/pc/common/features.json` sets `segmentedRegistryCodecData -> ['1.20.5', 'latest']`. Verified against protocol.json: 1.20.2 and 1.20.3 use monolithic `codec: anonymousNbt`; 1.20.5 uses `id: string` + `entries: []` (per-registry). Not 1.20.3. --- ## 7. Notable edge cases **Resource pack state across re-configuration** — Velocity 1.20.2 clears applied resource packs on `ConfigSessionHandler.activated()` and re-queues the first previously-applied pack after `FinishedUpdatePacket` to avoid double-applying. Source: `ConfigSessionHandler.java:activated()` and `handle(FinishedUpdatePacket):253`. **Brand forwarding** — the client sends `minecraft:brand` immediately after Login Acknowledged (before the backend may be ready). Velocity caches the brand string; on `handleBackendFinishUpdate` it re-writes a brand Plugin Message to the backend *before* firing finish-config events. Source: `ClientConfigSessionHandler.java:handleBackendFinishUpdate()` (line 330-337). **`PlayerConfigurationEvent` timing differs by version** — 1.20.5+ fires when `KnownPacksPacket` is received; 1.20.2–1.20.4 fires when `Acknowledge Finish Configuration` arrives. The comment in `callConfigurationEvent()` (lines 305-320) explains the reason. **Keep Alive in Configuration** — Keep Alive packets are valid in Configuration state (same packet, same format). The client can be held in Configuration indefinitely; Keep Alive prevents timeout. Source: `StateRegistry.java:179-185` — `KeepAlivePacket` registered at 0x03 (1.20.2) and 0x04 (1.20.5+) in the CONFIG serverbound block. --- ## 8. Resolution of prior VERIFY flags **Packet ordering (Client Information vs brand):** CORRECTED. minecraft.wiki/w/Java_Edition_protocol/FAQ steps 11–12: client sends brand (step 11) before Client Information (step 12). Sequence diagram above updated accordingly. **Update Tags during initial Configuration:** CONFIRMED OPTIONAL. Wiki step 18 lists Update Tags as "(Optional)" during initial configuration. It is present in the clientbound packet table and Vanilla sends it, but it is not mandatory. **Known Packs CB vs brand ordering:** CONFIRMED. Wiki steps 13 (server brand) and 15 (Known Packs CB): server sends its brand first, then Feature Flags (step 14), then Known Packs CB (step 15). Sequence diagram above already reflects this correctly (Known Packs inside the 1.20.5+ alt block, after server brand). **segmentedRegistryCodecData version:** CONFIRMED 1.20.5. `minecraft-data/data/pc/common/features.json`: `segmentedRegistryCodecData -> ['1.20.5', 'latest']`. Verified: 1.20.2/1.20.3 `protocol.json` shows monolithic `codec: anonymousNbt`; 1.20.5 shows `id: string` + `entries: array` (per-registry). Source: `node-minecraft-protocol/src/server/login.js:226` (`if (client.supportFeature('segmentedRegistryCodecData'))`). **Players hidden from tab list during mid-session re-configuration:** UNCONFIRMED (Vanilla server behaviour). Velocity `ClientPlaySessionHandler.java:607` calls `player.getTabList().clearAllSilent()` on entering Configuration, clearing the proxy's local tab-list state without sending a packet to other clients. Whether Vanilla removes the re-configuring player from other clients' tab lists is not documented on minecraft.wiki and is not observable from proxy source alone. The proxy-side behaviour is confirmed; Vanilla-side is not. --- ## Sources | Reference | Used for | |-----------|----------| | [minecraft.wiki/w/Java_Edition_protocol/Packets](https://minecraft.wiki/w/Java_Edition_protocol/Packets) | Packet tables, IDs, official names (fetched 2026-06-19) | | `Velocity/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java` | Authoritative packet ID mapping for all versions (lines 163-261 CONFIG block; 804-813 StartUpdatePacket) | | `Velocity/…/connection/backend/ConfigSessionHandler.java` | Backend-side Configuration handling; pipeline switch; resource pack state | | `Velocity/…/connection/client/ClientConfigSessionHandler.java` | Client-side Configuration handling; Known Packs event timing; brand forwarding | | `Velocity/…/protocol/packet/config/KnownPacksPacket.java` | Known Packs wire format — namespace/id/version record | | `Velocity/…/protocol/packet/config/RegistrySyncPacket.java` | Opaque-forward note; NBT change comment | | `Velocity/…/protocol/packet/config/StartUpdatePacket.java` | Zero-length signal packet | | `Velocity/…/protocol/packet/config/ActiveFeaturesPacket.java` | Feature flags as Key[] array | | `node-minecraft-protocol/src/states.js:3-9` | State enum: CONFIGURATION alongside HANDSHAKING/STATUS/LOGIN/PLAY | | `node-minecraft-protocol/src/server/login.js:189-239` | Server-side: Login Acknowledged handler → Configuration state; registry_data loop | | `node-minecraft-protocol/src/client/play.js:32-68` | Client-side: Login Success → Configuration entry; re-entry from Play; select_known_packs response |