feat: F5.3a the simulation rooms on server F

One room per project for everyone in it, held by a few worker_threads,
each room with a replica of the document that Hocuspocus' direct
connection keeps open: changes go in, what the room writes comes back.
/sim?project=... signs in as /sync does and says refusals and partings
as the socket's closing; only a write grant changes the room. The state
names who started the simulation. After the last person the room stays
60 s, then stops, writes back and ends. Freezing stops a running
simulation and refuses to start one, deleting ends the room without
writing back, revoking lets the person go from the room too; a failed
thread ends its rooms, stopping the service stops them all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 14:29:11 +00:00
co-authored by Claude Opus 5.5
parent 5ded84cb33
commit 8cfc518f0c
17 changed files with 1033 additions and 43 deletions
+14 -4
View File
@@ -71,8 +71,16 @@ export interface DroppedUnit {
readonly text: string;
}
/** engineFailed: the core threw (a part it has not got yet, such as remote links); the room is back in design mode. */
export const ERROR_CODES = ['badMessage', 'badVersion', 'unknownKind', 'badTopic', 'notRunning', 'unknownNode', 'unknownApp', 'unknownAction', 'badArgs', 'engineFailed'] as const;
/**
* engineFailed: the core threw (a part it has not got yet, such as remote
* links); the room is back in design mode. readOnly: the person may only
* watch (a read grant on F). frozen: the project is frozen (its deadline
* passed); its simulation does not start.
*/
export const ERROR_CODES = ['badMessage', 'badVersion', 'unknownKind', 'badTopic', 'notRunning', 'unknownNode', 'unknownApp', 'unknownAction', 'badArgs', 'engineFailed', 'readOnly', 'frozen'] as const;
/** What a client sends that changes the room: only with a write grant (plan §6.4); a reader subscribes. */
export const WRITING_KINDS: readonly ClientMessageKind[] = ['start', 'stop', 'speed', 'drop', 'clearCapture', 'input'];
export type ErrorCode = (typeof ERROR_CODES)[number];
export type HostMessage =
@@ -80,9 +88,11 @@ export type HostMessage =
* The room's mode, speed and time; sent to every client on a change and
* when it connects. `clock`: the time of day, in milliseconds since 1970,
* that the simulation's time 0 stands for (a viewer writes its times of day
* from it, as Java's write the wall clock's).
* from it, as Java's write the wall clock's). `startedBy`: who started the
* simulation that runs, by name, on F (plan §6.2: „Simulation gestartet von
* Mia“); a room of one's own page names no one.
*/
| { readonly v: 1; readonly kind: 'state'; readonly mode: SimMode; readonly speed: number; readonly t: number; readonly clock: number }
| { readonly v: 1; readonly kind: 'state'; readonly mode: SimMode; readonly speed: number; readonly t: number; readonly clock: number; readonly startedBy?: string }
/** The cables that carried a frame since the last tick. */
| { readonly v: 1; readonly kind: 'activity'; readonly links: readonly string[] }
/** A card's new frames; on subscribing, all it holds (`reset`). */
+12 -3
View File
@@ -1,7 +1,9 @@
// What F tells an editor about its place in a shared document (/sync, plan
// §5, §8.5) besides the document itself — the one list both sides import:
// why a connection is refused, why F lets it go, and the message that its
// access changed while it is connected (a project frozen or thawed).
// §5, §8.5) and in its simulation room (/sim, §6.4) besides what they carry
// — the one list both sides import: why a connection is refused, why F lets
// it go, and the message that its access changed while it is connected (a
// project frozen or thawed). /sim says both as a WebSocket's closing: the
// code tells which, the reason is the word.
/** Why F refuses a connection to a project (Hocuspocus' permission-denied, with this as its reason). */
export const SYNC_REFUSALS = [
@@ -25,6 +27,13 @@ export const SYNC_CLOSINGS = [
] as const;
export type SyncClosing = (typeof SYNC_CLOSINGS)[number];
/** /sim closed with one of SYNC_REFUSALS as its reason. */
export const SIM_REFUSED_CODE = 4403;
/** /sim closed with one of SYNC_CLOSINGS as its reason. */
export const SIM_CLOSED_CODE = 4410;
/** /sim closed because the room's thread failed: whoever comes back finds a new room, in design mode. */
export const SIM_ROOM_FAILED_CODE = 4500;
export const SYNC_PROTOCOL_VERSION = 1;
/** A stateless message from F: the connection's access now (sent when it changes on a connection that stays). */