@series-inc/rundot-syncplay 5.23.0-beta.7

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 (264) hide show
  1. package/README.md +610 -0
  2. package/dist/animation.d.ts +119 -0
  3. package/dist/animation.js +488 -0
  4. package/dist/asset-cooking.d.ts +82 -0
  5. package/dist/asset-cooking.js +364 -0
  6. package/dist/authority-room.d.ts +217 -0
  7. package/dist/authority-room.js +701 -0
  8. package/dist/bot-documents.d.ts +115 -0
  9. package/dist/bot-documents.js +549 -0
  10. package/dist/bot-primitives.d.ts +152 -0
  11. package/dist/bot-primitives.js +472 -0
  12. package/dist/browser.d.ts +57 -0
  13. package/dist/browser.js +53 -0
  14. package/dist/canonical.d.ts +12 -0
  15. package/dist/canonical.js +288 -0
  16. package/dist/certification.d.ts +455 -0
  17. package/dist/certification.js +1855 -0
  18. package/dist/chaos-certification.d.ts +56 -0
  19. package/dist/chaos-certification.js +1397 -0
  20. package/dist/cjs/animation.js +495 -0
  21. package/dist/cjs/asset-cooking.js +372 -0
  22. package/dist/cjs/authority-room.js +704 -0
  23. package/dist/cjs/bot-documents.js +556 -0
  24. package/dist/cjs/bot-primitives.js +482 -0
  25. package/dist/cjs/browser.js +96 -0
  26. package/dist/cjs/canonical.js +299 -0
  27. package/dist/cjs/certification.js +1860 -0
  28. package/dist/cjs/chaos-certification.js +1402 -0
  29. package/dist/cjs/collections.js +36 -0
  30. package/dist/cjs/collision.js +96 -0
  31. package/dist/cjs/commands.js +987 -0
  32. package/dist/cjs/config-bundle.js +491 -0
  33. package/dist/cjs/creator.js +30 -0
  34. package/dist/cjs/deterministic-primitives.js +134 -0
  35. package/dist/cjs/deterministic-session.js +1410 -0
  36. package/dist/cjs/ecs-lite.js +155 -0
  37. package/dist/cjs/effects-adapter.js +97 -0
  38. package/dist/cjs/engine-complete.js +2297 -0
  39. package/dist/cjs/engine-core-proof.js +70 -0
  40. package/dist/cjs/engine-parity-matrix.js +874 -0
  41. package/dist/cjs/engine-parity.js +3183 -0
  42. package/dist/cjs/engine-runtime.js +3141 -0
  43. package/dist/cjs/errors.js +22 -0
  44. package/dist/cjs/events.js +247 -0
  45. package/dist/cjs/examples/counter-duel.js +157 -0
  46. package/dist/cjs/examples/fixed-arena.js +368 -0
  47. package/dist/cjs/index.js +229 -0
  48. package/dist/cjs/input-authority.js +150 -0
  49. package/dist/cjs/input-button.js +256 -0
  50. package/dist/cjs/input-timeline.js +418 -0
  51. package/dist/cjs/instant-replay.js +66 -0
  52. package/dist/cjs/lag-compensation3d.js +837 -0
  53. package/dist/cjs/local-authority-server-runner.js +1121 -0
  54. package/dist/cjs/local-authority-wire.js +1497 -0
  55. package/dist/cjs/local-authority.js +817 -0
  56. package/dist/cjs/local-ws-adapter.js +150 -0
  57. package/dist/cjs/match-log.js +61 -0
  58. package/dist/cjs/math.js +157 -0
  59. package/dist/cjs/mock-network-adapter.js +26 -0
  60. package/dist/cjs/movement.js +703 -0
  61. package/dist/cjs/movement3d.js +1246 -0
  62. package/dist/cjs/multiclient-authority.js +308 -0
  63. package/dist/cjs/multiplayer-service.js +434 -0
  64. package/dist/cjs/navigation.js +347 -0
  65. package/dist/cjs/navmesh.js +1437 -0
  66. package/dist/cjs/networked-client.js +691 -0
  67. package/dist/cjs/networked-input-authority.js +443 -0
  68. package/dist/cjs/node.js +47 -0
  69. package/dist/cjs/number-lab.js +78 -0
  70. package/dist/cjs/offline-session.js +370 -0
  71. package/dist/cjs/package.json +1 -0
  72. package/dist/cjs/pause-resume.js +43 -0
  73. package/dist/cjs/physics-cert.js +426 -0
  74. package/dist/cjs/physics2d.js +751 -0
  75. package/dist/cjs/physics3d.js +1996 -0
  76. package/dist/cjs/prediction-culling.js +203 -0
  77. package/dist/cjs/protocol-lab.js +1299 -0
  78. package/dist/cjs/r3f-render-adapter.js +209 -0
  79. package/dist/cjs/random.js +59 -0
  80. package/dist/cjs/replay-bundle.js +1598 -0
  81. package/dist/cjs/replay-inspect.js +236 -0
  82. package/dist/cjs/replay-scoring.js +68 -0
  83. package/dist/cjs/replay.js +356 -0
  84. package/dist/cjs/reports.js +181 -0
  85. package/dist/cjs/rollback-history.js +251 -0
  86. package/dist/cjs/runtime-guards.js +111 -0
  87. package/dist/cjs/sample-lab.js +145 -0
  88. package/dist/cjs/sample-scenes.js +752 -0
  89. package/dist/cjs/schema-artifacts.js +359 -0
  90. package/dist/cjs/schema-codegen.js +544 -0
  91. package/dist/cjs/schema-dsl-binary.js +655 -0
  92. package/dist/cjs/schema.js +319 -0
  93. package/dist/cjs/sdk-offline.js +99 -0
  94. package/dist/cjs/session-promotion.js +74 -0
  95. package/dist/cjs/session-snapshot.js +265 -0
  96. package/dist/cjs/session-wire.js +216 -0
  97. package/dist/cjs/signals.js +103 -0
  98. package/dist/cjs/sparse-set.js +169 -0
  99. package/dist/cjs/synctest.js +136 -0
  100. package/dist/cjs/system-lifecycle.js +286 -0
  101. package/dist/cjs/timers.js +61 -0
  102. package/dist/cjs/tools/static-checker.js +1468 -0
  103. package/dist/cjs/types.js +2 -0
  104. package/dist/cjs/venus-mp-budget.js +125 -0
  105. package/dist/cjs/ws-frame.js +255 -0
  106. package/dist/cli.d.ts +2 -0
  107. package/dist/cli.js +11379 -0
  108. package/dist/collections.d.ts +9 -0
  109. package/dist/collections.js +30 -0
  110. package/dist/collision.d.ts +25 -0
  111. package/dist/collision.js +85 -0
  112. package/dist/commands.d.ts +137 -0
  113. package/dist/commands.js +977 -0
  114. package/dist/config-bundle.d.ts +89 -0
  115. package/dist/config-bundle.js +483 -0
  116. package/dist/creator.d.ts +20 -0
  117. package/dist/creator.js +11 -0
  118. package/dist/deterministic-primitives.d.ts +59 -0
  119. package/dist/deterministic-primitives.js +126 -0
  120. package/dist/deterministic-session.d.ts +197 -0
  121. package/dist/deterministic-session.js +1401 -0
  122. package/dist/ecs-lite.d.ts +26 -0
  123. package/dist/ecs-lite.js +152 -0
  124. package/dist/effects-adapter.d.ts +21 -0
  125. package/dist/effects-adapter.js +94 -0
  126. package/dist/engine-complete.d.ts +45 -0
  127. package/dist/engine-complete.js +2292 -0
  128. package/dist/engine-core-proof.d.ts +26 -0
  129. package/dist/engine-core-proof.js +66 -0
  130. package/dist/engine-parity-matrix.d.ts +300 -0
  131. package/dist/engine-parity-matrix.js +869 -0
  132. package/dist/engine-parity.d.ts +74 -0
  133. package/dist/engine-parity.js +3178 -0
  134. package/dist/engine-runtime.d.ts +351 -0
  135. package/dist/engine-runtime.js +3123 -0
  136. package/dist/errors.d.ts +11 -0
  137. package/dist/errors.js +17 -0
  138. package/dist/events.d.ts +45 -0
  139. package/dist/events.js +242 -0
  140. package/dist/examples/counter-duel.d.ts +43 -0
  141. package/dist/examples/counter-duel.js +150 -0
  142. package/dist/examples/fixed-arena.d.ts +110 -0
  143. package/dist/examples/fixed-arena.js +356 -0
  144. package/dist/index.d.ts +101 -0
  145. package/dist/index.js +66 -0
  146. package/dist/input-authority.d.ts +82 -0
  147. package/dist/input-authority.js +147 -0
  148. package/dist/input-button.d.ts +36 -0
  149. package/dist/input-button.js +249 -0
  150. package/dist/input-timeline.d.ts +65 -0
  151. package/dist/input-timeline.js +412 -0
  152. package/dist/instant-replay.d.ts +59 -0
  153. package/dist/instant-replay.js +62 -0
  154. package/dist/lag-compensation3d.d.ts +146 -0
  155. package/dist/lag-compensation3d.js +827 -0
  156. package/dist/local-authority-server-runner.d.ts +79 -0
  157. package/dist/local-authority-server-runner.js +1118 -0
  158. package/dist/local-authority-wire.d.ts +142 -0
  159. package/dist/local-authority-wire.js +1477 -0
  160. package/dist/local-authority.d.ts +160 -0
  161. package/dist/local-authority.js +810 -0
  162. package/dist/local-ws-adapter.d.ts +14 -0
  163. package/dist/local-ws-adapter.js +146 -0
  164. package/dist/match-log.d.ts +74 -0
  165. package/dist/match-log.js +57 -0
  166. package/dist/math.d.ts +41 -0
  167. package/dist/math.js +154 -0
  168. package/dist/mock-network-adapter.d.ts +13 -0
  169. package/dist/mock-network-adapter.js +23 -0
  170. package/dist/movement.d.ts +155 -0
  171. package/dist/movement.js +694 -0
  172. package/dist/movement3d.d.ts +195 -0
  173. package/dist/movement3d.js +1238 -0
  174. package/dist/multiclient-authority.d.ts +30 -0
  175. package/dist/multiclient-authority.js +305 -0
  176. package/dist/multiplayer-service.d.ts +153 -0
  177. package/dist/multiplayer-service.js +429 -0
  178. package/dist/navigation.d.ts +91 -0
  179. package/dist/navigation.js +338 -0
  180. package/dist/navmesh.d.ts +236 -0
  181. package/dist/navmesh.js +1423 -0
  182. package/dist/networked-client.d.ts +124 -0
  183. package/dist/networked-client.js +688 -0
  184. package/dist/networked-input-authority.d.ts +51 -0
  185. package/dist/networked-input-authority.js +439 -0
  186. package/dist/node.d.ts +22 -0
  187. package/dist/node.js +19 -0
  188. package/dist/number-lab.d.ts +15 -0
  189. package/dist/number-lab.js +75 -0
  190. package/dist/offline-session.d.ts +2 -0
  191. package/dist/offline-session.js +367 -0
  192. package/dist/pause-resume.d.ts +48 -0
  193. package/dist/pause-resume.js +40 -0
  194. package/dist/physics-cert.d.ts +40 -0
  195. package/dist/physics-cert.js +389 -0
  196. package/dist/physics2d.d.ts +142 -0
  197. package/dist/physics2d.js +741 -0
  198. package/dist/physics3d.d.ts +285 -0
  199. package/dist/physics3d.js +1979 -0
  200. package/dist/prediction-culling.d.ts +34 -0
  201. package/dist/prediction-culling.js +200 -0
  202. package/dist/protocol-lab.d.ts +115 -0
  203. package/dist/protocol-lab.js +1295 -0
  204. package/dist/r3f-render-adapter.d.ts +61 -0
  205. package/dist/r3f-render-adapter.js +203 -0
  206. package/dist/random.d.ts +13 -0
  207. package/dist/random.js +55 -0
  208. package/dist/replay-bundle.d.ts +150 -0
  209. package/dist/replay-bundle.js +1586 -0
  210. package/dist/replay-inspect.d.ts +60 -0
  211. package/dist/replay-inspect.js +228 -0
  212. package/dist/replay-scoring.d.ts +28 -0
  213. package/dist/replay-scoring.js +65 -0
  214. package/dist/replay.d.ts +17 -0
  215. package/dist/replay.js +351 -0
  216. package/dist/reports.d.ts +176 -0
  217. package/dist/reports.js +174 -0
  218. package/dist/rollback-history.d.ts +46 -0
  219. package/dist/rollback-history.js +247 -0
  220. package/dist/runtime-guards.d.ts +1 -0
  221. package/dist/runtime-guards.js +108 -0
  222. package/dist/sample-lab.d.ts +32 -0
  223. package/dist/sample-lab.js +134 -0
  224. package/dist/sample-scenes.d.ts +86 -0
  225. package/dist/sample-scenes.js +745 -0
  226. package/dist/schema-artifacts.d.ts +49 -0
  227. package/dist/schema-artifacts.js +356 -0
  228. package/dist/schema-codegen.d.ts +21 -0
  229. package/dist/schema-codegen.js +538 -0
  230. package/dist/schema-dsl-binary.d.ts +75 -0
  231. package/dist/schema-dsl-binary.js +648 -0
  232. package/dist/schema.d.ts +87 -0
  233. package/dist/schema.js +315 -0
  234. package/dist/sdk-offline.d.ts +34 -0
  235. package/dist/sdk-offline.js +96 -0
  236. package/dist/session-promotion.d.ts +26 -0
  237. package/dist/session-promotion.js +70 -0
  238. package/dist/session-snapshot.d.ts +95 -0
  239. package/dist/session-snapshot.js +255 -0
  240. package/dist/session-wire.d.ts +89 -0
  241. package/dist/session-wire.js +211 -0
  242. package/dist/signals.d.ts +31 -0
  243. package/dist/signals.js +99 -0
  244. package/dist/sparse-set.d.ts +34 -0
  245. package/dist/sparse-set.js +165 -0
  246. package/dist/synctest.d.ts +31 -0
  247. package/dist/synctest.js +133 -0
  248. package/dist/system-lifecycle.d.ts +72 -0
  249. package/dist/system-lifecycle.js +281 -0
  250. package/dist/timers.d.ts +17 -0
  251. package/dist/timers.js +53 -0
  252. package/dist/tools/index.d.ts +4 -0
  253. package/dist/tools/index.js +4 -0
  254. package/dist/tools/static-checker.d.ts +31 -0
  255. package/dist/tools/static-checker.js +1462 -0
  256. package/dist/tools/vite-plugin.d.ts +69 -0
  257. package/dist/tools/vite-plugin.js +203 -0
  258. package/dist/types.d.ts +148 -0
  259. package/dist/types.js +1 -0
  260. package/dist/venus-mp-budget.d.ts +29 -0
  261. package/dist/venus-mp-budget.js +122 -0
  262. package/dist/ws-frame.d.ts +49 -0
  263. package/dist/ws-frame.js +247 -0
  264. package/package.json +169 -0
@@ -0,0 +1,817 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDeterministicLocalAuthorityRuntime = createDeterministicLocalAuthorityRuntime;
4
+ exports.runDeterministicLocalAuthorityFixture = runDeterministicLocalAuthorityFixture;
5
+ exports.runDeterministicLocalAuthorityRuntimePolicyFixture = runDeterministicLocalAuthorityRuntimePolicyFixture;
6
+ exports.runDeterministicOfflineToAuthorityUpgradeFixture = runDeterministicOfflineToAuthorityUpgradeFixture;
7
+ exports.runDeterministicLocalAuthorityReplayMinimizerFixture = runDeterministicLocalAuthorityReplayMinimizerFixture;
8
+ const canonical_js_1 = require("./canonical.js");
9
+ const offline_session_js_1 = require("./offline-session.js");
10
+ const random_js_1 = require("./random.js");
11
+ const replay_bundle_js_1 = require("./replay-bundle.js");
12
+ function createDeterministicLocalAuthorityRuntime(players, options = {}) {
13
+ const maxFrameLead = options.maxFrameLead ?? 12;
14
+ const rollbackWindowFrames = options.rollbackWindowFrames ?? 0;
15
+ const stateReducer = options.stateReducer ?? defaultLocalAuthorityStateReducer;
16
+ const retainAcceptedMessages = rollbackWindowFrames > 0 || options.stateReducer !== undefined;
17
+ const authorityQueue = new Map();
18
+ const deliveryQueue = new Map();
19
+ const acceptedMessages = [];
20
+ const clientStateBySlot = Array.from({ length: players }, () => 0);
21
+ const lateJoinSlots = new Set();
22
+ const spectatorSlots = new Set();
23
+ const acceptedInputIds = new Set();
24
+ let commandChecksum = (0, canonical_js_1.defaultChecksum)({ kind: 'local-authority-runtime', players });
25
+ let state = {
26
+ frame: 0,
27
+ players,
28
+ clients: Array.from({ length: players }, (_, slot) => ({
29
+ slot,
30
+ connected: true,
31
+ spectator: false,
32
+ inputDelayFrames: 0,
33
+ lastAckFrame: 0,
34
+ })),
35
+ stateBySlot: Array.from({ length: players }, () => 0),
36
+ rejectedMessages: 0,
37
+ rollbackDepths: [],
38
+ clockDriftFrames: [],
39
+ };
40
+ function updateClient(slot, updater) {
41
+ assertSlot(state, slot);
42
+ state = {
43
+ ...state,
44
+ clients: state.clients.map((client) => client.slot === slot ? updater(client) : client),
45
+ };
46
+ }
47
+ const runtime = {
48
+ get state() {
49
+ return (0, canonical_js_1.cloneCanonical)(state);
50
+ },
51
+ submitInput(message) {
52
+ if (message.inputId !== undefined && !isValidInputId(message.inputId)) {
53
+ state = { ...state, rejectedMessages: state.rejectedMessages + 1 };
54
+ return false;
55
+ }
56
+ if (!Number.isInteger(message.slot) ||
57
+ message.slot < 0 ||
58
+ message.slot >= players ||
59
+ (message.senderSlot !== undefined && (!Number.isInteger(message.senderSlot) || message.senderSlot !== message.slot)) ||
60
+ !Number.isInteger(message.frame) ||
61
+ !Number.isFinite(message.value) ||
62
+ !Number.isInteger(message.value) ||
63
+ (message.delay !== undefined && (!Number.isInteger(message.delay) || message.delay < 0))) {
64
+ state = { ...state, rejectedMessages: state.rejectedMessages + 1 };
65
+ return false;
66
+ }
67
+ const frameInputId = defaultAuthorityInputId(message);
68
+ const inputId = message.inputId ?? frameInputId;
69
+ if (acceptedInputIds.has(inputId) || acceptedInputIds.has(frameInputId)) {
70
+ state = { ...state, rejectedMessages: state.rejectedMessages + 1 };
71
+ return false;
72
+ }
73
+ let inputData;
74
+ if (message.data !== undefined) {
75
+ try {
76
+ inputData = (0, canonical_js_1.cloneCanonical)(message.data);
77
+ }
78
+ catch {
79
+ state = { ...state, rejectedMessages: state.rejectedMessages + 1 };
80
+ return false;
81
+ }
82
+ }
83
+ if (message.frame < state.frame - rollbackWindowFrames ||
84
+ message.frame > state.frame + maxFrameLead) {
85
+ state = { ...state, rejectedMessages: state.rejectedMessages + 1 };
86
+ return false;
87
+ }
88
+ const client = state.clients[message.slot];
89
+ if (client.connected !== true || client.spectator === true) {
90
+ state = { ...state, rejectedMessages: state.rejectedMessages + 1 };
91
+ return false;
92
+ }
93
+ const delay = message.delay ?? client.inputDelayFrames;
94
+ const deliveryFrame = message.frame + Math.max(0, delay);
95
+ acceptedInputIds.add(inputId);
96
+ acceptedInputIds.add(frameInputId);
97
+ commandChecksum = (0, canonical_js_1.defaultChecksum)({ commandChecksum, inputId, frameInputId, slot: message.slot, frame: message.frame, value: message.value, data: inputData });
98
+ const acceptedMessage = {
99
+ slot: message.slot,
100
+ frame: message.frame,
101
+ delay,
102
+ deliveryFrame,
103
+ value: message.value,
104
+ ...(inputData === undefined ? {} : { data: inputData }),
105
+ };
106
+ enqueueAuthorityFrameMessage(authorityQueue, acceptedMessage);
107
+ enqueueAuthorityDeliveryMessage(deliveryQueue, acceptedMessage);
108
+ if (retainAcceptedMessages) {
109
+ acceptedMessages.push(acceptedMessage);
110
+ }
111
+ return true;
112
+ },
113
+ disconnect(slot) {
114
+ updateClient(slot, (client) => ({ ...client, connected: false }));
115
+ },
116
+ reconnect(slot) {
117
+ updateClient(slot, (client) => ({ ...client, connected: true, lastAckFrame: state.frame }));
118
+ },
119
+ lateJoin(slot) {
120
+ assertSlot(state, slot);
121
+ lateJoinSlots.add(slot);
122
+ clientStateBySlot[slot] = state.stateBySlot[slot];
123
+ updateClient(slot, (client) => ({ ...client, connected: true, lastAckFrame: state.frame }));
124
+ },
125
+ addSpectator(slot) {
126
+ assertSlot(state, slot);
127
+ spectatorSlots.add(slot);
128
+ removeQueuedMessagesForSlot(authorityQueue, slot);
129
+ removeQueuedMessagesForSlot(deliveryQueue, slot);
130
+ removeAcceptedMessagesForSlotFromFrame(acceptedMessages, slot, state.frame);
131
+ clientStateBySlot[slot] = state.stateBySlot[slot];
132
+ updateClient(slot, (client) => ({ ...client, spectator: true }));
133
+ },
134
+ step() {
135
+ state = retainAcceptedMessages
136
+ ? applyAuthorityMessages(state, authorityQueue, acceptedMessages, state.frame, stateReducer)
137
+ : applyQueuedAuthorityMessages(state, authorityQueue, state.frame, stateReducer);
138
+ deliverClientMessages(deliveryQueue, clientStateBySlot, state.frame, stateReducer);
139
+ state = { ...state, frame: state.frame + 1 };
140
+ },
141
+ snapshot() {
142
+ return (0, canonical_js_1.cloneCanonical)({
143
+ state,
144
+ authorityQueue: serializeAuthorityQueue(authorityQueue),
145
+ deliveryQueue: serializeDeliveryQueue(deliveryQueue),
146
+ acceptedMessages: [...acceptedMessages].sort(compareAuthorityFrameMessages),
147
+ clientStateBySlot,
148
+ commandChecksum,
149
+ lateJoinSlots: [...lateJoinSlots].sort(compareNumbers),
150
+ spectatorSlots: [...spectatorSlots].sort(compareNumbers),
151
+ acceptedInputIds: [...acceptedInputIds].sort(compareStrings),
152
+ });
153
+ },
154
+ restore(snapshot) {
155
+ const restoredState = (0, canonical_js_1.cloneCanonical)(snapshot.state);
156
+ state = {
157
+ ...restoredState,
158
+ stateBySlot: Array.from({ length: restoredState.players }, (_, slot) => restoredState.stateBySlot[slot] ?? 0),
159
+ };
160
+ restoreAuthorityQueue(authorityQueue, snapshot.authorityQueue ?? []);
161
+ restoreDeliveryQueue(deliveryQueue, snapshot.deliveryQueue);
162
+ acceptedMessages.length = 0;
163
+ acceptedMessages.push(...(snapshot.acceptedMessages ?? []).map((message) => (0, canonical_js_1.cloneCanonical)(message)));
164
+ for (let slot = 0; slot < clientStateBySlot.length; slot += 1) {
165
+ clientStateBySlot[slot] = snapshot.clientStateBySlot[slot] ?? 0;
166
+ }
167
+ commandChecksum = snapshot.commandChecksum;
168
+ lateJoinSlots.clear();
169
+ snapshot.lateJoinSlots.forEach((slot) => lateJoinSlots.add(slot));
170
+ spectatorSlots.clear();
171
+ snapshot.spectatorSlots.forEach((slot) => spectatorSlots.add(slot));
172
+ acceptedInputIds.clear();
173
+ (snapshot.acceptedInputIds ?? []).forEach((inputId) => acceptedInputIds.add(inputId));
174
+ },
175
+ checksums() {
176
+ const projectedClientStateBySlot = retainAcceptedMessages
177
+ ? projectClientMessages(players, acceptedMessages, stateReducer)
178
+ : projectDeliveryMessages(deliveryQueue, clientStateBySlot, stateReducer);
179
+ const authorityProjectionChecksum = (0, canonical_js_1.defaultChecksum)(state.stateBySlot);
180
+ const clientProjectionChecksum = (0, canonical_js_1.defaultChecksum)(projectedClientStateBySlot);
181
+ return {
182
+ authorityChecksum: (0, canonical_js_1.defaultChecksum)(state),
183
+ clientChecksum: (0, canonical_js_1.defaultChecksum)(clientStateBySlot),
184
+ authorityProjectionChecksum,
185
+ clientProjectionChecksum,
186
+ convergence: authorityProjectionChecksum === clientProjectionChecksum,
187
+ commandChecksum,
188
+ };
189
+ },
190
+ };
191
+ return runtime;
192
+ }
193
+ function defaultAuthorityInputId(message) {
194
+ return `slot:${message.slot}:frame:${message.frame}`;
195
+ }
196
+ function isValidInputId(inputId) {
197
+ return /^[A-Za-z0-9:._-]{1,128}$/.test(inputId);
198
+ }
199
+ function runDeterministicLocalAuthorityFixture(players, frames, seed) {
200
+ const random = new random_js_1.DeterministicRandom(seed);
201
+ const lateJoinSlots = new Set(Array.from({ length: players }, (_, slot) => slot).filter((slot) => slot > 0 && slot % 47 === 0));
202
+ const spectatorSlots = new Set(Array.from({ length: players }, (_, slot) => slot).filter((slot) => slot > 0 && slot % 53 === 0 && !lateJoinSlots.has(slot)));
203
+ let state = {
204
+ frame: 0,
205
+ players,
206
+ clients: Array.from({ length: players }, (_, slot) => ({
207
+ slot,
208
+ connected: !lateJoinSlots.has(slot),
209
+ spectator: false,
210
+ inputDelayFrames: 2 + (slot % 4),
211
+ lastAckFrame: 0,
212
+ })),
213
+ stateBySlot: Array.from({ length: players }, () => 0),
214
+ rejectedMessages: 0,
215
+ rollbackDepths: [],
216
+ clockDriftFrames: [],
217
+ };
218
+ let reconnects = 0;
219
+ let lateJoins = 0;
220
+ let spectators = 0;
221
+ let absentBeforeJoinInputs = 0;
222
+ let lateJoinReconstructions = 0;
223
+ let spectatorReconstructions = 0;
224
+ let acceptedMessages = 0;
225
+ let commandChecksum = (0, canonical_js_1.defaultChecksum)({ kind: 'local-authority-command-stream', seed, players });
226
+ const migrationFrame = Math.floor(frames / 2);
227
+ let persisted;
228
+ let migrationQueueRestored = false;
229
+ let migrationCommandTimelineRestored = false;
230
+ let slotReservationsRestored = false;
231
+ const deliveryQueue = new Map();
232
+ const rollbackDepthHistogram = new Map();
233
+ const clockDriftHistogram = new Map();
234
+ const clientStateBySlot = Array.from({ length: players }, () => 0);
235
+ const costs = [];
236
+ for (let frame = 0; frame < frames; frame += 1) {
237
+ if (frame === migrationFrame) {
238
+ persisted = persistAuthorityRuntime(state, deliveryQueue, clientStateBySlot, commandChecksum, lateJoinSlots, spectatorSlots);
239
+ state = (0, canonical_js_1.cloneCanonical)(persisted.state);
240
+ restoreDeliveryQueue(deliveryQueue, persisted.deliveryQueue);
241
+ for (let slot = 0; slot < clientStateBySlot.length; slot += 1) {
242
+ clientStateBySlot[slot] = persisted.clientStateBySlot[slot];
243
+ }
244
+ migrationQueueRestored = (0, canonical_js_1.defaultChecksum)(serializeDeliveryQueue(deliveryQueue)) === (0, canonical_js_1.defaultChecksum)(persisted.deliveryQueue);
245
+ migrationCommandTimelineRestored = commandChecksum === persisted.commandChecksum;
246
+ slotReservationsRestored = (0, canonical_js_1.defaultChecksum)([...lateJoinSlots].sort(compareNumbers)) === (0, canonical_js_1.defaultChecksum)(persisted.lateJoinSlots)
247
+ && (0, canonical_js_1.defaultChecksum)([...spectatorSlots].sort(compareNumbers)) === (0, canonical_js_1.defaultChecksum)(persisted.spectatorSlots);
248
+ }
249
+ const clients = state.clients.map((client) => {
250
+ if (frame === 7 && client.slot % 31 === 0) {
251
+ reconnects += 1;
252
+ return { ...client, connected: false };
253
+ }
254
+ if (frame === 11 && client.slot % 31 === 0) {
255
+ return { ...client, connected: true, lastAckFrame: frame };
256
+ }
257
+ if (frame === 13 && lateJoinSlots.has(client.slot) && !client.connected) {
258
+ lateJoins += 1;
259
+ lateJoinReconstructions += 1;
260
+ return { ...client, connected: true, lastAckFrame: frame };
261
+ }
262
+ if (frame === 17 && spectatorSlots.has(client.slot) && !client.spectator) {
263
+ spectators += 1;
264
+ removeQueuedMessagesForSlot(deliveryQueue, client.slot);
265
+ clientStateBySlot[client.slot] = state.stateBySlot[client.slot];
266
+ spectatorReconstructions += 1;
267
+ return { ...client, spectator: true };
268
+ }
269
+ return client;
270
+ });
271
+ let rejectedMessages = state.rejectedMessages;
272
+ const nextStateBySlot = [...state.stateBySlot];
273
+ for (const client of clients) {
274
+ const drift = random.nextInt(0, 3) - 1;
275
+ incrementHistogram(clockDriftHistogram, Math.abs(drift));
276
+ if (!client.connected || client.spectator) {
277
+ absentBeforeJoinInputs += !client.connected && lateJoinSlots.has(client.slot) ? 1 : 0;
278
+ continue;
279
+ }
280
+ if (client.slot === players - 1 && frame % 29 === 0) {
281
+ rejectedMessages += 1;
282
+ }
283
+ else {
284
+ acceptedMessages += 1;
285
+ const delay = client.inputDelayFrames + drift;
286
+ const value = frame + client.slot + delay;
287
+ commandChecksum = (0, canonical_js_1.defaultChecksum)({ commandChecksum, slot: client.slot, frame, value });
288
+ enqueueAuthorityDeliveryMessage(deliveryQueue, {
289
+ slot: client.slot,
290
+ frame,
291
+ delay,
292
+ deliveryFrame: frame + Math.max(0, delay),
293
+ value,
294
+ });
295
+ nextStateBySlot[client.slot] += value;
296
+ }
297
+ const delay = client.inputDelayFrames + drift;
298
+ incrementHistogram(rollbackDepthHistogram, Math.max(0, delay));
299
+ }
300
+ deliverClientMessages(deliveryQueue, clientStateBySlot, frame, defaultLocalAuthorityStateReducer);
301
+ state = {
302
+ frame: frame + 1,
303
+ players,
304
+ clients,
305
+ stateBySlot: nextStateBySlot,
306
+ rejectedMessages,
307
+ rollbackDepths: [],
308
+ clockDriftFrames: [],
309
+ };
310
+ costs.push(players / 10_000 + acceptedMessages / 1_000_000);
311
+ }
312
+ deliverAllClientMessages(deliveryQueue, clientStateBySlot);
313
+ const authorityProjectionChecksum = (0, canonical_js_1.defaultChecksum)(state.stateBySlot);
314
+ const clientProjectionChecksum = (0, canonical_js_1.defaultChecksum)(clientStateBySlot);
315
+ return {
316
+ authorityChecksum: (0, canonical_js_1.defaultChecksum)(state),
317
+ clientChecksum: (0, canonical_js_1.defaultChecksum)(clientStateBySlot),
318
+ authorityProjectionChecksum,
319
+ clientProjectionChecksum,
320
+ convergence: authorityProjectionChecksum === clientProjectionChecksum,
321
+ players,
322
+ frames,
323
+ reconnects,
324
+ lateJoins,
325
+ spectators,
326
+ absentBeforeJoinInputs,
327
+ lateJoinReconstructions,
328
+ spectatorReconstructions,
329
+ rejectedMessages: state.rejectedMessages,
330
+ migrationFrame,
331
+ migrationPersistedSections: persisted === undefined ? 0 : 5,
332
+ migrationQueueRestored,
333
+ migrationCommandTimelineRestored,
334
+ slotReservationsRestored,
335
+ inputDelayP95: percentile(state.clients.map((client) => client.inputDelayFrames), 95),
336
+ rollbackDepthP95: percentileFromHistogram(rollbackDepthHistogram, 95),
337
+ clockDriftP95: percentileFromHistogram(clockDriftHistogram, 95),
338
+ p95FrameCost: percentile(costs, 95),
339
+ p99FrameCost: percentile(costs, 99),
340
+ };
341
+ }
342
+ function runDeterministicLocalAuthorityRuntimePolicyFixture() {
343
+ const runtime = createDeterministicLocalAuthorityRuntime(2, { maxFrameLead: 4, rollbackWindowFrames: 2 });
344
+ runtime.step();
345
+ runtime.step();
346
+ const inputData = {
347
+ axisX: 2,
348
+ fire: true,
349
+ buttons: ['jump', 'dash'],
350
+ aim: { x: 7, y: -3 },
351
+ };
352
+ const lateWithinRollbackWindowAccepted = runtime.submitInput({
353
+ slot: 0,
354
+ frame: 1,
355
+ value: 5,
356
+ data: inputData,
357
+ delay: 0,
358
+ inputId: 'late-structured-input',
359
+ });
360
+ const queuedSnapshot = runtime.snapshot();
361
+ const queuedData = queuedSnapshot.authorityQueue
362
+ .flatMap((entry) => entry.messages)
363
+ .find((message) => message.slot === 0 && message.frame === 1)?.data;
364
+ runtime.step();
365
+ const afterLateStep = runtime.checksums();
366
+ const expiredLateInputRejected = runtime.submitInput({
367
+ slot: 1,
368
+ frame: 0,
369
+ value: 7,
370
+ inputId: 'expired-late-input',
371
+ }) === false;
372
+ const strictRuntime = createDeterministicLocalAuthorityRuntime(1, { maxFrameLead: 4 });
373
+ strictRuntime.step();
374
+ const strictStaleInputRejected = strictRuntime.submitInput({
375
+ slot: 0,
376
+ frame: 0,
377
+ value: 3,
378
+ inputId: 'strict-stale-input',
379
+ }) === false;
380
+ const malformedRuntime = createDeterministicLocalAuthorityRuntime(1, { maxFrameLead: 4, rollbackWindowFrames: 1 });
381
+ const malformedStructuredInputRejected = malformedRuntime.submitInput({
382
+ slot: 0,
383
+ frame: 0,
384
+ value: 1,
385
+ data: Symbol('malformed-structured-input'),
386
+ inputId: 'malformed-structured-input',
387
+ }) === false;
388
+ const restored = createDeterministicLocalAuthorityRuntime(2, { maxFrameLead: 4, rollbackWindowFrames: 2 });
389
+ restored.restore(queuedSnapshot);
390
+ restored.step();
391
+ const restoredAfterLateStep = restored.checksums();
392
+ let clientProjectionValueAfterLateInput = -1;
393
+ /* istanbul ignore else -- this fixture's rollbackEquivalent assertion requires convergence; the else value is diagnostic only. */
394
+ if (afterLateStep.convergence) {
395
+ clientProjectionValueAfterLateInput = runtime.state.stateBySlot[0];
396
+ }
397
+ const nonCommutative = runNonCommutativeLateInputResimulationFixture();
398
+ return {
399
+ checksum: afterLateStep.authorityProjectionChecksum,
400
+ restoredChecksum: restoredAfterLateStep.authorityProjectionChecksum,
401
+ rollbackEquivalent: afterLateStep.authorityProjectionChecksum === restoredAfterLateStep.authorityProjectionChecksum
402
+ && afterLateStep.clientProjectionChecksum === restoredAfterLateStep.clientProjectionChecksum,
403
+ nonCommutativeRuntimeChecksum: nonCommutative.runtimeChecksum,
404
+ nonCommutativeFullReplayChecksum: nonCommutative.fullReplayChecksum,
405
+ nonCommutativeAppendOnlyChecksum: nonCommutative.appendOnlyChecksum,
406
+ nonCommutativeLateInputResimulated: nonCommutative.runtimeChecksum === nonCommutative.fullReplayChecksum,
407
+ nonCommutativeAppendOnlyDiverged: nonCommutative.appendOnlyChecksum !== nonCommutative.fullReplayChecksum,
408
+ nonCommutativeResimulatedFrames: nonCommutative.resimulatedFrames,
409
+ lateWithinRollbackWindowAccepted,
410
+ expiredLateInputRejected,
411
+ strictStaleInputRejected,
412
+ structuredInputDataPreserved: (0, canonical_js_1.defaultChecksum)(queuedData) === (0, canonical_js_1.defaultChecksum)(inputData),
413
+ malformedStructuredInputRejected,
414
+ structuredInputChecksum: (0, canonical_js_1.defaultChecksum)(inputData),
415
+ authorityValueAfterLateInput: runtime.state.stateBySlot[0],
416
+ clientProjectionValueAfterLateInput,
417
+ rollbackWindowFrames: 2,
418
+ };
419
+ }
420
+ function runDeterministicOfflineToAuthorityUpgradeFixture(players, frames, seed) {
421
+ const random = new random_js_1.DeterministicRandom(seed);
422
+ const inputsByFrame = [];
423
+ for (let frame = 0; frame < frames; frame += 1) {
424
+ const frameInputs = [];
425
+ for (let slot = 0; slot < players; slot += 1) {
426
+ frameInputs.push((random.nextInt(0, 7) + frame + slot) % 11);
427
+ }
428
+ inputsByFrame.push(frameInputs);
429
+ }
430
+ const offline = (0, offline_session_js_1.createOfflineSession)({
431
+ tickRate: 60,
432
+ playerCount: players,
433
+ initialState: { stateBySlot: Array.from({ length: players }, () => 0), frame: 0 },
434
+ defaultInput: { value: 0 },
435
+ inputSchemaId: 'offline-authority-input/v1',
436
+ stateSchemaId: 'offline-authority-state/v1',
437
+ deterministicVersion: 'offline-authority-upgrade@1',
438
+ snapshotBufferSize: Math.max(8, Math.min(frames + 1, 512)),
439
+ step: (state, inputs, frame) => ({
440
+ stateBySlot: state.stateBySlot.map((value, slot) => value + inputs[slot].value),
441
+ frame: frame + 1,
442
+ }),
443
+ });
444
+ const authority = createDeterministicLocalAuthorityRuntime(players, { maxFrameLead: frames + 4 });
445
+ let snapshotFrame = Math.floor(frames / 2);
446
+ let authoritySnapshot;
447
+ for (let frame = 0; frame < frames; frame += 1) {
448
+ const frameInputs = inputsByFrame[frame];
449
+ for (let slot = 0; slot < players; slot += 1) {
450
+ const value = frameInputs[slot];
451
+ offline.setInput(slot, { value });
452
+ authority.submitInput({
453
+ slot,
454
+ frame,
455
+ value,
456
+ delay: 0,
457
+ inputId: `offline-authority:${frame}:${slot}`,
458
+ });
459
+ }
460
+ offline.stepFrames(1);
461
+ authority.step();
462
+ if (frame + 1 === snapshotFrame) {
463
+ authoritySnapshot = authority.snapshot();
464
+ }
465
+ }
466
+ if (authoritySnapshot === undefined) {
467
+ snapshotFrame = frames;
468
+ authoritySnapshot = authority.snapshot();
469
+ }
470
+ const restored = createDeterministicLocalAuthorityRuntime(players, { maxFrameLead: frames + 4 });
471
+ restored.restore(authoritySnapshot);
472
+ for (let frame = snapshotFrame; frame < frames; frame += 1) {
473
+ const frameInputs = inputsByFrame[frame];
474
+ for (let slot = 0; slot < players; slot += 1) {
475
+ restored.submitInput({
476
+ slot,
477
+ frame,
478
+ value: frameInputs[slot],
479
+ delay: 0,
480
+ inputId: `offline-authority-restored:${frame}:${slot}`,
481
+ });
482
+ }
483
+ restored.step();
484
+ }
485
+ const offlineState = offline.getState();
486
+ const replay = offline.exportReplay();
487
+ const authorityChecksums = authority.checksums();
488
+ const restoredChecksums = restored.checksums();
489
+ const offlineChecksum = (0, canonical_js_1.defaultChecksum)(offlineState.stateBySlot);
490
+ const replayFinalChecksum = replay.checksums.at(-1)?.checksum ?? '';
491
+ const offlineStateChecksum = (0, canonical_js_1.defaultChecksum)(offlineState);
492
+ const authorityChecksum = authorityChecksums.authorityProjectionChecksum;
493
+ const authorityRestoredChecksum = restoredChecksums.authorityProjectionChecksum;
494
+ return {
495
+ checksum: (0, canonical_js_1.defaultChecksum)({
496
+ offlineChecksum,
497
+ authorityChecksum,
498
+ authorityRestoredChecksum,
499
+ replayChecksum: (0, canonical_js_1.defaultChecksum)(replay),
500
+ }),
501
+ players,
502
+ frames,
503
+ offlineFrame: offline.currentFrame,
504
+ authorityFrame: authority.state.frame,
505
+ offlineChecksum,
506
+ authorityChecksum,
507
+ authorityRestoredChecksum,
508
+ replayChecksum: (0, canonical_js_1.defaultChecksum)(replay),
509
+ stateMatch: offlineChecksum === authorityChecksum,
510
+ replayVerified: replayFinalChecksum === offlineStateChecksum,
511
+ restoreVerified: authorityRestoredChecksum === authorityChecksum,
512
+ authorityConvergence: authorityChecksums.convergence,
513
+ inputRows: replay.inputFrames.reduce((sum, inputFrame) => sum + inputFrame.inputs.length, 0),
514
+ snapshotFrame,
515
+ };
516
+ }
517
+ function runDeterministicLocalAuthorityReplayMinimizerFixture(players, frames, seed) {
518
+ const random = new random_js_1.DeterministicRandom(seed);
519
+ const runtime = createDeterministicLocalAuthorityRuntime(players);
520
+ const inputHistory = [];
521
+ const checksums = [];
522
+ const snapshots = [];
523
+ const debugEvents = [];
524
+ for (let frame = 0; frame < frames; frame += 1) {
525
+ const inputs = [];
526
+ for (let slot = 0; slot < players; slot += 1) {
527
+ const value = (random.nextInt(0, 9) + frame + slot) % 13;
528
+ runtime.submitInput({
529
+ slot,
530
+ frame,
531
+ value,
532
+ delay: 0,
533
+ inputId: `authority-replay:${frame}:${slot}`,
534
+ });
535
+ inputs.push({ slot, dx: value, dy: 0 });
536
+ }
537
+ runtime.step();
538
+ inputHistory.push({ frame, inputs });
539
+ const snapshotPayload = {
540
+ frame,
541
+ positions: runtime.state.stateBySlot,
542
+ commandScore: 0,
543
+ };
544
+ const bytes = (0, canonical_js_1.canonicalStringify)(snapshotPayload);
545
+ const checksum = (0, canonical_js_1.defaultChecksum)(bytes);
546
+ snapshots.push({ frame, bytes, checksum });
547
+ checksums.push({ frame, checksum });
548
+ debugEvents.push({
549
+ frame,
550
+ type: 'local-authority.snapshot',
551
+ payload: { frame, checksum },
552
+ });
553
+ }
554
+ const validBundle = (0, replay_bundle_js_1.createDeterministicReplayBundle)({
555
+ runtimeVersion: 'local-authority-replay@1',
556
+ engineHash: 'local-authority-engine',
557
+ schemaHash: 'local-authority-schema',
558
+ assetDbHash: 'local-authority-assets',
559
+ configHash: 'local-authority-config',
560
+ sampleId: 'local-authority-replay-minimizer',
561
+ inputHistory,
562
+ commandHistory: [],
563
+ checksums,
564
+ debugEvents,
565
+ snapshots,
566
+ metadata: { players, frames, seed },
567
+ });
568
+ const mismatchFrame = Math.max(1, Math.floor(frames / 2));
569
+ const corruptedBundle = (0, replay_bundle_js_1.createDeterministicReplayBundle)({
570
+ ...validBundle,
571
+ checksums: validBundle.checksums.map((checksum) => checksum.frame === mismatchFrame
572
+ ? { ...checksum, checksum: `corrupt:${checksum.frame}` }
573
+ : checksum),
574
+ });
575
+ const validVerification = (0, replay_bundle_js_1.verifyDeterministicReplayBundle)(validBundle);
576
+ const corruptedVerification = (0, replay_bundle_js_1.verifyDeterministicReplayBundle)(corruptedBundle);
577
+ const minimization = (0, replay_bundle_js_1.minimizeDeterministicReplayDesyncBundle)(validBundle, corruptedBundle);
578
+ return {
579
+ checksum: (0, canonical_js_1.defaultChecksum)({
580
+ valid: validVerification.bundleHash,
581
+ corrupted: corruptedVerification.bundleHash,
582
+ minimized: minimization?.verifier.bundleHash ?? '',
583
+ }),
584
+ players,
585
+ frames,
586
+ validReplayVerified: validVerification.result === 'pass',
587
+ corruptedReplayFailed: corruptedVerification.result === 'fail',
588
+ minimizedReplayFailed: minimization?.verifier.result === 'fail',
589
+ mismatchFrame: corruptedVerification.firstMismatch.frame,
590
+ minimizedMismatchFrame: minimization?.verifier.firstMismatch?.frame ?? -1,
591
+ originalReplayBytes: minimization?.originalReplayBytes ?? 0,
592
+ minimizedReplayBytes: minimization?.minimizedReplayBytes ?? 0,
593
+ removedInputFrames: minimization?.removedInputFrames ?? 0,
594
+ removedSnapshots: minimization?.removedSnapshots ?? 0,
595
+ removedChecksumFrames: minimization?.removedChecksumFrames ?? 0,
596
+ debugEvents: validBundle.debugEvents.length,
597
+ };
598
+ }
599
+ function runNonCommutativeLateInputResimulationFixture() {
600
+ const stateReducer = (currentValue, message) => (currentValue * 31 + message.value) % 2_147_483_647;
601
+ const runtime = createDeterministicLocalAuthorityRuntime(1, { maxFrameLead: 4, rollbackWindowFrames: 4, stateReducer });
602
+ runtime.submitInput({ slot: 0, frame: 0, value: 2, inputId: 'noncommutative:f0' });
603
+ runtime.step();
604
+ runtime.submitInput({ slot: 0, frame: 2, value: 5, inputId: 'noncommutative:f2' });
605
+ runtime.step();
606
+ runtime.step();
607
+ const lateInputFrame = 1;
608
+ const verifiedFrame = runtime.state.frame;
609
+ runtime.submitInput({ slot: 0, frame: lateInputFrame, value: 3, inputId: 'noncommutative:f1-late' });
610
+ runtime.step();
611
+ const runtimeChecksum = runtime.checksums().authorityProjectionChecksum;
612
+ const fullReplay = createDeterministicLocalAuthorityRuntime(1, { maxFrameLead: 4, rollbackWindowFrames: 4, stateReducer });
613
+ fullReplay.submitInput({ slot: 0, frame: 0, value: 2, inputId: 'noncommutative-full:f0' });
614
+ fullReplay.step();
615
+ fullReplay.submitInput({ slot: 0, frame: 1, value: 3, inputId: 'noncommutative-full:f1' });
616
+ fullReplay.step();
617
+ fullReplay.submitInput({ slot: 0, frame: 2, value: 5, inputId: 'noncommutative-full:f2' });
618
+ fullReplay.step();
619
+ fullReplay.step();
620
+ const fullReplayChecksum = fullReplay.checksums().authorityProjectionChecksum;
621
+ const appendOnlyValue = stateReducer(stateReducer(stateReducer(0, { slot: 0, frame: 0, delay: 0, deliveryFrame: 0, value: 2 }), {
622
+ slot: 0,
623
+ frame: 2,
624
+ delay: 0,
625
+ deliveryFrame: 2,
626
+ value: 5,
627
+ }), {
628
+ slot: 0,
629
+ frame: lateInputFrame,
630
+ delay: 0,
631
+ deliveryFrame: lateInputFrame,
632
+ value: 3,
633
+ });
634
+ return {
635
+ runtimeChecksum,
636
+ fullReplayChecksum,
637
+ appendOnlyChecksum: (0, canonical_js_1.defaultChecksum)([appendOnlyValue]),
638
+ resimulatedFrames: Math.max(0, verifiedFrame - lateInputFrame),
639
+ };
640
+ }
641
+ function persistAuthorityRuntime(state, deliveryQueue, clientStateBySlot, commandChecksum, lateJoinSlots, spectatorSlots) {
642
+ return (0, canonical_js_1.cloneCanonical)({
643
+ state,
644
+ deliveryQueue: serializeDeliveryQueue(deliveryQueue),
645
+ clientStateBySlot,
646
+ commandChecksum,
647
+ lateJoinSlots: [...lateJoinSlots].sort(compareNumbers),
648
+ spectatorSlots: [...spectatorSlots].sort(compareNumbers),
649
+ });
650
+ }
651
+ function assertSlot(state, slot) {
652
+ if (!Number.isInteger(slot) || slot < 0 || slot >= state.players) {
653
+ throw new Error(`Invalid local authority slot ${slot}`);
654
+ }
655
+ }
656
+ function serializeDeliveryQueue(queue) {
657
+ return [...queue.entries()]
658
+ .map(([frame, messages]) => ({ frame, messages: [...messages].sort(compareAuthorityMessages) }))
659
+ .sort((left, right) => left.frame - right.frame);
660
+ }
661
+ function restoreDeliveryQueue(queue, entries) {
662
+ queue.clear();
663
+ for (const entry of entries) {
664
+ queue.set(entry.frame, entry.messages.map((message) => (0, canonical_js_1.cloneCanonical)(message)));
665
+ }
666
+ }
667
+ function serializeAuthorityQueue(queue) {
668
+ return [...queue.entries()]
669
+ .map(([frame, messages]) => ({ frame, messages: [...messages].sort(compareAuthorityMessages) }))
670
+ .sort((left, right) => left.frame - right.frame);
671
+ }
672
+ function restoreAuthorityQueue(queue, entries) {
673
+ queue.clear();
674
+ for (const entry of entries) {
675
+ queue.set(entry.frame, entry.messages.map((message) => (0, canonical_js_1.cloneCanonical)(message)));
676
+ }
677
+ }
678
+ function enqueueAuthorityFrameMessage(queue, message) {
679
+ const messages = queue.get(message.frame) ?? [];
680
+ messages.push(message);
681
+ queue.set(message.frame, messages);
682
+ }
683
+ function enqueueAuthorityDeliveryMessage(queue, message) {
684
+ const messages = queue.get(message.deliveryFrame) ?? [];
685
+ messages.push(message);
686
+ queue.set(message.deliveryFrame, messages);
687
+ }
688
+ function applyAuthorityMessages(state, queue, acceptedMessages, frame, stateReducer) {
689
+ const dueFrames = [...queue.keys()].filter((targetFrame) => targetFrame <= frame).sort((left, right) => left - right);
690
+ for (const targetFrame of dueFrames) {
691
+ queue.delete(targetFrame);
692
+ }
693
+ const nextStateBySlot = replayAuthorityMessages(state.players, acceptedMessages, frame, stateReducer);
694
+ return { ...state, stateBySlot: nextStateBySlot };
695
+ }
696
+ function applyQueuedAuthorityMessages(state, queue, frame, stateReducer) {
697
+ const dueFrames = [...queue.keys()].filter((targetFrame) => targetFrame <= frame).sort((left, right) => left - right);
698
+ if (dueFrames.length === 0) {
699
+ return state;
700
+ }
701
+ const nextStateBySlot = [...state.stateBySlot];
702
+ for (const targetFrame of dueFrames) {
703
+ const messages = queue.get(targetFrame);
704
+ for (const message of [...messages].sort(compareAuthorityMessages)) {
705
+ nextStateBySlot[message.slot] = stateReducer(nextStateBySlot[message.slot], message);
706
+ }
707
+ queue.delete(targetFrame);
708
+ }
709
+ return { ...state, stateBySlot: nextStateBySlot };
710
+ }
711
+ function deliverClientMessages(queue, clientStateBySlot, frame, stateReducer) {
712
+ const dueFrames = [...queue.keys()].filter((deliveryFrame) => deliveryFrame <= frame).sort((left, right) => left - right);
713
+ for (const deliveryFrame of dueFrames) {
714
+ const messages = queue.get(deliveryFrame);
715
+ for (const message of [...messages].sort(compareAuthorityMessages)) {
716
+ clientStateBySlot[message.slot] = stateReducer(clientStateBySlot[message.slot], message);
717
+ }
718
+ queue.delete(deliveryFrame);
719
+ }
720
+ }
721
+ function deliverAllClientMessages(queue, clientStateBySlot) {
722
+ for (const deliveryFrame of [...queue.keys()].sort((left, right) => left - right)) {
723
+ const messages = queue.get(deliveryFrame);
724
+ for (const message of [...messages].sort(compareAuthorityMessages)) {
725
+ clientStateBySlot[message.slot] += message.value;
726
+ }
727
+ queue.delete(deliveryFrame);
728
+ }
729
+ }
730
+ function projectClientMessages(players, acceptedMessages, stateReducer) {
731
+ return replayAuthorityMessages(players, acceptedMessages, Number.MAX_SAFE_INTEGER, stateReducer);
732
+ }
733
+ function projectDeliveryMessages(queue, clientStateBySlot, stateReducer) {
734
+ const projected = [...clientStateBySlot];
735
+ for (const deliveryFrame of [...queue.keys()].sort((left, right) => left - right)) {
736
+ const messages = queue.get(deliveryFrame);
737
+ for (const message of [...messages].sort(compareAuthorityMessages)) {
738
+ projected[message.slot] = stateReducer(projected[message.slot], message);
739
+ }
740
+ }
741
+ return projected;
742
+ }
743
+ function replayAuthorityMessages(players, acceptedMessages, frame, stateReducer) {
744
+ const stateBySlot = Array.from({ length: players }, () => 0);
745
+ for (const message of [...acceptedMessages]
746
+ .filter((candidate) => candidate.frame <= frame)
747
+ .sort(compareAuthorityFrameMessages)) {
748
+ stateBySlot[message.slot] = stateReducer(stateBySlot[message.slot], message);
749
+ }
750
+ return stateBySlot;
751
+ }
752
+ function defaultLocalAuthorityStateReducer(currentValue, message) {
753
+ return currentValue + message.value;
754
+ }
755
+ function removeQueuedMessagesForSlot(queue, slot) {
756
+ for (const deliveryFrame of [...queue.keys()].sort((left, right) => left - right)) {
757
+ const remaining = queue.get(deliveryFrame).filter((message) => message.slot !== slot);
758
+ if (remaining.length === 0) {
759
+ queue.delete(deliveryFrame);
760
+ }
761
+ else {
762
+ queue.set(deliveryFrame, remaining);
763
+ }
764
+ }
765
+ }
766
+ function removeAcceptedMessagesForSlotFromFrame(messages, slot, frame) {
767
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
768
+ const message = messages[index];
769
+ if (message.slot === slot && message.frame >= frame) {
770
+ messages.splice(index, 1);
771
+ }
772
+ }
773
+ }
774
+ function incrementHistogram(histogram, value) {
775
+ histogram.set(value, (histogram.get(value) ?? 0) + 1);
776
+ }
777
+ function percentileFromHistogram(histogram, percentileRank) {
778
+ const total = [...histogram.values()].reduce((sum, count) => sum + count, 0);
779
+ if (total === 0) {
780
+ return 0;
781
+ }
782
+ const target = Math.floor((percentileRank / 100) * total);
783
+ let seen = 0;
784
+ for (const [value, count] of [...histogram.entries()].sort((left, right) => left[0] - right[0])) {
785
+ seen += count;
786
+ if (seen > target) {
787
+ return value;
788
+ }
789
+ }
790
+ /* istanbul ignore next -- non-empty histogram entries exhaust the counted total above. */
791
+ throw new Error('Percentile histogram traversal ended before reaching its target');
792
+ }
793
+ function percentile(values, percentileRank) {
794
+ if (values.length === 0) {
795
+ return 0;
796
+ }
797
+ const sorted = [...values].sort((left, right) => left - right);
798
+ const percentileValue = sorted[Math.min(sorted.length - 1, Math.floor((percentileRank / 100) * sorted.length))];
799
+ return Math.round(percentileValue * 1e4) / 1e4;
800
+ }
801
+ function compareAuthorityMessages(left, right) {
802
+ return left.deliveryFrame - right.deliveryFrame
803
+ || left.frame - right.frame
804
+ || left.slot - right.slot;
805
+ }
806
+ function compareAuthorityFrameMessages(left, right) {
807
+ return left.frame - right.frame
808
+ || left.deliveryFrame - right.deliveryFrame
809
+ || left.slot - right.slot;
810
+ }
811
+ function compareNumbers(left, right) {
812
+ return left - right;
813
+ }
814
+ function compareStrings(left, right) {
815
+ /* istanbul ignore next -- accepted input ids are unique Set keys before sorting. */
816
+ return left < right ? -1 : left > right ? 1 : 0;
817
+ }