@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,751 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDeterministicPhysicsWorld2D = createDeterministicPhysicsWorld2D;
4
+ exports.stepDeterministicPhysicsWorld2D = stepDeterministicPhysicsWorld2D;
5
+ exports.queryDeterministicPhysicsAabb2D = queryDeterministicPhysicsAabb2D;
6
+ exports.raycastDeterministicPhysics2D = raycastDeterministicPhysics2D;
7
+ exports.raycastAllDeterministicPhysics2D = raycastAllDeterministicPhysics2D;
8
+ exports.nearestDeterministicPhysicsHit2D = nearestDeterministicPhysicsHit2D;
9
+ exports.queryDeterministicPhysicsShapeOverlap2D = queryDeterministicPhysicsShapeOverlap2D;
10
+ exports.circleCastDeterministicPhysics2D = circleCastDeterministicPhysics2D;
11
+ const canonical_js_1 = require("./canonical.js");
12
+ const deterministic_primitives_js_1 = require("./deterministic-primitives.js");
13
+ const defaultPhysicsMaterial = {
14
+ friction: 0,
15
+ restitution: 0,
16
+ };
17
+ const rayDistanceEpsilon = 0.000001;
18
+ function createDeterministicPhysicsWorld2D(bodies) {
19
+ return {
20
+ frame: 0,
21
+ bodies: normalizeBodies(bodies),
22
+ activePairs: [],
23
+ activePairMetadata: [],
24
+ };
25
+ }
26
+ function stepDeterministicPhysicsWorld2D(world, options = {}) {
27
+ const gravityX = options.gravityX ?? 0;
28
+ const gravityY = options.gravityY ?? 0;
29
+ let bodies = world.bodies.map((body) => integrateBody(body, gravityX, gravityY));
30
+ const iterations = options.iterations ?? 2;
31
+ for (let iteration = 0; iteration < iterations; iteration += 1) {
32
+ bodies = solveConstraints(bodies, options.constraints ?? []);
33
+ }
34
+ const contacts = detectContacts(bodies);
35
+ for (let iteration = 0; iteration < iterations; iteration += 1) {
36
+ bodies = solveContacts(bodies);
37
+ }
38
+ const activePairs = contacts.map((contact) => contact.pair).sort(compareStrings);
39
+ const activePairMetadata = contacts.map((contact) => ({
40
+ pair: contact.pair,
41
+ a: contact.a,
42
+ b: contact.b,
43
+ sensor: contact.sensor,
44
+ trigger: contact.trigger,
45
+ material: contact.material,
46
+ })).sort(compareActivePairMetadata);
47
+ const previousPairMetadata = new Map((world.activePairMetadata ?? []).map((pair) => [pair.pair, pair]));
48
+ const previousPairs = new Set(world.activePairs);
49
+ const currentPairs = new Set(activePairs);
50
+ const events = [
51
+ ...contacts.map((contact) => ({
52
+ ...contact,
53
+ type: previousPairs.has(contact.pair) ? 'stay' : 'enter',
54
+ })),
55
+ ...world.activePairs
56
+ .filter((pair) => !currentPairs.has(pair))
57
+ .sort(compareStrings)
58
+ .map((pair) => {
59
+ const previous = previousPairMetadata.get(pair);
60
+ const [a, b] = pair.split('|');
61
+ return {
62
+ type: 'exit',
63
+ pair,
64
+ a: previous?.a ?? a,
65
+ b: previous?.b ?? b,
66
+ sensor: previous?.sensor ?? false,
67
+ trigger: previous?.trigger ?? false,
68
+ material: previous?.material ?? defaultPhysicsMaterial,
69
+ };
70
+ }),
71
+ ].sort(compareEvents);
72
+ const nextWorld = {
73
+ frame: world.frame + 1,
74
+ bodies: normalizeBodies(bodies),
75
+ activePairs,
76
+ activePairMetadata,
77
+ };
78
+ return {
79
+ world: (0, canonical_js_1.cloneCanonical)(nextWorld),
80
+ events,
81
+ broadphasePairs: countCandidatePairs(nextWorld.bodies),
82
+ narrowphaseContacts: contacts.length,
83
+ solverIterations: iterations,
84
+ checksum: (0, canonical_js_1.defaultChecksum)(nextWorld),
85
+ };
86
+ }
87
+ function queryDeterministicPhysicsAabb2D(world, query) {
88
+ return world.bodies
89
+ .filter((body) => queryBodyAllowed(body, query))
90
+ .filter((body) => bodyOverlapsBounds(body, query.x, query.y, query.halfWidth, query.halfHeight))
91
+ .map((body) => body.id)
92
+ .sort(compareStrings);
93
+ }
94
+ function raycastDeterministicPhysics2D(world, query) {
95
+ const normalizedQuery = normalizeRaycastQuery2D(query);
96
+ return world.bodies
97
+ .filter((body) => queryBodyAllowed(body, normalizedQuery))
98
+ .map((body) => {
99
+ const distance = rayBodyDistance(body, normalizedQuery);
100
+ return distance === undefined ? undefined : { bodyId: body.id, distance };
101
+ })
102
+ .filter((hit) => hit !== undefined)
103
+ .sort((left, right) => left.distance - right.distance || compareStrings(left.bodyId, right.bodyId));
104
+ }
105
+ function raycastAllDeterministicPhysics2D(world, query) {
106
+ return raycastDeterministicPhysics2D(world, query);
107
+ }
108
+ function nearestDeterministicPhysicsHit2D(world, query) {
109
+ return raycastDeterministicPhysics2D(world, query)[0];
110
+ }
111
+ function queryDeterministicPhysicsShapeOverlap2D(world, query) {
112
+ return world.bodies
113
+ .filter((body) => queryBodyAllowed(body, query))
114
+ .filter((body) => bodyOverlapsShape(body, query))
115
+ .map((body) => body.id)
116
+ .sort(compareStrings);
117
+ }
118
+ function circleCastDeterministicPhysics2D(world, query) {
119
+ const normalizedQuery = normalizeCircleCastQuery2D(query);
120
+ return world.bodies
121
+ .filter((body) => queryBodyAllowed(body, normalizedQuery))
122
+ .map((body) => {
123
+ const distance = circleCastBodyDistance(body, normalizedQuery);
124
+ return distance === undefined ? undefined : { bodyId: body.id, distance };
125
+ })
126
+ .filter((hit) => hit !== undefined)
127
+ .sort((left, right) => left.distance - right.distance || compareStrings(left.bodyId, right.bodyId));
128
+ }
129
+ function normalizeRaycastQuery2D(query) {
130
+ const length = deterministicSqrt(query.dx * query.dx + query.dy * query.dy);
131
+ if (length === 0) {
132
+ return query;
133
+ }
134
+ return {
135
+ ...query,
136
+ dx: quantizeDistance(query.dx / length),
137
+ dy: quantizeDistance(query.dy / length),
138
+ };
139
+ }
140
+ function normalizeCircleCastQuery2D(query) {
141
+ const length = deterministicSqrt(query.dx * query.dx + query.dy * query.dy);
142
+ if (length === 0) {
143
+ return query;
144
+ }
145
+ return {
146
+ ...query,
147
+ dx: quantizeDistance(query.dx / length),
148
+ dy: quantizeDistance(query.dy / length),
149
+ };
150
+ }
151
+ function integrateBody(body, gravityX, gravityY) {
152
+ assertInteger(body.x, `${body.id}.x`);
153
+ assertInteger(body.y, `${body.id}.y`);
154
+ if (body.kind === 'static') {
155
+ return body;
156
+ }
157
+ const damping = body.damping ?? 0;
158
+ assertInteger(damping, `${body.id}.damping`);
159
+ const vx = Math.trunc(((body.vx + gravityX) * (1_000 - damping)) / 1_000);
160
+ const vy = Math.trunc(((body.vy + gravityY) * (1_000 - damping)) / 1_000);
161
+ return {
162
+ ...body,
163
+ x: body.x + vx,
164
+ y: body.y + vy,
165
+ vx,
166
+ vy,
167
+ };
168
+ }
169
+ function solveConstraints(bodies, constraints) {
170
+ let result = bodies;
171
+ for (const constraint of [...constraints].sort((left, right) => compareStrings(left.id, right.id))) {
172
+ result = applyConstraint(result, constraint);
173
+ }
174
+ return result;
175
+ }
176
+ function applyConstraint(bodies, constraint) {
177
+ const map = new Map(bodies.map((body) => [body.id, body]));
178
+ const a = map.get(constraint.a);
179
+ const b = constraint.b === undefined ? undefined : map.get(constraint.b);
180
+ if (a === undefined) {
181
+ return bodies;
182
+ }
183
+ if (constraint.type === 'motor') {
184
+ map.set(a.id, {
185
+ ...a,
186
+ vx: constraint.motorVx ?? a.vx,
187
+ vy: constraint.motorVy ?? a.vy,
188
+ });
189
+ return [...map.values()].sort(compareBodies);
190
+ }
191
+ if (b === undefined || b.kind === 'static') {
192
+ return bodies;
193
+ }
194
+ if (constraint.type === 'fixed') {
195
+ map.set(b.id, {
196
+ ...b,
197
+ x: a.x + (constraint.offsetX ?? 0),
198
+ y: a.y + (constraint.offsetY ?? 0),
199
+ vx: a.vx,
200
+ vy: a.vy,
201
+ });
202
+ return [...map.values()].sort(compareBodies);
203
+ }
204
+ if (constraint.type === 'distance') {
205
+ const restLength = constraint.restLength ?? 0;
206
+ const dx = b.x - a.x;
207
+ const dy = b.y - a.y;
208
+ const length = deterministicSqrt(dx * dx + dy * dy);
209
+ if (length > restLength && length > 0) {
210
+ const scale = restLength / length;
211
+ // Positions are integers (integrateBody asserts it every step) — trunc
212
+ // like the spring branch below, or any pair not exactly restLength apart
213
+ // writes a fractional coordinate and halts the sim next frame.
214
+ map.set(b.id, {
215
+ ...b,
216
+ x: Math.trunc(a.x + dx * scale),
217
+ y: Math.trunc(a.y + dy * scale),
218
+ vx: 0,
219
+ vy: 0,
220
+ });
221
+ }
222
+ return [...map.values()].sort(compareBodies);
223
+ }
224
+ const stiffness = constraint.stiffness ?? 500;
225
+ const restLength = constraint.restLength ?? 0;
226
+ const dx = b.x - a.x;
227
+ const dy = b.y - a.y;
228
+ const length = deterministicSqrt(dx * dx + dy * dy);
229
+ const scale = length === 0 ? 0 : restLength / length;
230
+ const targetX = a.x + dx * scale;
231
+ const targetY = a.y + dy * scale;
232
+ map.set(b.id, {
233
+ ...b,
234
+ x: quantizeDistance(b.x + Math.trunc(((targetX - b.x) * stiffness) / 1_000)),
235
+ y: quantizeDistance(b.y + Math.trunc(((targetY - b.y) * stiffness) / 1_000)),
236
+ });
237
+ return [...map.values()].sort(compareBodies);
238
+ }
239
+ function solveContacts(bodies) {
240
+ const map = new Map(bodies.map((body) => [body.id, body]));
241
+ for (const contact of detectContacts(bodies)) {
242
+ const a = map.get(contact.a);
243
+ const b = map.get(contact.b);
244
+ if (a === undefined || b === undefined || contact.sensor || contact.trigger) {
245
+ continue;
246
+ }
247
+ if (a.kind === 'dynamic' && b.kind === 'static') {
248
+ map.set(a.id, separateFromStatic(a, b));
249
+ }
250
+ else if (b.kind === 'dynamic' && a.kind === 'static') {
251
+ map.set(b.id, separateFromStatic(b, a));
252
+ }
253
+ else if (a.kind === 'dynamic' && b.kind === 'dynamic') {
254
+ const separated = separateDynamicPair(a, b);
255
+ map.set(a.id, separated.a);
256
+ map.set(b.id, separated.b);
257
+ }
258
+ }
259
+ return [...map.values()].sort(compareBodies);
260
+ }
261
+ function separateDynamicPair(a, b) {
262
+ const left = bodyBounds(a);
263
+ const right = bodyBounds(b);
264
+ const overlapX = Math.min(left.maxX, right.maxX) - Math.max(left.minX, right.minX);
265
+ const overlapY = Math.min(left.maxY, right.maxY) - Math.max(left.minY, right.minY);
266
+ if (overlapX <= 0 || overlapY <= 0) {
267
+ return { a, b };
268
+ }
269
+ const material = (0, deterministic_primitives_js_1.combineDeterministicPhysicsMaterial)(a.material ?? defaultPhysicsMaterial, b.material ?? defaultPhysicsMaterial);
270
+ if (overlapX <= overlapY) {
271
+ const direction = a.x <= b.x ? -1 : 1;
272
+ const correctionA = Math.ceil(overlapX / 2);
273
+ const correctionB = Math.ceil(overlapX / 2);
274
+ return {
275
+ a: { ...a, x: a.x + direction * correctionA, vx: normalizeZero(Math.trunc((-a.vx * material.restitution) / 10)) },
276
+ b: { ...b, x: b.x - direction * correctionB, vx: normalizeZero(Math.trunc((-b.vx * material.restitution) / 10)) },
277
+ };
278
+ }
279
+ const direction = a.y <= b.y ? -1 : 1;
280
+ const correctionA = Math.ceil(overlapY / 2);
281
+ const correctionB = Math.ceil(overlapY / 2);
282
+ return {
283
+ a: { ...a, y: a.y + direction * correctionA, vy: normalizeZero(Math.trunc((-a.vy * material.restitution) / 10)) },
284
+ b: { ...b, y: b.y - direction * correctionB, vy: normalizeZero(Math.trunc((-b.vy * material.restitution) / 10)) },
285
+ };
286
+ }
287
+ function separateFromStatic(dynamicBody, staticBody) {
288
+ const dynamicBounds = bodyBounds(dynamicBody);
289
+ const staticBounds = bodyBounds(staticBody);
290
+ const overlapX = Math.min(dynamicBounds.maxX, staticBounds.maxX) - Math.max(dynamicBounds.minX, staticBounds.minX);
291
+ const overlapY = Math.min(dynamicBounds.maxY, staticBounds.maxY) - Math.max(dynamicBounds.minY, staticBounds.minY);
292
+ if (overlapX <= 0 || overlapY <= 0) {
293
+ return dynamicBody;
294
+ }
295
+ const material = (0, deterministic_primitives_js_1.combineDeterministicPhysicsMaterial)(dynamicBody.material ?? defaultPhysicsMaterial, staticBody.material ?? defaultPhysicsMaterial);
296
+ if (overlapX <= overlapY) {
297
+ const direction = dynamicBody.x < staticBody.x ? -1 : 1;
298
+ return {
299
+ ...dynamicBody,
300
+ x: dynamicBody.x + direction * overlapX,
301
+ vx: normalizeZero(Math.trunc((-dynamicBody.vx * material.restitution) / 10)),
302
+ };
303
+ }
304
+ const direction = dynamicBody.y < staticBody.y ? -1 : 1;
305
+ return {
306
+ ...dynamicBody,
307
+ y: dynamicBody.y + direction * overlapY,
308
+ vy: normalizeZero(Math.trunc((-dynamicBody.vy * material.restitution) / 10)),
309
+ };
310
+ }
311
+ function detectContacts(bodies) {
312
+ const contacts = [];
313
+ const sorted = [...bodies].sort(compareBodies);
314
+ for (let leftIndex = 0; leftIndex < sorted.length; leftIndex += 1) {
315
+ for (let rightIndex = leftIndex + 1; rightIndex < sorted.length; rightIndex += 1) {
316
+ const left = sorted[leftIndex];
317
+ const right = sorted[rightIndex];
318
+ if (!canCollide(left, right) || !bodiesOverlap(left, right)) {
319
+ continue;
320
+ }
321
+ contacts.push({
322
+ pair: `${left.id}|${right.id}`,
323
+ a: left.id,
324
+ b: right.id,
325
+ sensor: left.sensor === true || right.sensor === true,
326
+ trigger: left.trigger === true || right.trigger === true,
327
+ material: (0, deterministic_primitives_js_1.combineDeterministicPhysicsMaterial)(left.material ?? defaultPhysicsMaterial, right.material ?? defaultPhysicsMaterial),
328
+ });
329
+ }
330
+ }
331
+ return contacts.sort((left, right) => compareStrings(left.pair, right.pair));
332
+ }
333
+ function countCandidatePairs(bodies) {
334
+ return (bodies.length * (bodies.length - 1)) / 2;
335
+ }
336
+ function canCollide(left, right) {
337
+ return (left.mask & right.layer) !== 0 && (right.mask & left.layer) !== 0;
338
+ }
339
+ function queryBodyAllowed(body, query) {
340
+ if (query.layerMask !== undefined && (body.layer & query.layerMask) === 0) {
341
+ return false;
342
+ }
343
+ if (!queryKindAllowsBody2D(body, query)) {
344
+ return false;
345
+ }
346
+ const hitSensors = query.hitSensors ?? query.includeSensors ?? false;
347
+ const hitTriggers = query.hitTriggers ?? query.includeTriggers ?? false;
348
+ const hitSolids = query.hitSolids ?? true;
349
+ if (body.sensor === true && hitSensors !== true) {
350
+ return false;
351
+ }
352
+ if (body.trigger === true && hitTriggers !== true) {
353
+ return false;
354
+ }
355
+ return body.sensor === true || body.trigger === true || hitSolids;
356
+ }
357
+ function queryKindAllowsBody2D(body, query) {
358
+ const kindFilterSpecified = query.hitStatics !== undefined || query.hitDynamics !== undefined || query.hitKinematics !== undefined;
359
+ if (!kindFilterSpecified) {
360
+ return true;
361
+ }
362
+ if (body.kind === 'static') {
363
+ return query.hitStatics === true;
364
+ }
365
+ if (body.kind === 'dynamic') {
366
+ return query.hitDynamics === true;
367
+ }
368
+ return query.hitKinematics === true;
369
+ }
370
+ function bodyOverlapsShape(body, query) {
371
+ if ((query.z !== undefined || query.halfDepth !== undefined)
372
+ && !heightOverlaps(body.z ?? 0, body.halfDepth ?? 0, query.z ?? 0, query.halfDepth ?? 0)) {
373
+ return false;
374
+ }
375
+ if (query.shape === 'circle') {
376
+ return circleOverlapsBody(query.x, query.y, query.radius ?? 0, body);
377
+ }
378
+ return bodyOverlapsBounds(body, query.x, query.y, query.halfWidth ?? 0, query.halfHeight ?? 0);
379
+ }
380
+ function heightOverlaps(centerA, halfA, centerB, halfB) {
381
+ return Math.abs(centerA - centerB) <= halfA + halfB;
382
+ }
383
+ function circleOverlapsAabb(circleX, circleY, radius, aabbX, aabbY, halfWidth, halfHeight) {
384
+ const closestX = Math.max(aabbX - halfWidth, Math.min(circleX, aabbX + halfWidth));
385
+ const closestY = Math.max(aabbY - halfHeight, Math.min(circleY, aabbY + halfHeight));
386
+ const dx = circleX - closestX;
387
+ const dy = circleY - closestY;
388
+ return dx * dx + dy * dy <= radius * radius;
389
+ }
390
+ function bodyBounds(body) {
391
+ const shape = body.shape ?? { type: 'aabb' };
392
+ if (shape.type === 'circle') {
393
+ return { minX: body.x - shape.radius, maxX: body.x + shape.radius, minY: body.y - shape.radius, maxY: body.y + shape.radius };
394
+ }
395
+ if (shape.type === 'capsule') {
396
+ return { minX: body.x - shape.radius, maxX: body.x + shape.radius, minY: body.y - shape.halfHeight - shape.radius, maxY: body.y + shape.halfHeight + shape.radius };
397
+ }
398
+ if (shape.type === 'convex') {
399
+ const xs = shape.vertices.map((vertex) => body.x + vertex.x);
400
+ const ys = shape.vertices.map((vertex) => body.y + vertex.y);
401
+ return { minX: Math.min(...xs), maxX: Math.max(...xs), minY: Math.min(...ys), maxY: Math.max(...ys) };
402
+ }
403
+ return { minX: body.x - body.halfWidth, maxX: body.x + body.halfWidth, minY: body.y - body.halfHeight, maxY: body.y + body.halfHeight };
404
+ }
405
+ function bodyOverlapsBounds(body, x, y, halfWidth, halfHeight) {
406
+ return bodiesOverlap(body, {
407
+ id: '__query',
408
+ kind: 'static',
409
+ x,
410
+ y,
411
+ z: body.z,
412
+ vx: 0,
413
+ vy: 0,
414
+ halfWidth,
415
+ halfHeight,
416
+ halfDepth: body.halfDepth,
417
+ layer: body.mask,
418
+ mask: body.layer,
419
+ });
420
+ }
421
+ function bodiesOverlap(left, right) {
422
+ const leftShape = left.shape ?? { type: 'aabb' };
423
+ const rightShape = right.shape ?? { type: 'aabb' };
424
+ if (hasHeightExtent(left) || hasHeightExtent(right)) {
425
+ const leftHalfDepth = left.halfDepth ?? 0;
426
+ const rightHalfDepth = right.halfDepth ?? 0;
427
+ if (!heightOverlaps(left.z ?? 0, leftHalfDepth, right.z ?? 0, rightHalfDepth)) {
428
+ return false;
429
+ }
430
+ }
431
+ if (!boundsOverlap(bodyBounds(left), bodyBounds(right))) {
432
+ return false;
433
+ }
434
+ if (leftShape.type === 'circle' && rightShape.type === 'circle') {
435
+ return square(left.x - right.x) + square(left.y - right.y) <= square(leftShape.radius + rightShape.radius);
436
+ }
437
+ if (leftShape.type === 'circle') {
438
+ return circleOverlapsBody(left.x, left.y, leftShape.radius, right);
439
+ }
440
+ if (rightShape.type === 'circle') {
441
+ return circleOverlapsBody(right.x, right.y, rightShape.radius, left);
442
+ }
443
+ if (leftShape.type === 'capsule') {
444
+ return capsuleOverlapsBody(left, leftShape, right);
445
+ }
446
+ if (rightShape.type === 'capsule') {
447
+ return capsuleOverlapsBody(right, rightShape, left);
448
+ }
449
+ if (leftShape.type === 'convex' || rightShape.type === 'convex') {
450
+ return convexBodiesOverlap(left, right);
451
+ }
452
+ return true;
453
+ }
454
+ function hasHeightExtent(body) {
455
+ return body.z !== undefined || body.halfDepth !== undefined;
456
+ }
457
+ function boundsOverlap(left, right) {
458
+ return left.minX < right.maxX && left.maxX > right.minX && left.minY < right.maxY && left.maxY > right.minY;
459
+ }
460
+ function circleOverlapsBody(x, y, radius, body) {
461
+ const shape = body.shape ?? { type: 'aabb' };
462
+ if (shape.type === 'circle') {
463
+ return square(x - body.x) + square(y - body.y) <= square(radius + shape.radius);
464
+ }
465
+ if (shape.type === 'capsule') {
466
+ const closestY = clamp(y, body.y - shape.halfHeight, body.y + shape.halfHeight);
467
+ return square(x - body.x) + square(y - closestY) <= square(radius + shape.radius);
468
+ }
469
+ if (shape.type === 'convex') {
470
+ return polygonCircleOverlap(shape.vertices.map((vertex) => ({ x: body.x + vertex.x, y: body.y + vertex.y })), x, y, radius);
471
+ }
472
+ return circleOverlapsAabb(x, y, radius, body.x, body.y, body.halfWidth, body.halfHeight);
473
+ }
474
+ function capsuleOverlapsBody(capsule, shape, body) {
475
+ return circleOverlapsBody(capsule.x, capsule.y - shape.halfHeight, shape.radius, body)
476
+ || circleOverlapsBody(capsule.x, capsule.y + shape.halfHeight, shape.radius, body)
477
+ || bodyOverlapsBounds(body, capsule.x, capsule.y, shape.radius, shape.halfHeight);
478
+ }
479
+ function convexBodiesOverlap(left, right) {
480
+ const leftShape = left.shape ?? { type: 'aabb' };
481
+ const rightShape = right.shape ?? { type: 'aabb' };
482
+ const leftPolygon = shapePolygon(left, leftShape);
483
+ const rightPolygon = shapePolygon(right, rightShape);
484
+ return polygonsOverlap(leftPolygon, rightPolygon);
485
+ }
486
+ function shapePolygon(body, shape) {
487
+ if (shape.type === 'convex') {
488
+ return shape.vertices.map((vertex) => ({ x: body.x + vertex.x, y: body.y + vertex.y }));
489
+ }
490
+ const bounds = bodyBounds(body);
491
+ return [
492
+ { x: bounds.minX, y: bounds.minY },
493
+ { x: bounds.maxX, y: bounds.minY },
494
+ { x: bounds.maxX, y: bounds.maxY },
495
+ { x: bounds.minX, y: bounds.maxY },
496
+ ];
497
+ }
498
+ function polygonsOverlap(left, right) {
499
+ return left.some((point) => pointInPolygon(point, right))
500
+ || right.some((point) => pointInPolygon(point, left))
501
+ || polygonEdges(left).some((leftEdge) => polygonEdges(right).some((rightEdge) => segmentsIntersect(leftEdge.a, leftEdge.b, rightEdge.a, rightEdge.b)));
502
+ }
503
+ function polygonCircleOverlap(polygon, x, y, radius) {
504
+ return pointInPolygon({ x, y }, polygon)
505
+ || polygon.some((point) => square(point.x - x) + square(point.y - y) <= square(radius))
506
+ || polygonEdges(polygon).some((edge) => distancePointToSegmentSquared(x, y, edge.a, edge.b) <= square(radius));
507
+ }
508
+ function polygonEdges(polygon) {
509
+ return polygon.map((point, index) => ({ a: point, b: polygon[(index + 1) % polygon.length] }));
510
+ }
511
+ function pointInPolygon(point, polygon) {
512
+ let inside = false;
513
+ for (let index = 0, previous = polygon.length - 1; index < polygon.length; previous = index, index += 1) {
514
+ const currentPoint = polygon[index];
515
+ const previousPoint = polygon[previous];
516
+ if (((currentPoint.y > point.y) !== (previousPoint.y > point.y))
517
+ && point.x < ((previousPoint.x - currentPoint.x) * (point.y - currentPoint.y)) / (previousPoint.y - currentPoint.y) + currentPoint.x) {
518
+ inside = !inside;
519
+ }
520
+ }
521
+ return inside;
522
+ }
523
+ function segmentsIntersect(a, b, c, d) {
524
+ const abC = orientation(a, b, c);
525
+ const abD = orientation(a, b, d);
526
+ const cdA = orientation(c, d, a);
527
+ const cdB = orientation(c, d, b);
528
+ return abC * abD <= 0 && cdA * cdB <= 0;
529
+ }
530
+ function orientation(a, b, c) {
531
+ return Math.sign((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x));
532
+ }
533
+ function distancePointToSegmentSquared(x, y, a, b) {
534
+ const dx = b.x - a.x;
535
+ const dy = b.y - a.y;
536
+ const lengthSquared = dx * dx + dy * dy;
537
+ if (lengthSquared === 0) {
538
+ return square(x - a.x) + square(y - a.y);
539
+ }
540
+ const t = clamp(((x - a.x) * dx + (y - a.y) * dy) / lengthSquared, 0, 1);
541
+ return square(x - (a.x + t * dx)) + square(y - (a.y + t * dy));
542
+ }
543
+ function rayBodyDistance(body, query) {
544
+ const shape = body.shape ?? { type: 'aabb' };
545
+ if (shape.type === 'circle') {
546
+ return rayCircleDistance2D(query.x, query.y, query.dx, query.dy, body.x, body.y, shape.radius, query.maxDistance);
547
+ }
548
+ if (shape.type === 'capsule') {
549
+ return [
550
+ rayCircleDistance2D(query.x, query.y, query.dx, query.dy, body.x, body.y - shape.halfHeight, shape.radius, query.maxDistance),
551
+ rayCircleDistance2D(query.x, query.y, query.dx, query.dy, body.x, body.y + shape.halfHeight, shape.radius, query.maxDistance),
552
+ rayAabbDistanceExact2D({ minX: body.x - shape.radius, maxX: body.x + shape.radius, minY: body.y - shape.halfHeight, maxY: body.y + shape.halfHeight }, query.x, query.y, query.dx, query.dy, query.maxDistance),
553
+ ].filter((distance) => distance !== undefined).sort((left, right) => left - right)[0];
554
+ }
555
+ if (shape.type === 'convex') {
556
+ const distances = polygonEdges(shape.vertices.map((vertex) => ({ x: body.x + vertex.x, y: body.y + vertex.y })))
557
+ .map((edge) => raySegmentDistance(query, edge.a, edge.b))
558
+ .filter((distance) => distance !== undefined)
559
+ .sort((left, right) => left - right);
560
+ return distances[0];
561
+ }
562
+ const distance = rayAabbDistanceExact2D({
563
+ minX: body.x - body.halfWidth,
564
+ maxX: body.x + body.halfWidth,
565
+ minY: body.y - body.halfHeight,
566
+ maxY: body.y + body.halfHeight,
567
+ }, query.x, query.y, query.dx, query.dy, query.maxDistance);
568
+ return distance === undefined ? undefined : quantizeDistance(distance);
569
+ }
570
+ function raySegmentDistance(query, a, b) {
571
+ const vx = b.x - a.x;
572
+ const vy = b.y - a.y;
573
+ const denominator = query.dx * vy - query.dy * vx;
574
+ if (denominator === 0) {
575
+ return undefined;
576
+ }
577
+ const ax = a.x - query.x;
578
+ const ay = a.y - query.y;
579
+ const distance = (ax * vy - ay * vx) / denominator;
580
+ const segmentT = (ax * query.dy - ay * query.dx) / denominator;
581
+ return distance >= 0 && distance <= query.maxDistance && segmentT >= 0 && segmentT <= 1 ? quantizeDistance(distance) : undefined;
582
+ }
583
+ function circleCastBodyDistance(body, query) {
584
+ if (bodyOverlapsShape(body, {
585
+ shape: 'circle',
586
+ x: query.x,
587
+ y: query.y,
588
+ radius: query.radius,
589
+ })) {
590
+ return 0;
591
+ }
592
+ const bounds = bodyBounds(body);
593
+ return sweptCircleAabbDistance(query.x, query.y, query.dx, query.dy, query.radius, bounds.minX, bounds.maxX, bounds.minY, bounds.maxY, query.maxDistance);
594
+ }
595
+ function sweptCircleAabbDistance(x, y, dx, dy, radius, minX, maxX, minY, maxY, maxDistance) {
596
+ const candidates = [];
597
+ addVerticalSideCandidate(candidates, x, y, dx, dy, minX - radius, minY, maxY, maxDistance);
598
+ addVerticalSideCandidate(candidates, x, y, dx, dy, maxX + radius, minY, maxY, maxDistance);
599
+ addHorizontalSideCandidate(candidates, x, y, dx, dy, minY - radius, minX, maxX, maxDistance);
600
+ addHorizontalSideCandidate(candidates, x, y, dx, dy, maxY + radius, minX, maxX, maxDistance);
601
+ for (const cornerX of [minX, maxX]) {
602
+ for (const cornerY of [minY, maxY]) {
603
+ const distance = rayCircleDistance2D(x, y, dx, dy, cornerX, cornerY, radius, maxDistance);
604
+ if (distance !== undefined) {
605
+ candidates.push(distance);
606
+ }
607
+ }
608
+ }
609
+ return candidates.sort((left, right) => left - right)[0];
610
+ }
611
+ function addVerticalSideCandidate(candidates, x, y, dx, dy, sideX, minY, maxY, maxDistance) {
612
+ if (dx === 0) {
613
+ return;
614
+ }
615
+ const distance = (sideX - x) / dx;
616
+ const hitY = y + dy * distance;
617
+ if (distance >= 0 && distance <= maxDistance && hitY >= minY && hitY <= maxY) {
618
+ candidates.push(quantizeDistance(distance));
619
+ }
620
+ }
621
+ function addHorizontalSideCandidate(candidates, x, y, dx, dy, sideY, minX, maxX, maxDistance) {
622
+ if (dy === 0) {
623
+ return;
624
+ }
625
+ const distance = (sideY - y) / dy;
626
+ const hitX = x + dx * distance;
627
+ if (distance >= 0 && distance <= maxDistance && hitX >= minX && hitX <= maxX) {
628
+ candidates.push(quantizeDistance(distance));
629
+ }
630
+ }
631
+ function rayCircleDistance2D(x, y, dx, dy, circleX, circleY, radius, maxDistance) {
632
+ const ox = x - circleX;
633
+ const oy = y - circleY;
634
+ const a = dx * dx + dy * dy;
635
+ if (a === 0 || !Number.isFinite(maxDistance) || maxDistance < 0) {
636
+ return undefined;
637
+ }
638
+ const b = 2 * (ox * dx + oy * dy);
639
+ const c = ox * ox + oy * oy - radius * radius;
640
+ if (c <= 0) {
641
+ return 0;
642
+ }
643
+ const discriminant = b * b - 4 * a * c;
644
+ if (discriminant < 0) {
645
+ return undefined;
646
+ }
647
+ const root = deterministicSqrt(discriminant);
648
+ const near = (-b - root) / (2 * a);
649
+ const far = (-b + root) / (2 * a);
650
+ let distance;
651
+ if (near >= 0) {
652
+ distance = near;
653
+ }
654
+ else if (near >= -rayDistanceEpsilon && far >= 0) {
655
+ distance = 0;
656
+ }
657
+ return distance === undefined || distance > maxDistance ? undefined : quantizeDistance(distance);
658
+ }
659
+ function rayAabbDistanceExact2D(bounds, x, y, dx, dy, maxDistance) {
660
+ if (!Number.isFinite(maxDistance) || maxDistance < 0) {
661
+ return undefined;
662
+ }
663
+ let tMin = 0;
664
+ let tMax = maxDistance;
665
+ const axes = [
666
+ { origin: x, direction: dx, min: bounds.minX, max: bounds.maxX },
667
+ { origin: y, direction: dy, min: bounds.minY, max: bounds.maxY },
668
+ ];
669
+ for (const axis of axes) {
670
+ if (axis.direction === 0) {
671
+ if (axis.origin < axis.min || axis.origin > axis.max) {
672
+ return undefined;
673
+ }
674
+ continue;
675
+ }
676
+ const inv = 1 / axis.direction;
677
+ const near = (axis.min - axis.origin) * inv;
678
+ const far = (axis.max - axis.origin) * inv;
679
+ tMin = Math.max(tMin, Math.min(near, far));
680
+ tMax = Math.min(tMax, Math.max(near, far));
681
+ if (tMin > tMax) {
682
+ return undefined;
683
+ }
684
+ }
685
+ return Math.max(0, tMin);
686
+ }
687
+ function quantizeDistance(distance) {
688
+ // Pure integer rounding, NOT Number.prototype.toFixed: toFixed's half-way
689
+ // rounding has diverged across JS engines historically, and this repo ships
690
+ // both Hermes (native) and V8 (web) — a cross-engine desync vector on the
691
+ // checksummed state path. normalizeZero keeps -0 out of state.
692
+ return normalizeZero(Math.round(distance * 1e6) / 1e6);
693
+ }
694
+ function normalizeZero(value) {
695
+ return value === 0 ? 0 : value;
696
+ }
697
+ function square(value) {
698
+ return value * value;
699
+ }
700
+ function clamp(value, min, max) {
701
+ return Math.max(min, Math.min(max, value));
702
+ }
703
+ function deterministicSqrt(value) {
704
+ if (value <= 0) {
705
+ return 0;
706
+ }
707
+ // Seed the Newton iteration from the argument's magnitude (2^ceil(bits/2)):
708
+ // starting from `value` itself needs ~0.5*log2(value) halving steps before
709
+ // the quadratic regime, so 8 iterations only converged for tiny inputs —
710
+ // at realistic fixed-point magnitudes (dx^2+dy^2 ~ 1e10) the old seed
711
+ // returned results off by orders of magnitude. Every operation here is
712
+ // exact integer/IEEE-deterministic arithmetic, so all peers still agree.
713
+ let estimate = 1;
714
+ let magnitude = value;
715
+ while (magnitude >= 4) {
716
+ magnitude /= 4;
717
+ estimate *= 2;
718
+ }
719
+ if (magnitude > 1) {
720
+ estimate *= 2;
721
+ }
722
+ for (let iteration = 0; iteration < 8; iteration += 1) {
723
+ estimate = (estimate + value / estimate) / 2;
724
+ }
725
+ return quantizeDistance(estimate);
726
+ }
727
+ function normalizeBodies(bodies) {
728
+ const sorted = [...bodies].sort(compareBodies);
729
+ const duplicate = sorted.find((body, index) => index > 0 && body.id === sorted[index - 1].id);
730
+ if (duplicate !== undefined) {
731
+ throw new Error(`Duplicate deterministic physics body id ${duplicate.id}`);
732
+ }
733
+ return (0, canonical_js_1.cloneCanonical)(sorted);
734
+ }
735
+ function compareBodies(left, right) {
736
+ return compareStrings(left.id, right.id);
737
+ }
738
+ function compareEvents(left, right) {
739
+ return compareStrings(left.pair, right.pair);
740
+ }
741
+ function compareActivePairMetadata(left, right) {
742
+ return compareStrings(left.pair, right.pair);
743
+ }
744
+ function compareStrings(left, right) {
745
+ return Number(left > right) - Number(left < right);
746
+ }
747
+ function assertInteger(value, name) {
748
+ if (!Number.isInteger(value)) {
749
+ throw new Error(`${name} must be an integer`);
750
+ }
751
+ }