Update .gitignore to exclude TypeScript build info and add dist directory. Expand README with project overview, technology stack, prerequisites, and instructions for running and testing the application.
ci / server (push) Failing after 4m10s
ci / client (push) Successful in 17s

This commit is contained in:
Leonid Pershin
2026-08-18 11:11:48 +03:00
parent 84aafb0b69
commit e6739e7912
84 changed files with 5698 additions and 2 deletions
+123
View File
@@ -0,0 +1,123 @@
# 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`](../src/HSchool.Protocol/ProtocolCodec.cs) |
| Client codec | [`src/HSchool.Client/src/net/protocol.ts`](../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.