@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,418 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runDeterministicInputTimelineFixture = runDeterministicInputTimelineFixture;
4
+ exports.evaluateDeterministicInputTimelineResult = evaluateDeterministicInputTimelineResult;
5
+ exports.accumulateDeterministicInputTimelineState = accumulateDeterministicInputTimelineState;
6
+ exports.sortDeterministicInputTimelineFlags = sortDeterministicInputTimelineFlags;
7
+ const canonical_js_1 = require("./canonical.js");
8
+ const local_authority_js_1 = require("./local-authority.js");
9
+ const random_js_1 = require("./random.js");
10
+ const deterministic_session_js_1 = require("./deterministic-session.js");
11
+ function runDeterministicInputTimelineFixture(options) {
12
+ validateInputTimelineOptions(options);
13
+ const descriptor = createInputTimelineDescriptor();
14
+ const systems = createInputTimelineSystems();
15
+ const session = (0, deterministic_session_js_1.createDeterministicSessionCore)({
16
+ players: options.players,
17
+ descriptor,
18
+ systems,
19
+ seed: options.seed,
20
+ mapInputsToSingleton,
21
+ });
22
+ const random = new random_js_1.DeterministicRandom(options.seed);
23
+ const lateJoinSlot = options.players - 1;
24
+ const spectatorSlot = 0;
25
+ const botSlot = 1;
26
+ const absentSlot = 2;
27
+ const transientSlot = 3;
28
+ const lateJoinFrame = Math.max(1, Math.floor(options.frames / 4));
29
+ const removeFrame = Math.max(1, Math.floor(options.frames / 3));
30
+ const readdFrame = Math.min(options.frames - 1, Math.max(removeFrame + 1, Math.floor(options.frames / 2)));
31
+ const lastRepeatableValue = Array.from({ length: options.players }, () => 0);
32
+ const hasRepeatableValue = Array.from({ length: options.players }, () => false);
33
+ const rows = [];
34
+ for (let slot = 0; slot < options.players; slot += 1) {
35
+ session.joinAsSpectator(slot, `actor-${slot}`, 0);
36
+ if (slot !== spectatorSlot && slot !== absentSlot && slot !== lateJoinSlot) {
37
+ session.addPlayer(slot, `actor-${slot}`, 0);
38
+ }
39
+ }
40
+ for (let frame = 0; frame < options.frames; frame += 1) {
41
+ if (frame === lateJoinFrame) {
42
+ session.addPlayer(lateJoinSlot, `actor-${lateJoinSlot}`, frame);
43
+ }
44
+ if (frame === removeFrame) {
45
+ session.removePlayer(transientSlot, `actor-${transientSlot}`, frame);
46
+ }
47
+ if (frame === readdFrame) {
48
+ session.addPlayer(transientSlot, `actor-${transientSlot}`, frame);
49
+ }
50
+ const pendingRows = [];
51
+ for (let slot = 0; slot < options.players; slot += 1) {
52
+ const slotState = classifySlot(frame, slot, {
53
+ spectatorSlot,
54
+ botSlot,
55
+ absentSlot,
56
+ transientSlot,
57
+ lateJoinSlot,
58
+ lateJoinFrame,
59
+ removeFrame,
60
+ readdFrame,
61
+ });
62
+ if (!slotState.active) {
63
+ session.replaceInput({ frame, slot, value: 0, flags: ['player-not-present'] });
64
+ pendingRows.push({
65
+ frame,
66
+ slot,
67
+ lane: slotState.lane,
68
+ prediction: 'none',
69
+ verification: 'absent',
70
+ correction: 'none',
71
+ predictedValue: 0,
72
+ verifiedValue: 0,
73
+ flags: ['player-not-present'],
74
+ });
75
+ continue;
76
+ }
77
+ const predictedPolicy = shouldSubmitExplicitPrediction(frame, slot, options.seed) ? 'explicit' : fallbackPolicy(hasRepeatableValue[slot]);
78
+ const predictedValue = predictedPolicy === 'explicit'
79
+ ? random.nextInt(-8, 8)
80
+ : predictedPolicy === 'repeat-last'
81
+ ? lastRepeatableValue[slot]
82
+ : 0;
83
+ const predictedFlags = inputFlags(frame, slot, options.seed);
84
+ if (predictedPolicy === 'explicit') {
85
+ session.submitPredictedInput({ frame, slot, value: predictedValue, flags: predictedFlags });
86
+ }
87
+ const replacement = shouldReplaceInput(frame, slot, options.seed);
88
+ const verifiedValue = replacement
89
+ ? (hasRepeatableValue[slot] ? lastRepeatableValue[slot] : 0)
90
+ : predictedValue + shouldLateFill(frame, slot, options.seed);
91
+ const verifiedFlags = replacement
92
+ ? sortDeterministicInputTimelineFlags(['replaced-by-server', ...(hasRepeatableValue[slot] ? ['repeatable'] : [])])
93
+ : predictedFlags;
94
+ pendingRows.push({
95
+ frame,
96
+ slot,
97
+ lane: slotState.lane,
98
+ prediction: predictedPolicy,
99
+ verification: replacement ? 'replacement' : 'confirmed',
100
+ correction: correctionFor(predictedPolicy, predictedValue, verifiedValue),
101
+ predictedValue,
102
+ verifiedValue,
103
+ flags: verifiedFlags,
104
+ });
105
+ }
106
+ session.predictTo(frame + 1);
107
+ for (const row of pendingRows) {
108
+ if (row.verification === 'absent') {
109
+ continue;
110
+ }
111
+ const input = {
112
+ frame: row.frame,
113
+ slot: row.slot,
114
+ value: row.verifiedValue,
115
+ flags: row.flags,
116
+ };
117
+ if (row.verification === 'replacement') {
118
+ session.replaceInput(input);
119
+ }
120
+ else {
121
+ session.confirmInput(input);
122
+ }
123
+ if (row.flags.includes('repeatable')) {
124
+ hasRepeatableValue[row.slot] = true;
125
+ lastRepeatableValue[row.slot] = row.verifiedValue;
126
+ }
127
+ }
128
+ session.advanceVerifiedTo(frame + 1);
129
+ rows.push(...pendingRows);
130
+ }
131
+ const replay = session.exportReplay();
132
+ const replayVerification = (0, deterministic_session_js_1.verifyDeterministicSessionRuntimeReplay)(replay, {
133
+ players: options.players,
134
+ descriptor,
135
+ systems,
136
+ seed: options.seed,
137
+ mapInputsToSingleton,
138
+ });
139
+ const counts = countRows(rows);
140
+ const negativeCases = runInputTimelineNegativeCases(options.players);
141
+ const rowHash = (0, canonical_js_1.defaultChecksum)(rows);
142
+ const invariants = {
143
+ allFramesCovered: counts.frames === options.frames,
144
+ allSlotsCovered: counts.slots === options.players,
145
+ rowCountStable: rows.length === options.frames * options.players,
146
+ orderStable: rows.every((row, index) => index === 0 || compareRows(rows[index - 1], row) <= 0),
147
+ explicitPredictionCovered: counts.explicitPredictions > 0,
148
+ neutralPredictionCovered: counts.neutralPredictions > 0,
149
+ repeatLastPredictionCovered: counts.repeatLastPredictions > 0,
150
+ confirmedCovered: counts.confirmed > 0,
151
+ replacementCovered: counts.replacements > 0,
152
+ absentCovered: counts.absent > 0,
153
+ botCovered: counts.bot > 0,
154
+ lateJoinCovered: counts.lateJoin > 0,
155
+ lateFillCovered: counts.lateFill > 0,
156
+ rollbackTriggeredCovered: counts.rollbackTriggered > 0,
157
+ replayVerified: replayVerification.result === 'pass',
158
+ inactivePredictionRejected: negativeCases.inactivePredictionRejected,
159
+ malformedValueRejected: negativeCases.malformedValueRejected,
160
+ duplicateIngressRejected: negativeCases.duplicateIngressRejected,
161
+ duplicateAfterRestoreRejected: negativeCases.duplicateAfterRestoreRejected,
162
+ futureIngressRejected: negativeCases.futureIngressRejected,
163
+ staleIngressRejected: negativeCases.staleIngressRejected,
164
+ spectatorIngressRejected: negativeCases.spectatorIngressRejected,
165
+ badSenderRejected: negativeCases.badSenderRejected,
166
+ };
167
+ const invariantChecks = [
168
+ invariants.allFramesCovered,
169
+ invariants.allSlotsCovered,
170
+ invariants.rowCountStable,
171
+ invariants.orderStable,
172
+ invariants.explicitPredictionCovered,
173
+ invariants.neutralPredictionCovered,
174
+ invariants.repeatLastPredictionCovered,
175
+ invariants.confirmedCovered,
176
+ invariants.replacementCovered,
177
+ invariants.absentCovered,
178
+ invariants.botCovered,
179
+ invariants.lateJoinCovered,
180
+ invariants.lateFillCovered,
181
+ invariants.rollbackTriggeredCovered,
182
+ invariants.replayVerified,
183
+ invariants.inactivePredictionRejected,
184
+ invariants.malformedValueRejected,
185
+ invariants.duplicateIngressRejected,
186
+ invariants.duplicateAfterRestoreRejected,
187
+ invariants.futureIngressRejected,
188
+ invariants.staleIngressRejected,
189
+ invariants.spectatorIngressRejected,
190
+ invariants.badSenderRejected,
191
+ ];
192
+ const result = evaluateDeterministicInputTimelineResult(invariantChecks);
193
+ const sampleLimit = options.sampleLimit ?? 64;
194
+ const reportWithoutChecksum = {
195
+ schemaVersion: 1,
196
+ kind: 'deterministic-input-timeline',
197
+ result,
198
+ players: options.players,
199
+ frames: options.frames,
200
+ seed: options.seed,
201
+ rowCount: rows.length,
202
+ rowHash,
203
+ sampledRows: rows.slice(0, sampleLimit),
204
+ ...(options.includeRows === true ? { rows } : {}),
205
+ counts,
206
+ negativeCases,
207
+ replayVerified: replayVerification.result === 'pass',
208
+ replayChecksum: replayVerification.replayChecksum,
209
+ finalVerifiedChecksum: replay.finalVerifiedChecksum,
210
+ invariants,
211
+ };
212
+ return {
213
+ ...reportWithoutChecksum,
214
+ checksum: (0, canonical_js_1.defaultChecksum)(reportWithoutChecksum),
215
+ };
216
+ }
217
+ function createInputTimelineDescriptor() {
218
+ return {
219
+ id: 'input-timeline-fixture',
220
+ tickRate: 60,
221
+ components: [
222
+ { id: 'sessionInput', singleton: true, fields: [
223
+ { name: 'sum', kind: 'int', defaultValue: 0 },
224
+ { name: 'active', kind: 'int', defaultValue: 0 },
225
+ ] },
226
+ { id: 'sessionState', singleton: true, fields: [
227
+ { name: 'sum', kind: 'int', defaultValue: 0 },
228
+ { name: 'frames', kind: 'int', defaultValue: 0 },
229
+ { name: 'activeInputs', kind: 'int', defaultValue: 0 },
230
+ ] },
231
+ ],
232
+ systems: [{ id: 'input-timeline-step', group: 'simulation', order: 0 }],
233
+ };
234
+ }
235
+ function createInputTimelineSystems() {
236
+ return {
237
+ 'input-timeline-step'(ctx) {
238
+ const input = ctx.getSingleton('sessionInput');
239
+ const state = (ctx.getSingleton('sessionState') ?? { sum: 0, frames: 0, activeInputs: 0 });
240
+ ctx.setSingleton('sessionState', accumulateDeterministicInputTimelineState(state, input));
241
+ },
242
+ };
243
+ }
244
+ function evaluateDeterministicInputTimelineResult(invariantChecks) {
245
+ return invariantChecks.every((value) => value) ? 'pass' : 'fail';
246
+ }
247
+ function accumulateDeterministicInputTimelineState(state, input) {
248
+ return {
249
+ sum: Number(state.sum ?? 0) + Number(input.sum ?? 0),
250
+ frames: Number(state.frames ?? 0) + 1,
251
+ activeInputs: Number(state.activeInputs ?? 0) + Number(input.active ?? 0),
252
+ };
253
+ }
254
+ function mapInputsToSingleton(_frame, inputs) {
255
+ return {
256
+ sum: inputs.reduce((sum, input) => sum + input.value, 0),
257
+ active: inputs.length,
258
+ };
259
+ }
260
+ function classifySlot(frame, slot, config) {
261
+ if (slot === config.spectatorSlot) {
262
+ return { lane: 'spectator', active: false };
263
+ }
264
+ if (slot === config.absentSlot) {
265
+ return { lane: 'absent', active: false };
266
+ }
267
+ if (slot === config.lateJoinSlot && frame < config.lateJoinFrame) {
268
+ return { lane: 'late-join', active: false };
269
+ }
270
+ if (slot === config.transientSlot && frame >= config.removeFrame && frame < config.readdFrame) {
271
+ return { lane: 'absent', active: false };
272
+ }
273
+ if (slot === config.botSlot) {
274
+ return { lane: 'bot', active: true };
275
+ }
276
+ return { lane: 'human', active: true };
277
+ }
278
+ function shouldSubmitExplicitPrediction(frame, slot, seed) {
279
+ return (frame + slot + seed) % 11 !== 0;
280
+ }
281
+ function shouldReplaceInput(frame, slot, seed) {
282
+ return (frame * 3 + slot + seed) % 17 === 0;
283
+ }
284
+ function shouldLateFill(frame, slot, seed) {
285
+ return (frame + slot + seed) % 5 === 0 ? 1 : 0;
286
+ }
287
+ function inputFlags(frame, slot, seed) {
288
+ const flags = [];
289
+ if ((frame + slot + seed) % 7 === 0) {
290
+ flags.push('repeatable');
291
+ }
292
+ if ((frame + slot + seed) % 13 === 0) {
293
+ flags.push('command');
294
+ }
295
+ return sortDeterministicInputTimelineFlags(flags);
296
+ }
297
+ function fallbackPolicy(hasRepeatable) {
298
+ return hasRepeatable ? 'repeat-last' : 'neutral';
299
+ }
300
+ function correctionFor(prediction, predictedValue, verifiedValue) {
301
+ if (prediction === 'none' || predictedValue === verifiedValue) {
302
+ return 'none';
303
+ }
304
+ return prediction === 'explicit' ? 'late-fill' : 'rollback-triggered';
305
+ }
306
+ function sortDeterministicInputTimelineFlags(flags) {
307
+ return [...new Set(flags)].sort(compareStrings);
308
+ }
309
+ function countRows(rows) {
310
+ const frames = new Set();
311
+ const slots = new Set();
312
+ const counts = {
313
+ rows: rows.length,
314
+ frames: 0,
315
+ slots: 0,
316
+ explicitPredictions: 0,
317
+ neutralPredictions: 0,
318
+ repeatLastPredictions: 0,
319
+ confirmed: 0,
320
+ replacements: 0,
321
+ absent: 0,
322
+ bot: 0,
323
+ lateJoin: 0,
324
+ lateFill: 0,
325
+ rollbackTriggered: 0,
326
+ };
327
+ for (const row of rows) {
328
+ frames.add(row.frame);
329
+ slots.add(row.slot);
330
+ counts.explicitPredictions += row.prediction === 'explicit' ? 1 : 0;
331
+ counts.neutralPredictions += row.prediction === 'neutral' ? 1 : 0;
332
+ counts.repeatLastPredictions += row.prediction === 'repeat-last' ? 1 : 0;
333
+ counts.confirmed += row.verification === 'confirmed' ? 1 : 0;
334
+ counts.replacements += row.verification === 'replacement' ? 1 : 0;
335
+ counts.absent += row.verification === 'absent' ? 1 : 0;
336
+ counts.bot += row.lane === 'bot' ? 1 : 0;
337
+ counts.lateJoin += row.lane === 'late-join' ? 1 : 0;
338
+ counts.lateFill += row.correction === 'late-fill' ? 1 : 0;
339
+ counts.rollbackTriggered += row.correction === 'rollback-triggered' ? 1 : 0;
340
+ }
341
+ counts.frames = frames.size;
342
+ counts.slots = slots.size;
343
+ return counts;
344
+ }
345
+ function runInputTimelineNegativeCases(players) {
346
+ const descriptor = createInputTimelineDescriptor();
347
+ const systems = createInputTimelineSystems();
348
+ const session = (0, deterministic_session_js_1.createDeterministicSessionCore)({ players, descriptor, systems, seed: 19, mapInputsToSingleton });
349
+ session.joinAsSpectator(0, 'actor-0', 0);
350
+ const inactivePredictionRejected = throws(() => session.submitPredictedInput({ frame: 0, slot: 0, value: 1 }));
351
+ session.addPlayer(1, 'actor-1', 0);
352
+ const validPredictionAccepted = !throws(() => session.submitPredictedInput({ frame: 0, slot: 1, value: 1 }));
353
+ const malformedValueRejected = throws(() => session.submitPredictedInput({ frame: 0, slot: 1, value: 1.5 }));
354
+ const runtime = (0, local_authority_js_1.createDeterministicLocalAuthorityRuntime)(players, { maxFrameLead: 4 });
355
+ const firstAccepted = runtime.submitInput({ slot: 1, frame: 0, value: 7, inputId: 'input:slot-1:frame-0' });
356
+ const duplicateIngressRejected = firstAccepted && !runtime.submitInput({ slot: 1, frame: 0, value: 7, inputId: 'input:slot-1:frame-0' });
357
+ const futureIngressRejected = !runtime.submitInput({ slot: 1, frame: 99, value: 1 });
358
+ runtime.step();
359
+ runtime.step();
360
+ const staleIngressRejected = !runtime.submitInput({ slot: 1, frame: 0, value: 1, inputId: 'input:stale' });
361
+ runtime.addSpectator(2);
362
+ const spectatorIngressRejected = !runtime.submitInput({ slot: 2, frame: runtime.state.frame, value: 3 });
363
+ const badSenderRejected = !runtime.submitInput({ slot: 1, senderSlot: 2, frame: runtime.state.frame, value: 3 });
364
+ const restoreSource = (0, local_authority_js_1.createDeterministicLocalAuthorityRuntime)(players, { maxFrameLead: 4 });
365
+ const restoreAccepted = restoreSource.submitInput({ slot: 1, frame: 0, value: 5, inputId: 'input:persisted' });
366
+ const snapshot = restoreSource.snapshot();
367
+ const restored = (0, local_authority_js_1.createDeterministicLocalAuthorityRuntime)(players, { maxFrameLead: 4 });
368
+ restored.restore(snapshot);
369
+ const duplicateAfterRestoreRejected = restoreAccepted && !restored.submitInput({ slot: 1, frame: 0, value: 5, inputId: 'input:persisted' });
370
+ return {
371
+ validPredictionAccepted,
372
+ inactivePredictionRejected,
373
+ malformedValueRejected,
374
+ duplicateIngressRejected,
375
+ duplicateAfterRestoreRejected,
376
+ futureIngressRejected,
377
+ staleIngressRejected,
378
+ spectatorIngressRejected,
379
+ badSenderRejected,
380
+ };
381
+ }
382
+ function compareRows(left, right) {
383
+ return left.frame - right.frame || left.slot - right.slot;
384
+ }
385
+ function compareStrings(left, right) {
386
+ if (left < right) {
387
+ return -1;
388
+ }
389
+ /* istanbul ignore else -- input timeline flag sorting de-duplicates before comparing. */
390
+ if (left > right) {
391
+ return 1;
392
+ }
393
+ /* istanbul ignore next -- stable flag sorting receives de-duplicated flag strings. */
394
+ return 0;
395
+ }
396
+ function throws(action) {
397
+ try {
398
+ action();
399
+ return false;
400
+ }
401
+ catch {
402
+ return true;
403
+ }
404
+ }
405
+ function validateInputTimelineOptions(options) {
406
+ if (!Number.isInteger(options.players) || options.players < 16 || options.players > 128) {
407
+ throw new Error('input timeline players must be an integer between 16 and 128');
408
+ }
409
+ if (!Number.isInteger(options.frames) || options.frames < 96) {
410
+ throw new Error('input timeline frames must be an integer >= 96');
411
+ }
412
+ if (!Number.isInteger(options.seed)) {
413
+ throw new Error('input timeline seed must be an integer');
414
+ }
415
+ if (options.sampleLimit !== undefined && (!Number.isInteger(options.sampleLimit) || options.sampleLimit < 0)) {
416
+ throw new Error('input timeline sampleLimit must be a non-negative integer');
417
+ }
418
+ }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runInstantReplay = runInstantReplay;
4
+ exports.createInstantReplayRunner = createInstantReplayRunner;
5
+ const errors_js_1 = require("./errors.js");
6
+ const offline_session_js_1 = require("./offline-session.js");
7
+ function runInstantReplay(options) {
8
+ const session = options.createSession();
9
+ session.restoreSnapshot(options.snapshot);
10
+ let expectedFrame = options.snapshot.frame;
11
+ for (const inputFrame of options.inputs) {
12
+ if (inputFrame.frame !== expectedFrame) {
13
+ throw new errors_js_1.SyncplayError(`Instant replay inputs must be contiguous from snapshot frame ${options.snapshot.frame}: expected frame ${expectedFrame}, got ${inputFrame.frame}`, { code: 'instant-replay.non-contiguous-inputs', frame: inputFrame.frame });
14
+ }
15
+ for (let slot = 0; slot < inputFrame.inputs.length; slot += 1) {
16
+ const recorded = inputFrame.inputs[slot];
17
+ // NOT `??`: a decoder returning null (the canonical neutral) must win
18
+ // over the raw recorded value (adversarial-review LOW).
19
+ session.setInput(slot, options.decodeInput !== undefined ? options.decodeInput(recorded, slot) : recorded);
20
+ }
21
+ session.stepFrames(1);
22
+ options.onFrame(session.getState(), session.currentFrame);
23
+ expectedFrame += 1;
24
+ }
25
+ const finalSnapshot = session.getSnapshot(session.currentFrame);
26
+ /* istanbul ignore if -- the session records a snapshot on every stepped frame. */
27
+ if (finalSnapshot === undefined) {
28
+ throw new errors_js_1.SyncplayError('Instant replay session has no snapshot at its final frame', {
29
+ code: 'instant-replay.missing-final-snapshot',
30
+ frame: session.currentFrame,
31
+ });
32
+ }
33
+ return { framesPlayed: options.inputs.length, finalChecksum: finalSnapshot.checksum };
34
+ }
35
+ /**
36
+ * Binds a session config to a replay window. Each `run` simulates on a fresh
37
+ * throwaway session with replay recording off, a single-frame snapshot buffer,
38
+ * and the config's recording callbacks stripped — so a killcam never leaks
39
+ * events into the live game's observers and stays allocation-light.
40
+ */
41
+ function createInstantReplayRunner(config, options) {
42
+ (0, errors_js_1.invariant)(Number.isInteger(options.window) && options.window >= 1, 'Instant replay window must be a positive integer', 'instant-replay.invalid-window');
43
+ const replayConfig = {
44
+ ...config,
45
+ replayRecordingMode: 'none',
46
+ snapshotBufferSize: 1,
47
+ onInputFrameRecorded: undefined,
48
+ onCommandFrameRecorded: undefined,
49
+ onChecksumRecorded: undefined,
50
+ };
51
+ return {
52
+ window: options.window,
53
+ run(runOptions) {
54
+ if (runOptions.inputs.length > options.window) {
55
+ throw new errors_js_1.SyncplayError(`Instant replay covers ${runOptions.inputs.length} ticks, above window ${options.window}`, { code: 'instant-replay.window-exceeded', frame: runOptions.snapshot.frame });
56
+ }
57
+ return runInstantReplay({
58
+ createSession: () => (0, offline_session_js_1.createOfflineSession)(replayConfig),
59
+ snapshot: runOptions.snapshot,
60
+ inputs: runOptions.inputs,
61
+ decodeInput: runOptions.decodeInput,
62
+ onFrame: runOptions.onFrame,
63
+ });
64
+ },
65
+ };
66
+ }