Files
minecraft_protocol/06-configuration.md
T
claude-timemachine a3d5f64ef5 verify pass: resolve VERIFY flags (corrections + citations + honest UNCONFIRMED)
Corrected real errors: several 1.7.x release dates, resource_pack_send version,
config packet ordering, structured-component count (56), PLAYER_LOADED (1.21.4),
entity_sound_effect field order. Confirmed+cited the rest; remaining ~19 items
re-marked UNCONFIRMED (third-party/ViaLegacy/26.2 internals unreachable from refs).

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

19 KiB
Raw Blame History

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 ConfigurationFinish 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-854LoginAcknowledgedPacket registered at 0x03 from MINECRAFT_1_20_2.


3. Configuration flow

3.1 Initial entry (Login → Configuration)

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 1120). 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 1120 (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.21.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.21.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.21.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.

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).

// 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.21.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:

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+).

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.31.20.4 0x67
1.20.51.21.1 0x69
1.21.21.21.4 0x70
1.21.51.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):

// 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:

// 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:

// 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):

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.21.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-185KeepAlivePacket 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 1112: 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:


Sources

Reference Used for
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