@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.
- package/PROTOCOL.md +102 -31
- package/README.md +35 -14
- package/WORLD-GLTF.md +109 -0
- package/WORLD-SCENE.md +135 -0
- package/dist/authority/fast-udp.d.ts +4 -2
- package/dist/authority/fast-udp.js +8 -46
- package/dist/authority/fast-webtransport.d.ts +26 -4
- package/dist/authority/fast-webtransport.js +12 -115
- package/dist/authority/index.d.ts +379 -41
- package/dist/authority/index.js +247 -47
- package/dist/authority/module.d.ts +2 -1
- package/dist/authority/module.js +1 -1
- package/dist/bridge-authority.d.ts +3 -2
- package/dist/bridge-authority.global.js +10 -9
- package/dist/bridge-authority.js +11 -6
- package/dist/bridge.global.js +5 -5
- package/dist/bridge.js +3 -3
- package/dist/chunk-2NGXXQDS.js +1916 -0
- package/dist/{chunk-7QVOIHJF.js → chunk-DJ2XYETS.js} +2 -2
- package/dist/{chunk-Y6XHXDIK.js → chunk-MQYARUGU.js} +864 -539
- package/dist/{chunk-K3SF5VDS.js → chunk-OD6NRSX2.js} +3 -1
- package/dist/{chunk-UX3F6YTV.js → chunk-TEHGMFAG.js} +1 -1
- package/dist/chunk-TSYEWPVO.js +44 -0
- package/dist/chunk-TVTVIMIH.js +675 -0
- package/dist/{chunk-ZSWF4VCN.js → chunk-WEMRRIHH.js} +80 -2
- package/dist/chunk-YWQ3ALYY.js +69 -0
- package/dist/{chunk-5JGRFT2N.js → chunk-ZDGSUR6E.js} +879 -1898
- package/dist/{fast-client-CZ41v-oB.d.ts → fast-client--A88q0kk.d.ts} +85 -4
- package/dist/index.js +3 -3
- package/dist/level-build-CbRqrYJq.d.ts +140 -0
- package/dist/{module-DqtFb5wA.d.ts → module-CSpXrzFc.d.ts} +151 -400
- package/dist/{physics-predict-49udN3a1.d.ts → physics-predict-C7ZB1gDG.d.ts} +18 -3
- package/dist/protocol.d.ts +90 -2
- package/dist/protocol.js +17 -1
- package/dist/shot-world-D22tqOUT.d.ts +485 -0
- package/dist/world-fit/index.d.ts +549 -0
- package/dist/world-fit/index.js +4763 -0
- package/dist/world-fit/three.d.ts +77 -0
- package/dist/world-fit/three.js +140 -0
- package/package.json +15 -3
|
@@ -5,8 +5,12 @@ import {
|
|
|
5
5
|
joinOptions,
|
|
6
6
|
useSdkReconnection,
|
|
7
7
|
waitForFirstState
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-TEHGMFAG.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
|
-
|
|
23
|
+
FAST_BIND_REV,
|
|
20
24
|
FAST_HANDSHAKE_MS,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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,60 @@ 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
|
-
|
|
45
|
+
blankEntityMotion,
|
|
46
|
+
blankSeatEcho,
|
|
47
|
+
chooseFastSocket,
|
|
41
48
|
dequantizeBodyValue,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
firstpersonWorld,
|
|
45
|
-
hullOps,
|
|
46
|
-
inputRedundancyTicks,
|
|
49
|
+
fastRetryDelayMs,
|
|
50
|
+
isFastBind,
|
|
47
51
|
isProjectile,
|
|
48
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
uplinkMaxBytes
|
|
62
|
-
} from "./chunk-5JGRFT2N.js";
|
|
62
|
+
validRoomCode
|
|
63
|
+
} from "./chunk-ZDGSUR6E.js";
|
|
63
64
|
import {
|
|
64
65
|
RemotePredict
|
|
65
|
-
} from "./chunk-
|
|
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,
|
|
86
|
+
FREE_QUOTA_REFUSAL,
|
|
72
87
|
JoinError,
|
|
73
|
-
RoomType
|
|
74
|
-
|
|
88
|
+
RoomType,
|
|
89
|
+
ServerMessage,
|
|
90
|
+
authorityCloseCodeOf
|
|
91
|
+
} from "./chunk-WEMRRIHH.js";
|
|
75
92
|
|
|
76
93
|
// src/authority/regions.ts
|
|
77
94
|
var AUTHORITY_REFUSALS_PER_REGION = 2;
|
|
@@ -120,6 +137,20 @@ function capacityWalk(regionCount, delays) {
|
|
|
120
137
|
}
|
|
121
138
|
return attempts;
|
|
122
139
|
}
|
|
140
|
+
var RefusalKind = {
|
|
141
|
+
/** A full region, or a process shutting down that a sibling answers: the schedule, on this region. */
|
|
142
|
+
Capacity: "capacity",
|
|
143
|
+
/** A dead path, with other regions healthy: the next region at once. */
|
|
144
|
+
NextRegion: "next_region",
|
|
145
|
+
/** The server's answer: final. */
|
|
146
|
+
Final: "final"
|
|
147
|
+
};
|
|
148
|
+
var CAPACITY_REFUSALS = /* @__PURE__ */ new Set([503, 4213]);
|
|
149
|
+
var DEAD_PATH_REFUSALS = /* @__PURE__ */ new Set([502, 504, 520, 521, 522, 523, 524, 525, 526, 530]);
|
|
150
|
+
function refusalKind(code) {
|
|
151
|
+
if (typeof code !== "number" || code <= 0 || DEAD_PATH_REFUSALS.has(code)) return RefusalKind.NextRegion;
|
|
152
|
+
return CAPACITY_REFUSALS.has(code) ? RefusalKind.Capacity : RefusalKind.Final;
|
|
153
|
+
}
|
|
123
154
|
function nextAttempt(state, regionCount, delays) {
|
|
124
155
|
const refused = state.refused + 1;
|
|
125
156
|
if (refused >= AUTHORITY_REFUSALS_PER_REGION && state.region + 1 < regionCount) {
|
|
@@ -192,6 +223,187 @@ async function probeLatencies(plans, options = {}) {
|
|
|
192
223
|
return results;
|
|
193
224
|
}
|
|
194
225
|
|
|
226
|
+
// src/authority/rejoin.ts
|
|
227
|
+
var REJOIN = {
|
|
228
|
+
/** Waits before each attempt; past the end the last one repeats. */
|
|
229
|
+
scheduleMs: [0, 250, 500, 1e3, 2e3, 4e3],
|
|
230
|
+
/** Each wait adds up to this much, by chance, so a room's seats do not arrive at once. */
|
|
231
|
+
jitterMs: 250,
|
|
232
|
+
/** How long a seat keeps trying: long enough for a roll's next process to come up on a full node. */
|
|
233
|
+
budgetMs: 18e4,
|
|
234
|
+
/** How long a seat stays with its own region before it tries the next one. */
|
|
235
|
+
regionStayMs: 6e4,
|
|
236
|
+
/** How long a public seat looks for its room's successor before it joins the game's pool. */
|
|
237
|
+
successorMs: 9e4
|
|
238
|
+
};
|
|
239
|
+
var SUCCESSOR_OPEN_MS = 12e4;
|
|
240
|
+
var RejoinWay = { Room: "room", Successor: "successor", Pool: "pool" };
|
|
241
|
+
var RejoinStop = { Final: "final", Budget: "budget" };
|
|
242
|
+
var COMES_BACK = /* @__PURE__ */ new Set([
|
|
243
|
+
CloseCode.GOING_AWAY,
|
|
244
|
+
CloseCode.NO_STATUS,
|
|
245
|
+
CloseCode.ABNORMAL,
|
|
246
|
+
1011,
|
|
247
|
+
1012,
|
|
248
|
+
1013,
|
|
249
|
+
CloseCode.SERVER_SHUTDOWN,
|
|
250
|
+
CloseCode.FAILED_TO_RECONNECT,
|
|
251
|
+
CloseCode.MAY_TRY_RECONNECT
|
|
252
|
+
]);
|
|
253
|
+
var FINAL_REFUSALS = /* @__PURE__ */ new Set([
|
|
254
|
+
JoinError.BAD_OPTION,
|
|
255
|
+
JoinError.NOT_ENTITLED,
|
|
256
|
+
JoinError.FORBIDDEN,
|
|
257
|
+
JoinError.MODULE_UNAVAILABLE,
|
|
258
|
+
JoinError.PROTOCOL_UNSUPPORTED
|
|
259
|
+
]);
|
|
260
|
+
function comesBackAfter(code) {
|
|
261
|
+
return COMES_BACK.has(code);
|
|
262
|
+
}
|
|
263
|
+
function rejoinStep(input) {
|
|
264
|
+
const { attempt, elapsedMs, refusal, lostCode, closed, regions, jitter } = input;
|
|
265
|
+
if (refusal !== null && FINAL_REFUSALS.has(refusal)) return { stop: RejoinStop.Final };
|
|
266
|
+
if (elapsedMs >= REJOIN.budgetMs) return { stop: RejoinStop.Budget };
|
|
267
|
+
const last = REJOIN.scheduleMs.length - 1;
|
|
268
|
+
const base = REJOIN.scheduleMs[Math.min(attempt, last)];
|
|
269
|
+
const waitMs = base + Math.floor(jitter * REJOIN.jitterMs);
|
|
270
|
+
if (attempt === 0 && lostCode !== CloseCode.SERVER_SHUTDOWN) return { waitMs, region: 0, way: RejoinWay.Room };
|
|
271
|
+
const region = regions < 1 ? 0 : Math.floor(elapsedMs / REJOIN.regionStayMs) % regions;
|
|
272
|
+
const way = closed || elapsedMs < REJOIN.successorMs ? RejoinWay.Successor : RejoinWay.Pool;
|
|
273
|
+
return { waitMs, region, way };
|
|
274
|
+
}
|
|
275
|
+
function validSuccessorOf(value) {
|
|
276
|
+
return validRoomCode(value);
|
|
277
|
+
}
|
|
278
|
+
function planSuccessorJoin(input) {
|
|
279
|
+
return {
|
|
280
|
+
...input.matchKey === "" ? {} : { matchKey: input.matchKey },
|
|
281
|
+
successorOf: input.lostRoomId,
|
|
282
|
+
...input.closed ? { successorClosed: true } : {}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/authority/session-rejoin.ts
|
|
287
|
+
var RejoinEvent = { Draining: "draining", Rejoining: "rejoining", Rejoined: "rejoined" };
|
|
288
|
+
var DISCONNECT = "disconnect";
|
|
289
|
+
var UNKNOWN_CLOSE_MS = -1;
|
|
290
|
+
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
291
|
+
var refusalOf = (err) => {
|
|
292
|
+
const code = err !== null && typeof err === "object" ? err.code : void 0;
|
|
293
|
+
return typeof code === "number" ? code : null;
|
|
294
|
+
};
|
|
295
|
+
function survivingSession(first, deps) {
|
|
296
|
+
const now = deps.now ?? Date.now;
|
|
297
|
+
const random = deps.random ?? Math.random;
|
|
298
|
+
const sleep = deps.sleep ?? defaultSleep;
|
|
299
|
+
const input = { ...first.input };
|
|
300
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
301
|
+
let current = first;
|
|
302
|
+
let left = false;
|
|
303
|
+
let physicsHash = null;
|
|
304
|
+
let world = null;
|
|
305
|
+
const fire = (event, ...payload) => {
|
|
306
|
+
for (const handler of listeners.get(event) ?? []) handler(...payload);
|
|
307
|
+
};
|
|
308
|
+
const forward = (session, event) => {
|
|
309
|
+
if (event === DISCONNECT || event === RejoinEvent.Draining || event === RejoinEvent.Rejoining || event === RejoinEvent.Rejoined) return;
|
|
310
|
+
session.on(event, (...payload) => {
|
|
311
|
+
if (session === current) fire(event, ...payload);
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
const rejoin = async (lostCode, lostReason) => {
|
|
315
|
+
const startedAt = now();
|
|
316
|
+
const lost = { roomId: current.code, matchKey: current.matchKey, closed: deps.closed };
|
|
317
|
+
const regions = deps.targets.length;
|
|
318
|
+
let attempt = 0;
|
|
319
|
+
let refusal = null;
|
|
320
|
+
while (!left) {
|
|
321
|
+
const step = rejoinStep({ attempt, elapsedMs: now() - startedAt, refusal, lostCode, closed: deps.closed, regions, jitter: random() });
|
|
322
|
+
if ("stop" in step) {
|
|
323
|
+
fire(DISCONNECT, lostCode, lostReason);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
attempt += 1;
|
|
327
|
+
fire(RejoinEvent.Rejoining, { attempt, code: lostCode, waitMs: step.waitMs });
|
|
328
|
+
await sleep(step.waitMs);
|
|
329
|
+
if (left) return;
|
|
330
|
+
try {
|
|
331
|
+
const next = await deps.join(deps.targets[step.region], step.way, lost);
|
|
332
|
+
if (left) {
|
|
333
|
+
await next.leave().catch(() => {
|
|
334
|
+
});
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
adopt(next);
|
|
338
|
+
fire(RejoinEvent.Rejoined, { code: next.code, url: next.url });
|
|
339
|
+
return;
|
|
340
|
+
} catch (err) {
|
|
341
|
+
refusal = refusalOf(err);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
const adopt = (session) => {
|
|
346
|
+
current = session;
|
|
347
|
+
if (physicsHash !== null) session.setPhysics(physicsHash);
|
|
348
|
+
if (world !== null) session.setWorld(world);
|
|
349
|
+
for (const event of listeners.keys()) forward(session, event);
|
|
350
|
+
watch(session);
|
|
351
|
+
};
|
|
352
|
+
const watch = (session) => {
|
|
353
|
+
session.on(ServerMessage.SERVER_RESTART, (notice) => {
|
|
354
|
+
if (session !== current) return;
|
|
355
|
+
const drainMs = notice?.drainMs;
|
|
356
|
+
fire(RejoinEvent.Draining, { closesInMs: typeof drainMs === "number" ? drainMs : UNKNOWN_CLOSE_MS });
|
|
357
|
+
});
|
|
358
|
+
session.on(DISCONNECT, (code, reason) => {
|
|
359
|
+
if (session !== current || left) return;
|
|
360
|
+
if (comesBackAfter(code)) void rejoin(code, reason);
|
|
361
|
+
else fire(DISCONNECT, code, reason);
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
watch(first);
|
|
365
|
+
const own = {
|
|
366
|
+
input,
|
|
367
|
+
frame(frameNow) {
|
|
368
|
+
Object.assign(current.input, input);
|
|
369
|
+
return current.frame(frameNow);
|
|
370
|
+
},
|
|
371
|
+
setWorld(next) {
|
|
372
|
+
const hash = current.setWorld(next);
|
|
373
|
+
world = next;
|
|
374
|
+
return hash;
|
|
375
|
+
},
|
|
376
|
+
setPhysics(hash) {
|
|
377
|
+
physicsHash = hash;
|
|
378
|
+
return current.setPhysics(hash);
|
|
379
|
+
},
|
|
380
|
+
on(event, handler) {
|
|
381
|
+
let set = listeners.get(event);
|
|
382
|
+
if (set === void 0) {
|
|
383
|
+
set = /* @__PURE__ */ new Set();
|
|
384
|
+
listeners.set(event, set);
|
|
385
|
+
forward(current, event);
|
|
386
|
+
}
|
|
387
|
+
const fn = handler;
|
|
388
|
+
set.add(fn);
|
|
389
|
+
return () => {
|
|
390
|
+
set.delete(fn);
|
|
391
|
+
};
|
|
392
|
+
},
|
|
393
|
+
async leave() {
|
|
394
|
+
left = true;
|
|
395
|
+
await current.leave();
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
return new Proxy(first, {
|
|
399
|
+
get(_target, key) {
|
|
400
|
+
if (Object.hasOwn(own, key)) return own[key];
|
|
401
|
+
const value = current[key];
|
|
402
|
+
return typeof value === "function" ? value.bind(current) : value;
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
195
407
|
// src/authority/physics-draw.ts
|
|
196
408
|
var IDENTITY = [0, 0, 0, 1];
|
|
197
409
|
function quatMul(a, b) {
|
|
@@ -289,85 +501,127 @@ var GRID_SIDE = 32;
|
|
|
289
501
|
var GRID_CELL_MIN = 64;
|
|
290
502
|
var GRID_WIDE = 64;
|
|
291
503
|
var GRID_QUERY_CELLS = 16;
|
|
504
|
+
var GRID_MERGED_WIDE = 64;
|
|
292
505
|
var UNSTICK_SOLIDS = 4;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
[0, 0, -1, -(z0 - h)]
|
|
302
|
-
];
|
|
303
|
-
}
|
|
304
|
-
function grownRamp(ramp, r, h) {
|
|
506
|
+
var BOUNDS_STRIDE = 6;
|
|
507
|
+
var PLANE_STRIDE = 4;
|
|
508
|
+
var BOX_PLANES = 6;
|
|
509
|
+
var RAMP_PLANES = 7;
|
|
510
|
+
var RAMP_STRIDE = RAMP_PLANES * PLANE_STRIDE;
|
|
511
|
+
var BOX_NORMALS = [[-1, 0, 0], [1, 0, 0], [0, -1, 0], [0, 1, 0], [0, 0, 1], [0, 0, -1]];
|
|
512
|
+
var BOX_OFFSETS = [0, 3, 1, 4, 5, 2];
|
|
513
|
+
function writeRamp(out, o, ramp, r, h) {
|
|
305
514
|
const [x0, y0, z0, x1, y1, z1, axis, lo, hi] = ramp;
|
|
306
515
|
const m0 = axis === 0 ? x0 : y0;
|
|
307
516
|
const slope = (hi - lo) / ((axis === 0 ? x1 : y1) - m0);
|
|
308
517
|
const k = 1 / Math.sqrt(slope * slope + 1);
|
|
309
518
|
const e = (lo - slope * m0 + r * Math.abs(slope)) * k;
|
|
310
|
-
const
|
|
311
|
-
|
|
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]] };
|
|
519
|
+
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];
|
|
520
|
+
out.set(planes, o);
|
|
315
521
|
}
|
|
316
522
|
function compileContactWorld(spec, r, h, skin) {
|
|
317
|
-
const boxes =
|
|
318
|
-
const ramps =
|
|
319
|
-
const
|
|
320
|
-
const
|
|
321
|
-
|
|
523
|
+
const boxes = spec?.boxes ?? [];
|
|
524
|
+
const ramps = spec?.ramps ?? [];
|
|
525
|
+
const count = boxes.length + ramps.length;
|
|
526
|
+
const bounds = new Float64Array(count * BOUNDS_STRIDE);
|
|
527
|
+
const rampPlanes = new Float64Array(ramps.length * RAMP_STRIDE);
|
|
528
|
+
for (let i = 0; i < count; i++) {
|
|
529
|
+
const src = i < boxes.length ? boxes[i] : ramps[i - boxes.length];
|
|
530
|
+
const o = i * BOUNDS_STRIDE;
|
|
531
|
+
bounds[o] = src[0] - r;
|
|
532
|
+
bounds[o + 1] = src[1] - r;
|
|
533
|
+
bounds[o + 2] = src[2] - h;
|
|
534
|
+
bounds[o + 3] = src[3] + r;
|
|
535
|
+
bounds[o + 4] = src[4] + r;
|
|
536
|
+
bounds[o + 5] = src[5];
|
|
537
|
+
if (i >= boxes.length) writeRamp(rampPlanes, (i - boxes.length) * RAMP_STRIDE, src, r, h);
|
|
538
|
+
}
|
|
539
|
+
const world = { count, boxCount: boxes.length, bounds, rampPlanes, skin };
|
|
540
|
+
const grid = gridOf(world);
|
|
541
|
+
return grid === null ? world : { ...world, grid };
|
|
542
|
+
}
|
|
543
|
+
function placeable(b, i) {
|
|
544
|
+
const o = i * BOUNDS_STRIDE;
|
|
545
|
+
return Number.isFinite(b[o]) && Number.isFinite(b[o + 3]) && Number.isFinite(b[o + 1]) && Number.isFinite(b[o + 4]);
|
|
322
546
|
}
|
|
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
547
|
function cellOf(v, origin, size, n) {
|
|
325
548
|
const c = Math.floor((v - origin) / size);
|
|
326
549
|
if (c < 0) return 0;
|
|
327
550
|
return c > n - 1 ? n - 1 : c;
|
|
328
551
|
}
|
|
329
|
-
function gridOf(
|
|
330
|
-
const
|
|
331
|
-
|
|
552
|
+
function gridOf(world) {
|
|
553
|
+
const { count, bounds } = world;
|
|
554
|
+
let placed2 = 0;
|
|
332
555
|
let x0 = Number.POSITIVE_INFINITY;
|
|
333
556
|
let y0 = Number.POSITIVE_INFINITY;
|
|
334
557
|
let x1 = Number.NEGATIVE_INFINITY;
|
|
335
558
|
let y1 = Number.NEGATIVE_INFINITY;
|
|
336
|
-
for (
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
559
|
+
for (let k = 0; k < count; k++) {
|
|
560
|
+
if (!placeable(bounds, k)) continue;
|
|
561
|
+
placed2 += 1;
|
|
562
|
+
const o = k * BOUNDS_STRIDE;
|
|
563
|
+
x0 = Math.min(x0, bounds[o] - CAST_MARGIN);
|
|
564
|
+
y0 = Math.min(y0, bounds[o + 1] - CAST_MARGIN);
|
|
565
|
+
x1 = Math.max(x1, bounds[o + 3] + CAST_MARGIN);
|
|
566
|
+
y1 = Math.max(y1, bounds[o + 4] + CAST_MARGIN);
|
|
567
|
+
}
|
|
568
|
+
if (placed2 < GRID_MIN_SOLIDS) return null;
|
|
342
569
|
const size = Math.max(GRID_CELL_MIN, (x1 - x0) / GRID_SIDE, (y1 - y0) / GRID_SIDE);
|
|
570
|
+
if (!Number.isFinite(size)) return null;
|
|
343
571
|
const nx = Math.min(GRID_SIDE, Math.floor((x1 - x0) / size) + 1);
|
|
344
572
|
const ny = Math.min(GRID_SIDE, Math.floor((y1 - y0) / size) + 1);
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
573
|
+
const visit = (onWide, onCell) => {
|
|
574
|
+
for (let k = 0; k < count; k++) {
|
|
575
|
+
if (!placeable(bounds, k)) {
|
|
576
|
+
onWide(k);
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
const o = k * BOUNDS_STRIDE;
|
|
580
|
+
const i0 = cellOf(bounds[o] - CAST_MARGIN, x0, size, nx);
|
|
581
|
+
const i1 = cellOf(bounds[o + 3] + CAST_MARGIN, x0, size, nx);
|
|
582
|
+
const j0 = cellOf(bounds[o + 1] - CAST_MARGIN, y0, size, ny);
|
|
583
|
+
const j1 = cellOf(bounds[o + 4] + CAST_MARGIN, y0, size, ny);
|
|
584
|
+
if ((i1 - i0 + 1) * (j1 - j0 + 1) > GRID_WIDE) {
|
|
585
|
+
onWide(k);
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
for (let j = j0; j <= j1; j++) for (let i = i0; i <= i1; i++) onCell(i + j * nx, k);
|
|
351
589
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
590
|
+
};
|
|
591
|
+
const cellStart = new Int32Array(nx * ny + 1);
|
|
592
|
+
let wideCount = 0;
|
|
593
|
+
visit(
|
|
594
|
+
() => {
|
|
595
|
+
wideCount += 1;
|
|
596
|
+
},
|
|
597
|
+
(c) => {
|
|
598
|
+
cellStart[c + 1] += 1;
|
|
359
599
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
600
|
+
);
|
|
601
|
+
for (let c = 0; c < nx * ny; c++) cellStart[c + 1] = cellStart[c + 1] + cellStart[c];
|
|
602
|
+
const cellSolids = new Int32Array(cellStart[nx * ny]);
|
|
603
|
+
const wide = new Int32Array(wideCount);
|
|
604
|
+
const written = cellStart.slice();
|
|
605
|
+
let wideAt = 0;
|
|
606
|
+
visit(
|
|
607
|
+
(k) => {
|
|
608
|
+
wide[wideAt] = k;
|
|
609
|
+
wideAt += 1;
|
|
610
|
+
},
|
|
611
|
+
(c, k) => {
|
|
612
|
+
cellSolids[written[c]] = k;
|
|
613
|
+
written[c] = written[c] + 1;
|
|
614
|
+
}
|
|
615
|
+
);
|
|
616
|
+
return { x0, y0, size, nx, ny, cellStart, cellSolids, wide, merged: new Array(wideAt <= GRID_MERGED_WIDE ? nx * ny : 0) };
|
|
363
617
|
}
|
|
364
|
-
function mergeAscending(a, b) {
|
|
365
|
-
if (
|
|
618
|
+
function mergeAscending(a, b, from, to) {
|
|
619
|
+
if (to === from) return a;
|
|
366
620
|
const out = [];
|
|
367
621
|
let i = 0;
|
|
368
|
-
let k =
|
|
369
|
-
while (i < a.length || k <
|
|
370
|
-
const fromA = k >=
|
|
622
|
+
let k = from;
|
|
623
|
+
while (i < a.length || k < to) {
|
|
624
|
+
const fromA = k >= to || i < a.length && a[i] <= b[k];
|
|
371
625
|
const next = fromA ? a[i] : b[k];
|
|
372
626
|
if (fromA) i += 1;
|
|
373
627
|
else k += 1;
|
|
@@ -379,50 +633,100 @@ function solidsNear(world, lx, hx, ly, hy) {
|
|
|
379
633
|
const g = world.grid;
|
|
380
634
|
if (g === void 0 || !(Number.isFinite(lx) && Number.isFinite(hx) && Number.isFinite(ly) && Number.isFinite(hy))) return null;
|
|
381
635
|
const i0 = cellOf(lx, g.x0, g.size, g.nx);
|
|
382
|
-
const i1 = cellOf(hx, g.x0, g.size, g.nx);
|
|
636
|
+
const i1 = hx === lx ? i0 : cellOf(hx, g.x0, g.size, g.nx);
|
|
383
637
|
const j0 = cellOf(ly, g.y0, g.size, g.ny);
|
|
384
|
-
const j1 = cellOf(hy, g.y0, g.size, g.ny);
|
|
638
|
+
const j1 = hy === ly ? j0 : cellOf(hy, g.y0, g.size, g.ny);
|
|
385
639
|
if ((i1 - i0 + 1) * (j1 - j0 + 1) > GRID_QUERY_CELLS) return null;
|
|
640
|
+
if (i0 === i1 && j0 === j1 && g.merged.length > 0) {
|
|
641
|
+
const c = i0 + j0 * g.nx;
|
|
642
|
+
const kept = g.merged[c];
|
|
643
|
+
if (kept !== void 0) return kept;
|
|
644
|
+
const made = mergeAscending(g.wide, g.cellSolids, g.cellStart[c], g.cellStart[c + 1]);
|
|
645
|
+
g.merged[c] = made;
|
|
646
|
+
return made;
|
|
647
|
+
}
|
|
386
648
|
let near2 = g.wide;
|
|
387
|
-
for (let j = j0; j <= j1; j++)
|
|
649
|
+
for (let j = j0; j <= j1; j++) {
|
|
650
|
+
for (let i = i0; i <= i1; i++) {
|
|
651
|
+
const c = i + j * g.nx;
|
|
652
|
+
near2 = mergeAscending(near2, g.cellSolids, g.cellStart[c], g.cellStart[c + 1]);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
388
655
|
return near2;
|
|
389
656
|
}
|
|
390
|
-
|
|
391
|
-
|
|
657
|
+
var P = { nx: 0, ny: 0, nz: 0, e: 0 };
|
|
658
|
+
function readPlane(world, i, k) {
|
|
659
|
+
if (i < world.boxCount) {
|
|
660
|
+
const n = BOX_NORMALS[k];
|
|
661
|
+
const at2 = BOX_OFFSETS[k];
|
|
662
|
+
const v = world.bounds[i * BOUNDS_STRIDE + at2];
|
|
663
|
+
P.nx = n[0];
|
|
664
|
+
P.ny = n[1];
|
|
665
|
+
P.nz = n[2];
|
|
666
|
+
P.e = at2 < 3 ? -v : v;
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
const o = (i - world.boxCount) * RAMP_STRIDE + k * PLANE_STRIDE;
|
|
670
|
+
const r = world.rampPlanes;
|
|
671
|
+
P.nx = r[o];
|
|
672
|
+
P.ny = r[o + 1];
|
|
673
|
+
P.nz = r[o + 2];
|
|
674
|
+
P.e = r[o + 3];
|
|
675
|
+
}
|
|
676
|
+
var planesOf = (world, i) => i < world.boxCount ? BOX_PLANES : RAMP_PLANES;
|
|
677
|
+
function insideSolid(world, i, skin, x, y, z) {
|
|
678
|
+
const n = planesOf(world, i);
|
|
679
|
+
for (let k = 0; k < n; k++) {
|
|
680
|
+
readPlane(world, i, k);
|
|
681
|
+
if (!(P.e - (P.nx * x + P.ny * y + P.nz * z) > skin)) return false;
|
|
682
|
+
}
|
|
392
683
|
return true;
|
|
393
684
|
}
|
|
685
|
+
var solidsAt = (world, x, y) => solidsNear(world, x, x, y, y);
|
|
394
686
|
function insideWorld(world, x, y, z) {
|
|
395
|
-
|
|
687
|
+
const near2 = solidsAt(world, x, y);
|
|
688
|
+
const n = near2 === null ? world.count : near2.length;
|
|
689
|
+
for (let m = 0; m < n; m++) {
|
|
690
|
+
if (insideSolid(world, near2 === null ? m : near2[m], world.skin, x, y, z)) return true;
|
|
691
|
+
}
|
|
396
692
|
return false;
|
|
397
693
|
}
|
|
398
|
-
function segmentNear(s, p, d, len) {
|
|
694
|
+
function segmentNear(world, s, p, d, len) {
|
|
695
|
+
const o = s * BOUNDS_STRIDE;
|
|
399
696
|
for (let i = 0; i < 3; i++) {
|
|
400
697
|
const a = p[i];
|
|
401
698
|
const b = a + d[i] * len;
|
|
402
699
|
const lo = a < b ? a : b;
|
|
403
700
|
const hi = a < b ? b : a;
|
|
404
|
-
if (hi <
|
|
701
|
+
if (hi < world.bounds[o + i] - CAST_MARGIN || lo > world.bounds[o + 3 + i] + CAST_MARGIN) return false;
|
|
405
702
|
}
|
|
406
703
|
return true;
|
|
407
704
|
}
|
|
408
|
-
function bandHit(s, skin, p, d, rides) {
|
|
409
|
-
|
|
410
|
-
|
|
705
|
+
function bandHit(world, s, skin, p, d, rides) {
|
|
706
|
+
const n = planesOf(world, s);
|
|
707
|
+
for (let k = 0; k < n; k++) {
|
|
708
|
+
readPlane(world, s, k);
|
|
709
|
+
const { nx, ny, nz, e } = P;
|
|
411
710
|
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
|
|
711
|
+
if (margin <= skin && nx * d[0] + ny * d[1] + nz * d[2] < 0) {
|
|
712
|
+
const plane = [nx, ny, nz, e];
|
|
713
|
+
if (!rides?.(plane)) return { t: 0, face: plane };
|
|
714
|
+
}
|
|
413
715
|
}
|
|
414
716
|
return null;
|
|
415
717
|
}
|
|
416
718
|
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 (
|
|
419
|
-
if (
|
|
719
|
+
function enterSolid(world, s, skin, p, d, len, rides) {
|
|
720
|
+
if (insideSolid(world, s, skin, p[0], p[1], p[2])) return null;
|
|
721
|
+
if (insideSolid(world, s, -skin, p[0], p[1], p[2])) return bandHit(world, s, skin, p, d, rides);
|
|
420
722
|
let lo = 0;
|
|
421
723
|
let hi = len;
|
|
422
724
|
let last = Number.NEGATIVE_INFINITY;
|
|
423
|
-
let face =
|
|
424
|
-
|
|
425
|
-
|
|
725
|
+
let face = -1;
|
|
726
|
+
const n = planesOf(world, s);
|
|
727
|
+
for (let k = 0; k < n; k++) {
|
|
728
|
+
readPlane(world, s, k);
|
|
729
|
+
const { nx, ny, nz, e } = P;
|
|
426
730
|
const s0 = e + skin - (nx * p[0] + ny * p[1] + nz * p[2]);
|
|
427
731
|
const sd = nx * d[0] + ny * d[1] + nz * d[2];
|
|
428
732
|
if (sd === 0) {
|
|
@@ -433,26 +737,29 @@ function enterSolid(s, skin, p, d, len, rides) {
|
|
|
433
737
|
if (sd > 0 && t2 < hi) hi = t2;
|
|
434
738
|
if (sd < 0 && t2 > last) {
|
|
435
739
|
last = t2;
|
|
436
|
-
face =
|
|
740
|
+
face = k;
|
|
437
741
|
}
|
|
438
742
|
}
|
|
439
743
|
if (last > lo) lo = last;
|
|
440
|
-
if (face
|
|
441
|
-
|
|
744
|
+
if (face < 0 || !(lo < hi)) return null;
|
|
745
|
+
readPlane(world, s, face);
|
|
746
|
+
const plane = [P.nx, P.ny, P.nz, P.e];
|
|
747
|
+
if (rides?.(plane)) return null;
|
|
748
|
+
return { t: lo, face: plane };
|
|
442
749
|
}
|
|
443
750
|
function castWorld(world, p, d, len, rides) {
|
|
444
751
|
let best = { t: len, face: null };
|
|
445
752
|
if (!(len > 0)) return { t: 0, face: null };
|
|
446
753
|
const visit = (s) => {
|
|
447
|
-
if (!segmentNear(s, p, d, best.t)) return;
|
|
448
|
-
const hit = enterSolid(s, world.skin, p, d, best.t, rides);
|
|
754
|
+
if (!segmentNear(world, s, p, d, best.t)) return;
|
|
755
|
+
const hit = enterSolid(world, s, world.skin, p, d, best.t, rides);
|
|
449
756
|
if (hit !== null && hit.t < best.t) best = hit;
|
|
450
757
|
};
|
|
451
758
|
const [px, py] = [p[0], p[1]];
|
|
452
759
|
const [ex, ey] = [px + d[0] * len, py + d[1] * len];
|
|
453
760
|
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 (
|
|
455
|
-
else for (
|
|
761
|
+
if (near2 === null) for (let s = 0; s < world.count; s++) visit(s);
|
|
762
|
+
else for (let k = 0; k < near2.length; k++) visit(near2[k]);
|
|
456
763
|
return best;
|
|
457
764
|
}
|
|
458
765
|
function roomAlong(world, p, d, len, rides) {
|
|
@@ -469,18 +776,22 @@ function surfaceUp(world, x, y, z, trace) {
|
|
|
469
776
|
return [hit.face[0], hit.face[1], hit.face[2]];
|
|
470
777
|
}
|
|
471
778
|
function covered(world, self, x, y, z, q) {
|
|
472
|
-
const {
|
|
473
|
-
|
|
779
|
+
const { count, skin } = world;
|
|
780
|
+
const near2 = solidsAt(world, q[0], q[1]);
|
|
781
|
+
const n = near2 === null ? count : near2.length;
|
|
782
|
+
for (let m = 0; m < n; m++) {
|
|
783
|
+
const j = near2 === null ? m : near2[m];
|
|
474
784
|
if (j === self) continue;
|
|
475
|
-
|
|
476
|
-
if (insidePlanes(planes, skin, q[0], q[1], q[2]) && !insidePlanes(planes, skin, x, y, z)) return true;
|
|
785
|
+
if (insideSolid(world, j, skin, q[0], q[1], q[2]) && !insideSolid(world, j, skin, x, y, z)) return true;
|
|
477
786
|
}
|
|
478
787
|
return false;
|
|
479
788
|
}
|
|
480
|
-
function leastFace(world, i,
|
|
789
|
+
function leastFace(world, i, x, y, z) {
|
|
481
790
|
let least = null;
|
|
482
|
-
|
|
483
|
-
|
|
791
|
+
const n = planesOf(world, i);
|
|
792
|
+
for (let k = 0; k < n - 1; k++) {
|
|
793
|
+
readPlane(world, i, k);
|
|
794
|
+
const { nx, ny, nz, e } = P;
|
|
484
795
|
const depth = e - (nx * x + ny * y + nz * z);
|
|
485
796
|
if (least !== null && !(depth < least[3])) continue;
|
|
486
797
|
if (nz > 0 && covered(world, i, x, y, z, [x + nx * depth, y + ny * depth, z + nz * depth])) continue;
|
|
@@ -489,10 +800,12 @@ function leastFace(world, i, planes, x, y, z) {
|
|
|
489
800
|
return least;
|
|
490
801
|
}
|
|
491
802
|
function insideFace(world, x, y, z) {
|
|
492
|
-
const {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
803
|
+
const { count, skin } = world;
|
|
804
|
+
const near2 = solidsAt(world, x, y);
|
|
805
|
+
const n = near2 === null ? count : near2.length;
|
|
806
|
+
for (let m = 0; m < n; m++) {
|
|
807
|
+
const i = near2 === null ? m : near2[m];
|
|
808
|
+
if (insideSolid(world, i, skin, x, y, z)) return leastFace(world, i, x, y, z);
|
|
496
809
|
}
|
|
497
810
|
return null;
|
|
498
811
|
}
|
|
@@ -528,7 +841,13 @@ var cross = (p, q) => [p[1] * q[2] - p[2] * q[1], p[2] * q[0] - p[0] * q[2], p[0
|
|
|
528
841
|
var neg = (p) => [-p[0], -p[1], -p[2]];
|
|
529
842
|
function supportOf(b, policy, env) {
|
|
530
843
|
if (env.supportUp !== void 0) return env.supportUp(b.x, b.y, b.z);
|
|
531
|
-
|
|
844
|
+
const memo = env.supports;
|
|
845
|
+
if (memo === void 0) return surfaceUp(env.world, b.x, b.y, b.z, policy.groundTrace);
|
|
846
|
+
const known = memo.get(b);
|
|
847
|
+
if (known !== void 0 && known.x === b.x && known.y === b.y && known.z === b.z) return known.up;
|
|
848
|
+
const up = surfaceUp(env.world, b.x, b.y, b.z, policy.groundTrace);
|
|
849
|
+
memo.set(b, { x: b.x, y: b.y, z: b.z, up });
|
|
850
|
+
return up;
|
|
532
851
|
}
|
|
533
852
|
function frameOf(b, policy, env) {
|
|
534
853
|
const shape = policy.shape;
|
|
@@ -981,7 +1300,9 @@ function contactPair(a, b, policy, env, cooling = false, others = []) {
|
|
|
981
1300
|
const fa = frameOf(a, policy, env);
|
|
982
1301
|
const fb = frameOf(b, policy, env);
|
|
983
1302
|
const found = contactOfFrames(fa, fb, policy);
|
|
984
|
-
|
|
1303
|
+
return found === null ? null : stepFound(fa, fb, found, rule, policy, env, cooling, others);
|
|
1304
|
+
}
|
|
1305
|
+
function stepFound(fa, fb, found, rule, policy, env, cooling, others) {
|
|
985
1306
|
const { contact, g } = found;
|
|
986
1307
|
if (contact.kind === ContactKind.Side) return { ...contact, ...sideContact(fa, fb, contact, g, policy, env, cooling), way: null, w: null };
|
|
987
1308
|
return { ...contact, ...topContact(fa, fb, contact, g, rule, policy, env, others) };
|
|
@@ -992,15 +1313,38 @@ function pairOfPass(a, b, pass, policy, env, options, seats) {
|
|
|
992
1313
|
return own === void 0 ? contactPair(a, b, policy, env, cooling, seats) : own;
|
|
993
1314
|
}
|
|
994
1315
|
var stillTouching = (c, policy) => c !== null && (c.depth > policy.slop || c.kind === ContactKind.Top && c.depth > 0);
|
|
995
|
-
|
|
1316
|
+
var memoryOf = (count) => ({ moves: new Int32Array(count), still: new Int32Array(count * count).fill(-1) });
|
|
1317
|
+
function stepIfTouching(a, b, policy, env, seats) {
|
|
1318
|
+
if (farApart(a, b, policy)) return null;
|
|
1319
|
+
const rule = topRuleOf(policy, a, b);
|
|
1320
|
+
if (rule.policy === TopPolicy.Ghost) return null;
|
|
1321
|
+
const fa = frameOf(a, policy, env);
|
|
1322
|
+
const fb = frameOf(b, policy, env);
|
|
1323
|
+
const found = contactOfFrames(fa, fb, policy);
|
|
1324
|
+
if (found === null || !stillTouching(found.contact, policy)) return null;
|
|
1325
|
+
return stepFound(fa, fb, found, rule, policy, env, false, seats);
|
|
1326
|
+
}
|
|
1327
|
+
function passOnce(seats, pass, policy, env, options, report, memory) {
|
|
996
1328
|
let touched = false;
|
|
997
|
-
|
|
998
|
-
|
|
1329
|
+
const count = seats.length;
|
|
1330
|
+
for (let i = 0; i < count; i++) {
|
|
1331
|
+
for (let k = i + 1; k < count; k++) {
|
|
999
1332
|
const a = seats[i];
|
|
1000
1333
|
const b = seats[k];
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1334
|
+
const at2 = i * count + k;
|
|
1335
|
+
if (memory !== null && pass > 0 && memory.still[at2] === memory.moves[i] + memory.moves[k]) continue;
|
|
1336
|
+
let out;
|
|
1337
|
+
if (memory !== null && pass > 0) out = stepIfTouching(a, b, policy, env, seats);
|
|
1338
|
+
else if (pass > 0 && !stillTouching(contactOf(a, b, policy, env), policy)) continue;
|
|
1339
|
+
else out = pairOfPass(a, b, pass, policy, env, options, seats);
|
|
1340
|
+
if (out === null) {
|
|
1341
|
+
if (memory !== null) memory.still[at2] = memory.moves[i] + memory.moves[k];
|
|
1342
|
+
continue;
|
|
1343
|
+
}
|
|
1344
|
+
if (memory !== null) {
|
|
1345
|
+
memory.moves[i] = memory.moves[i] + 1;
|
|
1346
|
+
memory.moves[k] = memory.moves[k] + 1;
|
|
1347
|
+
}
|
|
1004
1348
|
touched = true;
|
|
1005
1349
|
if (out.way === SlideWay.Pinned) report.pinned += 1;
|
|
1006
1350
|
options.onPair?.(a, b, out);
|
|
@@ -1010,17 +1354,20 @@ function passOnce(seats, pass, policy, env, options, report) {
|
|
|
1010
1354
|
}
|
|
1011
1355
|
function contactPass(seats, policy, env, options = {}) {
|
|
1012
1356
|
const report = { passes: 0, pushed: /* @__PURE__ */ new Map(), walls: /* @__PURE__ */ new Map(), pinned: 0 };
|
|
1357
|
+
env = { ...env, supports: /* @__PURE__ */ new Map() };
|
|
1358
|
+
const memory = options.first === void 0 && options.onPair === void 0 && options.cooling === void 0 ? memoryOf(seats.length) : null;
|
|
1013
1359
|
for (let pass = 0; pass < policy.passes; pass++) {
|
|
1014
|
-
if (!passOnce(seats, pass, policy, env, options, report)) break;
|
|
1360
|
+
if (!passOnce(seats, pass, policy, env, options, report, memory)) break;
|
|
1015
1361
|
report.passes += 1;
|
|
1016
1362
|
if (env.world === null) continue;
|
|
1017
|
-
|
|
1363
|
+
seats.forEach((seat, s) => {
|
|
1018
1364
|
const wall = (face) => {
|
|
1019
1365
|
if (face[2] < policy.supportCos && face[3] > (report.walls.get(seat.id) ?? 0)) report.walls.set(seat.id, face[3]);
|
|
1020
1366
|
};
|
|
1021
1367
|
const depth = unstickBody(env.world, seat, wall);
|
|
1022
1368
|
if (depth > (report.pushed.get(seat.id) ?? 0)) report.pushed.set(seat.id, depth);
|
|
1023
|
-
|
|
1369
|
+
if (memory !== null && depth > 0) memory.moves[s] = memory.moves[s] + 1;
|
|
1370
|
+
});
|
|
1024
1371
|
}
|
|
1025
1372
|
return report;
|
|
1026
1373
|
}
|
|
@@ -4110,7 +4457,10 @@ var roomStateFields = {
|
|
|
4110
4457
|
joints: t.map(AuthorityJoint).optional(),
|
|
4111
4458
|
// Lift rooms (PROTOCOL.md §17.11 *Lifts*): every called lift's start tick, JSON text `{"id":tick,…}`
|
|
4112
4459
|
// (ids ascending), written when a module's ctx.lifts.call is taken. Absent = none called.
|
|
4113
|
-
lifts: t.string().optional()
|
|
4460
|
+
lifts: t.string().optional(),
|
|
4461
|
+
// The room's match key (PROTOCOL.md §17.2 *Choosing a room*): how a friend who joined by code learns
|
|
4462
|
+
// which world to hand `setWorld`. Absent = no key, and never written.
|
|
4463
|
+
matchKey: t.string().optional()
|
|
4114
4464
|
};
|
|
4115
4465
|
var AuthorityRoomState = schema(roomStateFields, "AuthorityRoomState");
|
|
4116
4466
|
var AuthorityPhysicsRoomState = schema({ ...roomStateFields, entities: t.map(AuthorityPhysicsEntity).view() }, "AuthorityRoomState");
|
|
@@ -4250,380 +4600,98 @@ function throttleSteps(t2, dtMs, stepMs) {
|
|
|
4250
4600
|
return steps;
|
|
4251
4601
|
}
|
|
4252
4602
|
|
|
4253
|
-
// src/authority/fast-
|
|
4254
|
-
var
|
|
4255
|
-
var
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
Velocity: "q-velocity",
|
|
4263
|
-
/** A boolean: the mask bit alone. */
|
|
4264
|
-
Flag: "flag"
|
|
4603
|
+
// src/authority/fast-line.ts
|
|
4604
|
+
var FastTransport = { Udp: "udp", WebSocket: "websocket" };
|
|
4605
|
+
var FastNotAsked = {
|
|
4606
|
+
/** The game turned it off (`fast: false`, `bFastChannel = false`, ...). */
|
|
4607
|
+
Off: "off",
|
|
4608
|
+
/** This runtime has no UDP socket (WebGL, a browser with no opener). */
|
|
4609
|
+
NoUdp: "no_udp",
|
|
4610
|
+
/** The seat asked and the room sent no binding. */
|
|
4611
|
+
NotOffered: "not_offered"
|
|
4265
4612
|
};
|
|
4266
|
-
var
|
|
4267
|
-
|
|
4268
|
-
["
|
|
4269
|
-
["
|
|
4270
|
-
["
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
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;
|
|
4613
|
+
var FAST_OFFER_WAIT_MS = FAST_HANDSHAKE_MS;
|
|
4614
|
+
var WHY = {
|
|
4615
|
+
[FastNotAsked.Off]: "the game turned the fast channel off",
|
|
4616
|
+
[FastNotAsked.NoUdp]: "this platform has no UDP",
|
|
4617
|
+
[FastNotAsked.NotOffered]: "the room does not offer the fast channel",
|
|
4618
|
+
unreachable: "fell back, the binding could not be used or its socket failed",
|
|
4619
|
+
handshake: "fell back, no UDP snapshot within 1 s of the first datagram (UDP blocked?)",
|
|
4620
|
+
stall: "fell back, no UDP snapshot for 2 s",
|
|
4621
|
+
server: "fell back, the room closed the channel",
|
|
4622
|
+
"no-webtransport": "this browser has no WebTransport",
|
|
4623
|
+
"no-relay": "the room has no WebTransport relay"
|
|
4350
4624
|
};
|
|
4351
|
-
var
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
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);
|
|
4625
|
+
var udpLine = (url) => `genex transport: udp fast@1 ${url}`;
|
|
4626
|
+
var webSocketLine = (reason) => `genex transport: websocket, ${reason}: ${WHY[reason] ?? "unknown"}`;
|
|
4627
|
+
function fastLineAnnouncer() {
|
|
4628
|
+
const said = /* @__PURE__ */ new Set();
|
|
4629
|
+
const once = (line) => {
|
|
4630
|
+
if (said.has(line)) return null;
|
|
4631
|
+
said.add(line);
|
|
4632
|
+
return line;
|
|
4633
|
+
};
|
|
4553
4634
|
return {
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
return
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
return
|
|
4560
|
-
|
|
4561
|
-
get closed() {
|
|
4562
|
-
return closed;
|
|
4563
|
-
},
|
|
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;
|
|
4635
|
+
observe(o) {
|
|
4636
|
+
if (o.joinedMs === null) return null;
|
|
4637
|
+
if (!o.asked) return once(webSocketLine(FastNotAsked.Off));
|
|
4638
|
+
if (!o.udp) return once(webSocketLine(FastNotAsked.NoUdp));
|
|
4639
|
+
if (o.fallback !== null) return once(webSocketLine(o.fallback));
|
|
4640
|
+
if (o.url !== null) return o.snapshots > 0 ? once(udpLine(o.url)) : null;
|
|
4641
|
+
return o.joinedMs >= FAST_OFFER_WAIT_MS ? once(webSocketLine(FastNotAsked.NotOffered)) : null;
|
|
4598
4642
|
},
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
if (closed) return;
|
|
4602
|
-
closed = true;
|
|
4603
|
-
socket.close();
|
|
4643
|
+
reset() {
|
|
4644
|
+
said.clear();
|
|
4604
4645
|
}
|
|
4605
4646
|
};
|
|
4606
4647
|
}
|
|
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
4648
|
|
|
4619
4649
|
// src/authority/fast-session.ts
|
|
4620
4650
|
import { $refId, OPERATION } from "@colyseus/schema";
|
|
4621
4651
|
var ROOM_INPUT_RELIABLE = 19;
|
|
4652
|
+
var RETRY_AFTER_SERVER_OFF = 3;
|
|
4622
4653
|
var PROTOCOL_CODE_MASK = 31;
|
|
4623
4654
|
var ENTITY_MOTION_FIELDS = Object.keys(blankEntityMotion());
|
|
4624
4655
|
var SEAT_MOTION_FIELDS = Object.keys(blankSeatEcho()).filter((f) => f !== "inAck");
|
|
4625
4656
|
var motionFieldsOf = (kind) => kind === MotionRecordKind.Entity ? ENTITY_MOTION_FIELDS : SEAT_MOTION_FIELDS;
|
|
4626
4657
|
var ROOT_TICK_FIELDS = ["tick", "keyframe"];
|
|
4658
|
+
var SETTLE_ACK_TRAIL = 64;
|
|
4659
|
+
function settleAckTrail() {
|
|
4660
|
+
let trail = [];
|
|
4661
|
+
let floor = 0;
|
|
4662
|
+
return {
|
|
4663
|
+
snapshot(tick, ack) {
|
|
4664
|
+
const last = trail.at(-1)?.ack ?? floor;
|
|
4665
|
+
if (ack <= last) return;
|
|
4666
|
+
trail.push({ tick, ack });
|
|
4667
|
+
if (trail.length > SETTLE_ACK_TRAIL) trail = trail.slice(-SETTLE_ACK_TRAIL);
|
|
4668
|
+
},
|
|
4669
|
+
through(tick) {
|
|
4670
|
+
let kept = 0;
|
|
4671
|
+
while (kept < trail.length && trail[kept].tick <= tick) {
|
|
4672
|
+
floor = Math.max(floor, trail[kept].ack);
|
|
4673
|
+
kept += 1;
|
|
4674
|
+
}
|
|
4675
|
+
if (kept > 0) trail = trail.slice(kept);
|
|
4676
|
+
return floor;
|
|
4677
|
+
},
|
|
4678
|
+
reset(at2) {
|
|
4679
|
+
trail = [];
|
|
4680
|
+
floor = at2;
|
|
4681
|
+
}
|
|
4682
|
+
};
|
|
4683
|
+
}
|
|
4684
|
+
function defaultFastLog(line) {
|
|
4685
|
+
const stderr = globalThis.process?.stderr;
|
|
4686
|
+
if (stderr?.write !== void 0) stderr.write(`${line}
|
|
4687
|
+
`);
|
|
4688
|
+
else console.info(line);
|
|
4689
|
+
}
|
|
4690
|
+
var defaultEvery = (ms, fn) => {
|
|
4691
|
+
const timer = setInterval(fn, ms);
|
|
4692
|
+
timer.unref?.();
|
|
4693
|
+
return () => clearInterval(timer);
|
|
4694
|
+
};
|
|
4627
4695
|
function baselinesOf(handle) {
|
|
4628
4696
|
if (typeof handle !== "object" || handle === null) return null;
|
|
4629
4697
|
const h = handle;
|
|
@@ -4642,13 +4710,31 @@ function renderStampOf(handle) {
|
|
|
4642
4710
|
}
|
|
4643
4711
|
function fastSeat(o) {
|
|
4644
4712
|
const { room, handle } = o;
|
|
4645
|
-
const status = { live: false, snapshots: 0, fallback: null };
|
|
4713
|
+
const status = { live: false, snapshots: 0, fallback: o.neverAsked, fallbacks: 0, returns: 0, transport: FastTransport.WebSocket, line: null };
|
|
4714
|
+
let returnsSaid = 0;
|
|
4715
|
+
const announcer = fastLineAnnouncer();
|
|
4716
|
+
let joinedAt = null;
|
|
4717
|
+
let boundUrl = null;
|
|
4718
|
+
let snapshotsBefore = 0;
|
|
4646
4719
|
let link = null;
|
|
4647
4720
|
let fallback = null;
|
|
4648
4721
|
let fastAck = 0;
|
|
4722
|
+
const settle2 = settleAckTrail();
|
|
4649
4723
|
let early = [];
|
|
4650
4724
|
let pendingBind = null;
|
|
4725
|
+
let offerAwaitedFrom = null;
|
|
4726
|
+
let roundTripMs = 0;
|
|
4651
4727
|
const guard = o.open === void 0 ? null : motionGuard(room, () => link?.client ?? null);
|
|
4728
|
+
const stopTimer = o.open === void 0 ? null : (o.every ?? defaultEvery)(FAST_KEEPALIVE_BIND_MS, () => {
|
|
4729
|
+
link?.client.keepalive();
|
|
4730
|
+
probe?.client.keepalive();
|
|
4731
|
+
});
|
|
4732
|
+
const random = o.random ?? Math.random;
|
|
4733
|
+
let probe = null;
|
|
4734
|
+
let hostReturns = false;
|
|
4735
|
+
let attempt = 0;
|
|
4736
|
+
let retryAt = null;
|
|
4737
|
+
let liveSince = null;
|
|
4652
4738
|
const baselines = baselinesOf(handle);
|
|
4653
4739
|
const renderStamp = renderStampOf(handle);
|
|
4654
4740
|
const ack = () => {
|
|
@@ -4658,8 +4744,17 @@ function fastSeat(o) {
|
|
|
4658
4744
|
return ws > 0 ? Math.max(fallback.ack, fallback.at + ws) : fallback.ack;
|
|
4659
4745
|
};
|
|
4660
4746
|
const seqBase = () => room.state.players?.get(room.sessionId)?.seqBase ?? 0;
|
|
4747
|
+
const scheduleRetry = () => {
|
|
4748
|
+
if (!hostReturns || o.open === void 0) return;
|
|
4749
|
+
retryAt = o.now() + fastRetryDelayMs(attempt, random());
|
|
4750
|
+
attempt += 1;
|
|
4751
|
+
};
|
|
4661
4752
|
const fallBack = (reason) => {
|
|
4662
4753
|
if (fallback !== null) return;
|
|
4754
|
+
if (liveSince !== null && o.now() - liveSince >= FAST_RETURN_STABLE_MS) attempt = 0;
|
|
4755
|
+
if (reason === FastFallbackReason.Server) attempt = Math.max(attempt, RETRY_AFTER_SERVER_OFF);
|
|
4756
|
+
liveSince = null;
|
|
4757
|
+
status.fallbacks += 1;
|
|
4663
4758
|
const live = link;
|
|
4664
4759
|
const base = link?.seqBase ?? seqBase();
|
|
4665
4760
|
fallback = { at: handle.sentCount, wsBefore: link?.wsBefore ?? handle.sentCount, ack: ack(), reason };
|
|
@@ -4675,6 +4770,82 @@ function fastSeat(o) {
|
|
|
4675
4770
|
const off = { last: base + handle.sentCount };
|
|
4676
4771
|
room.send(FastMessage.Off, off);
|
|
4677
4772
|
guard?.release();
|
|
4773
|
+
scheduleRetry();
|
|
4774
|
+
};
|
|
4775
|
+
const offChannel = (reason) => {
|
|
4776
|
+
if (status.fallback === null) status.fallbacks += 1;
|
|
4777
|
+
status.fallback = reason;
|
|
4778
|
+
};
|
|
4779
|
+
const abandonProbe = (reason) => {
|
|
4780
|
+
if (probe === null) return;
|
|
4781
|
+
probe.client.close();
|
|
4782
|
+
probe = null;
|
|
4783
|
+
offChannel(reason);
|
|
4784
|
+
const off = { last: seqBase() + handle.sentCount };
|
|
4785
|
+
room.send(FastMessage.Off, off);
|
|
4786
|
+
scheduleRetry();
|
|
4787
|
+
};
|
|
4788
|
+
const swapToProbe = (s) => {
|
|
4789
|
+
const p = probe;
|
|
4790
|
+
if (p === null || fallback === null) return;
|
|
4791
|
+
probe = null;
|
|
4792
|
+
const base = seqBase();
|
|
4793
|
+
const wsBefore = fallback.wsBefore + (handle.sentCount - fallback.at);
|
|
4794
|
+
fastAck = ack();
|
|
4795
|
+
settle2.reset(fastAck);
|
|
4796
|
+
link = { client: p.client, seqBase: base, wsBefore, stampAtBind: baselines?._lastStamp ?? 0 };
|
|
4797
|
+
fallback = null;
|
|
4798
|
+
retryAt = null;
|
|
4799
|
+
liveSince = o.now();
|
|
4800
|
+
status.live = true;
|
|
4801
|
+
status.fallback = null;
|
|
4802
|
+
if (status.fallbacks > 0) status.returns += 1;
|
|
4803
|
+
const on = { last: base + handle.sentCount };
|
|
4804
|
+
room.send(FastMessage.On, on);
|
|
4805
|
+
applied({ ...s, changed: p.client.records() });
|
|
4806
|
+
};
|
|
4807
|
+
const askRoundTrip = (client) => {
|
|
4808
|
+
if (!client.opening()) return;
|
|
4809
|
+
room.ping((ms) => {
|
|
4810
|
+
roundTripMs = ms;
|
|
4811
|
+
});
|
|
4812
|
+
};
|
|
4813
|
+
const startProbe = (message) => {
|
|
4814
|
+
if (o.open === void 0) return;
|
|
4815
|
+
attempt = Math.max(0, attempt - 1);
|
|
4816
|
+
hostReturns = true;
|
|
4817
|
+
probe?.client.close();
|
|
4818
|
+
probe = null;
|
|
4819
|
+
if (fallback === null) status.fallback = null;
|
|
4820
|
+
fallback ??= { at: 0, wsBefore: 0, ack: 0, reason: FastFallbackReason.Unreachable };
|
|
4821
|
+
early = [];
|
|
4822
|
+
if (!isFastBind(message)) {
|
|
4823
|
+
offChannel(FastFallbackReason.Unreachable);
|
|
4824
|
+
return scheduleRetry();
|
|
4825
|
+
}
|
|
4826
|
+
boundUrl = message.url;
|
|
4827
|
+
const proof = { client: null, applied: 0 };
|
|
4828
|
+
const client = openFastClient({
|
|
4829
|
+
bind: message,
|
|
4830
|
+
tickHz: o.tickHz(),
|
|
4831
|
+
open: o.open,
|
|
4832
|
+
now: o.now,
|
|
4833
|
+
roundTripMs: () => roundTripMs,
|
|
4834
|
+
onSnapshot: (s) => {
|
|
4835
|
+
if (link !== null && link.client === proof.client) return applied(s);
|
|
4836
|
+
proof.applied += 1;
|
|
4837
|
+
if (proof.applied >= FAST_RETURN_PROOF) swapToProbe(s);
|
|
4838
|
+
}
|
|
4839
|
+
});
|
|
4840
|
+
if (client === null) {
|
|
4841
|
+
offChannel(o.open.whyNull?.(message) ?? FastFallbackReason.Unreachable);
|
|
4842
|
+
return scheduleRetry();
|
|
4843
|
+
}
|
|
4844
|
+
askRoundTrip(client);
|
|
4845
|
+
proof.client = client;
|
|
4846
|
+
probe = proof;
|
|
4847
|
+
retryAt = null;
|
|
4848
|
+
client.keepalive();
|
|
4678
4849
|
};
|
|
4679
4850
|
const applied = (s) => {
|
|
4680
4851
|
const l = link;
|
|
@@ -4689,25 +4860,55 @@ function fastSeat(o) {
|
|
|
4689
4860
|
if (at2 !== void 0) rtt = o.now() - at2;
|
|
4690
4861
|
fastAck = acked;
|
|
4691
4862
|
}
|
|
4863
|
+
settle2.snapshot(s.tick, fastAck);
|
|
4692
4864
|
room.clock.sample(s.sent, rtt);
|
|
4693
4865
|
guard?.write(s);
|
|
4694
4866
|
};
|
|
4695
4867
|
const bind = (message) => {
|
|
4696
|
-
|
|
4868
|
+
const asProbe = message?.probe === true;
|
|
4869
|
+
if (o.open === void 0 || link !== null || fallback !== null && !asProbe) return;
|
|
4697
4870
|
if (!o.stateCurrent()) {
|
|
4698
4871
|
pendingBind = message;
|
|
4699
4872
|
return;
|
|
4700
4873
|
}
|
|
4701
4874
|
pendingBind = null;
|
|
4875
|
+
if (asProbe) return startProbe(message);
|
|
4876
|
+
status.fallback = null;
|
|
4702
4877
|
if (!isFastBind(message)) return fallBack(FastFallbackReason.Unreachable);
|
|
4703
|
-
|
|
4704
|
-
|
|
4878
|
+
boundUrl = message.url;
|
|
4879
|
+
hostReturns = typeof message.rev === "number" && message.rev >= FAST_BIND_REV;
|
|
4880
|
+
const client = openFastClient({ bind: message, tickHz: o.tickHz(), open: o.open, now: o.now, roundTripMs: () => roundTripMs, onSnapshot: applied });
|
|
4881
|
+
if (client === null) return fallBack(o.open.whyNull?.(message) ?? FastFallbackReason.Unreachable);
|
|
4882
|
+
askRoundTrip(client);
|
|
4705
4883
|
link = { client, seqBase: seqBase(), wsBefore: handle.sentCount, stampAtBind: baselines?._lastStamp ?? 0 };
|
|
4706
4884
|
fastAck = 0;
|
|
4885
|
+
settle2.reset(0);
|
|
4707
4886
|
status.live = true;
|
|
4887
|
+
liveSince = o.now();
|
|
4708
4888
|
for (const s of early) client.queue({ ...s, seq: link.seqBase + s.seq });
|
|
4709
4889
|
early = [];
|
|
4710
4890
|
client.flush();
|
|
4891
|
+
client.keepalive();
|
|
4892
|
+
};
|
|
4893
|
+
const noteOffer = () => {
|
|
4894
|
+
const waiting = o.open !== void 0 && link === null && fallback === null && pendingBind === null && o.stateCurrent();
|
|
4895
|
+
if (!waiting) return;
|
|
4896
|
+
offerAwaitedFrom ??= o.now();
|
|
4897
|
+
if (o.now() - offerAwaitedFrom > FAST_HANDSHAKE_MS) status.fallback = FastFallbackReason.NotOffered;
|
|
4898
|
+
};
|
|
4899
|
+
const announce = () => {
|
|
4900
|
+
if (joinedAt === null && o.stateCurrent() && room.state.players?.get(room.sessionId) !== void 0) joinedAt = o.now();
|
|
4901
|
+
const snapshots = status.snapshots - snapshotsBefore;
|
|
4902
|
+
status.transport = status.live && snapshots > 0 ? FastTransport.Udp : FastTransport.WebSocket;
|
|
4903
|
+
if (status.returns !== returnsSaid) {
|
|
4904
|
+
returnsSaid = status.returns;
|
|
4905
|
+
announcer.reset();
|
|
4906
|
+
}
|
|
4907
|
+
const fallback2 = status.fallback === FastFallbackReason.NotOffered ? null : status.fallback;
|
|
4908
|
+
const line = announcer.observe({ asked: !o.off, udp: o.open !== void 0, joinedMs: joinedAt === null ? null : o.now() - joinedAt, url: boundUrl, snapshots, fallback: fallback2 });
|
|
4909
|
+
if (line === null) return;
|
|
4910
|
+
status.line = line;
|
|
4911
|
+
o.log(line);
|
|
4711
4912
|
};
|
|
4712
4913
|
if (o.open !== void 0) {
|
|
4713
4914
|
room.onMessage(FastMessage.Bind, (m) => bind(m));
|
|
@@ -4720,6 +4921,10 @@ function fastSeat(o) {
|
|
|
4720
4921
|
return {
|
|
4721
4922
|
status,
|
|
4722
4923
|
ack,
|
|
4924
|
+
settleAck() {
|
|
4925
|
+
if (link === null) return ack();
|
|
4926
|
+
return Math.min(ack(), settle2.through(guard?.webSocketTick() ?? Number(room.state.tick ?? 0)));
|
|
4927
|
+
},
|
|
4723
4928
|
sent(seq, tick, input) {
|
|
4724
4929
|
if (o.open === void 0 || fallback !== null) return;
|
|
4725
4930
|
const render = renderStamp();
|
|
@@ -4735,8 +4940,20 @@ function fastSeat(o) {
|
|
|
4735
4940
|
link?.client.flush();
|
|
4736
4941
|
},
|
|
4737
4942
|
poll() {
|
|
4943
|
+
link?.client.framed();
|
|
4738
4944
|
const reason = link?.client.due() ?? null;
|
|
4739
4945
|
if (reason !== null) fallBack(reason);
|
|
4946
|
+
link?.client.keepalive();
|
|
4947
|
+
probe?.client.framed();
|
|
4948
|
+
const probeReason = probe?.client.due() ?? null;
|
|
4949
|
+
if (probeReason !== null) abandonProbe(probeReason);
|
|
4950
|
+
probe?.client.keepalive();
|
|
4951
|
+
if (retryAt !== null && o.now() >= retryAt && probe === null && fallback !== null) {
|
|
4952
|
+
room.send(FastMessage.Again, {});
|
|
4953
|
+
scheduleRetry();
|
|
4954
|
+
}
|
|
4955
|
+
noteOffer();
|
|
4956
|
+
announce();
|
|
4740
4957
|
},
|
|
4741
4958
|
motionKnown(id) {
|
|
4742
4959
|
return link === null || link.client.motionOf(MotionRecordKind.Entity, id) !== void 0;
|
|
@@ -4749,15 +4966,30 @@ function fastSeat(o) {
|
|
|
4749
4966
|
dropped() {
|
|
4750
4967
|
link?.client.close();
|
|
4751
4968
|
link = null;
|
|
4969
|
+
probe?.client.close();
|
|
4970
|
+
probe = null;
|
|
4971
|
+
retryAt = null;
|
|
4972
|
+
attempt = 0;
|
|
4973
|
+
liveSince = null;
|
|
4974
|
+
hostReturns = false;
|
|
4752
4975
|
guard?.forget();
|
|
4753
4976
|
fallback = null;
|
|
4754
4977
|
fastAck = 0;
|
|
4755
4978
|
early = [];
|
|
4756
4979
|
pendingBind = null;
|
|
4980
|
+
offerAwaitedFrom = null;
|
|
4757
4981
|
status.live = false;
|
|
4758
|
-
status.fallback =
|
|
4982
|
+
status.fallback = o.neverAsked;
|
|
4983
|
+
status.transport = FastTransport.WebSocket;
|
|
4984
|
+
announcer.reset();
|
|
4985
|
+
joinedAt = null;
|
|
4986
|
+
boundUrl = null;
|
|
4987
|
+
snapshotsBefore = status.snapshots;
|
|
4759
4988
|
},
|
|
4760
4989
|
dispose() {
|
|
4990
|
+
stopTimer?.();
|
|
4991
|
+
probe?.client.close();
|
|
4992
|
+
probe = null;
|
|
4761
4993
|
link?.client.close();
|
|
4762
4994
|
link = null;
|
|
4763
4995
|
status.live = false;
|
|
@@ -4809,7 +5041,7 @@ function motionGuard(room, live) {
|
|
|
4809
5041
|
const known = fields.get(field);
|
|
4810
5042
|
if (known === void 0) fields.set(field, { fast: value, ws: ref[field] ?? BLANK_MOTION[field] });
|
|
4811
5043
|
else known.fast = value;
|
|
4812
|
-
|
|
5044
|
+
writeField(ref, field, value, changes);
|
|
4813
5045
|
};
|
|
4814
5046
|
const expose = () => {
|
|
4815
5047
|
learn();
|
|
@@ -4868,6 +5100,9 @@ function motionGuard(room, live) {
|
|
|
4868
5100
|
}
|
|
4869
5101
|
});
|
|
4870
5102
|
return {
|
|
5103
|
+
webSocketTick() {
|
|
5104
|
+
return Number(held.get(state)?.get("tick")?.ws ?? room.state.tick ?? 0);
|
|
5105
|
+
},
|
|
4871
5106
|
write(s) {
|
|
4872
5107
|
learn();
|
|
4873
5108
|
const changes = [];
|
|
@@ -4886,7 +5121,7 @@ function motionGuard(room, live) {
|
|
|
4886
5121
|
const resent = room.state.physics !== void 0;
|
|
4887
5122
|
for (const [ref, fields] of held) {
|
|
4888
5123
|
if (!current(ref) || resent && owners.get(ref)?.kind === MotionRecordKind.Entity) continue;
|
|
4889
|
-
for (const [field, f] of fields)
|
|
5124
|
+
for (const [field, f] of fields) writeField(ref, field, f.ws, changes);
|
|
4890
5125
|
}
|
|
4891
5126
|
held.clear();
|
|
4892
5127
|
if (changes.length === 0) return;
|
|
@@ -4913,7 +5148,7 @@ function writeRecord(ref, record, write) {
|
|
|
4913
5148
|
write(ref, field, value);
|
|
4914
5149
|
}
|
|
4915
5150
|
}
|
|
4916
|
-
function
|
|
5151
|
+
function writeField(ref, field, value, changes) {
|
|
4917
5152
|
const previousValue = ref[field];
|
|
4918
5153
|
if (previousValue === value) return;
|
|
4919
5154
|
ref[field] = value;
|
|
@@ -4924,6 +5159,29 @@ function writeField2(ref, field, value, changes) {
|
|
|
4924
5159
|
import { Client, getStateCallbacks } from "@colyseus/sdk";
|
|
4925
5160
|
import { Predict } from "@colyseus/sdk/predict";
|
|
4926
5161
|
|
|
5162
|
+
// src/authority/correction.ts
|
|
5163
|
+
var VISIBLE_CORRECTION_METRES = 0.05;
|
|
5164
|
+
var CORRECTION_BUCKET_METRES = [0.5, 1, 5, 20];
|
|
5165
|
+
var PLACE = /* @__PURE__ */ new Set(["x", "y", "z"]);
|
|
5166
|
+
var UNCOUNTED = /* @__PURE__ */ new Set(["mstate", "cap", "spin", "fuel"]);
|
|
5167
|
+
var TWO_PI4 = 2 * Math.PI;
|
|
5168
|
+
function correctionSizes(fields, before, after) {
|
|
5169
|
+
let squares = 0;
|
|
5170
|
+
let state = 0;
|
|
5171
|
+
fields.forEach((field, i) => {
|
|
5172
|
+
if (UNCOUNTED.has(field)) return;
|
|
5173
|
+
const delta = (after[i] ?? 0) - (before[i] ?? 0);
|
|
5174
|
+
if (PLACE.has(field)) squares += delta * delta;
|
|
5175
|
+
else state = Math.max(state, Math.abs(field === "yaw" ? delta - TWO_PI4 * Math.floor((delta + Math.PI) / TWO_PI4) : delta));
|
|
5176
|
+
});
|
|
5177
|
+
return { place: Math.sqrt(squares), state };
|
|
5178
|
+
}
|
|
5179
|
+
var isVisibleCorrection = (place2, unitsPerMetre) => place2 > VISIBLE_CORRECTION_METRES * unitsPerMetre;
|
|
5180
|
+
function correctionBucket(place2, unitsPerMetre) {
|
|
5181
|
+
const bucket = CORRECTION_BUCKET_METRES.findIndex((edge) => place2 < edge * unitsPerMetre);
|
|
5182
|
+
return bucket < 0 ? CORRECTION_BUCKET_METRES.length : bucket;
|
|
5183
|
+
}
|
|
5184
|
+
|
|
4927
5185
|
// src/authority/tick-drawing.ts
|
|
4928
5186
|
var RING = 80;
|
|
4929
5187
|
var fold = (prev, next) => prev + Math.atan2(Math.sin(next - prev), Math.cos(next - prev));
|
|
@@ -4979,8 +5237,17 @@ var TickDrawing = class {
|
|
|
4979
5237
|
}
|
|
4980
5238
|
};
|
|
4981
5239
|
|
|
5240
|
+
// src/authority/fast-default.ts
|
|
5241
|
+
function defaultFastOpener() {
|
|
5242
|
+
const builtin = globalThis.process?.getBuiltinModule;
|
|
5243
|
+
const dgram = builtin?.("node:dgram");
|
|
5244
|
+
const dns = builtin?.("node:dns");
|
|
5245
|
+
const net = builtin?.("node:net");
|
|
5246
|
+
if (dgram === void 0 || dns === void 0 || net === void 0) return void 0;
|
|
5247
|
+
return udpFastOpener({ createSocket: dgram.createSocket, lookup: dns.lookup, isIP: net.isIP });
|
|
5248
|
+
}
|
|
5249
|
+
|
|
4982
5250
|
// src/authority/session.ts
|
|
4983
|
-
var VISIBLE_CORRECTION = 0.05;
|
|
4984
5251
|
var LIFT_BLEND_MS = 100;
|
|
4985
5252
|
var LIFT_DRAWN = ["x", "y", "z", "yaw"];
|
|
4986
5253
|
var RIDE_FRAME_DONE = 1e-3;
|
|
@@ -4990,28 +5257,30 @@ function liftLeftover(was, k, shift, decay) {
|
|
|
4990
5257
|
if (was.k === k) return kept;
|
|
4991
5258
|
return was.shift.map((s, i) => s + kept[i] - shift[i]);
|
|
4992
5259
|
}
|
|
4993
|
-
var CORRECTION_BUCKETS = [0.5, 1, 5, 20];
|
|
4994
5260
|
var CAPACITY_RETRY_MS = [1e3, 2e3, 4e3];
|
|
4995
5261
|
var MAX_CATCH_UP_STEPS = 8;
|
|
4996
5262
|
var SIDE_STALE_MS = 250;
|
|
4997
5263
|
var PROJECTILE_TTL_MIN_MS = 600;
|
|
4998
5264
|
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
5265
|
var SessionEvent = {
|
|
5002
5266
|
Reconnecting: "reconnecting",
|
|
5003
5267
|
Reconnected: "reconnected",
|
|
5004
|
-
Disconnect: "disconnect"
|
|
5268
|
+
Disconnect: "disconnect",
|
|
5269
|
+
...RejoinEvent
|
|
5005
5270
|
};
|
|
5006
5271
|
var SESSION_EVENTS = new Set(Object.values(SessionEvent));
|
|
5007
5272
|
var MESSAGE = {
|
|
5008
5273
|
protocol: `[multiplayer] the authority server refused protocol ${AUTHORITY_PROTOCOL_VERSION} (code 426) \u2014 this game was built with a @genex-ai/multiplayer that the server no longer serves. Update the package and republish.`,
|
|
5009
|
-
notEntitled: "[multiplayer] server-authoritative rooms are not available for this game (code 402): its owner is not entitled to them. This is final; the session does not retry."
|
|
5274
|
+
notEntitled: "[multiplayer] server-authoritative rooms are not available for this game (code 402): its owner is not entitled to them. This is final; the session does not retry.",
|
|
5275
|
+
freeQuota: "[multiplayer] no new server-authoritative room for this game (code 429): its owner is on Genex's free tier and has used the free included room time for this period. Rooms already running are not affected; new rooms open again next period, or with Genex Pro."
|
|
5010
5276
|
};
|
|
5011
5277
|
var errorCode = (err) => err && typeof err === "object" ? err.code : void 0;
|
|
5278
|
+
var errorMessage = (err) => err && typeof err === "object" ? err.message : void 0;
|
|
5279
|
+
var isFreeQuotaRefusal = (err) => errorCode(err) === JoinError.ROOM_QUOTA && errorMessage(err) === FREE_QUOTA_REFUSAL;
|
|
5012
5280
|
function explainAuthorityRefusal(err) {
|
|
5013
5281
|
if (isProtocolRefusal(err)) console.error(MESSAGE.protocol);
|
|
5014
5282
|
else if (errorCode(err) === JoinError.NOT_ENTITLED) console.error(MESSAGE.notEntitled);
|
|
5283
|
+
else if (isFreeQuotaRefusal(err)) console.error(MESSAGE.freeQuota);
|
|
5015
5284
|
throw err;
|
|
5016
5285
|
}
|
|
5017
5286
|
var predictsPhysics = (opts) => opts.physicsCore !== void 0 && opts.predictPhysics !== false;
|
|
@@ -5026,16 +5295,29 @@ async function joinTargets(opts) {
|
|
|
5026
5295
|
const ranked = await selectAuthorityRegions(regions, opts.probe === void 0 ? {} : { probe: opts.probe });
|
|
5027
5296
|
return ranked.map((pick) => pick.url);
|
|
5028
5297
|
}
|
|
5029
|
-
async function joinAuthority(opts) {
|
|
5298
|
+
async function joinAuthority(opts, target) {
|
|
5299
|
+
const plan = planAuthorityJoin(target);
|
|
5300
|
+
if ("refused" in plan) throw new TypeError(`genex multiplayer: this room cannot be asked for (${plan.refused})`);
|
|
5030
5301
|
const targets = await joinTargets(opts);
|
|
5031
5302
|
const delays = opts.capacityRetryMs ?? CAPACITY_RETRY_MS;
|
|
5032
5303
|
let state = { region: 0, refused: 0, step: 0 };
|
|
5033
5304
|
for (; ; ) {
|
|
5034
5305
|
const url = targets[state.region];
|
|
5035
5306
|
try {
|
|
5036
|
-
return { room: await joinOnce(new Client(url), opts), url };
|
|
5307
|
+
return { room: await joinOnce(new Client(url), opts, plan), url, targets };
|
|
5037
5308
|
} catch (err) {
|
|
5038
|
-
|
|
5309
|
+
if (plan.method === AuthorityJoinMethod.JoinById) {
|
|
5310
|
+
const region = nextCodeRegion(errorCode(err), state.region, targets.length);
|
|
5311
|
+
if (region === null) explainAuthorityRefusal(err);
|
|
5312
|
+
state = { region, refused: 0, step: 0 };
|
|
5313
|
+
continue;
|
|
5314
|
+
}
|
|
5315
|
+
const kind = refusalKind(errorCode(err));
|
|
5316
|
+
if (kind === RefusalKind.NextRegion && state.region + 1 < targets.length) {
|
|
5317
|
+
state = { region: state.region + 1, refused: 0, step: state.step };
|
|
5318
|
+
continue;
|
|
5319
|
+
}
|
|
5320
|
+
const next = kind === RefusalKind.Capacity ? nextAttempt(state, targets.length, delays) : null;
|
|
5039
5321
|
if (next === null) explainAuthorityRefusal(err);
|
|
5040
5322
|
const wait = next.attempt.waitMs;
|
|
5041
5323
|
if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));
|
|
@@ -5043,17 +5325,23 @@ async function joinAuthority(opts) {
|
|
|
5043
5325
|
}
|
|
5044
5326
|
}
|
|
5045
5327
|
}
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5328
|
+
var fastChoiceOf = (opts) => chooseFastSocket(opts.fast ?? defaultFastOpener());
|
|
5329
|
+
function joinOnce(client, opts, plan) {
|
|
5330
|
+
const fastCaps = fastChoiceOf(opts).open === void 0 ? [] : [AuthorityCaps.FAST, AuthorityCaps.FAST_RETURN];
|
|
5331
|
+
const options = {
|
|
5049
5332
|
...joinOptions(opts.room, opts.name, opts.auth),
|
|
5333
|
+
...plan.options,
|
|
5050
5334
|
// §17.10 *Joining*: this session decodes and draws physics bodies (physics-draw.ts, held to
|
|
5051
5335
|
// the netcode bench's physics-draw vectors), so it may join a physics room.
|
|
5052
5336
|
// §17.11: a client that can load the core predicts too.
|
|
5053
5337
|
[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],
|
|
5338
|
+
[AuthorityJoinOption.SDK]: AUTHORITY_SDK_LABEL,
|
|
5054
5339
|
// Last, so nothing can misreport it (PROTOCOL.md §17.1: anything but 4 is refused with 426).
|
|
5055
5340
|
protocol: AUTHORITY_PROTOCOL_VERSION
|
|
5056
|
-
}
|
|
5341
|
+
};
|
|
5342
|
+
if (plan.method === AuthorityJoinMethod.JoinById) return client.joinById(plan.roomId, options);
|
|
5343
|
+
if (plan.method === AuthorityJoinMethod.Create) return client.create(RoomType.AUTHORITY, options);
|
|
5344
|
+
return client.joinOrCreate(RoomType.AUTHORITY, options);
|
|
5057
5345
|
}
|
|
5058
5346
|
function roomPreset(presetText) {
|
|
5059
5347
|
if (!presetText) return null;
|
|
@@ -5097,8 +5385,8 @@ function heldInput(player) {
|
|
|
5097
5385
|
}
|
|
5098
5386
|
var NOT_A_BODY = { body: false, qx: 0, qy: 0, qz: 0, qw: 1, sleep: false };
|
|
5099
5387
|
var NOTHING_YET = /* @__PURE__ */ new Map();
|
|
5100
|
-
var
|
|
5101
|
-
var wrapAngle = (v) => v -
|
|
5388
|
+
var TWO_PI5 = 2 * Math.PI;
|
|
5389
|
+
var wrapAngle = (v) => v - TWO_PI5 * Math.floor((v + Math.PI) / TWO_PI5);
|
|
5102
5390
|
var SEAT_DRAWINGS = [ContactDrawing.Predicted, ContactDrawing.Separate, ContactDrawing.Timeline];
|
|
5103
5391
|
var DEFAULT_SEAT_DRAWING = ContactDrawing.Predicted;
|
|
5104
5392
|
var renderInternals = (reconciler) => {
|
|
@@ -5107,8 +5395,35 @@ var renderInternals = (reconciler) => {
|
|
|
5107
5395
|
return ok ? r : null;
|
|
5108
5396
|
};
|
|
5109
5397
|
async function connectAuthority(opts) {
|
|
5110
|
-
const joined = await joinAuthority(opts);
|
|
5111
|
-
return attachAuthoritySession(useSdkReconnection(joined.room), opts, joined.url);
|
|
5398
|
+
const joined = await joinAuthority(opts, opts.matchKey === void 0 ? {} : { matchKey: opts.matchKey });
|
|
5399
|
+
return comingBack(attachAuthoritySession(useSdkReconnection(joined.room), opts, joined.url), opts, joined, false);
|
|
5400
|
+
}
|
|
5401
|
+
async function createPrivateAuthority(opts) {
|
|
5402
|
+
const joined = await joinAuthority(opts, { createPrivate: true, ...opts.matchKey === void 0 ? {} : { matchKey: opts.matchKey } });
|
|
5403
|
+
return comingBack(attachAuthoritySession(useSdkReconnection(joined.room), opts, joined.url), opts, joined, true);
|
|
5404
|
+
}
|
|
5405
|
+
async function joinPrivateAuthority(code, opts) {
|
|
5406
|
+
const joined = await joinAuthority(opts, { code, ...opts.matchKey === void 0 ? {} : { matchKey: opts.matchKey } });
|
|
5407
|
+
return comingBack(attachAuthoritySession(useSdkReconnection(joined.room), opts, joined.url), opts, joined, true);
|
|
5408
|
+
}
|
|
5409
|
+
function comingBack(session, opts, joined, closed) {
|
|
5410
|
+
if (opts.rejoin === false) return session;
|
|
5411
|
+
const others = joined.targets.filter((url) => url !== joined.url);
|
|
5412
|
+
return survivingSession(session, {
|
|
5413
|
+
closed,
|
|
5414
|
+
targets: [joined.url, ...others],
|
|
5415
|
+
join: async (url, way, lost) => {
|
|
5416
|
+
const room = await joinOnce(new Client(url), opts, rejoinPlan(way, lost));
|
|
5417
|
+
const next = attachAuthoritySession(useSdkReconnection(room), opts, url);
|
|
5418
|
+
await waitForFirstState(room, ClientReconnection.firstStateTimeoutMs);
|
|
5419
|
+
return next;
|
|
5420
|
+
}
|
|
5421
|
+
});
|
|
5422
|
+
}
|
|
5423
|
+
function rejoinPlan(way, lost) {
|
|
5424
|
+
if (way === RejoinWay.Room) return { method: AuthorityJoinMethod.JoinById, roomId: lost.roomId, options: {} };
|
|
5425
|
+
if (way === RejoinWay.Successor) return { method: AuthorityJoinMethod.JoinOrCreate, options: planSuccessorJoin({ lostRoomId: lost.roomId, matchKey: lost.matchKey, closed: lost.closed }) };
|
|
5426
|
+
return { method: AuthorityJoinMethod.JoinOrCreate, options: lost.matchKey === "" ? {} : { matchKey: lost.matchKey } };
|
|
5112
5427
|
}
|
|
5113
5428
|
function attachAuthoritySession(room, opts, url) {
|
|
5114
5429
|
const state = room.state;
|
|
@@ -5116,10 +5431,14 @@ function attachAuthoritySession(room, opts, url) {
|
|
|
5116
5431
|
const playerMap = () => state.players ?? NOTHING_YET;
|
|
5117
5432
|
const renderDelay = opts.renderDelayMs ?? AuthorityTiming.RENDER_DELAY_MS;
|
|
5118
5433
|
const handle = room.input({ type: AuthorityInput, mode: "reliable", renderDelay });
|
|
5434
|
+
const fastChoice = fastChoiceOf(opts);
|
|
5119
5435
|
const fast = fastSeat({
|
|
5120
5436
|
room,
|
|
5121
5437
|
handle,
|
|
5122
|
-
open:
|
|
5438
|
+
open: fastChoice.open,
|
|
5439
|
+
neverAsked: fastChoice.fallback,
|
|
5440
|
+
off: opts.fast === false,
|
|
5441
|
+
log: opts.fastLog ?? defaultFastLog,
|
|
5123
5442
|
now: opts.fastClock ?? (() => performance.now()),
|
|
5124
5443
|
tickHz: () => tickHz(),
|
|
5125
5444
|
stateCurrent: () => !resyncFirstState
|
|
@@ -5172,9 +5491,10 @@ function attachAuthoritySession(room, opts, url) {
|
|
|
5172
5491
|
inputsDropped: 0,
|
|
5173
5492
|
reconciles: 0,
|
|
5174
5493
|
visibleCorrections: 0,
|
|
5175
|
-
correctionBuckets:
|
|
5494
|
+
correctionBuckets: CORRECTION_BUCKET_METRES.map(() => 0).concat(0),
|
|
5176
5495
|
peakCorrection: 0,
|
|
5177
5496
|
lastCorrection: 0,
|
|
5497
|
+
peakStateCorrection: 0,
|
|
5178
5498
|
reseeds: 0,
|
|
5179
5499
|
bodyStates: 0,
|
|
5180
5500
|
bodySnaps: 0,
|
|
@@ -5185,8 +5505,8 @@ function attachAuthoritySession(room, opts, url) {
|
|
|
5185
5505
|
lastProjectileLeadMs: 0
|
|
5186
5506
|
};
|
|
5187
5507
|
const listeners = /* @__PURE__ */ new Map();
|
|
5188
|
-
const emit = (event, payload) => {
|
|
5189
|
-
for (const handler of listeners.get(event) ?? []) handler(payload);
|
|
5508
|
+
const emit = (event, ...payload) => {
|
|
5509
|
+
for (const handler of listeners.get(event) ?? []) handler(...payload);
|
|
5190
5510
|
};
|
|
5191
5511
|
let connected = true;
|
|
5192
5512
|
let ended = false;
|
|
@@ -5449,20 +5769,15 @@ function attachAuthoritySession(room, opts, url) {
|
|
|
5449
5769
|
};
|
|
5450
5770
|
const noteCorrection = (predicted, before, seqBefore) => {
|
|
5451
5771
|
if (predicted.reconciler.reconcileSeq === seqBefore) return;
|
|
5452
|
-
const
|
|
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
|
-
});
|
|
5772
|
+
const { place: place2, state: stateGap } = correctionSizes(predicted.fields, before, predictedPose(predicted));
|
|
5459
5773
|
telemetry.reconciles++;
|
|
5460
|
-
telemetry.lastCorrection =
|
|
5461
|
-
telemetry.peakCorrection = Math.max(telemetry.peakCorrection,
|
|
5462
|
-
|
|
5774
|
+
telemetry.lastCorrection = place2;
|
|
5775
|
+
telemetry.peakCorrection = Math.max(telemetry.peakCorrection, place2);
|
|
5776
|
+
telemetry.peakStateCorrection = Math.max(telemetry.peakStateCorrection, stateGap);
|
|
5777
|
+
const unit2 = unitsPerMetre();
|
|
5778
|
+
if (isVisibleCorrection(place2, unit2)) {
|
|
5463
5779
|
telemetry.visibleCorrections++;
|
|
5464
|
-
|
|
5465
|
-
telemetry.correctionBuckets[bucket < 0 ? CORRECTION_BUCKETS.length : bucket] += 1;
|
|
5780
|
+
telemetry.correctionBuckets[correctionBucket(place2, unit2)] += 1;
|
|
5466
5781
|
}
|
|
5467
5782
|
};
|
|
5468
5783
|
const yawBefore = (predicted) => {
|
|
@@ -5692,7 +6007,7 @@ function attachAuthoritySession(room, opts, url) {
|
|
|
5692
6007
|
const unsettled = /* @__PURE__ */ new Map();
|
|
5693
6008
|
const settleShots = () => {
|
|
5694
6009
|
if (unsettled.size === 0) return;
|
|
5695
|
-
const acked = fast.
|
|
6010
|
+
const acked = fast.settleAck();
|
|
5696
6011
|
for (const [shot, drop] of unsettled) {
|
|
5697
6012
|
if (shot.epoch !== handle.epoch || shot.seq > acked || entityMap().has(shot.id)) continue;
|
|
5698
6013
|
unsettled.delete(shot);
|
|
@@ -5922,10 +6237,10 @@ function attachAuthoritySession(room, opts, url) {
|
|
|
5922
6237
|
predict.dispose();
|
|
5923
6238
|
physicsPrediction.dispose();
|
|
5924
6239
|
};
|
|
5925
|
-
room.onLeave((code) => {
|
|
6240
|
+
room.onLeave((code, reason) => {
|
|
5926
6241
|
if (ended) return;
|
|
5927
6242
|
end();
|
|
5928
|
-
emit(SessionEvent.Disconnect, code);
|
|
6243
|
+
emit(SessionEvent.Disconnect, code, authorityCloseCodeOf(reason));
|
|
5929
6244
|
});
|
|
5930
6245
|
room.onMessage("*", (type, payload) => {
|
|
5931
6246
|
if (typeof type !== "string") return;
|
|
@@ -6278,6 +6593,10 @@ function attachAuthoritySession(room, opts, url) {
|
|
|
6278
6593
|
if (!ended) trackOwn();
|
|
6279
6594
|
return own ? view(own.id, own.entity) : void 0;
|
|
6280
6595
|
},
|
|
6596
|
+
code: room.roomId,
|
|
6597
|
+
get matchKey() {
|
|
6598
|
+
return state.matchKey ?? "";
|
|
6599
|
+
},
|
|
6281
6600
|
setWorld(next) {
|
|
6282
6601
|
const checked = firstpersonWorld(next);
|
|
6283
6602
|
const changed2 = checked.hash !== world.hash;
|
|
@@ -6414,7 +6733,17 @@ export {
|
|
|
6414
6733
|
regionProbePlan,
|
|
6415
6734
|
rankAuthorityRegions,
|
|
6416
6735
|
capacityWalk,
|
|
6736
|
+
RefusalKind,
|
|
6737
|
+
refusalKind,
|
|
6417
6738
|
selectAuthorityRegions,
|
|
6739
|
+
REJOIN,
|
|
6740
|
+
SUCCESSOR_OPEN_MS,
|
|
6741
|
+
RejoinWay,
|
|
6742
|
+
RejoinStop,
|
|
6743
|
+
comesBackAfter,
|
|
6744
|
+
rejoinStep,
|
|
6745
|
+
validSuccessorOf,
|
|
6746
|
+
planSuccessorJoin,
|
|
6418
6747
|
quatMul,
|
|
6419
6748
|
quatNormalize,
|
|
6420
6749
|
slerpFromIdentity,
|
|
@@ -6599,19 +6928,15 @@ export {
|
|
|
6599
6928
|
throttleState,
|
|
6600
6929
|
seatLeadClock,
|
|
6601
6930
|
throttleSteps,
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
encodeRecord,
|
|
6609
|
-
decodeRecord,
|
|
6610
|
-
FastFallbackReason,
|
|
6611
|
-
isFastBind,
|
|
6612
|
-
heldInputs,
|
|
6613
|
-
openFastClient,
|
|
6931
|
+
FastTransport,
|
|
6932
|
+
FastNotAsked,
|
|
6933
|
+
FAST_OFFER_WAIT_MS,
|
|
6934
|
+
udpLine,
|
|
6935
|
+
webSocketLine,
|
|
6936
|
+
fastLineAnnouncer,
|
|
6614
6937
|
SEAT_DRAWINGS,
|
|
6615
6938
|
DEFAULT_SEAT_DRAWING,
|
|
6616
|
-
connectAuthority
|
|
6939
|
+
connectAuthority,
|
|
6940
|
+
createPrivateAuthority,
|
|
6941
|
+
joinPrivateAuthority
|
|
6617
6942
|
};
|