Minecraft uses TCP for connections. TCP ensures reliable, ordered delivery of data. This means if the server sends
A -> B -> C the client will receive it in that exact order.
For anticheats to work properly, we need to know what "state" the client is in.
This is not as easy to detect because latency comes into play. For example, if we send Give Player Jump Boost to the player, it takes some time before the client actually receives it.
To track when state-changing packets are processed, we use ping-pong packets.
A ping-pong is simple: if the server sends a Ping, the client will respond with a Pong packet.
To detect state changes, we pair a ping with the state-changing packet. For example:
Ping -> Give Player Jump Boost 2
The client will eventually respond with a Pong.
Once we receive this, we can safely assume the Jump Boost 2 packet has been seen.
(Though in practice it’s a bit more complicated.)
Almost all anticheats follow the above logic with a few improvements. However, a problem arises:
TCP guarantees packet order but not that two Minecraft packets will be processed in the same tick.
We need to know which tick the client received them.
To solve this, we send two pings: one before and one after the state-changing packet.
Ping 1 -> Give Player Jump Boost -> Ping 2
It’s possible for the client to process these packets across different ticks or all in the same tick. Because of latency and lag, we can’t know exactly.
This leaves us with possible states, modeled like this:
| State | Outcome |
|---|---|
| No ping received yet | We have no Jump Boost |
| Pong 1 received | We either have Jump Boost or no Jump Boost |
| Pong 2 received | We 100% have Jump Boost |
When we simulate movement (on client tick packet) we can use this table to know which scenarios we have to simulate.
For example, if we receive the following from the client: Pong 1 -> Client Tick, we know that we have 2 possible jump heights when we run the simulation on the client tick.
But if we had received Pong 1 -> Pong 2 -> Client Tick, we only would have to simulate 1 scenario: the one with jump boost.
Note: Modern Minecraft solves this issue by introducing the bundle packet, which allows us to combine a ping with the state change. The bundle packet enforces all packets in it to be processed at once.
To optimize the ping-pong system and not exaggerate the amount of pings, we send one ping at the start of a tick and one at the end of a tick to wrap/"sandwhich" all packets in between. For example if we send:
Give Player Jump Boost -> Give Player Speed
This can be put in the same ping "sandwhich"
Ping 1 -> Give Player Jump Boost -> Give Player Speed -> Ping 2
| State | Outcome |
|---|---|
| No ping received yet | We have no Jump Boost |
| Pong 1 received | Jump Boost or no Jump Boost + Speed or no Speed (4 scenarios!) |
| Pong 2 received | We 100% have Jump Boost & Speed |
Its possible the same state property (like jump boost level) gets changed multiple times in the same ping sandwhich.
Give Player Jump Boost 1 -> Give Player Jump Boost 2
can be wrapped like
Ping 1 -> Give Player Jump Boost 1 -> Give Player Jump Boost 2 -> Ping 2
| State | Outcome |
|---|---|
| No ping received yet | We have no Jump Boost |
| Pong 1 received | Possible scenarios: no Jump Boost / Jump Boost 1 / Jump Boost 2 |
| Pong 2 received | We 100% have Jump Boost 2 |
However now we see that its possible the jump boost level can go up to 3 possible states (or even more if more jump boost packets were sent). To solve this we can insert extra pings to limit the amount of possible states
So instead we wrap our jump boosts like this:
Ping 1 -> Give Player Jump Boost 1 -> Ping 2 -> Give Player Jump Boost 2 -> Ping 3
| State | Outcome |
|---|---|
| No ping received yet | We have no Jump Boost |
| Pong 1 received | We either have Jump Boost 1 or no Jump Boost |
| Pong 2 received | We either have Jump Boost 1 or Jump Boost 2 |
| Pong 3 received | We 100% have Jump Boost 2 |
As you can see we never reach more than 2 possibilities for jump boost, which will help limit the amount of scenarios to simulate in a movement simulation.
Basically we are ending our sandwhich by sending an extra ping in the middle to end our current sandwhich and we use that same ping to begin our next sandwhich.
For questions you can join the Minecraft Anticheat Community or you can contact me on discord @1.7.10