Files
h-school/docs/protocol.md
T

3.7 KiB
Raw Blame History

Wire protocol v1

Binary frames over a single WebSocket at /ws/game. One protocol message per frame, no framing header beyond the message id. All multi-byte numbers are little-endian.

Three files must stay in sync — change them in the same commit:

Where File
Server codec src/HSchool.Protocol/ProtocolCodec.cs
Client codec src/HSchool.Client/src/net/protocol.ts
This document docs/protocol.md

Any change to a layout below bumps ProtocolConstants.Version / PROTOCOL_VERSION. The server closes connections whose hello carries a different version with 1002 ProtocolError.

Message ids

Client-to-server ids live in 0x000x7F, server-to-client ids in 0x800xFF, so a misrouted frame is obvious at a glance.

Id Direction Message
0x01 C → S Hello
0x02 C → S Input
0x03 C → S Ping
0x81 S → C Welcome
0x82 S → C Snapshot
0x83 S → C Pong

Client → server

0x01 Hello

Must be the first frame; the server drops the connection if it does not arrive within 5 seconds.

Offset Type Field
0 u8 0x01
1 u8 protocol version
2 u8 name length in bytes (≤ 32)
3 u8[] UTF-8 name

0x02 Input

Sent at ~30 Hz whether or not the mask changed.

Offset Type Field
0 u8 0x02
1 u32 sequence number, monotonically increasing
5 u8 button mask

Button mask: 1 up, 2 down, 4 left, 8 right. Frames with a sequence lower than the last accepted one are ignored, so a late packet cannot undo a newer intent.

0x03 Ping

Offset Type Field
0 u8 0x03
1 i64 client clock in milliseconds

Server → client

0x81 Welcome — 15 bytes

The first frame the client receives; no snapshot is queued before it.

Offset Type Field
0 u8 0x81
1 u8 protocol version
2 u32 replication id of this client's own avatar
6 u8 tick rate in Hz
7 f32 world width
11 f32 world height

0x82 Snapshot — 7 + 21·N bytes

Full state, no delta compression yet. Entities missing from a snapshot are despawned by the client, which is why every visible entity is present in every frame.

Header:

Offset Type Field
0 u8 0x82
1 u32 tick
5 u16 entity count

Then, per entity (21 bytes):

Offset Type Field
+0 u32 replication id (never reused within a session)
+4 u8 kind: 0 unknown, 1 player, 2 obstacle
+5 f32 x
+9 f32 y
+13 f32 radius
+17 u32 colour, packed 0x00RRGGBB

0x83 Pong — 13 bytes

Offset Type Field
0 u8 0x83
1 i64 client clock, echoed unchanged
9 u32 server tick when the ping was handled

Guarantees and limits

  • Frames larger than 64 KiB are refused with close status 1009 MessageTooBig.
  • A malformed frame closes the connection with 1007 InvalidPayloadData.
  • Unknown message ids are ignored rather than fatal, so new ids can be added without breaking older clients within the same protocol version.
  • Snapshot delivery is lossy under back pressure: each connection buffers 32 frames and drops the oldest, because a stale snapshot is worthless once a newer one exists.

Not in v1 yet

Client-side prediction and reconciliation (the sequence field exists for it but is never echoed back), delta compression, interest management, and any form of authentication.