Skip to content
mc-protocol
Esc
↑↓navigate↵open⌘Jpreview
On this page

Connection states

How a connection moves from handshake to play, and how the library tracks it.

A Minecraft connection is a small state machine. The same packet ID means different things in different states, so both peers must agree on where they are. mc-protocol tracks this for you and switches automatically when it sees the lifecycle packets below.

+-----------+   Intension(1)   +--------+
| handshake | ---------------> | status |
+-----------+                  +--------+
      |
      | Intension(2 or 3)
      v
+-----------+  LoginAcknowledged  +--------+  FinishConfiguration  +------+
|   login   | ------------------> | config | --------------------> | play |
+-----------+                     +--------+                       +------+
                                       ^                              |
                                       |  StartConfiguration /        |
                                       +--- ConfigurationAcknowledged +

The five states

State Purpose Packets
handshake Declares protocol version, target host, and where to go next. Also includes the legacy server-list ping. 2 packets
status Server list ping: description, player count, latency. No login. 4 packets
login Username exchange, optional encryption, compression setup. 11 packets
config Registries, tags, feature flags, resource packs. Everything the client needs before it can render a world. 30 packets
play The game. Chunks, entities, movement, chat, inventory. 210 packets

Two directions, tracked separately

Each Client has an incomingState and an outgoingState. Most of the time they match, but during the play → config round trip they briefly differ: the server sends StartConfiguration and switches its outgoing state to config, while its incoming state stays play until the client answers with ConfigurationAcknowledged.

client.on("stateChange", ({ direction, from, to }) => {
  console.log(`${direction}: ${from} -> ${to}`);
});

Transitions the library performs

These happen inside Client whenever the packet is sent or received, on either side of the connection:

Packet Effect
Intension nextState 1 → status; 2 or 3 → login. Also picks the packet tables for protocolVersion; see Versions.
Hello (serverbound) Records username and uuid on client.session from either the write names (username / playerUUID) or the parsed names (name / uuid).
LoginFinished Records the game profile on client.session.
LoginCompression Enables zlib compression at the given threshold for both directions. Emits compression.
LoginAcknowledged → config. Sets client.loggedIn = true and emits login.
FinishConfiguration (serverbound) → play. Sets client.joined = true and emits playerJoin the first time.
StartConfiguration Server’s outgoing / client’s incoming → config.
ConfigurationAcknowledged Server’s incoming / client’s outgoing → config. AcknowledgeConfiguration is accepted as the same packet name.

You never call a “set state” method. Send or receive the right packet and the state follows.

Packet lookup depends on state

sendPacket("KeepAlive", …) resolves the name against the current outgoing state. KeepAlive exists in both config and play with different IDs; the library picks the right one. If the name does not exist in the current state you get:

Error: Unknown packet: play/clientbound/StatusResponse

The string tells you which state and bound it searched, which is usually enough to spot a sequencing mistake.

Was this page helpful?