@genex-ai/multiplayer 0.17.1 → 0.17.8

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.
Files changed (45) hide show
  1. package/PROTOCOL.md +761 -9
  2. package/README.md +285 -1
  3. package/WORLD-GLTF.md +109 -0
  4. package/WORLD-SCENE.md +135 -0
  5. package/dist/authority/fast-udp.d.ts +15 -0
  6. package/dist/authority/fast-udp.js +14 -0
  7. package/dist/authority/fast-webtransport.d.ts +67 -0
  8. package/dist/authority/fast-webtransport.js +21 -0
  9. package/dist/authority/index.d.ts +2671 -0
  10. package/dist/authority/index.js +958 -0
  11. package/dist/authority/module.d.ts +2 -0
  12. package/dist/authority/module.js +12 -0
  13. package/dist/bridge-authority.d.ts +101 -0
  14. package/dist/bridge-authority.global.js +15 -0
  15. package/dist/bridge-authority.js +291 -0
  16. package/dist/bridge-core-Dhc73GC8.d.ts +27 -0
  17. package/dist/bridge.d.ts +26 -20
  18. package/dist/bridge.global.js +7 -7
  19. package/dist/bridge.js +17 -48
  20. package/dist/chunk-2NGXXQDS.js +1916 -0
  21. package/dist/chunk-4R7C2TLH.js +68 -0
  22. package/dist/{chunk-C52XFIB6.js → chunk-CSQEEANB.js} +217 -4
  23. package/dist/chunk-FPWP22Y7.js +143 -0
  24. package/dist/chunk-NFMGIKFZ.js +2627 -0
  25. package/dist/chunk-OD6NRSX2.js +29 -0
  26. package/dist/{chunk-3ZUPU3GT.js → chunk-QY3QQNDJ.js} +13 -122
  27. package/dist/chunk-QZOCUQJT.js +6900 -0
  28. package/dist/chunk-TSYEWPVO.js +44 -0
  29. package/dist/chunk-TVTVIMIH.js +675 -0
  30. package/dist/chunk-YWQ3ALYY.js +69 -0
  31. package/dist/fast-client--A88q0kk.d.ts +546 -0
  32. package/dist/index.d.ts +3 -5
  33. package/dist/index.js +3 -2
  34. package/dist/join-CGoInvUM.d.ts +6 -0
  35. package/dist/level-build-CbRqrYJq.d.ts +140 -0
  36. package/dist/module-CSpXrzFc.d.ts +1187 -0
  37. package/dist/physics-predict-C7ZB1gDG.d.ts +1223 -0
  38. package/dist/protocol.d.ts +222 -2
  39. package/dist/protocol.js +47 -1
  40. package/dist/shot-world-D22tqOUT.d.ts +485 -0
  41. package/dist/world-fit/index.d.ts +549 -0
  42. package/dist/world-fit/index.js +4763 -0
  43. package/dist/world-fit/three.d.ts +77 -0
  44. package/dist/world-fit/three.js +140 -0
  45. package/package.json +46 -4
@@ -0,0 +1,69 @@
1
+ // src/authority/fast-udp-socket.ts
2
+ var IPV6_FAMILY = 6;
3
+ var lookupTurns = /* @__PURE__ */ new Map();
4
+ var UDP_SCHEME = "udp:";
5
+ var PORT_MAX = 65535;
6
+ var CONNECT_QUEUE_MAX = 32;
7
+ var BARE_PATHS = ["", "/"];
8
+ var onlyHostAndPort = (u) => u.username === "" && u.password === "" && BARE_PATHS.includes(u.pathname) && u.search === "" && u.hash === "";
9
+ function udpEndpoint(url) {
10
+ let parsed;
11
+ try {
12
+ parsed = new URL(url);
13
+ } catch {
14
+ return null;
15
+ }
16
+ const port = Number(parsed.port);
17
+ const portOk = Number.isInteger(port) && port >= 1 && port <= PORT_MAX;
18
+ if (parsed.protocol !== UDP_SCHEME || !portOk || parsed.hostname === "" || !onlyHostAndPort(parsed)) return null;
19
+ return { host: parsed.hostname.replace(/^\[(.*)\]$/, "$1"), port };
20
+ }
21
+ var udpFastOpener = ({ createSocket, lookup, isIP }) => (url, receive) => {
22
+ const to = udpEndpoint(url);
23
+ if (to === null) return null;
24
+ let socket = null;
25
+ let connected = false;
26
+ let closed = false;
27
+ let waiting = [];
28
+ const attach = (address, family) => {
29
+ if (closed) return;
30
+ const s = createSocket(family === IPV6_FAMILY ? "udp6" : "udp4");
31
+ socket = s;
32
+ s.on("message", (msg) => receive(new Uint8Array(msg.buffer, msg.byteOffset, msg.byteLength)));
33
+ s.on("error", () => {
34
+ });
35
+ s.connect(to.port, address, () => {
36
+ connected = true;
37
+ if (!closed) for (const bytes of waiting) s.send(bytes);
38
+ waiting = [];
39
+ });
40
+ };
41
+ const literal = isIP(to.host);
42
+ if (literal !== 0) attach(to.host, literal);
43
+ else {
44
+ lookup(to.host, { all: true, verbatim: true }, (err, found) => {
45
+ if (err !== null || found.length === 0) return;
46
+ const turn = (lookupTurns.get(to.host) ?? 0) % found.length;
47
+ lookupTurns.set(to.host, turn + 1);
48
+ attach(found[turn].address, found[turn].family);
49
+ });
50
+ }
51
+ const fast = {
52
+ send(bytes) {
53
+ if (closed) return;
54
+ if (connected) socket?.send(bytes);
55
+ else waiting = [...waiting, bytes].slice(-CONNECT_QUEUE_MAX);
56
+ },
57
+ close() {
58
+ if (closed) return;
59
+ closed = true;
60
+ socket?.close();
61
+ }
62
+ };
63
+ return fast;
64
+ };
65
+
66
+ export {
67
+ udpEndpoint,
68
+ udpFastOpener
69
+ };
@@ -0,0 +1,546 @@
1
+ import { I as InputData } from './module-CSpXrzFc.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 goes as a partial snapshot (the host cuts it, §17.12). */
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
+ /** A seat with nothing to send sends an empty uplink this often while snapshots reach it (§17.12
35
+ * *Keepalive*): under {@link FAST_SNAPSHOT_HISTORY} snapshots at 120 Hz, so its deltas stay deltas, and far
36
+ * under the shortest NAT timer measured, 10 s (Richter et al., IMC 2016: 74% of NATs expire idle UDP state
37
+ * in 1 min or less, range 10 to 200 s; RFC 9308 §3.2: assume 30 s). */
38
+ declare const FAST_KEEPALIVE_MS = 250;
39
+ /** Likewise before its first snapshot: a lost first datagram or challenge costs no more than this. */
40
+ declare const FAST_KEEPALIVE_BIND_MS = 50;
41
+ /** A snapshot changing this many records is acknowledged as it lands by a seat with nothing else to send:
42
+ * its next delta shrinks by far more than the 57 bytes. */
43
+ declare const FAST_ACK_EAGER_RECORDS = 32;
44
+ /** No eager ack within this long of another uplink: a seat sending inputs every frame sends none. */
45
+ declare const FAST_ACK_EAGER_GAP_MS = 20;
46
+ /** A snapshot id at least this far past the client's newest is a return-path challenge (§17.12 *Return
47
+ * path*): the client echoes it at once, whatever the keepalive's clock says. */
48
+ declare const FAST_CHALLENGE_JUMP = 65536;
49
+ /** No frame for this long: the stall deadline's clock did not run, as the seat could not have read a
50
+ * snapshot or sent an uplink (a background tab, a suspended app, a long hitch). */
51
+ declare const FAST_FRAME_GAP_MS = 250;
52
+ /** The binding's `rev` from a host that takes empty uplinks and challenges addresses (§17.12 *Keepalive*)
53
+ * and offers a fresh binding to a seat that asks (*Return to the channel*). */
54
+ declare const FAST_BIND_REV = 2;
55
+ /** Snapshots a seat must apply on a fresh binding before its inputs and motion move to it: enough to trust
56
+ * the path both ways, little enough to swap within a tenth of a second. */
57
+ declare const FAST_RETURN_PROOF = 3;
58
+ /** A retry's delay at attempt 0, before jitter: a dropped-out Wi-Fi is usually back. */
59
+ declare const FAST_RETRY_BASE_MS = 1000;
60
+ /** Its most, before jitter: a blocked network costs a few datagrams a minute, forever. */
61
+ declare const FAST_RETRY_MAX_MS = 60000;
62
+ /** A seat that held the channel this long starts its next backoff from attempt 0; one that lost it sooner
63
+ * goes on climbing, so a flapping path does not swap back and forth. */
64
+ declare const FAST_RETURN_STABLE_MS = 30000;
65
+ /** The delay before retry number `attempt` (from 0), jittered by `unit` in [0, 1): doubling from
66
+ * {@link FAST_RETRY_BASE_MS} to {@link FAST_RETRY_MAX_MS}, half of it random ("equal jitter"), so seats
67
+ * behind one failed link do not come back as one. */
68
+ declare function fastRetryDelayMs(attempt: number, unit: number): number;
69
+ /** The reliable messages of the fast channel (§17.12): the server's binding at join and the client's
70
+ * fallback. Reserved names (`__`), so no game event can be mistaken for one. */
71
+ declare const FastMessage: {
72
+ /** Server → client: `{ sid, key, url }`, the seat's binding. */
73
+ readonly Bind: "__fast";
74
+ /** Client → server: `{ last }`, the last seq the client sent by datagram; its inputs go by
75
+ * `room.input()` from now on. */
76
+ readonly Off: "__fast:off";
77
+ /** Client → server: `{}`, a seat off the channel asks for a fresh binding (§17.12 *Return to the channel*). */
78
+ readonly Again: "__fast:again";
79
+ /** Client → server: `{ last }`, the last seq it sent by WebSocket; its inputs go by datagram from now on. */
80
+ readonly On: "__fast:on";
81
+ };
82
+ type FastMessage = (typeof FastMessage)[keyof typeof FastMessage];
83
+ /** Where a browser reaches the seat's channel (§17.12 *WebTransport*): a relay's WebTransport session
84
+ * `url` (`https://host:port/w/<worker>`) and the SHA-256 hashes (64 lowercase hex digits each) of the
85
+ * certificates it may serve, for `serverCertificateHashes`. The relay forwards the seat's datagrams,
86
+ * byte for byte, to the endpoint `url` names; the binding (`sid`, `key`) is the same. */
87
+ interface FastWebTransport {
88
+ url: string;
89
+ hashes: string[];
90
+ }
91
+ /** The `__fast` message's payload. `wt`, sent when the room's worker has a WebTransport relay, is read
92
+ * only by a browser; every other client ignores it. */
93
+ interface FastBindMessage {
94
+ sid: number;
95
+ key: string;
96
+ url: string;
97
+ wt?: FastWebTransport;
98
+ /** The host's revision of the channel ({@link FAST_BIND_REV}); absent from a host older than it, to which
99
+ * a client sends no empty uplink and asks for no fresh binding. */
100
+ rev?: number;
101
+ /** A fresh binding for a seat on the WebSocket (§17.12 *Return to the channel*): the WebSocket carries
102
+ * everything until the seat's `__fast:on`. */
103
+ probe?: boolean;
104
+ }
105
+ /** The `__fast:off` message's payload. */
106
+ interface FastOffMessage {
107
+ last: number;
108
+ }
109
+ /** The `__fast:on` message's payload. */
110
+ interface FastOnMessage {
111
+ last: number;
112
+ }
113
+ /** The datagram kinds, the second byte. */
114
+ declare const FastKind: {
115
+ readonly Uplink: 1;
116
+ readonly Snapshot: 2;
117
+ };
118
+ type FastKind = (typeof FastKind)[keyof typeof FastKind];
119
+ /** Why a datagram or a value was refused. */
120
+ declare const FastFault: {
121
+ readonly Oversized: "oversized";
122
+ readonly Truncated: "truncated";
123
+ readonly Trailing: "trailing";
124
+ readonly Version: "version";
125
+ readonly Kind: "kind";
126
+ readonly Session: "session";
127
+ readonly Key: "key";
128
+ readonly Count: "count";
129
+ readonly Fresh: "fresh";
130
+ readonly Actions: "actions";
131
+ readonly Flags: "flags";
132
+ readonly Order: "order";
133
+ readonly Field: "field";
134
+ readonly Ahead: "ahead";
135
+ readonly Fragment: "fragment";
136
+ readonly Mismatch: "mismatch";
137
+ readonly Duplicate: "duplicate";
138
+ readonly Keyframe: "keyframe";
139
+ readonly Body: "body";
140
+ readonly Base: "base";
141
+ readonly Stale: "stale";
142
+ };
143
+ type FastFault = (typeof FastFault)[keyof typeof FastFault];
144
+ /** How the server came by an accepted input. */
145
+ declare const AcceptKind: {
146
+ /** First sent in the datagram that brought it. */
147
+ readonly Fresh: "fresh";
148
+ /** Sent first in an earlier datagram that was lost; carried again by redundancy or as an action. */
149
+ readonly Recovered: "recovered";
150
+ /** No datagram the server got carried it: the last input held stands in for it. */
151
+ readonly Fill: "fill";
152
+ };
153
+ type AcceptKind = (typeof AcceptKind)[keyof typeof AcceptKind];
154
+ type FastResult<T> = {
155
+ ok: true;
156
+ value: T;
157
+ } | {
158
+ ok: false;
159
+ fault: FastFault;
160
+ };
161
+ /** One input on the wire: its client sequence, its `tick@1` field, its render time and its payload. */
162
+ interface FastInput {
163
+ seq: number;
164
+ tick: number;
165
+ /** The render time its sender drew the world at when it sent it (§17.5): the room clock, whole ms,
166
+ * as its WebSocket input frame's stamp would carry it; 0: not stamped (no synced clock). */
167
+ render: number;
168
+ input: InputData;
169
+ }
170
+ /** An uplink datagram's content. `window` is contiguous, oldest first; `actions` ascend below it. */
171
+ interface Uplink {
172
+ sid: number;
173
+ key: string;
174
+ ackSnap: number;
175
+ fresh: number;
176
+ actions: FastInput[];
177
+ window: FastInput[];
178
+ }
179
+ /** A seat's fast-channel binding, from the reliable `fast` message. */
180
+ interface FastBinding {
181
+ sid: number;
182
+ key: string;
183
+ }
184
+ /** The redundant window at `tickHz`: {@link INPUT_REDUNDANCY_MS} of inputs, within its bounds. */
185
+ declare function inputRedundancyTicks(tickHz: number): number;
186
+ /** The longest uplink a seat at `tickHz` sends: the header, {@link FAST_MAX_ACTIONS} actions and a full
187
+ * window of slots, each at its largest (a socket that cannot carry it cannot carry the channel). */
188
+ declare function uplinkMaxBytes(tickHz: number): number;
189
+ /** The longest gap filled at `tickHz` ({@link FAST_GAP_FILL_MS} of ticks). */
190
+ declare const gapFillMax: (tickHz: number) => number;
191
+ /** The farthest a datagram's newest seq may be past the last accepted at `tickHz`. */
192
+ declare const seqAheadMax: (tickHz: number) => number;
193
+ /** Lowercase hex of `bytes`. */
194
+ declare function toHex(bytes: Uint8Array): string;
195
+ /** The bytes of hex text `hex` (an even count of hex digits; anything else reads as no bytes). */
196
+ declare function fromHex(hex: string): Uint8Array;
197
+ /** An input whose buttons gain a bit over `previous`'s. */
198
+ declare const isPress: (previous: InputData | null, input: InputData) => boolean;
199
+ /** Encodes uplink `u` (§17.12 *Uplink datagram*); refuses (`field`) what the wire cannot carry. */
200
+ declare function encodeUplink(u: Uplink): FastResult<Uint8Array>;
201
+ /** Decodes an uplink datagram for the seat bound by `binding`: checked whole, the session and key
202
+ * first; any fault drops it. Payloads are as sent (the server sanitizes as it accepts). */
203
+ declare function decodeUplink(bytes: Uint8Array, binding: FastBinding): FastResult<Uplink>;
204
+ /** The render time a fast input carries (§17.12 *Render-time stamp*), the stamp its WebSocket input
205
+ * frame would carry: `round(serverNow) − round(renderDelay + rtt/2)` whole ms, the delay clamped to
206
+ * [0, 65535], 0 when that is not above 0 or the clock has not synced (`serverNow` null). */
207
+ declare function renderStamp(serverNow: number | null, renderDelay: number, rtt: number): number;
208
+ /** What {@link uplinkFor} needs beside the inputs. */
209
+ interface UplinkOptions {
210
+ sid: number;
211
+ key: string;
212
+ /** The newest snapshot the client applied. */
213
+ ackSnap: number;
214
+ /** The server's last accepted input seq, from the newest applied snapshot's `inputAck`. */
215
+ inputAck: number;
216
+ /** How many of the newest inputs this datagram sends first. */
217
+ fresh: number;
218
+ tickHz: number;
219
+ }
220
+ /** The client's next uplink (§17.12 *Redundancy and one-shot inputs*): `sent` is every input it still
221
+ * holds, ascending and contiguous, back past `inputAck`; the window is the newest
222
+ * {@link inputRedundancyTicks}, and the oldest {@link FAST_MAX_ACTIONS} presses before it that the
223
+ * server has not acked ride as actions. */
224
+ declare function uplinkFor(sent: readonly FastInput[], o: UplinkOptions): Uplink;
225
+ /** A seat's receive state: the last accepted seq (from `seqBase` at bind), the last accepted input
226
+ * (null before the first) and the newest snapshot the seat acknowledged. */
227
+ interface SeatRx {
228
+ last: number;
229
+ input: InputData | null;
230
+ ackSnap: number;
231
+ }
232
+ /** One input the server accepted, sanitized, in seq order, with the render time lag compensation
233
+ * judges it at (0: unstamped; a filled input takes the one of the input after its gap). */
234
+ interface AcceptedInput {
235
+ seq: number;
236
+ tick: number;
237
+ render: number;
238
+ input: InputData;
239
+ kind: AcceptKind;
240
+ }
241
+ interface AcceptOptions {
242
+ tickHz: number;
243
+ /** The newest snapshot id the server sent this seat (an `ackSnap` past it is ignored). */
244
+ snapSent: number;
245
+ }
246
+ interface AcceptResult {
247
+ seat: SeatRx;
248
+ accepted: AcceptedInput[];
249
+ /** Seqs consumed without a step: a gap longer than {@link gapFillMax}. */
250
+ skipped: number;
251
+ /** Set when the whole datagram was dropped; `seat` is then the very one given. */
252
+ fault?: FastFault;
253
+ }
254
+ /** The server's take of decoded uplink `u` for seat `seat` (§17.12 *Server dedup*): every held input
255
+ * and slot ascending through `accept(seq)` (a seq at or below the last accepted is dropped), a gap
256
+ * filled with the last input held or, past {@link gapFillMax}, skipped; `ackSnap` forward only. A
257
+ * datagram whose newest seq is past {@link seqAheadMax} is dropped whole. Nothing new: the very same
258
+ * seat. */
259
+ declare function acceptUplink(seat: SeatRx, u: Uplink, o: AcceptOptions): AcceptResult;
260
+ /** A snapshot's motion records, ascending by key; each record's bytes are the record codec's. */
261
+ type Records = readonly {
262
+ key: number;
263
+ bytes: Uint8Array;
264
+ }[];
265
+ /** A snapshot body: the keys removed since the base, then the records new or changed. */
266
+ interface SnapshotBody {
267
+ removed: number[];
268
+ records: Records;
269
+ }
270
+ /** The snapshot header the server writes. `base` 0 is a keyframe. */
271
+ interface SnapshotHead {
272
+ sid: number;
273
+ snap: number;
274
+ base: number;
275
+ tick: number;
276
+ inputAck: number;
277
+ /** The room clock (ms since room start, §16.1 `sNow`, truncated to a uint32) when the server wrote
278
+ * the snapshot: the value a WebSocket patch's TIMED prefix carries, so a fast seat's clock samples
279
+ * exactly what a WebSocket seat's does (the tick's nominal instant is up to a step earlier). */
280
+ sent: number;
281
+ }
282
+ /** The body that turns `base` into `next` (null `base`: a keyframe, every record). */
283
+ declare function diffSnapshot(base: Records | null, next: Records): SnapshotBody;
284
+ /** The base the server deltas against: the seat's `ackSnap` while it still holds that snapshot
285
+ * (`held`, its last {@link FAST_SNAPSHOT_HISTORY} sent ids), else 0 (a keyframe). */
286
+ declare const deltaBase: (held: readonly number[], ackSnap: number) => number;
287
+ /** The fragments of snapshot `head` with body `body` (§17.12 *Downlink snapshot*): `oversized` past
288
+ * {@link FAST_MAX_FRAGMENTS}, `field` for a value the wire cannot carry. */
289
+ declare function encodeSnapshot(head: SnapshotHead, body: SnapshotBody): FastResult<Uint8Array[]>;
290
+ /** One decoded snapshot fragment. */
291
+ interface Fragment {
292
+ snap: number;
293
+ base: number;
294
+ tick: number;
295
+ inputAck: number;
296
+ sent: number;
297
+ keyframe: boolean;
298
+ frag: number;
299
+ frags: number;
300
+ chunk: Uint8Array;
301
+ }
302
+ /** Decodes a snapshot fragment for seat `sid`; any fault drops it. */
303
+ declare function decodeFragment(bytes: Uint8Array, sid: number): FastResult<Fragment>;
304
+ /** Parses a reassembled body to the byte (keys ascending and unique, lengths in bounds, nothing left). */
305
+ declare function parseBody(bytes: Uint8Array): FastResult<SnapshotBody>;
306
+ /** `base`'s records less `body.removed`, with `body.records` written over them. */
307
+ declare function applyBody(base: Records, body: SnapshotBody): Records;
308
+ /** An applied snapshot the client holds as a base. */
309
+ interface HeldSnapshot {
310
+ snap: number;
311
+ tick: number;
312
+ inputAck: number;
313
+ sent: number;
314
+ records: Records;
315
+ }
316
+ /** A snapshot being gathered: its first fragment's header and the chunks so far. */
317
+ interface PartialSnapshot {
318
+ snap: number;
319
+ base: number;
320
+ tick: number;
321
+ inputAck: number;
322
+ sent: number;
323
+ keyframe: boolean;
324
+ frags: number;
325
+ chunks: readonly (Uint8Array | null)[];
326
+ }
327
+ /** The client's receive state: sets being gathered (ascending), applied snapshots held as bases
328
+ * (oldest first, at most {@link FAST_SNAPSHOT_HISTORY}) and the newest applied id (0: none). */
329
+ interface ClientRx {
330
+ partials: readonly PartialSnapshot[];
331
+ history: readonly HeldSnapshot[];
332
+ newest: number;
333
+ }
334
+ declare const emptyClientRx: () => ClientRx;
335
+ interface ReceiveResult {
336
+ rx: ClientRx;
337
+ /** The snapshot this fragment completed and applied, else null. */
338
+ applied: number | null;
339
+ fault?: FastFault;
340
+ }
341
+ /** The client's take of one snapshot datagram (§17.12 *Applying a snapshot*). A fault drops the
342
+ * fragment; while nothing was gathered for it, the answer is the very `rx` given. */
343
+ declare function receiveFragment(rx: ClientRx, bytes: Uint8Array, sid: number): ReceiveResult;
344
+
345
+ /** What a record describes, the first byte. */
346
+ declare const MotionRecordKind: {
347
+ readonly Entity: 1;
348
+ readonly Seat: 2;
349
+ };
350
+ type MotionRecordKind = (typeof MotionRecordKind)[keyof typeof MotionRecordKind];
351
+ /** An entity's motion fields (AuthorityEntity, §17.3, §17.7, §17.10), each at its default when absent. */
352
+ interface EntityMotion {
353
+ x: number;
354
+ y: number;
355
+ z: number;
356
+ vx: number;
357
+ vy: number;
358
+ vz: number;
359
+ yaw: number;
360
+ pitch: number;
361
+ duck: number;
362
+ spin: number;
363
+ fuel: number;
364
+ epoch: number;
365
+ mstate: number;
366
+ qx: number;
367
+ qy: number;
368
+ qz: number;
369
+ qw: number;
370
+ lvx: number;
371
+ lvy: number;
372
+ lvz: number;
373
+ avx: number;
374
+ avy: number;
375
+ avz: number;
376
+ sleep: boolean;
377
+ rest: number;
378
+ }
379
+ /** A seat's input echo and pacing counters (AuthorityPlayer `in*`, §17.11), each 0 when absent. */
380
+ interface SeatEcho {
381
+ inMoveX: number;
382
+ inMoveY: number;
383
+ inMoveZ: number;
384
+ inYaw: number;
385
+ inPitch: number;
386
+ inButtons: number;
387
+ inRep: number;
388
+ inStarve: number;
389
+ inBuf: number;
390
+ /** The receiving seat's own record only: its last input the server consumed (stepped or skipped)
391
+ * as of this snapshot, the ack its reconciler replays from (0 in every other seat's record). */
392
+ inAck: number;
393
+ }
394
+ /** One motion record: an entity's or a seat's, by its id. */
395
+ type MotionRecord = {
396
+ kind: typeof MotionRecordKind.Entity;
397
+ id: string;
398
+ motion: EntityMotion;
399
+ } | {
400
+ kind: typeof MotionRecordKind.Seat;
401
+ id: string;
402
+ motion: SeatEcho;
403
+ };
404
+ /** The longest id a record carries, in characters (AuthorityLimits MAX_ENTITY_ID_CHARS and seat ids). */
405
+ declare const RECORD_ID_MAX_CHARS = 64;
406
+ /** Every entity motion field at its default. */
407
+ declare const blankEntityMotion: () => EntityMotion;
408
+ /** Every seat motion field at its default. */
409
+ declare const blankSeatEcho: () => SeatEcho;
410
+ /** The schema's quantize on a symmetric clamped range of half-width `half`: the uint16 code. */
411
+ declare function quantizeCode(half: number, value: number): number;
412
+ /** The schema's dequantize: the float a decoder reads for `code`. */
413
+ declare const dequantizeCode: (half: number, code: number) => number;
414
+ /** The record's bytes (§17.12 *Motion records*); refuses (`field`) what it cannot carry. */
415
+ declare function encodeRecord(r: MotionRecord): FastResult<Uint8Array>;
416
+ /** Reads one record to the byte; any fault drops it (the whole snapshot it came in). Every field
417
+ * left out reads as its default. */
418
+ declare function decodeRecord(bytes: Uint8Array): FastResult<MotionRecord>;
419
+
420
+ /** Round trips a socket that opens asynchronously may take to open, where that is longer than
421
+ * {@link FAST_HANDSHAKE_MS}: WebTransport over QUIC and HTTP/3 took three in Safari 26 (Chromium's two
422
+ * are within it), so a long route's seat is not sent back for its session's own handshake. */
423
+ declare const FAST_OPEN_ROUND_TRIPS = 4;
424
+ /** A datagram socket to the room's fast endpoint. `send` never throws; a lost datagram is the
425
+ * channel's normal case. */
426
+ interface FastDatagramSocket {
427
+ send(bytes: Uint8Array): void;
428
+ close(): void;
429
+ /** True once the socket knows it can never carry the channel (a session that opens asynchronously
430
+ * and was refused): the seat falls back at once instead of waiting out a deadline. */
431
+ failed?(): boolean;
432
+ /** The longest datagram the socket can send now, where it knows one (WebTransport's
433
+ * `maxDatagramSize`): below the seat's largest uplink ({@link uplinkMaxBytes}), the seat falls back
434
+ * at once. */
435
+ maxDatagramBytes?(): number | undefined;
436
+ /** True while a socket that opens asynchronously is still opening (WebTransport: round trips before a
437
+ * first datagram can leave). The handshake then runs twice: to the socket opening, {@link FAST_HANDSHAKE_MS}
438
+ * or {@link FAST_OPEN_ROUND_TRIPS} of the seat's round trips, whichever is longer; and from then to the
439
+ * first snapshot, {@link FAST_HANDSHAKE_MS}. */
440
+ opening?(): boolean;
441
+ }
442
+ /** Opens a socket to a `__fast` `url`, handing each datagram that arrives to `receive`; null when this
443
+ * client cannot reach that kind of url (the seat then falls back at once). `bind` is the whole
444
+ * binding (the client always passes it), for an opener that reaches the channel another way
445
+ * (`bind.wt`, WebTransport). */
446
+ type FastSocketOpener = ((url: string, receive: (bytes: Uint8Array) => void, bind?: FastBindMessage) => FastDatagramSocket | null) & {
447
+ /** Why this opener answers null for `bind` (default {@link FastFallbackReason.Unreachable}). */
448
+ whyNull?: (bind: FastBindMessage) => FastFallbackReason;
449
+ };
450
+ /** Why a fast seat went back to the WebSocket. */
451
+ declare const FastFallbackReason: {
452
+ /** The `__fast` binding was malformed, this client cannot open its url, or its socket failed. */
453
+ readonly Unreachable: "unreachable";
454
+ /** No snapshot within {@link FAST_HANDSHAKE_MS} of the first datagram. */
455
+ readonly Handshake: "handshake";
456
+ /** No snapshot for {@link FAST_STALL_FALLBACK_MS} after the last one. */
457
+ readonly Stall: "stall";
458
+ /** The server asked for it (`__fast:off` from the server). */
459
+ readonly Server: "server";
460
+ /** A browser with no `WebTransport` (or not a secure context): the seat never asked for the channel. */
461
+ readonly NoWebTransport: "no-webtransport";
462
+ /** The binding carries no `wt` this browser can open: the room's server runs no relay. */
463
+ readonly NoRelay: "no-relay";
464
+ /** The seat asked and no binding came within {@link FAST_HANDSHAKE_MS} of its first frame: the room's
465
+ * server has no channel, or is too old to offer it. The seat was on the WebSocket all along, and a
466
+ * binding that comes after all still opens. */
467
+ readonly NotOffered: "not-offered";
468
+ };
469
+ type FastFallbackReason = (typeof FastFallbackReason)[keyof typeof FastFallbackReason];
470
+ /** One applied snapshot as the session uses it: its header and the motion records that differ from the
471
+ * previous applied snapshot's (a record that does not decode is left out: its id stays as it was). */
472
+ interface FastSnapshot {
473
+ snap: number;
474
+ tick: number;
475
+ /** The last input seq the server accepted (the header's `inputAck`). */
476
+ inputAck: number;
477
+ /** The room clock the server wrote it at (the header's `sent`, a WebSocket patch's TIMED `sNow`). */
478
+ sent: number;
479
+ changed: readonly MotionRecord[];
480
+ }
481
+ /** What one received datagram did. */
482
+ interface FastReceipt {
483
+ applied: FastSnapshot | null;
484
+ fault?: FastFault;
485
+ }
486
+ interface FastClientOptions {
487
+ bind: FastBindMessage;
488
+ tickHz: number;
489
+ open: FastSocketOpener;
490
+ /** The clock the deadlines run on (ms). */
491
+ now: () => number;
492
+ /** The seat's round trip as measured so far (ms; 0 or absent: not yet known). */
493
+ roundTripMs?: () => number;
494
+ /** Each applied snapshot, after the receive state took it. */
495
+ onSnapshot?: (s: FastSnapshot) => void;
496
+ }
497
+ /** One seat's fast channel on one socket. */
498
+ interface FastClient {
499
+ readonly bind: FastBindMessage;
500
+ /** The snapshot receive state (reassembly, held bases, newest applied). */
501
+ readonly rx: ClientRx;
502
+ /** The newest input seq queued (0: none). */
503
+ readonly lastQueued: number;
504
+ /** True once closed: nothing is sent or received again. */
505
+ readonly closed: boolean;
506
+ /** True while the socket is still opening. */
507
+ opening(): boolean;
508
+ /** An input sent this frame: seqs ascend by one from the first queued. */
509
+ queue(input: FastInput): void;
510
+ /** One datagram carrying the inputs queued since the last flush and the redundant window (one more for
511
+ * each further window's worth of new inputs); nothing when none was queued. */
512
+ flush(): void;
513
+ /** An empty uplink when one is due (§17.12 *Keepalive*), from a host that takes them: the seat binds with
514
+ * no input, its snapshots stay acknowledged and its NAT mapping stays open. */
515
+ keepalive(): void;
516
+ /** The input of seq `seq` while it is still held. */
517
+ inputAt(seq: number): FastInput | undefined;
518
+ /** When the input of seq `seq` was first sent (by `now`), for each of the newest {@link seqAheadMax}
519
+ * inputs: the server consumes an input well after it received it (its buffer, the next keyframe), and
520
+ * the round trip is timed from the send of the input a snapshot acks as consumed. */
521
+ sentAt(seq: number): number | undefined;
522
+ /** The latest decoded record of `id` of `kind` (from the newest applied snapshot), else undefined. */
523
+ motionOf(kind: MotionRecordKind, id: string): MotionRecord | undefined;
524
+ /** Every record of the newest applied snapshot. */
525
+ records(): MotionRecord[];
526
+ /** A frame begins: time since the last frame past {@link FAST_FRAME_GAP_MS} is taken off both deadlines'
527
+ * clocks (§17.12 *Keepalive*), so a seat whose frames stopped does not read its own stop as a dead path. */
528
+ framed(): void;
529
+ /** Whether a deadline has passed (§17.12 *Fallback*), by the injected clock. */
530
+ due(): FastFallbackReason | null;
531
+ /** One datagram from the socket. A fault, a stale or duplicate fragment or a datagram for another
532
+ * seat changes nothing. */
533
+ receive(bytes: Uint8Array): FastReceipt;
534
+ close(): void;
535
+ }
536
+ /** A `__fast` payload this client can use: sid a uint32, key {@link FAST_KEY_BYTES} bytes of lowercase
537
+ * hex, url a string. */
538
+ declare function isFastBind(m: unknown): m is FastBindMessage;
539
+ /** The inputs `uplinkFor` still needs once the server accepted through `inputAck`: the window's newest
540
+ * inputs, every input after the ack, and the one before the oldest of those (a press is a bit its
541
+ * previous input lacked). At most {@link seqAheadMax} of them. */
542
+ declare function heldInputs(sent: readonly FastInput[], inputAck: number, tickHz: number): FastInput[];
543
+ /** Opens a seat's fast channel on `o.bind`; null when the opener cannot reach its url. */
544
+ declare function openFastClient(o: FastClientOptions): FastClient | null;
545
+
546
+ export { MotionRecordKind as $, AcceptKind as A, FAST_SNAPSHOT_HISTORY as B, type ClientRx as C, FAST_STALL_FALLBACK_MS as D, type EntityMotion as E, type FastSocketOpener as F, FAST_VERSION as G, type FastBinding as H, type FastClient as I, type FastClientOptions as J, type FastDatagramSocket as K, FastFault as L, type FastInput as M, FastKind as N, FastMessage as O, type FastOffMessage as P, type FastOnMessage as Q, type FastReceipt as R, type FastResult as S, type FastSnapshot as T, type FastWebTransport as U, type Fragment as V, type HeldSnapshot as W, INPUT_REDUNDANCY_MAX as X, INPUT_REDUNDANCY_MIN as Y, INPUT_REDUNDANCY_MS as Z, type MotionRecord as _, FastFallbackReason as a, type PartialSnapshot as a0, RECORD_ID_MAX_CHARS as a1, type ReceiveResult as a2, type Records as a3, type SeatEcho as a4, type SeatRx as a5, type SnapshotBody as a6, type SnapshotHead as a7, type Uplink as a8, type UplinkOptions as a9, seqAheadMax as aA, toHex as aB, uplinkFor as aC, uplinkMaxBytes as aD, acceptUplink as aa, applyBody as ab, blankEntityMotion as ac, blankSeatEcho as ad, decodeFragment as ae, decodeRecord as af, decodeUplink as ag, deltaBase as ah, dequantizeCode as ai, diffSnapshot as aj, emptyClientRx as ak, encodeRecord as al, encodeSnapshot as am, encodeUplink as an, fastRetryDelayMs as ao, fromHex as ap, gapFillMax as aq, heldInputs as ar, inputRedundancyTicks as as, isFastBind as at, isPress as au, openFastClient as av, parseBody as aw, quantizeCode as ax, receiveFragment as ay, renderStamp as az, type FastBindMessage as b, type AcceptOptions as c, type AcceptResult as d, type AcceptedInput as e, FAST_ACK_EAGER_GAP_MS as f, FAST_ACK_EAGER_RECORDS as g, FAST_BIND_REV as h, FAST_CHALLENGE_JUMP as i, FAST_FRAME_GAP_MS as j, FAST_GAP_FILL_MS as k, FAST_HANDSHAKE_MS as l, FAST_KEEPALIVE_BIND_MS as m, FAST_KEEPALIVE_MS as n, FAST_KEY_BYTES as o, FAST_MAX_ACTIONS as p, FAST_MAX_DATAGRAM_BYTES as q, FAST_MAX_FRAGMENTS as r, FAST_MAX_RECORD_BYTES as s, FAST_OPEN_ROUND_TRIPS as t, FAST_REASSEMBLY_SLOTS as u, FAST_RETRY_BASE_MS as v, FAST_RETRY_MAX_MS as w, FAST_RETURN_PROOF as x, FAST_RETURN_STABLE_MS as y, FAST_SEQ_AHEAD_MS as z };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { ObjectControlOperation as ObjectControlOperation$1, ObjectControlReason as ObjectControlReason$1 } from './protocol.js';
2
+ import { A as AuthSource } from './join-CGoInvUM.js';
3
+ export { C as ColyseusAuth } from './join-CGoInvUM.js';
2
4
  export { ObjectState, PlayerState, RoomState } from './schema.js';
3
5
  import '@colyseus/schema';
4
6
 
@@ -39,10 +41,6 @@ type VoiceJoinOptions = {
39
41
  speakingThreshold?: number;
40
42
  };
41
43
 
42
- type ColyseusAuth = {
43
- embedToken: string;
44
- };
45
- type AuthSource = ColyseusAuth | (() => ColyseusAuth | undefined);
46
44
  type ConnectOptions = {
47
45
  /** Regional relay urls (pass `getColyseusUrls()`) — the SDK probes each and connects to the
48
46
  * lowest-latency one (a probe that times out falls back, it never fails the join); a single entry
@@ -562,4 +560,4 @@ declare function createPrivate<TState extends Record<string, unknown> = never>(o
562
560
  */
563
561
  declare function joinPrivate<TState extends Record<string, unknown> = never>(code: string, opts: MatchmakeOptions): Promise<Matchmaking<TState>>;
564
562
 
565
- export { type AuthSource, type ChatApi, type ChatMessage, type ColyseusAuth, type ConnectOptions, type MatchStatus, type MatchmakeOptions, type Matchmaking, type MatchmakingView, type ObjectControlOperation, type ObjectControlOptions, type ObjectControlReason, type ObjectControlResult, type ObjectView, type PlayerView, type PredictSmoothing, type PrivateMatch, type Session, type SessionOptions, type VoiceApi, type VoiceJoinOptions, type VoiceParticipant, connect, createPrivate, joinPrivate, matchmake };
563
+ export { AuthSource, type ChatApi, type ChatMessage, type ConnectOptions, type MatchStatus, type MatchmakeOptions, type Matchmaking, type MatchmakingView, type ObjectControlOperation, type ObjectControlOptions, type ObjectControlReason, type ObjectControlResult, type ObjectView, type PlayerView, type PredictSmoothing, type PrivateMatch, type Session, type SessionOptions, type VoiceApi, type VoiceJoinOptions, type VoiceParticipant, connect, createPrivate, joinPrivate, matchmake };
package/dist/index.js CHANGED
@@ -3,8 +3,9 @@ import {
3
3
  createPrivate,
4
4
  joinPrivate,
5
5
  matchmake
6
- } from "./chunk-3ZUPU3GT.js";
7
- import "./chunk-C52XFIB6.js";
6
+ } from "./chunk-QY3QQNDJ.js";
7
+ import "./chunk-FPWP22Y7.js";
8
+ import "./chunk-CSQEEANB.js";
8
9
  export {
9
10
  connect,
10
11
  createPrivate,
@@ -0,0 +1,6 @@
1
+ type ColyseusAuth = {
2
+ embedToken: string;
3
+ };
4
+ type AuthSource = ColyseusAuth | (() => ColyseusAuth | undefined);
5
+
6
+ export type { AuthSource as A, ColyseusAuth as C };