@genex-ai/multiplayer 0.17.1 → 0.17.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/PROTOCOL.md +761 -9
  2. package/README.md +285 -1
  3. package/WORLD-GLTF.md +109 -0
  4. package/WORLD-SCENE.md +135 -0
  5. package/dist/authority/fast-udp.d.ts +15 -0
  6. package/dist/authority/fast-udp.js +14 -0
  7. package/dist/authority/fast-webtransport.d.ts +67 -0
  8. package/dist/authority/fast-webtransport.js +21 -0
  9. package/dist/authority/index.d.ts +2671 -0
  10. package/dist/authority/index.js +958 -0
  11. package/dist/authority/module.d.ts +2 -0
  12. package/dist/authority/module.js +12 -0
  13. package/dist/bridge-authority.d.ts +101 -0
  14. package/dist/bridge-authority.global.js +15 -0
  15. package/dist/bridge-authority.js +291 -0
  16. package/dist/bridge-core-Dhc73GC8.d.ts +27 -0
  17. package/dist/bridge.d.ts +26 -20
  18. package/dist/bridge.global.js +7 -7
  19. package/dist/bridge.js +17 -48
  20. package/dist/chunk-2NGXXQDS.js +1916 -0
  21. package/dist/chunk-4R7C2TLH.js +68 -0
  22. package/dist/{chunk-C52XFIB6.js → chunk-CSQEEANB.js} +217 -4
  23. package/dist/chunk-FPWP22Y7.js +143 -0
  24. package/dist/chunk-NFMGIKFZ.js +2627 -0
  25. package/dist/chunk-OD6NRSX2.js +29 -0
  26. package/dist/{chunk-3ZUPU3GT.js → chunk-QY3QQNDJ.js} +13 -122
  27. package/dist/chunk-QZOCUQJT.js +6900 -0
  28. package/dist/chunk-TSYEWPVO.js +44 -0
  29. package/dist/chunk-TVTVIMIH.js +675 -0
  30. package/dist/chunk-YWQ3ALYY.js +69 -0
  31. package/dist/fast-client--A88q0kk.d.ts +546 -0
  32. package/dist/index.d.ts +3 -5
  33. package/dist/index.js +3 -2
  34. package/dist/join-CGoInvUM.d.ts +6 -0
  35. package/dist/level-build-CbRqrYJq.d.ts +140 -0
  36. package/dist/module-CSpXrzFc.d.ts +1187 -0
  37. package/dist/physics-predict-C7ZB1gDG.d.ts +1223 -0
  38. package/dist/protocol.d.ts +222 -2
  39. package/dist/protocol.js +47 -1
  40. package/dist/shot-world-D22tqOUT.d.ts +485 -0
  41. package/dist/world-fit/index.d.ts +549 -0
  42. package/dist/world-fit/index.js +4763 -0
  43. package/dist/world-fit/three.d.ts +77 -0
  44. package/dist/world-fit/three.js +140 -0
  45. package/package.json +46 -4
@@ -0,0 +1,2627 @@
1
+ import {
2
+ RemotePredict
3
+ } from "./chunk-OD6NRSX2.js";
4
+ import {
5
+ MASK_LIMIT,
6
+ MAX_ABS,
7
+ Preset,
8
+ axis,
9
+ definitionOf,
10
+ isMask,
11
+ isPresetName,
12
+ mask,
13
+ presetParamsValid,
14
+ resolvePresetParams,
15
+ unsignedZero,
16
+ wrappedAngle
17
+ } from "./chunk-2NGXXQDS.js";
18
+ import {
19
+ JoinError,
20
+ Limits,
21
+ isReservedEventType
22
+ } from "./chunk-CSQEEANB.js";
23
+
24
+ // src/authority/room-key.ts
25
+ var AuthorityRoomLimits = {
26
+ /** A match key's longest length. */
27
+ MAX_MATCH_KEY_CHARS: 32,
28
+ /** A room code's longest length (Colyseus's room ids are 9). */
29
+ MAX_CODE_CHARS: 32,
30
+ /** The most named worlds one module's `worlds` map may hold. */
31
+ MAX_WORLDS: 16
32
+ };
33
+ var ROOM_NOT_FOUND = 522;
34
+ var inRange = (c, lo, hi) => c >= lo.charCodeAt(0) && c <= hi.charCodeAt(0);
35
+ var isLowerOrDigit = (c) => inRange(c, "a", "z") || inRange(c, "0", "9");
36
+ var KEY_MARKS = "._:-";
37
+ var CODE_MARKS = "_-";
38
+ function validMatchKey(value2) {
39
+ if (typeof value2 !== "string" || value2.length === 0 || value2.length > AuthorityRoomLimits.MAX_MATCH_KEY_CHARS) return false;
40
+ for (let i = 0; i < value2.length; i++) {
41
+ if (isLowerOrDigit(value2.charCodeAt(i))) continue;
42
+ if (i === 0 || !KEY_MARKS.includes(value2.charAt(i))) return false;
43
+ }
44
+ return true;
45
+ }
46
+ function validRoomCode(value2) {
47
+ if (typeof value2 !== "string" || value2.length === 0 || value2.length > AuthorityRoomLimits.MAX_CODE_CHARS) return false;
48
+ for (let i = 0; i < value2.length; i++) {
49
+ const c = value2.charCodeAt(i);
50
+ if (!isLowerOrDigit(c) && !inRange(c, "A", "Z") && !CODE_MARKS.includes(value2.charAt(i))) return false;
51
+ }
52
+ return true;
53
+ }
54
+ function matchKeyOf(value2) {
55
+ if (value2 === void 0 || value2 === "") return "";
56
+ return validMatchKey(value2) ? value2 : null;
57
+ }
58
+ var AuthorityJoinMethod = { JoinOrCreate: "joinOrCreate", Create: "create", JoinById: "joinById" };
59
+ var AuthorityTargetRefusal = { BadMatchKey: "bad_match_key", BadCode: "bad_code", Conflict: "conflict" };
60
+ function planAuthorityJoin(target) {
61
+ const key = target.matchKey ?? "";
62
+ if (target.code !== void 0) {
63
+ if (target.createPrivate === true || key !== "") return { refused: AuthorityTargetRefusal.Conflict };
64
+ if (!validRoomCode(target.code)) return { refused: AuthorityTargetRefusal.BadCode };
65
+ return { method: AuthorityJoinMethod.JoinById, roomId: target.code, options: {} };
66
+ }
67
+ const pool = matchKeyOf(key);
68
+ if (pool === null) return { refused: AuthorityTargetRefusal.BadMatchKey };
69
+ const options = pool === "" ? {} : { matchKey: pool };
70
+ if (target.createPrivate === true) return { method: AuthorityJoinMethod.Create, options: { ...options, private: true } };
71
+ return { method: AuthorityJoinMethod.JoinOrCreate, options };
72
+ }
73
+ function nextCodeRegion(error, region, regions) {
74
+ const elsewhere = error === JoinError.WRONG_REGION || error === ROOM_NOT_FOUND;
75
+ return elsewhere && region + 1 < regions ? region + 1 : null;
76
+ }
77
+ function selectMatchWorld(worlds, world, key) {
78
+ return worlds !== void 0 && Object.hasOwn(worlds, key) ? worlds[key] : world;
79
+ }
80
+
81
+ // src/authority/physics.ts
82
+ var PHYSICS_CONTRACT = "1.0";
83
+ var PHYSICS_DEFAULT_LIMITS = Object.freeze({
84
+ bodies: 512,
85
+ collidersPerBody: 8,
86
+ hullVertices: 64,
87
+ triangles: 65536,
88
+ joints: 64,
89
+ ccdBodies: 16,
90
+ queriesPerStep: 64,
91
+ eventsPerStep: 64,
92
+ heightfieldSamples: 1048576,
93
+ staticPieces: 4608,
94
+ memoryBytes: 64 * 1024 * 1024,
95
+ worldExtent: 1e5,
96
+ maxSpeed: 1e3,
97
+ maxAngularSpeed: 1e3
98
+ });
99
+ var PHYSICS_LIMIT_NAMES = Object.freeze(Object.keys(PHYSICS_DEFAULT_LIMITS));
100
+ var PHYSICS_SCALED_LIMITS = Object.freeze(["worldExtent", "maxSpeed"]);
101
+ var PHYSICS_REAL_LIMITS = Object.freeze(["worldExtent", "maxSpeed", "maxAngularSpeed"]);
102
+ var PHYSICS_CEILING_FACTOR = 4;
103
+ var PHYSICS_STATIC_PIECE_BYTES = 2048;
104
+ var PHYSICS_PREDICT_MAX_BOXES = 4096;
105
+ var PHYSICS_PREDICT_MAX_RAMPS = 512;
106
+ var physicsWorldPredicts = (boxes, ramps) => boxes <= PHYSICS_PREDICT_MAX_BOXES && ramps <= PHYSICS_PREDICT_MAX_RAMPS;
107
+ function physicsLimitsForWorld(limits, pieces) {
108
+ const lowered = limits.staticPieces < PHYSICS_DEFAULT_LIMITS.staticPieces;
109
+ if (lowered || pieces <= limits.staticPieces) return limits;
110
+ const memoryCeiling = PHYSICS_DEFAULT_LIMITS.memoryBytes * PHYSICS_CEILING_FACTOR;
111
+ return { ...limits, staticPieces: pieces, memoryBytes: Math.min(memoryCeiling, limits.memoryBytes + pieces * PHYSICS_STATIC_PIECE_BYTES) };
112
+ }
113
+ var PHYSICS_DEFAULT_CEILING = PHYSICS_DEFAULT_LIMITS;
114
+ var isPlainObject = (v) => {
115
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
116
+ const proto = Object.getPrototypeOf(v);
117
+ return proto === Object.prototype || proto === null;
118
+ };
119
+ var isLimitName = (key) => Object.hasOwn(PHYSICS_DEFAULT_LIMITS, key);
120
+ var isRealLimit = (key) => PHYSICS_REAL_LIMITS.includes(key);
121
+ function limitValueValid(key, value2) {
122
+ if (typeof value2 !== "number" || !Number.isFinite(value2) || value2 <= 0) return false;
123
+ return isRealLimit(key) || Number.isInteger(value2);
124
+ }
125
+ function physicsCeilingValid(ceiling) {
126
+ if (!isPlainObject(ceiling)) return false;
127
+ return PHYSICS_LIMIT_NAMES.every((key) => {
128
+ const value2 = ceiling[key];
129
+ const base = PHYSICS_DEFAULT_LIMITS[key];
130
+ return limitValueValid(key, value2) && value2 >= base && value2 <= base * PHYSICS_CEILING_FACTOR;
131
+ });
132
+ }
133
+ function physicsLimitsAt(perUnit, lengthUnit) {
134
+ const out = { ...perUnit };
135
+ for (const key of PHYSICS_SCALED_LIMITS) out[key] = perUnit[key] * lengthUnit;
136
+ return out;
137
+ }
138
+ var PhysicsConfigRange = {
139
+ /** `lengthUnit` is in (0, MAX_LENGTH_UNIT]. */
140
+ MAX_LENGTH_UNIT: 1e6,
141
+ /** `gravity` is in [0, MAX_GRAVITY]; a default is never refused. */
142
+ MAX_GRAVITY: 1e7,
143
+ /** Gravity without a world-stepping preset: this many metres per second squared. */
144
+ EARTH_GRAVITY: 9.81,
145
+ MIN_ITERATIONS: 1,
146
+ MAX_ITERATIONS: 16,
147
+ DEFAULT_ITERATIONS: 4,
148
+ /** Distinct physics kinds `subscribe` may name. */
149
+ MAX_SUBSCRIBE_KINDS: 8,
150
+ /** `player.radius` and `player.height` are in (0, MAX_PLAYER_SIZE]. */
151
+ MAX_PLAYER_SIZE: 1e6
152
+ };
153
+ var PHYSICS_PRESET_LENGTH_UNITS = Object.freeze({
154
+ [Preset.FIRSTPERSON]: 39.37,
155
+ [Preset.VEHICLE]: 100
156
+ });
157
+ var PHYSICS_PATCH_HZ = Object.freeze([10, 15, 20, 30, 60]);
158
+ var PHYSICS_DEFAULT_PATCH_HZ = 20;
159
+ var PhysicsPredictRange = {
160
+ DEFAULT_REMOTE_PREDICT: RemotePredict.EXTRAPOLATE,
161
+ DEFAULT_REMOTE_DECAY_MS: 50,
162
+ MAX_REMOTE_DECAY_MS: 1e3,
163
+ DEFAULT_LEAD_MS: 250,
164
+ MIN_LEAD_MS: 50,
165
+ MAX_LEAD_MS: 1e3
166
+ };
167
+ var REMOTE_PREDICTS = Object.values(RemotePredict);
168
+ function isPhysicsPatchHz(v) {
169
+ return typeof v === "number" && PHYSICS_PATCH_HZ.includes(v);
170
+ }
171
+ function effectivePatchHz(tickHz, patchHz) {
172
+ const fits = PHYSICS_PATCH_HZ.filter((hz) => hz <= patchHz && hz <= tickHz && tickHz % hz === 0);
173
+ return fits.at(-1) ?? PHYSICS_PATCH_HZ[0];
174
+ }
175
+ function keyframeTicks(tickHz, patchHz) {
176
+ return tickHz / effectivePatchHz(tickHz, patchHz);
177
+ }
178
+ var PHYSICS_PREDICT_MAX_LINEAR = 64;
179
+ var PHYSICS_PREDICT_MAX_ANGULAR = 64;
180
+ var PHYSICS_PREDICT_MIN_ITERATIONS = null;
181
+ function solverIterationsOf(physics, predicting) {
182
+ const floor = predicting ? PHYSICS_PREDICT_MIN_ITERATIONS : null;
183
+ return floor === null ? physics.iterations : Math.max(physics.iterations, floor);
184
+ }
185
+ var PHYSICS_KEY_MAX_BYTES = 64;
186
+ var PHYSICS_KIND_MAX_BYTES = 32;
187
+ var PHYSICS_STATIC_PREFIX = "static:";
188
+ var KEY_CHARSET = /^[A-Za-z0-9_.:-]+$/;
189
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
190
+ function isPhysicsKey(v) {
191
+ if (typeof v !== "string" || v.length === 0 || v.length > PHYSICS_KEY_MAX_BYTES) return false;
192
+ if (!KEY_CHARSET.test(v) || FORBIDDEN_KEYS.has(v)) return false;
193
+ return !v.startsWith(PHYSICS_STATIC_PREFIX);
194
+ }
195
+ function isPhysicsKind(v) {
196
+ return typeof v === "string" && v.length > 0 && v.length <= PHYSICS_KIND_MAX_BYTES && KEY_CHARSET.test(v);
197
+ }
198
+ var PATH = "$.config.physics";
199
+ var REQUEST_KEYS = /* @__PURE__ */ new Set([
200
+ "lengthUnit",
201
+ "gravity",
202
+ "iterations",
203
+ "limits",
204
+ "subscribe",
205
+ "player",
206
+ "predict",
207
+ "patchHz",
208
+ "remotePredict",
209
+ "remoteDecayMs",
210
+ "leadMs"
211
+ ]);
212
+ var PLAYER_KEYS = /* @__PURE__ */ new Set(["radius", "height", "events"]);
213
+ var Bad = class {
214
+ path;
215
+ constructor(path) {
216
+ this.path = path;
217
+ }
218
+ };
219
+ var bad = (path) => {
220
+ throw new Bad(path);
221
+ };
222
+ var Mode = {
223
+ REQUEST: "request",
224
+ RESOLVED: "resolved"
225
+ };
226
+ var asked = (v) => v !== void 0;
227
+ function lengthUnitOf(v, preset, mode) {
228
+ if (!asked(v)) {
229
+ if (mode === Mode.RESOLVED) return bad(`${PATH}.lengthUnit`);
230
+ return (preset !== null ? PHYSICS_PRESET_LENGTH_UNITS[preset.name] : void 0) ?? 1;
231
+ }
232
+ const ok2 = typeof v === "number" && Number.isFinite(v) && v > 0 && v <= PhysicsConfigRange.MAX_LENGTH_UNIT;
233
+ return ok2 ? v : bad(`${PATH}.lengthUnit`);
234
+ }
235
+ function presetGravity(preset) {
236
+ if (preset === null || !isPresetName(preset.name)) return null;
237
+ if (preset.name !== Preset.FIRSTPERSON && preset.name !== Preset.VEHICLE) return null;
238
+ return resolvePresetParams(preset.name, preset.params).gravity ?? null;
239
+ }
240
+ function gravityOf(v, lengthUnit, preset, mode) {
241
+ if (!asked(v)) {
242
+ if (mode === Mode.RESOLVED) return bad(`${PATH}.gravity`);
243
+ return presetGravity(preset) ?? PhysicsConfigRange.EARTH_GRAVITY * lengthUnit;
244
+ }
245
+ const ok2 = typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= PhysicsConfigRange.MAX_GRAVITY;
246
+ return ok2 ? v : bad(`${PATH}.gravity`);
247
+ }
248
+ function iterationsOf(v, mode) {
249
+ if (!asked(v)) return mode === Mode.RESOLVED ? bad(`${PATH}.iterations`) : PhysicsConfigRange.DEFAULT_ITERATIONS;
250
+ const ok2 = typeof v === "number" && Number.isInteger(v) && v >= PhysicsConfigRange.MIN_ITERATIONS && v <= PhysicsConfigRange.MAX_ITERATIONS;
251
+ return ok2 ? v : bad(`${PATH}.iterations`);
252
+ }
253
+ function limitsOf(v, lengthUnit, ceiling, mode) {
254
+ const path = `${PATH}.limits`;
255
+ if (!asked(v) && mode === Mode.RESOLVED) return bad(path);
256
+ const given = asked(v) ? v : {};
257
+ if (!isPlainObject(given)) return bad(path);
258
+ if (mode === Mode.REQUEST) {
259
+ const unknown = Object.keys(given).find((key) => !isLimitName(key));
260
+ if (unknown !== void 0) return bad(`${path}.${unknown}`);
261
+ }
262
+ const top = physicsLimitsAt(ceiling, lengthUnit);
263
+ const defaults = physicsLimitsAt(PHYSICS_DEFAULT_LIMITS, lengthUnit);
264
+ const out = { ...defaults };
265
+ for (const key of PHYSICS_LIMIT_NAMES) {
266
+ const value2 = given[key];
267
+ if (!asked(value2)) {
268
+ if (mode === Mode.RESOLVED) return bad(`${path}.${key}`);
269
+ out[key] = Math.min(defaults[key], top[key]);
270
+ continue;
271
+ }
272
+ if (!limitValueValid(key, value2)) return bad(`${path}.${key}`);
273
+ if (mode === Mode.RESOLVED && value2 > top[key]) return bad(`${path}.${key}`);
274
+ out[key] = Math.min(value2, top[key]);
275
+ }
276
+ return out;
277
+ }
278
+ function subscribeOf(v, mode) {
279
+ const path = `${PATH}.subscribe`;
280
+ if (!asked(v)) return mode === Mode.RESOLVED ? bad(path) : [];
281
+ if (!Array.isArray(v) || v.length > PhysicsConfigRange.MAX_SUBSCRIBE_KINDS) return bad(path);
282
+ const seen = /* @__PURE__ */ new Set();
283
+ v.forEach((kind, i) => {
284
+ if (!isPhysicsKind(kind) || seen.has(kind)) bad(`${path}[${i}]`);
285
+ seen.add(kind);
286
+ });
287
+ return [...seen];
288
+ }
289
+ var playerSize = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 && v <= PhysicsConfigRange.MAX_PLAYER_SIZE;
290
+ function playerOf(v, preset, mode) {
291
+ const path = `${PATH}.player`;
292
+ if (!asked(v) && mode === Mode.RESOLVED) return bad(path);
293
+ const given = asked(v) ? v : {};
294
+ if (!isPlainObject(given)) return bad(path);
295
+ if (mode === Mode.REQUEST) {
296
+ const unknown = Object.keys(given).find((key) => !PLAYER_KEYS.has(key));
297
+ if (unknown !== void 0) return bad(`${path}.${unknown}`);
298
+ }
299
+ const out = { events: false };
300
+ if (asked(given.events)) {
301
+ if (typeof given.events !== "boolean") return bad(`${path}.events`);
302
+ out.events = given.events;
303
+ } else if (mode === Mode.RESOLVED) {
304
+ return bad(`${path}.events`);
305
+ }
306
+ const topdown = preset?.name === Preset.TOPDOWN;
307
+ for (const key of ["radius", "height"]) {
308
+ if (!asked(given[key])) continue;
309
+ if (!topdown || !playerSize(given[key])) return bad(`${path}.${key}`);
310
+ out[key] = given[key];
311
+ }
312
+ return out;
313
+ }
314
+ function predictOf(v, mode) {
315
+ if (!asked(v)) return mode === Mode.RESOLVED ? bad(`${PATH}.predict`) : true;
316
+ return typeof v === "boolean" ? v : bad(`${PATH}.predict`);
317
+ }
318
+ function patchHzOf(v, mode) {
319
+ if (!asked(v)) return mode === Mode.RESOLVED ? bad(`${PATH}.patchHz`) : PHYSICS_DEFAULT_PATCH_HZ;
320
+ return isPhysicsPatchHz(v) ? v : bad(`${PATH}.patchHz`);
321
+ }
322
+ function remotePredictOf(v, mode) {
323
+ if (!asked(v)) return mode === Mode.RESOLVED ? bad(`${PATH}.remotePredict`) : PhysicsPredictRange.DEFAULT_REMOTE_PREDICT;
324
+ return REMOTE_PREDICTS.includes(v) ? v : bad(`${PATH}.remotePredict`);
325
+ }
326
+ function rangeOf(v, name, min, max, fallback, mode) {
327
+ if (!asked(v)) return mode === Mode.RESOLVED ? bad(`${PATH}.${name}`) : fallback;
328
+ const ok2 = typeof v === "number" && Number.isFinite(v) && v >= min && v <= max;
329
+ return ok2 ? v : bad(`${PATH}.${name}`);
330
+ }
331
+ function physicsOf(v, preset, ceiling, mode) {
332
+ const none = v === void 0 || v === null || v === false;
333
+ if (none) return ceiling === null ? null : bad(PATH);
334
+ if (ceiling === null) return bad(PATH);
335
+ const given = v === true && mode === Mode.REQUEST ? {} : v;
336
+ if (!isPlainObject(given)) return bad(PATH);
337
+ if (mode === Mode.REQUEST) {
338
+ const unknown = Object.keys(given).find((key) => !REQUEST_KEYS.has(key));
339
+ if (unknown !== void 0) return bad(`${PATH}.${unknown}`);
340
+ }
341
+ const lengthUnit = lengthUnitOf(given.lengthUnit, preset, mode);
342
+ return {
343
+ lengthUnit,
344
+ gravity: gravityOf(given.gravity, lengthUnit, preset, mode),
345
+ iterations: iterationsOf(given.iterations, mode),
346
+ limits: limitsOf(given.limits, lengthUnit, ceiling, mode),
347
+ subscribe: subscribeOf(given.subscribe, mode),
348
+ player: playerOf(given.player, preset, mode),
349
+ predict: predictOf(given.predict, mode),
350
+ patchHz: patchHzOf(given.patchHz, mode),
351
+ remotePredict: remotePredictOf(given.remotePredict, mode),
352
+ remoteDecayMs: rangeOf(given.remoteDecayMs, "remoteDecayMs", 0, PhysicsPredictRange.MAX_REMOTE_DECAY_MS, PhysicsPredictRange.DEFAULT_REMOTE_DECAY_MS, mode),
353
+ leadMs: rangeOf(given.leadMs, "leadMs", PhysicsPredictRange.MIN_LEAD_MS, PhysicsPredictRange.MAX_LEAD_MS, PhysicsPredictRange.DEFAULT_LEAD_MS, mode)
354
+ };
355
+ }
356
+ function resolution(build) {
357
+ try {
358
+ return { ok: true, value: build() };
359
+ } catch (err) {
360
+ if (err instanceof Bad) return { ok: false, path: err.path };
361
+ throw err;
362
+ }
363
+ }
364
+ function resolvePhysicsConfig(request, preset, ceiling) {
365
+ return resolution(() => physicsOf(request, preset, ceiling, Mode.REQUEST));
366
+ }
367
+ function checkResolvedPhysics(resolved, preset, ceiling) {
368
+ return resolution(() => physicsOf(resolved, preset, ceiling, Mode.RESOLVED));
369
+ }
370
+ var PHYSICS_SUBSTEPS = Object.freeze({ 20: 3, 30: 2, 60: 1, 120: 1 });
371
+ function physicsWorldDesc(physics, tickHz, predicting = false, worldPieces = 0) {
372
+ return {
373
+ contract: PHYSICS_CONTRACT,
374
+ dt: 1 / tickHz,
375
+ substeps: PHYSICS_SUBSTEPS[tickHz],
376
+ gravity: [0, 0, -physics.gravity],
377
+ upAxis: "z",
378
+ lengthUnit: physics.lengthUnit,
379
+ limits: { ...physicsLimitsForWorld(physics.limits, worldPieces) },
380
+ solver: { iterations: solverIterationsOf(physics, predicting) }
381
+ };
382
+ }
383
+ var PhysEvKind = {
384
+ CONTACT_BEGIN: 0,
385
+ CONTACT_END: 1,
386
+ TRIGGER_ENTER: 2,
387
+ TRIGGER_EXIT: 3
388
+ };
389
+ var PHYSICS_OVERLAP_MAX_KEYS = 64;
390
+ var PHYSICS_BODY_FIELDS = Object.freeze([
391
+ "qx",
392
+ "qy",
393
+ "qz",
394
+ "qw",
395
+ "lvx",
396
+ "lvy",
397
+ "lvz",
398
+ "avx",
399
+ "avy",
400
+ "avz",
401
+ "sleep"
402
+ ]);
403
+ var HOST_BODY_FIELDS = Object.freeze(["x", "y", "z", ...PHYSICS_BODY_FIELDS]);
404
+ var BodyQuantization = {
405
+ BITS: 16,
406
+ /** Integer steps across a symmetric range (2^16 − 2). */
407
+ SPAN: 65534,
408
+ /** `qx qy qz qw`. */
409
+ ORIENTATION: Object.freeze({ min: -1, max: 1 }),
410
+ /** `lvx lvy lvz` (metres per second: velocity divided by lengthUnit) and `avx avy avz` (rad/s). */
411
+ VELOCITY: Object.freeze({ min: -64, max: 64 })
412
+ };
413
+ function quantizeBodyValue(v, range) {
414
+ if (Number.isNaN(v)) return 0;
415
+ const clamped = v < range.min ? range.min : v > range.max ? range.max : v;
416
+ return Math.floor((clamped - range.min) / (range.max - range.min) * BodyQuantization.SPAN + 0.5);
417
+ }
418
+ function dequantizeBodyValue(q, range) {
419
+ return range.min + q / BodyQuantization.SPAN * (range.max - range.min);
420
+ }
421
+ var PHYSICS_CONTESTED_METRES = 6;
422
+ var PHYSICS_FULL_RATE_BODIES = 100;
423
+ var PHYSICS_ROTATING_BODIES = 25;
424
+ var BODY_EXTRAPOLATE_MAX_MS = 250;
425
+ var BODY_EXTRAPOLATE_BACK_TICKS = 1;
426
+ var BODY_CORRECTION_TAU_MS = 100;
427
+ var BODY_SNAP_METRES = 1;
428
+
429
+ // src/authority/contact-config.ts
430
+ var ContactDrawing = { Predicted: "predicted", Separate: "separate", Timeline: "timeline" };
431
+ var ContactPush = { Shared: "shared", Mover: "mover" };
432
+ var ContactPreset = {
433
+ Shooter: "shooter",
434
+ Platformer: "platformer",
435
+ VehicleArena: "vehicle-arena",
436
+ PhysicsSandbox: "physics-sandbox",
437
+ Party: "party"
438
+ };
439
+ var TopPolicy = {
440
+ /** Rest on the head: a landing stands within `center` of the lower's half width and slides off by
441
+ * `slide` elsewhere; a resting seat holds while its centre is over the footprint; `ride`: carried. */
442
+ Stand: "stand",
443
+ /** Frictionless: the upper seat is slid off within a bounded time. */
444
+ Slide: "slide",
445
+ /** A stomp: the upper seat bounces off, and the module hears it. */
446
+ Bounce: "bounce",
447
+ /** No collision at all. */
448
+ Ghost: "ghost"
449
+ };
450
+ var ContactShapeKind = {
451
+ /** A box turned with the seat's heading and laid on its support up (a vehicle). */
452
+ Box: "box",
453
+ /** An upright axis-aligned square footprint (a `firstperson` character). */
454
+ Hull: "hull"
455
+ };
456
+ var HeadFriction = { Slide: "slide", Carry: "carry" };
457
+ function drawingOf(given, path, base) {
458
+ if (!has(given, "drawing")) return base;
459
+ const v = given.drawing;
460
+ if (!Object.values(ContactDrawing).includes(v)) throw new ContactConfigError(`${path}.drawing`, "must be predicted, separate or timeline");
461
+ return v;
462
+ }
463
+ function pushOf(given, path, base) {
464
+ if (!has(given, "push")) return base;
465
+ const v = given.push;
466
+ if (!Object.values(ContactPush).includes(v)) throw new ContactConfigError(`${path}.push`, "must be shared or mover");
467
+ return v;
468
+ }
469
+ var ContactConfigError = class extends RangeError {
470
+ path;
471
+ constructor(path, why) {
472
+ super(`${path}: ${why}`);
473
+ this.name = "ContactConfigError";
474
+ this.path = path;
475
+ }
476
+ };
477
+ var CONTACT_PAIRS_MAX = 32;
478
+ var CONTACT_TAG_MAX = 64;
479
+ var CONTACT_PASSES_MAX = 64;
480
+ var CONTACT_VALUE_MAX = 1e5;
481
+ var slide = (speed, step, share) => ({ speed, step, share });
482
+ var slideTop = (s) => ({ policy: TopPolicy.Slide, ...s });
483
+ var stand = (ride, center, s) => ({ policy: TopPolicy.Stand, ride, center, slide: s });
484
+ var CHARACTER_SLIDE = slide(300, 150, 1);
485
+ var VEHICLE_SLIDE = slide(950, 480, 1);
486
+ var ARENA_OFF_WALL = slide(600, null, 0.5);
487
+ var SHOOTER_CENTER = 0.25;
488
+ var PROP_SLIDE = Object.freeze({ speed: 300, step: 150 });
489
+ var STOMP = { policy: TopPolicy.Bounce, stompMin: 150, bounceSpeed: 450, jumpBoost: 150, slide: CHARACTER_SLIDE };
490
+ var CONTACT_PRESETS = {
491
+ [ContactPreset.Shooter]: { character: stand(false, SHOOTER_CENTER, CHARACTER_SLIDE), vehicle: slideTop(VEHICLE_SLIDE), restitution: 0, passes: 16, vehicleOffWall: VEHICLE_SLIDE, stopSpin: false, drawing: ContactDrawing.Separate, push: ContactPush.Mover, headFriction: HeadFriction.Slide },
492
+ [ContactPreset.Platformer]: { character: stand(true, null, CHARACTER_SLIDE), vehicle: slideTop(VEHICLE_SLIDE), restitution: 0, passes: 16, vehicleOffWall: VEHICLE_SLIDE, stopSpin: false, drawing: ContactDrawing.Separate, push: ContactPush.Mover, headFriction: HeadFriction.Slide },
493
+ [ContactPreset.VehicleArena]: { character: slideTop(CHARACTER_SLIDE), vehicle: slideTop(VEHICLE_SLIDE), restitution: 0.6, passes: 32, vehicleOffWall: ARENA_OFF_WALL, stopSpin: true, drawing: ContactDrawing.Predicted, push: ContactPush.Shared, headFriction: HeadFriction.Carry },
494
+ [ContactPreset.PhysicsSandbox]: { character: stand(true, null, CHARACTER_SLIDE), vehicle: stand(true, null, VEHICLE_SLIDE), restitution: 0.2, passes: 16, vehicleOffWall: VEHICLE_SLIDE, stopSpin: false, drawing: ContactDrawing.Separate, push: ContactPush.Shared, headFriction: HeadFriction.Carry },
495
+ [ContactPreset.Party]: { character: stand(true, null, CHARACTER_SLIDE), vehicle: slideTop(VEHICLE_SLIDE), restitution: 0.3, passes: 16, vehicleOffWall: VEHICLE_SLIDE, stopSpin: false, drawing: ContactDrawing.Separate, push: ContactPush.Shared, headFriction: HeadFriction.Slide }
496
+ };
497
+ var CONTACT_DEFAULTS = Object.freeze({ supportCos: 0.7, entrySpeed: 1, entryWindow: 1 / 60, slop: 0.1, roofSkin: 0.01 });
498
+ var KEYS = /* @__PURE__ */ new Set(["preset", "shape", "top", "offWall", "restitution", "passes", "slop", "supportCos", "entrySpeed", "entryWindow", "roofSkin", "maxVelocity", "stopSpin", "pairs", "drawing", "push", "headFriction", "propSlide"]);
499
+ var TOP_KEYS = {
500
+ [TopPolicy.Stand]: /* @__PURE__ */ new Set(["policy", "ride", "center", "slide"]),
501
+ [TopPolicy.Slide]: /* @__PURE__ */ new Set(["policy", "speed", "step", "share"]),
502
+ [TopPolicy.Bounce]: /* @__PURE__ */ new Set(["policy", "stompMin", "bounceSpeed", "jumpBoost"]),
503
+ [TopPolicy.Ghost]: /* @__PURE__ */ new Set(["policy"])
504
+ };
505
+ var SLIDE_KEYS = /* @__PURE__ */ new Set(["speed", "step", "share"]);
506
+ var PROP_SLIDE_KEYS = /* @__PURE__ */ new Set(["speed", "step"]);
507
+ var PAIR_KEYS = /* @__PURE__ */ new Set(["a", "b", "top"]);
508
+ var BOX_KEYS = /* @__PURE__ */ new Set(["kind", "length", "width", "top"]);
509
+ var HULL_KEYS = /* @__PURE__ */ new Set(["kind", "radius", "height"]);
510
+ var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
511
+ var has = (o, k) => Object.hasOwn(o, k) && o[k] !== void 0;
512
+ function onlyKeys(o, keys, path) {
513
+ const unknown = Object.keys(o).find((k) => !keys.has(k));
514
+ if (unknown !== void 0) throw new ContactConfigError(`${path}.${unknown}`, "is not a contact setting");
515
+ }
516
+ function num(o, key, path, fallback, lo, hi, open = false) {
517
+ if (!has(o, key)) return fallback;
518
+ const v = o[key];
519
+ const low = open ? v > lo : v >= lo;
520
+ if (typeof v !== "number" || !Number.isFinite(v) || !low || v > hi) throw new ContactConfigError(`${path}.${key}`, `must be a number in ${open ? "(" : "["}${lo}, ${hi}]`);
521
+ return v;
522
+ }
523
+ function slideOf(v, path, base, keys = SLIDE_KEYS) {
524
+ if (!isRecord(v)) throw new ContactConfigError(path, "must be an object");
525
+ onlyKeys(v, keys, path);
526
+ const speed = num(v, "speed", path, base.speed, 0, CONTACT_VALUE_MAX);
527
+ const step = has(v, "step") && v.step === null ? null : num(v, "step", path, base.step ?? CONTACT_VALUE_MAX, 0, CONTACT_VALUE_MAX, true);
528
+ const keptStep = !has(v, "step") ? base.step : step;
529
+ return { speed, step: keptStep, share: num(v, "share", path, base.share, 0, 1) };
530
+ }
531
+ var baseSlide = (top, vehicle) => {
532
+ if (top.policy === TopPolicy.Slide) return { speed: top.speed, step: top.step, share: top.share };
533
+ if (top.policy === TopPolicy.Bounce || top.policy === TopPolicy.Stand) return top.slide;
534
+ return vehicle ? VEHICLE_SLIDE : CHARACTER_SLIDE;
535
+ };
536
+ function standOf(v, path, base, vehicle) {
537
+ const from = base.policy === TopPolicy.Stand ? base : stand(true, null, baseSlide(base, vehicle));
538
+ if (has(v, "ride") && typeof v.ride !== "boolean") throw new ContactConfigError(`${path}.ride`, "must be true or false");
539
+ const ride = has(v, "ride") ? v.ride : from.ride;
540
+ const center = !has(v, "center") ? from.center : v.center === null ? null : num(v, "center", path, 1, 0, 1, true);
541
+ const s = has(v, "slide") ? slideOf(v.slide, `${path}.slide`, from.slide) : from.slide;
542
+ return stand(ride, center, s);
543
+ }
544
+ function topOf(v, path, base, vehicle) {
545
+ if (!isRecord(v)) throw new ContactConfigError(path, "must be an object");
546
+ const policy = v.policy;
547
+ if (typeof policy !== "string" || !Object.hasOwn(TOP_KEYS, policy)) throw new ContactConfigError(`${path}.policy`, "must be stand, slide, bounce or ghost");
548
+ const p = policy;
549
+ onlyKeys(v, TOP_KEYS[p], path);
550
+ if (p === TopPolicy.Stand) return standOf(v, path, base, vehicle);
551
+ if (p === TopPolicy.Ghost) return { policy: p };
552
+ if (p === TopPolicy.Slide) return slideTop(slideOf(v, path, baseSlide(base, vehicle), TOP_KEYS[p]));
553
+ const stomp = base.policy === TopPolicy.Bounce ? base : STOMP;
554
+ return {
555
+ policy: TopPolicy.Bounce,
556
+ stompMin: num(v, "stompMin", path, stomp.policy === TopPolicy.Bounce ? stomp.stompMin : 0, 0, CONTACT_VALUE_MAX),
557
+ bounceSpeed: num(v, "bounceSpeed", path, stomp.policy === TopPolicy.Bounce ? stomp.bounceSpeed : 0, 0, CONTACT_VALUE_MAX),
558
+ jumpBoost: num(v, "jumpBoost", path, stomp.policy === TopPolicy.Bounce ? stomp.jumpBoost : 0, 0, CONTACT_VALUE_MAX),
559
+ slide: baseSlide(base, vehicle)
560
+ };
561
+ }
562
+ function shapeOf(v, path, preset) {
563
+ const p = preset.params;
564
+ if (v === void 0) {
565
+ if (preset.name === "vehicle") return { kind: ContactShapeKind.Box, length: 2 * (p.radius ?? 0), width: 2 * (p.radius ?? 0), top: p.height ?? 0 };
566
+ return { kind: ContactShapeKind.Hull, radius: p.radius ?? 0, height: p.standHeight ?? p.height ?? 0 };
567
+ }
568
+ if (!isRecord(v)) throw new ContactConfigError(path, "must be an object");
569
+ if (v.kind === ContactShapeKind.Box) {
570
+ onlyKeys(v, BOX_KEYS, path);
571
+ const dim = (k) => num(v, k, path, Number.NaN, 0, CONTACT_VALUE_MAX, true);
572
+ return { kind: ContactShapeKind.Box, length: dim("length"), width: dim("width"), top: dim("top") };
573
+ }
574
+ if (v.kind === ContactShapeKind.Hull) {
575
+ onlyKeys(v, HULL_KEYS, path);
576
+ const dim = (k) => num(v, k, path, Number.NaN, 0, CONTACT_VALUE_MAX, true);
577
+ return { kind: ContactShapeKind.Hull, radius: dim("radius"), height: dim("height") };
578
+ }
579
+ throw new ContactConfigError(`${path}.kind`, "must be box or hull");
580
+ }
581
+ function requireShape(shape, path) {
582
+ for (const [k, v] of Object.entries(shape)) if (typeof v === "number" && Number.isNaN(v)) throw new ContactConfigError(`${path}.${k}`, "is required");
583
+ return shape;
584
+ }
585
+ function headFrictionOf(given, path, base) {
586
+ if (!has(given, "headFriction")) return base;
587
+ const v = given.headFriction;
588
+ if (!Object.values(HeadFriction).includes(v)) throw new ContactConfigError(`${path}.headFriction`, "must be slide or carry");
589
+ return v;
590
+ }
591
+ function propSlideOf(given, path) {
592
+ if (!has(given, "propSlide")) return PROP_SLIDE;
593
+ const v = given.propSlide;
594
+ const at = `${path}.propSlide`;
595
+ if (!isRecord(v)) throw new ContactConfigError(at, "must be an object");
596
+ onlyKeys(v, PROP_SLIDE_KEYS, at);
597
+ return { speed: num(v, "speed", at, PROP_SLIDE.speed, 0, CONTACT_VALUE_MAX), step: num(v, "step", at, PROP_SLIDE.step, 0, CONTACT_VALUE_MAX, true) };
598
+ }
599
+ function pairsOf(v, path, base, vehicle) {
600
+ if (v === void 0) return [];
601
+ if (!Array.isArray(v) || v.length > CONTACT_PAIRS_MAX) throw new ContactConfigError(path, `must be a list of at most ${CONTACT_PAIRS_MAX} pair rules`);
602
+ return v.map((r, i) => {
603
+ const at = `${path}[${i}]`;
604
+ if (!isRecord(r)) throw new ContactConfigError(at, "must be an object");
605
+ onlyKeys(r, PAIR_KEYS, at);
606
+ for (const k of ["a", "b"]) {
607
+ const tag = r[k];
608
+ if (typeof tag !== "string" || tag.length === 0 || tag.length > CONTACT_TAG_MAX) throw new ContactConfigError(`${at}.${k}`, `must be a tag of 1 to ${CONTACT_TAG_MAX} characters`);
609
+ }
610
+ return { a: r.a, b: r.b, top: topOf(r.top, `${at}.top`, base, vehicle) };
611
+ });
612
+ }
613
+ function passesOf(o, path, fallback) {
614
+ const n = num(o, "passes", path, fallback, 1, CONTACT_PASSES_MAX);
615
+ if (!Number.isInteger(n)) throw new ContactConfigError(`${path}.passes`, `must be an integer in [1, ${CONTACT_PASSES_MAX}]`);
616
+ return n;
617
+ }
618
+ var WORLD_PRESETS = /* @__PURE__ */ new Set(["firstperson", "vehicle"]);
619
+ function resolveContactConfig(given, preset, path = "$.config.contacts") {
620
+ if (!isRecord(given)) throw new ContactConfigError(path, "must be an object");
621
+ onlyKeys(given, KEYS, path);
622
+ if (preset === null || !WORLD_PRESETS.has(preset.name)) throw new ContactConfigError(path, "needs a firstperson or vehicle movement preset");
623
+ const name = given.preset;
624
+ if (typeof name !== "string" || !Object.hasOwn(CONTACT_PRESETS, name)) throw new ContactConfigError(`${path}.preset`, "must be shooter, platformer, vehicle-arena, physics-sandbox or party");
625
+ const table = CONTACT_PRESETS[name];
626
+ const shape = requireShape(shapeOf(given.shape, `${path}.shape`, preset), `${path}.shape`);
627
+ const vehicle = shape.kind === ContactShapeKind.Box;
628
+ const base = vehicle ? table.vehicle : table.character;
629
+ const top = given.top === void 0 ? base : topOf(given.top, `${path}.top`, base, vehicle);
630
+ const offBase = vehicle ? table.vehicleOffWall : baseSlide(base, false);
631
+ const p = preset.params;
632
+ const maxVelocity = has(given, "maxVelocity") && given.maxVelocity === null ? null : num(given, "maxVelocity", path, p.maxVelocity ?? CONTACT_VALUE_MAX, 0, CONTACT_VALUE_MAX, true);
633
+ if (has(given, "stopSpin") && typeof given.stopSpin !== "boolean") throw new ContactConfigError(`${path}.stopSpin`, "must be true or false");
634
+ return {
635
+ preset: name,
636
+ shape,
637
+ hull: { radius: p.radius ?? 0, height: p.standHeight ?? p.height ?? 0, skin: p.skin ?? 0 },
638
+ groundTrace: p.groundTrace ?? 0,
639
+ supportCos: num(given, "supportCos", path, CONTACT_DEFAULTS.supportCos, 0, 1),
640
+ entrySpeed: num(given, "entrySpeed", path, CONTACT_DEFAULTS.entrySpeed, 0, CONTACT_VALUE_MAX, true),
641
+ entryWindow: num(given, "entryWindow", path, CONTACT_DEFAULTS.entryWindow, 0, 1, true),
642
+ restitution: num(given, "restitution", path, table.restitution, 0, 1),
643
+ passes: passesOf(given, path, table.passes),
644
+ slop: num(given, "slop", path, CONTACT_DEFAULTS.slop, 0, CONTACT_VALUE_MAX),
645
+ roofSkin: num(given, "roofSkin", path, CONTACT_DEFAULTS.roofSkin, 0, CONTACT_VALUE_MAX),
646
+ maxVelocity,
647
+ stopSpin: has(given, "stopSpin") ? given.stopSpin : table.stopSpin,
648
+ top,
649
+ offWall: given.offWall === void 0 ? offBase : slideOf(given.offWall, `${path}.offWall`, offBase),
650
+ pairs: pairsOf(given.pairs, `${path}.pairs`, base, vehicle),
651
+ drawing: drawingOf(given, path, table.drawing),
652
+ push: pushOf(given, path, table.push),
653
+ headFriction: headFrictionOf(given, path, table.headFriction),
654
+ propSlide: propSlideOf(given, path)
655
+ };
656
+ }
657
+
658
+ // src/authority/input-config.ts
659
+ var InputRepeat = { On: "on", Off: "off" };
660
+ var INPUT_SETTLE_MS = { MIN: 100, MAX: 6e4 };
661
+ var INPUT_PRESETS = {
662
+ [ContactPreset.Shooter]: { repeat: InputRepeat.On },
663
+ [ContactPreset.Platformer]: { repeat: InputRepeat.On },
664
+ [ContactPreset.VehicleArena]: { repeat: InputRepeat.Off },
665
+ [ContactPreset.PhysicsSandbox]: { repeat: InputRepeat.On },
666
+ [ContactPreset.Party]: { repeat: InputRepeat.On }
667
+ };
668
+ var INPUTS_DEFAULT = { repeat: InputRepeat.On };
669
+ var InputConfigError = class extends Error {
670
+ path;
671
+ constructor(path) {
672
+ super(`bad config at ${path}`);
673
+ this.name = "InputConfigError";
674
+ this.path = path;
675
+ }
676
+ };
677
+ var KEYS2 = /* @__PURE__ */ new Set(["preset", "repeat", "settleMs"]);
678
+ var isPlainObject2 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
679
+ var isPreset = (v) => Object.values(ContactPreset).some((p) => p === v);
680
+ var isRepeat = (v) => v === InputRepeat.On || v === InputRepeat.Off;
681
+ var isSettleMs = (v) => typeof v === "number" && Number.isFinite(v) && v >= INPUT_SETTLE_MS.MIN && v <= INPUT_SETTLE_MS.MAX;
682
+ function resolveInputConfig(given, contactsPreset, path = "$.config.inputs") {
683
+ if (given === void 0 || given === null) return contactsPreset === null ? null : INPUT_PRESETS[contactsPreset];
684
+ if (!isPlainObject2(given)) throw new InputConfigError(path);
685
+ for (const key of Object.keys(given)) if (!KEYS2.has(key)) throw new InputConfigError(`${path}.${key}`);
686
+ if (given.preset !== void 0 && !isPreset(given.preset)) throw new InputConfigError(`${path}.preset`);
687
+ if (given.repeat !== void 0 && !isRepeat(given.repeat)) throw new InputConfigError(`${path}.repeat`);
688
+ if (given.settleMs !== void 0 && !isSettleMs(given.settleMs)) throw new InputConfigError(`${path}.settleMs`);
689
+ const preset = given.preset ?? contactsPreset;
690
+ const fromPreset = preset === null ? INPUTS_DEFAULT : INPUT_PRESETS[preset];
691
+ const repeat = given.repeat ?? fromPreset.repeat;
692
+ return given.settleMs === void 0 ? { repeat } : { repeat, settleMs: given.settleMs };
693
+ }
694
+ function presetInputRepeat(presetText) {
695
+ let spec;
696
+ try {
697
+ spec = JSON.parse(presetText);
698
+ } catch {
699
+ return InputRepeat.On;
700
+ }
701
+ const inputs = isPlainObject2(spec) ? spec.inputs : void 0;
702
+ return isPlainObject2(inputs) && inputs.repeat === InputRepeat.Off ? InputRepeat.Off : InputRepeat.On;
703
+ }
704
+
705
+ // src/authority/lifetime-config.ts
706
+ var AuthorityLifetime = {
707
+ IDLE_SEAT_S: 30 * 60,
708
+ IDLE_SEAT_MIN_S: 30,
709
+ IDLE_SEAT_MAX_S: 24 * 60 * 60,
710
+ ROOM_AGE_S: 24 * 60 * 60,
711
+ ROOM_AGE_MIN_S: 60,
712
+ ROOM_AGE_MAX_S: 7 * 24 * 60 * 60
713
+ };
714
+ var LIFETIME_DEFAULT = { idleSeatS: AuthorityLifetime.IDLE_SEAT_S, maxRoomAgeS: AuthorityLifetime.ROOM_AGE_S };
715
+ var LifetimeConfigError = class extends Error {
716
+ path;
717
+ constructor(path) {
718
+ super(`bad config at ${path}`);
719
+ this.name = "LifetimeConfigError";
720
+ this.path = path;
721
+ }
722
+ };
723
+ var KEYS3 = /* @__PURE__ */ new Set(["idleSeatS", "maxRoomAgeS"]);
724
+ var inRange2 = (v, min, max) => typeof v === "number" && Number.isInteger(v) && v >= min && v <= max;
725
+ function resolveLifetime(given, path = "$.config.lifetime") {
726
+ if (given === void 0 || given === null) return null;
727
+ if (typeof given !== "object" || Array.isArray(given)) throw new LifetimeConfigError(path);
728
+ const declared = given;
729
+ for (const key of Object.keys(declared)) if (!KEYS3.has(key)) throw new LifetimeConfigError(`${path}.${key}`);
730
+ const { idleSeatS, maxRoomAgeS } = declared;
731
+ if (idleSeatS !== void 0 && !inRange2(idleSeatS, AuthorityLifetime.IDLE_SEAT_MIN_S, AuthorityLifetime.IDLE_SEAT_MAX_S)) throw new LifetimeConfigError(`${path}.idleSeatS`);
732
+ if (maxRoomAgeS !== void 0 && !inRange2(maxRoomAgeS, AuthorityLifetime.ROOM_AGE_MIN_S, AuthorityLifetime.ROOM_AGE_MAX_S)) throw new LifetimeConfigError(`${path}.maxRoomAgeS`);
733
+ return { idleSeatS: idleSeatS ?? LIFETIME_DEFAULT.idleSeatS, maxRoomAgeS: maxRoomAgeS ?? LIFETIME_DEFAULT.maxRoomAgeS };
734
+ }
735
+
736
+ // src/authority/lifts-config.ts
737
+ var LiftShapeKind = {
738
+ /** A box `half` (x, y, z): axis-aligned, or turned about z when it spins (a sweeper bar). */
739
+ Box: "box",
740
+ /** An upright disc of `radius`, `half` its half height (a turntable, a spinner). */
741
+ Disc: "disc"
742
+ };
743
+ var LiftPathMode = {
744
+ /** Out along the legs, then back along them. */
745
+ PingPong: "pingpong",
746
+ /** Round the points and from the last back to the first. */
747
+ Loop: "loop"
748
+ };
749
+ var LiftEase = {
750
+ /** At a steady speed. */
751
+ Linear: "linear",
752
+ /** Starting and stopping at rest (`s·s·(3 − 2s)`; its peak speed 1.5 times the mean). */
753
+ Smooth: "smooth"
754
+ };
755
+ var LiftImpart = {
756
+ /** The lift's velocity at the rider's point (Unreal's and Godot's default). */
757
+ All: "all",
758
+ /** Nothing: the rider leaves with its own velocity. */
759
+ None: "none"
760
+ };
761
+ var NO_LIFTS = Object.freeze({ platforms: Object.freeze([]), bodies: false });
762
+ var LiftsConfigError = class extends RangeError {
763
+ path;
764
+ fast;
765
+ constructor(path, why, fast) {
766
+ super(`${path}: ${why}`);
767
+ this.name = "LiftsConfigError";
768
+ this.path = path;
769
+ this.fast = fast;
770
+ }
771
+ };
772
+ var LIFTS_MAX = 32;
773
+ var LIFT_POINTS_MAX = 16;
774
+ var LIFT_ID_MAX = 32;
775
+ var LIFT_VALUE_MAX = 1e6;
776
+ var LIFT_TICKS_MAX = 2 ** 24;
777
+ var LIFT_SPIN_MIN_PERIOD = 8;
778
+ var SMOOTH_PEAK = 1.5;
779
+ var TWO_PI = 2 * Math.PI;
780
+ var ID = /^[a-z0-9-]+$/;
781
+ var isLiftId = (id) => typeof id === "string" && id.length > 0 && id.length <= LIFT_ID_MAX && ID.test(id);
782
+ var KEYS4 = /* @__PURE__ */ new Set(["platforms", "bodies"]);
783
+ var PLATFORM_KEYS = /* @__PURE__ */ new Set(["id", "shape", "at", "path", "spin", "surface", "impart", "called"]);
784
+ var PATH_KEYS = /* @__PURE__ */ new Set(["points", "legs", "waits", "mode", "ease", "phase"]);
785
+ var SPIN_KEYS = /* @__PURE__ */ new Set(["period", "dir", "phase"]);
786
+ var BOX_KEYS2 = /* @__PURE__ */ new Set(["kind", "half"]);
787
+ var DISC_KEYS = /* @__PURE__ */ new Set(["kind", "radius", "half"]);
788
+ var isRecord2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
789
+ var has2 = (o, k) => Object.hasOwn(o, k) && o[k] !== void 0;
790
+ var fround = Math.fround;
791
+ function onlyKeys2(o, keys, path) {
792
+ const unknown = Object.keys(o).find((k) => !keys.has(k));
793
+ if (unknown !== void 0) throw new LiftsConfigError(`${path}.${unknown}`, "is not a lift setting");
794
+ }
795
+ function value(v, path, positive = false) {
796
+ if (typeof v !== "number" || !Number.isFinite(v) || Math.abs(v) > LIFT_VALUE_MAX) throw new LiftsConfigError(path, `must be a number of magnitude at most ${LIFT_VALUE_MAX}`);
797
+ const f = fround(v) === 0 ? 0 : fround(v);
798
+ if (positive && !(f > 0)) throw new LiftsConfigError(path, "must be above 0");
799
+ return f;
800
+ }
801
+ function ticks(v, path, lo) {
802
+ if (typeof v !== "number" || !Number.isInteger(v) || v < lo || v > LIFT_TICKS_MAX) throw new LiftsConfigError(path, `must be an integer number of ticks in [${lo}, ${LIFT_TICKS_MAX}]`);
803
+ return v;
804
+ }
805
+ function vector(v, path, n, positive = false) {
806
+ if (!Array.isArray(v) || v.length !== n) throw new LiftsConfigError(path, `must be an array of ${n} numbers`);
807
+ return v.map((x, i) => value(x, `${path}[${i}]`, positive));
808
+ }
809
+ function shapeOf2(v, path) {
810
+ if (!isRecord2(v)) throw new LiftsConfigError(path, "must be an object");
811
+ if (v.kind === LiftShapeKind.Box) {
812
+ onlyKeys2(v, BOX_KEYS2, path);
813
+ return { kind: LiftShapeKind.Box, half: vector(v.half, `${path}.half`, 3, true) };
814
+ }
815
+ if (v.kind === LiftShapeKind.Disc) {
816
+ onlyKeys2(v, DISC_KEYS, path);
817
+ return { kind: LiftShapeKind.Disc, radius: value(v.radius, `${path}.radius`, true), half: value(v.half, `${path}.half`, true) };
818
+ }
819
+ throw new LiftsConfigError(`${path}.kind`, "must be box or disc");
820
+ }
821
+ function enumOf(o, key, path, values, fallback) {
822
+ if (!has2(o, key)) return fallback;
823
+ const v = o[key];
824
+ if (!Object.values(values).includes(v)) throw new LiftsConfigError(`${path}.${key}`, `must be ${Object.values(values).join(" or ")}`);
825
+ return v;
826
+ }
827
+ function periodOf(legs, waits, mode) {
828
+ const sum = (xs) => xs.reduce((a, b) => a + b, 0);
829
+ if (mode === LiftPathMode.Loop) return sum(legs) + sum(waits);
830
+ return 2 * sum(legs) + 2 * sum(waits) - (waits[0] ?? 0) - (waits[waits.length - 1] ?? 0);
831
+ }
832
+ function pathOf(v, path, called) {
833
+ if (!isRecord2(v)) throw new LiftsConfigError(path, "must be an object");
834
+ onlyKeys2(v, PATH_KEYS, path);
835
+ const mode = enumOf(v, "mode", path, LiftPathMode, LiftPathMode.PingPong);
836
+ const ease = enumOf(v, "ease", path, LiftEase, LiftEase.Linear);
837
+ if (!Array.isArray(v.legs) || v.legs.length < 1 || v.legs.length > LIFT_POINTS_MAX) throw new LiftsConfigError(`${path}.legs`, `must be a list of 1 to ${LIFT_POINTS_MAX} leg tick counts`);
838
+ const legs = v.legs.map((t, i) => ticks(t, `${path}.legs[${i}]`, 1));
839
+ const wantPoints = mode === LiftPathMode.Loop ? legs.length : legs.length + 1;
840
+ if (!Array.isArray(v.points) || v.points.length !== wantPoints || wantPoints < 2) {
841
+ throw new LiftsConfigError(`${path}.points`, `must be ${wantPoints < 2 ? "at least 2" : wantPoints} points (${mode === LiftPathMode.Loop ? "one per leg" : "one more than the legs"})`);
842
+ }
843
+ const points = v.points.map((p, i) => vector(p, `${path}.points[${i}]`, 3));
844
+ const waits = has2(v, "waits") ? v.waits : points.map(() => 0);
845
+ if (!Array.isArray(waits) || waits.length !== points.length) throw new LiftsConfigError(`${path}.waits`, `must be one wait per point (${points.length})`);
846
+ const waitTicks = waits.map((t, i) => ticks(t, `${path}.waits[${i}]`, 0));
847
+ if (called && has2(v, "phase")) throw new LiftsConfigError(`${path}.phase`, "is not allowed on a called lift (its call sets its start)");
848
+ const phase = has2(v, "phase") ? ticks(v.phase, `${path}.phase`, 0) : 0;
849
+ return { points, legs, waits: waitTicks, mode, ease, phase, period: periodOf(legs, waitTicks, mode) };
850
+ }
851
+ function spinOf(v, path) {
852
+ if (!isRecord2(v)) throw new LiftsConfigError(path, "must be an object");
853
+ onlyKeys2(v, SPIN_KEYS, path);
854
+ const period = ticks(v.period, `${path}.period`, LIFT_SPIN_MIN_PERIOD);
855
+ if (has2(v, "dir") && v.dir !== 1 && v.dir !== -1) throw new LiftsConfigError(`${path}.dir`, "must be 1 or -1");
856
+ return { period, dir: has2(v, "dir") ? v.dir : 1, phase: has2(v, "phase") ? ticks(v.phase, `${path}.phase`, 0) : 0 };
857
+ }
858
+ var reachOf = (s) => s.kind === LiftShapeKind.Disc ? s.radius : Math.sqrt(s.half[0] * s.half[0] + s.half[1] * s.half[1]);
859
+ var speedText = (perTick, tickHz, rate = "") => `${Number(perTick.toFixed(3))} u a tick (${Number((perTick * tickHz).toFixed(1))} u/s${rate})`;
860
+ function refuseFast(lift, path, radius, tickHz) {
861
+ const spinTip = lift.spin === null ? 0 : reachOf(lift.shape) * TWO_PI / lift.spin.period;
862
+ const allowed = `at most ${speedText(radius, tickHz, ` at ${tickHz} Hz`)} is allowed (the hull radius a tick)`;
863
+ const legs = lift.path?.legs ?? [];
864
+ let fastest = { at: `${path}.spin.period`, move: spinTip };
865
+ legs.forEach((n, i) => {
866
+ const p = lift.path;
867
+ const a = p.points[i];
868
+ const b = p.points[(i + 1) % p.points.length];
869
+ const dx = b[0] - a[0];
870
+ const dy = b[1] - a[1];
871
+ const dz = b[2] - a[2];
872
+ const mean = Math.sqrt(dx * dx + dy * dy + dz * dz) / n;
873
+ const move = (p.ease === LiftEase.Smooth ? SMOOTH_PEAK * mean : mean) + spinTip;
874
+ if (move > fastest.move) fastest = { at: `${path}.path.legs[${i}]`, move };
875
+ });
876
+ if (fastest.move > radius) {
877
+ throw new LiftsConfigError(fastest.at, `platform '${lift.id}' moves up to ${speedText(fastest.move, tickHz)}; ${allowed}`, { platform: lift.id, move: fastest.move, allowed: radius, tickHz });
878
+ }
879
+ }
880
+ function platformOf(v, path, ids, radius, tickHz) {
881
+ if (!isRecord2(v)) throw new LiftsConfigError(path, "must be an object");
882
+ onlyKeys2(v, PLATFORM_KEYS, path);
883
+ const id = v.id;
884
+ if (typeof id !== "string" || id.length === 0 || id.length > LIFT_ID_MAX || !ID.test(id)) throw new LiftsConfigError(`${path}.id`, `must be 1 to ${LIFT_ID_MAX} of a-z, 0-9 and -`);
885
+ if (ids.has(id)) throw new LiftsConfigError(`${path}.id`, `repeats the id '${id}'`);
886
+ ids.add(id);
887
+ const shape = shapeOf2(v.shape, `${path}.shape`);
888
+ if (has2(v, "called") && typeof v.called !== "boolean") throw new LiftsConfigError(`${path}.called`, "must be true or false");
889
+ const called = v.called === true;
890
+ const lpath = has2(v, "path") ? pathOf(v.path, `${path}.path`, called) : null;
891
+ if (lpath === null && !has2(v, "at")) throw new LiftsConfigError(`${path}.at`, "is required without a path");
892
+ const at = has2(v, "at") ? vector(v.at, `${path}.at`, 3) : lpath?.points[0];
893
+ const lift = {
894
+ id,
895
+ shape,
896
+ at,
897
+ path: lpath,
898
+ spin: has2(v, "spin") ? spinOf(v.spin, `${path}.spin`) : null,
899
+ surface: has2(v, "surface") ? vector(v.surface, `${path}.surface`, 2) : null,
900
+ impart: enumOf(v, "impart", path, LiftImpart, LiftImpart.All),
901
+ called
902
+ };
903
+ refuseFast(lift, path, radius, tickHz);
904
+ return lift;
905
+ }
906
+ var WORLD_PRESETS2 = /* @__PURE__ */ new Set(["firstperson", "vehicle"]);
907
+ function resolveLifts(given, preset, tickHz, path = "$.config.lifts") {
908
+ if (!isRecord2(given)) throw new LiftsConfigError(path, "must be an object");
909
+ onlyKeys2(given, KEYS4, path);
910
+ if (preset === null || !WORLD_PRESETS2.has(preset.name)) throw new LiftsConfigError(path, "needs a firstperson or vehicle movement preset");
911
+ const list2 = given.platforms;
912
+ if (!Array.isArray(list2) || list2.length < 1 || list2.length > LIFTS_MAX) throw new LiftsConfigError(`${path}.platforms`, `must be a list of 1 to ${LIFTS_MAX} platforms`);
913
+ if (has2(given, "bodies") && typeof given.bodies !== "boolean") throw new LiftsConfigError(`${path}.bodies`, "must be true or false");
914
+ const radius = preset.params.radius ?? 0;
915
+ const ids = /* @__PURE__ */ new Set();
916
+ const platforms = list2.map((p, i) => platformOf(p, `${path}.platforms[${i}]`, ids, radius, tickHz));
917
+ return { platforms, bodies: given.bodies !== false };
918
+ }
919
+
920
+ // src/authority/projectile.ts
921
+ var MAX_PROJECTILE_TICK = 2 ** 32 - 1;
922
+ var MAX_PROJECTILE_G = 1e3;
923
+ var isProjectile = (entity) => typeof entity.t0 === "number" && entity.t0 > 0;
924
+ function projectileAt(launch, T, tickHz) {
925
+ const elapsed = (T - launch.t0) / tickHz;
926
+ const s = Number.isFinite(elapsed) && elapsed >= 0 ? elapsed : 0;
927
+ return {
928
+ x: unsignedZero(Math.fround(launch.x + launch.vx * s)),
929
+ y: unsignedZero(Math.fround(launch.y + launch.vy * s)),
930
+ z: unsignedZero(Math.fround(launch.z + launch.vz * s - 0.5 * launch.g * s * s)),
931
+ vx: unsignedZero(launch.vx),
932
+ vy: unsignedZero(launch.vy),
933
+ vz: unsignedZero(Math.fround(launch.vz - launch.g * s))
934
+ };
935
+ }
936
+ function serverTick(serverNowMs, tickOrigin, tickHz) {
937
+ return (serverNowMs - tickOrigin) * tickHz / 1e3;
938
+ }
939
+ function tickInstant(tick, tickOrigin, tickHz) {
940
+ return tickOrigin + tick * 1e3 / tickHz;
941
+ }
942
+
943
+ // src/authority/wire.ts
944
+ var GuestGlobal = {
945
+ INIT: "__genex_init",
946
+ TICK: "__genex_tick",
947
+ /** A physics room's static world, called once after init (§17.10 *The static world*). */
948
+ WORLD: "__genex_world",
949
+ /** The room's hand-off at the end of a drain (PROTOCOL.md §17.13), called once. */
950
+ HANDOFF: "__genex_handoff"
951
+ };
952
+ var AuthorityLimits = {
953
+ /** Entities one tick may update, and separately remove. */
954
+ MAX_ENTITIES: 256,
955
+ /** A room's hand-off (`onDrain`, PROTOCOL.md §17.13) as JSON text. Over it the successor starts clean. */
956
+ MAX_HANDOFF_CHARS: 256 * 1024,
957
+ /** Entities one room may hold at once. The room state lives on the host, outside the sandbox's
958
+ * memory limit, so this cap is what bounds it (and every client's patches). */
959
+ MAX_ROOM_ENTITIES: 1024,
960
+ /** One TickOut / InitOut as JSON text. */
961
+ MAX_OUTPUT_BYTES: 32 * 1024,
962
+ MAX_EVENTS_PER_TICK: 64,
963
+ /** One entity's `data` JSON text. */
964
+ MAX_ENTITY_DATA_BYTES: 256,
965
+ MAX_ENTITY_ID_CHARS: 64,
966
+ MAX_KIND_CHARS: 64,
967
+ /** A session id (`owner`, an event's `to`). */
968
+ MAX_SESSION_ID_CHARS: 64,
969
+ /** Every entity number's magnitude (float32 holds it without overflowing). */
970
+ MAX_ABS,
971
+ /** `flags` is an integer below this. */
972
+ MASK_LIMIT,
973
+ /** `epoch` is an integer in [0, MAX_EPOCH]. */
974
+ MAX_EPOCH: 2 ** 32 - 1,
975
+ /** A projectile's launch tick `t0` is an integer in [0, MAX_PROJECTILE_TICK] (§17.9). */
976
+ MAX_PROJECTILE_TICK,
977
+ /** A projectile's gravity `g` is in [0, MAX_PROJECTILE_G] units/s². */
978
+ MAX_PROJECTILE_G,
979
+ /** The launches waiting to go out (since the last output the host applied) may hold at most this
980
+ * many entities and this much of the output's JSON (§17.9): a launch past either is refused in
981
+ * the sandbox, so a burst alone can never make an output the host must refuse (about a hundred
982
+ * new projectiles whose floats print in full). The rest of MAX_ENTITIES and MAX_OUTPUT_BYTES
983
+ * stays for everything else a tick writes. */
984
+ MAX_LAUNCH_UPDATES: 224,
985
+ MAX_LAUNCH_BYTES: 28 * 1024,
986
+ /** One event: its type, and its `data` as JSON text. */
987
+ MAX_EVENT_TYPE_CHARS: Limits.MAX_EVENT_TYPE_CHARS,
988
+ MAX_EVENT_BYTES: Limits.MAX_EVENT_BYTES,
989
+ MAX_JSON_DEPTH: Limits.MAX_JSON_DEPTH,
990
+ MAX_JSON_NODES: Limits.MAX_JSON_NODES,
991
+ /** Seats one authority room may hold. */
992
+ MAX_PLAYERS: Limits.ROOM_MAX_CLIENTS,
993
+ /** A physics room's output (§17.10): at most this many `phys` commands, and `phys` plus `physQ`
994
+ * at most this much JSON, apart from the MAX_OUTPUT_BYTES the rest may hold, so the whole text is
995
+ * at most MAX_PHYSICS_OUTPUT_BYTES. */
996
+ MAX_PHYS_COMMANDS: 256,
997
+ MAX_PHYS_BYTES: 32 * 1024,
998
+ MAX_PHYSICS_OUTPUT_BYTES: 64 * 1024,
999
+ /** The most a physics room's static world holds (§17.10 *The static world*): §17.7's caps, so any world a
1000
+ * module may carry has its physics core. `limits.staticPieces` follows a world past its default. */
1001
+ MAX_PHYSICS_WORLD_BOXES: 32768,
1002
+ MAX_PHYSICS_WORLD_RAMPS: 4096,
1003
+ /** `__genex_world()`'s text: base64 of the largest such world, 4 · ⌈(8 + 24 · 32768 + 36 · 4096) / 3⌉. */
1004
+ MAX_WORLD_TEXT_CHARS: 1245196
1005
+ };
1006
+ var TICK_RATES = [20, 30, 60, 120];
1007
+ var PHYSICS_ONLY_TICK_HZ = 120;
1008
+ var MAX_TICK_HZ = 120;
1009
+ var DEFAULT_TICK_HZ = 60;
1010
+ var TickOutFault = {
1011
+ NOT_JSON: "not_json",
1012
+ TOO_LARGE: "too_large",
1013
+ BAD_SHAPE: "bad_shape",
1014
+ NON_FINITE: "non_finite",
1015
+ OUT_OF_RANGE: "out_of_range",
1016
+ BAD_ID: "bad_id",
1017
+ DATA_TOO_LARGE: "data_too_large",
1018
+ TOO_MANY_ENTITIES: "too_many_entities",
1019
+ TOO_MANY_EVENTS: "too_many_events",
1020
+ RESERVED_EVENT: "reserved_event",
1021
+ BAD_CONFIG: "bad_config",
1022
+ /** The module's maps leave a room without a shot world (§17.7 *The shot world*). */
1023
+ NO_SHOT_WORLD: "no_shot_world",
1024
+ /** More `phys` commands than MAX_PHYS_COMMANDS, or more `physQ` queries than `queriesPerStep`. */
1025
+ TOO_MANY_COMMANDS: "too_many_commands"
1026
+ };
1027
+ var InputUse = {
1028
+ /** A real input: the preset steps it, then `onInput` hears it. */
1029
+ STEP: "step",
1030
+ /** A starved tick: the preset steps the seat's last real input again; `onInput` never hears it. */
1031
+ REPEAT: "repeat",
1032
+ /** A late real input a repeat already stepped for: `onInput` hears it (its one-shots happen exactly
1033
+ * once), the preset does not step it again. Only an input whose buttons equal those the preset
1034
+ * last stepped is absorbed, so no press edge is ever lost or made. */
1035
+ ABSORB: "absorb"
1036
+ };
1037
+ function sanitizeInput(raw) {
1038
+ return {
1039
+ moveX: axis(raw.moveX),
1040
+ moveY: axis(raw.moveY),
1041
+ moveZ: axis(raw.moveZ),
1042
+ yaw: wrappedAngle(raw.yaw) ?? 0,
1043
+ pitch: wrappedAngle(raw.pitch) ?? 0,
1044
+ buttons: mask(raw.buttons)
1045
+ };
1046
+ }
1047
+ var Refusal = class {
1048
+ fault;
1049
+ path;
1050
+ constructor(fault, path) {
1051
+ this.fault = fault;
1052
+ this.path = path;
1053
+ }
1054
+ };
1055
+ var refuse = (fault, path) => {
1056
+ throw new Refusal(fault, path);
1057
+ };
1058
+ var FORBIDDEN_KEYS2 = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1059
+ var isRecord3 = (v) => {
1060
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
1061
+ const proto = Object.getPrototypeOf(v);
1062
+ return proto === Object.prototype || proto === null;
1063
+ };
1064
+ var FLOAT32_FIELDS = ["x", "y", "z", "vx", "vy", "vz", "yaw"];
1065
+ var FIRSTPERSON_FLOAT32_FIELDS = ["pitch", "duck", "cap"];
1066
+ var VEHICLE_FLOAT32_FIELDS = ["spin", "fuel"];
1067
+ var SEAT_ID = /^[A-Za-z0-9_-]{1,64}$/;
1068
+ function finite(v, path) {
1069
+ if (typeof v !== "number") return refuse(TickOutFault.BAD_SHAPE, path);
1070
+ if (!Number.isFinite(v)) return refuse(TickOutFault.NON_FINITE, path);
1071
+ if (Math.abs(v) > MAX_ABS) return refuse(TickOutFault.OUT_OF_RANGE, path);
1072
+ return unsignedZero(v);
1073
+ }
1074
+ function integerIn(v, max, path) {
1075
+ const n = finite(v, path);
1076
+ if (!Number.isInteger(n) || n < 0 || n > max) return refuse(TickOutFault.OUT_OF_RANGE, path);
1077
+ return n;
1078
+ }
1079
+ function launchTick(v, path) {
1080
+ if (typeof v !== "number") return refuse(TickOutFault.BAD_SHAPE, path);
1081
+ if (!Number.isFinite(v)) return refuse(TickOutFault.NON_FINITE, path);
1082
+ if (!Number.isInteger(v) || v < 0 || v > MAX_PROJECTILE_TICK) return refuse(TickOutFault.OUT_OF_RANGE, path);
1083
+ return unsignedZero(v);
1084
+ }
1085
+ function boundedString(v, max, path, fault) {
1086
+ if (typeof v !== "string" || v.length > max) return refuse(fault, path);
1087
+ return v;
1088
+ }
1089
+ function entityId(v, path) {
1090
+ const id = boundedString(v, AuthorityLimits.MAX_ENTITY_ID_CHARS, path, TickOutFault.BAD_ID);
1091
+ if (id.length === 0 || FORBIDDEN_KEYS2.has(id)) return refuse(TickOutFault.BAD_ID, path);
1092
+ return id;
1093
+ }
1094
+ function jsonValue(v, path, depth, budget) {
1095
+ budget.nodes += 1;
1096
+ if (depth > AuthorityLimits.MAX_JSON_DEPTH || budget.nodes > AuthorityLimits.MAX_JSON_NODES) {
1097
+ return refuse(TickOutFault.BAD_SHAPE, path);
1098
+ }
1099
+ if (v === null || typeof v === "boolean" || typeof v === "string") return v;
1100
+ if (typeof v === "number") {
1101
+ if (!Number.isFinite(v)) return refuse(TickOutFault.NON_FINITE, path);
1102
+ return v;
1103
+ }
1104
+ if (Array.isArray(v)) return v.map((item, i) => jsonValue(item, `${path}[${i}]`, depth + 1, budget));
1105
+ if (!isRecord3(v)) return refuse(TickOutFault.BAD_SHAPE, path);
1106
+ const out = {};
1107
+ for (const key of Object.keys(v)) {
1108
+ if (FORBIDDEN_KEYS2.has(key)) return refuse(TickOutFault.BAD_SHAPE, `${path}.${key}`);
1109
+ out[key] = jsonValue(v[key], `${path}.${key}`, depth + 1, budget);
1110
+ }
1111
+ return out;
1112
+ }
1113
+ function entityData(v, path) {
1114
+ if (typeof v !== "string") return refuse(TickOutFault.BAD_SHAPE, path);
1115
+ if (v.length > AuthorityLimits.MAX_ENTITY_DATA_BYTES) return refuse(TickOutFault.DATA_TOO_LARGE, path);
1116
+ if (v === "") return v;
1117
+ let parsed2;
1118
+ try {
1119
+ parsed2 = JSON.parse(v);
1120
+ } catch {
1121
+ return refuse(TickOutFault.BAD_SHAPE, path);
1122
+ }
1123
+ jsonValue(parsed2, path, 0, { nodes: 0 });
1124
+ return v;
1125
+ }
1126
+ function entityOut(v, path) {
1127
+ if (!isRecord3(v)) return refuse(TickOutFault.BAD_SHAPE, path);
1128
+ const out = { id: entityId(v.id, `${path}.id`) };
1129
+ if (v.kind !== void 0) {
1130
+ out.kind = boundedString(v.kind, AuthorityLimits.MAX_KIND_CHARS, `${path}.kind`, TickOutFault.BAD_SHAPE);
1131
+ }
1132
+ if (v.owner !== void 0) {
1133
+ out.owner = boundedString(v.owner, AuthorityLimits.MAX_SESSION_ID_CHARS, `${path}.owner`, TickOutFault.BAD_SHAPE);
1134
+ }
1135
+ for (const key of FLOAT32_FIELDS) {
1136
+ if (v[key] !== void 0) out[key] = unsignedZero(Math.fround(finite(v[key], `${path}.${key}`)));
1137
+ }
1138
+ if (v.hp !== void 0) out.hp = finite(v.hp, `${path}.hp`);
1139
+ if (v.flags !== void 0) out.flags = integerIn(v.flags, MASK_LIMIT - 1, `${path}.flags`);
1140
+ if (v.epoch !== void 0) out.epoch = integerIn(v.epoch, AuthorityLimits.MAX_EPOCH, `${path}.epoch`);
1141
+ if (v.data !== void 0) out.data = entityData(v.data, `${path}.data`);
1142
+ if (v.t0 !== void 0) out.t0 = launchTick(v.t0, `${path}.t0`);
1143
+ if (v.g !== void 0) {
1144
+ const g = finite(v.g, `${path}.g`);
1145
+ if (g < 0 || g > MAX_PROJECTILE_G) return refuse(TickOutFault.OUT_OF_RANGE, `${path}.g`);
1146
+ out.g = unsignedZero(Math.fround(g));
1147
+ }
1148
+ if (v.src !== void 0) {
1149
+ const src = boundedString(v.src, AuthorityLimits.MAX_SESSION_ID_CHARS, `${path}.src`, TickOutFault.BAD_SHAPE);
1150
+ if (src !== "" && !SEAT_ID.test(src)) return refuse(TickOutFault.BAD_SHAPE, `${path}.src`);
1151
+ out.src = src;
1152
+ }
1153
+ for (const key of FIRSTPERSON_FLOAT32_FIELDS) {
1154
+ if (v[key] !== void 0) out[key] = unsignedZero(Math.fround(finite(v[key], `${path}.${key}`)));
1155
+ }
1156
+ if (v.mstate !== void 0) out.mstate = integerIn(v.mstate, MASK_LIMIT - 1, `${path}.mstate`);
1157
+ for (const key of VEHICLE_FLOAT32_FIELDS) {
1158
+ if (v[key] !== void 0) out[key] = unsignedZero(Math.fround(finite(v[key], `${path}.${key}`)));
1159
+ }
1160
+ return out;
1161
+ }
1162
+ function eventOut(v, path) {
1163
+ if (!isRecord3(v)) return refuse(TickOutFault.BAD_SHAPE, path);
1164
+ const type = boundedString(v.type, AuthorityLimits.MAX_EVENT_TYPE_CHARS, `${path}.type`, TickOutFault.BAD_SHAPE);
1165
+ if (type.length === 0) return refuse(TickOutFault.BAD_SHAPE, `${path}.type`);
1166
+ if (isReservedEventType(type)) return refuse(TickOutFault.RESERVED_EVENT, `${path}.type`);
1167
+ const out = { type };
1168
+ if (v.data !== void 0) {
1169
+ const data = jsonValue(v.data, `${path}.data`, 0, { nodes: 0 });
1170
+ if (JSON.stringify(data).length > AuthorityLimits.MAX_EVENT_BYTES) return refuse(TickOutFault.TOO_LARGE, `${path}.data`);
1171
+ out.data = data;
1172
+ }
1173
+ if (v.to !== void 0) {
1174
+ const to = boundedString(v.to, AuthorityLimits.MAX_SESSION_ID_CHARS, `${path}.to`, TickOutFault.BAD_SHAPE);
1175
+ if (to.length === 0) return refuse(TickOutFault.BAD_SHAPE, `${path}.to`);
1176
+ out.to = to;
1177
+ }
1178
+ return out;
1179
+ }
1180
+ function list(v, path) {
1181
+ if (v === void 0) return [];
1182
+ if (!Array.isArray(v)) return refuse(TickOutFault.BAD_SHAPE, path);
1183
+ return v;
1184
+ }
1185
+ function parsed(raw, maxChars) {
1186
+ if (typeof raw !== "string") return raw;
1187
+ if (raw.length > maxChars) return refuse(TickOutFault.TOO_LARGE, "$");
1188
+ try {
1189
+ return JSON.parse(raw);
1190
+ } catch {
1191
+ return refuse(TickOutFault.NOT_JSON, "$");
1192
+ }
1193
+ }
1194
+ var finiteOnly = (path) => (_key, v) => typeof v === "number" && !Number.isFinite(v) ? refuse(TickOutFault.NON_FINITE, path) : v;
1195
+ function commandList(v, path, max) {
1196
+ const items = list(v, path);
1197
+ if (items.length > max) return refuse(TickOutFault.TOO_MANY_COMMANDS, path);
1198
+ items.forEach((item, i) => {
1199
+ if (!isRecord3(item) || Object.keys(item).length !== 1) refuse(TickOutFault.BAD_SHAPE, `${path}[${i}]`);
1200
+ });
1201
+ const text = JSON.stringify(items, finiteOnly(path));
1202
+ return { text, items: JSON.parse(text) };
1203
+ }
1204
+ function physicsOut(value2, physics) {
1205
+ const out = {};
1206
+ let bytes = 0;
1207
+ if (value2.phys !== void 0) {
1208
+ const phys = commandList(value2.phys, "$.phys", AuthorityLimits.MAX_PHYS_COMMANDS);
1209
+ bytes += phys.text.length;
1210
+ out.phys = phys.items;
1211
+ }
1212
+ if (value2.physQ !== void 0) {
1213
+ if (physics.queries === null) return refuse(TickOutFault.BAD_SHAPE, "$.physQ");
1214
+ const physQ = commandList(value2.physQ, "$.physQ", physics.queries);
1215
+ bytes += physQ.text.length;
1216
+ out.physQ = physQ.items;
1217
+ }
1218
+ if (bytes > AuthorityLimits.MAX_PHYS_BYTES) return refuse(TickOutFault.TOO_LARGE, "$.phys");
1219
+ const { phys: _phys, physQ: _physQ, ...rest } = value2;
1220
+ if (JSON.stringify(rest).length > AuthorityLimits.MAX_OUTPUT_BYTES) return refuse(TickOutFault.TOO_LARGE, "$");
1221
+ return out;
1222
+ }
1223
+ function checkBodyEntities(value2, out, bodies) {
1224
+ const raw = value2.up;
1225
+ const removed = new Set(out.rm);
1226
+ out.up.forEach((e, i) => {
1227
+ if (!bodies.has(e.id) || removed.has(e.id)) return;
1228
+ const fields = raw?.[i];
1229
+ for (const field of HOST_BODY_FIELDS) {
1230
+ if (fields[field] !== void 0) refuse(TickOutFault.BAD_SHAPE, `$.up[${i}].${field}`);
1231
+ }
1232
+ if ((e.t0 ?? 0) > 0) refuse(TickOutFault.BAD_SHAPE, `$.up[${i}].t0`);
1233
+ });
1234
+ }
1235
+ function tickOutOf(value2, physics) {
1236
+ if (!isRecord3(value2)) return refuse(TickOutFault.BAD_SHAPE, "$");
1237
+ const up = list(value2.up, "$.up");
1238
+ const rm = list(value2.rm, "$.rm");
1239
+ const ev = list(value2.ev, "$.ev");
1240
+ if (up.length > AuthorityLimits.MAX_ENTITIES) return refuse(TickOutFault.TOO_MANY_ENTITIES, "$.up");
1241
+ if (rm.length > AuthorityLimits.MAX_ENTITIES) return refuse(TickOutFault.TOO_MANY_ENTITIES, "$.rm");
1242
+ if (ev.length > AuthorityLimits.MAX_EVENTS_PER_TICK) return refuse(TickOutFault.TOO_MANY_EVENTS, "$.ev");
1243
+ const out = {
1244
+ up: up.map((e, i) => entityOut(e, `$.up[${i}]`)),
1245
+ rm: rm.map((id, i) => entityId(id, `$.rm[${i}]`)),
1246
+ ev: ev.map((e, i) => eventOut(e, `$.ev[${i}]`))
1247
+ };
1248
+ if (value2.lifts !== void 0) out.lifts = liftStartsOut(value2.lifts);
1249
+ if (physics === void 0) return out;
1250
+ checkBodyEntities(value2, out, physics.bodies);
1251
+ return { ...out, ...physicsOut(value2, physics) };
1252
+ }
1253
+ var LIFT_START_MAX = 2 ** 31 - 1;
1254
+ function liftStartsOut(v) {
1255
+ const path = "$.lifts";
1256
+ if (!isRecord3(v)) return refuse(TickOutFault.BAD_SHAPE, path);
1257
+ const entries = Object.entries(v);
1258
+ if (entries.length > LIFTS_MAX) return refuse(TickOutFault.BAD_SHAPE, path);
1259
+ const out = {};
1260
+ for (const [id, start] of entries) {
1261
+ if (!isLiftId(id)) return refuse(TickOutFault.BAD_SHAPE, path);
1262
+ if (typeof start !== "number" || !Number.isInteger(start) || start < 0 || start > LIFT_START_MAX) return refuse(TickOutFault.BAD_SHAPE, `${path}.${id}`);
1263
+ out[id] = start;
1264
+ }
1265
+ return out;
1266
+ }
1267
+ function entityCountAfter(room, out) {
1268
+ const added = new Set(out.up.filter((e) => !room.has(e.id)).map((e) => e.id));
1269
+ const updated = new Set(out.up.map((e) => e.id));
1270
+ const removed = new Set(out.rm.filter((id) => room.has(id) && !updated.has(id)));
1271
+ return room.size + added.size - removed.size;
1272
+ }
1273
+ function checkProjectileOwners(room, out) {
1274
+ const merged = /* @__PURE__ */ new Map();
1275
+ out.up.forEach((e, i) => {
1276
+ const current = merged.get(e.id) ?? room?.get?.(e.id);
1277
+ const next = { t0: e.t0 ?? current?.t0 ?? 0, owner: e.owner ?? current?.owner ?? "" };
1278
+ if (next.t0 > 0 && next.owner !== "") refuse(TickOutFault.BAD_SHAPE, `$.up[${i}].owner`);
1279
+ merged.set(e.id, next);
1280
+ });
1281
+ }
1282
+ function roomTickOutOf(value2, room, physics) {
1283
+ const out = tickOutOf(value2, physics);
1284
+ if (room !== void 0 && entityCountAfter(room, out) > AuthorityLimits.MAX_ROOM_ENTITIES) {
1285
+ return refuse(TickOutFault.TOO_MANY_ENTITIES, "$.up");
1286
+ }
1287
+ checkProjectileOwners(room, out);
1288
+ return out;
1289
+ }
1290
+ var EMPTY_ROOM = { size: 0, has: () => false };
1291
+ function presetOf(v, path) {
1292
+ if (v === null || v === void 0) return null;
1293
+ if (!isRecord3(v) || !isPresetName(v.name)) return refuse(TickOutFault.BAD_CONFIG, path);
1294
+ if (!presetParamsValid(v.name, v.params)) return refuse(TickOutFault.BAD_CONFIG, `${path}.params`);
1295
+ if (definitionOf(v.name)?.world === true && v.params?.world === void 0) {
1296
+ return refuse(TickOutFault.BAD_CONFIG, `${path}.params.world`);
1297
+ }
1298
+ return v.params === void 0 ? { name: v.name } : { name: v.name, params: { ...v.params } };
1299
+ }
1300
+ function configOf(v, admission) {
1301
+ if (!isRecord3(v)) return refuse(TickOutFault.BAD_CONFIG, "$.config");
1302
+ const tickHz = TICK_RATES.find((hz) => hz === v.tickHz);
1303
+ if (tickHz === void 0) return refuse(TickOutFault.BAD_CONFIG, "$.config.tickHz");
1304
+ const maxPlayers = v.maxPlayers;
1305
+ const playersOk = typeof maxPlayers === "number" && Number.isInteger(maxPlayers) && maxPlayers >= 1 && maxPlayers <= AuthorityLimits.MAX_PLAYERS;
1306
+ if (!playersOk) return refuse(TickOutFault.BAD_CONFIG, "$.config.maxPlayers");
1307
+ let lagComp = null;
1308
+ if (v.lagComp !== null && v.lagComp !== void 0) {
1309
+ if (!isRecord3(v.lagComp) || !isMask(v.lagComp.buttons)) return refuse(TickOutFault.BAD_CONFIG, "$.config.lagComp");
1310
+ lagComp = { buttons: v.lagComp.buttons };
1311
+ }
1312
+ const preset = presetOf(v.preset, "$.config.preset");
1313
+ const physics = checkResolvedPhysics(v.physics, preset, admission?.ceiling ?? null);
1314
+ if (!physics.ok) return refuse(TickOutFault.BAD_CONFIG, physics.path);
1315
+ if (tickHz === PHYSICS_ONLY_TICK_HZ && physics.value === null) return refuse(TickOutFault.BAD_CONFIG, "$.config.tickHz");
1316
+ const contacts = contactsOf(v.contacts, preset);
1317
+ const inputs = inputsOf(v.inputs, contacts);
1318
+ const lifts = liftsOf(v.lifts, preset, tickHz);
1319
+ const lifetime = lifetimeOf(v.lifetime);
1320
+ return {
1321
+ tickHz,
1322
+ maxPlayers,
1323
+ preset,
1324
+ lagComp,
1325
+ physics: physics.value,
1326
+ ...contacts === null ? {} : { contacts },
1327
+ ...inputs === null ? {} : { inputs },
1328
+ ...lifts === null ? {} : { lifts },
1329
+ ...lifetime === null ? {} : { lifetime }
1330
+ };
1331
+ }
1332
+ function lifetimeOf(v) {
1333
+ try {
1334
+ return resolveLifetime(v);
1335
+ } catch (e) {
1336
+ if (e instanceof LifetimeConfigError) return refuse(TickOutFault.BAD_CONFIG, e.path);
1337
+ throw e;
1338
+ }
1339
+ }
1340
+ function inputsOf(v, contacts) {
1341
+ try {
1342
+ return resolveInputConfig(v, contacts?.preset ?? null);
1343
+ } catch (e) {
1344
+ if (e instanceof InputConfigError) return refuse(TickOutFault.BAD_CONFIG, e.path);
1345
+ throw e;
1346
+ }
1347
+ }
1348
+ function liftsOf(v, preset, tickHz) {
1349
+ if (v === null || v === void 0) return null;
1350
+ const params = preset === null ? null : { name: preset.name, params: resolvePresetParams(preset.name, preset.params) };
1351
+ try {
1352
+ return resolveLifts(v, params, tickHz);
1353
+ } catch (e) {
1354
+ if (e instanceof LiftsConfigError) return refuse(TickOutFault.BAD_CONFIG, e.message);
1355
+ throw e;
1356
+ }
1357
+ }
1358
+ function contactsOf(v, preset) {
1359
+ if (v === null || v === void 0) return null;
1360
+ const params = preset === null ? null : { name: preset.name, params: resolvePresetParams(preset.name, preset.params) };
1361
+ try {
1362
+ return resolveContactConfig(v, params);
1363
+ } catch (e) {
1364
+ if (e instanceof ContactConfigError) return refuse(TickOutFault.BAD_CONFIG, e.path);
1365
+ throw e;
1366
+ }
1367
+ }
1368
+ function gate(raw, build, physics) {
1369
+ try {
1370
+ const maxChars = physics ? AuthorityLimits.MAX_PHYSICS_OUTPUT_BYTES : AuthorityLimits.MAX_OUTPUT_BYTES;
1371
+ const value2 = build(parsed(raw, maxChars));
1372
+ if (!physics && typeof raw !== "string" && JSON.stringify(value2).length > AuthorityLimits.MAX_OUTPUT_BYTES) {
1373
+ return { ok: false, fault: TickOutFault.TOO_LARGE, path: "$" };
1374
+ }
1375
+ return { ok: true, value: value2 };
1376
+ } catch (err) {
1377
+ if (err instanceof Refusal) return { ok: false, fault: err.fault, path: err.path };
1378
+ return { ok: false, fault: TickOutFault.BAD_SHAPE, path: "$" };
1379
+ }
1380
+ }
1381
+ function validateTickOut(raw, room, physics) {
1382
+ const wire = physics === void 0 ? void 0 : { queries: physics.queriesPerStep, bodies: physics.bodies };
1383
+ return gate(raw, (value2) => roomTickOutOf(value2, room, wire), wire !== void 0);
1384
+ }
1385
+ var NO_BODIES = { has: () => false };
1386
+ function shotCountOf(value2) {
1387
+ if (value2 === null || typeof value2 !== "object") return void 0;
1388
+ const { solids, opsPerRay } = value2;
1389
+ return Number.isInteger(solids) && Number.isInteger(opsPerRay) ? { solids, opsPerRay } : void 0;
1390
+ }
1391
+ function worldNamesOf(value2) {
1392
+ if (!Array.isArray(value2) || value2.length > AuthorityRoomLimits.MAX_WORLDS) return void 0;
1393
+ return value2.every(validMatchKey) ? value2 : void 0;
1394
+ }
1395
+ function validateInitOut(raw, physics) {
1396
+ const wire = physics === void 0 ? void 0 : { queries: null, bodies: NO_BODIES };
1397
+ return gate(
1398
+ raw,
1399
+ (value2) => {
1400
+ const out = roomTickOutOf(value2, EMPTY_ROOM, wire);
1401
+ const { config, worlds, shotWorld, noShotWorld } = value2;
1402
+ if (typeof noShotWorld === "string") return refuse(TickOutFault.NO_SHOT_WORLD, `$.shotWorld: ${noShotWorld}`);
1403
+ const named = worldNamesOf(worlds);
1404
+ const counted = shotCountOf(shotWorld);
1405
+ return { ...out, config: configOf(config, physics), ...named === void 0 ? {} : { worlds: named }, ...counted === void 0 ? {} : { shotWorld: counted } };
1406
+ },
1407
+ wire !== void 0
1408
+ );
1409
+ }
1410
+
1411
+ // src/authority/fast-transport.ts
1412
+ var FAST_VERSION = 1;
1413
+ var FAST_MAX_DATAGRAM_BYTES = 1200;
1414
+ var FAST_KEY_BYTES = 8;
1415
+ var INPUT_REDUNDANCY_MS = 170;
1416
+ var INPUT_REDUNDANCY_MIN = 3;
1417
+ var INPUT_REDUNDANCY_MAX = 32;
1418
+ var FAST_MAX_ACTIONS = 8;
1419
+ var FAST_GAP_FILL_MS = 250;
1420
+ var FAST_SEQ_AHEAD_MS = 5e3;
1421
+ var FAST_SNAPSHOT_HISTORY = 32;
1422
+ var FAST_MAX_FRAGMENTS = 32;
1423
+ var FAST_REASSEMBLY_SLOTS = 4;
1424
+ var FAST_MAX_RECORD_BYTES = 1024;
1425
+ var FAST_HANDSHAKE_MS = 1e3;
1426
+ var FAST_STALL_FALLBACK_MS = 2e3;
1427
+ var FAST_KEEPALIVE_MS = 250;
1428
+ var FAST_KEEPALIVE_BIND_MS = 50;
1429
+ var FAST_ACK_EAGER_RECORDS = 32;
1430
+ var FAST_ACK_EAGER_GAP_MS = 20;
1431
+ var FAST_CHALLENGE_JUMP = 65536;
1432
+ var FAST_FRAME_GAP_MS = 250;
1433
+ var FAST_BIND_REV = 2;
1434
+ var FAST_RETURN_PROOF = 3;
1435
+ var FAST_RETRY_BASE_MS = 1e3;
1436
+ var FAST_RETRY_MAX_MS = 6e4;
1437
+ var FAST_RETURN_STABLE_MS = 3e4;
1438
+ function fastRetryDelayMs(attempt, unit) {
1439
+ const full = Math.min(FAST_RETRY_MAX_MS, FAST_RETRY_BASE_MS * 2 ** Math.min(attempt, 16));
1440
+ return full / 2 + full / 2 * unit;
1441
+ }
1442
+ var FastMessage = {
1443
+ /** Server → client: `{ sid, key, url }`, the seat's binding. */
1444
+ Bind: "__fast",
1445
+ /** Client → server: `{ last }`, the last seq the client sent by datagram; its inputs go by
1446
+ * `room.input()` from now on. */
1447
+ Off: "__fast:off",
1448
+ /** Client → server: `{}`, a seat off the channel asks for a fresh binding (§17.12 *Return to the channel*). */
1449
+ Again: "__fast:again",
1450
+ /** Client → server: `{ last }`, the last seq it sent by WebSocket; its inputs go by datagram from now on. */
1451
+ On: "__fast:on"
1452
+ };
1453
+ var FastKind = { Uplink: 1, Snapshot: 2 };
1454
+ var FastFault = {
1455
+ Oversized: "oversized",
1456
+ Truncated: "truncated",
1457
+ Trailing: "trailing",
1458
+ Version: "version",
1459
+ Kind: "kind",
1460
+ Session: "session",
1461
+ Key: "key",
1462
+ Count: "count",
1463
+ Fresh: "fresh",
1464
+ Actions: "actions",
1465
+ Flags: "flags",
1466
+ Order: "order",
1467
+ Field: "field",
1468
+ Ahead: "ahead",
1469
+ Fragment: "fragment",
1470
+ Mismatch: "mismatch",
1471
+ Duplicate: "duplicate",
1472
+ Keyframe: "keyframe",
1473
+ Body: "body",
1474
+ Base: "base",
1475
+ Stale: "stale"
1476
+ };
1477
+ var AcceptKind = {
1478
+ /** First sent in the datagram that brought it. */
1479
+ Fresh: "fresh",
1480
+ /** Sent first in an earlier datagram that was lost; carried again by redundancy or as an action. */
1481
+ Recovered: "recovered",
1482
+ /** No datagram the server got carried it: the last input held stands in for it. */
1483
+ Fill: "fill"
1484
+ };
1485
+ var UPLINK_HEADER = 29;
1486
+ var SNAPSHOT_HEADER = 29;
1487
+ var PAYLOAD = 24;
1488
+ var SLOT_SAME = 1;
1489
+ var SLOT_TICK_SAME = 2;
1490
+ var SLOT_TICK_NEXT = 4;
1491
+ var SLOT_FLAGS = SLOT_SAME | SLOT_TICK_SAME | SLOT_TICK_NEXT;
1492
+ var BACK_NONE = 255;
1493
+ var ACTION_HEAD = 10;
1494
+ var SLOT_MAX = 4 + PAYLOAD;
1495
+ var UINT32_MAX = 4294967295;
1496
+ var TICK_MOD = 65536;
1497
+ var KEYFRAME = 1;
1498
+ var NEUTRAL = { moveX: 0, moveY: 0, moveZ: 0, yaw: 0, pitch: 0, buttons: 0 };
1499
+ var ok = (value2) => ({ ok: true, value: value2 });
1500
+ var fail = (fault) => ({ ok: false, fault });
1501
+ var ticksOf = (ms, tickHz) => Math.ceil(ms * (Number.isFinite(tickHz) && tickHz > 0 ? tickHz : 0) / 1e3);
1502
+ var isUint = (v, max) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= max;
1503
+ function inputRedundancyTicks(tickHz) {
1504
+ return Math.min(INPUT_REDUNDANCY_MAX, Math.max(INPUT_REDUNDANCY_MIN, ticksOf(INPUT_REDUNDANCY_MS, tickHz)));
1505
+ }
1506
+ function uplinkMaxBytes(tickHz) {
1507
+ return UPLINK_HEADER + FAST_MAX_ACTIONS * (ACTION_HEAD + PAYLOAD) + inputRedundancyTicks(tickHz) * SLOT_MAX;
1508
+ }
1509
+ var gapFillMax = (tickHz) => ticksOf(FAST_GAP_FILL_MS, tickHz);
1510
+ var seqAheadMax = (tickHz) => ticksOf(FAST_SEQ_AHEAD_MS, tickHz);
1511
+ function toHex(bytes) {
1512
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
1513
+ }
1514
+ function fromHex(hex) {
1515
+ if (!/^(?:[0-9a-fA-F]{2})*$/.test(hex)) return new Uint8Array(0);
1516
+ return Uint8Array.from(hex.match(/../g) ?? [], (h) => Number.parseInt(h, 16));
1517
+ }
1518
+ var isKey = (key) => typeof key === "string" && key.length === 2 * FAST_KEY_BYTES && /^[0-9a-f]+$/.test(key);
1519
+ var isPress = (previous, input) => (input.buttons & ~(previous?.buttons ?? 0)) !== 0;
1520
+ var validPayload = (p) => [p.moveX, p.moveY, p.moveZ, p.yaw, p.pitch].every((x) => typeof x === "number" && Number.isFinite(Math.fround(x))) && isUint(p.buttons, UINT32_MAX);
1521
+ var validInput = (s) => isUint(s.seq, UINT32_MAX) && s.seq >= 1 && isUint(s.tick, TICK_MOD - 1) && isUint(s.render, UINT32_MAX) && validPayload(s.input);
1522
+ var windowRender = (window) => window.reduce((m, s) => Math.max(m, s.render), 0);
1523
+ var backOf = (render, s) => s.render === 0 || render - s.render >= BACK_NONE ? BACK_NONE : render - s.render;
1524
+ var renderOf = (render, back) => back === BACK_NONE || render - back <= 0 ? 0 : render - back;
1525
+ function validUplink(u) {
1526
+ const { window, actions } = u;
1527
+ if (!isUint(u.sid, UINT32_MAX) || !isKey(u.key) || !isUint(u.ackSnap, UINT32_MAX)) return false;
1528
+ if (window.length === 0) return u.fresh === 0 && actions.length === 0;
1529
+ if (window.length > INPUT_REDUNDANCY_MAX || actions.length > FAST_MAX_ACTIONS) return false;
1530
+ if (!isUint(u.fresh, window.length) || u.fresh < 1) return false;
1531
+ if (![...actions, ...window].every(validInput)) return false;
1532
+ const contiguous = window.every((s, i) => i === 0 || s.seq === window[i - 1].seq + 1);
1533
+ const ascending = actions.every((s, i) => s.seq < (i + 1 < actions.length ? actions[i + 1].seq : window[0].seq));
1534
+ return contiguous && ascending;
1535
+ }
1536
+ function writePayload(view, at, p) {
1537
+ view.setFloat32(at, p.moveX, true);
1538
+ view.setFloat32(at + 4, p.moveY, true);
1539
+ view.setFloat32(at + 8, p.moveZ, true);
1540
+ view.setFloat32(at + 12, p.yaw, true);
1541
+ view.setFloat32(at + 16, p.pitch, true);
1542
+ view.setUint32(at + 20, p.buttons, true);
1543
+ return at + PAYLOAD;
1544
+ }
1545
+ function readPayload(view, at) {
1546
+ return {
1547
+ moveX: view.getFloat32(at, true),
1548
+ moveY: view.getFloat32(at + 4, true),
1549
+ moveZ: view.getFloat32(at + 8, true),
1550
+ yaw: view.getFloat32(at + 12, true),
1551
+ pitch: view.getFloat32(at + 16, true),
1552
+ buttons: view.getUint32(at + 20, true)
1553
+ };
1554
+ }
1555
+ var payloadBits = (p) => {
1556
+ const b = new Uint8Array(PAYLOAD);
1557
+ writePayload(new DataView(b.buffer), 0, p);
1558
+ return toHex(b);
1559
+ };
1560
+ function slotFlags(previous, s) {
1561
+ if (previous === null) return 0;
1562
+ let flags = payloadBits(previous.input) === payloadBits(s.input) ? SLOT_SAME : 0;
1563
+ if (s.tick === previous.tick) flags |= SLOT_TICK_SAME;
1564
+ else if (s.tick === (previous.tick + 1) % TICK_MOD) flags |= SLOT_TICK_NEXT;
1565
+ return flags;
1566
+ }
1567
+ function encodeUplink(u) {
1568
+ if (!validUplink(u)) return fail(FastFault.Field);
1569
+ const out = new Uint8Array(FAST_MAX_DATAGRAM_BYTES);
1570
+ const view = new DataView(out.buffer);
1571
+ out[0] = FAST_VERSION;
1572
+ out[1] = FastKind.Uplink;
1573
+ view.setUint32(2, u.sid, true);
1574
+ out.set(fromHex(u.key), 6);
1575
+ view.setUint32(14, u.ackSnap, true);
1576
+ view.setUint32(18, u.window.at(-1)?.seq ?? 0, true);
1577
+ out[22] = u.window.length;
1578
+ out[23] = u.fresh;
1579
+ out[24] = u.actions.length;
1580
+ const render = windowRender(u.window);
1581
+ view.setUint32(25, render, true);
1582
+ let at = UPLINK_HEADER;
1583
+ for (const a of u.actions) {
1584
+ view.setUint32(at, a.seq, true);
1585
+ view.setUint16(at + 4, a.tick, true);
1586
+ view.setUint32(at + 6, a.render, true);
1587
+ at = writePayload(view, at + ACTION_HEAD, a.input);
1588
+ }
1589
+ u.window.forEach((s, i) => {
1590
+ const flags = slotFlags(i === 0 ? null : u.window[i - 1], s);
1591
+ out[at] = flags;
1592
+ at += 1;
1593
+ if ((flags & (SLOT_TICK_SAME | SLOT_TICK_NEXT)) === 0) {
1594
+ view.setUint16(at, s.tick, true);
1595
+ at += 2;
1596
+ }
1597
+ out[at] = backOf(render, s);
1598
+ at += 1;
1599
+ if ((flags & SLOT_SAME) === 0) at = writePayload(view, at, s.input);
1600
+ });
1601
+ return ok(out.slice(0, at));
1602
+ }
1603
+ function frame(bytes, kind, header) {
1604
+ if (bytes.length > FAST_MAX_DATAGRAM_BYTES) return FastFault.Oversized;
1605
+ if (bytes.length < 2) return FastFault.Truncated;
1606
+ if (bytes[0] !== FAST_VERSION) return FastFault.Version;
1607
+ if (bytes[1] !== kind) return FastFault.Kind;
1608
+ return bytes.length < header ? FastFault.Truncated : null;
1609
+ }
1610
+ function decodeUplink(bytes, binding) {
1611
+ const framed = frame(bytes, FastKind.Uplink, UPLINK_HEADER);
1612
+ if (framed !== null) return fail(framed);
1613
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1614
+ const sid = view.getUint32(2, true);
1615
+ if (sid !== binding.sid) return fail(FastFault.Session);
1616
+ const key = toHex(bytes.subarray(6, 6 + FAST_KEY_BYTES));
1617
+ if (key !== binding.key) return fail(FastFault.Key);
1618
+ const newest = view.getUint32(18, true);
1619
+ const count = bytes[22];
1620
+ const fresh = bytes[23];
1621
+ const actionCount = bytes[24];
1622
+ const ackSnap = view.getUint32(14, true);
1623
+ if (count === 0 && newest === 0 && fresh === 0 && actionCount === 0 && view.getUint32(25, true) === 0) {
1624
+ if (bytes.length !== UPLINK_HEADER) return fail(FastFault.Trailing);
1625
+ return ok({ sid, key, ackSnap, fresh: 0, actions: [], window: [] });
1626
+ }
1627
+ if (count < 1 || count > INPUT_REDUNDANCY_MAX || newest < count) return fail(FastFault.Count);
1628
+ if (fresh < 1 || fresh > count) return fail(FastFault.Fresh);
1629
+ if (actionCount > FAST_MAX_ACTIONS) return fail(FastFault.Actions);
1630
+ const first = newest - count + 1;
1631
+ const read = readBody(view, actionCount, count, first, view.getUint32(25, true));
1632
+ if (!read.ok) return read;
1633
+ return ok({ sid, key, ackSnap, fresh, actions: read.value.actions, window: read.value.window });
1634
+ }
1635
+ function readBody(view, actionCount, count, first, render) {
1636
+ let at = UPLINK_HEADER;
1637
+ const actions = [];
1638
+ for (let i = 0; i < actionCount; i += 1) {
1639
+ if (at + ACTION_HEAD + PAYLOAD > view.byteLength) return fail(FastFault.Truncated);
1640
+ const seq = view.getUint32(at, true);
1641
+ if (seq < 1 || seq >= first || i > 0 && seq <= actions[i - 1].seq) return fail(FastFault.Order);
1642
+ actions.push({ seq, tick: view.getUint16(at + 4, true), render: view.getUint32(at + 6, true), input: readPayload(view, at + ACTION_HEAD) });
1643
+ at += ACTION_HEAD + PAYLOAD;
1644
+ }
1645
+ const window = [];
1646
+ for (let i = 0; i < count; i += 1) {
1647
+ const slot = readSlot(view, at, i === 0 ? null : window[i - 1], first + i, render);
1648
+ if (!slot.ok) return slot;
1649
+ window.push(slot.value.input);
1650
+ at = slot.value.at;
1651
+ }
1652
+ return at === view.byteLength ? ok({ actions, window }) : fail(FastFault.Trailing);
1653
+ }
1654
+ function readSlot(view, start, previous, seq, render) {
1655
+ if (start >= view.byteLength) return fail(FastFault.Truncated);
1656
+ const flags = view.getUint8(start);
1657
+ const tickBits = flags & (SLOT_TICK_SAME | SLOT_TICK_NEXT);
1658
+ const badFlags = (flags & ~SLOT_FLAGS) !== 0 || tickBits === (SLOT_TICK_SAME | SLOT_TICK_NEXT) || previous === null && flags !== 0;
1659
+ if (badFlags) return fail(FastFault.Flags);
1660
+ let at = start + 1;
1661
+ let tick = 0;
1662
+ if (previous !== null && tickBits === SLOT_TICK_SAME) tick = previous.tick;
1663
+ else if (previous !== null && tickBits === SLOT_TICK_NEXT) tick = (previous.tick + 1) % TICK_MOD;
1664
+ else {
1665
+ if (at + 2 > view.byteLength) return fail(FastFault.Truncated);
1666
+ tick = view.getUint16(at, true);
1667
+ at += 2;
1668
+ }
1669
+ if (at + 1 > view.byteLength) return fail(FastFault.Truncated);
1670
+ const slotRender = renderOf(render, view.getUint8(at));
1671
+ at += 1;
1672
+ if (previous !== null && (flags & SLOT_SAME) !== 0) return ok({ input: { seq, tick, render: slotRender, input: { ...previous.input } }, at });
1673
+ if (at + PAYLOAD > view.byteLength) return fail(FastFault.Truncated);
1674
+ return ok({ input: { seq, tick, render: slotRender, input: readPayload(view, at) }, at: at + PAYLOAD });
1675
+ }
1676
+ var wholeMs = (x) => Math.floor(x + 0.5);
1677
+ function renderStamp(serverNow, renderDelay, rtt) {
1678
+ if (serverNow === null || !Number.isFinite(serverNow)) return 0;
1679
+ const now = Math.min(UINT32_MAX, Math.max(0, wholeMs(serverNow)));
1680
+ const lead = renderDelay + (Number.isFinite(rtt) ? rtt : 0) / 2;
1681
+ const delta = Number.isFinite(lead) ? Math.min(65535, Math.max(0, wholeMs(lead))) : 0;
1682
+ return now > delta ? now - delta : 0;
1683
+ }
1684
+ function uplinkFor(sent, o) {
1685
+ const window = sent.slice(-inputRedundancyTicks(o.tickHz));
1686
+ const first = window.length > 0 ? window[0].seq : Number.POSITIVE_INFINITY;
1687
+ const actions = sent.filter((s, i) => s.seq > o.inputAck && s.seq < first && isPress(i > 0 ? sent[i - 1].input : null, s.input)).slice(0, FAST_MAX_ACTIONS);
1688
+ return { sid: o.sid, key: o.key, ackSnap: o.ackSnap, fresh: Math.min(o.fresh, window.length), actions, window };
1689
+ }
1690
+ function acceptUplink(seat, u, o) {
1691
+ const newestSlot = u.window.at(-1);
1692
+ if (newestSlot === void 0) {
1693
+ const moved = u.ackSnap > seat.ackSnap && u.ackSnap <= o.snapSent;
1694
+ return { seat: moved ? { ...seat, ackSnap: u.ackSnap } : seat, accepted: [], skipped: 0 };
1695
+ }
1696
+ const newest = newestSlot.seq;
1697
+ if (newest - seat.last > seqAheadMax(o.tickHz)) return { seat, accepted: [], skipped: 0, fault: FastFault.Ahead };
1698
+ const firstFresh = newest - u.fresh + 1;
1699
+ const accepted = [];
1700
+ let last = seat.last;
1701
+ let held = seat.input;
1702
+ let skipped = 0;
1703
+ for (const s of [...u.actions, ...u.window]) {
1704
+ if (s.seq <= last) continue;
1705
+ const gap = s.seq - last - 1;
1706
+ if (gap > gapFillMax(o.tickHz)) skipped += gap;
1707
+ else {
1708
+ for (let g = last + 1; g < s.seq; g += 1) {
1709
+ accepted.push({ seq: g, tick: ((s.tick - (s.seq - g)) % TICK_MOD + TICK_MOD) % TICK_MOD, render: s.render, input: { ...held ?? NEUTRAL }, kind: AcceptKind.Fill });
1710
+ }
1711
+ }
1712
+ const input = sanitizeInput({ ...s.input });
1713
+ accepted.push({ seq: s.seq, tick: s.tick, render: s.render, input, kind: s.seq >= firstFresh ? AcceptKind.Fresh : AcceptKind.Recovered });
1714
+ last = s.seq;
1715
+ held = input;
1716
+ }
1717
+ const ackSnap = u.ackSnap > seat.ackSnap && u.ackSnap <= o.snapSent ? u.ackSnap : seat.ackSnap;
1718
+ if (accepted.length === 0 && ackSnap === seat.ackSnap) return { seat, accepted, skipped };
1719
+ return { seat: { last, input: held, ackSnap }, accepted, skipped };
1720
+ }
1721
+ var sameBytes = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
1722
+ var byKey = (records) => [...records].sort((a, b) => a.key - b.key);
1723
+ function diffSnapshot(base, next) {
1724
+ if (base === null) return { removed: [], records: byKey(next) };
1725
+ const old = new Map(base.map((r) => [r.key, r.bytes]));
1726
+ const keys = new Set(next.map((r) => r.key));
1727
+ const removed = base.filter((r) => !keys.has(r.key)).map((r) => r.key).sort((a, b) => a - b);
1728
+ const changed = next.filter((r) => {
1729
+ const before = old.get(r.key);
1730
+ return before === void 0 || !sameBytes(before, r.bytes);
1731
+ });
1732
+ return { removed, records: byKey(changed) };
1733
+ }
1734
+ var deltaBase = (held, ackSnap) => ackSnap > 0 && held.includes(ackSnap) ? ackSnap : 0;
1735
+ var bodyScratch = new Uint8Array(4096);
1736
+ function bodyBytes(body) {
1737
+ const valid = body.removed.every((k) => isUint(k, 65535)) && body.records.every((r) => isUint(r.key, 65535) && r.bytes.length <= FAST_MAX_RECORD_BYTES);
1738
+ if (!valid || body.removed.length > 65535 || body.records.length > 65535) return null;
1739
+ const size = 4 + 2 * body.removed.length + body.records.reduce((n, r) => n + 4 + r.bytes.length, 0);
1740
+ if (size > bodyScratch.length) bodyScratch = new Uint8Array(size * 2);
1741
+ const out = bodyScratch.subarray(0, size);
1742
+ const view = new DataView(out.buffer, out.byteOffset, out.byteLength);
1743
+ view.setUint16(0, body.removed.length, true);
1744
+ let at = 2;
1745
+ for (const k of body.removed) {
1746
+ view.setUint16(at, k, true);
1747
+ at += 2;
1748
+ }
1749
+ view.setUint16(at, body.records.length, true);
1750
+ at += 2;
1751
+ for (const r of body.records) {
1752
+ view.setUint16(at, r.key, true);
1753
+ view.setUint16(at + 2, r.bytes.length, true);
1754
+ out.set(r.bytes, at + 4);
1755
+ at += 4 + r.bytes.length;
1756
+ }
1757
+ return out;
1758
+ }
1759
+ var validHead = (h) => isUint(h.sid, UINT32_MAX) && isUint(h.snap, UINT32_MAX) && h.snap >= 1 && isUint(h.base, UINT32_MAX) && h.base < h.snap && isUint(h.tick, UINT32_MAX) && isUint(h.inputAck, UINT32_MAX) && isUint(h.sent, UINT32_MAX);
1760
+ function encodeSnapshot(head, body) {
1761
+ const bytes = bodyBytes(body);
1762
+ const keyframeBody = head.base !== 0 || body.removed.length === 0;
1763
+ if (bytes === null || !validHead(head) || !keyframeBody) return fail(FastFault.Field);
1764
+ const room = FAST_MAX_DATAGRAM_BYTES - SNAPSHOT_HEADER;
1765
+ const frags = Math.max(1, Math.ceil(bytes.length / room));
1766
+ if (frags > FAST_MAX_FRAGMENTS) return fail(FastFault.Oversized);
1767
+ const out = [];
1768
+ for (let i = 0; i < frags; i += 1) {
1769
+ const chunk = bytes.subarray(i * room, (i + 1) * room);
1770
+ const d = new Uint8Array(SNAPSHOT_HEADER + chunk.length);
1771
+ const view = new DataView(d.buffer);
1772
+ d[0] = FAST_VERSION;
1773
+ d[1] = FastKind.Snapshot;
1774
+ view.setUint32(2, head.sid, true);
1775
+ view.setUint32(6, head.snap, true);
1776
+ view.setUint32(10, head.base, true);
1777
+ view.setUint32(14, head.tick, true);
1778
+ view.setUint32(18, head.inputAck, true);
1779
+ d[22] = head.base === 0 ? KEYFRAME : 0;
1780
+ d[23] = i;
1781
+ d[24] = frags;
1782
+ view.setUint32(25, head.sent, true);
1783
+ d.set(chunk, SNAPSHOT_HEADER);
1784
+ out.push(d);
1785
+ }
1786
+ return ok(out);
1787
+ }
1788
+ function decodeFragment(bytes, sid) {
1789
+ const framed = frame(bytes, FastKind.Snapshot, SNAPSHOT_HEADER);
1790
+ if (framed !== null) return fail(framed);
1791
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1792
+ if (view.getUint32(2, true) !== sid) return fail(FastFault.Session);
1793
+ const snap = view.getUint32(6, true);
1794
+ const base = view.getUint32(10, true);
1795
+ const flags = bytes[22];
1796
+ const frag = bytes[23];
1797
+ const frags = bytes[24];
1798
+ if (frags < 1 || frags > FAST_MAX_FRAGMENTS || frag >= frags) return fail(FastFault.Fragment);
1799
+ const keyframeAgrees = (flags & ~KEYFRAME) === 0 && flags === KEYFRAME === (base === 0);
1800
+ if (!keyframeAgrees || snap < 1 || base >= snap) return fail(FastFault.Keyframe);
1801
+ const chunk = bytes.slice(SNAPSHOT_HEADER);
1802
+ return ok({ snap, base, tick: view.getUint32(14, true), inputAck: view.getUint32(18, true), sent: view.getUint32(25, true), keyframe: flags === KEYFRAME, frag, frags, chunk });
1803
+ }
1804
+ function parseBody(bytes) {
1805
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1806
+ if (bytes.length < 2) return fail(FastFault.Body);
1807
+ const removedCount = view.getUint16(0, true);
1808
+ let at = 2;
1809
+ const removed = [];
1810
+ for (let i = 0; i < removedCount; i += 1) {
1811
+ if (at + 2 > bytes.length) return fail(FastFault.Body);
1812
+ const k = view.getUint16(at, true);
1813
+ if (i > 0 && k <= removed[i - 1]) return fail(FastFault.Body);
1814
+ removed.push(k);
1815
+ at += 2;
1816
+ }
1817
+ if (at + 2 > bytes.length) return fail(FastFault.Body);
1818
+ const count = view.getUint16(at, true);
1819
+ at += 2;
1820
+ const records = [];
1821
+ for (let i = 0; i < count; i += 1) {
1822
+ if (at + 4 > bytes.length) return fail(FastFault.Body);
1823
+ const key = view.getUint16(at, true);
1824
+ const len = view.getUint16(at + 2, true);
1825
+ const bad2 = len > FAST_MAX_RECORD_BYTES || at + 4 + len > bytes.length || i > 0 && key <= records[i - 1].key;
1826
+ if (bad2) return fail(FastFault.Body);
1827
+ records.push({ key, bytes: bytes.slice(at + 4, at + 4 + len) });
1828
+ at += 4 + len;
1829
+ }
1830
+ return at === bytes.length ? ok({ removed, records }) : fail(FastFault.Body);
1831
+ }
1832
+ function applyBody(base, body) {
1833
+ const gone = new Set(body.removed);
1834
+ const out = new Map(base.filter((r) => !gone.has(r.key)).map((r) => [r.key, r]));
1835
+ for (const r of body.records) out.set(r.key, r);
1836
+ return byKey([...out.values()]);
1837
+ }
1838
+ var emptyClientRx = () => ({ partials: [], history: [], newest: 0 });
1839
+ var sameHead = (p, f) => p.base === f.base && p.tick === f.tick && p.inputAck === f.inputAck && p.sent === f.sent && p.keyframe === f.keyframe && p.frags === f.frags;
1840
+ function receiveFragment(rx, bytes, sid) {
1841
+ const decoded = decodeFragment(bytes, sid);
1842
+ if (!decoded.ok) return { rx, applied: null, fault: decoded.fault };
1843
+ const f = decoded.value;
1844
+ if (f.snap <= rx.newest) return { rx, applied: null, fault: FastFault.Stale };
1845
+ const existing = rx.partials.find((p) => p.snap === f.snap);
1846
+ if (existing !== void 0 && !sameHead(existing, f)) return { rx, applied: null, fault: FastFault.Mismatch };
1847
+ if (existing?.chunks[f.frag]) return { rx, applied: null, fault: FastFault.Duplicate };
1848
+ const set = existing ?? { snap: f.snap, base: f.base, tick: f.tick, inputAck: f.inputAck, sent: f.sent, keyframe: f.keyframe, frags: f.frags, chunks: Array(f.frags).fill(null) };
1849
+ const gathered = { ...set, chunks: set.chunks.map((c, i) => i === f.frag ? f.chunk : c) };
1850
+ const others = rx.partials.filter((p) => p.snap !== f.snap);
1851
+ if (gathered.chunks.some((c) => c === null)) {
1852
+ const partials = [...others, gathered].sort((a, b) => a.snap - b.snap).slice(-FAST_REASSEMBLY_SLOTS);
1853
+ return { rx: { ...rx, partials }, applied: null };
1854
+ }
1855
+ return applyComplete({ ...rx, partials: others }, rx, gathered);
1856
+ }
1857
+ function applyComplete(rest, given, set) {
1858
+ const dropped = rest.partials.length === given.partials.length ? given : rest;
1859
+ const body = parseBody(concat(set.chunks));
1860
+ if (!body.ok) return { rx: dropped, applied: null, fault: FastFault.Body };
1861
+ const base = set.keyframe ? null : given.history.find((h) => h.snap === set.base);
1862
+ if (base === void 0) return { rx: dropped, applied: null, fault: FastFault.Base };
1863
+ if (set.keyframe && body.value.removed.length > 0) return { rx: dropped, applied: null, fault: FastFault.Body };
1864
+ const records = applyBody(base?.records ?? [], body.value);
1865
+ const history = [...given.history, { snap: set.snap, tick: set.tick, inputAck: set.inputAck, sent: set.sent, records }].slice(-FAST_SNAPSHOT_HISTORY);
1866
+ const partials = rest.partials.filter((p) => p.snap > set.snap);
1867
+ return { rx: { partials, history, newest: set.snap }, applied: set.snap };
1868
+ }
1869
+ function concat(chunks) {
1870
+ const out = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0));
1871
+ let at = 0;
1872
+ for (const c of chunks) {
1873
+ out.set(c, at);
1874
+ at += c.length;
1875
+ }
1876
+ return out;
1877
+ }
1878
+
1879
+ // src/authority/fast-records.ts
1880
+ var MotionRecordKind = { Entity: 1, Seat: 2 };
1881
+ var FieldWire = {
1882
+ F32: "f32",
1883
+ U32: "u32",
1884
+ U16: "u16",
1885
+ /** A quantized orientation component in [−1, 1], uint16. */
1886
+ Orientation: "q-orientation",
1887
+ /** A quantized velocity in [−64, 64], uint16. */
1888
+ Velocity: "q-velocity",
1889
+ /** A boolean: the mask bit alone. */
1890
+ Flag: "flag"
1891
+ };
1892
+ var ENTITY_FIELDS = [
1893
+ ["x", FieldWire.F32],
1894
+ ["y", FieldWire.F32],
1895
+ ["z", FieldWire.F32],
1896
+ ["vx", FieldWire.F32],
1897
+ ["vy", FieldWire.F32],
1898
+ ["vz", FieldWire.F32],
1899
+ ["yaw", FieldWire.F32],
1900
+ ["pitch", FieldWire.F32],
1901
+ ["duck", FieldWire.F32],
1902
+ ["spin", FieldWire.F32],
1903
+ ["fuel", FieldWire.F32],
1904
+ ["epoch", FieldWire.U32],
1905
+ ["mstate", FieldWire.U32],
1906
+ ["qx", FieldWire.Orientation],
1907
+ ["qy", FieldWire.Orientation],
1908
+ ["qz", FieldWire.Orientation],
1909
+ ["qw", FieldWire.Orientation],
1910
+ ["lvx", FieldWire.Velocity],
1911
+ ["lvy", FieldWire.Velocity],
1912
+ ["lvz", FieldWire.Velocity],
1913
+ ["avx", FieldWire.Velocity],
1914
+ ["avy", FieldWire.Velocity],
1915
+ ["avz", FieldWire.Velocity],
1916
+ ["sleep", FieldWire.Flag],
1917
+ ["rest", FieldWire.U16]
1918
+ ];
1919
+ var SEAT_FIELDS = [
1920
+ ["inMoveX", FieldWire.F32],
1921
+ ["inMoveY", FieldWire.F32],
1922
+ ["inMoveZ", FieldWire.F32],
1923
+ ["inYaw", FieldWire.F32],
1924
+ ["inPitch", FieldWire.F32],
1925
+ ["inButtons", FieldWire.U32],
1926
+ ["inRep", FieldWire.U32],
1927
+ ["inStarve", FieldWire.U32],
1928
+ ["inBuf", FieldWire.U32],
1929
+ ["inAck", FieldWire.U32]
1930
+ ];
1931
+ var RECORD_ID_MAX_CHARS = 64;
1932
+ var HEAD = 2;
1933
+ var MASK_BYTES = 4;
1934
+ var UINT32_MAX2 = 4294967295;
1935
+ var UINT16_MAX = 65535;
1936
+ var Q_SPAN = 65534;
1937
+ var Q_ZERO = Q_SPAN / 2;
1938
+ var Q_RANGE = { [FieldWire.Orientation]: 1, [FieldWire.Velocity]: 64 };
1939
+ var blankEntityMotion = () => ({
1940
+ x: 0,
1941
+ y: 0,
1942
+ z: 0,
1943
+ vx: 0,
1944
+ vy: 0,
1945
+ vz: 0,
1946
+ yaw: 0,
1947
+ pitch: 0,
1948
+ duck: 0,
1949
+ spin: 0,
1950
+ fuel: 0,
1951
+ epoch: 0,
1952
+ mstate: 0,
1953
+ qx: 0,
1954
+ qy: 0,
1955
+ qz: 0,
1956
+ qw: 0,
1957
+ lvx: 0,
1958
+ lvy: 0,
1959
+ lvz: 0,
1960
+ avx: 0,
1961
+ avy: 0,
1962
+ avz: 0,
1963
+ sleep: false,
1964
+ rest: 0
1965
+ });
1966
+ var blankSeatEcho = () => ({ inMoveX: 0, inMoveY: 0, inMoveZ: 0, inYaw: 0, inPitch: 0, inButtons: 0, inRep: 0, inStarve: 0, inBuf: 0, inAck: 0 });
1967
+ function quantizeCode(half, value2) {
1968
+ const v = Math.min(half, Math.max(-half, value2));
1969
+ return Math.floor((v + half) / (2 * half) * Q_SPAN + 0.5);
1970
+ }
1971
+ var dequantizeCode = (half, code) => -half + code / Q_SPAN * (2 * half);
1972
+ var fieldsOf = (kind) => {
1973
+ if (kind === MotionRecordKind.Entity) return ENTITY_FIELDS;
1974
+ if (kind === MotionRecordKind.Seat) return SEAT_FIELDS;
1975
+ return null;
1976
+ };
1977
+ var isUint2 = (v, max) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= max;
1978
+ function wireValue(wire, v) {
1979
+ switch (wire) {
1980
+ case FieldWire.F32: {
1981
+ if (typeof v !== "number" || !Number.isFinite(Math.fround(v))) return null;
1982
+ return Math.fround(v) === 0 ? void 0 : Math.fround(v);
1983
+ }
1984
+ case FieldWire.U32:
1985
+ if (!isUint2(v, UINT32_MAX2)) return null;
1986
+ return v === 0 ? void 0 : v;
1987
+ case FieldWire.U16:
1988
+ if (!isUint2(v, UINT16_MAX)) return null;
1989
+ return v === 0 ? void 0 : v;
1990
+ case FieldWire.Flag:
1991
+ if (typeof v !== "boolean") return null;
1992
+ return v ? 1 : void 0;
1993
+ default: {
1994
+ if (typeof v !== "number" || Number.isNaN(v)) return null;
1995
+ const code = quantizeCode(Q_RANGE[wire], v);
1996
+ return code === Q_ZERO ? void 0 : code;
1997
+ }
1998
+ }
1999
+ }
2000
+ var WIDTH = { f32: 4, u32: 4, u16: 2, "q-orientation": 2, "q-velocity": 2, flag: 0 };
2001
+ var ID_MAX_BYTES = 255;
2002
+ var FIELD_MAX_BYTES = 4;
2003
+ var FIELDS_MAX = 32;
2004
+ var RECORD_SCRATCH = new Uint8Array(HEAD + ID_MAX_BYTES + MASK_BYTES + FIELDS_MAX * FIELD_MAX_BYTES);
2005
+ var RECORD_VIEW = new DataView(RECORD_SCRATCH.buffer);
2006
+ var ID_BYTES = /* @__PURE__ */ new Map();
2007
+ var ID_BYTES_MAX = 4096;
2008
+ var ID_ENCODER = new TextEncoder();
2009
+ var idBytesOf = (id) => {
2010
+ const known = ID_BYTES.get(id);
2011
+ if (known !== void 0) return known;
2012
+ if (ID_BYTES.size >= ID_BYTES_MAX) ID_BYTES.clear();
2013
+ const bytes = ID_ENCODER.encode(id);
2014
+ ID_BYTES.set(id, bytes);
2015
+ return bytes;
2016
+ };
2017
+ function encodeRecord(r) {
2018
+ const fields = fieldsOf(r.kind);
2019
+ const idOk = typeof r.id === "string" && r.id.length >= 1 && r.id.length <= RECORD_ID_MAX_CHARS;
2020
+ const id = idOk ? idBytesOf(r.id) : null;
2021
+ if (fields === null || id === null || id.length > ID_MAX_BYTES || r.motion === null || typeof r.motion !== "object") return { ok: false, fault: FastFault.Field };
2022
+ const motion = r.motion;
2023
+ RECORD_SCRATCH[0] = r.kind;
2024
+ RECORD_SCRATCH[1] = id.length;
2025
+ RECORD_SCRATCH.set(id, HEAD);
2026
+ const maskAt = HEAD + id.length;
2027
+ let at = maskAt + MASK_BYTES;
2028
+ let mask2 = 0;
2029
+ for (let i = 0; i < fields.length; i += 1) {
2030
+ const [name, wire] = fields[i];
2031
+ const value2 = wireValue(wire, motion[name]);
2032
+ if (value2 === null) return { ok: false, fault: FastFault.Field };
2033
+ if (value2 === void 0) continue;
2034
+ mask2 = (mask2 | 1 << i) >>> 0;
2035
+ at = writeField(RECORD_VIEW, at, wire, value2);
2036
+ }
2037
+ RECORD_VIEW.setUint32(maskAt, mask2, true);
2038
+ return { ok: true, value: RECORD_SCRATCH.slice(0, at) };
2039
+ }
2040
+ function writeField(view, at, wire, value2) {
2041
+ if (wire === FieldWire.F32) view.setFloat32(at, value2, true);
2042
+ else if (wire === FieldWire.U32) view.setUint32(at, value2, true);
2043
+ else if (wire !== FieldWire.Flag) view.setUint16(at, value2, true);
2044
+ return at + WIDTH[wire];
2045
+ }
2046
+ function readField(view, at, wire) {
2047
+ if (at + WIDTH[wire] > view.byteLength) return { ok: false, fault: FastFault.Truncated };
2048
+ switch (wire) {
2049
+ case FieldWire.Flag:
2050
+ return { ok: true, value: true };
2051
+ case FieldWire.F32: {
2052
+ const v = view.getFloat32(at, true);
2053
+ return Number.isFinite(v) && v !== 0 ? { ok: true, value: v } : { ok: false, fault: FastFault.Field };
2054
+ }
2055
+ case FieldWire.U32: {
2056
+ const v = view.getUint32(at, true);
2057
+ return v !== 0 ? { ok: true, value: v } : { ok: false, fault: FastFault.Field };
2058
+ }
2059
+ case FieldWire.U16: {
2060
+ const v = view.getUint16(at, true);
2061
+ return v !== 0 ? { ok: true, value: v } : { ok: false, fault: FastFault.Field };
2062
+ }
2063
+ default: {
2064
+ const code = view.getUint16(at, true);
2065
+ const canonical = code !== Q_ZERO && code <= Q_SPAN;
2066
+ return canonical ? { ok: true, value: dequantizeCode(Q_RANGE[wire], code) } : { ok: false, fault: FastFault.Field };
2067
+ }
2068
+ }
2069
+ }
2070
+ var UTF8 = new TextDecoder("utf-8", { fatal: true });
2071
+ function readId(bytes) {
2072
+ const len = bytes[1];
2073
+ if (HEAD + len > bytes.length) return { ok: false, fault: FastFault.Truncated };
2074
+ if (len === 0) return { ok: false, fault: FastFault.Field };
2075
+ try {
2076
+ const id = UTF8.decode(bytes.subarray(HEAD, HEAD + len));
2077
+ return id.length <= RECORD_ID_MAX_CHARS ? { ok: true, value: id } : { ok: false, fault: FastFault.Field };
2078
+ } catch {
2079
+ return { ok: false, fault: FastFault.Field };
2080
+ }
2081
+ }
2082
+ function decodeRecord(bytes) {
2083
+ if (bytes.length < HEAD) return { ok: false, fault: FastFault.Truncated };
2084
+ const fields = fieldsOf(bytes[0]);
2085
+ if (fields === null) return { ok: false, fault: FastFault.Kind };
2086
+ const id = readId(bytes);
2087
+ if (!id.ok) return id;
2088
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
2089
+ let at = HEAD + bytes[1];
2090
+ if (at + MASK_BYTES > bytes.length) return { ok: false, fault: FastFault.Truncated };
2091
+ const mask2 = view.getUint32(at, true);
2092
+ at += MASK_BYTES;
2093
+ if (fields.length < 32 && mask2 >>> fields.length !== 0) return { ok: false, fault: FastFault.Flags };
2094
+ if (bytes[0] === MotionRecordKind.Entity) {
2095
+ const motion2 = readMotion(view, at, mask2, blankEntityMotion(), fields);
2096
+ return motion2.ok ? { ok: true, value: { kind: MotionRecordKind.Entity, id: id.value, motion: motion2.value } } : motion2;
2097
+ }
2098
+ const motion = readMotion(view, at, mask2, blankSeatEcho(), fields);
2099
+ return motion.ok ? { ok: true, value: { kind: MotionRecordKind.Seat, id: id.value, motion: motion.value } } : motion;
2100
+ }
2101
+ function readMotion(view, start, mask2, blank, fields) {
2102
+ const values = {};
2103
+ let at = start;
2104
+ for (const [i, [name, wire]] of fields.entries()) {
2105
+ if ((mask2 & 1 << i) === 0) continue;
2106
+ const v = readField(view, at, wire);
2107
+ if (!v.ok) return v;
2108
+ values[name] = v.value;
2109
+ at += WIDTH[wire];
2110
+ }
2111
+ if (at !== view.byteLength) return { ok: false, fault: FastFault.Trailing };
2112
+ return { ok: true, value: Object.assign(blank, values) };
2113
+ }
2114
+
2115
+ // src/authority/fast-client.ts
2116
+ var UINT32_MAX3 = 4294967295;
2117
+ var FAST_OPEN_ROUND_TRIPS = 4;
2118
+ var FastFallbackReason = {
2119
+ /** The `__fast` binding was malformed, this client cannot open its url, or its socket failed. */
2120
+ Unreachable: "unreachable",
2121
+ /** No snapshot within {@link FAST_HANDSHAKE_MS} of the first datagram. */
2122
+ Handshake: "handshake",
2123
+ /** No snapshot for {@link FAST_STALL_FALLBACK_MS} after the last one. */
2124
+ Stall: "stall",
2125
+ /** The server asked for it (`__fast:off` from the server). */
2126
+ Server: "server",
2127
+ /** A browser with no `WebTransport` (or not a secure context): the seat never asked for the channel. */
2128
+ NoWebTransport: "no-webtransport",
2129
+ /** The binding carries no `wt` this browser can open: the room's server runs no relay. */
2130
+ NoRelay: "no-relay",
2131
+ /** The seat asked and no binding came within {@link FAST_HANDSHAKE_MS} of its first frame: the room's
2132
+ * server has no channel, or is too old to offer it. The seat was on the WebSocket all along, and a
2133
+ * binding that comes after all still opens. */
2134
+ NotOffered: "not-offered"
2135
+ };
2136
+ var isUint3 = (v, max) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= max;
2137
+ function isFastBind(m) {
2138
+ if (typeof m !== "object" || m === null) return false;
2139
+ const { sid, key, url } = m;
2140
+ const keyOk = typeof key === "string" && key.length === 2 * FAST_KEY_BYTES && /^[0-9a-f]+$/.test(key);
2141
+ return isUint3(sid, UINT32_MAX3) && keyOk && typeof url === "string" && url.length > 0;
2142
+ }
2143
+ function heldInputs(sent, inputAck, tickHz) {
2144
+ if (sent.length === 0) return [];
2145
+ const newest = sent[sent.length - 1].seq;
2146
+ const keepFrom = Math.min(inputAck, newest - inputRedundancyTicks(tickHz) + 1);
2147
+ const capped = Math.max(keepFrom, newest - seqAheadMax(tickHz) + 1);
2148
+ return sent.filter((s) => s.seq >= capped);
2149
+ }
2150
+ var recordName = (kind, id) => `${kind}:${id}`;
2151
+ function openFastClient(o) {
2152
+ let rx = emptyClientRx();
2153
+ let sent = [];
2154
+ const sentTimes = /* @__PURE__ */ new Map();
2155
+ let fresh = 0;
2156
+ let closed = false;
2157
+ let firstSentAt = null;
2158
+ let lastAppliedAt = null;
2159
+ let lastUplinkAt = null;
2160
+ let lastFrameAt = null;
2161
+ const keepsAlive = typeof o.bind.rev === "number" && o.bind.rev >= FAST_BIND_REV;
2162
+ let openedAt = null;
2163
+ let decodedByKey = /* @__PURE__ */ new Map();
2164
+ let latest = /* @__PURE__ */ new Map();
2165
+ const inputAck = () => rx.history.at(-1)?.inputAck ?? 0;
2166
+ const decodeChanged = (records) => {
2167
+ const nextByKey = /* @__PURE__ */ new Map();
2168
+ const nextLatest = /* @__PURE__ */ new Map();
2169
+ const changed = [];
2170
+ for (const r of records) {
2171
+ const before = decodedByKey.get(r.key);
2172
+ const same = before !== void 0 && (before.bytes === r.bytes || sameBytes2(before.bytes, r.bytes));
2173
+ const record = same ? before.record : decodeOrNull(r.bytes);
2174
+ nextByKey.set(r.key, { bytes: r.bytes, record });
2175
+ if (record === null) continue;
2176
+ nextLatest.set(recordName(record.kind, record.id), record);
2177
+ if (!same) changed.push(record);
2178
+ }
2179
+ decodedByKey = nextByKey;
2180
+ latest = nextLatest;
2181
+ return changed;
2182
+ };
2183
+ function receive(bytes) {
2184
+ if (closed) return { applied: null };
2185
+ const r = receiveFragment(rx, bytes, o.bind.sid);
2186
+ rx = r.rx;
2187
+ if (r.fault !== void 0 || r.applied === null) return { applied: null, fault: r.fault };
2188
+ const held = rx.history.at(-1);
2189
+ if (held === void 0) return { applied: null };
2190
+ const before = lastAppliedSnap;
2191
+ lastAppliedSnap = held.snap;
2192
+ lastAppliedAt = o.now();
2193
+ sent = heldInputs(sent, held.inputAck, o.tickHz);
2194
+ const applied = { snap: held.snap, tick: held.tick, inputAck: held.inputAck, sent: held.sent, changed: decodeChanged(held.records) };
2195
+ o.onSnapshot?.(applied);
2196
+ const eager = applied.changed.length >= FAST_ACK_EAGER_RECORDS && (lastUplinkAt === null || o.now() - lastUplinkAt >= FAST_ACK_EAGER_GAP_MS);
2197
+ if (held.snap - before >= FAST_CHALLENGE_JUMP || eager) sendEmpty();
2198
+ return { applied };
2199
+ }
2200
+ function sendEmpty() {
2201
+ if (closed || !keepsAlive || socket === null) return;
2202
+ const bytes = encodeUplink({ sid: o.bind.sid, key: o.bind.key, ackSnap: rx.newest, fresh: 0, actions: [], window: [] });
2203
+ if (!bytes.ok) return;
2204
+ const now = o.now();
2205
+ firstSentAt ??= now;
2206
+ lastUplinkAt = now;
2207
+ socket.send(bytes.value);
2208
+ }
2209
+ let lastAppliedSnap = 0;
2210
+ const socket = o.open(
2211
+ o.bind.url,
2212
+ (bytes) => {
2213
+ receive(bytes);
2214
+ },
2215
+ o.bind
2216
+ );
2217
+ if (socket === null) return null;
2218
+ const largestUplink = uplinkMaxBytes(o.tickHz);
2219
+ return {
2220
+ bind: o.bind,
2221
+ get rx() {
2222
+ return rx;
2223
+ },
2224
+ get lastQueued() {
2225
+ return sent.at(-1)?.seq ?? 0;
2226
+ },
2227
+ get closed() {
2228
+ return closed;
2229
+ },
2230
+ opening: () => socket.opening?.() === true,
2231
+ queue(input) {
2232
+ if (closed) return;
2233
+ sent.push({ seq: input.seq, tick: input.tick, render: input.render, input: { ...input.input } });
2234
+ fresh += 1;
2235
+ },
2236
+ flush() {
2237
+ if (closed || fresh === 0) return;
2238
+ const now = o.now();
2239
+ for (const s of sent.slice(-fresh)) sentTimes.set(s.seq, now);
2240
+ forgetSendTimes(sentTimes, (sent.at(-1)?.seq ?? 0) - seqAheadMax(o.tickHz) + 1);
2241
+ const window = inputRedundancyTicks(o.tickHz);
2242
+ let from = sent.length - fresh;
2243
+ fresh = 0;
2244
+ while (from < sent.length) {
2245
+ const to = Math.min(from + window, sent.length);
2246
+ 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 });
2247
+ from = to;
2248
+ const bytes = encodeUplink(u);
2249
+ if (!bytes.ok) continue;
2250
+ firstSentAt ??= now;
2251
+ lastUplinkAt = now;
2252
+ socket.send(bytes.value);
2253
+ }
2254
+ },
2255
+ framed() {
2256
+ const now = o.now();
2257
+ const gap = lastFrameAt === null ? 0 : now - lastFrameAt;
2258
+ lastFrameAt = now;
2259
+ if (gap <= FAST_FRAME_GAP_MS) return;
2260
+ if (lastAppliedAt !== null) lastAppliedAt = Math.min(now, lastAppliedAt + gap);
2261
+ if (firstSentAt !== null) firstSentAt = Math.min(now, firstSentAt + gap);
2262
+ },
2263
+ keepalive() {
2264
+ if (closed || !keepsAlive) return;
2265
+ const every = lastAppliedAt === null ? FAST_KEEPALIVE_BIND_MS : FAST_KEEPALIVE_MS;
2266
+ if (lastUplinkAt === null || o.now() - lastUplinkAt >= every) sendEmpty();
2267
+ },
2268
+ inputAt: (seq) => sent.find((s) => s.seq === seq),
2269
+ sentAt: (seq) => sentTimes.get(seq),
2270
+ motionOf: (kind, id) => latest.get(recordName(kind, id)),
2271
+ records: () => [...latest.values()],
2272
+ due() {
2273
+ if (closed) return null;
2274
+ if (socket.failed?.() === true) return FastFallbackReason.Unreachable;
2275
+ const room = socket.maxDatagramBytes?.();
2276
+ if (room !== void 0 && room < largestUplink) return FastFallbackReason.Unreachable;
2277
+ const now = o.now();
2278
+ if (lastAppliedAt !== null) return now - lastAppliedAt > FAST_STALL_FALLBACK_MS ? FastFallbackReason.Stall : null;
2279
+ if (firstSentAt === null) return null;
2280
+ const opening = socket.opening?.();
2281
+ if (opening === true) {
2282
+ const openBound = Math.max(FAST_HANDSHAKE_MS, FAST_OPEN_ROUND_TRIPS * (o.roundTripMs?.() ?? 0));
2283
+ return now - firstSentAt > openBound ? FastFallbackReason.Handshake : null;
2284
+ }
2285
+ if (opening === false) openedAt ??= now;
2286
+ const from = opening === false ? Math.max(firstSentAt, openedAt ?? now) : firstSentAt;
2287
+ return now - from > FAST_HANDSHAKE_MS ? FastFallbackReason.Handshake : null;
2288
+ },
2289
+ receive,
2290
+ close() {
2291
+ if (closed) return;
2292
+ closed = true;
2293
+ socket.close();
2294
+ }
2295
+ };
2296
+ }
2297
+ function forgetSendTimes(times, from) {
2298
+ for (const seq of times.keys()) {
2299
+ if (seq >= from) return;
2300
+ times.delete(seq);
2301
+ }
2302
+ }
2303
+ function decodeOrNull(bytes) {
2304
+ const d = decodeRecord(bytes);
2305
+ return d.ok ? d.value : null;
2306
+ }
2307
+ var sameBytes2 = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
2308
+
2309
+ // src/authority/fast-webtransport.ts
2310
+ var FAST_WT_HASH_HEX_DIGITS = 64;
2311
+ var FAST_WT_HASHES_MAX = 4;
2312
+ var WT_SCHEME = "https:";
2313
+ var CONNECT_QUEUE_MAX = 32;
2314
+ var HASH_PATTERN = /^[0-9a-f]+$/;
2315
+ var RELIABLE_ONLY = "reliable-only";
2316
+ function fastWebTransportOf(bind) {
2317
+ const wt = bind.wt;
2318
+ if (typeof wt !== "object" || wt === null) return null;
2319
+ const { url, hashes } = wt;
2320
+ if (typeof url !== "string" || !Array.isArray(hashes)) return null;
2321
+ if (hashes.length === 0 || hashes.length > FAST_WT_HASHES_MAX) return null;
2322
+ const hashOk = (h) => typeof h === "string" && h.length === FAST_WT_HASH_HEX_DIGITS && HASH_PATTERN.test(h);
2323
+ if (!hashes.every(hashOk)) return null;
2324
+ let parsed2;
2325
+ try {
2326
+ parsed2 = new URL(url);
2327
+ } catch {
2328
+ return null;
2329
+ }
2330
+ const bare = parsed2.username === "" && parsed2.password === "" && parsed2.search === "" && parsed2.hash === "";
2331
+ if (parsed2.protocol !== WT_SCHEME || parsed2.hostname === "" || !bare) return null;
2332
+ return { url, hashes: hashes.map((h) => fromHex(h)) };
2333
+ }
2334
+ function webTransportOptions(target) {
2335
+ return {
2336
+ serverCertificateHashes: target.hashes.map((value2) => ({ algorithm: "sha-256", value: value2 })),
2337
+ requireUnreliable: true,
2338
+ allowPooling: false,
2339
+ congestionControl: "low-latency"
2340
+ };
2341
+ }
2342
+ var carriesFast = (t) => t.reliability !== RELIABLE_ONLY;
2343
+ function datagramWriter(t) {
2344
+ const writable = t.datagrams.createWritable?.() ?? t.datagrams.writable;
2345
+ return writable?.getWriter() ?? null;
2346
+ }
2347
+ var SocketState = { Connecting: "connecting", Open: "open", Failed: "failed", Closed: "closed" };
2348
+ function webTransportFastOpener(WebTransport) {
2349
+ const open = (_url, receive, bind) => {
2350
+ const target = bind === void 0 ? null : fastWebTransportOf(bind);
2351
+ if (target === null || WebTransport === void 0) return null;
2352
+ let t;
2353
+ try {
2354
+ t = new WebTransport(target.url, webTransportOptions(target));
2355
+ } catch {
2356
+ return null;
2357
+ }
2358
+ let state = SocketState.Connecting;
2359
+ let waiting = [];
2360
+ let writer = null;
2361
+ const fail2 = () => {
2362
+ if (state === SocketState.Closed || state === SocketState.Failed) return;
2363
+ state = SocketState.Failed;
2364
+ writer = null;
2365
+ waiting = [];
2366
+ try {
2367
+ t.close();
2368
+ } catch {
2369
+ }
2370
+ };
2371
+ const write = (w, bytes) => void w.write(bytes).catch(() => {
2372
+ });
2373
+ const readAll = async (reader) => {
2374
+ for (; ; ) {
2375
+ const r = await reader.read();
2376
+ if (r.done) return;
2377
+ if (state === SocketState.Open) receive(r.value);
2378
+ }
2379
+ };
2380
+ t.ready.then(
2381
+ () => {
2382
+ if (state !== SocketState.Connecting) return;
2383
+ if (!carriesFast(t)) return fail2();
2384
+ const w = datagramWriter(t);
2385
+ if (w === null) return fail2();
2386
+ state = SocketState.Open;
2387
+ writer = w;
2388
+ for (const bytes of waiting) write(w, bytes);
2389
+ waiting = [];
2390
+ readAll(t.datagrams.readable.getReader()).then(fail2, fail2);
2391
+ },
2392
+ () => fail2()
2393
+ );
2394
+ t.closed.then(fail2, fail2);
2395
+ const socket = {
2396
+ send(bytes) {
2397
+ if (state === SocketState.Open && writer !== null) write(writer, bytes);
2398
+ else if (state === SocketState.Connecting) waiting = [...waiting, bytes].slice(-CONNECT_QUEUE_MAX);
2399
+ },
2400
+ close() {
2401
+ if (state === SocketState.Closed) return;
2402
+ const was = state;
2403
+ state = SocketState.Closed;
2404
+ writer = null;
2405
+ waiting = [];
2406
+ if (was === SocketState.Failed) return;
2407
+ try {
2408
+ t.close();
2409
+ } catch {
2410
+ }
2411
+ },
2412
+ failed: () => state === SocketState.Failed,
2413
+ opening: () => state === SocketState.Connecting,
2414
+ // The browser's outgoing limit, known once the session is open: the browser sends only uplinks,
2415
+ // so the seat's largest uplink must fit (Chromium reports 1024; a 120 Hz seat's is 889).
2416
+ maxDatagramBytes() {
2417
+ const size = state === SocketState.Open ? t.datagrams.maxDatagramSize : void 0;
2418
+ return typeof size === "number" ? size : void 0;
2419
+ }
2420
+ };
2421
+ return socket;
2422
+ };
2423
+ open.whyNull = (bind) => {
2424
+ if (WebTransport === void 0) return FastFallbackReason.NoWebTransport;
2425
+ return fastWebTransportOf(bind) === null ? FastFallbackReason.NoRelay : FastFallbackReason.Unreachable;
2426
+ };
2427
+ return open;
2428
+ }
2429
+ var webTransportOf = (scope) => typeof scope.WebTransport === "function" ? scope.WebTransport : void 0;
2430
+ var openWebTransportFastSocket = (url, receive, bind) => webTransportFastOpener(webTransportOf(globalThis))(url, receive, bind);
2431
+ openWebTransportFastSocket.whyNull = (bind) => webTransportFastOpener(webTransportOf(globalThis)).whyNull?.(bind) ?? FastFallbackReason.Unreachable;
2432
+ function chooseFastSocket(given, scope = globalThis) {
2433
+ if (given === false) return { open: void 0, fallback: null };
2434
+ if (given !== void 0) return { open: given, fallback: null };
2435
+ if (webTransportOf(scope) !== void 0) return { open: openWebTransportFastSocket, fallback: null };
2436
+ const inBrowser = scope.document !== void 0 || typeof scope.importScripts === "function";
2437
+ return { open: void 0, fallback: inBrowser ? FastFallbackReason.NoWebTransport : null };
2438
+ }
2439
+
2440
+ export {
2441
+ AuthorityRoomLimits,
2442
+ validMatchKey,
2443
+ validRoomCode,
2444
+ matchKeyOf,
2445
+ AuthorityJoinMethod,
2446
+ AuthorityTargetRefusal,
2447
+ planAuthorityJoin,
2448
+ nextCodeRegion,
2449
+ selectMatchWorld,
2450
+ PHYSICS_CONTRACT,
2451
+ PHYSICS_DEFAULT_LIMITS,
2452
+ PHYSICS_LIMIT_NAMES,
2453
+ PHYSICS_SCALED_LIMITS,
2454
+ PHYSICS_REAL_LIMITS,
2455
+ PHYSICS_CEILING_FACTOR,
2456
+ PHYSICS_STATIC_PIECE_BYTES,
2457
+ PHYSICS_PREDICT_MAX_BOXES,
2458
+ PHYSICS_PREDICT_MAX_RAMPS,
2459
+ physicsWorldPredicts,
2460
+ physicsLimitsForWorld,
2461
+ PHYSICS_DEFAULT_CEILING,
2462
+ physicsCeilingValid,
2463
+ physicsLimitsAt,
2464
+ PhysicsConfigRange,
2465
+ PHYSICS_PRESET_LENGTH_UNITS,
2466
+ PHYSICS_PATCH_HZ,
2467
+ PHYSICS_DEFAULT_PATCH_HZ,
2468
+ PhysicsPredictRange,
2469
+ isPhysicsPatchHz,
2470
+ effectivePatchHz,
2471
+ keyframeTicks,
2472
+ PHYSICS_PREDICT_MAX_LINEAR,
2473
+ PHYSICS_PREDICT_MAX_ANGULAR,
2474
+ PHYSICS_PREDICT_MIN_ITERATIONS,
2475
+ solverIterationsOf,
2476
+ PHYSICS_KEY_MAX_BYTES,
2477
+ PHYSICS_KIND_MAX_BYTES,
2478
+ PHYSICS_STATIC_PREFIX,
2479
+ isPhysicsKey,
2480
+ isPhysicsKind,
2481
+ resolvePhysicsConfig,
2482
+ checkResolvedPhysics,
2483
+ PHYSICS_SUBSTEPS,
2484
+ physicsWorldDesc,
2485
+ PhysEvKind,
2486
+ PHYSICS_OVERLAP_MAX_KEYS,
2487
+ PHYSICS_BODY_FIELDS,
2488
+ HOST_BODY_FIELDS,
2489
+ BodyQuantization,
2490
+ quantizeBodyValue,
2491
+ dequantizeBodyValue,
2492
+ PHYSICS_CONTESTED_METRES,
2493
+ PHYSICS_FULL_RATE_BODIES,
2494
+ PHYSICS_ROTATING_BODIES,
2495
+ BODY_EXTRAPOLATE_MAX_MS,
2496
+ BODY_EXTRAPOLATE_BACK_TICKS,
2497
+ BODY_CORRECTION_TAU_MS,
2498
+ BODY_SNAP_METRES,
2499
+ MAX_PROJECTILE_TICK,
2500
+ MAX_PROJECTILE_G,
2501
+ isProjectile,
2502
+ projectileAt,
2503
+ serverTick,
2504
+ tickInstant,
2505
+ ContactDrawing,
2506
+ ContactPush,
2507
+ ContactPreset,
2508
+ TopPolicy,
2509
+ ContactShapeKind,
2510
+ HeadFriction,
2511
+ ContactConfigError,
2512
+ CONTACT_PAIRS_MAX,
2513
+ CONTACT_TAG_MAX,
2514
+ CONTACT_PASSES_MAX,
2515
+ CONTACT_VALUE_MAX,
2516
+ SHOOTER_CENTER,
2517
+ CONTACT_PRESETS,
2518
+ CONTACT_DEFAULTS,
2519
+ resolveContactConfig,
2520
+ LiftShapeKind,
2521
+ LiftPathMode,
2522
+ LiftEase,
2523
+ LiftImpart,
2524
+ NO_LIFTS,
2525
+ LiftsConfigError,
2526
+ LIFTS_MAX,
2527
+ LIFT_POINTS_MAX,
2528
+ LIFT_ID_MAX,
2529
+ LIFT_VALUE_MAX,
2530
+ LIFT_TICKS_MAX,
2531
+ LIFT_SPIN_MIN_PERIOD,
2532
+ isLiftId,
2533
+ resolveLifts,
2534
+ InputRepeat,
2535
+ INPUT_SETTLE_MS,
2536
+ INPUT_PRESETS,
2537
+ INPUTS_DEFAULT,
2538
+ InputConfigError,
2539
+ resolveInputConfig,
2540
+ presetInputRepeat,
2541
+ AuthorityLifetime,
2542
+ LIFETIME_DEFAULT,
2543
+ LifetimeConfigError,
2544
+ resolveLifetime,
2545
+ GuestGlobal,
2546
+ AuthorityLimits,
2547
+ TICK_RATES,
2548
+ PHYSICS_ONLY_TICK_HZ,
2549
+ MAX_TICK_HZ,
2550
+ DEFAULT_TICK_HZ,
2551
+ TickOutFault,
2552
+ InputUse,
2553
+ sanitizeInput,
2554
+ validateTickOut,
2555
+ validateInitOut,
2556
+ FAST_VERSION,
2557
+ FAST_MAX_DATAGRAM_BYTES,
2558
+ FAST_KEY_BYTES,
2559
+ INPUT_REDUNDANCY_MS,
2560
+ INPUT_REDUNDANCY_MIN,
2561
+ INPUT_REDUNDANCY_MAX,
2562
+ FAST_MAX_ACTIONS,
2563
+ FAST_GAP_FILL_MS,
2564
+ FAST_SEQ_AHEAD_MS,
2565
+ FAST_SNAPSHOT_HISTORY,
2566
+ FAST_MAX_FRAGMENTS,
2567
+ FAST_REASSEMBLY_SLOTS,
2568
+ FAST_MAX_RECORD_BYTES,
2569
+ FAST_HANDSHAKE_MS,
2570
+ FAST_STALL_FALLBACK_MS,
2571
+ FAST_KEEPALIVE_MS,
2572
+ FAST_KEEPALIVE_BIND_MS,
2573
+ FAST_ACK_EAGER_RECORDS,
2574
+ FAST_ACK_EAGER_GAP_MS,
2575
+ FAST_CHALLENGE_JUMP,
2576
+ FAST_FRAME_GAP_MS,
2577
+ FAST_BIND_REV,
2578
+ FAST_RETURN_PROOF,
2579
+ FAST_RETRY_BASE_MS,
2580
+ FAST_RETRY_MAX_MS,
2581
+ FAST_RETURN_STABLE_MS,
2582
+ fastRetryDelayMs,
2583
+ FastMessage,
2584
+ FastKind,
2585
+ FastFault,
2586
+ AcceptKind,
2587
+ inputRedundancyTicks,
2588
+ uplinkMaxBytes,
2589
+ gapFillMax,
2590
+ seqAheadMax,
2591
+ toHex,
2592
+ fromHex,
2593
+ isPress,
2594
+ encodeUplink,
2595
+ decodeUplink,
2596
+ renderStamp,
2597
+ uplinkFor,
2598
+ acceptUplink,
2599
+ diffSnapshot,
2600
+ deltaBase,
2601
+ encodeSnapshot,
2602
+ decodeFragment,
2603
+ parseBody,
2604
+ applyBody,
2605
+ emptyClientRx,
2606
+ receiveFragment,
2607
+ MotionRecordKind,
2608
+ RECORD_ID_MAX_CHARS,
2609
+ blankEntityMotion,
2610
+ blankSeatEcho,
2611
+ quantizeCode,
2612
+ dequantizeCode,
2613
+ encodeRecord,
2614
+ decodeRecord,
2615
+ FAST_OPEN_ROUND_TRIPS,
2616
+ FastFallbackReason,
2617
+ isFastBind,
2618
+ heldInputs,
2619
+ openFastClient,
2620
+ FAST_WT_HASH_HEX_DIGITS,
2621
+ FAST_WT_HASHES_MAX,
2622
+ fastWebTransportOf,
2623
+ webTransportOptions,
2624
+ webTransportFastOpener,
2625
+ openWebTransportFastSocket,
2626
+ chooseFastSocket
2627
+ };