@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,181 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createProtocolRollbackReport = createProtocolRollbackReport;
4
+ exports.stableReportJson = stableReportJson;
5
+ exports.diffDeterministicReports = diffDeterministicReports;
6
+ exports.stableReportDiffJson = stableReportDiffJson;
7
+ exports.renderDeterministicReportHtml = renderDeterministicReportHtml;
8
+ const canonical_js_1 = require("./canonical.js");
9
+ function createProtocolRollbackReport(options, result) {
10
+ const config = {
11
+ game: options.game ?? 'lab',
12
+ players: options.players,
13
+ frames: options.frames,
14
+ seed: options.seed,
15
+ minDelayFrames: options.minDelayFrames,
16
+ maxDelayFrames: options.maxDelayFrames,
17
+ bundleSizeFrames: options.bundleSizeFrames ?? 1,
18
+ inputDelayFrames: options.inputDelayFrames ?? 0,
19
+ duplicatePermille: options.duplicatePermille ?? 0,
20
+ lossPermille: options.lossPermille ?? 0,
21
+ resendIntervalFrames: options.resendIntervalFrames ?? Math.max(1, options.maxDelayFrames + 1),
22
+ maxResendAttempts: options.maxResendAttempts ?? 1,
23
+ botSlots: options.botSlots ?? [],
24
+ expectDesync: options.expectDesync === true,
25
+ eventTimelineLimit: options.eventTimelineLimit ?? 64,
26
+ predictionPolicy: options.predictionPolicy ?? 'repeat-last',
27
+ ...optionalNumber('disconnectStartFrame', options.disconnectStartFrame),
28
+ ...optionalNumber('disconnectEndFrame', options.disconnectEndFrame),
29
+ ...optionalNumber('lateJoinSlot', options.lateJoinSlot),
30
+ ...optionalNumber('lateJoinFrame', options.lateJoinFrame),
31
+ ...optionalNumber('latencySkewSlot', options.latencySkewSlot),
32
+ ...optionalNumber('latencySkewFrames', options.latencySkewFrames),
33
+ ...optionalNumber('desyncFrame', options.desyncFrame),
34
+ ...optionalNumber('desyncSlot', options.desyncSlot),
35
+ };
36
+ const reportWithoutId = {
37
+ schemaVersion: 1,
38
+ reportId: '',
39
+ kind: 'protocol-rollback',
40
+ generatedAtFrame: result.frames,
41
+ config,
42
+ metrics: result,
43
+ result: result.result,
44
+ };
45
+ return {
46
+ ...reportWithoutId,
47
+ reportId: (0, canonical_js_1.defaultChecksum)(reportWithoutId),
48
+ };
49
+ }
50
+ function stableReportJson(report) {
51
+ return `${(0, canonical_js_1.canonicalStringify)(report)}\n`;
52
+ }
53
+ function diffDeterministicReports(left, right, maxDifferences = 200) {
54
+ const entries = [];
55
+ let differences = 0;
56
+ let truncatedDifferences = 0;
57
+ const addDifference = (path, leftValue, rightValue) => {
58
+ differences += 1;
59
+ if (entries.length < maxDifferences) {
60
+ entries.push({
61
+ path,
62
+ left: summarizeReportValue(leftValue),
63
+ right: summarizeReportValue(rightValue),
64
+ });
65
+ }
66
+ else {
67
+ truncatedDifferences += 1;
68
+ }
69
+ };
70
+ const visit = (path, leftValue, rightValue) => {
71
+ if (Object.is(leftValue, rightValue)) {
72
+ return;
73
+ }
74
+ if (Array.isArray(leftValue) || Array.isArray(rightValue)) {
75
+ if (!Array.isArray(leftValue) || !Array.isArray(rightValue)) {
76
+ addDifference(path, leftValue, rightValue);
77
+ return;
78
+ }
79
+ if (leftValue.length !== rightValue.length) {
80
+ addDifference(`${path}.length`, leftValue.length, rightValue.length);
81
+ }
82
+ for (let index = 0; index < Math.min(leftValue.length, rightValue.length); index += 1) {
83
+ visit(`${path}[${index}]`, leftValue[index], rightValue[index]);
84
+ }
85
+ return;
86
+ }
87
+ if (isReportObject(leftValue) || isReportObject(rightValue)) {
88
+ if (!isReportObject(leftValue) || !isReportObject(rightValue)) {
89
+ addDifference(path, leftValue, rightValue);
90
+ return;
91
+ }
92
+ const keys = [...new Set([...Object.keys(leftValue), ...Object.keys(rightValue)])].sort();
93
+ for (const key of keys) {
94
+ visit(path === '$' ? `$.${key}` : `${path}.${key}`, leftValue[key], rightValue[key]);
95
+ }
96
+ return;
97
+ }
98
+ addDifference(path, leftValue, rightValue);
99
+ };
100
+ visit('$', left, right);
101
+ return {
102
+ schemaVersion: 1,
103
+ kind: 'report-diff',
104
+ leftReportId: reportIdentity(left),
105
+ rightReportId: reportIdentity(right),
106
+ differences,
107
+ truncatedDifferences,
108
+ entries,
109
+ result: differences === 0 ? 'pass' : 'diff',
110
+ };
111
+ }
112
+ function stableReportDiffJson(diff) {
113
+ return `${(0, canonical_js_1.canonicalStringify)(diff)}\n`;
114
+ }
115
+ function renderDeterministicReportHtml(report) {
116
+ const json = (0, canonical_js_1.canonicalStringify)(report);
117
+ return [
118
+ '<!doctype html>',
119
+ '<html lang="en">',
120
+ '<head>',
121
+ '<meta charset="utf-8">',
122
+ '<meta name="viewport" content="width=device-width,initial-scale=1">',
123
+ `<title>${escapeHtml(report.kind)} ${escapeHtml(report.result)}</title>`,
124
+ '<style>',
125
+ 'body{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;margin:24px;color:#171717;background:#fafafa}',
126
+ 'main{max-width:1120px;margin:0 auto}',
127
+ 'header{display:flex;gap:12px;align-items:baseline;justify-content:space-between;border-bottom:1px solid #d4d4d4;padding-bottom:12px}',
128
+ 'h1{font-size:20px;margin:0}',
129
+ 'section{margin-top:18px}',
130
+ 'pre{white-space:pre-wrap;overflow:auto;background:#fff;border:1px solid #d4d4d4;border-radius:6px;padding:12px;font-size:12px;line-height:1.45}',
131
+ '.pill{font-size:12px;border:1px solid #a3a3a3;border-radius:999px;padding:2px 8px;background:#fff}',
132
+ '.pass{border-color:#16a34a;color:#166534}.fail,.desync{border-color:#dc2626;color:#991b1b}.warn{border-color:#ca8a04;color:#854d0e}',
133
+ '</style>',
134
+ '</head>',
135
+ '<body>',
136
+ `<main data-report-id="${escapeHtml(report.reportId)}">`,
137
+ '<header>',
138
+ `<h1>${escapeHtml(report.kind)}</h1>`,
139
+ `<span class="pill ${escapeHtml(report.result)}">${escapeHtml(report.result)}</span>`,
140
+ '</header>',
141
+ '<section>',
142
+ `<p><strong>Report:</strong> ${escapeHtml(report.reportId)}</p>`,
143
+ `<p><strong>Generated at frame:</strong> ${report.generatedAtFrame}</p>`,
144
+ '</section>',
145
+ '<section>',
146
+ '<h2>Canonical JSON</h2>',
147
+ `<pre>${escapeHtml(JSON.stringify(JSON.parse(json), null, 2))}</pre>`,
148
+ '</section>',
149
+ '</main>',
150
+ '</body>',
151
+ '</html>',
152
+ '',
153
+ ].join('\n');
154
+ }
155
+ function optionalNumber(key, value) {
156
+ return value === undefined ? {} : { [key]: value };
157
+ }
158
+ function isReportObject(value) {
159
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
160
+ }
161
+ function reportIdentity(value) {
162
+ if (isReportObject(value) && typeof value.reportId === 'string' && value.reportId.length > 0) {
163
+ return value.reportId;
164
+ }
165
+ return (0, canonical_js_1.defaultChecksum)(value);
166
+ }
167
+ function summarizeReportValue(value) {
168
+ if (value === undefined) {
169
+ return '<missing>';
170
+ }
171
+ const text = typeof value === 'string' ? value : (0, canonical_js_1.canonicalStringify)(value);
172
+ return text.length > 240 ? `${text.slice(0, 237)}...` : text;
173
+ }
174
+ function escapeHtml(value) {
175
+ return value
176
+ .replaceAll('&', '&amp;')
177
+ .replaceAll('<', '&lt;')
178
+ .replaceAll('>', '&gt;')
179
+ .replaceAll('"', '&quot;')
180
+ .replaceAll("'", '&#39;');
181
+ }
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDeterministicRollbackHistory = createDeterministicRollbackHistory;
4
+ exports.runDeterministicRollbackHistoryFixture = runDeterministicRollbackHistoryFixture;
5
+ const canonical_js_1 = require("./canonical.js");
6
+ const engine_runtime_js_1 = require("./engine-runtime.js");
7
+ function createDeterministicRollbackHistory(capacity) {
8
+ if (!Number.isInteger(capacity) || capacity <= 0) {
9
+ throw new Error('Deterministic rollback history capacity must be a positive integer');
10
+ }
11
+ const entries = new Map();
12
+ function sortedFrames() {
13
+ return [...entries.keys()].sort((left, right) => left - right);
14
+ }
15
+ function prune() {
16
+ while (entries.size > capacity) {
17
+ const oldest = sortedFrames()[0];
18
+ entries.delete(oldest);
19
+ }
20
+ }
21
+ return {
22
+ capacity,
23
+ get size() {
24
+ return entries.size;
25
+ },
26
+ get oldestFrame() {
27
+ return sortedFrames()[0];
28
+ },
29
+ get latestFrame() {
30
+ return sortedFrames().at(-1);
31
+ },
32
+ record(frame, snapshot) {
33
+ assertRollbackFrame(frame);
34
+ if (entries.has(frame)) {
35
+ throw new Error(`Deterministic rollback history already has frame ${frame}`);
36
+ }
37
+ const cloned = (0, canonical_js_1.cloneCanonical)(snapshot);
38
+ const bytes = JSON.stringify(cloned).length;
39
+ const entry = {
40
+ frame,
41
+ snapshot: cloned,
42
+ checksum: (0, canonical_js_1.defaultChecksum)({ frame, snapshot: cloned }),
43
+ bytes,
44
+ };
45
+ entries.set(frame, entry);
46
+ prune();
47
+ return (0, canonical_js_1.cloneCanonical)(entry);
48
+ },
49
+ has(frame) {
50
+ assertRollbackFrame(frame);
51
+ return entries.has(frame);
52
+ },
53
+ get(frame) {
54
+ assertRollbackFrame(frame);
55
+ const entry = entries.get(frame);
56
+ return entry === undefined ? undefined : (0, canonical_js_1.cloneCanonical)(entry);
57
+ },
58
+ require(frame) {
59
+ const entry = this.get(frame);
60
+ if (entry === undefined) {
61
+ throw new Error(`Deterministic rollback history frame ${frame} is outside the retained window`);
62
+ }
63
+ return entry;
64
+ },
65
+ frames() {
66
+ return sortedFrames();
67
+ },
68
+ snapshot() {
69
+ const withoutChecksum = {
70
+ capacity,
71
+ entries: sortedFrames().map((frame) => {
72
+ const entry = entries.get(frame);
73
+ /* istanbul ignore next -- sortedFrames is derived from the same entries map being read here. */
74
+ if (entry === undefined) {
75
+ throw new Error(`Deterministic rollback history missing retained frame ${frame}`);
76
+ }
77
+ return (0, canonical_js_1.cloneCanonical)(entry);
78
+ }),
79
+ };
80
+ return {
81
+ ...withoutChecksum,
82
+ checksum: (0, canonical_js_1.defaultChecksum)(withoutChecksum),
83
+ };
84
+ },
85
+ restore(snapshot) {
86
+ validateRollbackHistorySnapshot(snapshot, capacity);
87
+ entries.clear();
88
+ for (const entry of snapshot.entries) {
89
+ entries.set(entry.frame, (0, canonical_js_1.cloneCanonical)(entry));
90
+ }
91
+ },
92
+ checksum() {
93
+ return this.snapshot().checksum;
94
+ },
95
+ };
96
+ }
97
+ function runDeterministicRollbackHistoryFixture(frames = 96, capacity = 24, seed = 71) {
98
+ const restoreFrame = Math.max(8, Math.floor(frames / 3));
99
+ const predictedToFrame = Math.min(frames, restoreFrame + Math.max(6, Math.floor(capacity / 2)));
100
+ const descriptor = rollbackHistoryDescriptor();
101
+ const systems = rollbackHistorySystems();
102
+ const baseline = (0, engine_runtime_js_1.createDeterministicFrameRuntime)(descriptor, seed);
103
+ const predicted = (0, engine_runtime_js_1.createDeterministicFrameRuntime)(descriptor, seed);
104
+ const history = createDeterministicRollbackHistory(capacity);
105
+ for (let frame = 0; frame <= predictedToFrame; frame += 1) {
106
+ history.record(frame, predicted.snapshot());
107
+ stepRollbackRuntime(predicted, systems, predictedInput(frame, seed, restoreFrame));
108
+ stepRollbackRuntime(baseline, systems, actualInput(frame, seed));
109
+ }
110
+ const predictedChecksumBeforeCorrection = predicted.snapshot().checksum;
111
+ const restoreEntry = history.require(restoreFrame);
112
+ predicted.restore(restoreEntry.snapshot);
113
+ for (let frame = restoreFrame; frame <= predictedToFrame; frame += 1) {
114
+ stepRollbackRuntime(predicted, systems, actualInput(frame, seed));
115
+ }
116
+ const resimulatedChecksum = predicted.snapshot().checksum;
117
+ const baselineChecksum = baseline.snapshot().checksum;
118
+ let duplicateRejected = false;
119
+ try {
120
+ history.record(restoreFrame, predicted.snapshot());
121
+ }
122
+ catch {
123
+ duplicateRejected = true;
124
+ }
125
+ const evictingHistory = createDeterministicRollbackHistory(capacity);
126
+ for (let frame = 0; frame < capacity + 4; frame += 1) {
127
+ evictingHistory.record(frame, baseline.snapshot());
128
+ }
129
+ let evictedRestoreRejected = false;
130
+ try {
131
+ evictingHistory.require(0);
132
+ }
133
+ catch {
134
+ evictedRestoreRejected = true;
135
+ }
136
+ const historySnapshot = history.snapshot();
137
+ const restoredHistory = createDeterministicRollbackHistory(capacity);
138
+ restoredHistory.restore(historySnapshot);
139
+ let malformedRestoreRejected = false;
140
+ try {
141
+ restoredHistory.restore({ ...historySnapshot, checksum: 'tampered' });
142
+ }
143
+ catch {
144
+ malformedRestoreRejected = true;
145
+ }
146
+ const withoutChecksum = {
147
+ baselineChecksum,
148
+ predictedChecksumBeforeCorrection,
149
+ resimulatedChecksum,
150
+ restoredHistoryChecksum: restoredHistory.checksum(),
151
+ frames,
152
+ capacity,
153
+ retainedFrames: history.frames(),
154
+ restoreFrame,
155
+ predictedToFrame,
156
+ };
157
+ return {
158
+ checksum: (0, canonical_js_1.defaultChecksum)(withoutChecksum),
159
+ baselineChecksum,
160
+ predictedChecksumBeforeCorrection,
161
+ resimulatedChecksum,
162
+ restoredHistoryChecksum: restoredHistory.checksum(),
163
+ capacity,
164
+ recordedFrames: history.size,
165
+ evictedFrames: Math.max(0, predictedToFrame + 1 - history.size),
166
+ restoredFrame: restoreFrame,
167
+ predictedToFrame,
168
+ resimulatedFrames: predictedToFrame - restoreFrame + 1,
169
+ convergence: baselineChecksum === resimulatedChecksum && baselineChecksum !== predictedChecksumBeforeCorrection,
170
+ duplicateRejected,
171
+ evictedRestoreRejected,
172
+ malformedRestoreRejected,
173
+ rollbackDepthBounded: history.size <= capacity && evictingHistory.oldestFrame === 4,
174
+ orderStable: (0, canonical_js_1.defaultChecksum)(history.frames()) === (0, canonical_js_1.defaultChecksum)(restoredHistory.frames()),
175
+ };
176
+ }
177
+ function assertRollbackFrame(frame) {
178
+ if (!Number.isInteger(frame) || frame < 0) {
179
+ throw new Error(`Deterministic rollback history frame ${frame} must be a non-negative integer`);
180
+ }
181
+ }
182
+ function validateRollbackHistorySnapshot(snapshot, capacity) {
183
+ if (snapshot.capacity !== capacity) {
184
+ throw new Error(`Deterministic rollback history capacity mismatch ${snapshot.capacity} !== ${capacity}`);
185
+ }
186
+ if (snapshot.entries.length > capacity) {
187
+ throw new Error(`Deterministic rollback history snapshot has ${snapshot.entries.length} entries for capacity ${capacity}`);
188
+ }
189
+ const frames = new Set();
190
+ let previousFrame = -1;
191
+ for (const entry of snapshot.entries) {
192
+ assertRollbackFrame(entry.frame);
193
+ if (frames.has(entry.frame)) {
194
+ throw new Error(`Deterministic rollback history snapshot duplicate frame ${entry.frame}`);
195
+ }
196
+ if (entry.frame <= previousFrame) {
197
+ throw new Error('Deterministic rollback history snapshot entries must be sorted by frame');
198
+ }
199
+ frames.add(entry.frame);
200
+ previousFrame = entry.frame;
201
+ if (!Number.isInteger(entry.bytes) || entry.bytes <= 0) {
202
+ throw new Error(`Deterministic rollback history snapshot frame ${entry.frame} has invalid byte size`);
203
+ }
204
+ const expectedBytes = JSON.stringify((0, canonical_js_1.cloneCanonical)(entry.snapshot)).length;
205
+ if (entry.bytes !== expectedBytes) {
206
+ throw new Error(`Deterministic rollback history snapshot frame ${entry.frame} byte size mismatch`);
207
+ }
208
+ const expected = (0, canonical_js_1.defaultChecksum)({ frame: entry.frame, snapshot: entry.snapshot });
209
+ if (entry.checksum !== expected) {
210
+ throw new Error(`Deterministic rollback history snapshot checksum mismatch at frame ${entry.frame}`);
211
+ }
212
+ }
213
+ const expectedSnapshotChecksum = (0, canonical_js_1.defaultChecksum)({
214
+ capacity: snapshot.capacity,
215
+ entries: snapshot.entries,
216
+ });
217
+ if (snapshot.checksum !== expectedSnapshotChecksum) {
218
+ throw new Error('Deterministic rollback history snapshot checksum mismatch');
219
+ }
220
+ }
221
+ function rollbackHistoryDescriptor() {
222
+ return {
223
+ id: 'rollback-history-fixture',
224
+ tickRate: 60,
225
+ components: [
226
+ { id: 'input', singleton: true, fields: [{ name: 'value', kind: 'int', defaultValue: 0 }] },
227
+ { id: 'score', singleton: true, fields: [{ name: 'total', kind: 'int', defaultValue: 0 }] },
228
+ ],
229
+ systems: [{ id: 'score', group: 'simulation', order: 0 }],
230
+ };
231
+ }
232
+ function rollbackHistorySystems() {
233
+ return {
234
+ score(ctx) {
235
+ const input = ctx.getSingleton('input');
236
+ const score = ctx.getSingleton('score');
237
+ const total = score === undefined ? 0 : Number(score.total);
238
+ ctx.setSingleton('score', { total: total + Number(input.value) });
239
+ },
240
+ };
241
+ }
242
+ function stepRollbackRuntime(runtime, systems, input) {
243
+ runtime.setSingleton('input', { value: input });
244
+ runtime.step(systems);
245
+ }
246
+ function actualInput(frame, seed) {
247
+ return ((frame * 17 + seed) % 11) - 5;
248
+ }
249
+ function predictedInput(frame, seed, correctionFrame) {
250
+ return frame < correctionFrame ? actualInput(frame, seed) : actualInput(frame, seed) + 1;
251
+ }
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withRuntimeDeterminismGuards = withRuntimeDeterminismGuards;
4
+ const errors_js_1 = require("./errors.js");
5
+ const noopRestore = () => undefined;
6
+ const deterministicTickReplacement = 'ctx.frame or deterministic tick counters';
7
+ const deterministicStateTransitionReplacement = 'synchronous deterministic state transitions';
8
+ function withRuntimeDeterminismGuards(frame, run) {
9
+ const restoreGuards = [
10
+ replaceValue(Math, 'random', blockedFunction(frame, 'Math.random()', 'ctx.random.nextInt() or ctx.random.nextFixed()')),
11
+ replaceDateConstructor(frame),
12
+ replacePrototypeValue(globalThis.performance, 'now', blockedFunction(frame, 'performance.now()', 'ctx.frame or deterministic tick counters')),
13
+ replacePrototypeValue(globalThis.crypto, 'randomUUID', blockedFunction(frame, 'crypto.randomUUID()', 'ctx.random.nextInt() or deterministic ids')),
14
+ replacePrototypeValue(globalThis.crypto, 'getRandomValues', blockedFunction(frame, 'crypto.getRandomValues()', 'ctx.random deterministic streams')),
15
+ replaceGlobalTimer(frame, 'setTimeout', 'deterministic countdown fields advanced by fixed ticks'),
16
+ replaceGlobalTimer(frame, 'setInterval', 'deterministic countdown fields advanced by fixed ticks'),
17
+ replaceGlobalTimer(frame, 'setImmediate', 'deterministic countdown fields advanced by fixed ticks'),
18
+ replaceGlobalTimer(frame, 'queueMicrotask', 'explicit deterministic event queues in frame state'),
19
+ replaceGlobalTimer(frame, 'requestAnimationFrame', 'render-only callbacks outside simulation'),
20
+ replacePromiseConstructor(frame),
21
+ ];
22
+ try {
23
+ return run();
24
+ }
25
+ finally {
26
+ for (let index = restoreGuards.length - 1; index >= 0; index -= 1) {
27
+ restoreGuards[index]();
28
+ }
29
+ }
30
+ }
31
+ function blockedFunction(frame, api, replacement) {
32
+ return () => {
33
+ throw new errors_js_1.SyncplayError(`${api} is not allowed during deterministic step. Use ${replacement}.`, {
34
+ code: 'runtime-guard.blocked-api',
35
+ frame,
36
+ });
37
+ };
38
+ }
39
+ function replacePromiseConstructor(frame) {
40
+ const target = globalThis;
41
+ const original = target.Promise;
42
+ const blockedPromise = blockedFunction(frame, 'new Promise()', deterministicStateTransitionReplacement);
43
+ const guarded = function deterministicPromiseGuard() {
44
+ throw blockedPromise();
45
+ };
46
+ guarded.prototype = original.prototype;
47
+ for (const key of ['all', 'allSettled', 'any', 'race', 'reject', 'resolve']) {
48
+ guarded[key] = blockedFunction(frame, `Promise.${key}()`, deterministicStateTransitionReplacement);
49
+ }
50
+ return replaceValue(target, 'Promise', guarded);
51
+ }
52
+ function replaceGlobalTimer(frame, key, replacement) {
53
+ const target = globalThis;
54
+ if (target[key] === undefined) {
55
+ return noopRestore;
56
+ }
57
+ return replaceValue(target, key, blockedFunction(frame, `${key}()`, replacement));
58
+ }
59
+ function replaceDateConstructor(frame) {
60
+ const target = globalThis;
61
+ const original = target.Date;
62
+ const blockedDate = blockedFunction(frame, 'new Date()', deterministicTickReplacement);
63
+ const blockedDateNow = blockedFunction(frame, 'Date.now()', deterministicTickReplacement);
64
+ const guarded = function deterministicDateGuard() {
65
+ throw blockedDate();
66
+ };
67
+ guarded.now = blockedDateNow;
68
+ guarded.parse = original.parse.bind(original);
69
+ guarded.UTC = original.UTC.bind(original);
70
+ guarded.prototype = original.prototype;
71
+ return replaceValue(target, 'Date', guarded);
72
+ }
73
+ function replacePrototypeValue(target, key, replacement) {
74
+ if (target === undefined) {
75
+ return noopRestore;
76
+ }
77
+ let current = target;
78
+ while (current !== null) {
79
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
80
+ if (descriptor !== undefined) {
81
+ if (descriptor.configurable !== true) {
82
+ return noopRestore;
83
+ }
84
+ Object.defineProperty(current, key, {
85
+ configurable: true,
86
+ writable: true,
87
+ value: replacement,
88
+ });
89
+ return () => {
90
+ Object.defineProperty(current, key, descriptor);
91
+ };
92
+ }
93
+ current = Object.getPrototypeOf(current);
94
+ }
95
+ return noopRestore;
96
+ }
97
+ function replaceValue(target, key, replacement) {
98
+ const originalDescriptor = Object.getOwnPropertyDescriptor(target, key);
99
+ /* istanbul ignore next -- non-configurable core globals cannot be made safely reversible inside this process. */
100
+ if (originalDescriptor?.configurable !== true) {
101
+ return noopRestore;
102
+ }
103
+ Object.defineProperty(target, key, {
104
+ configurable: true,
105
+ writable: true,
106
+ value: replacement,
107
+ });
108
+ return () => {
109
+ Object.defineProperty(target, key, originalDescriptor);
110
+ };
111
+ }
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.labStep = exports.labDefaultInput = void 0;
4
+ exports.createLabSession = createLabSession;
5
+ exports.canonicalizeLabInput = canonicalizeLabInput;
6
+ exports.hydrateLabInput = hydrateLabInput;
7
+ exports.makeLabInput = makeLabInput;
8
+ exports.applyLabInputs = applyLabInputs;
9
+ exports.packLabInputFrame = packLabInputFrame;
10
+ exports.unpackLabInputFrame = unpackLabInputFrame;
11
+ const offline_session_js_1 = require("./offline-session.js");
12
+ exports.labDefaultInput = { dx: 0, dy: 0, action: 0 };
13
+ const labStep = (state, inputs) => {
14
+ const positions = state.positions.slice();
15
+ const actions = state.actions.slice();
16
+ inputs.forEach((input, slot) => {
17
+ const baseIndex = slot * 2;
18
+ positions[baseIndex] = (positions[baseIndex] ?? 0) + input.dx;
19
+ positions[baseIndex + 1] = (positions[baseIndex + 1] ?? 0) + input.dy;
20
+ actions[slot] = (actions[slot] ?? 0) + input.action;
21
+ });
22
+ return {
23
+ positions,
24
+ actions,
25
+ frame: state.frame + 1,
26
+ ...(state.payload === undefined ? {} : { payload: state.payload }),
27
+ };
28
+ };
29
+ exports.labStep = labStep;
30
+ function createLabSession(options) {
31
+ const initialState = {
32
+ positions: Array.from({ length: options.players * 2 }, () => 0),
33
+ actions: Array.from({ length: options.players }, () => 0),
34
+ frame: 0,
35
+ ...(options.payloadBytes === undefined ? {} : { payload: 'x'.repeat(options.payloadBytes) }),
36
+ };
37
+ return (0, offline_session_js_1.createOfflineSession)({
38
+ tickRate: options.tickRate ?? 30,
39
+ playerCount: options.players,
40
+ initialState,
41
+ step: exports.labStep,
42
+ defaultInput: exports.labDefaultInput,
43
+ inputSchemaId: 'lab-input/v1',
44
+ stateSchemaId: 'lab-state/v1',
45
+ deterministicVersion: '0.0.0-source',
46
+ randomSeed: options.seed,
47
+ snapshotBufferSize: options.snapshotBufferSize,
48
+ replayRecordingMode: options.replayRecordingMode,
49
+ onInputFrameRecorded: options.onInputFrameRecorded,
50
+ onChecksumRecorded: options.onChecksumRecorded,
51
+ canonicalizeInput: canonicalizeLabInput,
52
+ hydrateInput: hydrateLabInput,
53
+ });
54
+ }
55
+ function canonicalizeLabInput(input) {
56
+ return [input.dx, input.dy, input.action];
57
+ }
58
+ function hydrateLabInput(recordedInput) {
59
+ if (Array.isArray(recordedInput) &&
60
+ recordedInput.length === 3 &&
61
+ typeof recordedInput[0] === 'number' &&
62
+ typeof recordedInput[1] === 'number' &&
63
+ (recordedInput[2] === 0 || recordedInput[2] === 1)) {
64
+ return {
65
+ dx: recordedInput[0],
66
+ dy: recordedInput[1],
67
+ action: recordedInput[2],
68
+ };
69
+ }
70
+ throw new Error('Invalid lab replay input');
71
+ }
72
+ function makeLabInput(random) {
73
+ return {
74
+ dx: random.nextInt(-1, 1),
75
+ dy: random.nextInt(-1, 1),
76
+ action: random.nextInt(0, 9) === 0 ? 1 : 0,
77
+ };
78
+ }
79
+ function applyLabInputs(session, players, random) {
80
+ const inputs = [];
81
+ for (let slot = 0; slot < players; slot += 1) {
82
+ const input = makeLabInput(random);
83
+ inputs.push(input);
84
+ session.setInput(slot, input);
85
+ }
86
+ return inputs;
87
+ }
88
+ function packLabInputFrame(inputs) {
89
+ const writer = new BitWriter(inputs.length * 5);
90
+ for (const input of inputs) {
91
+ writer.write(input.dx + 1, 2);
92
+ writer.write(input.dy + 1, 2);
93
+ writer.write(input.action, 1);
94
+ }
95
+ return writer.toBytes();
96
+ }
97
+ function unpackLabInputFrame(bytes, players) {
98
+ const reader = new BitReader(bytes);
99
+ return Array.from({ length: players }, () => {
100
+ const dx = reader.read(2) - 1;
101
+ const dy = reader.read(2) - 1;
102
+ const action = reader.read(1);
103
+ if ((dx !== -1 && dx !== 0 && dx !== 1) || (dy !== -1 && dy !== 0 && dy !== 1) || (action !== 0 && action !== 1)) {
104
+ throw new Error('Invalid packed lab input frame');
105
+ }
106
+ return { dx, dy, action };
107
+ });
108
+ }
109
+ class BitWriter {
110
+ bytes;
111
+ bitOffset = 0;
112
+ constructor(totalBits) {
113
+ this.bytes = new Uint8Array(Math.ceil(totalBits / 8));
114
+ }
115
+ write(value, bits) {
116
+ for (let bit = 0; bit < bits; bit += 1) {
117
+ if (((value >> bit) & 1) === 1) {
118
+ const byteIndex = Math.floor(this.bitOffset / 8);
119
+ const bitIndex = this.bitOffset % 8;
120
+ this.bytes[byteIndex] |= 1 << bitIndex;
121
+ }
122
+ this.bitOffset += 1;
123
+ }
124
+ }
125
+ toBytes() {
126
+ return this.bytes;
127
+ }
128
+ }
129
+ class BitReader {
130
+ bytes;
131
+ bitOffset = 0;
132
+ constructor(bytes) {
133
+ this.bytes = bytes;
134
+ }
135
+ read(bits) {
136
+ let value = 0;
137
+ for (let bit = 0; bit < bits; bit += 1) {
138
+ const byteIndex = Math.floor(this.bitOffset / 8);
139
+ const bitIndex = this.bitOffset % 8;
140
+ value |= ((this.bytes[byteIndex] >> bitIndex) & 1) << bit;
141
+ this.bitOffset += 1;
142
+ }
143
+ return value;
144
+ }
145
+ }