Developer James Monger (jkm.dev) decompiled a 2004 RuneScape 2 client to explain how Jagex ran a live multiplayer RPG over a 56k modem, which delivered only about 5 KB/s downstream. The article follows a single "walk one tile north" click from the player's mouse to the server and back to another player's screen, byte by byte.

walk packet encoding
this.outboundStream.putOpcode(ClientToServerOpcodes.WALK_TILE);
this.outboundStream.putByte(4 + 2 * (pathLength - 1) + 1);
this.outboundStream.putShort(this.playerPositionX + firstX);
this.outboundStream.putShort(this.playerPositionZ + firstZ);
for (int i = 1; i < pathLength; i++) {
 this.outboundStream.putByte(this.pathX[i] - firstX);
 this.outboundStream.putByte(this.pathZ[i] - firstZ);
}

Three constraints shaped the design: the modem's tiny bandwidth, the fact that the game ran as a Java applet sandboxed to a single TCP connection with no raw sockets or UDP, and a server that advanced in fixed cycles of about 600 milliseconds, recalculating what every player could see each tick.

Before any game data flows, the client and server set up an ISAAC stream cipher that encrypts only the one-byte opcode at the start of each packet, not the body. Two streams run in parallel, one per direction, seeded from four integers exchanged during login; the server-to-client stream adds 50 to each seed value so the two directions never share a keystream.

A single "walk to tile" packet consists of an opcode, a length byte, the absolute start coordinates as two 16-bit values, and then only the corner points of the path as signed one-byte deltas rather than every tile crossed. A final byte records whether Ctrl was held, which in early versions forced run mode and later inverted the movement toggle. Field order and byte offsets were also shuffled between game revisions as an anti-cheat measure.

The server processes each 600ms cycle in a fixed order: read incoming packets, run game logic, build outbound updates, then flush. A packet arriving just before a cycle starts is handled almost instantly, one arriving just after waits up to 600ms.

The server answers with a single composite "player update" packet per client per cycle, covering the local player, already-tracked nearby players, and newcomers. Most of it is packed bit by bit rather than byte by byte: an unchanged player costs one single bit, a player who just walked costs about seven bits, and a newly visible player's position is stored as two signed 5-bit deltas relative to the viewer rather than full 16-bit world coordinates. Only detailed changes, like a new animation or changed equipment, switch to byte-aligned fields, adding 44 to 80 bytes per player when appearance data is sent.

Monger's math: the outgoing walk packet costs about seven bytes, and the update another nearby player receives describing that step costs about nine bytes, all inside a 600ms window with 5 KB/s of bandwidth to spare for dozens of simultaneous players.

The broader point of the piece is that the RuneScape client and server behave as one program split across a wire: both sides agree in advance on defaults, visibility ranges and packet schemas, so almost nothing needs to be spelled out on the wire. Modern web APIs trade that efficiency for loose coupling and independent deployability, the right trade when bandwidth is cheap and change velocity matters more than bytes.