e4ab541200
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
382 lines
19 KiB
Markdown
382 lines
19 KiB
Markdown
# 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. <!-- UNCONFIRMED (re-configuration only): Velocity `ClientPlaySessionHandler.java:607` calls `player.getTabList().clearAllSilent()` when switching to Configuration — this clears the proxy's local tab-list state without sending a removal packet to other clients. Whether Vanilla also removes the re-configuring player from other clients' tab lists is not observable from proxy source alone; minecraft.wiki does not document this behaviour for re-configuration. -->
|
||
|
||
### 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 |
|