@genex-ai/multiplayer 0.17.6 → 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 (40) hide show
  1. package/PROTOCOL.md +101 -30
  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-ZSWF4VCN.js → chunk-CSQEEANB.js} +78 -2
  20. package/dist/{chunk-UX3F6YTV.js → chunk-FPWP22Y7.js} +1 -1
  21. package/dist/{chunk-5JGRFT2N.js → chunk-NFMGIKFZ.js} +879 -1898
  22. package/dist/{chunk-K3SF5VDS.js → chunk-OD6NRSX2.js} +3 -1
  23. package/dist/{chunk-7QVOIHJF.js → chunk-QY3QQNDJ.js} +2 -2
  24. package/dist/{chunk-Y6XHXDIK.js → chunk-QZOCUQJT.js} +820 -537
  25. package/dist/chunk-TSYEWPVO.js +44 -0
  26. package/dist/chunk-TVTVIMIH.js +675 -0
  27. package/dist/chunk-YWQ3ALYY.js +69 -0
  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 +87 -2
  34. package/dist/protocol.js +15 -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
@@ -5,8 +5,12 @@ import {
5
5
  joinOptions,
6
6
  useSdkReconnection,
7
7
  waitForFirstState
8
- } from "./chunk-UX3F6YTV.js";
8
+ } from "./chunk-FPWP22Y7.js";
9
9
  import {
10
+ udpFastOpener
11
+ } from "./chunk-YWQ3ALYY.js";
12
+ import {
13
+ AuthorityJoinMethod,
10
14
  BODY_CORRECTION_TAU_MS,
11
15
  BODY_EXTRAPOLATE_BACK_TICKS,
12
16
  BODY_EXTRAPOLATE_MAX_MS,
@@ -16,11 +20,12 @@ import {
16
20
  ContactDrawing,
17
21
  ContactPush,
18
22
  ContactShapeKind,
19
- EMPTY_WORLD,
23
+ FAST_BIND_REV,
20
24
  FAST_HANDSHAKE_MS,
21
- FAST_KEY_BYTES,
22
- FAST_STALL_FALLBACK_MS,
23
- FastFault,
25
+ FAST_KEEPALIVE_BIND_MS,
26
+ FAST_RETURN_PROOF,
27
+ FAST_RETURN_STABLE_MS,
28
+ FastFallbackReason,
24
29
  FastMessage,
25
30
  HeadFriction,
26
31
  InputRepeat,
@@ -30,48 +35,59 @@ import {
30
35
  LiftPathMode,
31
36
  LiftShapeKind,
32
37
  MAX_PROJECTILE_G,
38
+ MotionRecordKind,
33
39
  NO_LIFTS,
34
40
  PHYSICS_PREDICT_MAX_ANGULAR,
35
41
  PHYSICS_PREDICT_MAX_LINEAR,
36
42
  PHYSICS_PRESET_LENGTH_UNITS,
37
43
  PhysicsPredictRange,
38
- Preset,
39
44
  TopPolicy,
40
- definitionOf,
45
+ blankEntityMotion,
46
+ blankSeatEcho,
47
+ chooseFastSocket,
41
48
  dequantizeBodyValue,
42
- emptyClientRx,
43
- encodeUplink,
44
- firstpersonWorld,
45
- hullOps,
46
- inputRedundancyTicks,
49
+ fastRetryDelayMs,
50
+ isFastBind,
47
51
  isProjectile,
48
- posedWorld,
52
+ nextCodeRegion,
53
+ openFastClient,
54
+ planAuthorityJoin,
49
55
  presetInputRepeat,
50
56
  projectileAt,
51
- psincos,
52
57
  quantizeBodyValue,
53
- receiveFragment,
54
- resolvePresetParams,
55
58
  sanitizeInput,
56
59
  seqAheadMax,
57
60
  serverTick,
58
61
  tickInstant,
59
- unsignedZero,
60
- uplinkFor,
61
- uplinkMaxBytes
62
- } from "./chunk-5JGRFT2N.js";
62
+ validRoomCode
63
+ } from "./chunk-NFMGIKFZ.js";
63
64
  import {
64
65
  RemotePredict
65
- } from "./chunk-K3SF5VDS.js";
66
+ } from "./chunk-OD6NRSX2.js";
67
+ import {
68
+ EMPTY_WORLD,
69
+ Preset,
70
+ definitionOf,
71
+ firstpersonWorld,
72
+ hullOps,
73
+ posedWorld,
74
+ psincos,
75
+ resolvePresetParams,
76
+ unsignedZero
77
+ } from "./chunk-2NGXXQDS.js";
66
78
  import {
67
79
  AUTHORITY_PROTOCOL_VERSION,
80
+ AUTHORITY_SDK_LABEL,
68
81
  AuthorityCaps,
69
82
  AuthorityJoinOption,
70
83
  AuthorityTiming,
71
84
  ClientReconnection,
85
+ CloseCode,
72
86
  JoinError,
73
- RoomType
74
- } from "./chunk-ZSWF4VCN.js";
87
+ RoomType,
88
+ ServerMessage,
89
+ authorityCloseCodeOf
90
+ } from "./chunk-CSQEEANB.js";
75
91
 
76
92
  // src/authority/regions.ts
77
93
  var AUTHORITY_REFUSALS_PER_REGION = 2;
@@ -120,6 +136,20 @@ function capacityWalk(regionCount, delays) {
120
136
  }
121
137
  return attempts;
122
138
  }
139
+ var RefusalKind = {
140
+ /** A full region, or a process shutting down that a sibling answers: the schedule, on this region. */
141
+ Capacity: "capacity",
142
+ /** A dead path, with other regions healthy: the next region at once. */
143
+ NextRegion: "next_region",
144
+ /** The server's answer: final. */
145
+ Final: "final"
146
+ };
147
+ var CAPACITY_REFUSALS = /* @__PURE__ */ new Set([503, 4213]);
148
+ var DEAD_PATH_REFUSALS = /* @__PURE__ */ new Set([502, 504, 520, 521, 522, 523, 524, 525, 526, 530]);
149
+ function refusalKind(code) {
150
+ if (typeof code !== "number" || code <= 0 || DEAD_PATH_REFUSALS.has(code)) return RefusalKind.NextRegion;
151
+ return CAPACITY_REFUSALS.has(code) ? RefusalKind.Capacity : RefusalKind.Final;
152
+ }
123
153
  function nextAttempt(state, regionCount, delays) {
124
154
  const refused = state.refused + 1;
125
155
  if (refused >= AUTHORITY_REFUSALS_PER_REGION && state.region + 1 < regionCount) {
@@ -192,6 +222,187 @@ async function probeLatencies(plans, options = {}) {
192
222
  return results;
193
223
  }
194
224
 
225
+ // src/authority/rejoin.ts
226
+ var REJOIN = {
227
+ /** Waits before each attempt; past the end the last one repeats. */
228
+ scheduleMs: [0, 250, 500, 1e3, 2e3, 4e3],
229
+ /** Each wait adds up to this much, by chance, so a room's seats do not arrive at once. */
230
+ jitterMs: 250,
231
+ /** How long a seat keeps trying: long enough for a roll's next process to come up on a full node. */
232
+ budgetMs: 18e4,
233
+ /** How long a seat stays with its own region before it tries the next one. */
234
+ regionStayMs: 6e4,
235
+ /** How long a public seat looks for its room's successor before it joins the game's pool. */
236
+ successorMs: 9e4
237
+ };
238
+ var SUCCESSOR_OPEN_MS = 12e4;
239
+ var RejoinWay = { Room: "room", Successor: "successor", Pool: "pool" };
240
+ var RejoinStop = { Final: "final", Budget: "budget" };
241
+ var COMES_BACK = /* @__PURE__ */ new Set([
242
+ CloseCode.GOING_AWAY,
243
+ CloseCode.NO_STATUS,
244
+ CloseCode.ABNORMAL,
245
+ 1011,
246
+ 1012,
247
+ 1013,
248
+ CloseCode.SERVER_SHUTDOWN,
249
+ CloseCode.FAILED_TO_RECONNECT,
250
+ CloseCode.MAY_TRY_RECONNECT
251
+ ]);
252
+ var FINAL_REFUSALS = /* @__PURE__ */ new Set([
253
+ JoinError.BAD_OPTION,
254
+ JoinError.NOT_ENTITLED,
255
+ JoinError.FORBIDDEN,
256
+ JoinError.MODULE_UNAVAILABLE,
257
+ JoinError.PROTOCOL_UNSUPPORTED
258
+ ]);
259
+ function comesBackAfter(code) {
260
+ return COMES_BACK.has(code);
261
+ }
262
+ function rejoinStep(input) {
263
+ const { attempt, elapsedMs, refusal, lostCode, closed, regions, jitter } = input;
264
+ if (refusal !== null && FINAL_REFUSALS.has(refusal)) return { stop: RejoinStop.Final };
265
+ if (elapsedMs >= REJOIN.budgetMs) return { stop: RejoinStop.Budget };
266
+ const last = REJOIN.scheduleMs.length - 1;
267
+ const base = REJOIN.scheduleMs[Math.min(attempt, last)];
268
+ const waitMs = base + Math.floor(jitter * REJOIN.jitterMs);
269
+ if (attempt === 0 && lostCode !== CloseCode.SERVER_SHUTDOWN) return { waitMs, region: 0, way: RejoinWay.Room };
270
+ const region = regions < 1 ? 0 : Math.floor(elapsedMs / REJOIN.regionStayMs) % regions;
271
+ const way = closed || elapsedMs < REJOIN.successorMs ? RejoinWay.Successor : RejoinWay.Pool;
272
+ return { waitMs, region, way };
273
+ }
274
+ function validSuccessorOf(value) {
275
+ return validRoomCode(value);
276
+ }
277
+ function planSuccessorJoin(input) {
278
+ return {
279
+ ...input.matchKey === "" ? {} : { matchKey: input.matchKey },
280
+ successorOf: input.lostRoomId,
281
+ ...input.closed ? { successorClosed: true } : {}
282
+ };
283
+ }
284
+
285
+ // src/authority/session-rejoin.ts
286
+ var RejoinEvent = { Draining: "draining", Rejoining: "rejoining", Rejoined: "rejoined" };
287
+ var DISCONNECT = "disconnect";
288
+ var UNKNOWN_CLOSE_MS = -1;
289
+ var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
290
+ var refusalOf = (err) => {
291
+ const code = err !== null && typeof err === "object" ? err.code : void 0;
292
+ return typeof code === "number" ? code : null;
293
+ };
294
+ function survivingSession(first, deps) {
295
+ const now = deps.now ?? Date.now;
296
+ const random = deps.random ?? Math.random;
297
+ const sleep = deps.sleep ?? defaultSleep;
298
+ const input = { ...first.input };
299
+ const listeners = /* @__PURE__ */ new Map();
300
+ let current = first;
301
+ let left = false;
302
+ let physicsHash = null;
303
+ let world = null;
304
+ const fire = (event, ...payload) => {
305
+ for (const handler of listeners.get(event) ?? []) handler(...payload);
306
+ };
307
+ const forward = (session, event) => {
308
+ if (event === DISCONNECT || event === RejoinEvent.Draining || event === RejoinEvent.Rejoining || event === RejoinEvent.Rejoined) return;
309
+ session.on(event, (...payload) => {
310
+ if (session === current) fire(event, ...payload);
311
+ });
312
+ };
313
+ const rejoin = async (lostCode, lostReason) => {
314
+ const startedAt = now();
315
+ const lost = { roomId: current.code, matchKey: current.matchKey, closed: deps.closed };
316
+ const regions = deps.targets.length;
317
+ let attempt = 0;
318
+ let refusal = null;
319
+ while (!left) {
320
+ const step = rejoinStep({ attempt, elapsedMs: now() - startedAt, refusal, lostCode, closed: deps.closed, regions, jitter: random() });
321
+ if ("stop" in step) {
322
+ fire(DISCONNECT, lostCode, lostReason);
323
+ return;
324
+ }
325
+ attempt += 1;
326
+ fire(RejoinEvent.Rejoining, { attempt, code: lostCode, waitMs: step.waitMs });
327
+ await sleep(step.waitMs);
328
+ if (left) return;
329
+ try {
330
+ const next = await deps.join(deps.targets[step.region], step.way, lost);
331
+ if (left) {
332
+ await next.leave().catch(() => {
333
+ });
334
+ return;
335
+ }
336
+ adopt(next);
337
+ fire(RejoinEvent.Rejoined, { code: next.code, url: next.url });
338
+ return;
339
+ } catch (err) {
340
+ refusal = refusalOf(err);
341
+ }
342
+ }
343
+ };
344
+ const adopt = (session) => {
345
+ current = session;
346
+ if (physicsHash !== null) session.setPhysics(physicsHash);
347
+ if (world !== null) session.setWorld(world);
348
+ for (const event of listeners.keys()) forward(session, event);
349
+ watch(session);
350
+ };
351
+ const watch = (session) => {
352
+ session.on(ServerMessage.SERVER_RESTART, (notice) => {
353
+ if (session !== current) return;
354
+ const drainMs = notice?.drainMs;
355
+ fire(RejoinEvent.Draining, { closesInMs: typeof drainMs === "number" ? drainMs : UNKNOWN_CLOSE_MS });
356
+ });
357
+ session.on(DISCONNECT, (code, reason) => {
358
+ if (session !== current || left) return;
359
+ if (comesBackAfter(code)) void rejoin(code, reason);
360
+ else fire(DISCONNECT, code, reason);
361
+ });
362
+ };
363
+ watch(first);
364
+ const own = {
365
+ input,
366
+ frame(frameNow) {
367
+ Object.assign(current.input, input);
368
+ return current.frame(frameNow);
369
+ },
370
+ setWorld(next) {
371
+ const hash = current.setWorld(next);
372
+ world = next;
373
+ return hash;
374
+ },
375
+ setPhysics(hash) {
376
+ physicsHash = hash;
377
+ return current.setPhysics(hash);
378
+ },
379
+ on(event, handler) {
380
+ let set = listeners.get(event);
381
+ if (set === void 0) {
382
+ set = /* @__PURE__ */ new Set();
383
+ listeners.set(event, set);
384
+ forward(current, event);
385
+ }
386
+ const fn = handler;
387
+ set.add(fn);
388
+ return () => {
389
+ set.delete(fn);
390
+ };
391
+ },
392
+ async leave() {
393
+ left = true;
394
+ await current.leave();
395
+ }
396
+ };
397
+ return new Proxy(first, {
398
+ get(_target, key) {
399
+ if (Object.hasOwn(own, key)) return own[key];
400
+ const value = current[key];
401
+ return typeof value === "function" ? value.bind(current) : value;
402
+ }
403
+ });
404
+ }
405
+
195
406
  // src/authority/physics-draw.ts
196
407
  var IDENTITY = [0, 0, 0, 1];
197
408
  function quatMul(a, b) {
@@ -289,85 +500,127 @@ var GRID_SIDE = 32;
289
500
  var GRID_CELL_MIN = 64;
290
501
  var GRID_WIDE = 64;
291
502
  var GRID_QUERY_CELLS = 16;
503
+ var GRID_MERGED_WIDE = 64;
292
504
  var UNSTICK_SOLIDS = 4;
293
- function grownBox(b, r, h) {
294
- const [x0, y0, z0, x1, y1, z1] = b;
295
- return [
296
- [-1, 0, 0, -(x0 - r)],
297
- [1, 0, 0, x1 + r],
298
- [0, -1, 0, -(y0 - r)],
299
- [0, 1, 0, y1 + r],
300
- [0, 0, 1, z1],
301
- [0, 0, -1, -(z0 - h)]
302
- ];
303
- }
304
- function grownRamp(ramp, r, h) {
505
+ var BOUNDS_STRIDE = 6;
506
+ var PLANE_STRIDE = 4;
507
+ var BOX_PLANES = 6;
508
+ var RAMP_PLANES = 7;
509
+ var RAMP_STRIDE = RAMP_PLANES * PLANE_STRIDE;
510
+ var BOX_NORMALS = [[-1, 0, 0], [1, 0, 0], [0, -1, 0], [0, 1, 0], [0, 0, 1], [0, 0, -1]];
511
+ var BOX_OFFSETS = [0, 3, 1, 4, 5, 2];
512
+ function writeRamp(out, o, ramp, r, h) {
305
513
  const [x0, y0, z0, x1, y1, z1, axis, lo, hi] = ramp;
306
514
  const m0 = axis === 0 ? x0 : y0;
307
515
  const slope = (hi - lo) / ((axis === 0 ? x1 : y1) - m0);
308
516
  const k = 1 / Math.sqrt(slope * slope + 1);
309
517
  const e = (lo - slope * m0 + r * Math.abs(slope)) * k;
310
- const top = axis === 0 ? [-slope * k, 0, k, e] : [0, -slope * k, k, e];
311
- return [[1, 0, 0, x1 + r], [-1, 0, 0, -x0 + r], [0, 1, 0, y1 + r], [0, -1, 0, -y0 + r], top, [0, 0, 1, z1], [0, 0, -1, -z0 + h]];
312
- }
313
- function solidOf(planes, src, r, h) {
314
- return { planes, min: [src[0] - r, src[1] - r, src[2] - h], max: [src[3] + r, src[4] + r, src[5]] };
518
+ const planes = [1, 0, 0, x1 + r, -1, 0, 0, -x0 + r, 0, 1, 0, y1 + r, 0, -1, 0, -y0 + r, axis === 0 ? -slope * k : 0, axis === 0 ? 0 : -slope * k, k, e, 0, 0, 1, z1, 0, 0, -1, -z0 + h];
519
+ out.set(planes, o);
315
520
  }
316
521
  function compileContactWorld(spec, r, h, skin) {
317
- const boxes = (spec?.boxes ?? []).map((b) => solidOf(grownBox(b, r, h), b, r, h));
318
- const ramps = (spec?.ramps ?? []).map((b) => solidOf(grownRamp(b, r, h), b, r, h));
319
- const solids = [...boxes, ...ramps];
320
- const grid = gridOf(solids);
321
- return grid === null ? { solids, skin } : { solids, skin, grid };
522
+ const boxes = spec?.boxes ?? [];
523
+ const ramps = spec?.ramps ?? [];
524
+ const count = boxes.length + ramps.length;
525
+ const bounds = new Float64Array(count * BOUNDS_STRIDE);
526
+ const rampPlanes = new Float64Array(ramps.length * RAMP_STRIDE);
527
+ for (let i = 0; i < count; i++) {
528
+ const src = i < boxes.length ? boxes[i] : ramps[i - boxes.length];
529
+ const o = i * BOUNDS_STRIDE;
530
+ bounds[o] = src[0] - r;
531
+ bounds[o + 1] = src[1] - r;
532
+ bounds[o + 2] = src[2] - h;
533
+ bounds[o + 3] = src[3] + r;
534
+ bounds[o + 4] = src[4] + r;
535
+ bounds[o + 5] = src[5];
536
+ if (i >= boxes.length) writeRamp(rampPlanes, (i - boxes.length) * RAMP_STRIDE, src, r, h);
537
+ }
538
+ const world = { count, boxCount: boxes.length, bounds, rampPlanes, skin };
539
+ const grid = gridOf(world);
540
+ return grid === null ? world : { ...world, grid };
541
+ }
542
+ function placeable(b, i) {
543
+ const o = i * BOUNDS_STRIDE;
544
+ return Number.isFinite(b[o]) && Number.isFinite(b[o + 3]) && Number.isFinite(b[o + 1]) && Number.isFinite(b[o + 4]);
322
545
  }
323
- var placeable = (s) => Number.isFinite(s.min[0]) && Number.isFinite(s.min[1]) && Number.isFinite(s.max[0]) && Number.isFinite(s.max[1]);
324
546
  function cellOf(v, origin, size, n) {
325
547
  const c = Math.floor((v - origin) / size);
326
548
  if (c < 0) return 0;
327
549
  return c > n - 1 ? n - 1 : c;
328
550
  }
329
- function gridOf(solids) {
330
- const placed2 = solids.filter(placeable);
331
- if (placed2.length < GRID_MIN_SOLIDS) return null;
551
+ function gridOf(world) {
552
+ const { count, bounds } = world;
553
+ let placed2 = 0;
332
554
  let x0 = Number.POSITIVE_INFINITY;
333
555
  let y0 = Number.POSITIVE_INFINITY;
334
556
  let x1 = Number.NEGATIVE_INFINITY;
335
557
  let y1 = Number.NEGATIVE_INFINITY;
336
- for (const s of placed2) {
337
- x0 = Math.min(x0, s.min[0] - CAST_MARGIN);
338
- y0 = Math.min(y0, s.min[1] - CAST_MARGIN);
339
- x1 = Math.max(x1, s.max[0] + CAST_MARGIN);
340
- y1 = Math.max(y1, s.max[1] + CAST_MARGIN);
341
- }
558
+ for (let k = 0; k < count; k++) {
559
+ if (!placeable(bounds, k)) continue;
560
+ placed2 += 1;
561
+ const o = k * BOUNDS_STRIDE;
562
+ x0 = Math.min(x0, bounds[o] - CAST_MARGIN);
563
+ y0 = Math.min(y0, bounds[o + 1] - CAST_MARGIN);
564
+ x1 = Math.max(x1, bounds[o + 3] + CAST_MARGIN);
565
+ y1 = Math.max(y1, bounds[o + 4] + CAST_MARGIN);
566
+ }
567
+ if (placed2 < GRID_MIN_SOLIDS) return null;
342
568
  const size = Math.max(GRID_CELL_MIN, (x1 - x0) / GRID_SIDE, (y1 - y0) / GRID_SIDE);
569
+ if (!Number.isFinite(size)) return null;
343
570
  const nx = Math.min(GRID_SIDE, Math.floor((x1 - x0) / size) + 1);
344
571
  const ny = Math.min(GRID_SIDE, Math.floor((y1 - y0) / size) + 1);
345
- const cells = Array.from({ length: nx * ny }, () => []);
346
- const wide = [];
347
- solids.forEach((s, k) => {
348
- if (!placeable(s)) {
349
- wide.push(k);
350
- return;
572
+ const visit = (onWide, onCell) => {
573
+ for (let k = 0; k < count; k++) {
574
+ if (!placeable(bounds, k)) {
575
+ onWide(k);
576
+ continue;
577
+ }
578
+ const o = k * BOUNDS_STRIDE;
579
+ const i0 = cellOf(bounds[o] - CAST_MARGIN, x0, size, nx);
580
+ const i1 = cellOf(bounds[o + 3] + CAST_MARGIN, x0, size, nx);
581
+ const j0 = cellOf(bounds[o + 1] - CAST_MARGIN, y0, size, ny);
582
+ const j1 = cellOf(bounds[o + 4] + CAST_MARGIN, y0, size, ny);
583
+ if ((i1 - i0 + 1) * (j1 - j0 + 1) > GRID_WIDE) {
584
+ onWide(k);
585
+ continue;
586
+ }
587
+ for (let j = j0; j <= j1; j++) for (let i = i0; i <= i1; i++) onCell(i + j * nx, k);
351
588
  }
352
- const i0 = cellOf(s.min[0] - CAST_MARGIN, x0, size, nx);
353
- const i1 = cellOf(s.max[0] + CAST_MARGIN, x0, size, nx);
354
- const j0 = cellOf(s.min[1] - CAST_MARGIN, y0, size, ny);
355
- const j1 = cellOf(s.max[1] + CAST_MARGIN, y0, size, ny);
356
- if ((i1 - i0 + 1) * (j1 - j0 + 1) > GRID_WIDE) {
357
- wide.push(k);
358
- return;
589
+ };
590
+ const cellStart = new Int32Array(nx * ny + 1);
591
+ let wideCount = 0;
592
+ visit(
593
+ () => {
594
+ wideCount += 1;
595
+ },
596
+ (c) => {
597
+ cellStart[c + 1] += 1;
359
598
  }
360
- for (let j = j0; j <= j1; j++) for (let i = i0; i <= i1; i++) cells[i + j * nx].push(k);
361
- });
362
- return { x0, y0, size, nx, ny, cells, wide };
599
+ );
600
+ for (let c = 0; c < nx * ny; c++) cellStart[c + 1] = cellStart[c + 1] + cellStart[c];
601
+ const cellSolids = new Int32Array(cellStart[nx * ny]);
602
+ const wide = new Int32Array(wideCount);
603
+ const written = cellStart.slice();
604
+ let wideAt = 0;
605
+ visit(
606
+ (k) => {
607
+ wide[wideAt] = k;
608
+ wideAt += 1;
609
+ },
610
+ (c, k) => {
611
+ cellSolids[written[c]] = k;
612
+ written[c] = written[c] + 1;
613
+ }
614
+ );
615
+ return { x0, y0, size, nx, ny, cellStart, cellSolids, wide, merged: new Array(wideAt <= GRID_MERGED_WIDE ? nx * ny : 0) };
363
616
  }
364
- function mergeAscending(a, b) {
365
- if (b.length === 0) return a;
617
+ function mergeAscending(a, b, from, to) {
618
+ if (to === from) return a;
366
619
  const out = [];
367
620
  let i = 0;
368
- let k = 0;
369
- while (i < a.length || k < b.length) {
370
- const fromA = k >= b.length || i < a.length && a[i] <= b[k];
621
+ let k = from;
622
+ while (i < a.length || k < to) {
623
+ const fromA = k >= to || i < a.length && a[i] <= b[k];
371
624
  const next = fromA ? a[i] : b[k];
372
625
  if (fromA) i += 1;
373
626
  else k += 1;
@@ -379,50 +632,100 @@ function solidsNear(world, lx, hx, ly, hy) {
379
632
  const g = world.grid;
380
633
  if (g === void 0 || !(Number.isFinite(lx) && Number.isFinite(hx) && Number.isFinite(ly) && Number.isFinite(hy))) return null;
381
634
  const i0 = cellOf(lx, g.x0, g.size, g.nx);
382
- const i1 = cellOf(hx, g.x0, g.size, g.nx);
635
+ const i1 = hx === lx ? i0 : cellOf(hx, g.x0, g.size, g.nx);
383
636
  const j0 = cellOf(ly, g.y0, g.size, g.ny);
384
- const j1 = cellOf(hy, g.y0, g.size, g.ny);
637
+ const j1 = hy === ly ? j0 : cellOf(hy, g.y0, g.size, g.ny);
385
638
  if ((i1 - i0 + 1) * (j1 - j0 + 1) > GRID_QUERY_CELLS) return null;
639
+ if (i0 === i1 && j0 === j1 && g.merged.length > 0) {
640
+ const c = i0 + j0 * g.nx;
641
+ const kept = g.merged[c];
642
+ if (kept !== void 0) return kept;
643
+ const made = mergeAscending(g.wide, g.cellSolids, g.cellStart[c], g.cellStart[c + 1]);
644
+ g.merged[c] = made;
645
+ return made;
646
+ }
386
647
  let near2 = g.wide;
387
- for (let j = j0; j <= j1; j++) for (let i = i0; i <= i1; i++) near2 = mergeAscending(near2, g.cells[i + j * g.nx]);
648
+ for (let j = j0; j <= j1; j++) {
649
+ for (let i = i0; i <= i1; i++) {
650
+ const c = i + j * g.nx;
651
+ near2 = mergeAscending(near2, g.cellSolids, g.cellStart[c], g.cellStart[c + 1]);
652
+ }
653
+ }
388
654
  return near2;
389
655
  }
390
- function insidePlanes(planes, skin, x, y, z) {
391
- for (const [nx, ny, nz, e] of planes) if (!(e - (nx * x + ny * y + nz * z) > skin)) return false;
656
+ var P = { nx: 0, ny: 0, nz: 0, e: 0 };
657
+ function readPlane(world, i, k) {
658
+ if (i < world.boxCount) {
659
+ const n = BOX_NORMALS[k];
660
+ const at2 = BOX_OFFSETS[k];
661
+ const v = world.bounds[i * BOUNDS_STRIDE + at2];
662
+ P.nx = n[0];
663
+ P.ny = n[1];
664
+ P.nz = n[2];
665
+ P.e = at2 < 3 ? -v : v;
666
+ return;
667
+ }
668
+ const o = (i - world.boxCount) * RAMP_STRIDE + k * PLANE_STRIDE;
669
+ const r = world.rampPlanes;
670
+ P.nx = r[o];
671
+ P.ny = r[o + 1];
672
+ P.nz = r[o + 2];
673
+ P.e = r[o + 3];
674
+ }
675
+ var planesOf = (world, i) => i < world.boxCount ? BOX_PLANES : RAMP_PLANES;
676
+ function insideSolid(world, i, skin, x, y, z) {
677
+ const n = planesOf(world, i);
678
+ for (let k = 0; k < n; k++) {
679
+ readPlane(world, i, k);
680
+ if (!(P.e - (P.nx * x + P.ny * y + P.nz * z) > skin)) return false;
681
+ }
392
682
  return true;
393
683
  }
684
+ var solidsAt = (world, x, y) => solidsNear(world, x, x, y, y);
394
685
  function insideWorld(world, x, y, z) {
395
- for (const s of world.solids) if (insidePlanes(s.planes, world.skin, x, y, z)) return true;
686
+ const near2 = solidsAt(world, x, y);
687
+ const n = near2 === null ? world.count : near2.length;
688
+ for (let m = 0; m < n; m++) {
689
+ if (insideSolid(world, near2 === null ? m : near2[m], world.skin, x, y, z)) return true;
690
+ }
396
691
  return false;
397
692
  }
398
- function segmentNear(s, p, d, len) {
693
+ function segmentNear(world, s, p, d, len) {
694
+ const o = s * BOUNDS_STRIDE;
399
695
  for (let i = 0; i < 3; i++) {
400
696
  const a = p[i];
401
697
  const b = a + d[i] * len;
402
698
  const lo = a < b ? a : b;
403
699
  const hi = a < b ? b : a;
404
- if (hi < s.min[i] - CAST_MARGIN || lo > s.max[i] + CAST_MARGIN) return false;
700
+ if (hi < world.bounds[o + i] - CAST_MARGIN || lo > world.bounds[o + 3 + i] + CAST_MARGIN) return false;
405
701
  }
406
702
  return true;
407
703
  }
408
- function bandHit(s, skin, p, d, rides) {
409
- for (const plane of s.planes) {
410
- const [nx, ny, nz, e] = plane;
704
+ function bandHit(world, s, skin, p, d, rides) {
705
+ const n = planesOf(world, s);
706
+ for (let k = 0; k < n; k++) {
707
+ readPlane(world, s, k);
708
+ const { nx, ny, nz, e } = P;
411
709
  const margin = e - (nx * p[0] + ny * p[1] + nz * p[2]);
412
- if (margin <= skin && nx * d[0] + ny * d[1] + nz * d[2] < 0 && !rides?.(plane)) return { t: 0, face: plane };
710
+ if (margin <= skin && nx * d[0] + ny * d[1] + nz * d[2] < 0) {
711
+ const plane = [nx, ny, nz, e];
712
+ if (!rides?.(plane)) return { t: 0, face: plane };
713
+ }
413
714
  }
414
715
  return null;
415
716
  }
416
717
  var ridesOn = (up, cos) => (face) => face[2] >= cos && face[0] * up[0] + face[1] * up[1] + face[2] * up[2] >= cos;
417
- function enterSolid(s, skin, p, d, len, rides) {
418
- if (insidePlanes(s.planes, skin, p[0], p[1], p[2])) return null;
419
- if (insidePlanes(s.planes, -skin, p[0], p[1], p[2])) return bandHit(s, skin, p, d, rides);
718
+ function enterSolid(world, s, skin, p, d, len, rides) {
719
+ if (insideSolid(world, s, skin, p[0], p[1], p[2])) return null;
720
+ if (insideSolid(world, s, -skin, p[0], p[1], p[2])) return bandHit(world, s, skin, p, d, rides);
420
721
  let lo = 0;
421
722
  let hi = len;
422
723
  let last = Number.NEGATIVE_INFINITY;
423
- let face = null;
424
- for (const plane of s.planes) {
425
- const [nx, ny, nz, e] = plane;
724
+ let face = -1;
725
+ const n = planesOf(world, s);
726
+ for (let k = 0; k < n; k++) {
727
+ readPlane(world, s, k);
728
+ const { nx, ny, nz, e } = P;
426
729
  const s0 = e + skin - (nx * p[0] + ny * p[1] + nz * p[2]);
427
730
  const sd = nx * d[0] + ny * d[1] + nz * d[2];
428
731
  if (sd === 0) {
@@ -433,26 +736,29 @@ function enterSolid(s, skin, p, d, len, rides) {
433
736
  if (sd > 0 && t2 < hi) hi = t2;
434
737
  if (sd < 0 && t2 > last) {
435
738
  last = t2;
436
- face = plane;
739
+ face = k;
437
740
  }
438
741
  }
439
742
  if (last > lo) lo = last;
440
- if (face === null || !(lo < hi) || rides?.(face)) return null;
441
- return { t: lo, face };
743
+ if (face < 0 || !(lo < hi)) return null;
744
+ readPlane(world, s, face);
745
+ const plane = [P.nx, P.ny, P.nz, P.e];
746
+ if (rides?.(plane)) return null;
747
+ return { t: lo, face: plane };
442
748
  }
443
749
  function castWorld(world, p, d, len, rides) {
444
750
  let best = { t: len, face: null };
445
751
  if (!(len > 0)) return { t: 0, face: null };
446
752
  const visit = (s) => {
447
- if (!segmentNear(s, p, d, best.t)) return;
448
- const hit = enterSolid(s, world.skin, p, d, best.t, rides);
753
+ if (!segmentNear(world, s, p, d, best.t)) return;
754
+ const hit = enterSolid(world, s, world.skin, p, d, best.t, rides);
449
755
  if (hit !== null && hit.t < best.t) best = hit;
450
756
  };
451
757
  const [px, py] = [p[0], p[1]];
452
758
  const [ex, ey] = [px + d[0] * len, py + d[1] * len];
453
759
  const near2 = solidsNear(world, Math.min(px, ex), Math.max(px, ex), Math.min(py, ey), Math.max(py, ey));
454
- if (near2 === null) for (const s of world.solids) visit(s);
455
- else for (const k of near2) visit(world.solids[k]);
760
+ if (near2 === null) for (let s = 0; s < world.count; s++) visit(s);
761
+ else for (let k = 0; k < near2.length; k++) visit(near2[k]);
456
762
  return best;
457
763
  }
458
764
  function roomAlong(world, p, d, len, rides) {
@@ -469,18 +775,22 @@ function surfaceUp(world, x, y, z, trace) {
469
775
  return [hit.face[0], hit.face[1], hit.face[2]];
470
776
  }
471
777
  function covered(world, self, x, y, z, q) {
472
- const { solids, skin } = world;
473
- for (let j = 0; j < solids.length; j++) {
778
+ const { count, skin } = world;
779
+ const near2 = solidsAt(world, q[0], q[1]);
780
+ const n = near2 === null ? count : near2.length;
781
+ for (let m = 0; m < n; m++) {
782
+ const j = near2 === null ? m : near2[m];
474
783
  if (j === self) continue;
475
- const planes = solids[j].planes;
476
- if (insidePlanes(planes, skin, q[0], q[1], q[2]) && !insidePlanes(planes, skin, x, y, z)) return true;
784
+ if (insideSolid(world, j, skin, q[0], q[1], q[2]) && !insideSolid(world, j, skin, x, y, z)) return true;
477
785
  }
478
786
  return false;
479
787
  }
480
- function leastFace(world, i, planes, x, y, z) {
788
+ function leastFace(world, i, x, y, z) {
481
789
  let least = null;
482
- for (let k = 0; k < planes.length - 1; k++) {
483
- const [nx, ny, nz, e] = planes[k];
790
+ const n = planesOf(world, i);
791
+ for (let k = 0; k < n - 1; k++) {
792
+ readPlane(world, i, k);
793
+ const { nx, ny, nz, e } = P;
484
794
  const depth = e - (nx * x + ny * y + nz * z);
485
795
  if (least !== null && !(depth < least[3])) continue;
486
796
  if (nz > 0 && covered(world, i, x, y, z, [x + nx * depth, y + ny * depth, z + nz * depth])) continue;
@@ -489,10 +799,12 @@ function leastFace(world, i, planes, x, y, z) {
489
799
  return least;
490
800
  }
491
801
  function insideFace(world, x, y, z) {
492
- const { solids, skin } = world;
493
- for (let i = 0; i < solids.length; i++) {
494
- const planes = solids[i].planes;
495
- if (insidePlanes(planes, skin, x, y, z)) return leastFace(world, i, planes, x, y, z);
802
+ const { count, skin } = world;
803
+ const near2 = solidsAt(world, x, y);
804
+ const n = near2 === null ? count : near2.length;
805
+ for (let m = 0; m < n; m++) {
806
+ const i = near2 === null ? m : near2[m];
807
+ if (insideSolid(world, i, skin, x, y, z)) return leastFace(world, i, x, y, z);
496
808
  }
497
809
  return null;
498
810
  }
@@ -528,7 +840,13 @@ var cross = (p, q) => [p[1] * q[2] - p[2] * q[1], p[2] * q[0] - p[0] * q[2], p[0
528
840
  var neg = (p) => [-p[0], -p[1], -p[2]];
529
841
  function supportOf(b, policy, env) {
530
842
  if (env.supportUp !== void 0) return env.supportUp(b.x, b.y, b.z);
531
- return surfaceUp(env.world, b.x, b.y, b.z, policy.groundTrace);
843
+ const memo = env.supports;
844
+ if (memo === void 0) return surfaceUp(env.world, b.x, b.y, b.z, policy.groundTrace);
845
+ const known = memo.get(b);
846
+ if (known !== void 0 && known.x === b.x && known.y === b.y && known.z === b.z) return known.up;
847
+ const up = surfaceUp(env.world, b.x, b.y, b.z, policy.groundTrace);
848
+ memo.set(b, { x: b.x, y: b.y, z: b.z, up });
849
+ return up;
532
850
  }
533
851
  function frameOf(b, policy, env) {
534
852
  const shape = policy.shape;
@@ -981,7 +1299,9 @@ function contactPair(a, b, policy, env, cooling = false, others = []) {
981
1299
  const fa = frameOf(a, policy, env);
982
1300
  const fb = frameOf(b, policy, env);
983
1301
  const found = contactOfFrames(fa, fb, policy);
984
- if (found === null) return null;
1302
+ return found === null ? null : stepFound(fa, fb, found, rule, policy, env, cooling, others);
1303
+ }
1304
+ function stepFound(fa, fb, found, rule, policy, env, cooling, others) {
985
1305
  const { contact, g } = found;
986
1306
  if (contact.kind === ContactKind.Side) return { ...contact, ...sideContact(fa, fb, contact, g, policy, env, cooling), way: null, w: null };
987
1307
  return { ...contact, ...topContact(fa, fb, contact, g, rule, policy, env, others) };
@@ -992,15 +1312,38 @@ function pairOfPass(a, b, pass, policy, env, options, seats) {
992
1312
  return own === void 0 ? contactPair(a, b, policy, env, cooling, seats) : own;
993
1313
  }
994
1314
  var stillTouching = (c, policy) => c !== null && (c.depth > policy.slop || c.kind === ContactKind.Top && c.depth > 0);
995
- function passOnce(seats, pass, policy, env, options, report) {
1315
+ var memoryOf = (count) => ({ moves: new Int32Array(count), still: new Int32Array(count * count).fill(-1) });
1316
+ function stepIfTouching(a, b, policy, env, seats) {
1317
+ if (farApart(a, b, policy)) return null;
1318
+ const rule = topRuleOf(policy, a, b);
1319
+ if (rule.policy === TopPolicy.Ghost) return null;
1320
+ const fa = frameOf(a, policy, env);
1321
+ const fb = frameOf(b, policy, env);
1322
+ const found = contactOfFrames(fa, fb, policy);
1323
+ if (found === null || !stillTouching(found.contact, policy)) return null;
1324
+ return stepFound(fa, fb, found, rule, policy, env, false, seats);
1325
+ }
1326
+ function passOnce(seats, pass, policy, env, options, report, memory) {
996
1327
  let touched = false;
997
- for (let i = 0; i < seats.length; i++) {
998
- for (let k = i + 1; k < seats.length; k++) {
1328
+ const count = seats.length;
1329
+ for (let i = 0; i < count; i++) {
1330
+ for (let k = i + 1; k < count; k++) {
999
1331
  const a = seats[i];
1000
1332
  const b = seats[k];
1001
- if (pass > 0 && !stillTouching(contactOf(a, b, policy, env), policy)) continue;
1002
- const out = pairOfPass(a, b, pass, policy, env, options, seats);
1003
- if (out === null) continue;
1333
+ const at2 = i * count + k;
1334
+ if (memory !== null && pass > 0 && memory.still[at2] === memory.moves[i] + memory.moves[k]) continue;
1335
+ let out;
1336
+ if (memory !== null && pass > 0) out = stepIfTouching(a, b, policy, env, seats);
1337
+ else if (pass > 0 && !stillTouching(contactOf(a, b, policy, env), policy)) continue;
1338
+ else out = pairOfPass(a, b, pass, policy, env, options, seats);
1339
+ if (out === null) {
1340
+ if (memory !== null) memory.still[at2] = memory.moves[i] + memory.moves[k];
1341
+ continue;
1342
+ }
1343
+ if (memory !== null) {
1344
+ memory.moves[i] = memory.moves[i] + 1;
1345
+ memory.moves[k] = memory.moves[k] + 1;
1346
+ }
1004
1347
  touched = true;
1005
1348
  if (out.way === SlideWay.Pinned) report.pinned += 1;
1006
1349
  options.onPair?.(a, b, out);
@@ -1010,17 +1353,20 @@ function passOnce(seats, pass, policy, env, options, report) {
1010
1353
  }
1011
1354
  function contactPass(seats, policy, env, options = {}) {
1012
1355
  const report = { passes: 0, pushed: /* @__PURE__ */ new Map(), walls: /* @__PURE__ */ new Map(), pinned: 0 };
1356
+ env = { ...env, supports: /* @__PURE__ */ new Map() };
1357
+ const memory = options.first === void 0 && options.onPair === void 0 && options.cooling === void 0 ? memoryOf(seats.length) : null;
1013
1358
  for (let pass = 0; pass < policy.passes; pass++) {
1014
- if (!passOnce(seats, pass, policy, env, options, report)) break;
1359
+ if (!passOnce(seats, pass, policy, env, options, report, memory)) break;
1015
1360
  report.passes += 1;
1016
1361
  if (env.world === null) continue;
1017
- for (const seat of seats) {
1362
+ seats.forEach((seat, s) => {
1018
1363
  const wall = (face) => {
1019
1364
  if (face[2] < policy.supportCos && face[3] > (report.walls.get(seat.id) ?? 0)) report.walls.set(seat.id, face[3]);
1020
1365
  };
1021
1366
  const depth = unstickBody(env.world, seat, wall);
1022
1367
  if (depth > (report.pushed.get(seat.id) ?? 0)) report.pushed.set(seat.id, depth);
1023
- }
1368
+ if (memory !== null && depth > 0) memory.moves[s] = memory.moves[s] + 1;
1369
+ });
1024
1370
  }
1025
1371
  return report;
1026
1372
  }
@@ -4110,7 +4456,10 @@ var roomStateFields = {
4110
4456
  joints: t.map(AuthorityJoint).optional(),
4111
4457
  // Lift rooms (PROTOCOL.md §17.11 *Lifts*): every called lift's start tick, JSON text `{"id":tick,…}`
4112
4458
  // (ids ascending), written when a module's ctx.lifts.call is taken. Absent = none called.
4113
- lifts: t.string().optional()
4459
+ lifts: t.string().optional(),
4460
+ // The room's match key (PROTOCOL.md §17.2 *Choosing a room*): how a friend who joined by code learns
4461
+ // which world to hand `setWorld`. Absent = no key, and never written.
4462
+ matchKey: t.string().optional()
4114
4463
  };
4115
4464
  var AuthorityRoomState = schema(roomStateFields, "AuthorityRoomState");
4116
4465
  var AuthorityPhysicsRoomState = schema({ ...roomStateFields, entities: t.map(AuthorityPhysicsEntity).view() }, "AuthorityRoomState");
@@ -4250,380 +4599,72 @@ function throttleSteps(t2, dtMs, stepMs) {
4250
4599
  return steps;
4251
4600
  }
4252
4601
 
4253
- // src/authority/fast-records.ts
4254
- var MotionRecordKind = { Entity: 1, Seat: 2 };
4255
- var FieldWire = {
4256
- F32: "f32",
4257
- U32: "u32",
4258
- U16: "u16",
4259
- /** A quantized orientation component in [−1, 1], uint16. */
4260
- Orientation: "q-orientation",
4261
- /** A quantized velocity in [−64, 64], uint16. */
4262
- Velocity: "q-velocity",
4263
- /** A boolean: the mask bit alone. */
4264
- Flag: "flag"
4602
+ // src/authority/fast-line.ts
4603
+ var FastTransport = { Udp: "udp", WebSocket: "websocket" };
4604
+ var FastNotAsked = {
4605
+ /** The game turned it off (`fast: false`, `bFastChannel = false`, ...). */
4606
+ Off: "off",
4607
+ /** This runtime has no UDP socket (WebGL, a browser with no opener). */
4608
+ NoUdp: "no_udp",
4609
+ /** The seat asked and the room sent no binding. */
4610
+ NotOffered: "not_offered"
4265
4611
  };
4266
- var ENTITY_FIELDS = [
4267
- ["x", FieldWire.F32],
4268
- ["y", FieldWire.F32],
4269
- ["z", FieldWire.F32],
4270
- ["vx", FieldWire.F32],
4271
- ["vy", FieldWire.F32],
4272
- ["vz", FieldWire.F32],
4273
- ["yaw", FieldWire.F32],
4274
- ["pitch", FieldWire.F32],
4275
- ["duck", FieldWire.F32],
4276
- ["spin", FieldWire.F32],
4277
- ["fuel", FieldWire.F32],
4278
- ["epoch", FieldWire.U32],
4279
- ["mstate", FieldWire.U32],
4280
- ["qx", FieldWire.Orientation],
4281
- ["qy", FieldWire.Orientation],
4282
- ["qz", FieldWire.Orientation],
4283
- ["qw", FieldWire.Orientation],
4284
- ["lvx", FieldWire.Velocity],
4285
- ["lvy", FieldWire.Velocity],
4286
- ["lvz", FieldWire.Velocity],
4287
- ["avx", FieldWire.Velocity],
4288
- ["avy", FieldWire.Velocity],
4289
- ["avz", FieldWire.Velocity],
4290
- ["sleep", FieldWire.Flag],
4291
- ["rest", FieldWire.U16]
4292
- ];
4293
- var SEAT_FIELDS = [
4294
- ["inMoveX", FieldWire.F32],
4295
- ["inMoveY", FieldWire.F32],
4296
- ["inMoveZ", FieldWire.F32],
4297
- ["inYaw", FieldWire.F32],
4298
- ["inPitch", FieldWire.F32],
4299
- ["inButtons", FieldWire.U32],
4300
- ["inRep", FieldWire.U32],
4301
- ["inStarve", FieldWire.U32],
4302
- ["inBuf", FieldWire.U32],
4303
- ["inAck", FieldWire.U32]
4304
- ];
4305
- var RECORD_ID_MAX_CHARS = 64;
4306
- var HEAD = 2;
4307
- var MASK_BYTES = 4;
4308
- var UINT32_MAX = 4294967295;
4309
- var UINT16_MAX = 65535;
4310
- var Q_SPAN = 65534;
4311
- var Q_ZERO = Q_SPAN / 2;
4312
- var Q_RANGE = { [FieldWire.Orientation]: 1, [FieldWire.Velocity]: 64 };
4313
- var blankEntityMotion = () => ({
4314
- x: 0,
4315
- y: 0,
4316
- z: 0,
4317
- vx: 0,
4318
- vy: 0,
4319
- vz: 0,
4320
- yaw: 0,
4321
- pitch: 0,
4322
- duck: 0,
4323
- spin: 0,
4324
- fuel: 0,
4325
- epoch: 0,
4326
- mstate: 0,
4327
- qx: 0,
4328
- qy: 0,
4329
- qz: 0,
4330
- qw: 0,
4331
- lvx: 0,
4332
- lvy: 0,
4333
- lvz: 0,
4334
- avx: 0,
4335
- avy: 0,
4336
- avz: 0,
4337
- sleep: false,
4338
- rest: 0
4339
- });
4340
- var blankSeatEcho = () => ({ inMoveX: 0, inMoveY: 0, inMoveZ: 0, inYaw: 0, inPitch: 0, inButtons: 0, inRep: 0, inStarve: 0, inBuf: 0, inAck: 0 });
4341
- function quantizeCode(half, value) {
4342
- const v = Math.min(half, Math.max(-half, value));
4343
- return Math.floor((v + half) / (2 * half) * Q_SPAN + 0.5);
4344
- }
4345
- var dequantizeCode = (half, code) => -half + code / Q_SPAN * (2 * half);
4346
- var fieldsOf = (kind) => {
4347
- if (kind === MotionRecordKind.Entity) return ENTITY_FIELDS;
4348
- if (kind === MotionRecordKind.Seat) return SEAT_FIELDS;
4349
- return null;
4612
+ var FAST_OFFER_WAIT_MS = FAST_HANDSHAKE_MS;
4613
+ var WHY = {
4614
+ [FastNotAsked.Off]: "the game turned the fast channel off",
4615
+ [FastNotAsked.NoUdp]: "this platform has no UDP",
4616
+ [FastNotAsked.NotOffered]: "the room does not offer the fast channel",
4617
+ unreachable: "fell back, the binding could not be used or its socket failed",
4618
+ handshake: "fell back, no UDP snapshot within 1 s of the first datagram (UDP blocked?)",
4619
+ stall: "fell back, no UDP snapshot for 2 s",
4620
+ server: "fell back, the room closed the channel",
4621
+ "no-webtransport": "this browser has no WebTransport",
4622
+ "no-relay": "the room has no WebTransport relay"
4350
4623
  };
4351
- var isUint = (v, max) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= max;
4352
- function wireValue(wire, v) {
4353
- switch (wire) {
4354
- case FieldWire.F32: {
4355
- if (typeof v !== "number" || !Number.isFinite(Math.fround(v))) return null;
4356
- return Math.fround(v) === 0 ? void 0 : Math.fround(v);
4357
- }
4358
- case FieldWire.U32:
4359
- if (!isUint(v, UINT32_MAX)) return null;
4360
- return v === 0 ? void 0 : v;
4361
- case FieldWire.U16:
4362
- if (!isUint(v, UINT16_MAX)) return null;
4363
- return v === 0 ? void 0 : v;
4364
- case FieldWire.Flag:
4365
- if (typeof v !== "boolean") return null;
4366
- return v ? 1 : void 0;
4367
- default: {
4368
- if (typeof v !== "number" || Number.isNaN(v)) return null;
4369
- const code = quantizeCode(Q_RANGE[wire], v);
4370
- return code === Q_ZERO ? void 0 : code;
4371
- }
4372
- }
4373
- }
4374
- var WIDTH = { f32: 4, u32: 4, u16: 2, "q-orientation": 2, "q-velocity": 2, flag: 0 };
4375
- function encodeRecord(r) {
4376
- const fields = fieldsOf(r.kind);
4377
- const id = typeof r.id === "string" ? new TextEncoder().encode(r.id) : null;
4378
- const idOk = id !== null && r.id.length >= 1 && r.id.length <= RECORD_ID_MAX_CHARS && id.length <= 255;
4379
- if (fields === null || !idOk || r.motion === null || typeof r.motion !== "object") return { ok: false, fault: FastFault.Field };
4380
- const motion = r.motion;
4381
- const present = [];
4382
- let mask = 0;
4383
- for (const [i, [name, wire]] of fields.entries()) {
4384
- const value = wireValue(wire, motion[name]);
4385
- if (value === null) return { ok: false, fault: FastFault.Field };
4386
- if (value === void 0) continue;
4387
- mask = (mask | 1 << i) >>> 0;
4388
- present.push([wire, value]);
4389
- }
4390
- const out = new Uint8Array(HEAD + id.length + MASK_BYTES + present.reduce((n, [w]) => n + WIDTH[w], 0));
4391
- const view = new DataView(out.buffer);
4392
- out[0] = r.kind;
4393
- out[1] = id.length;
4394
- out.set(id, HEAD);
4395
- let at2 = HEAD + id.length;
4396
- view.setUint32(at2, mask, true);
4397
- at2 += MASK_BYTES;
4398
- for (const [wire, value] of present) at2 = writeField(view, at2, wire, value);
4399
- return { ok: true, value: out };
4400
- }
4401
- function writeField(view, at2, wire, value) {
4402
- if (wire === FieldWire.F32) view.setFloat32(at2, value, true);
4403
- else if (wire === FieldWire.U32) view.setUint32(at2, value, true);
4404
- else if (wire !== FieldWire.Flag) view.setUint16(at2, value, true);
4405
- return at2 + WIDTH[wire];
4406
- }
4407
- function readField(view, at2, wire) {
4408
- if (at2 + WIDTH[wire] > view.byteLength) return { ok: false, fault: FastFault.Truncated };
4409
- switch (wire) {
4410
- case FieldWire.Flag:
4411
- return { ok: true, value: true };
4412
- case FieldWire.F32: {
4413
- const v = view.getFloat32(at2, true);
4414
- return Number.isFinite(v) && v !== 0 ? { ok: true, value: v } : { ok: false, fault: FastFault.Field };
4415
- }
4416
- case FieldWire.U32: {
4417
- const v = view.getUint32(at2, true);
4418
- return v !== 0 ? { ok: true, value: v } : { ok: false, fault: FastFault.Field };
4419
- }
4420
- case FieldWire.U16: {
4421
- const v = view.getUint16(at2, true);
4422
- return v !== 0 ? { ok: true, value: v } : { ok: false, fault: FastFault.Field };
4423
- }
4424
- default: {
4425
- const code = view.getUint16(at2, true);
4426
- const canonical = code !== Q_ZERO && code <= Q_SPAN;
4427
- return canonical ? { ok: true, value: dequantizeCode(Q_RANGE[wire], code) } : { ok: false, fault: FastFault.Field };
4428
- }
4429
- }
4430
- }
4431
- var UTF8 = new TextDecoder("utf-8", { fatal: true });
4432
- function readId(bytes) {
4433
- const len = bytes[1];
4434
- if (HEAD + len > bytes.length) return { ok: false, fault: FastFault.Truncated };
4435
- if (len === 0) return { ok: false, fault: FastFault.Field };
4436
- try {
4437
- const id = UTF8.decode(bytes.subarray(HEAD, HEAD + len));
4438
- return id.length <= RECORD_ID_MAX_CHARS ? { ok: true, value: id } : { ok: false, fault: FastFault.Field };
4439
- } catch {
4440
- return { ok: false, fault: FastFault.Field };
4441
- }
4442
- }
4443
- function decodeRecord(bytes) {
4444
- if (bytes.length < HEAD) return { ok: false, fault: FastFault.Truncated };
4445
- const fields = fieldsOf(bytes[0]);
4446
- if (fields === null) return { ok: false, fault: FastFault.Kind };
4447
- const id = readId(bytes);
4448
- if (!id.ok) return id;
4449
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
4450
- let at2 = HEAD + bytes[1];
4451
- if (at2 + MASK_BYTES > bytes.length) return { ok: false, fault: FastFault.Truncated };
4452
- const mask = view.getUint32(at2, true);
4453
- at2 += MASK_BYTES;
4454
- if (fields.length < 32 && mask >>> fields.length !== 0) return { ok: false, fault: FastFault.Flags };
4455
- if (bytes[0] === MotionRecordKind.Entity) {
4456
- const motion2 = readMotion(view, at2, mask, blankEntityMotion(), fields);
4457
- return motion2.ok ? { ok: true, value: { kind: MotionRecordKind.Entity, id: id.value, motion: motion2.value } } : motion2;
4458
- }
4459
- const motion = readMotion(view, at2, mask, blankSeatEcho(), fields);
4460
- return motion.ok ? { ok: true, value: { kind: MotionRecordKind.Seat, id: id.value, motion: motion.value } } : motion;
4461
- }
4462
- function readMotion(view, start, mask, blank, fields) {
4463
- const values = {};
4464
- let at2 = start;
4465
- for (const [i, [name, wire]] of fields.entries()) {
4466
- if ((mask & 1 << i) === 0) continue;
4467
- const v = readField(view, at2, wire);
4468
- if (!v.ok) return v;
4469
- values[name] = v.value;
4470
- at2 += WIDTH[wire];
4471
- }
4472
- if (at2 !== view.byteLength) return { ok: false, fault: FastFault.Trailing };
4473
- return { ok: true, value: Object.assign(blank, values) };
4474
- }
4475
-
4476
- // src/authority/fast-client.ts
4477
- var UINT32_MAX2 = 4294967295;
4478
- var FastFallbackReason = {
4479
- /** The `__fast` binding was malformed, this client cannot open its url, or its socket failed. */
4480
- Unreachable: "unreachable",
4481
- /** No snapshot within {@link FAST_HANDSHAKE_MS} of the first datagram. */
4482
- Handshake: "handshake",
4483
- /** No snapshot for {@link FAST_STALL_FALLBACK_MS} after the last one. */
4484
- Stall: "stall",
4485
- /** The server asked for it (`__fast:off` from the server). */
4486
- Server: "server"
4487
- };
4488
- var isUint2 = (v, max) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= max;
4489
- function isFastBind(m) {
4490
- if (typeof m !== "object" || m === null) return false;
4491
- const { sid, key, url } = m;
4492
- const keyOk = typeof key === "string" && key.length === 2 * FAST_KEY_BYTES && /^[0-9a-f]+$/.test(key);
4493
- return isUint2(sid, UINT32_MAX2) && keyOk && typeof url === "string" && url.length > 0;
4494
- }
4495
- function heldInputs(sent, inputAck, tickHz) {
4496
- if (sent.length === 0) return [];
4497
- const newest = sent[sent.length - 1].seq;
4498
- const keepFrom = Math.min(inputAck, newest - inputRedundancyTicks(tickHz) + 1);
4499
- const capped = Math.max(keepFrom, newest - seqAheadMax(tickHz) + 1);
4500
- return sent.filter((s) => s.seq >= capped);
4501
- }
4502
- var recordName = (kind, id) => `${kind}:${id}`;
4503
- function openFastClient(o) {
4504
- let rx = emptyClientRx();
4505
- let sent = [];
4506
- const sentTimes = /* @__PURE__ */ new Map();
4507
- let fresh = 0;
4508
- let closed = false;
4509
- let firstSentAt = null;
4510
- let lastAppliedAt = null;
4511
- let decodedByKey = /* @__PURE__ */ new Map();
4512
- let latest = /* @__PURE__ */ new Map();
4513
- const inputAck = () => rx.history.at(-1)?.inputAck ?? 0;
4514
- const decodeChanged = (records) => {
4515
- const nextByKey = /* @__PURE__ */ new Map();
4516
- const nextLatest = /* @__PURE__ */ new Map();
4517
- const changed = [];
4518
- for (const r of records) {
4519
- const before = decodedByKey.get(r.key);
4520
- const same = before !== void 0 && (before.bytes === r.bytes || sameBytes(before.bytes, r.bytes));
4521
- const record = same ? before.record : decodeOrNull(r.bytes);
4522
- nextByKey.set(r.key, { bytes: r.bytes, record });
4523
- if (record === null) continue;
4524
- nextLatest.set(recordName(record.kind, record.id), record);
4525
- if (!same) changed.push(record);
4526
- }
4527
- decodedByKey = nextByKey;
4528
- latest = nextLatest;
4529
- return changed;
4530
- };
4531
- function receive(bytes) {
4532
- if (closed) return { applied: null };
4533
- const r = receiveFragment(rx, bytes, o.bind.sid);
4534
- rx = r.rx;
4535
- if (r.fault !== void 0 || r.applied === null) return { applied: null, fault: r.fault };
4536
- const held = rx.history.at(-1);
4537
- if (held === void 0) return { applied: null };
4538
- lastAppliedAt = o.now();
4539
- sent = heldInputs(sent, held.inputAck, o.tickHz);
4540
- const applied = { snap: held.snap, tick: held.tick, inputAck: held.inputAck, sent: held.sent, changed: decodeChanged(held.records) };
4541
- o.onSnapshot?.(applied);
4542
- return { applied };
4543
- }
4544
- const socket = o.open(
4545
- o.bind.url,
4546
- (bytes) => {
4547
- receive(bytes);
4548
- },
4549
- o.bind
4550
- );
4551
- if (socket === null) return null;
4552
- const largestUplink = uplinkMaxBytes(o.tickHz);
4624
+ var udpLine = (url) => `genex transport: udp fast@1 ${url}`;
4625
+ var webSocketLine = (reason) => `genex transport: websocket, ${reason}: ${WHY[reason] ?? "unknown"}`;
4626
+ function fastLineAnnouncer() {
4627
+ const said = /* @__PURE__ */ new Set();
4628
+ const once = (line) => {
4629
+ if (said.has(line)) return null;
4630
+ said.add(line);
4631
+ return line;
4632
+ };
4553
4633
  return {
4554
- bind: o.bind,
4555
- get rx() {
4556
- return rx;
4557
- },
4558
- get lastQueued() {
4559
- return sent.at(-1)?.seq ?? 0;
4560
- },
4561
- get closed() {
4562
- return closed;
4634
+ observe(o) {
4635
+ if (o.joinedMs === null) return null;
4636
+ if (!o.asked) return once(webSocketLine(FastNotAsked.Off));
4637
+ if (!o.udp) return once(webSocketLine(FastNotAsked.NoUdp));
4638
+ if (o.fallback !== null) return once(webSocketLine(o.fallback));
4639
+ if (o.url !== null) return o.snapshots > 0 ? once(udpLine(o.url)) : null;
4640
+ return o.joinedMs >= FAST_OFFER_WAIT_MS ? once(webSocketLine(FastNotAsked.NotOffered)) : null;
4563
4641
  },
4564
- queue(input) {
4565
- if (closed) return;
4566
- sent.push({ seq: input.seq, tick: input.tick, render: input.render, input: { ...input.input } });
4567
- fresh += 1;
4568
- },
4569
- flush() {
4570
- if (closed || fresh === 0) return;
4571
- const now = o.now();
4572
- for (const s of sent.slice(-fresh)) sentTimes.set(s.seq, now);
4573
- forgetSendTimes(sentTimes, (sent.at(-1)?.seq ?? 0) - seqAheadMax(o.tickHz) + 1);
4574
- const window = inputRedundancyTicks(o.tickHz);
4575
- let from = sent.length - fresh;
4576
- fresh = 0;
4577
- while (from < sent.length) {
4578
- const to = Math.min(from + window, sent.length);
4579
- const u = uplinkFor(sent.slice(0, to), { sid: o.bind.sid, key: o.bind.key, ackSnap: rx.newest, inputAck: inputAck(), fresh: to - from, tickHz: o.tickHz });
4580
- from = to;
4581
- const bytes = encodeUplink(u);
4582
- if (!bytes.ok) continue;
4583
- firstSentAt ??= now;
4584
- socket.send(bytes.value);
4585
- }
4586
- },
4587
- inputAt: (seq) => sent.find((s) => s.seq === seq),
4588
- sentAt: (seq) => sentTimes.get(seq),
4589
- motionOf: (kind, id) => latest.get(recordName(kind, id)),
4590
- due() {
4591
- if (closed) return null;
4592
- if (socket.failed?.() === true) return FastFallbackReason.Unreachable;
4593
- const room = socket.maxDatagramBytes?.();
4594
- if (room !== void 0 && room < largestUplink) return FastFallbackReason.Unreachable;
4595
- const now = o.now();
4596
- if (lastAppliedAt !== null) return now - lastAppliedAt > FAST_STALL_FALLBACK_MS ? FastFallbackReason.Stall : null;
4597
- return firstSentAt !== null && now - firstSentAt > FAST_HANDSHAKE_MS ? FastFallbackReason.Handshake : null;
4598
- },
4599
- receive,
4600
- close() {
4601
- if (closed) return;
4602
- closed = true;
4603
- socket.close();
4642
+ reset() {
4643
+ said.clear();
4604
4644
  }
4605
4645
  };
4606
4646
  }
4607
- function forgetSendTimes(times, from) {
4608
- for (const seq of times.keys()) {
4609
- if (seq >= from) return;
4610
- times.delete(seq);
4611
- }
4612
- }
4613
- function decodeOrNull(bytes) {
4614
- const d = decodeRecord(bytes);
4615
- return d.ok ? d.value : null;
4616
- }
4617
- var sameBytes = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
4618
4647
 
4619
4648
  // src/authority/fast-session.ts
4620
4649
  import { $refId, OPERATION } from "@colyseus/schema";
4621
4650
  var ROOM_INPUT_RELIABLE = 19;
4651
+ var RETRY_AFTER_SERVER_OFF = 3;
4622
4652
  var PROTOCOL_CODE_MASK = 31;
4623
4653
  var ENTITY_MOTION_FIELDS = Object.keys(blankEntityMotion());
4624
4654
  var SEAT_MOTION_FIELDS = Object.keys(blankSeatEcho()).filter((f) => f !== "inAck");
4625
4655
  var motionFieldsOf = (kind) => kind === MotionRecordKind.Entity ? ENTITY_MOTION_FIELDS : SEAT_MOTION_FIELDS;
4626
4656
  var ROOT_TICK_FIELDS = ["tick", "keyframe"];
4657
+ function defaultFastLog(line) {
4658
+ const stderr = globalThis.process?.stderr;
4659
+ if (stderr?.write !== void 0) stderr.write(`${line}
4660
+ `);
4661
+ else console.info(line);
4662
+ }
4663
+ var defaultEvery = (ms, fn) => {
4664
+ const timer = setInterval(fn, ms);
4665
+ timer.unref?.();
4666
+ return () => clearInterval(timer);
4667
+ };
4627
4668
  function baselinesOf(handle) {
4628
4669
  if (typeof handle !== "object" || handle === null) return null;
4629
4670
  const h = handle;
@@ -4642,13 +4683,30 @@ function renderStampOf(handle) {
4642
4683
  }
4643
4684
  function fastSeat(o) {
4644
4685
  const { room, handle } = o;
4645
- const status = { live: false, snapshots: 0, fallback: null };
4686
+ const status = { live: false, snapshots: 0, fallback: o.neverAsked, fallbacks: 0, returns: 0, transport: FastTransport.WebSocket, line: null };
4687
+ let returnsSaid = 0;
4688
+ const announcer = fastLineAnnouncer();
4689
+ let joinedAt = null;
4690
+ let boundUrl = null;
4691
+ let snapshotsBefore = 0;
4646
4692
  let link = null;
4647
4693
  let fallback = null;
4648
4694
  let fastAck = 0;
4649
4695
  let early = [];
4650
4696
  let pendingBind = null;
4697
+ let offerAwaitedFrom = null;
4698
+ let roundTripMs = 0;
4651
4699
  const guard = o.open === void 0 ? null : motionGuard(room, () => link?.client ?? null);
4700
+ const stopTimer = o.open === void 0 ? null : (o.every ?? defaultEvery)(FAST_KEEPALIVE_BIND_MS, () => {
4701
+ link?.client.keepalive();
4702
+ probe?.client.keepalive();
4703
+ });
4704
+ const random = o.random ?? Math.random;
4705
+ let probe = null;
4706
+ let hostReturns = false;
4707
+ let attempt = 0;
4708
+ let retryAt = null;
4709
+ let liveSince = null;
4652
4710
  const baselines = baselinesOf(handle);
4653
4711
  const renderStamp = renderStampOf(handle);
4654
4712
  const ack = () => {
@@ -4658,8 +4716,17 @@ function fastSeat(o) {
4658
4716
  return ws > 0 ? Math.max(fallback.ack, fallback.at + ws) : fallback.ack;
4659
4717
  };
4660
4718
  const seqBase = () => room.state.players?.get(room.sessionId)?.seqBase ?? 0;
4719
+ const scheduleRetry = () => {
4720
+ if (!hostReturns || o.open === void 0) return;
4721
+ retryAt = o.now() + fastRetryDelayMs(attempt, random());
4722
+ attempt += 1;
4723
+ };
4661
4724
  const fallBack = (reason) => {
4662
4725
  if (fallback !== null) return;
4726
+ if (liveSince !== null && o.now() - liveSince >= FAST_RETURN_STABLE_MS) attempt = 0;
4727
+ if (reason === FastFallbackReason.Server) attempt = Math.max(attempt, RETRY_AFTER_SERVER_OFF);
4728
+ liveSince = null;
4729
+ status.fallbacks += 1;
4663
4730
  const live = link;
4664
4731
  const base = link?.seqBase ?? seqBase();
4665
4732
  fallback = { at: handle.sentCount, wsBefore: link?.wsBefore ?? handle.sentCount, ack: ack(), reason };
@@ -4675,6 +4742,81 @@ function fastSeat(o) {
4675
4742
  const off = { last: base + handle.sentCount };
4676
4743
  room.send(FastMessage.Off, off);
4677
4744
  guard?.release();
4745
+ scheduleRetry();
4746
+ };
4747
+ const offChannel = (reason) => {
4748
+ if (status.fallback === null) status.fallbacks += 1;
4749
+ status.fallback = reason;
4750
+ };
4751
+ const abandonProbe = (reason) => {
4752
+ if (probe === null) return;
4753
+ probe.client.close();
4754
+ probe = null;
4755
+ offChannel(reason);
4756
+ const off = { last: seqBase() + handle.sentCount };
4757
+ room.send(FastMessage.Off, off);
4758
+ scheduleRetry();
4759
+ };
4760
+ const swapToProbe = (s) => {
4761
+ const p = probe;
4762
+ if (p === null || fallback === null) return;
4763
+ probe = null;
4764
+ const base = seqBase();
4765
+ const wsBefore = fallback.wsBefore + (handle.sentCount - fallback.at);
4766
+ fastAck = ack();
4767
+ link = { client: p.client, seqBase: base, wsBefore, stampAtBind: baselines?._lastStamp ?? 0 };
4768
+ fallback = null;
4769
+ retryAt = null;
4770
+ liveSince = o.now();
4771
+ status.live = true;
4772
+ status.fallback = null;
4773
+ if (status.fallbacks > 0) status.returns += 1;
4774
+ const on = { last: base + handle.sentCount };
4775
+ room.send(FastMessage.On, on);
4776
+ applied({ ...s, changed: p.client.records() });
4777
+ };
4778
+ const askRoundTrip = (client) => {
4779
+ if (!client.opening()) return;
4780
+ room.ping((ms) => {
4781
+ roundTripMs = ms;
4782
+ });
4783
+ };
4784
+ const startProbe = (message) => {
4785
+ if (o.open === void 0) return;
4786
+ attempt = Math.max(0, attempt - 1);
4787
+ hostReturns = true;
4788
+ probe?.client.close();
4789
+ probe = null;
4790
+ if (fallback === null) status.fallback = null;
4791
+ fallback ??= { at: 0, wsBefore: 0, ack: 0, reason: FastFallbackReason.Unreachable };
4792
+ early = [];
4793
+ if (!isFastBind(message)) {
4794
+ offChannel(FastFallbackReason.Unreachable);
4795
+ return scheduleRetry();
4796
+ }
4797
+ boundUrl = message.url;
4798
+ const proof = { client: null, applied: 0 };
4799
+ const client = openFastClient({
4800
+ bind: message,
4801
+ tickHz: o.tickHz(),
4802
+ open: o.open,
4803
+ now: o.now,
4804
+ roundTripMs: () => roundTripMs,
4805
+ onSnapshot: (s) => {
4806
+ if (link !== null && link.client === proof.client) return applied(s);
4807
+ proof.applied += 1;
4808
+ if (proof.applied >= FAST_RETURN_PROOF) swapToProbe(s);
4809
+ }
4810
+ });
4811
+ if (client === null) {
4812
+ offChannel(o.open.whyNull?.(message) ?? FastFallbackReason.Unreachable);
4813
+ return scheduleRetry();
4814
+ }
4815
+ askRoundTrip(client);
4816
+ proof.client = client;
4817
+ probe = proof;
4818
+ retryAt = null;
4819
+ client.keepalive();
4678
4820
  };
4679
4821
  const applied = (s) => {
4680
4822
  const l = link;
@@ -4693,21 +4835,49 @@ function fastSeat(o) {
4693
4835
  guard?.write(s);
4694
4836
  };
4695
4837
  const bind = (message) => {
4696
- if (o.open === void 0 || link !== null || fallback !== null) return;
4838
+ const asProbe = message?.probe === true;
4839
+ if (o.open === void 0 || link !== null || fallback !== null && !asProbe) return;
4697
4840
  if (!o.stateCurrent()) {
4698
4841
  pendingBind = message;
4699
4842
  return;
4700
4843
  }
4701
4844
  pendingBind = null;
4845
+ if (asProbe) return startProbe(message);
4846
+ status.fallback = null;
4702
4847
  if (!isFastBind(message)) return fallBack(FastFallbackReason.Unreachable);
4703
- const client = openFastClient({ bind: message, tickHz: o.tickHz(), open: o.open, now: o.now, onSnapshot: applied });
4704
- if (client === null) return fallBack(FastFallbackReason.Unreachable);
4848
+ boundUrl = message.url;
4849
+ hostReturns = typeof message.rev === "number" && message.rev >= FAST_BIND_REV;
4850
+ const client = openFastClient({ bind: message, tickHz: o.tickHz(), open: o.open, now: o.now, roundTripMs: () => roundTripMs, onSnapshot: applied });
4851
+ if (client === null) return fallBack(o.open.whyNull?.(message) ?? FastFallbackReason.Unreachable);
4852
+ askRoundTrip(client);
4705
4853
  link = { client, seqBase: seqBase(), wsBefore: handle.sentCount, stampAtBind: baselines?._lastStamp ?? 0 };
4706
4854
  fastAck = 0;
4707
4855
  status.live = true;
4856
+ liveSince = o.now();
4708
4857
  for (const s of early) client.queue({ ...s, seq: link.seqBase + s.seq });
4709
4858
  early = [];
4710
4859
  client.flush();
4860
+ client.keepalive();
4861
+ };
4862
+ const noteOffer = () => {
4863
+ const waiting = o.open !== void 0 && link === null && fallback === null && pendingBind === null && o.stateCurrent();
4864
+ if (!waiting) return;
4865
+ offerAwaitedFrom ??= o.now();
4866
+ if (o.now() - offerAwaitedFrom > FAST_HANDSHAKE_MS) status.fallback = FastFallbackReason.NotOffered;
4867
+ };
4868
+ const announce = () => {
4869
+ if (joinedAt === null && o.stateCurrent() && room.state.players?.get(room.sessionId) !== void 0) joinedAt = o.now();
4870
+ const snapshots = status.snapshots - snapshotsBefore;
4871
+ status.transport = status.live && snapshots > 0 ? FastTransport.Udp : FastTransport.WebSocket;
4872
+ if (status.returns !== returnsSaid) {
4873
+ returnsSaid = status.returns;
4874
+ announcer.reset();
4875
+ }
4876
+ const fallback2 = status.fallback === FastFallbackReason.NotOffered ? null : status.fallback;
4877
+ const line = announcer.observe({ asked: !o.off, udp: o.open !== void 0, joinedMs: joinedAt === null ? null : o.now() - joinedAt, url: boundUrl, snapshots, fallback: fallback2 });
4878
+ if (line === null) return;
4879
+ status.line = line;
4880
+ o.log(line);
4711
4881
  };
4712
4882
  if (o.open !== void 0) {
4713
4883
  room.onMessage(FastMessage.Bind, (m) => bind(m));
@@ -4735,8 +4905,20 @@ function fastSeat(o) {
4735
4905
  link?.client.flush();
4736
4906
  },
4737
4907
  poll() {
4908
+ link?.client.framed();
4738
4909
  const reason = link?.client.due() ?? null;
4739
4910
  if (reason !== null) fallBack(reason);
4911
+ link?.client.keepalive();
4912
+ probe?.client.framed();
4913
+ const probeReason = probe?.client.due() ?? null;
4914
+ if (probeReason !== null) abandonProbe(probeReason);
4915
+ probe?.client.keepalive();
4916
+ if (retryAt !== null && o.now() >= retryAt && probe === null && fallback !== null) {
4917
+ room.send(FastMessage.Again, {});
4918
+ scheduleRetry();
4919
+ }
4920
+ noteOffer();
4921
+ announce();
4740
4922
  },
4741
4923
  motionKnown(id) {
4742
4924
  return link === null || link.client.motionOf(MotionRecordKind.Entity, id) !== void 0;
@@ -4749,15 +4931,30 @@ function fastSeat(o) {
4749
4931
  dropped() {
4750
4932
  link?.client.close();
4751
4933
  link = null;
4934
+ probe?.client.close();
4935
+ probe = null;
4936
+ retryAt = null;
4937
+ attempt = 0;
4938
+ liveSince = null;
4939
+ hostReturns = false;
4752
4940
  guard?.forget();
4753
4941
  fallback = null;
4754
4942
  fastAck = 0;
4755
4943
  early = [];
4756
4944
  pendingBind = null;
4945
+ offerAwaitedFrom = null;
4757
4946
  status.live = false;
4758
- status.fallback = null;
4947
+ status.fallback = o.neverAsked;
4948
+ status.transport = FastTransport.WebSocket;
4949
+ announcer.reset();
4950
+ joinedAt = null;
4951
+ boundUrl = null;
4952
+ snapshotsBefore = status.snapshots;
4759
4953
  },
4760
4954
  dispose() {
4955
+ stopTimer?.();
4956
+ probe?.client.close();
4957
+ probe = null;
4761
4958
  link?.client.close();
4762
4959
  link = null;
4763
4960
  status.live = false;
@@ -4809,7 +5006,7 @@ function motionGuard(room, live) {
4809
5006
  const known = fields.get(field);
4810
5007
  if (known === void 0) fields.set(field, { fast: value, ws: ref[field] ?? BLANK_MOTION[field] });
4811
5008
  else known.fast = value;
4812
- writeField2(ref, field, value, changes);
5009
+ writeField(ref, field, value, changes);
4813
5010
  };
4814
5011
  const expose = () => {
4815
5012
  learn();
@@ -4886,7 +5083,7 @@ function motionGuard(room, live) {
4886
5083
  const resent = room.state.physics !== void 0;
4887
5084
  for (const [ref, fields] of held) {
4888
5085
  if (!current(ref) || resent && owners.get(ref)?.kind === MotionRecordKind.Entity) continue;
4889
- for (const [field, f] of fields) writeField2(ref, field, f.ws, changes);
5086
+ for (const [field, f] of fields) writeField(ref, field, f.ws, changes);
4890
5087
  }
4891
5088
  held.clear();
4892
5089
  if (changes.length === 0) return;
@@ -4913,7 +5110,7 @@ function writeRecord(ref, record, write) {
4913
5110
  write(ref, field, value);
4914
5111
  }
4915
5112
  }
4916
- function writeField2(ref, field, value, changes) {
5113
+ function writeField(ref, field, value, changes) {
4917
5114
  const previousValue = ref[field];
4918
5115
  if (previousValue === value) return;
4919
5116
  ref[field] = value;
@@ -4924,6 +5121,29 @@ function writeField2(ref, field, value, changes) {
4924
5121
  import { Client, getStateCallbacks } from "@colyseus/sdk";
4925
5122
  import { Predict } from "@colyseus/sdk/predict";
4926
5123
 
5124
+ // src/authority/correction.ts
5125
+ var VISIBLE_CORRECTION_METRES = 0.05;
5126
+ var CORRECTION_BUCKET_METRES = [0.5, 1, 5, 20];
5127
+ var PLACE = /* @__PURE__ */ new Set(["x", "y", "z"]);
5128
+ var UNCOUNTED = /* @__PURE__ */ new Set(["mstate", "cap", "spin", "fuel"]);
5129
+ var TWO_PI4 = 2 * Math.PI;
5130
+ function correctionSizes(fields, before, after) {
5131
+ let squares = 0;
5132
+ let state = 0;
5133
+ fields.forEach((field, i) => {
5134
+ if (UNCOUNTED.has(field)) return;
5135
+ const delta = (after[i] ?? 0) - (before[i] ?? 0);
5136
+ if (PLACE.has(field)) squares += delta * delta;
5137
+ else state = Math.max(state, Math.abs(field === "yaw" ? delta - TWO_PI4 * Math.floor((delta + Math.PI) / TWO_PI4) : delta));
5138
+ });
5139
+ return { place: Math.sqrt(squares), state };
5140
+ }
5141
+ var isVisibleCorrection = (place2, unitsPerMetre) => place2 > VISIBLE_CORRECTION_METRES * unitsPerMetre;
5142
+ function correctionBucket(place2, unitsPerMetre) {
5143
+ const bucket = CORRECTION_BUCKET_METRES.findIndex((edge) => place2 < edge * unitsPerMetre);
5144
+ return bucket < 0 ? CORRECTION_BUCKET_METRES.length : bucket;
5145
+ }
5146
+
4927
5147
  // src/authority/tick-drawing.ts
4928
5148
  var RING = 80;
4929
5149
  var fold = (prev, next) => prev + Math.atan2(Math.sin(next - prev), Math.cos(next - prev));
@@ -4979,8 +5199,17 @@ var TickDrawing = class {
4979
5199
  }
4980
5200
  };
4981
5201
 
5202
+ // src/authority/fast-default.ts
5203
+ function defaultFastOpener() {
5204
+ const builtin = globalThis.process?.getBuiltinModule;
5205
+ const dgram = builtin?.("node:dgram");
5206
+ const dns = builtin?.("node:dns");
5207
+ const net = builtin?.("node:net");
5208
+ if (dgram === void 0 || dns === void 0 || net === void 0) return void 0;
5209
+ return udpFastOpener({ createSocket: dgram.createSocket, lookup: dns.lookup, isIP: net.isIP });
5210
+ }
5211
+
4982
5212
  // src/authority/session.ts
4983
- var VISIBLE_CORRECTION = 0.05;
4984
5213
  var LIFT_BLEND_MS = 100;
4985
5214
  var LIFT_DRAWN = ["x", "y", "z", "yaw"];
4986
5215
  var RIDE_FRAME_DONE = 1e-3;
@@ -4990,18 +5219,16 @@ function liftLeftover(was, k, shift, decay) {
4990
5219
  if (was.k === k) return kept;
4991
5220
  return was.shift.map((s, i) => s + kept[i] - shift[i]);
4992
5221
  }
4993
- var CORRECTION_BUCKETS = [0.5, 1, 5, 20];
4994
5222
  var CAPACITY_RETRY_MS = [1e3, 2e3, 4e3];
4995
5223
  var MAX_CATCH_UP_STEPS = 8;
4996
5224
  var SIDE_STALE_MS = 250;
4997
5225
  var PROJECTILE_TTL_MIN_MS = 600;
4998
5226
  var ALL_PREDICTED_FIELDS = ["x", "y", "z", "vx", "vy", "vz", "yaw"];
4999
- var NOT_A_PLACE = /* @__PURE__ */ new Set(["mstate", "cap", "spin", "fuel"]);
5000
- var ANGLES = /* @__PURE__ */ new Set(["yaw"]);
5001
5227
  var SessionEvent = {
5002
5228
  Reconnecting: "reconnecting",
5003
5229
  Reconnected: "reconnected",
5004
- Disconnect: "disconnect"
5230
+ Disconnect: "disconnect",
5231
+ ...RejoinEvent
5005
5232
  };
5006
5233
  var SESSION_EVENTS = new Set(Object.values(SessionEvent));
5007
5234
  var MESSAGE = {
@@ -5026,16 +5253,29 @@ async function joinTargets(opts) {
5026
5253
  const ranked = await selectAuthorityRegions(regions, opts.probe === void 0 ? {} : { probe: opts.probe });
5027
5254
  return ranked.map((pick) => pick.url);
5028
5255
  }
5029
- async function joinAuthority(opts) {
5256
+ async function joinAuthority(opts, target) {
5257
+ const plan = planAuthorityJoin(target);
5258
+ if ("refused" in plan) throw new TypeError(`genex multiplayer: this room cannot be asked for (${plan.refused})`);
5030
5259
  const targets = await joinTargets(opts);
5031
5260
  const delays = opts.capacityRetryMs ?? CAPACITY_RETRY_MS;
5032
5261
  let state = { region: 0, refused: 0, step: 0 };
5033
5262
  for (; ; ) {
5034
5263
  const url = targets[state.region];
5035
5264
  try {
5036
- return { room: await joinOnce(new Client(url), opts), url };
5265
+ return { room: await joinOnce(new Client(url), opts, plan), url, targets };
5037
5266
  } catch (err) {
5038
- const next = errorCode(err) === JoinError.AUTHORITY_CAPACITY ? nextAttempt(state, targets.length, delays) : null;
5267
+ if (plan.method === AuthorityJoinMethod.JoinById) {
5268
+ const region = nextCodeRegion(errorCode(err), state.region, targets.length);
5269
+ if (region === null) explainAuthorityRefusal(err);
5270
+ state = { region, refused: 0, step: 0 };
5271
+ continue;
5272
+ }
5273
+ const kind = refusalKind(errorCode(err));
5274
+ if (kind === RefusalKind.NextRegion && state.region + 1 < targets.length) {
5275
+ state = { region: state.region + 1, refused: 0, step: state.step };
5276
+ continue;
5277
+ }
5278
+ const next = kind === RefusalKind.Capacity ? nextAttempt(state, targets.length, delays) : null;
5039
5279
  if (next === null) explainAuthorityRefusal(err);
5040
5280
  const wait = next.attempt.waitMs;
5041
5281
  if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));
@@ -5043,17 +5283,23 @@ async function joinAuthority(opts) {
5043
5283
  }
5044
5284
  }
5045
5285
  }
5046
- function joinOnce(client, opts) {
5047
- const fastCaps = opts.fast === void 0 ? [] : [AuthorityCaps.FAST];
5048
- return client.joinOrCreate(RoomType.AUTHORITY, {
5286
+ var fastChoiceOf = (opts) => chooseFastSocket(opts.fast ?? defaultFastOpener());
5287
+ function joinOnce(client, opts, plan) {
5288
+ const fastCaps = fastChoiceOf(opts).open === void 0 ? [] : [AuthorityCaps.FAST, AuthorityCaps.FAST_RETURN];
5289
+ const options = {
5049
5290
  ...joinOptions(opts.room, opts.name, opts.auth),
5291
+ ...plan.options,
5050
5292
  // §17.10 *Joining*: this session decodes and draws physics bodies (physics-draw.ts, held to
5051
5293
  // the netcode bench's physics-draw vectors), so it may join a physics room.
5052
5294
  // §17.11: a client that can load the core predicts too.
5053
5295
  [AuthorityJoinOption.CAPS]: predictsPhysics(opts) ? [AuthorityCaps.PHYSICS, AuthorityCaps.PHYSICS_PREDICT, AuthorityCaps.INPUT_REPEAT, AuthorityCaps.LIFTS, AuthorityCaps.INPUT_TICK, ...fastCaps] : [AuthorityCaps.PHYSICS, AuthorityCaps.INPUT_REPEAT, AuthorityCaps.LIFTS, AuthorityCaps.INPUT_TICK, ...fastCaps],
5296
+ [AuthorityJoinOption.SDK]: AUTHORITY_SDK_LABEL,
5054
5297
  // Last, so nothing can misreport it (PROTOCOL.md §17.1: anything but 4 is refused with 426).
5055
5298
  protocol: AUTHORITY_PROTOCOL_VERSION
5056
- });
5299
+ };
5300
+ if (plan.method === AuthorityJoinMethod.JoinById) return client.joinById(plan.roomId, options);
5301
+ if (plan.method === AuthorityJoinMethod.Create) return client.create(RoomType.AUTHORITY, options);
5302
+ return client.joinOrCreate(RoomType.AUTHORITY, options);
5057
5303
  }
5058
5304
  function roomPreset(presetText) {
5059
5305
  if (!presetText) return null;
@@ -5097,8 +5343,8 @@ function heldInput(player) {
5097
5343
  }
5098
5344
  var NOT_A_BODY = { body: false, qx: 0, qy: 0, qz: 0, qw: 1, sleep: false };
5099
5345
  var NOTHING_YET = /* @__PURE__ */ new Map();
5100
- var TWO_PI4 = 2 * Math.PI;
5101
- var wrapAngle = (v) => v - TWO_PI4 * Math.floor((v + Math.PI) / TWO_PI4);
5346
+ var TWO_PI5 = 2 * Math.PI;
5347
+ var wrapAngle = (v) => v - TWO_PI5 * Math.floor((v + Math.PI) / TWO_PI5);
5102
5348
  var SEAT_DRAWINGS = [ContactDrawing.Predicted, ContactDrawing.Separate, ContactDrawing.Timeline];
5103
5349
  var DEFAULT_SEAT_DRAWING = ContactDrawing.Predicted;
5104
5350
  var renderInternals = (reconciler) => {
@@ -5107,8 +5353,35 @@ var renderInternals = (reconciler) => {
5107
5353
  return ok ? r : null;
5108
5354
  };
5109
5355
  async function connectAuthority(opts) {
5110
- const joined = await joinAuthority(opts);
5111
- return attachAuthoritySession(useSdkReconnection(joined.room), opts, joined.url);
5356
+ const joined = await joinAuthority(opts, opts.matchKey === void 0 ? {} : { matchKey: opts.matchKey });
5357
+ return comingBack(attachAuthoritySession(useSdkReconnection(joined.room), opts, joined.url), opts, joined, false);
5358
+ }
5359
+ async function createPrivateAuthority(opts) {
5360
+ const joined = await joinAuthority(opts, { createPrivate: true, ...opts.matchKey === void 0 ? {} : { matchKey: opts.matchKey } });
5361
+ return comingBack(attachAuthoritySession(useSdkReconnection(joined.room), opts, joined.url), opts, joined, true);
5362
+ }
5363
+ async function joinPrivateAuthority(code, opts) {
5364
+ const joined = await joinAuthority(opts, { code, ...opts.matchKey === void 0 ? {} : { matchKey: opts.matchKey } });
5365
+ return comingBack(attachAuthoritySession(useSdkReconnection(joined.room), opts, joined.url), opts, joined, true);
5366
+ }
5367
+ function comingBack(session, opts, joined, closed) {
5368
+ if (opts.rejoin === false) return session;
5369
+ const others = joined.targets.filter((url) => url !== joined.url);
5370
+ return survivingSession(session, {
5371
+ closed,
5372
+ targets: [joined.url, ...others],
5373
+ join: async (url, way, lost) => {
5374
+ const room = await joinOnce(new Client(url), opts, rejoinPlan(way, lost));
5375
+ const next = attachAuthoritySession(useSdkReconnection(room), opts, url);
5376
+ await waitForFirstState(room, ClientReconnection.firstStateTimeoutMs);
5377
+ return next;
5378
+ }
5379
+ });
5380
+ }
5381
+ function rejoinPlan(way, lost) {
5382
+ if (way === RejoinWay.Room) return { method: AuthorityJoinMethod.JoinById, roomId: lost.roomId, options: {} };
5383
+ if (way === RejoinWay.Successor) return { method: AuthorityJoinMethod.JoinOrCreate, options: planSuccessorJoin({ lostRoomId: lost.roomId, matchKey: lost.matchKey, closed: lost.closed }) };
5384
+ return { method: AuthorityJoinMethod.JoinOrCreate, options: lost.matchKey === "" ? {} : { matchKey: lost.matchKey } };
5112
5385
  }
5113
5386
  function attachAuthoritySession(room, opts, url) {
5114
5387
  const state = room.state;
@@ -5116,10 +5389,14 @@ function attachAuthoritySession(room, opts, url) {
5116
5389
  const playerMap = () => state.players ?? NOTHING_YET;
5117
5390
  const renderDelay = opts.renderDelayMs ?? AuthorityTiming.RENDER_DELAY_MS;
5118
5391
  const handle = room.input({ type: AuthorityInput, mode: "reliable", renderDelay });
5392
+ const fastChoice = fastChoiceOf(opts);
5119
5393
  const fast = fastSeat({
5120
5394
  room,
5121
5395
  handle,
5122
- open: opts.fast,
5396
+ open: fastChoice.open,
5397
+ neverAsked: fastChoice.fallback,
5398
+ off: opts.fast === false,
5399
+ log: opts.fastLog ?? defaultFastLog,
5123
5400
  now: opts.fastClock ?? (() => performance.now()),
5124
5401
  tickHz: () => tickHz(),
5125
5402
  stateCurrent: () => !resyncFirstState
@@ -5172,9 +5449,10 @@ function attachAuthoritySession(room, opts, url) {
5172
5449
  inputsDropped: 0,
5173
5450
  reconciles: 0,
5174
5451
  visibleCorrections: 0,
5175
- correctionBuckets: CORRECTION_BUCKETS.map(() => 0).concat(0),
5452
+ correctionBuckets: CORRECTION_BUCKET_METRES.map(() => 0).concat(0),
5176
5453
  peakCorrection: 0,
5177
5454
  lastCorrection: 0,
5455
+ peakStateCorrection: 0,
5178
5456
  reseeds: 0,
5179
5457
  bodyStates: 0,
5180
5458
  bodySnaps: 0,
@@ -5185,8 +5463,8 @@ function attachAuthoritySession(room, opts, url) {
5185
5463
  lastProjectileLeadMs: 0
5186
5464
  };
5187
5465
  const listeners = /* @__PURE__ */ new Map();
5188
- const emit = (event, payload) => {
5189
- for (const handler of listeners.get(event) ?? []) handler(payload);
5466
+ const emit = (event, ...payload) => {
5467
+ for (const handler of listeners.get(event) ?? []) handler(...payload);
5190
5468
  };
5191
5469
  let connected = true;
5192
5470
  let ended = false;
@@ -5449,20 +5727,15 @@ function attachAuthoritySession(room, opts, url) {
5449
5727
  };
5450
5728
  const noteCorrection = (predicted, before, seqBefore) => {
5451
5729
  if (predicted.reconciler.reconcileSeq === seqBefore) return;
5452
- const after = predictedPose(predicted);
5453
- let magnitude = 0;
5454
- predicted.fields.forEach((field, i) => {
5455
- if (NOT_A_PLACE.has(field)) return;
5456
- const delta = (after[i] ?? 0) - (before[i] ?? 0);
5457
- magnitude = Math.max(magnitude, Math.abs(ANGLES.has(field) ? wrapAngle(delta) : delta));
5458
- });
5730
+ const { place: place2, state: stateGap } = correctionSizes(predicted.fields, before, predictedPose(predicted));
5459
5731
  telemetry.reconciles++;
5460
- telemetry.lastCorrection = magnitude;
5461
- telemetry.peakCorrection = Math.max(telemetry.peakCorrection, magnitude);
5462
- if (magnitude > VISIBLE_CORRECTION) {
5732
+ telemetry.lastCorrection = place2;
5733
+ telemetry.peakCorrection = Math.max(telemetry.peakCorrection, place2);
5734
+ telemetry.peakStateCorrection = Math.max(telemetry.peakStateCorrection, stateGap);
5735
+ const unit2 = unitsPerMetre();
5736
+ if (isVisibleCorrection(place2, unit2)) {
5463
5737
  telemetry.visibleCorrections++;
5464
- const bucket = CORRECTION_BUCKETS.findIndex((edge) => magnitude < edge);
5465
- telemetry.correctionBuckets[bucket < 0 ? CORRECTION_BUCKETS.length : bucket] += 1;
5738
+ telemetry.correctionBuckets[correctionBucket(place2, unit2)] += 1;
5466
5739
  }
5467
5740
  };
5468
5741
  const yawBefore = (predicted) => {
@@ -5922,10 +6195,10 @@ function attachAuthoritySession(room, opts, url) {
5922
6195
  predict.dispose();
5923
6196
  physicsPrediction.dispose();
5924
6197
  };
5925
- room.onLeave((code) => {
6198
+ room.onLeave((code, reason) => {
5926
6199
  if (ended) return;
5927
6200
  end();
5928
- emit(SessionEvent.Disconnect, code);
6201
+ emit(SessionEvent.Disconnect, code, authorityCloseCodeOf(reason));
5929
6202
  });
5930
6203
  room.onMessage("*", (type, payload) => {
5931
6204
  if (typeof type !== "string") return;
@@ -6278,6 +6551,10 @@ function attachAuthoritySession(room, opts, url) {
6278
6551
  if (!ended) trackOwn();
6279
6552
  return own ? view(own.id, own.entity) : void 0;
6280
6553
  },
6554
+ code: room.roomId,
6555
+ get matchKey() {
6556
+ return state.matchKey ?? "";
6557
+ },
6281
6558
  setWorld(next) {
6282
6559
  const checked = firstpersonWorld(next);
6283
6560
  const changed2 = checked.hash !== world.hash;
@@ -6414,7 +6691,17 @@ export {
6414
6691
  regionProbePlan,
6415
6692
  rankAuthorityRegions,
6416
6693
  capacityWalk,
6694
+ RefusalKind,
6695
+ refusalKind,
6417
6696
  selectAuthorityRegions,
6697
+ REJOIN,
6698
+ SUCCESSOR_OPEN_MS,
6699
+ RejoinWay,
6700
+ RejoinStop,
6701
+ comesBackAfter,
6702
+ rejoinStep,
6703
+ validSuccessorOf,
6704
+ planSuccessorJoin,
6418
6705
  quatMul,
6419
6706
  quatNormalize,
6420
6707
  slerpFromIdentity,
@@ -6599,19 +6886,15 @@ export {
6599
6886
  throttleState,
6600
6887
  seatLeadClock,
6601
6888
  throttleSteps,
6602
- MotionRecordKind,
6603
- RECORD_ID_MAX_CHARS,
6604
- blankEntityMotion,
6605
- blankSeatEcho,
6606
- quantizeCode,
6607
- dequantizeCode,
6608
- encodeRecord,
6609
- decodeRecord,
6610
- FastFallbackReason,
6611
- isFastBind,
6612
- heldInputs,
6613
- openFastClient,
6889
+ FastTransport,
6890
+ FastNotAsked,
6891
+ FAST_OFFER_WAIT_MS,
6892
+ udpLine,
6893
+ webSocketLine,
6894
+ fastLineAnnouncer,
6614
6895
  SEAT_DRAWINGS,
6615
6896
  DEFAULT_SEAT_DRAWING,
6616
- connectAuthority
6897
+ connectAuthority,
6898
+ createPrivateAuthority,
6899
+ joinPrivateAuthority
6617
6900
  };