@genex-ai/multiplayer 0.17.6 → 0.17.9

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 (40) hide show
  1. package/PROTOCOL.md +102 -31
  2. package/README.md +35 -14
  3. package/WORLD-GLTF.md +109 -0
  4. package/WORLD-SCENE.md +135 -0
  5. package/dist/authority/fast-udp.d.ts +4 -2
  6. package/dist/authority/fast-udp.js +8 -46
  7. package/dist/authority/fast-webtransport.d.ts +26 -4
  8. package/dist/authority/fast-webtransport.js +12 -115
  9. package/dist/authority/index.d.ts +379 -41
  10. package/dist/authority/index.js +247 -47
  11. package/dist/authority/module.d.ts +2 -1
  12. package/dist/authority/module.js +1 -1
  13. package/dist/bridge-authority.d.ts +3 -2
  14. package/dist/bridge-authority.global.js +10 -9
  15. package/dist/bridge-authority.js +11 -6
  16. package/dist/bridge.global.js +5 -5
  17. package/dist/bridge.js +3 -3
  18. package/dist/chunk-2NGXXQDS.js +1916 -0
  19. package/dist/{chunk-7QVOIHJF.js → chunk-DJ2XYETS.js} +2 -2
  20. package/dist/{chunk-Y6XHXDIK.js → chunk-MQYARUGU.js} +864 -539
  21. package/dist/{chunk-K3SF5VDS.js → chunk-OD6NRSX2.js} +3 -1
  22. package/dist/{chunk-UX3F6YTV.js → chunk-TEHGMFAG.js} +1 -1
  23. package/dist/chunk-TSYEWPVO.js +44 -0
  24. package/dist/chunk-TVTVIMIH.js +675 -0
  25. package/dist/{chunk-ZSWF4VCN.js → chunk-WEMRRIHH.js} +80 -2
  26. package/dist/chunk-YWQ3ALYY.js +69 -0
  27. package/dist/{chunk-5JGRFT2N.js → chunk-ZDGSUR6E.js} +879 -1898
  28. package/dist/{fast-client-CZ41v-oB.d.ts → fast-client--A88q0kk.d.ts} +85 -4
  29. package/dist/index.js +3 -3
  30. package/dist/level-build-CbRqrYJq.d.ts +140 -0
  31. package/dist/{module-DqtFb5wA.d.ts → module-CSpXrzFc.d.ts} +151 -400
  32. package/dist/{physics-predict-49udN3a1.d.ts → physics-predict-C7ZB1gDG.d.ts} +18 -3
  33. package/dist/protocol.d.ts +90 -2
  34. package/dist/protocol.js +17 -1
  35. package/dist/shot-world-D22tqOUT.d.ts +485 -0
  36. package/dist/world-fit/index.d.ts +549 -0
  37. package/dist/world-fit/index.js +4763 -0
  38. package/dist/world-fit/three.d.ts +77 -0
  39. package/dist/world-fit/three.js +140 -0
  40. package/package.json +15 -3
@@ -36,8 +36,26 @@ var AuthorityJoinOption = {
36
36
  /** The capabilities this client implements: at most {@link AuthorityCaps.MAX_CAPS} strings of at
37
37
  * most {@link AuthorityCaps.MAX_CAP_CHARS} characters; absent = []. Any other value is refused
38
38
  * with 400 by every authority room. */
39
- CAPS: "caps"
39
+ CAPS: "caps",
40
+ /** The client's SDK and version, `<sdk>/<version>` (§17.1): counted so a protocol bump can see whom it would
41
+ * strand. Telemetry only: anything malformed is counted as unknown, never refused. */
42
+ SDK: "sdk",
43
+ /** The pool this join asks for (§17.2 *Choosing a room*): rooms are found by it beside the game and
44
+ * the region. Absent or empty = the game's one pool, as a client that names no room has always had.
45
+ * Malformed (authority/room-key.ts `matchKeyOf`) is refused with 400. */
46
+ MATCH_KEY: "matchKey",
47
+ /** `true` on a `create` makes a private room: no matchmaking reaches it, only its code. Any other
48
+ * type is refused with 400. */
49
+ PRIVATE: "private",
50
+ /** The room this seat lost (§17.13 *Drain and rejoin*): a room code. Rooms are found by it too, so a
51
+ * lost room's seats meet in one successor, which alone reads the room's hand-off. Absent or empty =
52
+ * no successor. Malformed is refused with 400. */
53
+ SUCCESSOR_OF: "successorOf",
54
+ /** `true` keeps a successor closed to the game's pool: the lost room was private. Any other type is
55
+ * refused with 400. */
56
+ SUCCESSOR_CLOSED: "successorClosed"
40
57
  };
58
+ var AUTHORITY_SDK_LABEL = "ts/0.17.9";
41
59
  var AuthorityCaps = {
42
60
  /** Decodes and draws §17.10's physics bodies. A physics room refuses a join without it (426). An
43
61
  * SDK sends it only once its drawing passes `physics-draw.json` and, for an engine SDK, its own
@@ -68,6 +86,10 @@ var AuthorityCaps = {
68
86
  * beside `tick@1`. An SDK sends it only once it passes `fast-transport.json` and its transport
69
87
  * tests; a room that does not offer the channel ignores it. */
70
88
  FAST: "fast@1",
89
+ /** The seat can come back to the fast channel after a fallback (§17.12 *Return to the channel*): it asks
90
+ * for a fresh binding, proves the path and swaps. Sent beside `fast@1`; a host that does not know it
91
+ * ignores it, and a client asks only a host whose binding says `rev` 2. */
92
+ FAST_RETURN: "fast-return@1",
71
93
  MAX_CAPS: 16,
72
94
  MAX_CAP_CHARS: 32
73
95
  };
@@ -101,7 +123,8 @@ var AUTHORITY_SERVER_OWNED_JOIN_OPTIONS = [
101
123
  "seed",
102
124
  "units",
103
125
  "leaseId",
104
- "region"
126
+ "region",
127
+ "createdBy"
105
128
  ];
106
129
  var JoinError = {
107
130
  /** A server-owned option was sent. */
@@ -110,6 +133,9 @@ var JoinError = {
110
133
  AUTH_REQUIRED: 401,
111
134
  /** Token for another game ('wrong game'), or the room's guest cap is full. */
112
135
  FORBIDDEN: 403,
136
+ /** A join by code entered through another region than the room's (§17.2 *Choosing a room*): the
137
+ * client tries its next region. */
138
+ WRONG_REGION: 421,
113
139
  /** `protocol` missing or outside the room type's window ({@link RELAY_WINDOW},
114
140
  * {@link AUTHORITY_WINDOW}). Update the client. */
115
141
  PROTOCOL_UNSUPPORTED: 426,
@@ -123,6 +149,7 @@ var JoinError = {
123
149
  /** No authority server can take the room right now, or admission could not be checked. Retry later. */
124
150
  AUTHORITY_CAPACITY: 503
125
151
  };
152
+ var FREE_QUOTA_REFUSAL = "free_quota_exhausted";
126
153
  var STAGING_ROOM_PREFIX = "preview--";
127
154
  var roomKeyFor = (slug, channel) => channel === "staging" ? `${STAGING_ROOM_PREFIX}${slug}` : slug;
128
155
  var slugOfRoomKey = (key) => key.startsWith(STAGING_ROOM_PREFIX) ? key.slice(STAGING_ROOM_PREFIX.length) : key;
@@ -190,6 +217,14 @@ var RATE_LANES = {
190
217
  };
191
218
  var GLOBAL_RATE = { rate: 180, burst: 360 };
192
219
  var MAX_MESSAGES_PER_SECOND = 1200;
220
+ var AUTHORITY_MAX_MESSAGES_PER_SECOND = 2400;
221
+ var AUTHORITY_MAX_FRAME_BYTES = 4 * 1024;
222
+ var SeatCloseReason = {
223
+ /** Its socket passed {@link AUTHORITY_MAX_MESSAGES_PER_SECOND} (4002). */
224
+ MessageFlood: "message_flood",
225
+ /** Its input had not changed for the room's idle limit (4408). */
226
+ SeatIdle: "seat_idle"
227
+ };
193
228
  var MESSAGE_LANE = {
194
229
  [ClientMessage.STATE]: "state",
195
230
  [ClientMessage.STATE_SNAP]: "state",
@@ -235,12 +270,47 @@ var CloseCode = {
235
270
  REVOKED: 4401,
236
271
  /** A newer session of the same player took the seat (second tab, or the page reloaded). */
237
272
  SUPERSEDED: 4409,
273
+ /** Authority rooms: the seat's input had not changed for the room's idle limit (§17.6 *Room lifetime*). */
274
+ IDLE: 4408,
238
275
  // ---- authority rooms only (§17.6), terminal ----
239
276
  /** The operator or the platform halted the room (kill switch, takedown, disabled game). */
240
277
  MODULE_HALTED: 4500,
241
278
  /** The game's room module broke its limits or threw; the room is gone. */
242
279
  MODULE_FAULT: 4501
243
280
  };
281
+ var AuthorityCloseCode = {
282
+ /** A call ran past its hard timeout (an infinite loop, a long microtask). */
283
+ ModuleTimeout: "module_timeout",
284
+ /** The module hit its memory limit, or the heap poll caught it near the limit. */
285
+ ModuleMemory: "module_memory",
286
+ /** The module threw (or failed to load) too many times. */
287
+ ModuleError: "module_error",
288
+ /** The module's output broke the wire limits too many times. */
289
+ ModuleOutputInvalid: "module_output_invalid",
290
+ /** The module stayed under the hard timeout but over the mean CPU budget for a whole window. */
291
+ ModuleBudget: "module_budget",
292
+ /** The operator or the platform stopped the room (kill switch, takedown, disabled game). */
293
+ Halted: "halted",
294
+ /** isolated-vm's wall-clock backstop stopped a call while the guest's own CPU stayed under its limit: the
295
+ * host stalled (contention, a GC pause, descheduled). The guest was cut off mid-call, so it is
296
+ * never called again; the room ends, but the module is not at fault. */
297
+ HostStall: "host_stall",
298
+ /** The room's physics passed the physics meter for the third strike (§17.10). */
299
+ PhysicsBudget: "physics_budget",
300
+ /** The room reached its age limit (§17.6 *Room lifetime*), announced like a drain. Not a fault. */
301
+ RoomAge: "room_age",
302
+ /** The room sent more than its ceiling for a whole window (§17.6 *The downlink budget*): its module writes
303
+ * too much state or too many events for its seats. Lower what it writes; no strike comes first. */
304
+ ModuleBandwidth: "module_bandwidth",
305
+ /** The physics core answered `internal` (C§11.3): every physics room on that instance ends. Never
306
+ * the module's fault. */
307
+ PhysicsInternal: "physics_internal"
308
+ };
309
+ function authorityCloseCodeOf(reason) {
310
+ const known = Object.values(AuthorityCloseCode);
311
+ return known.includes(reason) ? reason : null;
312
+ }
313
+ var AUTHORITY_ROOM_ENDED_TEXT = "This match ended because of a problem with the game. Please try again.";
244
314
  var Limits = {
245
315
  ROOM_MAX_CLIENTS: 64,
246
316
  MAX_NAME_CHARS: 64,
@@ -413,6 +483,7 @@ export {
413
483
  RoomType,
414
484
  JoinOption,
415
485
  AuthorityJoinOption,
486
+ AUTHORITY_SDK_LABEL,
416
487
  AuthorityCaps,
417
488
  capsOf,
418
489
  AUTHORITY_PHYSICS_UNITS,
@@ -424,6 +495,7 @@ export {
424
495
  SERVER_OWNED_JOIN_OPTIONS,
425
496
  AUTHORITY_SERVER_OWNED_JOIN_OPTIONS,
426
497
  JoinError,
498
+ FREE_QUOTA_REFUSAL,
427
499
  STAGING_ROOM_PREFIX,
428
500
  roomKeyFor,
429
501
  slugOfRoomKey,
@@ -437,8 +509,14 @@ export {
437
509
  RATE_LANES,
438
510
  GLOBAL_RATE,
439
511
  MAX_MESSAGES_PER_SECOND,
512
+ AUTHORITY_MAX_MESSAGES_PER_SECOND,
513
+ AUTHORITY_MAX_FRAME_BYTES,
514
+ SeatCloseReason,
440
515
  MESSAGE_LANE,
441
516
  CloseCode,
517
+ AuthorityCloseCode,
518
+ authorityCloseCodeOf,
519
+ AUTHORITY_ROOM_ENDED_TEXT,
442
520
  Limits,
443
521
  RelayTiming,
444
522
  AuthorityTiming,
@@ -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
+ };