Files
h-school/docs/protocol.md
T

6.2 KiB
Raw Blame History

Wire protocol v3

The client talks to the server two ways:

  • HTTP/JSON for the main menu — listing, creating and deleting schools. Those are request/response by nature, so they are plain REST.
  • A binary WebSocket at /ws/game for the school calendar, which changes 20 times a second.

This document covers both. One protocol message per WebSocket 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.

HTTP API

Game dates are ISO-8601 UTC instants. The in-game calendar has no time zone — UTC is only used so the wire format is unambiguous, and the client formats it back in UTC.

GET /api/schools

Everything the main menu needs in one request.

{
  "maxSchools": 6,
  "defaultStartDate": "2012-04-03T06:00:00Z",
  "gameMinutesPerRealSecond": 5,
  "schools": [
    { "id": 1, "name": "Гимназия №14", "gameTime": "2012-04-03T07:35:00Z", "running": false, "speedIndex": 1 }
  ]
}

GET /api/schools/random-name

{ "name": "Лицей «Северная»" } — a suggestion that is not already taken.

POST /api/schools

Body: { "name": "Гимназия №14", "startDate": "2012-04-03T06:00:00Z" }

Status Meaning
201 Created; body is the school.
400 invalid-name Blank, or longer than 40 characters.
400 invalid-start-date Outside 19002999.
409 school-limit-reached maxSchools schools already exist.

Failures are RFC 7807 problem details with an extra code field — that is what the UI switches on.

DELETE /api/schools/{id}

204 when deleted, 404 when the id is unknown. Anyone watching that school over a WebSocket gets a SchoolGone frame.

WebSocket 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 Ping
0x03 C → S OpenSchool
0x04 C → S CloseSchool
0x05 C → S SetRunning
0x06 C → S SetSpeed
0x81 S → C Welcome
0x82 S → C Pong
0x83 S → C Clock
0x84 S → C SchoolGone

Client → server

0x01 Hello — 2 bytes

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

0x02 Ping — 9 bytes

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

0x03 OpenSchool — 5 bytes

Starts watching a school: clock frames for it begin to arrive. It does not start the calendar — every school runs on its own from the moment it is created.

Offset Type Field
0 u8 0x03
1 i32 school id

0x04 CloseSchool — 1 byte

Back to the menu: the clock frames stop. The school keeps running — only SetRunning pauses it, and that pause survives leaving and reconnecting.

0x05 SetRunning — 2 bytes

Offset Type Field
0 u8 0x05
1 u8 1 running, 0 paused

0x06 SetSpeed — 2 bytes

Offset Type Field
0 u8 0x06
1 u8 speed index

Running and speed are separate messages on purpose. A single "set clock" message forces each button to resend the other field from the client's own copy of the state, which is always at least one tick stale — pressing play and then a speed button would pause the school again.

Speed indexes are 0 = ×½, 1 = ×1, 2 = ×2, 3 = ×3, 4 = ×4; out-of-range values are ignored rather than fatal. The base rate is gameMinutesPerRealSecond (5), so ×1 is five game minutes per real second.

Server → client

0x81 Welcome — 4 bytes

The first frame the client receives.

Offset Type Field
0 u8 0x81
1 u8 protocol version
2 u8 tick rate in Hz
3 u8 maximum number of schools

0x82 Pong — 13 bytes

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

0x83 Clock — 15 bytes

Sent every tick to every connection that has a school open, and only to those.

Offset Type Field
0 u8 0x83
1 i32 school id
5 i64 in-game date, milliseconds since the Unix epoch, read as UTC
13 u8 1 running, 0 paused
14 u8 speed index

0x84 SchoolGone — 5 bytes

The open school no longer exists — deleted from the menu in another tab, or never existed. The client returns to the menu.

Offset Type Field
0 u8 0x84
1 i32 school id

Guarantees and limits

  • Frames larger than 8 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.
  • Clock delivery is lossy under back pressure: each connection buffers 32 frames and drops the oldest, because a stale clock is worthless once a newer one exists.

Not in v3 yet

Saving schools to disk (they live in server memory), authentication, and any game state beyond the calendar — the school's ECS world is created but still empty.