@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,837 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runDeterministicLagCompensation3DFixture = runDeterministicLagCompensation3DFixture;
4
+ exports.createDeterministicLagCompensation3DHistory = createDeterministicLagCompensation3DHistory;
5
+ exports.rewindDeterministicLagCompensatedTargets3D = rewindDeterministicLagCompensatedTargets3D;
6
+ exports.buildDeterministicLagCompensatedHitboxProxies3D = buildDeterministicLagCompensatedHitboxProxies3D;
7
+ exports.validateDeterministicLagCompensatedShot3D = validateDeterministicLagCompensatedShot3D;
8
+ exports.validateDeterministicAuthoritativeLagCompensatedShot3D = validateDeterministicAuthoritativeLagCompensatedShot3D;
9
+ exports.runDeterministicLagCompensation3DApiFixture = runDeterministicLagCompensation3DApiFixture;
10
+ exports.runDeterministicAuthoritativeLagCompensation3DFixture = runDeterministicAuthoritativeLagCompensation3DFixture;
11
+ const canonical_js_1 = require("./canonical.js");
12
+ const physics3d_js_1 = require("./physics3d.js");
13
+ function runDeterministicLagCompensation3DFixture(players = 128, frames = 512, seed = 71) {
14
+ const primary = simulateLagCompensation3D(players, frames, seed);
15
+ const restored = simulateLagCompensation3D(players, frames, seed);
16
+ return {
17
+ ...primary,
18
+ restoredChecksum: restored.checksum,
19
+ rollbackEquivalent: primary.checksum === restored.checksum,
20
+ };
21
+ }
22
+ function createDeterministicLagCompensation3DHistory(options = {}) {
23
+ const maxStoredFrames = options.maxStoredFrames ?? 128;
24
+ if (!Number.isInteger(maxStoredFrames) || maxStoredFrames <= 0) {
25
+ throw new Error('Deterministic lag compensation history maxStoredFrames must be a positive integer');
26
+ }
27
+ let targets = [];
28
+ let occluders = normalizePhysicsBodies(options.occluders ?? []);
29
+ const prune = () => {
30
+ if (targets.length === 0) {
31
+ return;
32
+ }
33
+ const newestFrame = targets.reduce((max, target) => Math.max(max, target.frame), 0);
34
+ const oldestFrame = newestFrame - maxStoredFrames + 1;
35
+ targets = targets.filter((target) => target.frame >= oldestFrame).sort(compareLagCompensatedTarget3DByFrame);
36
+ };
37
+ return {
38
+ maxStoredFrames,
39
+ get frameCount() {
40
+ return new Set(targets.map((target) => target.frame)).size;
41
+ },
42
+ get targetCount() {
43
+ return targets.length;
44
+ },
45
+ recordTargets(frameTargets) {
46
+ const normalized = normalizeLagCompensatedTargets3D(frameTargets);
47
+ const duplicateKeys = new Set(targets.map((target) => lagCompensatedTargetKey(target)));
48
+ for (const target of normalized) {
49
+ const key = lagCompensatedTargetKey(target);
50
+ if (duplicateKeys.has(key)) {
51
+ throw new Error(`Duplicate deterministic lag compensation target ${key}`);
52
+ }
53
+ duplicateKeys.add(key);
54
+ }
55
+ targets = [...targets, ...normalized].sort(compareLagCompensatedTarget3DByFrame);
56
+ prune();
57
+ },
58
+ rewind(frame, shooterSlot) {
59
+ assertLagCompensationFrame(frame);
60
+ const rewoundTargets = rewindDeterministicLagCompensatedTargets3D(targets, frame);
61
+ const proxies = buildDeterministicLagCompensatedHitboxProxies3D(targets, frame, shooterSlot);
62
+ const withoutChecksum = {
63
+ frame,
64
+ targets: rewoundTargets,
65
+ proxies,
66
+ interpolatedTargets: rewoundTargets.filter(isInterpolatedLagCompensatedTarget3D).length,
67
+ };
68
+ return {
69
+ ...withoutChecksum,
70
+ checksum: (0, canonical_js_1.defaultChecksum)(withoutChecksum),
71
+ };
72
+ },
73
+ validateShot(shot) {
74
+ return validateDeterministicLagCompensatedShot3D(targets, occluders, shot);
75
+ },
76
+ snapshot() {
77
+ const withoutChecksum = {
78
+ maxStoredFrames,
79
+ targets: normalizeLagCompensatedTargets3D(targets),
80
+ occluders,
81
+ };
82
+ return {
83
+ ...withoutChecksum,
84
+ checksum: (0, canonical_js_1.defaultChecksum)(withoutChecksum),
85
+ };
86
+ },
87
+ restore(snapshot) {
88
+ const expected = (0, canonical_js_1.defaultChecksum)({
89
+ maxStoredFrames: snapshot.maxStoredFrames,
90
+ targets: normalizeLagCompensatedTargets3D(snapshot.targets),
91
+ occluders: normalizePhysicsBodies(snapshot.occluders),
92
+ });
93
+ if (snapshot.maxStoredFrames !== maxStoredFrames || snapshot.checksum !== expected) {
94
+ throw new Error('Deterministic lag compensation history snapshot checksum mismatch');
95
+ }
96
+ targets = [...normalizeLagCompensatedTargets3D(snapshot.targets)];
97
+ occluders = normalizePhysicsBodies(snapshot.occluders);
98
+ },
99
+ checksum() {
100
+ return (0, canonical_js_1.defaultChecksum)({ maxStoredFrames, targets: normalizeLagCompensatedTargets3D(targets), occluders });
101
+ },
102
+ };
103
+ }
104
+ function rewindDeterministicLagCompensatedTargets3D(history, rewindFrame) {
105
+ assertLagCompensationFrame(rewindFrame);
106
+ return interpolateLagCompensatedTargets3D(normalizeLagCompensatedTargets3D(history), rewindFrame);
107
+ }
108
+ function buildDeterministicLagCompensatedHitboxProxies3D(history, rewindFrame, shooterSlot) {
109
+ assertLagCompensationFrame(rewindFrame);
110
+ return buildHitboxProxies(normalizeLagCompensatedTargets3D(history), rewindFrame, shooterSlot);
111
+ }
112
+ function validateDeterministicLagCompensatedShot3D(history, occluders, shot) {
113
+ return validateLagCompensatedShot3D(normalizeLagCompensatedTargets3D(history), normalizePhysicsBodies(occluders), shot);
114
+ }
115
+ function validateDeterministicAuthoritativeLagCompensatedShot3D(history, occluders, shot, context) {
116
+ if (!Number.isInteger(context.acceptedShotFrame)
117
+ || !Number.isInteger(context.serverReceiveFrame)
118
+ || !Number.isInteger(context.maxHistoryFrames)
119
+ || !Number.isInteger(context.maxLatencyFrames)
120
+ || context.acceptedShotFrame < 0
121
+ || context.serverReceiveFrame < context.acceptedShotFrame
122
+ || context.maxHistoryFrames <= 0
123
+ || context.maxLatencyFrames < 0) {
124
+ return shotResult(shot, 'invalid-shot', shot.shotFrame - shot.latencyFrames, '', '', 0, 0, 0);
125
+ }
126
+ const derivedLatencyFrames = context.serverReceiveFrame - context.acceptedShotFrame;
127
+ if (derivedLatencyFrames > context.maxLatencyFrames
128
+ || shot.shotFrame !== context.serverReceiveFrame
129
+ || shot.latencyFrames !== derivedLatencyFrames
130
+ || shot.maxHistoryFrames !== context.maxHistoryFrames) {
131
+ return shotResult(shot, 'invalid-shot', context.acceptedShotFrame, '', '', 0, 0, 0);
132
+ }
133
+ return validateDeterministicLagCompensatedShot3D(history, occluders, {
134
+ ...shot,
135
+ shotFrame: context.serverReceiveFrame,
136
+ latencyFrames: derivedLatencyFrames,
137
+ maxHistoryFrames: context.maxHistoryFrames,
138
+ });
139
+ }
140
+ function runDeterministicLagCompensation3DApiFixture() {
141
+ const history = createDeterministicLagCompensation3DHistory({ maxStoredFrames: 4 });
142
+ const frameTargets = [
143
+ [
144
+ targetSnapshot(0, 'slot-0', 0, 1, 0, 'standing'),
145
+ targetSnapshot(0, 'slot-1', 10, 1, 0, 'standing'),
146
+ ],
147
+ [
148
+ targetSnapshot(1, 'slot-0', 0, 1, 0, 'standing'),
149
+ targetSnapshot(1, 'slot-1', 12, 1, 0, 'standing'),
150
+ ],
151
+ [
152
+ targetSnapshot(2, 'slot-0', 0, 1, 0, 'standing'),
153
+ targetSnapshot(2, 'slot-1', 14, 1, 0, 'crouching'),
154
+ ],
155
+ [
156
+ targetSnapshot(3, 'slot-0', 0, 1, 0, 'standing'),
157
+ targetSnapshot(3, 'slot-1', 16, 1, 0, 'crouching'),
158
+ ],
159
+ ];
160
+ for (const targetsAtFrame of frameTargets) {
161
+ history.recordTargets(targetsAtFrame);
162
+ }
163
+ const exactRewind = history.rewind(2, 0);
164
+ const sparseHistory = createDeterministicLagCompensation3DHistory({ maxStoredFrames: 8 });
165
+ sparseHistory.recordTargets(frameTargets[0]);
166
+ sparseHistory.recordTargets(frameTargets[2]);
167
+ const interpolatedRewind = sparseHistory.rewind(1, 0);
168
+ const hitShot = shotAtTarget('api-hit', 0, 3, 1, 10, 1.45, 0);
169
+ const hit = history.validateShot(hitShot);
170
+ const generatedZeroDirectionRejectionReason = captureGeneratedZeroDirectionRejectionReason();
171
+ const occluded = validateDeterministicLagCompensatedShot3D(history.snapshot().targets, [{
172
+ id: 'occluder.api-wall',
173
+ kind: 'static',
174
+ x: 8,
175
+ y: 1.4,
176
+ z: 0,
177
+ vx: 0,
178
+ vy: 0,
179
+ vz: 0,
180
+ shape: { type: 'box', halfX: 0.25, halfY: 2, halfZ: 2 },
181
+ layer: 2,
182
+ mask: 3,
183
+ }], hitShot);
184
+ const stale = history.validateShot({ ...hitShot, id: 'api-stale', latencyFrames: 8, maxHistoryFrames: 2 });
185
+ const future = history.validateShot({ ...hitShot, id: 'api-future', latencyFrames: -1 });
186
+ const snapshot = history.snapshot();
187
+ const restored = createDeterministicLagCompensation3DHistory({ maxStoredFrames: 4 });
188
+ restored.restore(snapshot);
189
+ history.recordTargets([
190
+ targetSnapshot(4, 'slot-0', 0, 1, 0, 'standing'),
191
+ targetSnapshot(4, 'slot-1', 18, 1, 0, 'standing'),
192
+ ]);
193
+ const withoutChecksum = {
194
+ exactRewindTargets: exactRewind.targets.length,
195
+ interpolatedRewindTargets: interpolatedRewind.interpolatedTargets,
196
+ hit,
197
+ occluded,
198
+ stale,
199
+ future,
200
+ generatedZeroDirectionRejectionReason,
201
+ restoredChecksum: restored.checksum(),
202
+ snapshotChecksum: snapshot.checksum,
203
+ prunedFrameCount: history.frameCount,
204
+ };
205
+ return {
206
+ checksum: (0, canonical_js_1.defaultChecksum)(withoutChecksum),
207
+ restoredChecksum: restored.checksum(),
208
+ rollbackEquivalent: restored.checksum() === snapshot.checksum,
209
+ generatedZeroDirectionRejectionReason,
210
+ exactRewindTargets: exactRewind.targets.length,
211
+ interpolatedRewindTargets: interpolatedRewind.interpolatedTargets,
212
+ hitReason: hit.reason,
213
+ occludedReason: occluded.reason,
214
+ staleReason: stale.reason,
215
+ futureReason: future.reason,
216
+ proxiesCreated: hit.proxiesCreated,
217
+ proxiesDestroyed: hit.proxiesDestroyed,
218
+ historyFrameCount: history.frameCount,
219
+ prunedOldFrames: history.frameCount === 4 && history.snapshot().targets.every((target) => target.frame >= 1),
220
+ };
221
+ }
222
+ function runDeterministicAuthoritativeLagCompensation3DFixture() {
223
+ const history = createDeterministicLagCompensation3DHistory({ maxStoredFrames: 8 });
224
+ for (const targetsAtFrame of [
225
+ [
226
+ targetSnapshot(0, 'slot-0', 0, 1, 0, 'standing'),
227
+ targetSnapshot(0, 'slot-1', 6, 1, 0, 'standing'),
228
+ ],
229
+ [
230
+ targetSnapshot(1, 'slot-0', 0, 1, 0, 'standing'),
231
+ targetSnapshot(1, 'slot-1', 8, 1, 0, 'standing'),
232
+ ],
233
+ [
234
+ targetSnapshot(2, 'slot-0', 0, 1, 0, 'standing'),
235
+ targetSnapshot(2, 'slot-1', 12, 1, 0, 'standing'),
236
+ ],
237
+ [
238
+ targetSnapshot(3, 'slot-0', 0, 1, 0, 'standing'),
239
+ targetSnapshot(3, 'slot-1', 16, 1, 0, 'standing'),
240
+ ],
241
+ ]) {
242
+ history.recordTargets(targetsAtFrame);
243
+ }
244
+ const context = {
245
+ acceptedShotFrame: 1,
246
+ serverReceiveFrame: 3,
247
+ maxHistoryFrames: 4,
248
+ maxLatencyFrames: 4,
249
+ };
250
+ const shot = { ...shotAtTarget('authority-hit', 0, 3, 2, 8, 1.45, 0), maxHistoryFrames: 4 };
251
+ const authorityHit = validateDeterministicAuthoritativeLagCompensatedShot3D(history.snapshot().targets, [], shot, context);
252
+ const forgedLatency = validateDeterministicAuthoritativeLagCompensatedShot3D(history.snapshot().targets, [], {
253
+ ...shot,
254
+ id: 'forged-latency',
255
+ latencyFrames: 1,
256
+ }, context);
257
+ const forgedShotFrame = validateDeterministicAuthoritativeLagCompensatedShot3D(history.snapshot().targets, [], {
258
+ ...shot,
259
+ id: 'forged-shot-frame',
260
+ shotFrame: 2,
261
+ }, context);
262
+ const forgedHistory = validateDeterministicAuthoritativeLagCompensatedShot3D(history.snapshot().targets, [], {
263
+ ...shot,
264
+ id: 'forged-history',
265
+ maxHistoryFrames: 99,
266
+ }, context);
267
+ const rawForged = validateDeterministicLagCompensatedShot3D(history.snapshot().targets, [], {
268
+ ...shot,
269
+ id: 'raw-forged-latency',
270
+ latencyFrames: 1,
271
+ });
272
+ const snapshot = history.snapshot();
273
+ const restored = createDeterministicLagCompensation3DHistory({ maxStoredFrames: 8 });
274
+ restored.restore(snapshot);
275
+ const metrics = {
276
+ authorityHitReason: authorityHit.reason,
277
+ forgedLatencyReason: forgedLatency.reason,
278
+ forgedShotFrameReason: forgedShotFrame.reason,
279
+ forgedHistoryReason: forgedHistory.reason,
280
+ derivedRewindFrame: authorityHit.rewindFrame,
281
+ rawForgedRewindFrame: rawForged.rewindFrame,
282
+ authorityTimingRejected: [forgedLatency, forgedShotFrame, forgedHistory].filter((result) => result.reason === 'invalid-shot').length,
283
+ replayChecksum: history.checksum(),
284
+ restoredChecksum: restored.checksum(),
285
+ rollbackEquivalent: history.checksum() === restored.checksum(),
286
+ };
287
+ return { ...metrics, checksum: (0, canonical_js_1.defaultChecksum)(metrics) };
288
+ }
289
+ function simulateLagCompensation3D(players, frames, seed) {
290
+ const occlusionShotFrame = Math.max(1, Math.min(Math.max(1, frames - 1), 9));
291
+ const occlusionRewindFrame = Math.max(0, occlusionShotFrame - 1);
292
+ const history = [
293
+ ...buildTargetHistory(players, frames, seed),
294
+ targetSnapshot(occlusionRewindFrame, 'occlusion-target', 4, 1, 0, 'standing'),
295
+ ];
296
+ const occluders = buildStaticOccluders();
297
+ const shots = [
298
+ ...buildShotCorpus(players, frames, seed),
299
+ {
300
+ id: 'shot-occluded-fixed-wall',
301
+ shooterSlot: 0,
302
+ shotFrame: occlusionShotFrame,
303
+ latencyFrames: occlusionShotFrame - occlusionRewindFrame,
304
+ x: -4,
305
+ y: 1.45,
306
+ z: 0,
307
+ dx: 1,
308
+ dy: 0,
309
+ dz: 0,
310
+ maxDistance: 16,
311
+ weapon: 'rifle',
312
+ pellets: [{ dx: 1, dy: 0, dz: 0 }],
313
+ maxHistoryFrames: 12,
314
+ },
315
+ ];
316
+ const sparseHistory = history.filter((snapshot) => snapshot.frame % 2 === 0);
317
+ const interpolationShots = buildInterpolationShotCorpus(seed);
318
+ const results = [
319
+ ...shots.map((shot) => validateLagCompensatedShot3D(history, occluders, shot)),
320
+ ...interpolationShots.map((shot) => validateLagCompensatedShot3D(sparseHistory, occluders, shot)),
321
+ ];
322
+ const metrics = {
323
+ players,
324
+ frames,
325
+ shots: shots.length + interpolationShots.length,
326
+ rifleShots: [...shots, ...interpolationShots].filter((shot) => shot.weapon === 'rifle').length,
327
+ shotgunShots: [...shots, ...interpolationShots].filter((shot) => shot.weapon === 'shotgun').length,
328
+ burstShots: [...shots, ...interpolationShots].filter((shot) => shot.weapon === 'burst').length,
329
+ hits: results.filter((result) => result.reason === 'hit').length,
330
+ misses: results.filter((result) => result.reason === 'miss').length,
331
+ occluded: results.filter((result) => result.reason === 'occluded').length,
332
+ staleRejected: results.filter((result) => result.reason === 'stale-frame').length,
333
+ futureRejected: results.filter((result) => result.reason === 'future-frame').length,
334
+ multiHitboxQueries: results.filter((result) => result.proxiesCreated >= 2).length,
335
+ animatedHitboxSamples: results.filter((result) => result.hitboxId.includes('.head') || result.hitboxId.includes('.chest')).length,
336
+ interpolatedHitboxSamples: results.filter((result) => result.interpolatedProxies > 0).length,
337
+ staticOcclusionQueries: results.filter((result) => result.reason === 'occluded' || result.reason === 'hit').length,
338
+ proxyBodiesCreated: results.reduce((sum, result) => sum + result.proxiesCreated, 0),
339
+ proxyBodiesDestroyed: results.reduce((sum, result) => sum + result.proxiesDestroyed, 0),
340
+ proxyLifecycleBalanced: results.every((result) => result.proxiesCreated === result.proxiesDestroyed),
341
+ perClientShotTimesCovered: new Set([...shots, ...interpolationShots].map((shot) => shot.latencyFrames)).size >= 4,
342
+ };
343
+ return {
344
+ ...metrics,
345
+ checksum: (0, canonical_js_1.defaultChecksum)({ metrics, results }),
346
+ };
347
+ }
348
+ function validateLagCompensatedShot3D(history, occluders, shot) {
349
+ if (!Number.isInteger(shot.shotFrame)
350
+ || !Number.isInteger(shot.latencyFrames)
351
+ || !Number.isFinite(shot.x)
352
+ || !Number.isFinite(shot.y)
353
+ || !Number.isFinite(shot.z)
354
+ || !Number.isFinite(shot.dx)
355
+ || !Number.isFinite(shot.dy)
356
+ || !Number.isFinite(shot.dz)
357
+ || !Number.isFinite(shot.maxDistance)
358
+ || shot.maxDistance <= 0
359
+ || shot.pellets.length === 0
360
+ || shot.pellets.some((pellet) => !Number.isFinite(pellet.dx) || !Number.isFinite(pellet.dy) || !Number.isFinite(pellet.dz))) {
361
+ return shotResult(shot, 'invalid-shot', shot.shotFrame - shot.latencyFrames, '', '', 0, 0, 0);
362
+ }
363
+ const rewindFrame = shot.shotFrame - shot.latencyFrames;
364
+ if (rewindFrame > shot.shotFrame) {
365
+ return shotResult(shot, 'future-frame', rewindFrame, '', '', 0, 0, 0);
366
+ }
367
+ if (rewindFrame < 0 || shot.shotFrame - rewindFrame > shot.maxHistoryFrames) {
368
+ return shotResult(shot, 'stale-frame', rewindFrame, '', '', 0, 0, 0);
369
+ }
370
+ const shotDirection = normalizeDirection(shot.dx, shot.dy, shot.dz);
371
+ if (shotDirection === undefined) {
372
+ return shotResult(shot, 'invalid-shot', rewindFrame, '', '', 0, 0, 0);
373
+ }
374
+ const pelletDirections = [];
375
+ for (const pellet of shot.pellets) {
376
+ const pelletDirection = normalizeDirection(pellet.dx, pellet.dy, pellet.dz);
377
+ if (pelletDirection === undefined || !pelletDirectionMatchesShot(shotDirection, pelletDirection)) {
378
+ return shotResult(shot, 'invalid-shot', rewindFrame, '', '', 0, 0, 0);
379
+ }
380
+ pelletDirections.push(pelletDirection);
381
+ }
382
+ const proxies = buildHitboxProxies(history, rewindFrame, shot.shooterSlot);
383
+ const pelletHits = pelletDirections.map((pelletDirection) => validatePellet([...occluders, ...proxies], shot, pelletDirection));
384
+ const hitPellets = pelletHits.filter(isHitPellet);
385
+ const occludedPellets = pelletHits.filter(isOccludedPellet);
386
+ const firstHit = hitPellets[0];
387
+ const reason = firstHit !== undefined ? 'hit' : occludedPellets.length > 0 ? 'occluded' : 'miss';
388
+ const firstOcclusion = occludedPellets[0];
389
+ return shotResult(shot, reason, rewindFrame, firstHit?.targetId ?? '', firstHit?.hitboxId ?? '', firstHit?.distance ?? firstOcclusion?.distance ?? 0, hitPellets.length, proxies.length, proxies.filter((proxy) => proxy.id.includes('.interpolated.')).length);
390
+ }
391
+ function pelletDirectionMatchesShot(shotDirection, pelletDirection) {
392
+ const dot = shotDirection.x * pelletDirection.x + shotDirection.y * pelletDirection.y + shotDirection.z * pelletDirection.z;
393
+ return dot >= 0.95;
394
+ }
395
+ function validatePellet(bodies, shot, pelletDirection) {
396
+ const world = (0, physics3d_js_1.createDeterministicPhysicsWorld3D)(bodies);
397
+ const hit = (0, physics3d_js_1.raycastDeterministicPhysics3D)(world, {
398
+ x: shot.x,
399
+ y: shot.y,
400
+ z: shot.z,
401
+ dx: pelletDirection.x,
402
+ dy: pelletDirection.y,
403
+ dz: pelletDirection.z,
404
+ maxDistance: shot.maxDistance,
405
+ layerMask: 3,
406
+ })[0];
407
+ if (hit === undefined) {
408
+ return { kind: 'miss' };
409
+ }
410
+ if (hit.bodyId.startsWith('occluder.')) {
411
+ return { kind: 'occluded', targetId: '', hitboxId: hit.bodyId, distance: hit.distance };
412
+ }
413
+ const separator = hit.bodyId.indexOf('.');
414
+ return {
415
+ kind: 'hit',
416
+ targetId: separator === -1 ? hit.bodyId : hit.bodyId.slice(0, separator),
417
+ hitboxId: hit.bodyId,
418
+ distance: hit.distance,
419
+ };
420
+ }
421
+ function isHitPellet(result) {
422
+ return result.kind === 'hit';
423
+ }
424
+ function isOccludedPellet(result) {
425
+ return result.kind === 'occluded';
426
+ }
427
+ function buildTargetHistory(players, frames, seed) {
428
+ const snapshots = [];
429
+ for (let frame = 0; frame < frames; frame += 1) {
430
+ for (let slot = 0; slot < players; slot += 1) {
431
+ snapshots.push({
432
+ frame,
433
+ targetId: `slot-${slot}`,
434
+ x: targetX(slot, frame, seed),
435
+ y: 1,
436
+ z: targetZ(slot, frame, seed),
437
+ radius: 0.45,
438
+ pose: (frame + slot + seed) % 5 === 0 ? 'crouching' : 'standing',
439
+ });
440
+ }
441
+ }
442
+ return snapshots;
443
+ }
444
+ function buildHitboxProxies(history, rewindFrame, shooterSlot) {
445
+ return interpolateLagCompensatedTargets3D(history, rewindFrame)
446
+ .filter((snapshot) => shooterSlot === undefined || snapshot.targetId !== `slot-${shooterSlot}`)
447
+ .flatMap((snapshot) => {
448
+ const headOffset = snapshot.pose === 'crouching' ? 0.85 : 1.35;
449
+ const interpolationMarker = isInterpolatedLagCompensatedTarget3D(snapshot) ? '.interpolated' : '';
450
+ return [
451
+ hitboxProxy(`${snapshot.targetId}${interpolationMarker}.chest`, snapshot.x, snapshot.y + 0.45, snapshot.z, { type: 'capsule', radius: snapshot.radius, halfHeight: 0.55 }),
452
+ hitboxProxy(`${snapshot.targetId}${interpolationMarker}.head`, snapshot.x, snapshot.y + headOffset, snapshot.z, { type: 'sphere', radius: 0.28 }),
453
+ ];
454
+ });
455
+ }
456
+ function interpolateLagCompensatedTargets3D(history, rewindFrame) {
457
+ const exact = history.filter((snapshot) => snapshot.frame === rewindFrame);
458
+ if (exact.length > 0) {
459
+ return [...exact].sort(compareLagCompensatedTarget3D);
460
+ }
461
+ const byTarget = new Map();
462
+ for (const snapshot of history) {
463
+ const entry = byTarget.get(snapshot.targetId) ?? {};
464
+ if (snapshot.frame < rewindFrame && (entry.before === undefined || snapshot.frame > entry.before.frame)) {
465
+ byTarget.set(snapshot.targetId, { ...entry, before: snapshot });
466
+ continue;
467
+ }
468
+ if (snapshot.frame > rewindFrame && (entry.after === undefined || snapshot.frame < entry.after.frame)) {
469
+ byTarget.set(snapshot.targetId, { ...entry, after: snapshot });
470
+ }
471
+ }
472
+ const interpolated = [];
473
+ for (const [targetId, entry] of [...byTarget.entries()].sort(([left], [right]) => compareStrings(left, right))) {
474
+ if (entry.before === undefined || entry.after === undefined || entry.before.frame === entry.after.frame) {
475
+ continue;
476
+ }
477
+ const alpha = (rewindFrame - entry.before.frame) / (entry.after.frame - entry.before.frame);
478
+ interpolated.push({
479
+ frame: rewindFrame,
480
+ targetId,
481
+ x: interpolateNumber(entry.before.x, entry.after.x, alpha),
482
+ y: interpolateNumber(entry.before.y, entry.after.y, alpha),
483
+ z: interpolateNumber(entry.before.z, entry.after.z, alpha),
484
+ radius: interpolateNumber(entry.before.radius, entry.after.radius, alpha),
485
+ pose: alpha < 0.5 ? entry.before.pose : entry.after.pose,
486
+ interpolated: true,
487
+ });
488
+ }
489
+ return interpolated;
490
+ }
491
+ function compareLagCompensatedTarget3D(left, right) {
492
+ return compareStrings(left.targetId, right.targetId);
493
+ }
494
+ function compareLagCompensatedTarget3DByFrame(left, right) {
495
+ return left.frame - right.frame || compareLagCompensatedTarget3D(left, right);
496
+ }
497
+ function normalizeLagCompensatedTargets3D(targets) {
498
+ const normalized = targets.map((target) => {
499
+ assertLagCompensationFrame(target.frame);
500
+ if (typeof target.targetId !== 'string' || target.targetId.length === 0) {
501
+ throw new Error('Deterministic lag compensation target requires a non-empty target id');
502
+ }
503
+ if (!Number.isFinite(target.x) || !Number.isFinite(target.y) || !Number.isFinite(target.z) || !Number.isFinite(target.radius) || target.radius <= 0) {
504
+ throw new Error(`Deterministic lag compensation target ${target.targetId} has invalid position or radius`);
505
+ }
506
+ if (target.pose !== 'standing' && target.pose !== 'crouching') {
507
+ throw new Error(`Deterministic lag compensation target ${target.targetId} has invalid pose`);
508
+ }
509
+ return {
510
+ frame: target.frame,
511
+ targetId: target.targetId,
512
+ x: target.x,
513
+ y: target.y,
514
+ z: target.z,
515
+ radius: target.radius,
516
+ pose: target.pose,
517
+ };
518
+ }).sort(compareLagCompensatedTarget3DByFrame);
519
+ const duplicate = normalized.find((target, index) => index > 0 && lagCompensatedTargetKey(target) === lagCompensatedTargetKey(normalized[index - 1]));
520
+ if (duplicate !== undefined) {
521
+ throw new Error(`Duplicate deterministic lag compensation target ${lagCompensatedTargetKey(duplicate)}`);
522
+ }
523
+ return normalized;
524
+ }
525
+ function normalizePhysicsBodies(bodies) {
526
+ return [...bodies].sort((left, right) => compareStrings(left.id, right.id)).map((body) => ({ ...body }));
527
+ }
528
+ function lagCompensatedTargetKey(target) {
529
+ return `${target.frame}:${target.targetId}`;
530
+ }
531
+ function assertLagCompensationFrame(frame) {
532
+ if (!Number.isInteger(frame) || frame < 0) {
533
+ throw new Error('Deterministic lag compensation frame must be a non-negative integer');
534
+ }
535
+ }
536
+ function targetSnapshot(frame, targetId, x, y, z, pose) {
537
+ return {
538
+ frame,
539
+ targetId,
540
+ x,
541
+ y,
542
+ z,
543
+ radius: 0.45,
544
+ pose,
545
+ };
546
+ }
547
+ function shotAtTarget(id, shooterSlot, shotFrame, latencyFrames, targetX, targetY, targetZ) {
548
+ const direction = requireGeneratedShotDirection(targetX, targetY - 1.45, targetZ);
549
+ return {
550
+ id,
551
+ shooterSlot,
552
+ shotFrame,
553
+ latencyFrames,
554
+ x: 0,
555
+ y: 1.45,
556
+ z: 0,
557
+ dx: direction.x,
558
+ dy: direction.y,
559
+ dz: direction.z,
560
+ maxDistance: 40,
561
+ weapon: 'rifle',
562
+ pellets: [{ dx: direction.x, dy: direction.y, dz: direction.z }],
563
+ maxHistoryFrames: 8,
564
+ };
565
+ }
566
+ function compareStrings(left, right) {
567
+ return left < right ? -1 : left > right ? 1 : 0;
568
+ }
569
+ function captureGeneratedZeroDirectionRejectionReason() {
570
+ let reason = 'missing rejection';
571
+ try {
572
+ shotAtTarget('api-zero-direction', 0, 0, 0, 0, 1.45, 0);
573
+ }
574
+ catch (error) {
575
+ reason = error.message;
576
+ }
577
+ return reason;
578
+ }
579
+ function isInterpolatedLagCompensatedTarget3D(snapshot) {
580
+ return snapshot.interpolated === true;
581
+ }
582
+ function buildStaticOccluders() {
583
+ return [
584
+ {
585
+ id: 'occluder.center-wall',
586
+ kind: 'static',
587
+ x: 0,
588
+ y: 1.2,
589
+ z: 0,
590
+ vx: 0,
591
+ vy: 0,
592
+ vz: 0,
593
+ shape: { type: 'box', halfX: 0.2, halfY: 2, halfZ: 18 },
594
+ layer: 2,
595
+ mask: 3,
596
+ },
597
+ {
598
+ id: 'occluder.side-pillar',
599
+ kind: 'static',
600
+ x: -7,
601
+ y: 1.2,
602
+ z: 4,
603
+ vx: 0,
604
+ vy: 0,
605
+ vz: 0,
606
+ shape: { type: 'box', halfX: 0.8, halfY: 2, halfZ: 0.8 },
607
+ layer: 2,
608
+ mask: 3,
609
+ },
610
+ ];
611
+ }
612
+ function buildShotCorpus(players, frames, seed) {
613
+ const shots = [];
614
+ const start = 8;
615
+ const end = Math.max(start, frames - 3);
616
+ for (let frame = start; frame < end; frame += 3) {
617
+ const shooterSlot = (frame + seed) % players;
618
+ const targetSlot = (shooterSlot + 7 + (frame % 11)) % players;
619
+ const rewindFrame = Math.max(0, frame - latencyForShot(shooterSlot, frame, seed));
620
+ const shooter = shooterPose(shooterSlot, frame, seed);
621
+ const target = {
622
+ x: targetX(targetSlot, rewindFrame, seed),
623
+ y: 1.6,
624
+ z: targetZ(targetSlot, rewindFrame, seed),
625
+ };
626
+ const direction = requireGeneratedShotDirection(target.x - shooter.x, target.y - shooter.y, target.z - shooter.z);
627
+ const weapon = frame % 5 === 0 ? 'shotgun' : frame % 7 === 0 ? 'burst' : 'rifle';
628
+ shots.push({
629
+ id: `shot-${frame}-${shooterSlot}`,
630
+ shooterSlot,
631
+ shotFrame: frame,
632
+ latencyFrames: latencyForShot(shooterSlot, frame, seed),
633
+ x: shooter.x,
634
+ y: shooter.y,
635
+ z: shooter.z,
636
+ dx: direction.x,
637
+ dy: direction.y,
638
+ dz: direction.z,
639
+ maxDistance: 40,
640
+ weapon,
641
+ pellets: pelletsForWeapon(weapon, direction),
642
+ maxHistoryFrames: 12,
643
+ });
644
+ }
645
+ shots.push({
646
+ id: 'shot-miss',
647
+ shooterSlot: 2,
648
+ shotFrame: Math.min(Math.max(start + 2, 12), Math.max(12, frames - 4)),
649
+ latencyFrames: 2,
650
+ x: 32,
651
+ y: 1.6,
652
+ z: 32,
653
+ dx: 1,
654
+ dy: 0,
655
+ dz: 0,
656
+ maxDistance: 12,
657
+ weapon: 'rifle',
658
+ pellets: [{ dx: 1, dy: 0, dz: 0 }],
659
+ maxHistoryFrames: 12,
660
+ });
661
+ shots.push({
662
+ id: 'shot-stale',
663
+ shooterSlot: 0,
664
+ shotFrame: 3,
665
+ latencyFrames: 6,
666
+ x: -18,
667
+ y: 1.6,
668
+ z: -18,
669
+ dx: 1,
670
+ dy: 0,
671
+ dz: 1,
672
+ maxDistance: 40,
673
+ weapon: 'rifle',
674
+ pellets: [{ dx: 1, dy: 0, dz: 1 }],
675
+ maxHistoryFrames: 4,
676
+ });
677
+ shots.push({
678
+ id: 'shot-future',
679
+ shooterSlot: 1,
680
+ shotFrame: frames - 2,
681
+ latencyFrames: -1,
682
+ x: 18,
683
+ y: 1.6,
684
+ z: 18,
685
+ dx: -1,
686
+ dy: 0,
687
+ dz: -1,
688
+ maxDistance: 40,
689
+ weapon: 'rifle',
690
+ pellets: [{ dx: -1, dy: 0, dz: -1 }],
691
+ maxHistoryFrames: 4,
692
+ });
693
+ return shots;
694
+ }
695
+ function buildInterpolationShotCorpus(seed) {
696
+ const shotFrame = 5;
697
+ const rewindFrame = 3;
698
+ const shooterSlot = 0;
699
+ const targetSlot = 1;
700
+ const shooter = shooterPose(shooterSlot, shotFrame, seed);
701
+ const target = {
702
+ x: targetX(targetSlot, rewindFrame, seed),
703
+ y: 1.6,
704
+ z: targetZ(targetSlot, rewindFrame, seed),
705
+ };
706
+ const direction = requireGeneratedShotDirection(target.x - shooter.x, target.y - shooter.y, target.z - shooter.z);
707
+ return [{
708
+ id: 'shot-interpolated-3d',
709
+ shooterSlot,
710
+ shotFrame,
711
+ latencyFrames: shotFrame - rewindFrame,
712
+ x: shooter.x,
713
+ y: shooter.y,
714
+ z: shooter.z,
715
+ dx: direction.x,
716
+ dy: direction.y,
717
+ dz: direction.z,
718
+ maxDistance: 40,
719
+ weapon: 'rifle',
720
+ pellets: [{ dx: direction.x, dy: direction.y, dz: direction.z }],
721
+ maxHistoryFrames: 12,
722
+ }];
723
+ }
724
+ function pelletsForWeapon(weapon, direction) {
725
+ if (weapon === 'shotgun') {
726
+ return [-2, -1, 0, 1, 2].map((offset) => ({ dx: direction.x, dy: direction.y + offset * 0.015, dz: direction.z + offset * 0.03 }));
727
+ }
728
+ if (weapon === 'burst') {
729
+ return [0, 1, 2].map((offset) => ({ dx: direction.x, dy: direction.y, dz: direction.z + offset * 0.01 }));
730
+ }
731
+ return [{ dx: direction.x, dy: direction.y, dz: direction.z }];
732
+ }
733
+ function hitboxProxy(id, x, y, z, shape) {
734
+ return {
735
+ id,
736
+ kind: 'static',
737
+ x,
738
+ y,
739
+ z,
740
+ vx: 0,
741
+ vy: 0,
742
+ vz: 0,
743
+ shape,
744
+ layer: 1,
745
+ mask: 3,
746
+ };
747
+ }
748
+ function shotResult(shot, reason, rewindFrame, hitTargetId, hitboxId, distance, pelletHits, proxiesCreated, interpolatedProxies = 0) {
749
+ const withoutChecksum = {
750
+ id: shot.id,
751
+ reason,
752
+ rewindFrame,
753
+ hitTargetId,
754
+ hitboxId,
755
+ distance,
756
+ pelletHits,
757
+ proxiesCreated,
758
+ proxiesDestroyed: proxiesCreated,
759
+ interpolatedProxies,
760
+ };
761
+ return {
762
+ ...withoutChecksum,
763
+ checksum: (0, canonical_js_1.defaultChecksum)(withoutChecksum),
764
+ };
765
+ }
766
+ function shooterPose(slot, frame, seed) {
767
+ return {
768
+ x: targetX(slot, frame, seed) - 0.4,
769
+ y: 1.6,
770
+ z: targetZ(slot, frame, seed) - 0.4,
771
+ };
772
+ }
773
+ function targetX(slot, frame, seed) {
774
+ return ((slot % 16) - 8) * 2 + (((frame + seed + slot) % 5) - 2) * 0.1;
775
+ }
776
+ function targetZ(slot, frame, seed) {
777
+ return (Math.floor(slot / 16) - 4) * 2 + (((frame * 2 + seed + slot) % 7) - 3) * 0.1;
778
+ }
779
+ function interpolateNumber(before, after, alpha) {
780
+ return before + (after - before) * alpha;
781
+ }
782
+ function latencyForShot(slot, frame, seed) {
783
+ return 1 + ((slot + frame + seed) % 6);
784
+ }
785
+ function normalizeDirection(x, y, z) {
786
+ const scale = 10_000;
787
+ const ix = Math.round(x * scale);
788
+ const iy = Math.round(y * scale);
789
+ const iz = Math.round(z * scale);
790
+ const lengthSquared = (ix * ix) + (iy * iy) + (iz * iz);
791
+ if (!Number.isSafeInteger(lengthSquared) || lengthSquared < 0) {
792
+ return undefined;
793
+ }
794
+ const length = integerSqrt(lengthSquared);
795
+ if (length === 0) {
796
+ return undefined;
797
+ }
798
+ return {
799
+ x: Math.trunc((ix * scale) / length) / scale,
800
+ y: Math.trunc((iy * scale) / length) / scale,
801
+ z: Math.trunc((iz * scale) / length) / scale,
802
+ };
803
+ }
804
+ function requireGeneratedShotDirection(x, y, z) {
805
+ const direction = normalizeDirection(x, y, z);
806
+ if (direction === undefined) {
807
+ throw new Error('Deterministic lag compensation fixture generated a zero-length shot direction');
808
+ }
809
+ return direction;
810
+ }
811
+ function integerSqrt(value) {
812
+ /* istanbul ignore next -- normalizeDirection validates integerSqrt inputs before calling this private guard. */
813
+ if (!Number.isInteger(value) || value < 0) {
814
+ throw new Error('integerSqrt requires a non-negative integer');
815
+ }
816
+ if (value < 2) {
817
+ return value;
818
+ }
819
+ let low = 1;
820
+ let high = Math.min(value, 1_000_000_000);
821
+ let result = 1;
822
+ while (low <= high) {
823
+ const mid = Math.trunc((low + high) / 2);
824
+ const square = mid * mid;
825
+ if (square === value) {
826
+ return mid;
827
+ }
828
+ if (square < value) {
829
+ result = mid;
830
+ low = mid + 1;
831
+ }
832
+ else {
833
+ high = mid - 1;
834
+ }
835
+ }
836
+ return result;
837
+ }