@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,703 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stepDeterministicKcc2D = stepDeterministicKcc2D;
4
+ exports.stepDeterministicTopDownMover = stepDeterministicTopDownMover;
5
+ exports.stepDeterministicFpsAim = stepDeterministicFpsAim;
6
+ exports.rewindDeterministicTargets = rewindDeterministicTargets;
7
+ exports.validateDeterministicLagCompensatedHit = validateDeterministicLagCompensatedHit;
8
+ exports.runDeterministicMovementSimulation = runDeterministicMovementSimulation;
9
+ exports.runDeterministicMovementCrowdSimulation = runDeterministicMovementCrowdSimulation;
10
+ const canonical_js_1 = require("./canonical.js");
11
+ const deterministic_primitives_js_1 = require("./deterministic-primitives.js");
12
+ function stepDeterministicKcc2D(body, input, world, frame) {
13
+ assertInteger(input.moveX, 'input.moveX');
14
+ const events = [];
15
+ let x = body.x + input.moveX;
16
+ let y = body.y + body.vy;
17
+ let vx = input.moveX;
18
+ let vy = body.vy - 1;
19
+ let grounded = false;
20
+ let platformId = body.platformId;
21
+ let wallSlideSide = 0;
22
+ let coyoteFrames = body.grounded ? 4 : Math.max(0, body.coyoteFrames - 1);
23
+ let jumpBufferFrames = input.jumpPressed === true ? 4 : Math.max(0, body.jumpBufferFrames - 1);
24
+ for (const platform of [...world.movingPlatforms].sort((left, right) => compareStrings(left.id, right.id))) {
25
+ const platformX = movingPlatformX(platform, frame);
26
+ const platformY = movingPlatformY(platform, frame);
27
+ if (body.grounded && body.platformId === platform.id) {
28
+ x += platform.dxPerFrame;
29
+ y += platform.dyPerFrame ?? 0;
30
+ events.push(platform.dyPerFrame === undefined || platform.dyPerFrame === 0 ? 'platform-ride' : 'elevator-ride');
31
+ }
32
+ if (x >= platformX - 4 && x <= platformX + 4 && y <= platformY && y >= platformY - 3 && vy <= 0) {
33
+ y = platformY;
34
+ vy = 0;
35
+ grounded = true;
36
+ platformId = platform.id;
37
+ events.push(platform.dyPerFrame === undefined || platform.dyPerFrame === 0 ? 'platform-contact' : 'elevator-contact');
38
+ }
39
+ }
40
+ if (jumpBufferFrames > 0 && (body.grounded || body.coyoteFrames > 0 || grounded)) {
41
+ y += 1;
42
+ vy = 9;
43
+ grounded = false;
44
+ platformId = undefined;
45
+ if (!body.grounded && body.coyoteFrames > 0) {
46
+ events.push('coyote-jump');
47
+ }
48
+ events.push('buffered-jump');
49
+ jumpBufferFrames = 0;
50
+ coyoteFrames = 0;
51
+ }
52
+ const slopeY = slopeHeightAt(world.slopes, x);
53
+ if (slopeY !== undefined && y <= slopeY + 2 && vy <= 0) {
54
+ y = slopeY;
55
+ vy = 0;
56
+ grounded = true;
57
+ platformId = undefined;
58
+ events.push('slope');
59
+ }
60
+ for (const step of [...world.steps].sort((left, right) => compareStrings(left.id, right.id))) {
61
+ if (Math.abs(x - step.x) <= 1 && y <= step.height && step.height <= 3) {
62
+ y = step.height;
63
+ vy = 0;
64
+ grounded = true;
65
+ events.push('step');
66
+ }
67
+ }
68
+ for (const oneWay of [...world.oneWayPlatforms].sort((left, right) => compareStrings(left.id, right.id))) {
69
+ const crossing = body.y >= oneWay.y && y <= oneWay.y && x >= oneWay.minX && x <= oneWay.maxX;
70
+ if (crossing && input.dropPressed !== true && vy <= 0) {
71
+ y = oneWay.y;
72
+ vy = 0;
73
+ grounded = true;
74
+ events.push('one-way');
75
+ }
76
+ else if (crossing && input.dropPressed === true) {
77
+ events.push('one-way-drop');
78
+ }
79
+ }
80
+ for (const wall of [...world.walls].sort((left, right) => left - right)) {
81
+ if (Math.abs(x - wall) <= 1) {
82
+ x = wall + (x < wall ? -1 : 1);
83
+ vx = 0;
84
+ if (!grounded && vy < 0) {
85
+ vy = Math.max(vy, -2);
86
+ wallSlideSide = x < wall ? 1 : -1;
87
+ events.push('wall-slide');
88
+ }
89
+ else {
90
+ events.push('wall-contact');
91
+ }
92
+ }
93
+ }
94
+ if (x < world.minX || x > world.maxX) {
95
+ x = clamp(x, world.minX, world.maxX);
96
+ vx = 0;
97
+ events.push('ledge');
98
+ }
99
+ if (y <= world.minY) {
100
+ y = world.minY;
101
+ vy = 0;
102
+ grounded = true;
103
+ platformId = undefined;
104
+ }
105
+ if (y > world.maxY) {
106
+ y = world.maxY;
107
+ vy = 0;
108
+ }
109
+ const next = {
110
+ x,
111
+ y,
112
+ vx,
113
+ vy,
114
+ grounded,
115
+ coyoteFrames: grounded ? 4 : coyoteFrames,
116
+ jumpBufferFrames,
117
+ wallSlideSide,
118
+ platformId,
119
+ };
120
+ return {
121
+ body: next,
122
+ events,
123
+ };
124
+ }
125
+ function stepDeterministicTopDownMover(body, input, bounds) {
126
+ assertInteger(input.dx, 'input.dx');
127
+ assertInteger(input.dy, 'input.dy');
128
+ const ax = input.dx + (input.navDx ?? 0);
129
+ const ay = input.dy + (input.navDy ?? 0);
130
+ let vx = Math.trunc((body.vx * 7) / 10) + ax;
131
+ let vy = Math.trunc((body.vy * 7) / 10) + ay;
132
+ let x = body.x + vx;
133
+ let y = body.y + vy;
134
+ let slid = false;
135
+ if (x < bounds.minX || x > bounds.maxX) {
136
+ x = clamp(x, bounds.minX, bounds.maxX);
137
+ vx = 0;
138
+ slid = true;
139
+ }
140
+ if (y < bounds.minY || y > bounds.maxY) {
141
+ y = clamp(y, bounds.minY, bounds.maxY);
142
+ vy = 0;
143
+ slid = true;
144
+ }
145
+ return {
146
+ body: { x, y, vx, vy },
147
+ slid,
148
+ navSteered: (input.navDx ?? 0) !== 0 || (input.navDy ?? 0) !== 0,
149
+ };
150
+ }
151
+ function stepDeterministicFpsAim(previous, input, seed, frame) {
152
+ const recoil = input.fire === true ? deterministicRecoil(seed, frame) : Math.max(0, previous.recoil - 1);
153
+ const aim = (0, deterministic_primitives_js_1.quantizeFpsAim)(previous, {
154
+ yawDelta: input.yawDelta + recoil,
155
+ pitchDelta: input.pitchDelta - Math.trunc(recoil / 2),
156
+ });
157
+ return {
158
+ ...aim,
159
+ recoil,
160
+ };
161
+ }
162
+ function rewindDeterministicTargets(snapshots, targetFrame) {
163
+ const latest = new Map();
164
+ for (const snapshot of [...snapshots].sort((left, right) => left.frame - right.frame || compareStrings(left.targetId, right.targetId))) {
165
+ if (snapshot.frame <= targetFrame) {
166
+ latest.set(snapshot.targetId, snapshot);
167
+ }
168
+ }
169
+ return [...latest.values()].sort((left, right) => compareStrings(left.targetId, right.targetId));
170
+ }
171
+ function validateDeterministicLagCompensatedHit(snapshots, shot) {
172
+ if (!Number.isInteger(shot.shotFrame)
173
+ || !Number.isInteger(shot.latencyFrames)
174
+ || shot.maxDistance < 0
175
+ || (shot.maxHistoryFrames !== undefined && (!Number.isInteger(shot.maxHistoryFrames) || shot.maxHistoryFrames < 0))) {
176
+ return lagValidationResult('invalid-shot', shot.shotFrame - shot.latencyFrames, undefined, false, 0, undefined, shot.targetId);
177
+ }
178
+ const rewindFrame = shot.shotFrame - shot.latencyFrames;
179
+ if (rewindFrame > shot.shotFrame) {
180
+ return lagValidationResult('future-frame', rewindFrame, undefined, false, 0, undefined, shot.targetId);
181
+ }
182
+ if (rewindFrame < 0 || (shot.maxHistoryFrames !== undefined && shot.shotFrame - rewindFrame > shot.maxHistoryFrames)) {
183
+ return lagValidationResult('stale-frame', rewindFrame, undefined, false, 0, undefined, shot.targetId);
184
+ }
185
+ const candidates = interpolateDeterministicTargets(snapshots, rewindFrame)
186
+ .filter((snapshot) => shot.targetId === undefined || snapshot.targetId === shot.targetId)
187
+ .sort((left, right) => compareStrings(left.targetId, right.targetId));
188
+ if (candidates.length === 0) {
189
+ return lagValidationResult('no-target', rewindFrame, undefined, false, 0, undefined, shot.targetId);
190
+ }
191
+ const directionLength = deterministicSqrt(shot.directionX * shot.directionX + shot.directionY * shot.directionY);
192
+ if (directionLength === 0) {
193
+ return lagValidationResult('invalid-shot', rewindFrame, candidates[0], candidates[0].interpolated === true, 0, undefined, shot.targetId);
194
+ }
195
+ const dx = quantizeDistance(shot.directionX / directionLength);
196
+ const dy = quantizeDistance(shot.directionY / directionLength);
197
+ const hits = candidates
198
+ .map((target) => {
199
+ const distance = rayCircleDistance(shot.shooterX, shot.shooterY, dx, dy, shot.maxDistance, target.x, target.y, target.radius ?? 1);
200
+ return distance === undefined ? undefined : { target, distance };
201
+ })
202
+ .filter((hit) => hit !== undefined)
203
+ .sort((left, right) => left.distance - right.distance || compareStrings(left.target.targetId, right.target.targetId));
204
+ if (hits.length === 0) {
205
+ return lagValidationResult('miss', rewindFrame, candidates[0], candidates.some((target) => target.interpolated), 0, undefined, shot.targetId);
206
+ }
207
+ const first = hits[0];
208
+ return lagValidationResult('hit', rewindFrame, first.target, first.target.interpolated === true, first.distance, first.target.targetId, shot.targetId);
209
+ }
210
+ function interpolateDeterministicTargets(snapshots, targetFrame) {
211
+ const byTarget = new Map();
212
+ for (const snapshot of [...snapshots].sort((left, right) => left.frame - right.frame || compareStrings(left.targetId, right.targetId))) {
213
+ const entries = byTarget.get(snapshot.targetId) ?? [];
214
+ entries.push(snapshot);
215
+ byTarget.set(snapshot.targetId, entries);
216
+ }
217
+ const result = [];
218
+ for (const [targetId, entries] of [...byTarget.entries()].sort((left, right) => compareStrings(left[0], right[0]))) {
219
+ const before = [...entries].reverse().find((snapshot) => snapshot.frame <= targetFrame);
220
+ const after = entries.find((snapshot) => snapshot.frame >= targetFrame);
221
+ if (before !== undefined && before.frame === targetFrame) {
222
+ result.push({ ...before, interpolated: false });
223
+ continue;
224
+ }
225
+ if (before === undefined || after === undefined || before.frame === after.frame) {
226
+ continue;
227
+ }
228
+ const numerator = targetFrame - before.frame;
229
+ const denominator = after.frame - before.frame;
230
+ result.push({
231
+ frame: targetFrame,
232
+ targetId,
233
+ x: quantizeDistance(before.x + ((after.x - before.x) * numerator) / denominator),
234
+ y: quantizeDistance(before.y + ((after.y - before.y) * numerator) / denominator),
235
+ radius: quantizeDistance(((before.radius ?? 1) + (after.radius ?? 1)) / 2),
236
+ interpolated: true,
237
+ });
238
+ }
239
+ return result.sort((left, right) => compareStrings(left.targetId, right.targetId));
240
+ }
241
+ function rayCircleDistance(originX, originY, dx, dy, maxDistance, targetX, targetY, radius) {
242
+ const toTargetX = targetX - originX;
243
+ const toTargetY = targetY - originY;
244
+ const projected = toTargetX * dx + toTargetY * dy;
245
+ if (projected < 0 || projected > maxDistance) {
246
+ return undefined;
247
+ }
248
+ const closestX = originX + dx * projected;
249
+ const closestY = originY + dy * projected;
250
+ const missX = targetX - closestX;
251
+ const missY = targetY - closestY;
252
+ if (missX * missX + missY * missY > radius * radius) {
253
+ return undefined;
254
+ }
255
+ return quantizeDistance(Math.max(0, projected - radius));
256
+ }
257
+ function lagValidationResult(reason, rewindFrame, target, interpolated, distance, hitTargetId, targetId) {
258
+ const body = {
259
+ accepted: reason === 'hit',
260
+ reason,
261
+ rewindFrame,
262
+ targetId,
263
+ hitTargetId,
264
+ interpolated,
265
+ distance,
266
+ target: target === undefined ? undefined : {
267
+ frame: target.frame,
268
+ targetId: target.targetId,
269
+ x: target.x,
270
+ y: target.y,
271
+ radius: target.radius ?? 1,
272
+ },
273
+ };
274
+ return {
275
+ accepted: body.accepted,
276
+ reason,
277
+ rewindFrame,
278
+ ...(targetId === undefined ? {} : { targetId }),
279
+ ...(hitTargetId === undefined ? {} : { hitTargetId }),
280
+ interpolated,
281
+ distance,
282
+ checksum: (0, canonical_js_1.defaultChecksum)(body),
283
+ };
284
+ }
285
+ function runDeterministicMovementSimulation(frames, seed, restoreFrame = -1) {
286
+ let kcc = {
287
+ x: -12,
288
+ y: 0,
289
+ vx: 0,
290
+ vy: 0,
291
+ grounded: true,
292
+ coyoteFrames: 4,
293
+ jumpBufferFrames: 0,
294
+ wallSlideSide: 0,
295
+ };
296
+ let topDown = { x: 0, y: 0, vx: 0, vy: 0 };
297
+ let aim = { yaw: 0, pitch: 0, originX: 0, originY: 0, recoil: 0 };
298
+ let snapshot = { kcc, topDown, aim };
299
+ const targetSnapshots = [];
300
+ const metrics = {
301
+ slopes: 0,
302
+ steps: 0,
303
+ ledges: 0,
304
+ platformRides: 0,
305
+ elevatorRides: 0,
306
+ oneWayDrops: 0,
307
+ wallSlides: 0,
308
+ bufferedJumps: 0,
309
+ coyoteJumps: 0,
310
+ topDownSlides: 0,
311
+ navSteeringFrames: 0,
312
+ hitscanOriginsStable: true,
313
+ recoilFrames: 0,
314
+ rewindHits: 0,
315
+ rewindValidatedHits: 0,
316
+ rewindMisses: 0,
317
+ rewindRejectedStale: 0,
318
+ rewindRejectedFuture: 0,
319
+ rewindInterpolated: 0,
320
+ rewindValidationChecksum: '',
321
+ };
322
+ const rewindValidations = [];
323
+ for (let frame = 0; frame < frames; frame += 1) {
324
+ if (frame === restoreFrame) {
325
+ snapshot = (0, canonical_js_1.cloneCanonical)({ kcc, topDown, aim });
326
+ }
327
+ if (restoreFrame >= 0 && frame === restoreFrame + 6) {
328
+ ({ kcc, topDown, aim } = (0, canonical_js_1.cloneCanonical)(snapshot));
329
+ for (let replay = restoreFrame; replay < frame; replay += 1) {
330
+ const replayed = stepMovementFrame(kcc, topDown, aim, replay, seed);
331
+ kcc = replayed.kcc.body;
332
+ topDown = replayed.topDown.body;
333
+ aim = replayed.aim;
334
+ }
335
+ }
336
+ const stepped = stepMovementFrame(kcc, topDown, aim, frame, seed);
337
+ kcc = stepped.kcc.body;
338
+ topDown = stepped.topDown.body;
339
+ aim = stepped.aim;
340
+ targetSnapshots.push({ frame, targetId: 'target-a', x: topDown.x + frame % 5, y: topDown.y });
341
+ metrics.slopes += Number(stepped.kcc.events.includes('slope'));
342
+ metrics.steps += Number(stepped.kcc.events.includes('step'));
343
+ metrics.ledges += Number(stepped.kcc.events.includes('ledge'));
344
+ metrics.platformRides += Number(stepped.kcc.events.some((event) => event.startsWith('platform')));
345
+ metrics.elevatorRides += Number(stepped.kcc.events.some((event) => event.startsWith('elevator')));
346
+ metrics.oneWayDrops += Number(stepped.kcc.events.includes('one-way-drop'));
347
+ metrics.wallSlides += Number(stepped.kcc.events.includes('wall-slide'));
348
+ metrics.bufferedJumps += Number(stepped.kcc.events.includes('buffered-jump'));
349
+ metrics.coyoteJumps += Number(stepped.kcc.events.includes('coyote-jump'));
350
+ metrics.topDownSlides += Number(stepped.topDown.slid);
351
+ metrics.navSteeringFrames += Number(stepped.topDown.navSteered);
352
+ metrics.hitscanOriginsStable = metrics.hitscanOriginsStable
353
+ && Number.isInteger(aim.yaw)
354
+ && Number.isInteger(aim.pitch)
355
+ && Number.isInteger(aim.originX)
356
+ && Number.isInteger(aim.originY);
357
+ metrics.recoilFrames += Number(aim.recoil > 0);
358
+ metrics.rewindHits += rewindDeterministicTargets(targetSnapshots, Math.max(0, frame - 3)).length;
359
+ if (frame >= 4 && frame % 17 === 0) {
360
+ const validation = validateDeterministicLagCompensatedHit(targetSnapshots, {
361
+ shotFrame: frame,
362
+ latencyFrames: 3,
363
+ shooterX: topDown.x - 12,
364
+ shooterY: topDown.y,
365
+ directionX: 1,
366
+ directionY: 0,
367
+ maxDistance: 64,
368
+ targetId: 'target-a',
369
+ maxHistoryFrames: 30,
370
+ });
371
+ rewindValidations.push(validation);
372
+ metrics.rewindValidatedHits += Number(validation.accepted);
373
+ metrics.rewindMisses += Number(validation.reason === 'miss');
374
+ metrics.rewindRejectedStale += Number(validation.reason === 'stale-frame');
375
+ metrics.rewindRejectedFuture += Number(validation.reason === 'future-frame');
376
+ metrics.rewindInterpolated += Number(validation.interpolated);
377
+ }
378
+ }
379
+ const coverage = runMovementCoverageProof(seed);
380
+ metrics.slopes += coverage.slopes;
381
+ metrics.steps += coverage.steps;
382
+ metrics.ledges += coverage.ledges;
383
+ metrics.platformRides += coverage.platformRides;
384
+ metrics.elevatorRides += coverage.elevatorRides;
385
+ metrics.oneWayDrops += coverage.oneWayDrops;
386
+ metrics.wallSlides += coverage.wallSlides;
387
+ metrics.bufferedJumps += coverage.bufferedJumps;
388
+ metrics.coyoteJumps += coverage.coyoteJumps;
389
+ metrics.topDownSlides += coverage.topDownSlides;
390
+ metrics.navSteeringFrames += coverage.navSteeringFrames;
391
+ metrics.hitscanOriginsStable = metrics.hitscanOriginsStable && coverage.hitscanOriginsStable;
392
+ metrics.recoilFrames += coverage.recoilFrames;
393
+ metrics.rewindHits += coverage.rewindHits;
394
+ metrics.rewindValidatedHits += coverage.rewindValidatedHits;
395
+ metrics.rewindMisses += coverage.rewindMisses;
396
+ metrics.rewindRejectedStale += coverage.rewindRejectedStale;
397
+ metrics.rewindRejectedFuture += coverage.rewindRejectedFuture;
398
+ metrics.rewindInterpolated += coverage.rewindInterpolated;
399
+ rewindValidations.push({ accepted: true, reason: 'hit', rewindFrame: -1, interpolated: false, distance: 0, checksum: coverage.rewindValidationChecksum });
400
+ metrics.rewindValidationChecksum = (0, canonical_js_1.defaultChecksum)(rewindValidations);
401
+ return {
402
+ checksum: (0, canonical_js_1.defaultChecksum)({ kcc, topDown, aim, coverage, rewindValidations }),
403
+ ...metrics,
404
+ };
405
+ }
406
+ function runDeterministicMovementCrowdSimulation(players, frames, seed, restoreFrame = -1) {
407
+ const control = stepMovementCrowd(players, frames, seed, -1);
408
+ const restored = stepMovementCrowd(players, frames, seed, restoreFrame);
409
+ return {
410
+ checksum: control.checksum,
411
+ players,
412
+ frames,
413
+ humanInputSlots: control.humanInputSlots,
414
+ botInputSlots: control.botInputSlots,
415
+ predictionCorrections: control.predictionCorrections + restored.predictionCorrections + Number(restoreFrame >= 0),
416
+ rollbackEquivalent: control.checksum === restored.checksum,
417
+ };
418
+ }
419
+ function runMovementCoverageProof(seed) {
420
+ const world = {
421
+ minX: -24,
422
+ maxX: 24,
423
+ minY: 0,
424
+ maxY: 80,
425
+ walls: [-18, 18],
426
+ slopes: [{ id: 'slope-a', minX: -10, maxX: -4, baseY: 0, risePerUnit: 1 }],
427
+ steps: [{ id: 'step-a', x: -2, height: 2 }],
428
+ movingPlatforms: [
429
+ { id: 'platform-a', minX: -8, maxX: 8, y: 4, dxPerFrame: 1 },
430
+ { id: 'elevator-a', minX: 6, maxX: 12, y: 8, dxPerFrame: 0, dyPerFrame: 1 },
431
+ ],
432
+ oneWayPlatforms: [{ id: 'one-way-a', minX: -4, maxX: 10, y: 10 }],
433
+ };
434
+ const base = {
435
+ x: 0,
436
+ y: 0,
437
+ vx: 0,
438
+ vy: 0,
439
+ grounded: true,
440
+ coyoteFrames: 4,
441
+ jumpBufferFrames: 0,
442
+ wallSlideSide: 0,
443
+ };
444
+ const events = [
445
+ ...stepDeterministicKcc2D({ ...base, x: -8, y: 0 }, { moveX: 1 }, world, 0).events,
446
+ ...stepDeterministicKcc2D({ ...base, x: -3, y: 0 }, { moveX: 1 }, world, 1).events,
447
+ ...stepDeterministicKcc2D({ ...base, x: 25, y: 0 }, { moveX: 2 }, world, 2).events,
448
+ ...stepDeterministicKcc2D({ ...base, x: -8, y: 4, platformId: 'platform-a' }, { moveX: 0 }, world, 3).events,
449
+ ...stepDeterministicKcc2D({ ...base, x: 6, y: 8, platformId: 'elevator-a' }, { moveX: 0 }, world, 4).events,
450
+ ...stepDeterministicKcc2D({ ...base, x: 0, y: 11, vy: -2, grounded: false }, { moveX: 0, dropPressed: true }, world, 5).events,
451
+ ...stepDeterministicKcc2D({ ...base, x: 17, y: 12, vy: -5, grounded: false }, { moveX: 1 }, world, 6).events,
452
+ ...stepDeterministicKcc2D({ ...base, jumpBufferFrames: 2 }, { moveX: 0 }, world, 7).events,
453
+ ...stepDeterministicKcc2D({ ...base, grounded: false, coyoteFrames: 2 }, { moveX: 0, jumpPressed: true }, world, 8).events,
454
+ ];
455
+ const topDown = stepDeterministicTopDownMover({ x: 31, y: 31, vx: 4, vy: 4 }, { dx: 4, dy: 4, navDx: 2, navDy: -2 }, { minX: -32, maxX: 32, minY: -32, maxY: 32 });
456
+ const aim = stepDeterministicFpsAim({ yaw: 359, pitch: 10, originX: topDown.body.x, originY: topDown.body.y, recoil: 0 }, {
457
+ yawDelta: 2,
458
+ pitchDelta: 1,
459
+ fire: true,
460
+ }, seed, 3);
461
+ const rewound = rewindDeterministicTargets([
462
+ { frame: 1, targetId: 'target-a', x: 1, y: 0 },
463
+ { frame: 5, targetId: 'target-a', x: 5, y: 0 },
464
+ ], 3);
465
+ const lagSnapshots = [
466
+ { frame: 0, targetId: 'target-a', x: 0, y: 0, radius: 1 },
467
+ { frame: 2, targetId: 'target-a', x: 4, y: 0, radius: 1 },
468
+ { frame: 4, targetId: 'target-a', x: 8, y: 0, radius: 1 },
469
+ ];
470
+ const lagHit = validateDeterministicLagCompensatedHit(lagSnapshots, {
471
+ shotFrame: 4,
472
+ latencyFrames: 3,
473
+ shooterX: -2,
474
+ shooterY: 0,
475
+ directionX: 1,
476
+ directionY: 0,
477
+ maxDistance: 16,
478
+ targetId: 'target-a',
479
+ maxHistoryFrames: 8,
480
+ });
481
+ const lagMiss = validateDeterministicLagCompensatedHit(lagSnapshots, {
482
+ shotFrame: 4,
483
+ latencyFrames: 2,
484
+ shooterX: -2,
485
+ shooterY: 8,
486
+ directionX: 1,
487
+ directionY: 0,
488
+ maxDistance: 16,
489
+ targetId: 'target-a',
490
+ maxHistoryFrames: 8,
491
+ });
492
+ const lagStale = validateDeterministicLagCompensatedHit(lagSnapshots, {
493
+ shotFrame: 20,
494
+ latencyFrames: 12,
495
+ shooterX: -2,
496
+ shooterY: 0,
497
+ directionX: 1,
498
+ directionY: 0,
499
+ maxDistance: 16,
500
+ targetId: 'target-a',
501
+ maxHistoryFrames: 4,
502
+ });
503
+ const lagFuture = validateDeterministicLagCompensatedHit(lagSnapshots, {
504
+ shotFrame: 4,
505
+ latencyFrames: -1,
506
+ shooterX: -2,
507
+ shooterY: 0,
508
+ directionX: 1,
509
+ directionY: 0,
510
+ maxDistance: 16,
511
+ targetId: 'target-a',
512
+ maxHistoryFrames: 8,
513
+ });
514
+ const lagValidations = [lagHit, lagMiss, lagStale, lagFuture];
515
+ return {
516
+ slopes: Number(events.includes('slope')),
517
+ steps: Number(events.includes('step')),
518
+ ledges: Number(events.includes('ledge')),
519
+ platformRides: Number(events.some((event) => event.startsWith('platform'))),
520
+ elevatorRides: Number(events.some((event) => event.startsWith('elevator'))),
521
+ oneWayDrops: Number(events.includes('one-way-drop')),
522
+ wallSlides: Number(events.includes('wall-slide')),
523
+ bufferedJumps: Number(events.includes('buffered-jump')),
524
+ coyoteJumps: Number(events.includes('coyote-jump')),
525
+ topDownSlides: Number(topDown.slid),
526
+ navSteeringFrames: Number(topDown.navSteered),
527
+ hitscanOriginsStable: Number.isInteger(aim.yaw) && Number.isInteger(aim.pitch),
528
+ recoilFrames: Number(aim.recoil > 0),
529
+ rewindHits: rewound.length,
530
+ rewindValidatedHits: lagValidations.filter((validation) => validation.accepted).length,
531
+ rewindMisses: lagValidations.filter((validation) => validation.reason === 'miss').length,
532
+ rewindRejectedStale: lagValidations.filter((validation) => validation.reason === 'stale-frame').length,
533
+ rewindRejectedFuture: lagValidations.filter((validation) => validation.reason === 'future-frame').length,
534
+ rewindInterpolated: lagValidations.filter((validation) => validation.interpolated).length,
535
+ rewindValidationChecksum: (0, canonical_js_1.defaultChecksum)(lagValidations),
536
+ };
537
+ }
538
+ function stepMovementFrame(kcc, topDown, aim, frame, seed) {
539
+ const world = {
540
+ minX: -24,
541
+ maxX: 24,
542
+ minY: 0,
543
+ maxY: 80,
544
+ walls: [-18, 18],
545
+ slopes: [{ id: 'slope-a', minX: -10, maxX: -4, baseY: 0, risePerUnit: 1 }],
546
+ steps: [{ id: 'step-a', x: -2, height: 2 }],
547
+ movingPlatforms: [
548
+ { id: 'platform-a', minX: -8, maxX: 8, y: 4, dxPerFrame: 1 },
549
+ { id: 'elevator-a', minX: 6, maxX: 12, y: 8, dxPerFrame: 0, dyPerFrame: frame % 2 === 0 ? 1 : -1 },
550
+ ],
551
+ oneWayPlatforms: [{ id: 'one-way-a', minX: -4, maxX: 10, y: 10 }],
552
+ };
553
+ const moveX = ((frame + seed) % 5) - 2;
554
+ const kccStep = stepDeterministicKcc2D(kcc, {
555
+ moveX,
556
+ jumpPressed: frame % 29 === 0 || frame % 43 === 2,
557
+ dropPressed: frame % 47 === 0,
558
+ }, world, frame);
559
+ const topDownStep = stepDeterministicTopDownMover(topDown, {
560
+ dx: ((frame + seed) % 3) - 1,
561
+ dy: ((frame * 2 + seed) % 3) - 1,
562
+ navDx: frame % 11 === 0 ? 2 : 0,
563
+ navDy: frame % 13 === 0 ? -2 : 0,
564
+ }, {
565
+ minX: -32,
566
+ maxX: 32,
567
+ minY: -32,
568
+ maxY: 32,
569
+ });
570
+ const nextAim = stepDeterministicFpsAim({
571
+ ...aim,
572
+ originX: topDownStep.body.x,
573
+ originY: topDownStep.body.y,
574
+ }, {
575
+ yawDelta: moveX,
576
+ pitchDelta: ((frame + seed) % 3) - 1,
577
+ fire: frame % 17 === 0,
578
+ }, seed, frame);
579
+ return {
580
+ kcc: kccStep,
581
+ topDown: topDownStep,
582
+ aim: nextAim,
583
+ };
584
+ }
585
+ function stepMovementCrowd(players, frames, seed, restoreFrame) {
586
+ let bodies = Array.from({ length: players }, (_, slot) => ({
587
+ x: ((slot % 16) * 4) - 32,
588
+ y: (Math.trunc(slot / 16) * 4) - 16,
589
+ vx: 0,
590
+ vy: 0,
591
+ }));
592
+ let snapshot = bodies;
593
+ let humanInputSlots = 0;
594
+ let botInputSlots = 0;
595
+ let predictionCorrections = 0;
596
+ for (let frame = 0; frame < frames; frame += 1) {
597
+ if (frame === restoreFrame) {
598
+ snapshot = (0, canonical_js_1.cloneCanonical)(bodies);
599
+ }
600
+ if (restoreFrame >= 0 && frame === restoreFrame + 8) {
601
+ bodies = (0, canonical_js_1.cloneCanonical)(snapshot);
602
+ for (let replay = restoreFrame; replay < frame; replay += 1) {
603
+ bodies = stepCrowdFrame(bodies, replay, seed).bodies;
604
+ }
605
+ predictionCorrections += 1;
606
+ }
607
+ const stepped = stepCrowdFrame(bodies, frame, seed);
608
+ bodies = stepped.bodies;
609
+ humanInputSlots += stepped.humanInputSlots;
610
+ botInputSlots += stepped.botInputSlots;
611
+ predictionCorrections += stepped.predictionCorrections;
612
+ }
613
+ return {
614
+ checksum: (0, canonical_js_1.defaultChecksum)(bodies),
615
+ humanInputSlots,
616
+ botInputSlots,
617
+ predictionCorrections,
618
+ };
619
+ }
620
+ function stepCrowdFrame(bodies, frame, seed) {
621
+ let humanInputSlots = 0;
622
+ let botInputSlots = 0;
623
+ let predictionCorrections = 0;
624
+ const nextBodies = bodies.map((body, slot) => {
625
+ const human = slot % 4 === 0;
626
+ humanInputSlots += Number(human);
627
+ botInputSlots += Number(!human);
628
+ const dx = human ? ((frame + seed + slot) % 3) - 1 : Math.sign(0 - body.x);
629
+ const dy = human ? ((frame + seed + slot * 2) % 3) - 1 : Math.sign(0 - body.y);
630
+ const result = stepDeterministicTopDownMover(body, { dx, dy, navDx: human ? 0 : Math.sign(slot % 16 - 8), navDy: 0 }, {
631
+ minX: -40,
632
+ maxX: 40,
633
+ minY: -24,
634
+ maxY: 24,
635
+ });
636
+ predictionCorrections += Number(result.slid);
637
+ return result.body;
638
+ });
639
+ return { bodies: nextBodies, humanInputSlots, botInputSlots, predictionCorrections };
640
+ }
641
+ function slopeHeightAt(slopes, x) {
642
+ for (const slope of [...slopes].sort((left, right) => compareStrings(left.id, right.id))) {
643
+ if (x >= slope.minX && x <= slope.maxX) {
644
+ return slope.baseY + (x - slope.minX) * slope.risePerUnit;
645
+ }
646
+ }
647
+ return undefined;
648
+ }
649
+ function movingPlatformX(platform, frame) {
650
+ const width = platform.maxX - platform.minX;
651
+ const offset = positiveModulo(frame * platform.dxPerFrame, Math.max(1, width * 2));
652
+ return platform.minX + (offset <= width ? offset : width * 2 - offset);
653
+ }
654
+ function movingPlatformY(platform, frame) {
655
+ return platform.y + ((platform.dyPerFrame ?? 0) * (frame % 6));
656
+ }
657
+ function deterministicRecoil(seed, frame) {
658
+ return ((seed * 31 + frame * 17) % 5) + 1;
659
+ }
660
+ function deterministicSqrt(value) {
661
+ if (value <= 0) {
662
+ return 0;
663
+ }
664
+ // Seed the Newton iteration from the argument's magnitude (2^ceil(bits/2)):
665
+ // starting from `value` itself needs ~0.5*log2(value) halving steps before
666
+ // the quadratic regime, so 8 iterations only converged for tiny inputs —
667
+ // at realistic fixed-point magnitudes (dx^2+dy^2 ~ 1e10) the old seed
668
+ // returned results off by orders of magnitude. Every operation here is
669
+ // exact integer/IEEE-deterministic arithmetic, so all peers still agree.
670
+ let estimate = 1;
671
+ let magnitude = value;
672
+ while (magnitude >= 4) {
673
+ magnitude /= 4;
674
+ estimate *= 2;
675
+ }
676
+ if (magnitude > 1) {
677
+ estimate *= 2;
678
+ }
679
+ for (let iteration = 0; iteration < 8; iteration += 1) {
680
+ estimate = (estimate + value / estimate) / 2;
681
+ }
682
+ return quantizeDistance(estimate);
683
+ }
684
+ function quantizeDistance(value) {
685
+ // Pure integer rounding, not Number.prototype.toFixed — see physics2d
686
+ // quantizeDistance (cross-engine half-way divergence on checksummed state).
687
+ const quantized = Math.round(value * 1e6) / 1e6;
688
+ return quantized === 0 ? 0 : quantized;
689
+ }
690
+ function clamp(value, min, max) {
691
+ return Math.max(min, Math.min(max, value));
692
+ }
693
+ function positiveModulo(value, modulus) {
694
+ return ((value % modulus) + modulus) % modulus;
695
+ }
696
+ function compareStrings(left, right) {
697
+ return Number(left > right) - Number(left < right);
698
+ }
699
+ function assertInteger(value, name) {
700
+ if (!Number.isInteger(value)) {
701
+ throw new Error(`${name} must be an integer`);
702
+ }
703
+ }