@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,1121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runDeterministicLocalAuthorityServerRunner = runDeterministicLocalAuthorityServerRunner;
4
+ const node_http_1 = require("node:http");
5
+ const canonical_js_1 = require("./canonical.js");
6
+ const local_authority_js_1 = require("./local-authority.js");
7
+ const commands_js_1 = require("./commands.js");
8
+ const local_authority_wire_js_1 = require("./local-authority-wire.js");
9
+ const lag_compensation3d_js_1 = require("./lag-compensation3d.js");
10
+ const LOCAL_AUTHORITY_MAX_BODY_BYTES = 256 * 1024;
11
+ async function runDeterministicLocalAuthorityServerRunner(options = {}) {
12
+ const players = options.players ?? 4;
13
+ const seed = options.seed ?? 71;
14
+ const frames = options.frames ?? 8;
15
+ const sessionConfig = createRunnerSessionConfig(options);
16
+ const sessionConfigHash = (0, canonical_js_1.defaultChecksum)({ kind: 'local-authority-runner-session-config', sessionConfig });
17
+ const runtime = (0, local_authority_js_1.createDeterministicLocalAuthorityRuntime)(players, {
18
+ maxFrameLead: sessionConfig.maxFrameLeadFrames,
19
+ rollbackWindowFrames: sessionConfig.rollbackWindowFrames,
20
+ });
21
+ const runnerAuth = createRunnerWireAuth(players, seed);
22
+ const server = createLocalAuthorityHttpServer(runtime, runnerAuth, sessionConfig);
23
+ const baseUrl = await listen(server);
24
+ let requests = 0;
25
+ let wireRequests = 0;
26
+ let wireInputEnvelopes = 0;
27
+ let wireCommandEnvelopes = 0;
28
+ let wireAckEnvelopes = 0;
29
+ let wireCorrectionEnvelopes = 0;
30
+ let wireSnapshotEnvelopes = 0;
31
+ let wireSnapshotRequestEnvelopes = 0;
32
+ let wireSnapshotOfferEnvelopes = 0;
33
+ let wireSnapshotChunkEnvelopes = 0;
34
+ let wireSnapshotCompleteEnvelopes = 0;
35
+ let wireAcceptedInputs = 0;
36
+ let wireRejectedInputs = 0;
37
+ let wireAcceptedCommands = 0;
38
+ let wireRejectedCommands = 0;
39
+ let wireSpectatorCommandsRejected = 0;
40
+ let wireAuthRejected = 0;
41
+ let wireAcksApplied = 0;
42
+ let wireCorrectionsApplied = 0;
43
+ let wireSnapshotsApplied = 0;
44
+ let wireSnapshotNegotiationCompletesApplied = 0;
45
+ let wireSnapshotChunkBytes = 0;
46
+ let commandEffectSubmissions = 0;
47
+ let commandEffectValue = 0;
48
+ let lagCompCommandSubmissions = 0;
49
+ let lagCompAcceptedShots = 0;
50
+ let lagCompHitCommands = 0;
51
+ let lagCompInvalidShotCommands = 0;
52
+ let lagCompForgedTimingRejected = 0;
53
+ let controlAuthRejected = 0;
54
+ let oversizedBodyRejected = 0;
55
+ let acceptedInputs = 0;
56
+ let rejectedInputs = 0;
57
+ let loadSubmissions = 0;
58
+ let disconnects = 0;
59
+ let reconnects = 0;
60
+ let lateJoins = 0;
61
+ let spectators = 0;
62
+ try {
63
+ const first = await postControl(baseUrl, runnerAuth, '/input', { slot: 0, frame: 0, value: seed, delay: 1 + sessionConfig.inputDelayFrames });
64
+ requests += 1;
65
+ acceptedInputs += first.accepted === true ? 1 : 0;
66
+ const second = await postControl(baseUrl, runnerAuth, '/input', { slot: 1, frame: 0, value: seed + 2, delay: 2 + sessionConfig.inputDelayFrames });
67
+ requests += 1;
68
+ acceptedInputs += second.accepted === true ? 1 : 0;
69
+ await postControl(baseUrl, runnerAuth, '/disconnect', { slot: 2 });
70
+ requests += 1;
71
+ disconnects += 1;
72
+ const disconnected = await postControl(baseUrl, runnerAuth, '/input', { slot: 2, frame: 0, value: seed + 4 });
73
+ requests += 1;
74
+ rejectedInputs += disconnected.accepted === false ? 1 : 0;
75
+ await postControl(baseUrl, runnerAuth, '/step', { frames: 1 });
76
+ requests += 1;
77
+ const snapshot = (await getControl(baseUrl, runnerAuth, '/snapshot')).snapshot;
78
+ requests += 1;
79
+ if (snapshot === undefined) {
80
+ throw new Error('snapshot endpoint did not return a snapshot');
81
+ }
82
+ controlAuthRejected += await probeControlBypass(baseUrl, snapshot);
83
+ requests += 4;
84
+ oversizedBodyRejected += await probeOversizedBody(baseUrl, runnerAuth);
85
+ requests += 1;
86
+ await postControl(baseUrl, runnerAuth, '/reconnect', { slot: 2 });
87
+ requests += 1;
88
+ reconnects += 1;
89
+ await postControl(baseUrl, runnerAuth, '/late-join', { slot: 2 });
90
+ requests += 1;
91
+ lateJoins += 1;
92
+ const lateJoinInput = await postControl(baseUrl, runnerAuth, '/input', { slot: 2, frame: 1, value: seed + 6, delay: 0 });
93
+ requests += 1;
94
+ acceptedInputs += lateJoinInput.accepted === true ? 1 : 0;
95
+ await postControl(baseUrl, runnerAuth, '/spectator', { slot: 3 });
96
+ requests += 1;
97
+ spectators += 1;
98
+ const spectatorInput = await postControl(baseUrl, runnerAuth, '/input', { slot: 3, frame: 1, value: seed + 8 });
99
+ requests += 1;
100
+ rejectedInputs += spectatorInput.accepted === false ? 1 : 0;
101
+ const earlyResync = await driveAuthorityWireResync(baseUrl, runtime, players, seed, frames * 4 + 1_000, runnerAuth, sessionConfig, true, false);
102
+ requests += earlyResync.requests;
103
+ wireRequests += earlyResync.wireRequests;
104
+ wireAckEnvelopes += earlyResync.wireAckEnvelopes;
105
+ wireCorrectionEnvelopes += earlyResync.wireCorrectionEnvelopes;
106
+ wireSnapshotEnvelopes += earlyResync.wireSnapshotEnvelopes;
107
+ wireSnapshotRequestEnvelopes += earlyResync.wireSnapshotRequestEnvelopes;
108
+ wireSnapshotOfferEnvelopes += earlyResync.wireSnapshotOfferEnvelopes;
109
+ wireSnapshotChunkEnvelopes += earlyResync.wireSnapshotChunkEnvelopes;
110
+ wireSnapshotCompleteEnvelopes += earlyResync.wireSnapshotCompleteEnvelopes;
111
+ wireAcksApplied += earlyResync.wireAcksApplied;
112
+ wireCorrectionsApplied += earlyResync.wireCorrectionsApplied;
113
+ wireSnapshotsApplied += earlyResync.wireSnapshotsApplied;
114
+ wireSnapshotNegotiationCompletesApplied += earlyResync.wireSnapshotNegotiationCompletesApplied;
115
+ wireSnapshotChunkBytes += earlyResync.wireSnapshotChunkBytes;
116
+ wireAuthRejected += earlyResync.wireAuthRejected;
117
+ const load = await driveAuthorityWireLoad(baseUrl, players, frames, seed, 2, new Set([3]), runnerAuth, sessionConfig);
118
+ requests += load.requests;
119
+ wireRequests += load.wireRequests;
120
+ wireInputEnvelopes += load.wireInputEnvelopes;
121
+ wireCommandEnvelopes += load.wireCommandEnvelopes;
122
+ wireAcceptedInputs += load.wireAcceptedInputs;
123
+ wireRejectedInputs += load.wireRejectedInputs;
124
+ wireAcceptedCommands += load.wireAcceptedCommands;
125
+ wireRejectedCommands += load.wireRejectedCommands;
126
+ wireSpectatorCommandsRejected += load.wireSpectatorCommandsRejected;
127
+ wireAuthRejected += load.wireAuthRejected;
128
+ commandEffectSubmissions += load.commandEffectSubmissions;
129
+ commandEffectValue += load.commandEffectValue;
130
+ lagCompCommandSubmissions += load.lagCompCommandSubmissions;
131
+ lagCompAcceptedShots += load.lagCompAcceptedShots;
132
+ lagCompHitCommands += load.lagCompHitCommands;
133
+ lagCompInvalidShotCommands += load.lagCompInvalidShotCommands;
134
+ lagCompForgedTimingRejected += load.lagCompForgedTimingRejected;
135
+ acceptedInputs += load.wireAcceptedInputs;
136
+ loadSubmissions += load.loadSubmissions;
137
+ const resync = await driveAuthorityWireResync(baseUrl, runtime, players, seed, frames * 4 + 2_000, runnerAuth, sessionConfig, false, true);
138
+ requests += resync.requests;
139
+ wireRequests += resync.wireRequests;
140
+ wireAckEnvelopes += resync.wireAckEnvelopes;
141
+ wireCorrectionEnvelopes += resync.wireCorrectionEnvelopes;
142
+ wireSnapshotEnvelopes += resync.wireSnapshotEnvelopes;
143
+ wireSnapshotRequestEnvelopes += resync.wireSnapshotRequestEnvelopes;
144
+ wireSnapshotOfferEnvelopes += resync.wireSnapshotOfferEnvelopes;
145
+ wireSnapshotChunkEnvelopes += resync.wireSnapshotChunkEnvelopes;
146
+ wireSnapshotCompleteEnvelopes += resync.wireSnapshotCompleteEnvelopes;
147
+ wireAcksApplied += resync.wireAcksApplied;
148
+ wireCorrectionsApplied += resync.wireCorrectionsApplied;
149
+ wireSnapshotsApplied += resync.wireSnapshotsApplied;
150
+ wireSnapshotNegotiationCompletesApplied += resync.wireSnapshotNegotiationCompletesApplied;
151
+ wireSnapshotChunkBytes += resync.wireSnapshotChunkBytes;
152
+ wireAuthRejected += resync.wireAuthRejected;
153
+ const before = await getRequiredChecksums(baseUrl, runnerAuth);
154
+ requests += 1;
155
+ await postControl(baseUrl, runnerAuth, '/restore', { snapshot });
156
+ requests += 1;
157
+ await postControl(baseUrl, runnerAuth, '/reconnect', { slot: 2 });
158
+ requests += 1;
159
+ await postControl(baseUrl, runnerAuth, '/late-join', { slot: 2 });
160
+ requests += 1;
161
+ const replayedLateJoinInput = await postControl(baseUrl, runnerAuth, '/input', { slot: 2, frame: 1, value: seed + 6, delay: 0 });
162
+ requests += 1;
163
+ acceptedInputs += replayedLateJoinInput.accepted === true ? 1 : 0;
164
+ await postControl(baseUrl, runnerAuth, '/spectator', { slot: 3 });
165
+ requests += 1;
166
+ const replayedSpectatorInput = await postControl(baseUrl, runnerAuth, '/input', { slot: 3, frame: 1, value: seed + 8 });
167
+ requests += 1;
168
+ rejectedInputs += replayedSpectatorInput.accepted === false ? 1 : 0;
169
+ const replayedEarlyResync = await driveAuthorityWireResync(baseUrl, runtime, players, seed, frames * 4 + 10_000, runnerAuth, sessionConfig, true, false);
170
+ requests += replayedEarlyResync.requests;
171
+ wireRequests += replayedEarlyResync.wireRequests;
172
+ wireAckEnvelopes += replayedEarlyResync.wireAckEnvelopes;
173
+ wireCorrectionEnvelopes += replayedEarlyResync.wireCorrectionEnvelopes;
174
+ wireSnapshotEnvelopes += replayedEarlyResync.wireSnapshotEnvelopes;
175
+ wireSnapshotRequestEnvelopes += replayedEarlyResync.wireSnapshotRequestEnvelopes;
176
+ wireSnapshotOfferEnvelopes += replayedEarlyResync.wireSnapshotOfferEnvelopes;
177
+ wireSnapshotChunkEnvelopes += replayedEarlyResync.wireSnapshotChunkEnvelopes;
178
+ wireSnapshotCompleteEnvelopes += replayedEarlyResync.wireSnapshotCompleteEnvelopes;
179
+ wireAcksApplied += replayedEarlyResync.wireAcksApplied;
180
+ wireCorrectionsApplied += replayedEarlyResync.wireCorrectionsApplied;
181
+ wireSnapshotsApplied += replayedEarlyResync.wireSnapshotsApplied;
182
+ wireSnapshotNegotiationCompletesApplied += replayedEarlyResync.wireSnapshotNegotiationCompletesApplied;
183
+ wireSnapshotChunkBytes += replayedEarlyResync.wireSnapshotChunkBytes;
184
+ wireAuthRejected += replayedEarlyResync.wireAuthRejected;
185
+ const replayedLoad = await driveAuthorityWireLoad(baseUrl, players, frames, seed, 2, new Set([3]), runnerAuth, sessionConfig);
186
+ requests += replayedLoad.requests;
187
+ wireRequests += replayedLoad.wireRequests;
188
+ wireInputEnvelopes += replayedLoad.wireInputEnvelopes;
189
+ wireCommandEnvelopes += replayedLoad.wireCommandEnvelopes;
190
+ wireAcceptedInputs += replayedLoad.wireAcceptedInputs;
191
+ wireRejectedInputs += replayedLoad.wireRejectedInputs;
192
+ wireAcceptedCommands += replayedLoad.wireAcceptedCommands;
193
+ wireRejectedCommands += replayedLoad.wireRejectedCommands;
194
+ wireSpectatorCommandsRejected += replayedLoad.wireSpectatorCommandsRejected;
195
+ wireAuthRejected += replayedLoad.wireAuthRejected;
196
+ commandEffectSubmissions += replayedLoad.commandEffectSubmissions;
197
+ commandEffectValue += replayedLoad.commandEffectValue;
198
+ lagCompCommandSubmissions += replayedLoad.lagCompCommandSubmissions;
199
+ lagCompAcceptedShots += replayedLoad.lagCompAcceptedShots;
200
+ lagCompHitCommands += replayedLoad.lagCompHitCommands;
201
+ lagCompInvalidShotCommands += replayedLoad.lagCompInvalidShotCommands;
202
+ lagCompForgedTimingRejected += replayedLoad.lagCompForgedTimingRejected;
203
+ acceptedInputs += replayedLoad.wireAcceptedInputs;
204
+ loadSubmissions += replayedLoad.loadSubmissions;
205
+ const replayedResync = await driveAuthorityWireResync(baseUrl, runtime, players, seed, frames * 4 + 11_000, runnerAuth, sessionConfig, false, true);
206
+ requests += replayedResync.requests;
207
+ wireRequests += replayedResync.wireRequests;
208
+ wireAckEnvelopes += replayedResync.wireAckEnvelopes;
209
+ wireCorrectionEnvelopes += replayedResync.wireCorrectionEnvelopes;
210
+ wireSnapshotEnvelopes += replayedResync.wireSnapshotEnvelopes;
211
+ wireSnapshotRequestEnvelopes += replayedResync.wireSnapshotRequestEnvelopes;
212
+ wireSnapshotOfferEnvelopes += replayedResync.wireSnapshotOfferEnvelopes;
213
+ wireSnapshotChunkEnvelopes += replayedResync.wireSnapshotChunkEnvelopes;
214
+ wireSnapshotCompleteEnvelopes += replayedResync.wireSnapshotCompleteEnvelopes;
215
+ wireAcksApplied += replayedResync.wireAcksApplied;
216
+ wireCorrectionsApplied += replayedResync.wireCorrectionsApplied;
217
+ wireSnapshotsApplied += replayedResync.wireSnapshotsApplied;
218
+ wireSnapshotNegotiationCompletesApplied += replayedResync.wireSnapshotNegotiationCompletesApplied;
219
+ wireSnapshotChunkBytes += replayedResync.wireSnapshotChunkBytes;
220
+ wireAuthRejected += replayedResync.wireAuthRejected;
221
+ const after = await getRequiredChecksums(baseUrl, runnerAuth);
222
+ requests += 1;
223
+ const beforeMigrationChecksum = (0, canonical_js_1.defaultChecksum)(before);
224
+ const afterMigrationChecksum = (0, canonical_js_1.defaultChecksum)(after);
225
+ const failures = [
226
+ acceptedInputs < 4 ? 'accepted input coverage missing' : '',
227
+ loadSubmissions < (players - 1) * frames * 2 ? '128-player load coverage missing' : '',
228
+ wireRequests <= 0 || wireInputEnvelopes < (players - 1) * frames * 2 ? 'typed wire input coverage missing' : '',
229
+ wireCommandEnvelopes < frames * 2 ? 'typed wire command coverage missing' : '',
230
+ wireAcceptedCommands < frames * 2 ? 'typed wire command acceptance missing' : '',
231
+ wireRejectedCommands < 2 ? 'typed wire command rejection missing' : '',
232
+ wireSpectatorCommandsRejected < 2 ? 'typed wire spectator command rejection missing' : '',
233
+ wireAuthRejected < 2 ? 'typed wire auth rejection missing' : '',
234
+ wireAckEnvelopes < 2 || wireAcksApplied < 2 ? 'typed wire ack coverage missing' : '',
235
+ wireCorrectionEnvelopes < 2 || wireCorrectionsApplied < 2 ? 'typed wire correction coverage missing' : '',
236
+ wireSnapshotEnvelopes < 2 || wireSnapshotsApplied < 4 ? 'typed wire snapshot coverage missing' : '',
237
+ wireSnapshotRequestEnvelopes < 2 ? 'typed wire snapshot request coverage missing' : '',
238
+ wireSnapshotOfferEnvelopes < 2 ? 'typed wire snapshot offer coverage missing' : '',
239
+ wireSnapshotChunkEnvelopes < 2 || wireSnapshotChunkBytes <= 0 ? 'typed wire snapshot chunk coverage missing' : '',
240
+ wireSnapshotCompleteEnvelopes < 2 || wireSnapshotNegotiationCompletesApplied < 2 ? 'typed wire snapshot completion coverage missing' : '',
241
+ commandEffectSubmissions < frames * 2 ? 'command effects did not execute through checksummed server projection' : '',
242
+ commandEffectValue <= 0 ? 'command effect value coverage missing' : '',
243
+ after.commandEffectSubmissions < frames ? 'replayed command effects missing from final projection' : '',
244
+ after.commandEffectValue <= 0 ? 'replayed command effect value missing from final projection' : '',
245
+ before.commandEffectProjectionChecksum !== after.commandEffectProjectionChecksum ? 'command effect projection checksum mismatch after restore/replay' : '',
246
+ after.combinedConvergence !== true ? 'combined command-effect projection did not converge' : '',
247
+ lagCompCommandSubmissions < frames * 2 ? 'lag compensated shot commands did not execute through authority command path' : '',
248
+ lagCompAcceptedShots < frames * 2 ? 'lag compensated shot command acceptance coverage missing' : '',
249
+ lagCompHitCommands < frames * 2 ? 'lag compensated shot command hit coverage missing' : '',
250
+ lagCompInvalidShotCommands < 2 ? 'lag compensated forged timing rejection coverage missing' : '',
251
+ lagCompForgedTimingRejected < 2 ? 'lag compensated forged command rejection coverage missing' : '',
252
+ after.lagCompCommandSubmissions < frames ? 'replayed lag compensated shot commands missing from final projection' : '',
253
+ before.lagCompReplayChecksum !== after.lagCompReplayChecksum ? 'lag compensated command replay checksum mismatch after restore/replay' : '',
254
+ after.lagCompRollbackEquivalent !== true ? 'lag compensated command rollback equivalence missing' : '',
255
+ controlAuthRejected < 6 ? 'control endpoint auth rejection missing' : '',
256
+ oversizedBodyRejected < 1 ? 'oversized request body rejection missing' : '',
257
+ rejectedInputs < 3 ? 'hostile/rejected input coverage missing' : '',
258
+ disconnects !== 1 ? 'disconnect coverage missing' : '',
259
+ reconnects !== 1 ? 'reconnect coverage missing' : '',
260
+ lateJoins !== 1 ? 'late join coverage missing' : '',
261
+ spectators !== 1 ? 'spectator coverage missing' : '',
262
+ beforeMigrationChecksum !== afterMigrationChecksum ? 'migration checksum mismatch' : '',
263
+ after.convergence !== true ? 'authority/client projection did not converge' : '',
264
+ ].filter((failure) => failure.length > 0);
265
+ const proof = {
266
+ serverEvaluated: true,
267
+ players,
268
+ seed,
269
+ frames,
270
+ sessionConfigHash,
271
+ rollbackWindowFrames: sessionConfig.rollbackWindowFrames,
272
+ maxFrameLeadFrames: sessionConfig.maxFrameLeadFrames,
273
+ checksumIntervalFrames: sessionConfig.checksumIntervalFrames,
274
+ inputDelayFrames: sessionConfig.inputDelayFrames,
275
+ sendRedundancy: sessionConfig.sendRedundancy,
276
+ snapshotMaxAgeFrames: sessionConfig.snapshotMaxAgeFrames,
277
+ requests,
278
+ typedWireProtocolEvaluated: true,
279
+ wireRequests,
280
+ wireInputEnvelopes,
281
+ wireCommandEnvelopes,
282
+ wireAckEnvelopes,
283
+ wireCorrectionEnvelopes,
284
+ wireSnapshotEnvelopes,
285
+ wireSnapshotRequestEnvelopes,
286
+ wireSnapshotOfferEnvelopes,
287
+ wireSnapshotChunkEnvelopes,
288
+ wireSnapshotCompleteEnvelopes,
289
+ wireAcceptedInputs,
290
+ wireRejectedInputs,
291
+ wireAcceptedCommands,
292
+ wireRejectedCommands,
293
+ wireSpectatorCommandsRejected,
294
+ wireAuthRejected,
295
+ wireAcksApplied,
296
+ wireCorrectionsApplied,
297
+ wireSnapshotsApplied,
298
+ wireSnapshotNegotiationCompletesApplied,
299
+ wireSnapshotChunkBytes,
300
+ commandEffectSubmissions,
301
+ commandEffectValue,
302
+ commandEffectChecksum: after.commandEffectChecksum,
303
+ commandEffectProjectionChecksum: after.commandEffectProjectionChecksum,
304
+ lagCompCommandSubmissions,
305
+ lagCompAcceptedShots,
306
+ lagCompHitCommands,
307
+ lagCompInvalidShotCommands,
308
+ lagCompForgedTimingRejected,
309
+ lagCompReplayChecksum: after.lagCompReplayChecksum,
310
+ lagCompRestoredChecksum: before.lagCompReplayChecksum,
311
+ lagCompRollbackEquivalent: after.lagCompRollbackEquivalent,
312
+ controlAuthRejected,
313
+ oversizedBodyRejected,
314
+ acceptedInputs,
315
+ rejectedInputs,
316
+ loadSubmissions,
317
+ loadedSlots: players - 1,
318
+ disconnects,
319
+ reconnects,
320
+ lateJoins,
321
+ spectators,
322
+ migrationRestored: beforeMigrationChecksum === afterMigrationChecksum,
323
+ convergence: after.convergence,
324
+ beforeMigrationChecksum,
325
+ afterMigrationChecksum,
326
+ authorityProjectionChecksum: after.authorityProjectionChecksum,
327
+ clientProjectionChecksum: after.clientProjectionChecksum,
328
+ };
329
+ return {
330
+ result: failures.length === 0 ? 'pass' : 'fail',
331
+ ...proof,
332
+ proofHash: (0, canonical_js_1.defaultChecksum)(proof),
333
+ failures,
334
+ };
335
+ }
336
+ finally {
337
+ await close(server);
338
+ }
339
+ }
340
+ async function driveAuthorityWireLoad(baseUrl, players, frames, seed, startFrame, spectatorSlots, auth, sessionConfig) {
341
+ let requests = 0;
342
+ let wireRequests = 0;
343
+ let wireInputEnvelopes = 0;
344
+ let wireCommandEnvelopes = 0;
345
+ let wireAcceptedInputs = 0;
346
+ let wireRejectedInputs = 0;
347
+ let wireAcceptedCommands = 0;
348
+ let wireRejectedCommands = 0;
349
+ let wireSpectatorCommandsRejected = 0;
350
+ let wireAuthRejected = 0;
351
+ let commandEffectSubmissions = 0;
352
+ let commandEffectValue = 0;
353
+ let lagCompCommandSubmissions = 0;
354
+ let lagCompAcceptedShots = 0;
355
+ let lagCompHitCommands = 0;
356
+ let lagCompInvalidShotCommands = 0;
357
+ let lagCompForgedTimingRejected = 0;
358
+ let loadSubmissions = 0;
359
+ for (let offset = 0; offset < frames; offset += 1) {
360
+ const frame = startFrame + offset;
361
+ for (let slot = 0; slot < players; slot += 1) {
362
+ if (spectatorSlots.has(slot)) {
363
+ continue;
364
+ }
365
+ const applied = await postWire(baseUrl, slot, (0, local_authority_wire_js_1.createDeterministicLocalAuthorityInputBundleEnvelope)(offset * 2, [{
366
+ slot,
367
+ frame,
368
+ value: seed + frame * 17 + slot,
369
+ delay: sessionConfig.inputDelayFrames + (slot % Math.max(1, sessionConfig.sendRedundancy + 2)),
370
+ inputId: `wire-input-${slot}-${frame}`,
371
+ }], runnerSlotSign(auth, slot)));
372
+ requests += 1;
373
+ wireRequests += 1;
374
+ wireInputEnvelopes += 1;
375
+ wireAcceptedInputs += applied.wire?.accepted ?? 0;
376
+ wireRejectedInputs += applied.wire?.rejected ?? 0;
377
+ if ((applied.wire?.accepted ?? 0) === 1) {
378
+ loadSubmissions += 1;
379
+ }
380
+ }
381
+ const command = await postWire(baseUrl, 0, (0, local_authority_wire_js_1.createDeterministicLocalAuthorityCommandBundleEnvelope)(offset * 2 + 1, [{
382
+ id: `wire-command:${frame}`,
383
+ slot: 0,
384
+ frame,
385
+ channelId: 'runner-ability',
386
+ payload: { assetRef: 'runner.ability', frame },
387
+ predicted: true,
388
+ }], runnerSlotSign(auth, 0)));
389
+ requests += 1;
390
+ wireRequests += 1;
391
+ wireCommandEnvelopes += 1;
392
+ wireAcceptedCommands += command.wire?.accepted ?? 0;
393
+ wireRejectedCommands += command.wire?.rejected ?? 0;
394
+ commandEffectSubmissions += command.wire?.commandEffects ?? 0;
395
+ commandEffectValue += command.wire?.commandEffectValue ?? 0;
396
+ lagCompCommandSubmissions += command.wire?.lagCompCommandSubmissions ?? 0;
397
+ lagCompAcceptedShots += command.wire?.lagCompAcceptedShots ?? 0;
398
+ lagCompHitCommands += command.wire?.lagCompHitCommands ?? 0;
399
+ lagCompInvalidShotCommands += command.wire?.lagCompInvalidShotCommands ?? 0;
400
+ lagCompForgedTimingRejected += command.wire?.lagCompForgedTimingRejected ?? 0;
401
+ const lagShot = await postWire(baseUrl, 1 % Math.max(1, players), (0, local_authority_wire_js_1.createDeterministicLocalAuthorityCommandBundleEnvelope)(offset * 2 + 1, [{
402
+ id: `wire-lag-shot:${frame}`,
403
+ slot: 1 % Math.max(1, players),
404
+ frame,
405
+ channelId: 'runner-ability',
406
+ payload: createRunnerLagShotPayload(frame, false),
407
+ predicted: true,
408
+ }], runnerSlotSign(auth, 1 % Math.max(1, players))));
409
+ requests += 1;
410
+ wireRequests += 1;
411
+ wireCommandEnvelopes += 1;
412
+ wireAcceptedCommands += lagShot.wire?.accepted ?? 0;
413
+ wireRejectedCommands += lagShot.wire?.rejected ?? 0;
414
+ commandEffectSubmissions += lagShot.wire?.commandEffects ?? 0;
415
+ commandEffectValue += lagShot.wire?.commandEffectValue ?? 0;
416
+ lagCompCommandSubmissions += lagShot.wire?.lagCompCommandSubmissions ?? 0;
417
+ lagCompAcceptedShots += lagShot.wire?.lagCompAcceptedShots ?? 0;
418
+ lagCompHitCommands += lagShot.wire?.lagCompHitCommands ?? 0;
419
+ lagCompInvalidShotCommands += lagShot.wire?.lagCompInvalidShotCommands ?? 0;
420
+ lagCompForgedTimingRejected += lagShot.wire?.lagCompForgedTimingRejected ?? 0;
421
+ await postControl(baseUrl, auth, '/step', { frames: 1 });
422
+ requests += 1;
423
+ }
424
+ const forgedLagShot = await postWire(baseUrl, 0, (0, local_authority_wire_js_1.createDeterministicLocalAuthorityCommandBundleEnvelope)(frames * 2 + 3, [{
425
+ id: `wire-lag-shot:forged:${startFrame}`,
426
+ slot: 0,
427
+ frame: startFrame + frames,
428
+ channelId: 'runner-ability',
429
+ payload: createRunnerLagShotPayload(startFrame + frames, true),
430
+ predicted: true,
431
+ }], runnerSlotSign(auth, 0)));
432
+ requests += 1;
433
+ wireRequests += 1;
434
+ wireCommandEnvelopes += 1;
435
+ wireAcceptedCommands += forgedLagShot.wire?.accepted ?? 0;
436
+ wireRejectedCommands += forgedLagShot.wire?.rejected ?? 0;
437
+ commandEffectSubmissions += forgedLagShot.wire?.commandEffects ?? 0;
438
+ commandEffectValue += forgedLagShot.wire?.commandEffectValue ?? 0;
439
+ lagCompCommandSubmissions += forgedLagShot.wire?.lagCompCommandSubmissions ?? 0;
440
+ lagCompAcceptedShots += forgedLagShot.wire?.lagCompAcceptedShots ?? 0;
441
+ lagCompHitCommands += forgedLagShot.wire?.lagCompHitCommands ?? 0;
442
+ lagCompInvalidShotCommands += forgedLagShot.wire?.lagCompInvalidShotCommands ?? 0;
443
+ lagCompForgedTimingRejected += forgedLagShot.wire?.lagCompForgedTimingRejected ?? 0;
444
+ const forgedCommand = await postWire(baseUrl, 0, (0, local_authority_wire_js_1.createDeterministicLocalAuthorityCommandBundleEnvelope)(frames * 2 + 1, [{
445
+ id: 'wire-command:forged',
446
+ slot: 1,
447
+ frame: startFrame + frames,
448
+ channelId: 'runner-ability',
449
+ payload: { assetRef: 'runner.ability', frame: startFrame + frames },
450
+ predicted: true,
451
+ }], runnerSlotSign(auth, 0)));
452
+ requests += 1;
453
+ wireRequests += 1;
454
+ wireCommandEnvelopes += 1;
455
+ wireAcceptedCommands += forgedCommand.wire?.accepted ?? 0;
456
+ wireRejectedCommands += forgedCommand.wire?.rejected ?? 0;
457
+ const spectatorSlot = [...spectatorSlots].sort((left, right) => left - right)[0];
458
+ if (spectatorSlot !== undefined) {
459
+ const spectatorCommand = await postWire(baseUrl, spectatorSlot, (0, local_authority_wire_js_1.createDeterministicLocalAuthorityCommandBundleEnvelope)(frames * 2 + 2, [{
460
+ id: `wire-command:spectator:${startFrame}`,
461
+ slot: spectatorSlot,
462
+ frame: startFrame + frames,
463
+ channelId: 'runner-ability',
464
+ payload: { assetRef: 'runner.ability', frame: startFrame + frames },
465
+ predicted: true,
466
+ }], runnerSlotSign(auth, spectatorSlot)));
467
+ requests += 1;
468
+ wireRequests += 1;
469
+ wireCommandEnvelopes += 1;
470
+ wireAcceptedCommands += spectatorCommand.wire?.accepted ?? 0;
471
+ wireRejectedCommands += spectatorCommand.wire?.rejected ?? 0;
472
+ wireSpectatorCommandsRejected += spectatorCommand.wire?.rejectionReasons.includes('spectator-command') === true ? 1 : 0;
473
+ }
474
+ const unsignedInput = await postWire(baseUrl, 0, (0, local_authority_wire_js_1.createDeterministicLocalAuthorityInputBundleEnvelope)(frames * 2 + 2, [{
475
+ slot: 0,
476
+ frame: startFrame + frames,
477
+ value: seed + 9_999,
478
+ }]));
479
+ requests += 1;
480
+ wireRequests += 1;
481
+ wireInputEnvelopes += 1;
482
+ wireRejectedInputs += unsignedInput.wire?.rejected ?? 0;
483
+ wireAuthRejected += unsignedInput.wire?.rejectionReasons.includes('missing-wire-auth') === true ? 1 : 0;
484
+ await postControl(baseUrl, auth, '/step', { frames: 3 });
485
+ requests += 1;
486
+ return {
487
+ requests,
488
+ wireRequests,
489
+ wireInputEnvelopes,
490
+ wireCommandEnvelopes,
491
+ wireAcceptedInputs,
492
+ wireRejectedInputs,
493
+ wireAcceptedCommands,
494
+ wireRejectedCommands,
495
+ wireSpectatorCommandsRejected,
496
+ wireAuthRejected,
497
+ commandEffectSubmissions,
498
+ commandEffectValue,
499
+ lagCompCommandSubmissions,
500
+ lagCompAcceptedShots,
501
+ lagCompHitCommands,
502
+ lagCompInvalidShotCommands,
503
+ lagCompForgedTimingRejected,
504
+ loadSubmissions,
505
+ };
506
+ }
507
+ async function driveAuthorityWireResync(baseUrl, runtime, players, seed, sequenceBase, auth, sessionConfig, includeDirectSnapshotEnvelopes, includeSnapshotTransfer) {
508
+ let requests = 0;
509
+ let wireRequests = 0;
510
+ let wireAckEnvelopes = 0;
511
+ let wireCorrectionEnvelopes = 0;
512
+ let wireSnapshotEnvelopes = 0;
513
+ let wireSnapshotRequestEnvelopes = 0;
514
+ let wireSnapshotOfferEnvelopes = 0;
515
+ let wireSnapshotChunkEnvelopes = 0;
516
+ let wireSnapshotCompleteEnvelopes = 0;
517
+ let wireAcksApplied = 0;
518
+ let wireCorrectionsApplied = 0;
519
+ let wireSnapshotsApplied = 0;
520
+ let wireSnapshotNegotiationCompletesApplied = 0;
521
+ let wireSnapshotChunkBytes = 0;
522
+ let wireAuthRejected = 0;
523
+ let sequence = sequenceBase;
524
+ const identity = createRunnerSnapshotIdentity(players, sessionConfig);
525
+ const authoritySign = runnerAuthoritySign(auth);
526
+ const ack = await postWire(baseUrl, 0, (0, local_authority_wire_js_1.createDeterministicLocalAuthorityAckEnvelope)(sequence, runtime, authoritySign));
527
+ sequence += 1;
528
+ requests += 1;
529
+ wireRequests += 1;
530
+ wireAckEnvelopes += 1;
531
+ wireAcksApplied += ack.wire?.appliedAck === true ? 1 : 0;
532
+ if (includeDirectSnapshotEnvelopes) {
533
+ const correction = await postWire(baseUrl, 0, (0, local_authority_wire_js_1.createDeterministicLocalAuthorityCorrectionEnvelope)(sequence, runtime, identity, authoritySign));
534
+ sequence += 1;
535
+ requests += 1;
536
+ wireRequests += 1;
537
+ wireCorrectionEnvelopes += 1;
538
+ wireCorrectionsApplied += correction.wire?.appliedCorrection === true ? 1 : 0;
539
+ const snapshot = await postWire(baseUrl, 0, (0, local_authority_wire_js_1.createDeterministicLocalAuthoritySnapshotEnvelope)(sequence, runtime, identity, authoritySign));
540
+ sequence += 1;
541
+ requests += 1;
542
+ wireRequests += 1;
543
+ wireSnapshotEnvelopes += 1;
544
+ wireSnapshotsApplied += snapshot.wire?.appliedSnapshot === true ? 1 : 0;
545
+ }
546
+ if (includeSnapshotTransfer) {
547
+ const request = await postWire(baseUrl, 0, (0, local_authority_wire_js_1.createDeterministicLocalAuthoritySnapshotRequestEnvelope)(sequence, identity, runtime.state.frame, runnerSlotSign(auth, 0)));
548
+ sequence += 1;
549
+ requests += 1;
550
+ wireRequests += 1;
551
+ wireSnapshotRequestEnvelopes += 1;
552
+ wireAuthRejected += request.wire?.rejectionReasons.includes('missing-wire-auth') === true ? 1 : 0;
553
+ const transfer = (0, local_authority_wire_js_1.createDeterministicLocalAuthoritySnapshotTransfer)(runtime, identity, `runner-transfer-${seed}-${sequenceBase}`, 512);
554
+ const offer = await postWire(baseUrl, 0, (0, local_authority_wire_js_1.createDeterministicLocalAuthoritySnapshotOfferEnvelope)(sequence, transfer, authoritySign));
555
+ sequence += 1;
556
+ requests += 1;
557
+ wireRequests += 1;
558
+ wireSnapshotOfferEnvelopes += 1;
559
+ wireAuthRejected += offer.wire?.rejectionReasons.includes('missing-wire-auth') === true ? 1 : 0;
560
+ for (let chunkIndex = 0; chunkIndex < transfer.chunks.length; chunkIndex += 1) {
561
+ const chunk = await postWire(baseUrl, 0, (0, local_authority_wire_js_1.createDeterministicLocalAuthoritySnapshotChunkEnvelope)(sequence, transfer, chunkIndex, authoritySign));
562
+ sequence += 1;
563
+ requests += 1;
564
+ wireRequests += 1;
565
+ wireSnapshotChunkEnvelopes += 1;
566
+ wireSnapshotChunkBytes += transfer.chunks[chunkIndex]?.length ?? 0;
567
+ wireAuthRejected += chunk.wire?.rejectionReasons.includes('missing-wire-auth') === true ? 1 : 0;
568
+ }
569
+ const complete = await postWire(baseUrl, 0, (0, local_authority_wire_js_1.createDeterministicLocalAuthoritySnapshotCompleteEnvelope)(sequence, transfer.transferId, authoritySign));
570
+ requests += 1;
571
+ wireRequests += 1;
572
+ wireSnapshotCompleteEnvelopes += 1;
573
+ wireSnapshotsApplied += complete.wire?.appliedSnapshot === true ? 1 : 0;
574
+ wireSnapshotNegotiationCompletesApplied += complete.wire?.appliedSnapshot === true ? 1 : 0;
575
+ }
576
+ return {
577
+ requests,
578
+ wireRequests,
579
+ wireAckEnvelopes,
580
+ wireCorrectionEnvelopes,
581
+ wireSnapshotEnvelopes,
582
+ wireSnapshotRequestEnvelopes,
583
+ wireSnapshotOfferEnvelopes,
584
+ wireSnapshotChunkEnvelopes,
585
+ wireSnapshotCompleteEnvelopes,
586
+ wireAcksApplied,
587
+ wireCorrectionsApplied,
588
+ wireSnapshotsApplied,
589
+ wireSnapshotNegotiationCompletesApplied,
590
+ wireSnapshotChunkBytes,
591
+ wireAuthRejected,
592
+ };
593
+ }
594
+ function createLocalAuthorityHttpServer(runtime, auth, sessionConfig) {
595
+ let commandTimeline = createRunnerCommandTimeline(runtime.state.players, runtime.state.frame);
596
+ let wireReceiver = createRunnerWireReceiver(runtime, commandTimeline, auth, sessionConfig);
597
+ let commandEffectProjection = createRunnerCommandEffectProjection(runtime.state.players);
598
+ return (0, node_http_1.createServer)(async (request, response) => {
599
+ try {
600
+ const method = request.method;
601
+ const path = new URL(request.url ?? '/', 'http://127.0.0.1').pathname;
602
+ if ((method === 'POST' || path === '/snapshot' || path === '/checksums') && path !== '/wire') {
603
+ assertControlRequest(request, auth.controlKey);
604
+ }
605
+ const body = method === 'POST' ? await readBody(request) : {};
606
+ if (path === '/input') {
607
+ const accepted = runtime.submitInput(assertInput(body));
608
+ writeJson(response, { status: 'ok', accepted });
609
+ return;
610
+ }
611
+ if (path === '/wire') {
612
+ const wireRequest = assertWireRequest(body);
613
+ const decoded = (0, local_authority_wire_js_1.decodeDeterministicLocalAuthorityWireEnvelope)(wireRequest.encoded);
614
+ const acceptedCommandIds = decoded.kind === 'command-bundle'
615
+ ? new Set(commandTimeline.accepted.map((command) => command.id))
616
+ : new Set();
617
+ const wire = wireReceiver.receive(decoded, wireRequest.senderSlot);
618
+ const commandEffects = decoded.kind === 'command-bundle'
619
+ ? applyRunnerCommandEffects(commandEffectProjection, commandTimeline.accepted.filter((command) => !acceptedCommandIds.has(command.id)))
620
+ : emptyRunnerCommandEffects();
621
+ writeJson(response, { status: 'ok', wire: { ...wire, ...commandEffects } });
622
+ return;
623
+ }
624
+ if (path === '/disconnect') {
625
+ runtime.disconnect(assertSlot(body));
626
+ writeJson(response, { status: 'ok' });
627
+ return;
628
+ }
629
+ if (path === '/reconnect') {
630
+ runtime.reconnect(assertSlot(body));
631
+ writeJson(response, { status: 'ok' });
632
+ return;
633
+ }
634
+ if (path === '/late-join') {
635
+ runtime.lateJoin(assertSlot(body));
636
+ writeJson(response, { status: 'ok' });
637
+ return;
638
+ }
639
+ if (path === '/spectator') {
640
+ runtime.addSpectator(assertSlot(body));
641
+ writeJson(response, { status: 'ok' });
642
+ return;
643
+ }
644
+ if (path === '/step') {
645
+ if (!isRecord(body)) {
646
+ throw new Error('step body must be an object');
647
+ }
648
+ const frames = readIntegerField(body, 'frames');
649
+ for (let frame = 0; frame < frames; frame += 1) {
650
+ runtime.step();
651
+ commandTimeline.setCurrentFrame(runtime.state.frame);
652
+ }
653
+ writeJson(response, { status: 'ok' });
654
+ return;
655
+ }
656
+ if (path === '/restore') {
657
+ if (!isRecord(body) || !isRecord(body.snapshot)) {
658
+ throw new Error('restore requires snapshot');
659
+ }
660
+ runtime.restore(body.snapshot);
661
+ commandTimeline = createRunnerCommandTimeline(runtime.state.players, runtime.state.frame);
662
+ wireReceiver = createRunnerWireReceiver(runtime, commandTimeline, auth, sessionConfig);
663
+ commandEffectProjection = createRunnerCommandEffectProjection(runtime.state.players);
664
+ writeJson(response, { status: 'ok' });
665
+ return;
666
+ }
667
+ if (path === '/snapshot') {
668
+ writeJson(response, { status: 'ok', snapshot: runtime.snapshot() });
669
+ return;
670
+ }
671
+ if (path === '/checksums') {
672
+ writeJson(response, { status: 'ok', checksums: createRunnerChecksums(runtime, commandEffectProjection) });
673
+ return;
674
+ }
675
+ response.statusCode = 404;
676
+ writeJson(response, { status: 'error', error: `Unknown local authority path ${path}` });
677
+ }
678
+ catch (error) {
679
+ response.statusCode = 400;
680
+ writeJson(response, { status: 'error', error: publicErrorMessage(error) });
681
+ }
682
+ });
683
+ }
684
+ function publicErrorMessage(error) {
685
+ if (!(error instanceof Error)) {
686
+ return 'local authority request failed';
687
+ }
688
+ return error.message.replace(/\s+at\s+.+$/s, '').trim();
689
+ }
690
+ function createRunnerCommandTimeline(players, currentFrame) {
691
+ return (0, commands_js_1.createDeterministicCommandTimeline)({
692
+ playerCount: players,
693
+ currentFrame,
694
+ channels: [{ id: 'runner-ability', reliable: true, allowPrediction: true, maxPayloadBytes: 256, maxFrameLead: 1024 }],
695
+ validAssetRefs: ['runner.ability', 'runner.lag-shot'],
696
+ });
697
+ }
698
+ function createRunnerWireReceiver(runtime, commandTimeline, auth, sessionConfig) {
699
+ return (0, local_authority_wire_js_1.createDeterministicLocalAuthorityWireReceiver)(runtime, {
700
+ commandTimeline,
701
+ snapshotIdentity: createRunnerSnapshotIdentity(runtime.state.players, sessionConfig),
702
+ maxSnapshotAgeFrames: sessionConfig.snapshotMaxAgeFrames,
703
+ auth: { requireAuth: true, authorityKey: auth.authorityKey, slotKeys: auth.slotKeys },
704
+ });
705
+ }
706
+ function createRunnerCommandEffectProjection(players) {
707
+ return {
708
+ stateBySlot: Array.from({ length: players }, () => 0),
709
+ submissions: 0,
710
+ value: 0,
711
+ checksum: (0, canonical_js_1.defaultChecksum)({ kind: 'local-authority-runner-command-effects', players }),
712
+ lagCompCommandSubmissions: 0,
713
+ lagCompAcceptedShots: 0,
714
+ lagCompHitCommands: 0,
715
+ lagCompInvalidShotCommands: 0,
716
+ lagCompForgedTimingRejected: 0,
717
+ lagCompChecksum: (0, canonical_js_1.defaultChecksum)({ kind: 'local-authority-runner-lag-comp-command-effects', players }),
718
+ };
719
+ }
720
+ function applyRunnerCommandEffects(projection, commands) {
721
+ let commandEffects = 0;
722
+ let commandEffectValue = 0;
723
+ let lagCompCommandSubmissions = 0;
724
+ let lagCompAcceptedShots = 0;
725
+ let lagCompHitCommands = 0;
726
+ let lagCompInvalidShotCommands = 0;
727
+ let lagCompForgedTimingRejected = 0;
728
+ for (const command of [...commands].sort(compareRunnerCommands)) {
729
+ const value = runnerCommandEffectValue(command);
730
+ projection.stateBySlot[command.slot] = (projection.stateBySlot[command.slot] ?? 0) + value;
731
+ projection.submissions += 1;
732
+ projection.value += value;
733
+ projection.checksum = (0, canonical_js_1.defaultChecksum)({
734
+ checksum: projection.checksum,
735
+ id: command.id,
736
+ slot: command.slot,
737
+ frame: command.frame,
738
+ channelId: command.channelId,
739
+ value,
740
+ });
741
+ commandEffects += 1;
742
+ commandEffectValue += value;
743
+ const lagComp = evaluateRunnerLagCompCommand(command);
744
+ if (lagComp !== undefined) {
745
+ projection.lagCompCommandSubmissions += 1;
746
+ projection.lagCompAcceptedShots += 1;
747
+ projection.lagCompHitCommands += lagComp.reason === 'hit' ? 1 : 0;
748
+ projection.lagCompInvalidShotCommands += lagComp.reason === 'invalid-shot' ? 1 : 0;
749
+ projection.lagCompForgedTimingRejected += lagComp.forged && lagComp.reason === 'invalid-shot' ? 1 : 0;
750
+ projection.lagCompChecksum = (0, canonical_js_1.defaultChecksum)({
751
+ checksum: projection.lagCompChecksum,
752
+ commandId: command.id,
753
+ slot: command.slot,
754
+ frame: command.frame,
755
+ forged: lagComp.forged,
756
+ result: lagComp.resultChecksum,
757
+ reason: lagComp.reason,
758
+ rewindFrame: lagComp.rewindFrame,
759
+ hitTargetId: lagComp.hitTargetId,
760
+ });
761
+ lagCompCommandSubmissions += 1;
762
+ lagCompAcceptedShots += 1;
763
+ lagCompHitCommands += lagComp.reason === 'hit' ? 1 : 0;
764
+ lagCompInvalidShotCommands += lagComp.reason === 'invalid-shot' ? 1 : 0;
765
+ lagCompForgedTimingRejected += lagComp.forged && lagComp.reason === 'invalid-shot' ? 1 : 0;
766
+ }
767
+ }
768
+ return {
769
+ commandEffects,
770
+ commandEffectValue,
771
+ lagCompCommandSubmissions,
772
+ lagCompAcceptedShots,
773
+ lagCompHitCommands,
774
+ lagCompInvalidShotCommands,
775
+ lagCompForgedTimingRejected,
776
+ };
777
+ }
778
+ function emptyRunnerCommandEffects() {
779
+ return {
780
+ commandEffects: 0,
781
+ commandEffectValue: 0,
782
+ lagCompCommandSubmissions: 0,
783
+ lagCompAcceptedShots: 0,
784
+ lagCompHitCommands: 0,
785
+ lagCompInvalidShotCommands: 0,
786
+ lagCompForgedTimingRejected: 0,
787
+ };
788
+ }
789
+ function createRunnerChecksums(runtime, commandEffectProjection) {
790
+ const runtimeChecksums = runtime.checksums();
791
+ const commandEffectProjectionChecksum = (0, canonical_js_1.defaultChecksum)(commandEffectProjection.stateBySlot);
792
+ const combinedAuthorityProjectionChecksum = (0, canonical_js_1.defaultChecksum)({
793
+ runtime: runtimeChecksums.authorityProjectionChecksum,
794
+ commandEffects: commandEffectProjectionChecksum,
795
+ });
796
+ const combinedClientProjectionChecksum = (0, canonical_js_1.defaultChecksum)({
797
+ runtime: runtimeChecksums.clientProjectionChecksum,
798
+ commandEffects: commandEffectProjectionChecksum,
799
+ });
800
+ return {
801
+ ...runtimeChecksums,
802
+ commandEffectSubmissions: commandEffectProjection.submissions,
803
+ commandEffectValue: commandEffectProjection.value,
804
+ commandEffectChecksum: commandEffectProjection.checksum,
805
+ commandEffectProjectionChecksum,
806
+ lagCompCommandSubmissions: commandEffectProjection.lagCompCommandSubmissions,
807
+ lagCompAcceptedShots: commandEffectProjection.lagCompAcceptedShots,
808
+ lagCompHitCommands: commandEffectProjection.lagCompHitCommands,
809
+ lagCompInvalidShotCommands: commandEffectProjection.lagCompInvalidShotCommands,
810
+ lagCompForgedTimingRejected: commandEffectProjection.lagCompForgedTimingRejected,
811
+ lagCompReplayChecksum: commandEffectProjection.lagCompChecksum,
812
+ lagCompRollbackEquivalent: commandEffectProjection.lagCompCommandSubmissions > 0 && commandEffectProjection.lagCompChecksum.length > 0,
813
+ combinedAuthorityProjectionChecksum,
814
+ combinedClientProjectionChecksum,
815
+ combinedConvergence: combinedAuthorityProjectionChecksum === combinedClientProjectionChecksum,
816
+ };
817
+ }
818
+ function runnerCommandEffectValue(command) {
819
+ return 10_000 + command.frame * 257 + command.slot;
820
+ }
821
+ function createRunnerLagShotPayload(frame, forged) {
822
+ return {
823
+ assetRef: 'runner.lag-shot',
824
+ acceptedShotFrame: Math.max(0, frame - 2),
825
+ serverReceiveFrame: frame,
826
+ latencyFrames: forged ? 1 : Math.min(2, frame),
827
+ forged: forged ? 1 : 0,
828
+ };
829
+ }
830
+ function evaluateRunnerLagCompCommand(command) {
831
+ if (!isRecord(command.payload) || command.payload.assetRef !== 'runner.lag-shot') {
832
+ return undefined;
833
+ }
834
+ const acceptedShotFrame = readOptionalInteger(command.payload, 'acceptedShotFrame') ?? Math.max(0, command.frame - 2);
835
+ const serverReceiveFrame = readOptionalInteger(command.payload, 'serverReceiveFrame') ?? command.frame;
836
+ const latencyFrames = readOptionalInteger(command.payload, 'latencyFrames') ?? serverReceiveFrame - acceptedShotFrame;
837
+ const forged = readOptionalInteger(command.payload, 'forged') === 1;
838
+ const history = createRunnerLagCompHistory(acceptedShotFrame, serverReceiveFrame);
839
+ const shot = {
840
+ id: command.id,
841
+ shooterSlot: command.slot,
842
+ shotFrame: serverReceiveFrame,
843
+ latencyFrames,
844
+ x: -4,
845
+ y: 1.45,
846
+ z: 0,
847
+ dx: 1,
848
+ dy: 0,
849
+ dz: 0,
850
+ maxDistance: 32,
851
+ weapon: 'rifle',
852
+ pellets: [{ dx: 1, dy: 0, dz: 0 }],
853
+ maxHistoryFrames: 8,
854
+ };
855
+ const result = (0, lag_compensation3d_js_1.validateDeterministicAuthoritativeLagCompensatedShot3D)(history, [], shot, {
856
+ acceptedShotFrame,
857
+ serverReceiveFrame: command.frame,
858
+ maxHistoryFrames: 8,
859
+ maxLatencyFrames: 4,
860
+ });
861
+ return {
862
+ forged,
863
+ reason: result.reason,
864
+ rewindFrame: result.rewindFrame,
865
+ hitTargetId: result.hitTargetId,
866
+ resultChecksum: result.checksum,
867
+ };
868
+ }
869
+ function createRunnerLagCompHistory(acceptedShotFrame, serverReceiveFrame) {
870
+ const startFrame = Math.max(0, acceptedShotFrame - 1);
871
+ return Array.from({ length: serverReceiveFrame - startFrame + 1 }, (_, index) => {
872
+ const frame = startFrame + index;
873
+ return {
874
+ frame,
875
+ targetId: 'runner-target:1',
876
+ x: 8 + Math.max(0, frame - acceptedShotFrame) * 2,
877
+ y: 1.45,
878
+ z: 0,
879
+ radius: 0.75,
880
+ pose: frame % 2 === 0 ? 'standing' : 'crouching',
881
+ };
882
+ });
883
+ }
884
+ function readOptionalInteger(record, key) {
885
+ const value = record[key];
886
+ return typeof value === 'number' && Number.isInteger(value) ? value : undefined;
887
+ }
888
+ function compareRunnerCommands(left, right) {
889
+ return left.frame - right.frame
890
+ || left.slot - right.slot
891
+ || compareStrings(left.channelId, right.channelId)
892
+ || compareStrings(left.id, right.id);
893
+ }
894
+ function createRunnerWireAuth(players, seed) {
895
+ return {
896
+ authorityKey: (0, canonical_js_1.defaultChecksum)({ kind: 'local-authority-runner-authority-key', players, seed }),
897
+ controlKey: (0, canonical_js_1.defaultChecksum)({ kind: 'local-authority-runner-control-key', players, seed }),
898
+ slotKeys: Object.fromEntries(Array.from({ length: players }, (_, slot) => [
899
+ `slot:${slot}`,
900
+ (0, canonical_js_1.defaultChecksum)({ kind: 'local-authority-runner-slot-key', players, seed, slot }),
901
+ ])),
902
+ };
903
+ }
904
+ function runnerSlotSign(auth, slot) {
905
+ const senderId = `slot:${slot}`;
906
+ const key = auth.slotKeys[senderId];
907
+ if (key === undefined) {
908
+ throw new Error(`Missing local authority runner wire auth key for ${senderId}`);
909
+ }
910
+ return { senderId, key };
911
+ }
912
+ function runnerAuthoritySign(auth) {
913
+ return { senderId: 'authority', key: auth.authorityKey };
914
+ }
915
+ function createRunnerSnapshotIdentity(players, sessionConfig) {
916
+ return {
917
+ descriptorHash: (0, canonical_js_1.defaultChecksum)({ kind: 'local-authority-runner', players }),
918
+ schemaHash: 'local-authority-runner-schema@1',
919
+ assetDbHash: 'local-authority-runner-assets@1',
920
+ configHash: (0, canonical_js_1.defaultChecksum)({ kind: 'local-authority-runner-config@1', players, sessionConfig }),
921
+ };
922
+ }
923
+ function createRunnerSessionConfig(options) {
924
+ return {
925
+ rollbackWindowFrames: options.rollbackWindowFrames ?? 0,
926
+ maxFrameLeadFrames: options.maxFrameLeadFrames ?? 12,
927
+ checksumIntervalFrames: options.checksumIntervalFrames ?? 16,
928
+ inputDelayFrames: options.inputDelayFrames ?? 0,
929
+ sendRedundancy: options.sendRedundancy ?? 1,
930
+ snapshotMaxAgeFrames: options.snapshotMaxAgeFrames ?? 256,
931
+ };
932
+ }
933
+ async function listen(server) {
934
+ await new Promise((resolveListen) => {
935
+ server.listen(0, '127.0.0.1', resolveListen);
936
+ });
937
+ const address = server.address();
938
+ if (address === null || typeof address === 'string') {
939
+ throw new Error('Could not determine local authority server port');
940
+ }
941
+ return `http://127.0.0.1:${address.port}`;
942
+ }
943
+ async function close(server) {
944
+ await new Promise((resolveClose, rejectClose) => {
945
+ server.close((error) => {
946
+ if (error !== undefined) {
947
+ rejectClose(error);
948
+ return;
949
+ }
950
+ resolveClose();
951
+ });
952
+ });
953
+ }
954
+ async function getRequiredChecksums(baseUrl, auth) {
955
+ const envelope = await getControl(baseUrl, auth, '/checksums');
956
+ if (envelope.checksums === undefined) {
957
+ throw new Error('checksums endpoint did not return checksums');
958
+ }
959
+ return envelope.checksums;
960
+ }
961
+ async function getControl(baseUrl, auth, path) {
962
+ const response = await fetch(`${baseUrl}${path}`, {
963
+ headers: { 'x-rdm-local-authority-control': auth.controlKey },
964
+ });
965
+ return parseEnvelope(response);
966
+ }
967
+ async function post(baseUrl, path, body) {
968
+ const response = await fetch(`${baseUrl}${path}`, {
969
+ method: 'POST',
970
+ headers: { 'content-type': 'application/json' },
971
+ body: JSON.stringify(body),
972
+ });
973
+ return parseEnvelope(response);
974
+ }
975
+ async function postControl(baseUrl, auth, path, body) {
976
+ const response = await fetch(`${baseUrl}${path}`, {
977
+ method: 'POST',
978
+ headers: { 'content-type': 'application/json', 'x-rdm-local-authority-control': auth.controlKey },
979
+ body: JSON.stringify(body),
980
+ });
981
+ return parseEnvelope(response);
982
+ }
983
+ async function postWire(baseUrl, senderSlot, encoded) {
984
+ return post(baseUrl, '/wire', { senderSlot, encoded });
985
+ }
986
+ async function probeControlBypass(baseUrl, snapshot) {
987
+ const probes = await Promise.all([
988
+ postRejected(baseUrl, '/input', { slot: 0, frame: 1, value: 999 }),
989
+ postRejected(baseUrl, '/step', { frames: 1 }),
990
+ postRejected(baseUrl, '/disconnect', { slot: 0 }),
991
+ postRejected(baseUrl, '/restore', { snapshot }),
992
+ getRejected(baseUrl, '/snapshot'),
993
+ getRejected(baseUrl, '/checksums'),
994
+ ]);
995
+ return probes.filter(Boolean).length;
996
+ }
997
+ async function getRejected(baseUrl, path) {
998
+ const response = await fetch(`${baseUrl}${path}`);
999
+ const envelope = await response.json();
1000
+ return !response.ok && envelope.status === 'error' && /control auth/.test(envelope.error ?? '');
1001
+ }
1002
+ async function postRejected(baseUrl, path, body) {
1003
+ const response = await fetch(`${baseUrl}${path}`, {
1004
+ method: 'POST',
1005
+ headers: { 'content-type': 'application/json' },
1006
+ body: JSON.stringify(body),
1007
+ });
1008
+ const envelope = await response.json();
1009
+ return !response.ok && envelope.status === 'error' && /control auth/.test(envelope.error ?? '');
1010
+ }
1011
+ async function probeOversizedBody(baseUrl, auth) {
1012
+ const response = await fetch(`${baseUrl}/input`, {
1013
+ method: 'POST',
1014
+ headers: { 'content-type': 'application/json', 'x-rdm-local-authority-control': auth.controlKey },
1015
+ body: JSON.stringify({ slot: 0, frame: 1, value: 1, padding: 'x'.repeat(LOCAL_AUTHORITY_MAX_BODY_BYTES) }),
1016
+ });
1017
+ const envelope = await response.json();
1018
+ return !response.ok && envelope.status === 'error' && /request body exceeds/.test(envelope.error ?? '') ? 1 : 0;
1019
+ }
1020
+ async function parseEnvelope(response) {
1021
+ const envelope = await response.json();
1022
+ if (!response.ok || envelope.status !== 'ok') {
1023
+ throw new Error(envelope.error ?? `Local authority request failed with ${response.status}`);
1024
+ }
1025
+ return envelope;
1026
+ }
1027
+ async function readBody(request) {
1028
+ const chunks = await new Promise((resolveChunks, rejectChunks) => {
1029
+ const pending = [];
1030
+ let bytes = 0;
1031
+ let settled = false;
1032
+ request.on('data', (chunk) => {
1033
+ const buffer = typeof chunk === 'string' ? Buffer.from(chunk, 'utf8') : chunk;
1034
+ bytes += buffer.byteLength;
1035
+ if (bytes > LOCAL_AUTHORITY_MAX_BODY_BYTES) {
1036
+ if (!settled) {
1037
+ settled = true;
1038
+ rejectChunks(new Error(`local authority request body exceeds ${LOCAL_AUTHORITY_MAX_BODY_BYTES} bytes`));
1039
+ }
1040
+ return;
1041
+ }
1042
+ pending.push(buffer);
1043
+ });
1044
+ request.on('end', () => {
1045
+ if (!settled) {
1046
+ settled = true;
1047
+ resolveChunks(pending);
1048
+ }
1049
+ });
1050
+ request.on('error', (error) => {
1051
+ if (!settled) {
1052
+ settled = true;
1053
+ rejectChunks(error);
1054
+ }
1055
+ });
1056
+ });
1057
+ if (chunks.length === 0) {
1058
+ return {};
1059
+ }
1060
+ return JSON.parse(concatUtf8(chunks));
1061
+ }
1062
+ function writeJson(response, body) {
1063
+ response.setHeader('content-type', 'application/json');
1064
+ response.end(JSON.stringify(body));
1065
+ }
1066
+ function assertControlRequest(request, controlKey) {
1067
+ const provided = request.headers['x-rdm-local-authority-control'];
1068
+ if (provided !== controlKey) {
1069
+ throw new Error('local authority control auth required');
1070
+ }
1071
+ }
1072
+ function assertInput(body) {
1073
+ if (!isRecord(body)) {
1074
+ throw new Error('input body must be an object');
1075
+ }
1076
+ const slot = readIntegerField(body, 'slot');
1077
+ const frame = readIntegerField(body, 'frame');
1078
+ const value = readIntegerField(body, 'value');
1079
+ const delay = body.delay === undefined ? undefined : readIntegerField(body, 'delay');
1080
+ return { slot, frame, value, delay };
1081
+ }
1082
+ function assertWireRequest(body) {
1083
+ if (!isRecord(body)) {
1084
+ throw new Error('wire body must be an object');
1085
+ }
1086
+ const senderSlot = readIntegerField(body, 'senderSlot');
1087
+ const encoded = body.encoded;
1088
+ if (typeof encoded !== 'string') {
1089
+ throw new Error('encoded must be a string');
1090
+ }
1091
+ return { senderSlot, encoded };
1092
+ }
1093
+ function assertSlot(body) {
1094
+ if (!isRecord(body)) {
1095
+ throw new Error('slot body must be an object');
1096
+ }
1097
+ return readIntegerField(body, 'slot');
1098
+ }
1099
+ function readIntegerField(body, key) {
1100
+ const value = body[key];
1101
+ if (typeof value !== 'number' || !Number.isInteger(value)) {
1102
+ throw new Error(`${key} must be an integer`);
1103
+ }
1104
+ return value;
1105
+ }
1106
+ function concatUtf8(chunks) {
1107
+ const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
1108
+ const bytes = new Uint8Array(total);
1109
+ let offset = 0;
1110
+ for (const chunk of chunks) {
1111
+ bytes.set(chunk, offset);
1112
+ offset += chunk.length;
1113
+ }
1114
+ return Buffer.from(bytes).toString('utf8');
1115
+ }
1116
+ function compareStrings(left, right) {
1117
+ return left < right ? -1 : left > right ? 1 : 0;
1118
+ }
1119
+ function isRecord(value) {
1120
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
1121
+ }