Update wire protocol to version 6 and enhance timetable functionality
ci / server (push) Failing after 3m53s
ci / client (push) Successful in 15s

- Bumped the wire protocol version to 6, reflecting changes in the communication structure.
- Expanded the timetable API with new endpoints for fetching and managing lesson schedules, including `GET /api/schools/{id}/timetable` and `POST /api/schools/{id}/timetable/pin`.
- Updated the protocol documentation to include detailed descriptions of the new timetable features and message structures.
- Enhanced the client-side implementation to support the new timetable functionalities, including lesson pinning and unpinning.
- Revised server-side logic to handle timetable operations and ensure proper integration with existing school management features.
- Added tests to validate the new timetable functionalities and ensure robustness in handling lesson data.
This commit is contained in:
Leonid Pershin
2026-08-19 10:05:05 +03:00
parent 2011d12b1d
commit cad3068bac
30 changed files with 1621 additions and 43 deletions
+72 -13
View File
@@ -1,13 +1,13 @@
# Wire protocol v5
# Wire protocol v6
The client talks to the server two ways:
- **HTTP/JSON** for the main menu and the in-school people browser — listing, creating and
deleting schools, listing mods, loading a catalog for the create editor, and reading a school's
roster (filtered list + one-person card). Those are request/response by nature, so they are
plain REST.
- **A binary WebSocket at `/ws/game`** for the school calendar (20 Hz) and the one-shot map
snapshot sent when a school is opened.
deleting schools, listing mods, loading a catalog for the create editor, reading a school's
roster (filtered list + one-person card), staffing, and the timetable. Those are request/response
by nature, so they are plain REST.
- **A binary WebSocket at `/ws/game`** for the school calendar (20 Hz) and the map snapshot
sent when a school is opened and whenever the current lesson slot changes.
This document covers both. One protocol message per WebSocket frame, no framing header beyond the
message id. **All multi-byte numbers are little-endian.**
@@ -331,6 +331,62 @@ Body: `{ "subject": "Mathematics" }`. Teachers only. Same success payload as GET
Removes one assignment. Payroll drops when the subject was not the only one. Unknown
assignment is `404` `unknown-assignment`.
### `GET /api/schools/{id}/timetable`
The published lesson table and uncovered hours. Optional `?classId=` or `?personId=` filter
the lessons. `?lang=ru|en` labels subjects. Reads the snapshot — it does not post to the
worker. Unknown `{id}` is `404` `unknown-school`.
`day` is 0 = Monday. `period` is the 1-based lesson number from the day frame.
```json
{
"weekDays": 5,
"lessonCount": 7,
"lessons": [
{
"classId": "c5A",
"classYear": 5,
"classLetter": "A",
"subject": "Mathematics",
"subjectLabel": "Математика",
"teacherId": "f3.p1",
"teacherName": "Иванова Ольга Михайловна",
"roomId": "classroom-204",
"day": 1,
"period": 3,
"locked": false
}
],
"uncovered": [
{
"classId": "c5A",
"classYear": 5,
"classLetter": "A",
"subject": "Informatics",
"subjectLabel": "Информатика",
"hours": 1
}
]
}
```
### `POST /api/schools/{id}/timetable/pin`
Body: `{ "classId", "subject", "roomId", "day", "period" }`. Pins a locked lesson there and
rebuilds the rest around it. Same success payload as GET timetable.
| Status | `code` | When |
| --- | --- | --- |
| `400` | `unknown-class` / `unknown-subject` / `unknown-room` | Not in this school. |
| `409` | `no-teacher` | Nobody is assigned that subject. |
| `409` | `pin-rejected` | The slot or room violates the four constraints. |
### `DELETE /api/schools/{id}/timetable/pin`
Query: `classId`, `subject`, `day`, `period`. Drops that lock and rebuilds. Unknown lock is
`404` `unknown-lesson`.
## WebSocket message ids
Client-to-server ids live in `0x000x7F`, server-to-client ids in `0x800xFF`, so a misrouted
@@ -452,9 +508,10 @@ client returns to the menu.
### `0x85` MapSnapshot — variable
Sent once when a school is opened (and again on reconnect OpenSchool). Not every tick, not on
tree clicks. Labels are in the Hello locale. People and in-place activities are omitted — the
client keeps those sections empty.
Sent when a school is opened, on reconnect OpenSchool, and again when the current lesson slot
changes (a bell, a weekend, a holiday — not every tick, not on tree clicks). Labels are in the
Hello locale. Occupancy is computed from the timetable and the clock; the client must not
derive it.
Strings are `u16` byte length + UTF-8. Empty string is a zero length.
@@ -476,8 +533,10 @@ Each node:
| `u16` | pupil slots — how many pupils can take a lesson here. Summed from things on the server. |
| `u8` | item count, then that many records of: string name + `u8` count |
| `u8` | position count, then that many strings |
| `u8` | `1` if a lesson is in this room right now, then subject label + class label strings; `0` if free |
| `u8` | character count, then that many name strings (teacher and pupils of the lesson) |
Item `count` is how many of that thing stand in the room (`Парта ×16` is one record, not sixteen). The client must not recompute pupil slots from items.
Item `count` is how many of that thing stand in the room (`Парта ×16` is one record, not sixteen). The client must not recompute pupil slots from items. The location panel draws activity and characters from the last two fields.
## Guarantees and limits
@@ -491,7 +550,7 @@ Item `count` is how many of that thing stand in the room (`Парта ×16` is o
the oldest, because a stale clock is worthless once a newer one exists.
- The map snapshot uses a separate reliable queue so ticks cannot crowd it out.
## Not in v5 yet
## Not in v6 yet
Authentication, Sit orders, an event log, and `OpenLocation` on the server — the tree is filtered
on the client from the snapshot. The school's ECS world is created but still empty.
Authentication, Sit orders, an event log, walking, and `OpenLocation` on the server — the tree is
filtered on the client from the snapshot.