@genex-ai/multiplayer 0.17.1 → 0.17.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROTOCOL.md +688 -7
- package/README.md +263 -0
- package/dist/authority/fast-udp.d.ts +13 -0
- package/dist/authority/fast-udp.js +52 -0
- package/dist/authority/fast-webtransport.d.ts +45 -0
- package/dist/authority/fast-webtransport.js +124 -0
- package/dist/authority/index.d.ts +2333 -0
- package/dist/authority/index.js +758 -0
- package/dist/authority/module.d.ts +1 -0
- package/dist/authority/module.js +12 -0
- package/dist/bridge-authority.d.ts +100 -0
- package/dist/bridge-authority.global.js +14 -0
- package/dist/bridge-authority.js +286 -0
- package/dist/bridge-core-Dhc73GC8.d.ts +27 -0
- package/dist/bridge.d.ts +26 -20
- package/dist/bridge.global.js +7 -7
- package/dist/bridge.js +17 -48
- package/dist/chunk-4R7C2TLH.js +68 -0
- package/dist/chunk-5JGRFT2N.js +3646 -0
- package/dist/{chunk-3ZUPU3GT.js → chunk-7QVOIHJF.js} +13 -122
- package/dist/chunk-K3SF5VDS.js +27 -0
- package/dist/chunk-UX3F6YTV.js +143 -0
- package/dist/chunk-Y6XHXDIK.js +6617 -0
- package/dist/{chunk-C52XFIB6.js → chunk-ZSWF4VCN.js} +141 -4
- package/dist/fast-client-CZ41v-oB.d.ts +465 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.js +3 -2
- package/dist/join-CGoInvUM.d.ts +6 -0
- package/dist/module-DqtFb5wA.d.ts +1436 -0
- package/dist/physics-predict-49udN3a1.d.ts +1208 -0
- package/dist/protocol.d.ts +137 -2
- package/dist/protocol.js +33 -1
- package/package.json +33 -3
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
// src/protocol.ts
|
|
2
2
|
var PROTOCOL_VERSION = 3;
|
|
3
3
|
var MIN_PROTOCOL_VERSION = 3;
|
|
4
|
+
var AUTHORITY_PROTOCOL_VERSION = 4;
|
|
5
|
+
var AUTHORITY_MIN_PROTOCOL_VERSION = 4;
|
|
6
|
+
var RELAY_WINDOW = Object.freeze({ min: MIN_PROTOCOL_VERSION, max: PROTOCOL_VERSION });
|
|
7
|
+
var AUTHORITY_WINDOW = Object.freeze({
|
|
8
|
+
min: AUTHORITY_MIN_PROTOCOL_VERSION,
|
|
9
|
+
max: AUTHORITY_PROTOCOL_VERSION
|
|
10
|
+
});
|
|
11
|
+
function inProtocolWindow(sent, window) {
|
|
12
|
+
return typeof sent === "number" && Number.isInteger(sent) && sent >= window.min && sent <= window.max;
|
|
13
|
+
}
|
|
4
14
|
var RoomType = {
|
|
5
15
|
/** The plain relay room: joinOrCreate, fullest-first placement. */
|
|
6
16
|
PLAY: "play",
|
|
7
17
|
/** The matchmaking search pool. No game session — it only hands out seat reservations. */
|
|
8
18
|
QUEUE: "queue",
|
|
9
19
|
/** A capped match arena: seat reservation (matchmade), create (private lobby) or joinById (invite). */
|
|
10
|
-
MATCH: "match"
|
|
20
|
+
MATCH: "match",
|
|
21
|
+
/** A server-authoritative room (§17): the server runs the game's room module, clients send input.
|
|
22
|
+
* Served by the authority cluster, never by a relay. */
|
|
23
|
+
AUTHORITY: "authority"
|
|
11
24
|
};
|
|
12
25
|
var JoinOption = {
|
|
13
26
|
/** The game's slug. Must equal the embed token's `slug` claim. */
|
|
@@ -19,7 +32,77 @@ var JoinOption = {
|
|
|
19
32
|
/** {@link PROTOCOL_VERSION}. Required. */
|
|
20
33
|
PROTOCOL: "protocol"
|
|
21
34
|
};
|
|
35
|
+
var AuthorityJoinOption = {
|
|
36
|
+
/** The capabilities this client implements: at most {@link AuthorityCaps.MAX_CAPS} strings of at
|
|
37
|
+
* most {@link AuthorityCaps.MAX_CAP_CHARS} characters; absent = []. Any other value is refused
|
|
38
|
+
* with 400 by every authority room. */
|
|
39
|
+
CAPS: "caps"
|
|
40
|
+
};
|
|
41
|
+
var AuthorityCaps = {
|
|
42
|
+
/** Decodes and draws §17.10's physics bodies. A physics room refuses a join without it (426). An
|
|
43
|
+
* SDK sends it only once its drawing passes `physics-draw.json` and, for an engine SDK, its own
|
|
44
|
+
* probe on the pinned core (§17.10 *Conformance*). */
|
|
45
|
+
PHYSICS: "physics@1",
|
|
46
|
+
/** Also predicts a predicting room's bodies (§17.11): it builds the room's world from `physDesc`,
|
|
47
|
+
* adopts and replays keyframes, and draws its predicted bodies. A client sends it beside
|
|
48
|
+
* `physics@1`, never alone; a room serves the same state to both, so the cap only says what the
|
|
49
|
+
* client does. An SDK sends it only once it passes `physics-predict.json` (§17.11 *Conformance*). */
|
|
50
|
+
PHYSICS_PREDICT: "physics@2",
|
|
51
|
+
/** Reconciles a predicting room's repeated inputs (§17.11 *Input pacing*): it predicts its own
|
|
52
|
+
* entity from the state as stepped through `ackSeq + inRep`, not `ackSeq`. A predicting room repeats
|
|
53
|
+
* a starved seat's last input only for a client that sends it; without it a starved tick steps
|
|
54
|
+
* nothing, as before. An SDK sends it only once it passes `input-repeat.json`. */
|
|
55
|
+
INPUT_REPEAT: "repeat@1",
|
|
56
|
+
/** Collides with, rides and draws a room's declared lifts (§17.11 *Lifts*). A room that declares
|
|
57
|
+
* lifts refuses a join without it (426, `lifts@1 required`): a client that does not pose them
|
|
58
|
+
* would fall through them in its prediction. An SDK sends it only once it passes `lifts.json`. */
|
|
59
|
+
LIFTS: "lifts@1",
|
|
60
|
+
/** Writes into each input the server tick its prediction steps it at (`AuthorityInput.tick`, modulo
|
|
61
|
+
* 2^16; §17.11 *The input's tick*): the server then judges a re-timed input (a tick that steps two of
|
|
62
|
+
* a seat's inputs or none) at the tick it was meant for — a firstperson view on a spinning lift
|
|
63
|
+
* carried by the lift's turn between the two, lag compensation's lift frames. Without it the field
|
|
64
|
+
* is ignored, as before. */
|
|
65
|
+
INPUT_TICK: "tick@1",
|
|
66
|
+
/** Moves the seat's inputs and motion state to the fast channel (§17.12): redundant input datagrams
|
|
67
|
+
* and ack-delta snapshots over UDP or WebTransport, the WebSocket kept for everything else. Sent
|
|
68
|
+
* beside `tick@1`. An SDK sends it only once it passes `fast-transport.json` and its transport
|
|
69
|
+
* tests; a room that does not offer the channel ignores it. */
|
|
70
|
+
FAST: "fast@1",
|
|
71
|
+
MAX_CAPS: 16,
|
|
72
|
+
MAX_CAP_CHARS: 32
|
|
73
|
+
};
|
|
74
|
+
function capsOf(value) {
|
|
75
|
+
if (value === void 0) return [];
|
|
76
|
+
if (!Array.isArray(value) || value.length > AuthorityCaps.MAX_CAPS) return null;
|
|
77
|
+
const ok = value.every((cap) => typeof cap === "string" && cap.length <= AuthorityCaps.MAX_CAP_CHARS);
|
|
78
|
+
return ok ? [...value] : null;
|
|
79
|
+
}
|
|
80
|
+
var AUTHORITY_PHYSICS_UNITS = 6;
|
|
81
|
+
var SIXTIETHS_PER_SECOND = 60;
|
|
82
|
+
var AUTHORITY_PHYSICS_TICK_WEIGHT = { 20: 1, 30: 1, 60: 1, 120: 2 };
|
|
83
|
+
function authorityRoomUnits(physics, tickHz) {
|
|
84
|
+
return physics ? AUTHORITY_PHYSICS_UNITS * AUTHORITY_PHYSICS_TICK_WEIGHT[tickHz] : 1;
|
|
85
|
+
}
|
|
86
|
+
function authorityPhysicsBudgetSixtieths(tickHz) {
|
|
87
|
+
return SIXTIETHS_PER_SECOND / tickHz * AUTHORITY_PHYSICS_TICK_WEIGHT[tickHz];
|
|
88
|
+
}
|
|
89
|
+
var MS_PER_SECOND = 1e3;
|
|
90
|
+
function authorityTickBudgetMs(tickHz) {
|
|
91
|
+
return authorityPhysicsBudgetSixtieths(tickHz) * MS_PER_SECOND / SIXTIETHS_PER_SECOND;
|
|
92
|
+
}
|
|
93
|
+
var AUTHORITY_ENCODER_BUFFER_BYTES = 1048576;
|
|
22
94
|
var SERVER_OWNED_JOIN_OPTIONS = ["clients", "preset", "winCondition", "config", "matchmade"];
|
|
95
|
+
var AUTHORITY_SERVER_OWNED_JOIN_OPTIONS = [
|
|
96
|
+
...SERVER_OWNED_JOIN_OPTIONS,
|
|
97
|
+
"moduleSha",
|
|
98
|
+
"tickHz",
|
|
99
|
+
"maxPlayers",
|
|
100
|
+
"lagComp",
|
|
101
|
+
"seed",
|
|
102
|
+
"units",
|
|
103
|
+
"leaseId",
|
|
104
|
+
"region"
|
|
105
|
+
];
|
|
23
106
|
var JoinError = {
|
|
24
107
|
/** A server-owned option was sent. */
|
|
25
108
|
BAD_OPTION: 400,
|
|
@@ -27,8 +110,18 @@ var JoinError = {
|
|
|
27
110
|
AUTH_REQUIRED: 401,
|
|
28
111
|
/** Token for another game ('wrong game'), or the room's guest cap is full. */
|
|
29
112
|
FORBIDDEN: 403,
|
|
30
|
-
/** `protocol` missing or outside
|
|
31
|
-
|
|
113
|
+
/** `protocol` missing or outside the room type's window ({@link RELAY_WINDOW},
|
|
114
|
+
* {@link AUTHORITY_WINDOW}). Update the client. */
|
|
115
|
+
PROTOCOL_UNSUPPORTED: 426,
|
|
116
|
+
// ---- authority rooms only (§17.2); a relay room never answers these ----
|
|
117
|
+
/** The game's owner is not entitled to server-authoritative rooms. Terminal: do not retry. */
|
|
118
|
+
NOT_ENTITLED: 402,
|
|
119
|
+
/** The owner's room quota, concurrency cap or credits refused a NEW room. Live rooms never close for it. */
|
|
120
|
+
ROOM_QUOTA: 429,
|
|
121
|
+
/** The game has no published room module on this channel, or it could not be loaded. */
|
|
122
|
+
MODULE_UNAVAILABLE: 424,
|
|
123
|
+
/** No authority server can take the room right now, or admission could not be checked. Retry later. */
|
|
124
|
+
AUTHORITY_CAPACITY: 503
|
|
32
125
|
};
|
|
33
126
|
var STAGING_ROOM_PREFIX = "preview--";
|
|
34
127
|
var roomKeyFor = (slug, channel) => channel === "staging" ? `${STAGING_ROOM_PREFIX}${slug}` : slug;
|
|
@@ -141,7 +234,12 @@ var CloseCode = {
|
|
|
141
234
|
/** The player's session was revoked (signed out from the dashboard). Do not rejoin with it. */
|
|
142
235
|
REVOKED: 4401,
|
|
143
236
|
/** A newer session of the same player took the seat (second tab, or the page reloaded). */
|
|
144
|
-
SUPERSEDED: 4409
|
|
237
|
+
SUPERSEDED: 4409,
|
|
238
|
+
// ---- authority rooms only (§17.6), terminal ----
|
|
239
|
+
/** The operator or the platform halted the room (kill switch, takedown, disabled game). */
|
|
240
|
+
MODULE_HALTED: 4500,
|
|
241
|
+
/** The game's room module broke its limits or threw; the room is gone. */
|
|
242
|
+
MODULE_FAULT: 4501
|
|
145
243
|
};
|
|
146
244
|
var Limits = {
|
|
147
245
|
ROOM_MAX_CLIENTS: 64,
|
|
@@ -207,6 +305,29 @@ var RelayTiming = {
|
|
|
207
305
|
/** Embed tokens are re-checked for revocation on this cadence (REVOKED close on hit). */
|
|
208
306
|
REVOCATION_SWEEP_MS: 3e4
|
|
209
307
|
};
|
|
308
|
+
var AuthorityTiming = {
|
|
309
|
+
/** Fixed simulation step rate when the module does not choose one. */
|
|
310
|
+
TICK_HZ: 30,
|
|
311
|
+
/** How far behind the server clock clients render other entities (the interpolation buffer). */
|
|
312
|
+
RENDER_DELAY_MS: 100,
|
|
313
|
+
/** The furthest back lag compensation rewinds for one input's `seen` snapshot. */
|
|
314
|
+
MAX_REWIND_MS: 500,
|
|
315
|
+
/** Inputs buffered per seat before the oldest are folded away. */
|
|
316
|
+
INPUT_BUFFER: 32,
|
|
317
|
+
/** State patch cadence. The spike measured its feel gates at 50 ms; 33 is an owner call. */
|
|
318
|
+
PATCH_INTERVAL_MS: 50,
|
|
319
|
+
/** A predicting room repeats a starved seat's last input for at most this long of repeats not yet
|
|
320
|
+
* paid back (§17.11 *Input pacing*), then the seat holds: a vanished player stops instead of
|
|
321
|
+
* driving into a wall. Rocket League's client resends its last 10 frames of input in every packet
|
|
322
|
+
* (83 ms at 120 Hz), the gap its server's repeat bridges; 100 ms is that rounded up to whole ticks
|
|
323
|
+
* at every tick rate (12 at 120 Hz, 6 at 60, 3 at 30). */
|
|
324
|
+
INPUT_REPEAT_MAX_MS: 100,
|
|
325
|
+
/** One starve run is repeated for at most this long; past it the seat holds as before (§17.11 *Input
|
|
326
|
+
* pacing*): a stick steady before a starve is less and less likely to stay so through it, and on
|
|
327
|
+
* car-ball (5 runs a profile) 4 repeats a run at 120 Hz kept its corrections over 20 u at the base's
|
|
328
|
+
* under loss where 12 doubled them. 4 ticks at 120 Hz, 2 at 60, 1 at 30. */
|
|
329
|
+
INPUT_REPEAT_RUN_MS: 33
|
|
330
|
+
};
|
|
210
331
|
var ClientReconnection = {
|
|
211
332
|
enabled: true,
|
|
212
333
|
minUptime: 0,
|
|
@@ -284,9 +405,24 @@ var ClockSync = {
|
|
|
284
405
|
export {
|
|
285
406
|
PROTOCOL_VERSION,
|
|
286
407
|
MIN_PROTOCOL_VERSION,
|
|
408
|
+
AUTHORITY_PROTOCOL_VERSION,
|
|
409
|
+
AUTHORITY_MIN_PROTOCOL_VERSION,
|
|
410
|
+
RELAY_WINDOW,
|
|
411
|
+
AUTHORITY_WINDOW,
|
|
412
|
+
inProtocolWindow,
|
|
287
413
|
RoomType,
|
|
288
414
|
JoinOption,
|
|
415
|
+
AuthorityJoinOption,
|
|
416
|
+
AuthorityCaps,
|
|
417
|
+
capsOf,
|
|
418
|
+
AUTHORITY_PHYSICS_UNITS,
|
|
419
|
+
AUTHORITY_PHYSICS_TICK_WEIGHT,
|
|
420
|
+
authorityRoomUnits,
|
|
421
|
+
authorityPhysicsBudgetSixtieths,
|
|
422
|
+
authorityTickBudgetMs,
|
|
423
|
+
AUTHORITY_ENCODER_BUFFER_BYTES,
|
|
289
424
|
SERVER_OWNED_JOIN_OPTIONS,
|
|
425
|
+
AUTHORITY_SERVER_OWNED_JOIN_OPTIONS,
|
|
290
426
|
JoinError,
|
|
291
427
|
STAGING_ROOM_PREFIX,
|
|
292
428
|
roomKeyFor,
|
|
@@ -305,6 +441,7 @@ export {
|
|
|
305
441
|
CloseCode,
|
|
306
442
|
Limits,
|
|
307
443
|
RelayTiming,
|
|
444
|
+
AuthorityTiming,
|
|
308
445
|
ClientReconnection,
|
|
309
446
|
POSE_KEYS,
|
|
310
447
|
Interpolation,
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { I as InputData } from './module-DqtFb5wA.js';
|
|
2
|
+
|
|
3
|
+
/** The datagram format's version, the first byte of every datagram. */
|
|
4
|
+
declare const FAST_VERSION = 1;
|
|
5
|
+
/** No datagram is longer: the IPv6 minimum MTU (1280) less IP, UDP and QUIC headers. */
|
|
6
|
+
declare const FAST_MAX_DATAGRAM_BYTES = 1200;
|
|
7
|
+
/** The binding key's length: 8 random bytes per socket, sent on the reliable channel at join. */
|
|
8
|
+
declare const FAST_KEY_BYTES = 8;
|
|
9
|
+
/** The redundant window as time: the longest delay episode measured on `real-mac-wifi` (70–170 ms). */
|
|
10
|
+
declare const INPUT_REDUNDANCY_MS = 170;
|
|
11
|
+
/** The window's least inputs: two datagrams lost in a row are recovered at any tick rate. */
|
|
12
|
+
declare const INPUT_REDUNDANCY_MIN = 3;
|
|
13
|
+
/** The window's most inputs, and the most a datagram may carry: 32 full slots and
|
|
14
|
+
* {@link FAST_MAX_ACTIONS} actions still fit {@link FAST_MAX_DATAGRAM_BYTES}. */
|
|
15
|
+
declare const INPUT_REDUNDANCY_MAX = 32;
|
|
16
|
+
/** Presses past the window awaiting the server's ack that one datagram carries (the oldest). */
|
|
17
|
+
declare const FAST_MAX_ACTIONS = 8;
|
|
18
|
+
/** The longest gap the server fills with the last input held; a longer one is skipped. */
|
|
19
|
+
declare const FAST_GAP_FILL_MS = 250;
|
|
20
|
+
/** A datagram whose newest seq is more than this far (as ticks) past the last accepted is dropped. */
|
|
21
|
+
declare const FAST_SEQ_AHEAD_MS = 5000;
|
|
22
|
+
/** Snapshots each side holds as bases: 533 ms at 60 Hz, over a 409 ms p99 round trip plus an episode. */
|
|
23
|
+
declare const FAST_SNAPSHOT_HISTORY = 32;
|
|
24
|
+
/** The most fragments one snapshot may have (about 37 KB); a larger body is not sent on this channel. */
|
|
25
|
+
declare const FAST_MAX_FRAGMENTS = 32;
|
|
26
|
+
/** Incomplete snapshots a client gathers at once; the oldest gives way. */
|
|
27
|
+
declare const FAST_REASSEMBLY_SLOTS = 4;
|
|
28
|
+
/** One record's most bytes. */
|
|
29
|
+
declare const FAST_MAX_RECORD_BYTES = 1024;
|
|
30
|
+
/** A client with no snapshot this long after its first datagram falls back to the WebSocket. */
|
|
31
|
+
declare const FAST_HANDSHAKE_MS = 1000;
|
|
32
|
+
/** A client with no snapshot for this long falls back to the WebSocket. */
|
|
33
|
+
declare const FAST_STALL_FALLBACK_MS = 2000;
|
|
34
|
+
/** The reliable messages of the fast channel (§17.12): the server's binding at join and the client's
|
|
35
|
+
* fallback. Reserved names (`__`), so no game event can be mistaken for one. */
|
|
36
|
+
declare const FastMessage: {
|
|
37
|
+
/** Server → client: `{ sid, key, url }`, the seat's binding. */
|
|
38
|
+
readonly Bind: "__fast";
|
|
39
|
+
/** Client → server: `{ last }`, the last seq the client sent by datagram; its inputs go by
|
|
40
|
+
* `room.input()` from now on. */
|
|
41
|
+
readonly Off: "__fast:off";
|
|
42
|
+
};
|
|
43
|
+
type FastMessage = (typeof FastMessage)[keyof typeof FastMessage];
|
|
44
|
+
/** Where a browser reaches the seat's channel (§17.12 *WebTransport*): a relay's WebTransport session
|
|
45
|
+
* `url` (`https://host:port/w/<worker>`) and the SHA-256 hashes (64 lowercase hex digits each) of the
|
|
46
|
+
* certificates it may serve, for `serverCertificateHashes`. The relay forwards the seat's datagrams,
|
|
47
|
+
* byte for byte, to the endpoint `url` names; the binding (`sid`, `key`) is the same. */
|
|
48
|
+
interface FastWebTransport {
|
|
49
|
+
url: string;
|
|
50
|
+
hashes: string[];
|
|
51
|
+
}
|
|
52
|
+
/** The `__fast` message's payload. `wt`, sent when the room's worker has a WebTransport relay, is read
|
|
53
|
+
* only by a browser; every other client ignores it. */
|
|
54
|
+
interface FastBindMessage {
|
|
55
|
+
sid: number;
|
|
56
|
+
key: string;
|
|
57
|
+
url: string;
|
|
58
|
+
wt?: FastWebTransport;
|
|
59
|
+
}
|
|
60
|
+
/** The `__fast:off` message's payload. */
|
|
61
|
+
interface FastOffMessage {
|
|
62
|
+
last: number;
|
|
63
|
+
}
|
|
64
|
+
/** The datagram kinds, the second byte. */
|
|
65
|
+
declare const FastKind: {
|
|
66
|
+
readonly Uplink: 1;
|
|
67
|
+
readonly Snapshot: 2;
|
|
68
|
+
};
|
|
69
|
+
type FastKind = (typeof FastKind)[keyof typeof FastKind];
|
|
70
|
+
/** Why a datagram or a value was refused. */
|
|
71
|
+
declare const FastFault: {
|
|
72
|
+
readonly Oversized: "oversized";
|
|
73
|
+
readonly Truncated: "truncated";
|
|
74
|
+
readonly Trailing: "trailing";
|
|
75
|
+
readonly Version: "version";
|
|
76
|
+
readonly Kind: "kind";
|
|
77
|
+
readonly Session: "session";
|
|
78
|
+
readonly Key: "key";
|
|
79
|
+
readonly Count: "count";
|
|
80
|
+
readonly Fresh: "fresh";
|
|
81
|
+
readonly Actions: "actions";
|
|
82
|
+
readonly Flags: "flags";
|
|
83
|
+
readonly Order: "order";
|
|
84
|
+
readonly Field: "field";
|
|
85
|
+
readonly Ahead: "ahead";
|
|
86
|
+
readonly Fragment: "fragment";
|
|
87
|
+
readonly Mismatch: "mismatch";
|
|
88
|
+
readonly Duplicate: "duplicate";
|
|
89
|
+
readonly Keyframe: "keyframe";
|
|
90
|
+
readonly Body: "body";
|
|
91
|
+
readonly Base: "base";
|
|
92
|
+
readonly Stale: "stale";
|
|
93
|
+
};
|
|
94
|
+
type FastFault = (typeof FastFault)[keyof typeof FastFault];
|
|
95
|
+
/** How the server came by an accepted input. */
|
|
96
|
+
declare const AcceptKind: {
|
|
97
|
+
/** First sent in the datagram that brought it. */
|
|
98
|
+
readonly Fresh: "fresh";
|
|
99
|
+
/** Sent first in an earlier datagram that was lost; carried again by redundancy or as an action. */
|
|
100
|
+
readonly Recovered: "recovered";
|
|
101
|
+
/** No datagram the server got carried it: the last input held stands in for it. */
|
|
102
|
+
readonly Fill: "fill";
|
|
103
|
+
};
|
|
104
|
+
type AcceptKind = (typeof AcceptKind)[keyof typeof AcceptKind];
|
|
105
|
+
type FastResult<T> = {
|
|
106
|
+
ok: true;
|
|
107
|
+
value: T;
|
|
108
|
+
} | {
|
|
109
|
+
ok: false;
|
|
110
|
+
fault: FastFault;
|
|
111
|
+
};
|
|
112
|
+
/** One input on the wire: its client sequence, its `tick@1` field, its render time and its payload. */
|
|
113
|
+
interface FastInput {
|
|
114
|
+
seq: number;
|
|
115
|
+
tick: number;
|
|
116
|
+
/** The render time its sender drew the world at when it sent it (§17.5): the room clock, whole ms,
|
|
117
|
+
* as its WebSocket input frame's stamp would carry it; 0: not stamped (no synced clock). */
|
|
118
|
+
render: number;
|
|
119
|
+
input: InputData;
|
|
120
|
+
}
|
|
121
|
+
/** An uplink datagram's content. `window` is contiguous, oldest first; `actions` ascend below it. */
|
|
122
|
+
interface Uplink {
|
|
123
|
+
sid: number;
|
|
124
|
+
key: string;
|
|
125
|
+
ackSnap: number;
|
|
126
|
+
fresh: number;
|
|
127
|
+
actions: FastInput[];
|
|
128
|
+
window: FastInput[];
|
|
129
|
+
}
|
|
130
|
+
/** A seat's fast-channel binding, from the reliable `fast` message. */
|
|
131
|
+
interface FastBinding {
|
|
132
|
+
sid: number;
|
|
133
|
+
key: string;
|
|
134
|
+
}
|
|
135
|
+
/** The redundant window at `tickHz`: {@link INPUT_REDUNDANCY_MS} of inputs, within its bounds. */
|
|
136
|
+
declare function inputRedundancyTicks(tickHz: number): number;
|
|
137
|
+
/** The longest uplink a seat at `tickHz` sends: the header, {@link FAST_MAX_ACTIONS} actions and a full
|
|
138
|
+
* window of slots, each at its largest (a socket that cannot carry it cannot carry the channel). */
|
|
139
|
+
declare function uplinkMaxBytes(tickHz: number): number;
|
|
140
|
+
/** The longest gap filled at `tickHz` ({@link FAST_GAP_FILL_MS} of ticks). */
|
|
141
|
+
declare const gapFillMax: (tickHz: number) => number;
|
|
142
|
+
/** The farthest a datagram's newest seq may be past the last accepted at `tickHz`. */
|
|
143
|
+
declare const seqAheadMax: (tickHz: number) => number;
|
|
144
|
+
/** Lowercase hex of `bytes`. */
|
|
145
|
+
declare function toHex(bytes: Uint8Array): string;
|
|
146
|
+
/** The bytes of hex text `hex` (an even count of hex digits; anything else reads as no bytes). */
|
|
147
|
+
declare function fromHex(hex: string): Uint8Array;
|
|
148
|
+
/** An input whose buttons gain a bit over `previous`'s. */
|
|
149
|
+
declare const isPress: (previous: InputData | null, input: InputData) => boolean;
|
|
150
|
+
/** Encodes uplink `u` (§17.12 *Uplink datagram*); refuses (`field`) what the wire cannot carry. */
|
|
151
|
+
declare function encodeUplink(u: Uplink): FastResult<Uint8Array>;
|
|
152
|
+
/** Decodes an uplink datagram for the seat bound by `binding`: checked whole, the session and key
|
|
153
|
+
* first; any fault drops it. Payloads are as sent (the server sanitizes as it accepts). */
|
|
154
|
+
declare function decodeUplink(bytes: Uint8Array, binding: FastBinding): FastResult<Uplink>;
|
|
155
|
+
/** The render time a fast input carries (§17.12 *Render-time stamp*), the stamp its WebSocket input
|
|
156
|
+
* frame would carry: `round(serverNow) − round(renderDelay + rtt/2)` whole ms, the delay clamped to
|
|
157
|
+
* [0, 65535], 0 when that is not above 0 or the clock has not synced (`serverNow` null). */
|
|
158
|
+
declare function renderStamp(serverNow: number | null, renderDelay: number, rtt: number): number;
|
|
159
|
+
/** What {@link uplinkFor} needs beside the inputs. */
|
|
160
|
+
interface UplinkOptions {
|
|
161
|
+
sid: number;
|
|
162
|
+
key: string;
|
|
163
|
+
/** The newest snapshot the client applied. */
|
|
164
|
+
ackSnap: number;
|
|
165
|
+
/** The server's last accepted input seq, from the newest applied snapshot's `inputAck`. */
|
|
166
|
+
inputAck: number;
|
|
167
|
+
/** How many of the newest inputs this datagram sends first. */
|
|
168
|
+
fresh: number;
|
|
169
|
+
tickHz: number;
|
|
170
|
+
}
|
|
171
|
+
/** The client's next uplink (§17.12 *Redundancy and one-shot inputs*): `sent` is every input it still
|
|
172
|
+
* holds, ascending and contiguous, back past `inputAck`; the window is the newest
|
|
173
|
+
* {@link inputRedundancyTicks}, and the oldest {@link FAST_MAX_ACTIONS} presses before it that the
|
|
174
|
+
* server has not acked ride as actions. */
|
|
175
|
+
declare function uplinkFor(sent: readonly FastInput[], o: UplinkOptions): Uplink;
|
|
176
|
+
/** A seat's receive state: the last accepted seq (from `seqBase` at bind), the last accepted input
|
|
177
|
+
* (null before the first) and the newest snapshot the seat acknowledged. */
|
|
178
|
+
interface SeatRx {
|
|
179
|
+
last: number;
|
|
180
|
+
input: InputData | null;
|
|
181
|
+
ackSnap: number;
|
|
182
|
+
}
|
|
183
|
+
/** One input the server accepted, sanitized, in seq order, with the render time lag compensation
|
|
184
|
+
* judges it at (0: unstamped; a filled input takes the one of the input after its gap). */
|
|
185
|
+
interface AcceptedInput {
|
|
186
|
+
seq: number;
|
|
187
|
+
tick: number;
|
|
188
|
+
render: number;
|
|
189
|
+
input: InputData;
|
|
190
|
+
kind: AcceptKind;
|
|
191
|
+
}
|
|
192
|
+
interface AcceptOptions {
|
|
193
|
+
tickHz: number;
|
|
194
|
+
/** The newest snapshot id the server sent this seat (an `ackSnap` past it is ignored). */
|
|
195
|
+
snapSent: number;
|
|
196
|
+
}
|
|
197
|
+
interface AcceptResult {
|
|
198
|
+
seat: SeatRx;
|
|
199
|
+
accepted: AcceptedInput[];
|
|
200
|
+
/** Seqs consumed without a step: a gap longer than {@link gapFillMax}. */
|
|
201
|
+
skipped: number;
|
|
202
|
+
/** Set when the whole datagram was dropped; `seat` is then the very one given. */
|
|
203
|
+
fault?: FastFault;
|
|
204
|
+
}
|
|
205
|
+
/** The server's take of decoded uplink `u` for seat `seat` (§17.12 *Server dedup*): every held input
|
|
206
|
+
* and slot ascending through `accept(seq)` (a seq at or below the last accepted is dropped), a gap
|
|
207
|
+
* filled with the last input held or, past {@link gapFillMax}, skipped; `ackSnap` forward only. A
|
|
208
|
+
* datagram whose newest seq is past {@link seqAheadMax} is dropped whole. Nothing new: the very same
|
|
209
|
+
* seat. */
|
|
210
|
+
declare function acceptUplink(seat: SeatRx, u: Uplink, o: AcceptOptions): AcceptResult;
|
|
211
|
+
/** A snapshot's motion records, ascending by key; each record's bytes are the record codec's. */
|
|
212
|
+
type Records = readonly {
|
|
213
|
+
key: number;
|
|
214
|
+
bytes: Uint8Array;
|
|
215
|
+
}[];
|
|
216
|
+
/** A snapshot body: the keys removed since the base, then the records new or changed. */
|
|
217
|
+
interface SnapshotBody {
|
|
218
|
+
removed: number[];
|
|
219
|
+
records: Records;
|
|
220
|
+
}
|
|
221
|
+
/** The snapshot header the server writes. `base` 0 is a keyframe. */
|
|
222
|
+
interface SnapshotHead {
|
|
223
|
+
sid: number;
|
|
224
|
+
snap: number;
|
|
225
|
+
base: number;
|
|
226
|
+
tick: number;
|
|
227
|
+
inputAck: number;
|
|
228
|
+
/** The room clock (ms since room start, §16.1 `sNow`, truncated to a uint32) when the server wrote
|
|
229
|
+
* the snapshot: the value a WebSocket patch's TIMED prefix carries, so a fast seat's clock samples
|
|
230
|
+
* exactly what a WebSocket seat's does (the tick's nominal instant is up to a step earlier). */
|
|
231
|
+
sent: number;
|
|
232
|
+
}
|
|
233
|
+
/** The body that turns `base` into `next` (null `base`: a keyframe, every record). */
|
|
234
|
+
declare function diffSnapshot(base: Records | null, next: Records): SnapshotBody;
|
|
235
|
+
/** The base the server deltas against: the seat's `ackSnap` while it still holds that snapshot
|
|
236
|
+
* (`held`, its last {@link FAST_SNAPSHOT_HISTORY} sent ids), else 0 (a keyframe). */
|
|
237
|
+
declare const deltaBase: (held: readonly number[], ackSnap: number) => number;
|
|
238
|
+
/** The fragments of snapshot `head` with body `body` (§17.12 *Downlink snapshot*): `oversized` past
|
|
239
|
+
* {@link FAST_MAX_FRAGMENTS}, `field` for a value the wire cannot carry. */
|
|
240
|
+
declare function encodeSnapshot(head: SnapshotHead, body: SnapshotBody): FastResult<Uint8Array[]>;
|
|
241
|
+
/** One decoded snapshot fragment. */
|
|
242
|
+
interface Fragment {
|
|
243
|
+
snap: number;
|
|
244
|
+
base: number;
|
|
245
|
+
tick: number;
|
|
246
|
+
inputAck: number;
|
|
247
|
+
sent: number;
|
|
248
|
+
keyframe: boolean;
|
|
249
|
+
frag: number;
|
|
250
|
+
frags: number;
|
|
251
|
+
chunk: Uint8Array;
|
|
252
|
+
}
|
|
253
|
+
/** Decodes a snapshot fragment for seat `sid`; any fault drops it. */
|
|
254
|
+
declare function decodeFragment(bytes: Uint8Array, sid: number): FastResult<Fragment>;
|
|
255
|
+
/** Parses a reassembled body to the byte (keys ascending and unique, lengths in bounds, nothing left). */
|
|
256
|
+
declare function parseBody(bytes: Uint8Array): FastResult<SnapshotBody>;
|
|
257
|
+
/** `base`'s records less `body.removed`, with `body.records` written over them. */
|
|
258
|
+
declare function applyBody(base: Records, body: SnapshotBody): Records;
|
|
259
|
+
/** An applied snapshot the client holds as a base. */
|
|
260
|
+
interface HeldSnapshot {
|
|
261
|
+
snap: number;
|
|
262
|
+
tick: number;
|
|
263
|
+
inputAck: number;
|
|
264
|
+
sent: number;
|
|
265
|
+
records: Records;
|
|
266
|
+
}
|
|
267
|
+
/** A snapshot being gathered: its first fragment's header and the chunks so far. */
|
|
268
|
+
interface PartialSnapshot {
|
|
269
|
+
snap: number;
|
|
270
|
+
base: number;
|
|
271
|
+
tick: number;
|
|
272
|
+
inputAck: number;
|
|
273
|
+
sent: number;
|
|
274
|
+
keyframe: boolean;
|
|
275
|
+
frags: number;
|
|
276
|
+
chunks: readonly (Uint8Array | null)[];
|
|
277
|
+
}
|
|
278
|
+
/** The client's receive state: sets being gathered (ascending), applied snapshots held as bases
|
|
279
|
+
* (oldest first, at most {@link FAST_SNAPSHOT_HISTORY}) and the newest applied id (0: none). */
|
|
280
|
+
interface ClientRx {
|
|
281
|
+
partials: readonly PartialSnapshot[];
|
|
282
|
+
history: readonly HeldSnapshot[];
|
|
283
|
+
newest: number;
|
|
284
|
+
}
|
|
285
|
+
declare const emptyClientRx: () => ClientRx;
|
|
286
|
+
interface ReceiveResult {
|
|
287
|
+
rx: ClientRx;
|
|
288
|
+
/** The snapshot this fragment completed and applied, else null. */
|
|
289
|
+
applied: number | null;
|
|
290
|
+
fault?: FastFault;
|
|
291
|
+
}
|
|
292
|
+
/** The client's take of one snapshot datagram (§17.12 *Applying a snapshot*). A fault drops the
|
|
293
|
+
* fragment; while nothing was gathered for it, the answer is the very `rx` given. */
|
|
294
|
+
declare function receiveFragment(rx: ClientRx, bytes: Uint8Array, sid: number): ReceiveResult;
|
|
295
|
+
|
|
296
|
+
/** What a record describes, the first byte. */
|
|
297
|
+
declare const MotionRecordKind: {
|
|
298
|
+
readonly Entity: 1;
|
|
299
|
+
readonly Seat: 2;
|
|
300
|
+
};
|
|
301
|
+
type MotionRecordKind = (typeof MotionRecordKind)[keyof typeof MotionRecordKind];
|
|
302
|
+
/** An entity's motion fields (AuthorityEntity, §17.3, §17.7, §17.10), each at its default when absent. */
|
|
303
|
+
interface EntityMotion {
|
|
304
|
+
x: number;
|
|
305
|
+
y: number;
|
|
306
|
+
z: number;
|
|
307
|
+
vx: number;
|
|
308
|
+
vy: number;
|
|
309
|
+
vz: number;
|
|
310
|
+
yaw: number;
|
|
311
|
+
pitch: number;
|
|
312
|
+
duck: number;
|
|
313
|
+
spin: number;
|
|
314
|
+
fuel: number;
|
|
315
|
+
epoch: number;
|
|
316
|
+
mstate: number;
|
|
317
|
+
qx: number;
|
|
318
|
+
qy: number;
|
|
319
|
+
qz: number;
|
|
320
|
+
qw: number;
|
|
321
|
+
lvx: number;
|
|
322
|
+
lvy: number;
|
|
323
|
+
lvz: number;
|
|
324
|
+
avx: number;
|
|
325
|
+
avy: number;
|
|
326
|
+
avz: number;
|
|
327
|
+
sleep: boolean;
|
|
328
|
+
rest: number;
|
|
329
|
+
}
|
|
330
|
+
/** A seat's input echo and pacing counters (AuthorityPlayer `in*`, §17.11), each 0 when absent. */
|
|
331
|
+
interface SeatEcho {
|
|
332
|
+
inMoveX: number;
|
|
333
|
+
inMoveY: number;
|
|
334
|
+
inMoveZ: number;
|
|
335
|
+
inYaw: number;
|
|
336
|
+
inPitch: number;
|
|
337
|
+
inButtons: number;
|
|
338
|
+
inRep: number;
|
|
339
|
+
inStarve: number;
|
|
340
|
+
inBuf: number;
|
|
341
|
+
/** The receiving seat's own record only: its last input the server consumed (stepped or skipped)
|
|
342
|
+
* as of this snapshot, the ack its reconciler replays from (0 in every other seat's record). */
|
|
343
|
+
inAck: number;
|
|
344
|
+
}
|
|
345
|
+
/** One motion record: an entity's or a seat's, by its id. */
|
|
346
|
+
type MotionRecord = {
|
|
347
|
+
kind: typeof MotionRecordKind.Entity;
|
|
348
|
+
id: string;
|
|
349
|
+
motion: EntityMotion;
|
|
350
|
+
} | {
|
|
351
|
+
kind: typeof MotionRecordKind.Seat;
|
|
352
|
+
id: string;
|
|
353
|
+
motion: SeatEcho;
|
|
354
|
+
};
|
|
355
|
+
/** The longest id a record carries, in characters (AuthorityLimits MAX_ENTITY_ID_CHARS and seat ids). */
|
|
356
|
+
declare const RECORD_ID_MAX_CHARS = 64;
|
|
357
|
+
/** Every entity motion field at its default. */
|
|
358
|
+
declare const blankEntityMotion: () => EntityMotion;
|
|
359
|
+
/** Every seat motion field at its default. */
|
|
360
|
+
declare const blankSeatEcho: () => SeatEcho;
|
|
361
|
+
/** The schema's quantize on a symmetric clamped range of half-width `half`: the uint16 code. */
|
|
362
|
+
declare function quantizeCode(half: number, value: number): number;
|
|
363
|
+
/** The schema's dequantize: the float a decoder reads for `code`. */
|
|
364
|
+
declare const dequantizeCode: (half: number, code: number) => number;
|
|
365
|
+
/** The record's bytes (§17.12 *Motion records*); refuses (`field`) what it cannot carry. */
|
|
366
|
+
declare function encodeRecord(r: MotionRecord): FastResult<Uint8Array>;
|
|
367
|
+
/** Reads one record to the byte; any fault drops it (the whole snapshot it came in). Every field
|
|
368
|
+
* left out reads as its default. */
|
|
369
|
+
declare function decodeRecord(bytes: Uint8Array): FastResult<MotionRecord>;
|
|
370
|
+
|
|
371
|
+
/** A datagram socket to the room's fast endpoint. `send` never throws; a lost datagram is the
|
|
372
|
+
* channel's normal case. */
|
|
373
|
+
interface FastDatagramSocket {
|
|
374
|
+
send(bytes: Uint8Array): void;
|
|
375
|
+
close(): void;
|
|
376
|
+
/** True once the socket knows it can never carry the channel (a session that opens asynchronously
|
|
377
|
+
* and was refused): the seat falls back at once instead of waiting out a deadline. */
|
|
378
|
+
failed?(): boolean;
|
|
379
|
+
/** The longest datagram the socket can send now, where it knows one (WebTransport's
|
|
380
|
+
* `maxDatagramSize`): below the seat's largest uplink ({@link uplinkMaxBytes}), the seat falls back
|
|
381
|
+
* at once. */
|
|
382
|
+
maxDatagramBytes?(): number | undefined;
|
|
383
|
+
}
|
|
384
|
+
/** Opens a socket to a `__fast` `url`, handing each datagram that arrives to `receive`; null when this
|
|
385
|
+
* client cannot reach that kind of url (the seat then falls back at once). `bind` is the whole
|
|
386
|
+
* binding (the client always passes it), for an opener that reaches the channel another way
|
|
387
|
+
* (`bind.wt`, WebTransport). */
|
|
388
|
+
type FastSocketOpener = (url: string, receive: (bytes: Uint8Array) => void, bind?: FastBindMessage) => FastDatagramSocket | null;
|
|
389
|
+
/** Why a fast seat went back to the WebSocket. */
|
|
390
|
+
declare const FastFallbackReason: {
|
|
391
|
+
/** The `__fast` binding was malformed, this client cannot open its url, or its socket failed. */
|
|
392
|
+
readonly Unreachable: "unreachable";
|
|
393
|
+
/** No snapshot within {@link FAST_HANDSHAKE_MS} of the first datagram. */
|
|
394
|
+
readonly Handshake: "handshake";
|
|
395
|
+
/** No snapshot for {@link FAST_STALL_FALLBACK_MS} after the last one. */
|
|
396
|
+
readonly Stall: "stall";
|
|
397
|
+
/** The server asked for it (`__fast:off` from the server). */
|
|
398
|
+
readonly Server: "server";
|
|
399
|
+
};
|
|
400
|
+
type FastFallbackReason = (typeof FastFallbackReason)[keyof typeof FastFallbackReason];
|
|
401
|
+
/** One applied snapshot as the session uses it: its header and the motion records that differ from the
|
|
402
|
+
* previous applied snapshot's (a record that does not decode is left out: its id stays as it was). */
|
|
403
|
+
interface FastSnapshot {
|
|
404
|
+
snap: number;
|
|
405
|
+
tick: number;
|
|
406
|
+
/** The last input seq the server accepted (the header's `inputAck`). */
|
|
407
|
+
inputAck: number;
|
|
408
|
+
/** The room clock the server wrote it at (the header's `sent`, a WebSocket patch's TIMED `sNow`). */
|
|
409
|
+
sent: number;
|
|
410
|
+
changed: readonly MotionRecord[];
|
|
411
|
+
}
|
|
412
|
+
/** What one received datagram did. */
|
|
413
|
+
interface FastReceipt {
|
|
414
|
+
applied: FastSnapshot | null;
|
|
415
|
+
fault?: FastFault;
|
|
416
|
+
}
|
|
417
|
+
interface FastClientOptions {
|
|
418
|
+
bind: FastBindMessage;
|
|
419
|
+
tickHz: number;
|
|
420
|
+
open: FastSocketOpener;
|
|
421
|
+
/** The clock the deadlines run on (ms). */
|
|
422
|
+
now: () => number;
|
|
423
|
+
/** Each applied snapshot, after the receive state took it. */
|
|
424
|
+
onSnapshot?: (s: FastSnapshot) => void;
|
|
425
|
+
}
|
|
426
|
+
/** One seat's fast channel on one socket. */
|
|
427
|
+
interface FastClient {
|
|
428
|
+
readonly bind: FastBindMessage;
|
|
429
|
+
/** The snapshot receive state (reassembly, held bases, newest applied). */
|
|
430
|
+
readonly rx: ClientRx;
|
|
431
|
+
/** The newest input seq queued (0: none). */
|
|
432
|
+
readonly lastQueued: number;
|
|
433
|
+
/** True once closed: nothing is sent or received again. */
|
|
434
|
+
readonly closed: boolean;
|
|
435
|
+
/** An input sent this frame: seqs ascend by one from the first queued. */
|
|
436
|
+
queue(input: FastInput): void;
|
|
437
|
+
/** One datagram carrying the inputs queued since the last flush and the redundant window (one more for
|
|
438
|
+
* each further window's worth of new inputs); nothing when none was queued. */
|
|
439
|
+
flush(): void;
|
|
440
|
+
/** The input of seq `seq` while it is still held. */
|
|
441
|
+
inputAt(seq: number): FastInput | undefined;
|
|
442
|
+
/** When the input of seq `seq` was first sent (by `now`), for each of the newest {@link seqAheadMax}
|
|
443
|
+
* inputs: the server consumes an input well after it received it (its buffer, the next keyframe), and
|
|
444
|
+
* the round trip is timed from the send of the input a snapshot acks as consumed. */
|
|
445
|
+
sentAt(seq: number): number | undefined;
|
|
446
|
+
/** The latest decoded record of `id` of `kind` (from the newest applied snapshot), else undefined. */
|
|
447
|
+
motionOf(kind: MotionRecordKind, id: string): MotionRecord | undefined;
|
|
448
|
+
/** Whether a deadline has passed (§17.12 *Fallback*), by the injected clock. */
|
|
449
|
+
due(): FastFallbackReason | null;
|
|
450
|
+
/** One datagram from the socket. A fault, a stale or duplicate fragment or a datagram for another
|
|
451
|
+
* seat changes nothing. */
|
|
452
|
+
receive(bytes: Uint8Array): FastReceipt;
|
|
453
|
+
close(): void;
|
|
454
|
+
}
|
|
455
|
+
/** A `__fast` payload this client can use: sid a uint32, key {@link FAST_KEY_BYTES} bytes of lowercase
|
|
456
|
+
* hex, url a string. */
|
|
457
|
+
declare function isFastBind(m: unknown): m is FastBindMessage;
|
|
458
|
+
/** The inputs `uplinkFor` still needs once the server accepted through `inputAck`: the window's newest
|
|
459
|
+
* inputs, every input after the ack, and the one before the oldest of those (a press is a bit its
|
|
460
|
+
* previous input lacked). At most {@link seqAheadMax} of them. */
|
|
461
|
+
declare function heldInputs(sent: readonly FastInput[], inputAck: number, tickHz: number): FastInput[];
|
|
462
|
+
/** Opens a seat's fast channel on `o.bind`; null when the opener cannot reach its url. */
|
|
463
|
+
declare function openFastClient(o: FastClientOptions): FastClient | null;
|
|
464
|
+
|
|
465
|
+
export { blankEntityMotion as $, AcceptKind as A, type FastReceipt as B, type ClientRx as C, type FastResult as D, type EntityMotion as E, type FastSocketOpener as F, type FastSnapshot as G, type FastWebTransport as H, type Fragment as I, type HeldSnapshot as J, INPUT_REDUNDANCY_MAX as K, INPUT_REDUNDANCY_MIN as L, INPUT_REDUNDANCY_MS as M, type MotionRecord as N, MotionRecordKind as O, type PartialSnapshot as P, type ReceiveResult as Q, RECORD_ID_MAX_CHARS as R, type Records as S, type SeatEcho as T, type SeatRx as U, type SnapshotBody as V, type SnapshotHead as W, type Uplink as X, type UplinkOptions as Y, acceptUplink as Z, applyBody as _, type FastBindMessage as a, blankSeatEcho as a0, decodeFragment as a1, decodeRecord as a2, decodeUplink as a3, deltaBase as a4, dequantizeCode as a5, diffSnapshot as a6, emptyClientRx as a7, encodeRecord as a8, encodeSnapshot as a9, encodeUplink as aa, fromHex as ab, gapFillMax as ac, heldInputs as ad, inputRedundancyTicks as ae, isFastBind as af, isPress as ag, openFastClient as ah, parseBody as ai, quantizeCode as aj, receiveFragment as ak, renderStamp as al, seqAheadMax as am, toHex as an, uplinkFor as ao, uplinkMaxBytes as ap, FastFallbackReason as b, type AcceptOptions as c, type AcceptResult as d, type AcceptedInput as e, FAST_GAP_FILL_MS as f, FAST_HANDSHAKE_MS as g, FAST_KEY_BYTES as h, FAST_MAX_ACTIONS as i, FAST_MAX_DATAGRAM_BYTES as j, FAST_MAX_FRAGMENTS as k, FAST_MAX_RECORD_BYTES as l, FAST_REASSEMBLY_SLOTS as m, FAST_SEQ_AHEAD_MS as n, FAST_SNAPSHOT_HISTORY as o, FAST_STALL_FALLBACK_MS as p, FAST_VERSION as q, type FastBinding as r, type FastClient as s, type FastClientOptions as t, type FastDatagramSocket as u, FastFault as v, type FastInput as w, FastKind as x, FastMessage as y, type FastOffMessage as z };
|