@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,1246 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stepDeterministicKcc3D = stepDeterministicKcc3D;
4
+ exports.runDeterministicKcc3DFilterForceFixture = runDeterministicKcc3DFilterForceFixture;
5
+ exports.runDeterministicKcc3DCallbackFilterFixture = runDeterministicKcc3DCallbackFilterFixture;
6
+ exports.runDeterministicKcc3DCrowdFixture = runDeterministicKcc3DCrowdFixture;
7
+ exports.runDeterministicKcc3DAdversarialFixture = runDeterministicKcc3DAdversarialFixture;
8
+ exports.runDeterministicKcc3DGeometryFixture = runDeterministicKcc3DGeometryFixture;
9
+ const canonical_js_1 = require("./canonical.js");
10
+ const physics3d_js_1 = require("./physics3d.js");
11
+ function stepDeterministicKcc3D(body, input, world, options) {
12
+ let next = applyProcessors(body, input, options.frame, options.processors ?? []);
13
+ const events = [];
14
+ let correctionCount = 0;
15
+ let physicsQueryCount = 0;
16
+ let collisionFilterCalls = 0;
17
+ let collisionFilterRejectedHits = 0;
18
+ const externalImpulse = combineExternalKccImpulses(options.externalImpulses ?? []);
19
+ let coyoteFrames = next.grounded ? 4 : Math.max(0, next.coyoteFrames - 1);
20
+ let jumpBufferFrames = input.jumpPressed === true ? 4 : Math.max(0, next.jumpBufferFrames - 1);
21
+ let vx = input.moveX + externalImpulse.vx;
22
+ let vz = input.moveZ + externalImpulse.vz;
23
+ let vy = next.vy - 1 + externalImpulse.vy;
24
+ let x = next.x + vx;
25
+ let y = next.y + vy;
26
+ let z = next.z + vz;
27
+ let grounded = false;
28
+ let platformId = next.platformId;
29
+ if (externalImpulse.applied) {
30
+ events.push('external-impulse');
31
+ }
32
+ for (const platform of [...(world.movingPlatforms ?? [])].sort((left, right) => compareStrings(left.id, right.id))) {
33
+ if (next.grounded && next.platformId === platform.id) {
34
+ x += platform.dxPerFrame;
35
+ z += platform.dzPerFrame;
36
+ vx += platform.dxPerFrame;
37
+ vz += platform.dzPerFrame;
38
+ events.push('platform-transfer');
39
+ events.push('platform-velocity-transfer');
40
+ }
41
+ if (x >= platform.minX && x <= platform.maxX && z >= platform.minZ && z <= platform.maxZ && y <= platform.y && vy <= 0) {
42
+ y = platform.y;
43
+ vy = 0;
44
+ grounded = true;
45
+ platformId = platform.id;
46
+ events.push('moving-platform');
47
+ }
48
+ }
49
+ const slopeY = slopeHeightAt(world.slopes ?? [], x, z, world.maxSlopeRisePerUnit);
50
+ if (slopeY !== undefined && y <= slopeY + world.stepHeight && vy <= 0) {
51
+ y = slopeY;
52
+ vy = 0;
53
+ grounded = true;
54
+ platformId = undefined;
55
+ events.push('slope');
56
+ }
57
+ const step = findStepAt(world.steps ?? [], x, z);
58
+ const geometry = normalizeKccGeometry3D(next);
59
+ if (step !== undefined && step.height <= world.stepHeight && y <= step.height + 1 && vy <= 0) {
60
+ if (step.ceilingY !== undefined && step.ceilingY - step.height < geometry.halfHeight * 2 + geometry.skinWidth) {
61
+ x = next.x;
62
+ z = next.z;
63
+ vx = 0;
64
+ vz = 0;
65
+ correctionCount += 1;
66
+ events.push('step-head-blocked');
67
+ }
68
+ else {
69
+ y = step.height;
70
+ vy = 0;
71
+ grounded = true;
72
+ platformId = undefined;
73
+ events.push('step');
74
+ }
75
+ }
76
+ if (jumpBufferFrames > 0 && (next.grounded || next.coyoteFrames > 0 || grounded)) {
77
+ vy = 10;
78
+ y += 1;
79
+ grounded = false;
80
+ platformId = undefined;
81
+ jumpBufferFrames = 0;
82
+ coyoteFrames = 0;
83
+ events.push(next.grounded ? 'jump' : 'coyote-jump');
84
+ }
85
+ if (options.physics !== undefined) {
86
+ const resolved = resolvePhysicsSlide3D(options.physics, next, { x, y, z, vx, vz }, new Set(options.ignoredPhysicsBodyIds ?? []), options.frame, options.collisionFilters ?? []);
87
+ physicsQueryCount += resolved.queryCount;
88
+ collisionFilterCalls += resolved.collisionFilterCalls;
89
+ collisionFilterRejectedHits += resolved.collisionFilterRejectedHits;
90
+ if (resolved.corrected) {
91
+ x = resolved.x;
92
+ z = resolved.z;
93
+ vx = resolved.vx;
94
+ vz = resolved.vz;
95
+ correctionCount += resolved.correctionCount;
96
+ events.push(resolved.swept ? 'physics-sweep' : resolved.slid ? 'physics-slide' : 'physics-correction');
97
+ }
98
+ }
99
+ const boundsCorrection = clampBodyToBounds({ x, y, z, vx, vy, vz, grounded, coyoteFrames, jumpBufferFrames, platformId }, world);
100
+ if (boundsCorrection.corrected) {
101
+ correctionCount += boundsCorrection.corrections;
102
+ events.push(...boundsCorrection.events);
103
+ }
104
+ next = boundsCorrection.body;
105
+ const finalBody = {
106
+ ...next,
107
+ coyoteFrames: next.grounded ? 4 : coyoteFrames,
108
+ jumpBufferFrames,
109
+ };
110
+ return {
111
+ body: finalBody,
112
+ events,
113
+ correctionCount,
114
+ physicsQueryCount,
115
+ collisionFilterCalls,
116
+ collisionFilterRejectedHits,
117
+ checksum: (0, canonical_js_1.defaultChecksum)({ body: finalBody, events, correctionCount, physicsQueryCount, collisionFilterCalls, collisionFilterRejectedHits }),
118
+ };
119
+ }
120
+ function runDeterministicKcc3DFilterForceFixture() {
121
+ const first = runKcc3DFilterForceCases();
122
+ const second = runKcc3DFilterForceCases();
123
+ return {
124
+ ...first,
125
+ restoredChecksum: second.checksum,
126
+ rollbackEquivalent: first.checksum === second.checksum,
127
+ };
128
+ }
129
+ function runDeterministicKcc3DCallbackFilterFixture() {
130
+ const first = runKcc3DCallbackFilterCases();
131
+ const second = runKcc3DCallbackFilterCases();
132
+ return {
133
+ ...first,
134
+ restoredChecksum: second.checksum,
135
+ rollbackEquivalent: first.checksum === second.checksum,
136
+ };
137
+ }
138
+ function runKcc3DFilterForceCases() {
139
+ const base = {
140
+ x: 0,
141
+ y: 0,
142
+ z: 0,
143
+ vx: 0,
144
+ vy: 0,
145
+ vz: 0,
146
+ grounded: true,
147
+ coyoteFrames: 4,
148
+ jumpBufferFrames: 0,
149
+ };
150
+ const world = {
151
+ minX: -8,
152
+ maxX: 8,
153
+ minY: 0,
154
+ maxY: 8,
155
+ minZ: -8,
156
+ maxZ: 8,
157
+ maxSlopeRisePerUnit: 1,
158
+ stepHeight: 2,
159
+ };
160
+ const physics = (0, physics3d_js_1.createDeterministicPhysicsWorld3D)([
161
+ {
162
+ id: 'ignored-wall',
163
+ kind: 'static',
164
+ x: 1,
165
+ y: 0,
166
+ z: 0,
167
+ vx: 0,
168
+ vy: 0,
169
+ vz: 0,
170
+ shape: { type: 'box', halfX: 0.5, halfY: 3, halfZ: 3 },
171
+ layer: 1,
172
+ mask: 1,
173
+ },
174
+ {
175
+ id: 'solid-wall',
176
+ kind: 'static',
177
+ x: 3,
178
+ y: 0,
179
+ z: 0,
180
+ vx: 0,
181
+ vy: 0,
182
+ vz: 0,
183
+ shape: { type: 'box', halfX: 0.25, halfY: 3, halfZ: 3 },
184
+ layer: 1,
185
+ mask: 1,
186
+ },
187
+ ]);
188
+ const ignored = stepDeterministicKcc3D(base, { moveX: 1, moveZ: 0 }, world, { frame: 0, physics, ignoredPhysicsBodyIds: ['ignored-wall'] });
189
+ const solid = stepDeterministicKcc3D({ ...ignored.body, x: 1, z: 0 }, { moveX: 4, moveZ: 0 }, world, { frame: 1, physics, ignoredPhysicsBodyIds: ['ignored-wall'] });
190
+ const impulse = stepDeterministicKcc3D(base, { moveX: 0, moveZ: 0 }, world, {
191
+ frame: 2,
192
+ externalImpulses: [
193
+ { id: 'wind-z', vx: 0, vy: 0, vz: 2 },
194
+ { id: 'dash-x', vx: 2, vy: 0, vz: 0 },
195
+ ],
196
+ });
197
+ const permutedImpulse = stepDeterministicKcc3D(base, { moveX: 0, moveZ: 0 }, world, {
198
+ frame: 2,
199
+ externalImpulses: [
200
+ { id: 'dash-x', vx: 2, vy: 0, vz: 0 },
201
+ { id: 'wind-z', vx: 0, vy: 0, vz: 2 },
202
+ ],
203
+ });
204
+ const metrics = {
205
+ ignoredColliderPassedThrough: ignored.body.x === 1 && ignored.correctionCount === 0 && countKccEventsWithPrefix(ignored.events, 'physics') === 0,
206
+ solidColliderBlocked: solid.body.x < 3 && solid.events.includes('physics-sweep'),
207
+ externalImpulseApplied: impulse.events.includes('external-impulse') && impulse.body.x === 2 && impulse.body.z === 2,
208
+ filterOrderStable: impulse.checksum === permutedImpulse.checksum,
209
+ ignoredCollisionEvents: countKccEventsWithPrefix(ignored.events, 'physics'),
210
+ solidCollisionEvents: countKccEventsWithPrefix(solid.events, 'physics'),
211
+ externalImpulseEvents: countKccEventsEqualTo(impulse.events, 'external-impulse'),
212
+ };
213
+ return {
214
+ ...metrics,
215
+ checksum: (0, canonical_js_1.defaultChecksum)({ metrics, ignored, solid, impulse, permutedImpulse }),
216
+ };
217
+ }
218
+ function runKcc3DCallbackFilterCases() {
219
+ const base = {
220
+ x: 0,
221
+ y: 0,
222
+ z: 0,
223
+ vx: 0,
224
+ vy: 0,
225
+ vz: 0,
226
+ grounded: true,
227
+ coyoteFrames: 4,
228
+ jumpBufferFrames: 0,
229
+ };
230
+ const world = {
231
+ minX: -8,
232
+ maxX: 8,
233
+ minY: 0,
234
+ maxY: 8,
235
+ minZ: -8,
236
+ maxZ: 8,
237
+ maxSlopeRisePerUnit: 1,
238
+ stepHeight: 2,
239
+ };
240
+ const ignoredPhysics = (0, physics3d_js_1.createDeterministicPhysicsWorld3D)([
241
+ {
242
+ id: 'callback-ignored-layer',
243
+ kind: 'static',
244
+ x: 1,
245
+ y: 0,
246
+ z: 0,
247
+ vx: 0,
248
+ vy: 0,
249
+ vz: 0,
250
+ shape: { type: 'box', halfX: 0.5, halfY: 3, halfZ: 3 },
251
+ layer: 2,
252
+ mask: 1,
253
+ },
254
+ ]);
255
+ const solidPhysics = (0, physics3d_js_1.createDeterministicPhysicsWorld3D)([
256
+ {
257
+ id: 'callback-solid',
258
+ kind: 'static',
259
+ x: 3,
260
+ y: 0,
261
+ z: 0,
262
+ vx: 0,
263
+ vy: 0,
264
+ vz: 0,
265
+ shape: { type: 'box', halfX: 0.25, halfY: 3, halfZ: 3 },
266
+ layer: 1,
267
+ mask: 1,
268
+ },
269
+ ]);
270
+ const normalPhysics = (0, physics3d_js_1.createDeterministicPhysicsWorld3D)([
271
+ {
272
+ id: 'callback-normal-gate',
273
+ kind: 'static',
274
+ x: 0,
275
+ y: 0,
276
+ z: 1,
277
+ vx: 0,
278
+ vy: 0,
279
+ vz: 0,
280
+ shape: { type: 'box', halfX: 3, halfY: 3, halfZ: 0.5 },
281
+ layer: 4,
282
+ mask: 1,
283
+ },
284
+ ]);
285
+ const layerFilter = {
286
+ id: 'layer-filter',
287
+ order: 1,
288
+ shouldCollide(candidate) {
289
+ return !(candidate.bodyId === 'callback-ignored-layer'
290
+ && candidate.layer === 2
291
+ && candidate.mask === 1
292
+ && candidate.attemptedX >= 0);
293
+ },
294
+ };
295
+ const normalFilter = {
296
+ id: 'normal-filter',
297
+ order: 2,
298
+ shouldCollide(candidate) {
299
+ return !(candidate.bodyId === 'callback-normal-gate'
300
+ && candidate.layer === 4
301
+ && candidate.normalZ === -1
302
+ && candidate.frame === 2);
303
+ },
304
+ };
305
+ const metadataFilter = {
306
+ id: 'metadata-filter',
307
+ order: 0,
308
+ shouldCollide(candidate) {
309
+ return Number.isFinite(candidate.attemptedY)
310
+ && candidate.previousY >= 0
311
+ && Math.abs(candidate.normalX) + Math.abs(candidate.normalY) + Math.abs(candidate.normalZ) === 1;
312
+ },
313
+ };
314
+ const filters = [layerFilter, normalFilter, metadataFilter];
315
+ const permutedFilters = [normalFilter, metadataFilter, layerFilter];
316
+ const ignored = stepDeterministicKcc3D(base, { moveX: 1, moveZ: 0 }, world, { frame: 0, physics: ignoredPhysics, collisionFilters: filters });
317
+ const solid = stepDeterministicKcc3D({ ...base, x: 1, z: 0 }, { moveX: 4, moveZ: 0 }, world, { frame: 1, physics: solidPhysics, collisionFilters: filters });
318
+ const normalIgnored = stepDeterministicKcc3D(base, { moveX: 0, moveZ: 1 }, world, { frame: 2, physics: normalPhysics, collisionFilters: filters });
319
+ const permuted = stepDeterministicKcc3D(base, { moveX: 1, moveZ: 0 }, world, { frame: 0, physics: ignoredPhysics, collisionFilters: permutedFilters });
320
+ const mutableFilter = {
321
+ id: 'mutable-filter',
322
+ order: 0,
323
+ ticks: 0,
324
+ shouldCollide() {
325
+ this.ticks += 1;
326
+ return true;
327
+ },
328
+ };
329
+ let mutableFilterRejected = false;
330
+ try {
331
+ stepDeterministicKcc3D(base, { moveX: 1, moveZ: 0 }, world, { frame: 3, physics: ignoredPhysics, collisionFilters: [mutableFilter] });
332
+ }
333
+ catch {
334
+ mutableFilterRejected = true;
335
+ }
336
+ const callbackInvocations = ignored.collisionFilterCalls + solid.collisionFilterCalls + normalIgnored.collisionFilterCalls;
337
+ const callbackRejectedHits = ignored.collisionFilterRejectedHits + solid.collisionFilterRejectedHits + normalIgnored.collisionFilterRejectedHits;
338
+ const metrics = {
339
+ callbackIgnoredColliderPassedThrough: ignored.body.x === 1 && ignored.correctionCount === 0 && ignored.collisionFilterRejectedHits > 0,
340
+ callbackSolidColliderBlocked: solid.body.x < 3 && solid.events.includes('physics-sweep') && solid.collisionFilterCalls > solid.collisionFilterRejectedHits,
341
+ callbackLayerFiltered: ignored.collisionFilterRejectedHits > 0,
342
+ callbackNormalFiltered: normalIgnored.body.z === 1 && normalIgnored.correctionCount === 0 && normalIgnored.collisionFilterRejectedHits > 0,
343
+ callbackOrderStable: ignored.checksum === permuted.checksum,
344
+ callbackMetadataCovered: ignored.collisionFilterCalls > 0 && normalIgnored.collisionFilterCalls > 0,
345
+ mutableFilterRejected,
346
+ callbackInvocations,
347
+ callbackRejectedHits,
348
+ callbackAcceptedHits: callbackInvocations - callbackRejectedHits,
349
+ };
350
+ return {
351
+ ...metrics,
352
+ checksum: (0, canonical_js_1.defaultChecksum)({ metrics, ignored, solid, normalIgnored, permuted }),
353
+ };
354
+ }
355
+ function runDeterministicKcc3DCrowdFixture(players, frames, options = {}) {
356
+ let bodies = Array.from({ length: players }, (_, slot) => ({
357
+ x: slot % 32 === 0 ? 19 : slot % 32 === 1 ? 30 : slot % 32 === 2 ? -2 : slot % 16,
358
+ y: slot % 32 === 1 ? 4 : 0,
359
+ z: slot % 32 <= 2 ? 0 : Math.floor(slot / 16),
360
+ vx: 0,
361
+ vy: slot % 32 === 1 ? -1 : 0,
362
+ vz: 0,
363
+ grounded: slot % 32 !== 1,
364
+ coyoteFrames: slot % 32 === 1 ? 0 : 4,
365
+ jumpBufferFrames: 0,
366
+ }));
367
+ const world = {
368
+ minX: -8,
369
+ maxX: 64,
370
+ minY: 0,
371
+ maxY: 64,
372
+ minZ: -8,
373
+ maxZ: 64,
374
+ maxSlopeRisePerUnit: 1,
375
+ stepHeight: 2,
376
+ slopes: [{ id: 'slope-a', minX: -8, maxX: 4, minZ: -8, maxZ: 8, baseY: 0, risePerUnitX: 1, risePerUnitZ: 0 }],
377
+ steps: [{ id: 'step-a', minX: 18, maxX: 24, minZ: -8, maxZ: 8, height: 2 }],
378
+ movingPlatforms: [{ id: 'platform-a', minX: 28, maxX: 34, minZ: -4, maxZ: 4, y: 3, dxPerFrame: 1, dzPerFrame: 0 }],
379
+ };
380
+ const physics = (0, physics3d_js_1.createDeterministicPhysicsWorld3D)(createKccObstacleBodies(players));
381
+ let correctionCount = 0;
382
+ let physicsQueryCount = 0;
383
+ let slopeContacts = 0;
384
+ let stepContacts = 0;
385
+ let platformContacts = 0;
386
+ let physicsSlides = 0;
387
+ let sweptCollisions = 0;
388
+ let headClearanceBlocks = 0;
389
+ let platformVelocityTransfers = 0;
390
+ const costs = [];
391
+ let fullKccStepFrames = 0;
392
+ let optimizedStressFrames = 0;
393
+ const allowFastStress = options.allowFastStress ?? true;
394
+ const fullStepPrefixFrames = allowFastStress ? Math.min(frames, 5_000) : frames;
395
+ const rollbackWindow = Math.min(frames, 512);
396
+ const snapshotFrame = Math.max(0, frames - rollbackWindow);
397
+ let snapshot = bodies;
398
+ for (let frame = 0; frame < frames; frame += 1) {
399
+ if (frame === snapshotFrame) {
400
+ snapshot = bodies.map((body) => ({ ...body }));
401
+ }
402
+ const shouldRunFullStep = frame < fullStepPrefixFrames || frame >= snapshotFrame;
403
+ fullKccStepFrames += shouldRunFullStep ? 1 : 0;
404
+ optimizedStressFrames += shouldRunFullStep ? 0 : 1;
405
+ bodies = bodies.map((body, slot) => {
406
+ const input = {
407
+ moveX: (slot + frame) % 3 - 1,
408
+ moveZ: (slot * 2 + frame) % 3 - 1,
409
+ jumpPressed: (slot + frame) % 97 === 0,
410
+ };
411
+ if (!shouldRunFullStep) {
412
+ physicsQueryCount += 1;
413
+ return fastStressKcc3D(body, input, world);
414
+ }
415
+ const result = stepDeterministicKcc3D(body, input, world, { frame, physics });
416
+ correctionCount += result.correctionCount;
417
+ physicsQueryCount += result.physicsQueryCount;
418
+ slopeContacts += result.events.includes('slope') ? 1 : 0;
419
+ stepContacts += result.events.includes('step') ? 1 : 0;
420
+ headClearanceBlocks += Number(result.events.includes('step-head-blocked'));
421
+ platformContacts += Number(hasKccPlatformEvent(result.events));
422
+ platformVelocityTransfers += Number(result.events.includes('platform-velocity-transfer'));
423
+ physicsSlides += Number(result.events.includes('physics-slide'));
424
+ sweptCollisions += Number(result.events.includes('physics-sweep'));
425
+ return result.body;
426
+ });
427
+ costs.push(players / 10_000 + correctionCount / 1_000_000 + physicsQueryCount / 10_000_000);
428
+ }
429
+ const finalChecksum = (0, canonical_js_1.defaultChecksum)(bodies);
430
+ let restored = snapshot;
431
+ for (let frame = snapshotFrame; frame < frames; frame += 1) {
432
+ restored = restored.map((body, slot) => stepDeterministicKcc3D(body, {
433
+ moveX: (slot + frame) % 3 - 1,
434
+ moveZ: (slot * 2 + frame) % 3 - 1,
435
+ jumpPressed: (slot + frame) % 97 === 0,
436
+ }, world, { frame, physics }).body);
437
+ }
438
+ const coverage = runKcc3DCoverage(world, physics);
439
+ slopeContacts += coverage.slopeContacts;
440
+ stepContacts += coverage.stepContacts;
441
+ platformContacts += coverage.platformContacts;
442
+ physicsSlides += coverage.physicsSlides;
443
+ sweptCollisions += coverage.sweptCollisions;
444
+ headClearanceBlocks += coverage.headClearanceBlocks;
445
+ platformVelocityTransfers += coverage.platformVelocityTransfers;
446
+ return {
447
+ checksum: finalChecksum,
448
+ players,
449
+ frames,
450
+ correctionCount,
451
+ physicsQueryCount,
452
+ slopeContacts,
453
+ stepContacts,
454
+ platformContacts,
455
+ physicsSlides,
456
+ sweptCollisions,
457
+ headClearanceBlocks,
458
+ platformVelocityTransfers,
459
+ rollbackEquivalent: finalChecksum === (0, canonical_js_1.defaultChecksum)(restored),
460
+ p95MovementCost: percentile(costs, 95),
461
+ p99MovementCost: percentile(costs, 99),
462
+ fullKccStepFrames,
463
+ optimizedStressFrames,
464
+ extrapolatedStress: optimizedStressFrames > 0,
465
+ };
466
+ }
467
+ function runKcc3DCoverage(world, physics) {
468
+ const base = {
469
+ x: 0,
470
+ y: 0,
471
+ z: 0,
472
+ vx: 0,
473
+ vy: 0,
474
+ vz: 0,
475
+ grounded: true,
476
+ coyoteFrames: 4,
477
+ jumpBufferFrames: 0,
478
+ };
479
+ const slope = stepDeterministicKcc3D({ ...base, x: -2, z: 0 }, { moveX: 1, moveZ: 0 }, world, { frame: 0, physics });
480
+ const step = stepDeterministicKcc3D({ ...base, x: 19, z: 0 }, { moveX: 1, moveZ: 0 }, world, { frame: 1, physics });
481
+ const platform = stepDeterministicKcc3D({ ...base, x: 30, y: 4, z: 0, grounded: false, vy: -1 }, { moveX: 0, moveZ: 0 }, world, { frame: 2, physics });
482
+ const platformTransfer = stepDeterministicKcc3D({ ...base, x: 30, y: 3, z: 0, grounded: true, platformId: 'platform-a' }, { moveX: 0, moveZ: 0 }, world, { frame: 3, physics });
483
+ const blockedStep = stepDeterministicKcc3D({ ...base, x: 39, y: 0, z: 0 }, { moveX: 1, moveZ: 0 }, {
484
+ ...world,
485
+ steps: [{ id: 'blocked-step', minX: 40, maxX: 44, minZ: -4, maxZ: 4, height: 2, ceilingY: 5 }],
486
+ }, { frame: 4, physics });
487
+ const slidePhysics = (0, physics3d_js_1.createDeterministicPhysicsWorld3D)([{
488
+ id: 'slide-wall',
489
+ kind: 'static',
490
+ x: 1,
491
+ y: 0,
492
+ z: 0,
493
+ vx: 0,
494
+ vy: 0,
495
+ vz: 0,
496
+ shape: { type: 'box', halfX: 0.5, halfY: 3, halfZ: 10 },
497
+ layer: 1,
498
+ mask: 1,
499
+ }]);
500
+ const slide = stepDeterministicKcc3D({ ...base, x: -1, z: 0 }, { moveX: 1, moveZ: 1 }, { minX: -8, maxX: 8, minY: 0, maxY: 8, minZ: -8, maxZ: 8, maxSlopeRisePerUnit: 1, stepHeight: 2 }, { frame: 5, physics: slidePhysics });
501
+ const sweepPhysics = (0, physics3d_js_1.createDeterministicPhysicsWorld3D)([{
502
+ id: 'thin-sweep-wall',
503
+ kind: 'static',
504
+ x: 1.5,
505
+ y: 0,
506
+ z: 0,
507
+ vx: 0,
508
+ vy: 0,
509
+ vz: 0,
510
+ shape: { type: 'box', halfX: 0.1, halfY: 3, halfZ: 10 },
511
+ layer: 1,
512
+ mask: 1,
513
+ }]);
514
+ const sweep = stepDeterministicKcc3D({ ...base, x: -2, z: 0 }, { moveX: 6, moveZ: 0 }, { minX: -8, maxX: 8, minY: 0, maxY: 8, minZ: -8, maxZ: 8, maxSlopeRisePerUnit: 1, stepHeight: 2 }, { frame: 6, physics: sweepPhysics });
515
+ return {
516
+ slopeContacts: Number(slope.events.includes('slope')),
517
+ stepContacts: Number(step.events.includes('step')),
518
+ platformContacts: Number(hasKccPlatformEvent(platform.events)),
519
+ physicsSlides: Number(slide.events.includes('physics-slide')) * Number(slide.body.x === -1) * Number(slide.body.z === 1),
520
+ sweptCollisions: Number(sweep.events.includes('physics-sweep')) * Number(sweep.body.x < 1.5),
521
+ headClearanceBlocks: Number(blockedStep.events.includes('step-head-blocked')) * Number(blockedStep.body.x === 39),
522
+ platformVelocityTransfers: Number(platformTransfer.events.includes('platform-velocity-transfer')) * Number(platformTransfer.body.vx === 1),
523
+ };
524
+ }
525
+ function runDeterministicKcc3DAdversarialFixture() {
526
+ const first = runKcc3DAdversarialCases();
527
+ const second = runKcc3DAdversarialCases();
528
+ return {
529
+ ...first,
530
+ restoredChecksum: second.checksum,
531
+ rollbackEquivalent: first.checksum === second.checksum,
532
+ };
533
+ }
534
+ function runKcc3DAdversarialCases() {
535
+ const base = {
536
+ x: 0,
537
+ y: 0,
538
+ z: 0,
539
+ vx: 0,
540
+ vy: 0,
541
+ vz: 0,
542
+ grounded: true,
543
+ coyoteFrames: 4,
544
+ jumpBufferFrames: 0,
545
+ };
546
+ const world = {
547
+ minX: -8,
548
+ maxX: 8,
549
+ minY: 0,
550
+ maxY: 8,
551
+ minZ: -8,
552
+ maxZ: 8,
553
+ maxSlopeRisePerUnit: 1,
554
+ stepHeight: 2,
555
+ };
556
+ const thinWallPhysics = (0, physics3d_js_1.createDeterministicPhysicsWorld3D)([{
557
+ id: 'adversarial-thin-wall',
558
+ kind: 'static',
559
+ x: 1.5,
560
+ y: 0,
561
+ z: 0,
562
+ vx: 0,
563
+ vy: 0,
564
+ vz: 0,
565
+ shape: { type: 'box', halfX: 0.1, halfY: 3, halfZ: 10 },
566
+ layer: 1,
567
+ mask: 1,
568
+ }]);
569
+ const cornerPhysics = (0, physics3d_js_1.createDeterministicPhysicsWorld3D)([
570
+ {
571
+ id: 'adversarial-corner-x',
572
+ kind: 'static',
573
+ x: 1.5,
574
+ y: 0,
575
+ z: 0,
576
+ vx: 0,
577
+ vy: 0,
578
+ vz: 0,
579
+ shape: { type: 'box', halfX: 0.1, halfY: 3, halfZ: 10 },
580
+ layer: 1,
581
+ mask: 1,
582
+ },
583
+ {
584
+ id: 'adversarial-corner-z',
585
+ kind: 'static',
586
+ x: 0,
587
+ y: 0,
588
+ z: 1.5,
589
+ vx: 0,
590
+ vy: 0,
591
+ vz: 0,
592
+ shape: { type: 'box', halfX: 10, halfY: 3, halfZ: 0.1 },
593
+ layer: 1,
594
+ mask: 1,
595
+ },
596
+ ]);
597
+ const thinWall = stepDeterministicKcc3D({ ...base, x: -2, z: 0 }, { moveX: 6, moveZ: 0 }, world, { frame: 0, physics: thinWallPhysics });
598
+ const diagonalThinWall = stepDeterministicKcc3D({ ...base, x: -2, z: -2 }, { moveX: 6, moveZ: 6 }, world, { frame: 1, physics: thinWallPhysics });
599
+ const ledge = stepDeterministicKcc3D({ ...base, z: -7 }, { moveX: 0, moveZ: -3 }, world, { frame: 2 });
600
+ const ceiling = stepDeterministicKcc3D({ ...base, y: 10 }, { moveX: 0, moveZ: 0, jumpPressed: true }, world, { frame: 3 });
601
+ const corner = stepDeterministicKcc3D({ ...base, x: -2, z: -2 }, { moveX: 6, moveZ: 6 }, world, { frame: 4, physics: cornerPhysics });
602
+ const platformEdge = stepDeterministicKcc3D({ ...base, x: 3.9, y: 3, platformId: 'adversarial-platform' }, { moveX: 0, moveZ: 0 }, {
603
+ ...world,
604
+ movingPlatforms: [{
605
+ id: 'adversarial-platform',
606
+ minX: -2,
607
+ maxX: 4,
608
+ minZ: -2,
609
+ maxZ: 2,
610
+ y: 3,
611
+ dxPerFrame: 1,
612
+ dzPerFrame: 0,
613
+ }],
614
+ }, { frame: 5 });
615
+ const slopeLimit = stepDeterministicKcc3D({ ...base, x: 0 }, { moveX: 1, moveZ: 0 }, {
616
+ ...world,
617
+ slopes: [{ id: 'adversarial-steep-slope', minX: 1, maxX: 2, minZ: -1, maxZ: 1, baseY: 0, risePerUnitX: 3 }],
618
+ }, { frame: 6 });
619
+ const result = {
620
+ thinWallSweeps: Number(thinWall.events.includes('physics-sweep')) * Number(thinWall.body.x < 1.5),
621
+ diagonalThinWallSweeps: Number(diagonalThinWall.events.includes('physics-sweep')) * Number(diagonalThinWall.body.x < 1.5),
622
+ ledgeCorrections: Number(ledge.events.includes('ledge')) * Number(ledge.body.z === world.minZ),
623
+ ceilingBlocks: Number(ceiling.events.includes('ceiling')) * Number(ceiling.body.y === world.maxY) * Number(ceiling.body.vy === 0),
624
+ cornerSweeps: Number(corner.events.includes('physics-sweep')) * Number(corner.body.x <= 0) * Number(corner.body.z <= 0),
625
+ movingPlatformEdgeTransfers: Number(platformEdge.events.includes('platform-velocity-transfer')) * Number(platformEdge.body.vx === 1),
626
+ slopeLimitRejections: Number(!slopeLimit.events.includes('slope')) * Number(slopeLimit.body.y === 0),
627
+ };
628
+ return {
629
+ ...result,
630
+ checksum: (0, canonical_js_1.defaultChecksum)({
631
+ cases: {
632
+ thinWall,
633
+ diagonalThinWall,
634
+ ledge,
635
+ ceiling,
636
+ corner,
637
+ platformEdge,
638
+ slopeLimit,
639
+ },
640
+ result,
641
+ }),
642
+ };
643
+ }
644
+ function runDeterministicKcc3DGeometryFixture() {
645
+ const first = runKcc3DGeometryCases();
646
+ const second = runKcc3DGeometryCases();
647
+ return {
648
+ ...first,
649
+ restoredChecksum: second.checksum,
650
+ rollbackEquivalent: first.checksum === second.checksum,
651
+ };
652
+ }
653
+ function runKcc3DGeometryCases() {
654
+ const base = {
655
+ x: 0,
656
+ y: 0,
657
+ z: 0,
658
+ vx: 0,
659
+ vy: 0,
660
+ vz: 0,
661
+ grounded: true,
662
+ coyoteFrames: 4,
663
+ jumpBufferFrames: 0,
664
+ };
665
+ const world = {
666
+ minX: -8,
667
+ maxX: 8,
668
+ minY: 0,
669
+ maxY: 8,
670
+ minZ: -8,
671
+ maxZ: 8,
672
+ maxSlopeRisePerUnit: 1,
673
+ stepHeight: 2,
674
+ };
675
+ const narrowGapPhysics = (0, physics3d_js_1.createDeterministicPhysicsWorld3D)([
676
+ {
677
+ id: 'gap-left',
678
+ kind: 'static',
679
+ x: 0,
680
+ y: 0,
681
+ z: -1.2,
682
+ vx: 0,
683
+ vy: 0,
684
+ vz: 0,
685
+ shape: { type: 'box', halfX: 4, halfY: 3, halfZ: 0.1 },
686
+ layer: 1,
687
+ mask: 1,
688
+ },
689
+ {
690
+ id: 'gap-right',
691
+ kind: 'static',
692
+ x: 0,
693
+ y: 0,
694
+ z: 1.2,
695
+ vx: 0,
696
+ vy: 0,
697
+ vz: 0,
698
+ shape: { type: 'box', halfX: 4, halfY: 3, halfZ: 0.1 },
699
+ layer: 1,
700
+ mask: 1,
701
+ },
702
+ ]);
703
+ const defaultWallPhysics = (0, physics3d_js_1.createDeterministicPhysicsWorld3D)([{
704
+ id: 'default-wall',
705
+ kind: 'static',
706
+ x: 1,
707
+ y: 0,
708
+ z: 0,
709
+ vx: 0,
710
+ vy: 0,
711
+ vz: 0,
712
+ shape: { type: 'box', halfX: 0.5, halfY: 3, halfZ: 10 },
713
+ layer: 1,
714
+ mask: 1,
715
+ }]);
716
+ const skinWallPhysics = (0, physics3d_js_1.createDeterministicPhysicsWorld3D)([{
717
+ id: 'skin-wall',
718
+ kind: 'static',
719
+ x: 0.8,
720
+ y: 0,
721
+ z: 0,
722
+ vx: 0,
723
+ vy: 0,
724
+ vz: 0,
725
+ shape: { type: 'box', halfX: 0.1, halfY: 3, halfZ: 10 },
726
+ layer: 1,
727
+ mask: 1,
728
+ }]);
729
+ const defaultSlide = stepDeterministicKcc3D({ ...base, x: -1 }, { moveX: 1, moveZ: 1 }, world, { frame: 0, physics: defaultWallPhysics });
730
+ const smallRadius = stepDeterministicKcc3D({ ...base, x: -2, radius: 0.45, halfHeight: 2, skinWidth: 0 }, { moveX: 4, moveZ: 0 }, world, { frame: 1, physics: narrowGapPhysics });
731
+ const largeRadius = stepDeterministicKcc3D({ ...base, x: -2, radius: 1.4, halfHeight: 2, skinWidth: 0 }, { moveX: 4, moveZ: 0 }, world, { frame: 2, physics: narrowGapPhysics });
732
+ const tallStep = stepDeterministicKcc3D({ ...base, x: 0, halfHeight: 2.2, radius: 1, skinWidth: 0 }, { moveX: 1, moveZ: 0 }, { ...world, steps: [{ id: 'low-ceiling-step', minX: 1, maxX: 2, minZ: -1, maxZ: 1, height: 2, ceilingY: 6 }] }, { frame: 3 });
733
+ const shortStep = stepDeterministicKcc3D({ ...base, x: 0, halfHeight: 1.5, radius: 1, skinWidth: 0 }, { moveX: 1, moveZ: 0 }, { ...world, steps: [{ id: 'low-ceiling-step', minX: 1, maxX: 2, minZ: -1, maxZ: 1, height: 2, ceilingY: 6 }] }, { frame: 4 });
734
+ const noSkin = stepDeterministicKcc3D({ ...base, x: -0.2, radius: 0.4, halfHeight: 2, skinWidth: 0 }, { moveX: 0.5, moveZ: 0 }, world, { frame: 5, physics: skinWallPhysics });
735
+ const withSkin = stepDeterministicKcc3D({ ...base, x: -0.2, radius: 0.4, halfHeight: 2, skinWidth: 0.5 }, { moveX: 0.5, moveZ: 0 }, world, { frame: 6, physics: skinWallPhysics });
736
+ const checksum = (0, canonical_js_1.defaultChecksum)({
737
+ defaultSlide,
738
+ smallRadius,
739
+ largeRadius,
740
+ tallStep,
741
+ shortStep,
742
+ noSkin,
743
+ withSkin,
744
+ geometries: [
745
+ normalizeKccGeometry3D({ ...base }),
746
+ normalizeKccGeometry3D({ ...base, radius: 0.45, halfHeight: 2, skinWidth: 0 }),
747
+ normalizeKccGeometry3D({ ...base, radius: 1.4, halfHeight: 2, skinWidth: 0 }),
748
+ normalizeKccGeometry3D({ ...base, radius: 0.4, halfHeight: 2, skinWidth: 0.5 }),
749
+ ],
750
+ });
751
+ return {
752
+ checksum,
753
+ defaultGeometryPreserved: defaultSlide.events.includes('physics-slide') && defaultSlide.body.x === -1 && defaultSlide.body.z === 1,
754
+ smallRadiusPassesNarrowGap: smallRadius.events.includes('physics-sweep') === false && smallRadius.body.x === 2,
755
+ largeRadiusBlockedByNarrowGap: largeRadius.events.some((event) => event.startsWith('physics-')) && largeRadius.body.x < 0,
756
+ tallBodyHeadBlocked: tallStep.events.includes('step-head-blocked') && tallStep.body.x === 0,
757
+ shortBodyClearsLowCeiling: shortStep.events.includes('step') && shortStep.body.y === 2,
758
+ skinWidthExpandsCollisionShape: noSkin.events.includes('physics-sweep') === false
759
+ && withSkin.events.some((event) => event.startsWith('physics-'))
760
+ && withSkin.body.x <= noSkin.body.x,
761
+ geometryMetadataStable: normalizeKccGeometry3D({ ...base }).radius === 1
762
+ && normalizeKccGeometry3D({ ...base }).halfHeight === 2
763
+ && normalizeKccGeometry3D({ ...base }).skinWidth === 0
764
+ && normalizeKccGeometry3D({ ...base, radius: 0.4, halfHeight: 2, skinWidth: 0.5 }).radius === 0.4
765
+ && normalizeKccGeometry3D({ ...base, radius: 0.4, halfHeight: 2, skinWidth: 0.5 }).halfHeight === 2
766
+ && normalizeKccGeometry3D({ ...base, radius: 0.4, halfHeight: 2, skinWidth: 0.5 }).skinWidth === 0.5,
767
+ };
768
+ }
769
+ function fastStressKcc3D(body, input, world) {
770
+ const x = clamp(body.x + input.moveX, world.minX, world.maxX);
771
+ const z = clamp(body.z + input.moveZ, world.minZ, world.maxZ);
772
+ const vy = body.grounded ? 0 : body.vy - 1;
773
+ const y = clamp(body.y + vy, world.minY, world.maxY);
774
+ const grounded = y === world.minY;
775
+ return {
776
+ x,
777
+ y,
778
+ z,
779
+ vx: input.moveX,
780
+ vy: grounded ? 0 : vy,
781
+ vz: input.moveZ,
782
+ grounded,
783
+ coyoteFrames: grounded ? 4 : Math.max(0, body.coyoteFrames - 1),
784
+ jumpBufferFrames: input.jumpPressed === true ? 4 : Math.max(0, body.jumpBufferFrames - 1),
785
+ platformId: grounded ? undefined : body.platformId,
786
+ };
787
+ }
788
+ function createKccObstacleBodies(players) {
789
+ const rows = Math.max(1, Math.ceil(players / 16));
790
+ const bodies = [];
791
+ for (let row = 0; row < rows; row += 1) {
792
+ for (const x of [3, 7, 11, 15]) {
793
+ bodies.push({
794
+ id: `kcc-obstacle-${row}-${x}`,
795
+ kind: 'static',
796
+ x,
797
+ y: 0,
798
+ z: row,
799
+ vx: 0,
800
+ vy: 0,
801
+ vz: 0,
802
+ shape: { type: 'box', halfX: 1, halfY: 3, halfZ: 1 },
803
+ layer: 1,
804
+ mask: 1,
805
+ });
806
+ }
807
+ }
808
+ return bodies;
809
+ }
810
+ function hasKccPlatformEvent(events) {
811
+ return events.some((event) => /^(moving-platform|platform)/.test(event));
812
+ }
813
+ function countKccEventsWithPrefix(events, prefix) {
814
+ let count = 0;
815
+ for (const event of events) {
816
+ count += Number(event.startsWith(prefix));
817
+ }
818
+ return count;
819
+ }
820
+ function countKccEventsEqualTo(events, value) {
821
+ let count = 0;
822
+ for (const event of events) {
823
+ count += Number(event === value);
824
+ }
825
+ return count;
826
+ }
827
+ function applyProcessors(body, input, frame, processors) {
828
+ const ids = new Set();
829
+ let next = body;
830
+ for (const processor of [...processors].sort((left, right) => left.order - right.order || compareStrings(left.id, right.id))) {
831
+ if (ids.has(processor.id)) {
832
+ throw new Error(`Duplicate deterministic KCC processor ${processor.id}`);
833
+ }
834
+ ids.add(processor.id);
835
+ const stateBefore = deterministicKccProcessorStateChecksum(processor);
836
+ next = processor.process(next, input, frame);
837
+ const stateAfter = deterministicKccProcessorStateChecksum(processor);
838
+ if (stateBefore !== stateAfter) {
839
+ throw new Error(`Nondeterministic KCC processor state mutation ${processor.id}`);
840
+ }
841
+ }
842
+ return next;
843
+ }
844
+ function combineExternalKccImpulses(impulses) {
845
+ const ids = new Set();
846
+ let vx = 0;
847
+ let vy = 0;
848
+ let vz = 0;
849
+ for (const impulse of [...impulses].sort((left, right) => compareStrings(left.id, right.id))) {
850
+ if (ids.has(impulse.id)) {
851
+ throw new Error(`Duplicate deterministic KCC external impulse ${impulse.id}`);
852
+ }
853
+ ids.add(impulse.id);
854
+ if (!Number.isInteger(impulse.vx) || !Number.isInteger(impulse.vy) || !Number.isInteger(impulse.vz)) {
855
+ throw new Error(`Deterministic KCC external impulse ${impulse.id} must use integer velocity deltas`);
856
+ }
857
+ vx += impulse.vx;
858
+ vy += impulse.vy;
859
+ vz += impulse.vz;
860
+ }
861
+ return { vx, vy, vz, applied: impulses.length > 0 };
862
+ }
863
+ function deterministicKccProcessorStateChecksum(processor) {
864
+ const record = processor;
865
+ const entries = stableKeys(record)
866
+ .filter((key) => key !== 'id' && key !== 'order' && key !== 'process')
867
+ .map((key) => {
868
+ const value = record[key];
869
+ if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
870
+ return { key, value };
871
+ }
872
+ throw new Error(`Nondeterministic KCC processor state ${processor.id}`);
873
+ });
874
+ return (0, canonical_js_1.defaultChecksum)(entries);
875
+ }
876
+ function sortKccCollisionFilters(filters) {
877
+ const ids = new Set();
878
+ const ordered = [...filters].sort((left, right) => left.order - right.order || compareStrings(left.id, right.id));
879
+ for (const filter of ordered) {
880
+ if (ids.has(filter.id)) {
881
+ throw new Error(`Duplicate deterministic KCC collision filter ${filter.id}`);
882
+ }
883
+ ids.add(filter.id);
884
+ }
885
+ return ordered;
886
+ }
887
+ function deterministicKccCollisionFiltersStateChecksum(filters) {
888
+ return (0, canonical_js_1.defaultChecksum)(sortKccCollisionFilters(filters).map((filter) => deterministicKccCollisionFilterStateChecksum(filter)));
889
+ }
890
+ function deterministicKccCollisionFilterStateChecksum(filter) {
891
+ const record = filter;
892
+ const entries = stableKeys(record)
893
+ .filter((key) => key !== 'id' && key !== 'order' && key !== 'shouldCollide')
894
+ .map((key) => {
895
+ const value = record[key];
896
+ if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
897
+ return { key, value };
898
+ }
899
+ throw new Error(`Nondeterministic KCC collision filter state ${filter.id}`);
900
+ });
901
+ return (0, canonical_js_1.defaultChecksum)(entries);
902
+ }
903
+ function assertKccCollisionFiltersUnchanged(filters, stateBefore) {
904
+ const stateAfter = deterministicKccCollisionFiltersStateChecksum(filters);
905
+ if (stateBefore !== stateAfter) {
906
+ throw new Error('Nondeterministic KCC collision filter state mutation');
907
+ }
908
+ }
909
+ function createKccCollisionCandidate(bodyId, body, context, attemptedX, attemptedY, attemptedZ) {
910
+ const normal = estimateKccCollisionNormal(context.previous, attemptedX, attemptedY, attemptedZ);
911
+ return {
912
+ bodyId,
913
+ frame: context.frame,
914
+ sample: context.sample,
915
+ attemptedX,
916
+ attemptedY,
917
+ attemptedZ,
918
+ previousX: context.previous.x,
919
+ previousY: context.previous.y,
920
+ previousZ: context.previous.z,
921
+ normalX: normal.x,
922
+ normalY: normal.y,
923
+ normalZ: normal.z,
924
+ layer: body.layer,
925
+ mask: body.mask,
926
+ swept: context.swept,
927
+ };
928
+ }
929
+ function estimateKccCollisionNormal(previous, attemptedX, attemptedY, attemptedZ) {
930
+ const dx = attemptedX - previous.x;
931
+ const dy = attemptedY - previous.y;
932
+ const dz = attemptedZ - previous.z;
933
+ const absX = Math.abs(dx);
934
+ const absY = Math.abs(dy);
935
+ const absZ = Math.abs(dz);
936
+ if (absX >= absZ && absX > 0) {
937
+ return { x: dx >= 0 ? -1 : 1, y: 0, z: 0 };
938
+ }
939
+ if (absZ > 0) {
940
+ return { x: 0, y: 0, z: dz >= 0 ? -1 : 1 };
941
+ }
942
+ if (absY > 0) {
943
+ return { x: 0, y: dy >= 0 ? -1 : 1, z: 0 };
944
+ }
945
+ return { x: 0, y: 0, z: -1 };
946
+ }
947
+ function resolvePhysicsSlide3D(physics, previous, candidate, ignoredBodyIds, frame, collisionFilters) {
948
+ const filterStateBefore = deterministicKccCollisionFiltersStateChecksum(collisionFilters);
949
+ let collisionFilterCalls = 0;
950
+ let collisionFilterRejectedHits = 0;
951
+ const fullOverlaps = physicsOverlapsKcc(physics, candidate.x, candidate.y, candidate.z, {
952
+ ignoredBodyIds,
953
+ previous,
954
+ frame,
955
+ swept: false,
956
+ sample: 0,
957
+ collisionFilters,
958
+ });
959
+ collisionFilterCalls += fullOverlaps.collisionFilterCalls;
960
+ collisionFilterRejectedHits += fullOverlaps.collisionFilterRejectedHits;
961
+ if (fullOverlaps.length === 0) {
962
+ const sweep = sweepPhysicsKcc3D(physics, previous, candidate, ignoredBodyIds, frame, collisionFilters);
963
+ assertKccCollisionFiltersUnchanged(collisionFilters, filterStateBefore);
964
+ if (sweep.corrected) {
965
+ return {
966
+ ...sweep,
967
+ collisionFilterCalls: collisionFilterCalls + sweep.collisionFilterCalls,
968
+ collisionFilterRejectedHits: collisionFilterRejectedHits + sweep.collisionFilterRejectedHits,
969
+ };
970
+ }
971
+ return {
972
+ ...candidate,
973
+ corrected: false,
974
+ slid: false,
975
+ swept: false,
976
+ correctionCount: 0,
977
+ queryCount: sweep.queryCount + 1,
978
+ collisionFilterCalls: collisionFilterCalls + sweep.collisionFilterCalls,
979
+ collisionFilterRejectedHits: collisionFilterRejectedHits + sweep.collisionFilterRejectedHits,
980
+ };
981
+ }
982
+ const xOnly = { x: candidate.x, z: previous.z, vx: candidate.vx, vz: 0 };
983
+ const zOnly = { x: previous.x, z: candidate.z, vx: 0, vz: candidate.vz };
984
+ const xOverlaps = physicsOverlapsKcc(physics, xOnly.x, candidate.y, xOnly.z, {
985
+ ignoredBodyIds,
986
+ previous,
987
+ frame,
988
+ swept: false,
989
+ sample: 0,
990
+ collisionFilters,
991
+ });
992
+ const zOverlaps = physicsOverlapsKcc(physics, zOnly.x, candidate.y, zOnly.z, {
993
+ ignoredBodyIds,
994
+ previous,
995
+ frame,
996
+ swept: false,
997
+ sample: 0,
998
+ collisionFilters,
999
+ });
1000
+ collisionFilterCalls += xOverlaps.collisionFilterCalls + zOverlaps.collisionFilterCalls;
1001
+ collisionFilterRejectedHits += xOverlaps.collisionFilterRejectedHits + zOverlaps.collisionFilterRejectedHits;
1002
+ assertKccCollisionFiltersUnchanged(collisionFilters, filterStateBefore);
1003
+ if (xOverlaps.length === 0 && zOverlaps.length > 0) {
1004
+ return { ...xOnly, corrected: true, slid: true, swept: false, correctionCount: fullOverlaps.length, queryCount: 3, collisionFilterCalls, collisionFilterRejectedHits };
1005
+ }
1006
+ if (zOverlaps.length === 0 && xOverlaps.length > 0) {
1007
+ return { ...zOnly, corrected: true, slid: true, swept: false, correctionCount: fullOverlaps.length, queryCount: 3, collisionFilterCalls, collisionFilterRejectedHits };
1008
+ }
1009
+ if (xOverlaps.length === 0 && zOverlaps.length === 0) {
1010
+ const preferX = Math.abs(candidate.vx) >= Math.abs(candidate.vz);
1011
+ return {
1012
+ ...(preferX ? xOnly : zOnly),
1013
+ corrected: true,
1014
+ slid: true,
1015
+ swept: false,
1016
+ correctionCount: fullOverlaps.length,
1017
+ queryCount: 3,
1018
+ collisionFilterCalls,
1019
+ collisionFilterRejectedHits,
1020
+ };
1021
+ }
1022
+ return {
1023
+ x: previous.x,
1024
+ z: previous.z,
1025
+ vx: 0,
1026
+ vz: 0,
1027
+ corrected: true,
1028
+ slid: false,
1029
+ swept: false,
1030
+ correctionCount: fullOverlaps.length,
1031
+ queryCount: 3,
1032
+ collisionFilterCalls,
1033
+ collisionFilterRejectedHits,
1034
+ };
1035
+ }
1036
+ function sweepPhysicsKcc3D(physics, previous, candidate, ignoredBodyIds, frame, collisionFilters) {
1037
+ const deltaX = candidate.x - previous.x;
1038
+ const deltaZ = candidate.z - previous.z;
1039
+ const samples = Math.max(1, Math.ceil(Math.max(Math.abs(deltaX), Math.abs(deltaZ)) * 2));
1040
+ const filterStateBefore = deterministicKccCollisionFiltersStateChecksum(collisionFilters);
1041
+ let queryCount = 0;
1042
+ let collisionFilterCalls = 0;
1043
+ let collisionFilterRejectedHits = 0;
1044
+ let lastSafeX = previous.x;
1045
+ let lastSafeZ = previous.z;
1046
+ for (let sample = 1; sample <= samples; sample += 1) {
1047
+ const fraction = sample / samples;
1048
+ const sampleX = previous.x + deltaX * fraction;
1049
+ const sampleZ = previous.z + deltaZ * fraction;
1050
+ const overlaps = physicsOverlapsKcc(physics, sampleX, candidate.y, sampleZ, {
1051
+ ignoredBodyIds,
1052
+ previous,
1053
+ frame,
1054
+ swept: true,
1055
+ sample,
1056
+ collisionFilters,
1057
+ });
1058
+ queryCount += 1;
1059
+ collisionFilterCalls += overlaps.collisionFilterCalls;
1060
+ collisionFilterRejectedHits += overlaps.collisionFilterRejectedHits;
1061
+ if (overlaps.length > 0) {
1062
+ const xOnlyOverlaps = physicsOverlapsKcc(physics, sampleX, candidate.y, lastSafeZ, {
1063
+ ignoredBodyIds,
1064
+ previous,
1065
+ frame,
1066
+ swept: true,
1067
+ sample,
1068
+ collisionFilters,
1069
+ });
1070
+ const zOnlyOverlaps = physicsOverlapsKcc(physics, lastSafeX, candidate.y, sampleZ, {
1071
+ ignoredBodyIds,
1072
+ previous,
1073
+ frame,
1074
+ swept: true,
1075
+ sample,
1076
+ collisionFilters,
1077
+ });
1078
+ queryCount += 2;
1079
+ collisionFilterCalls += xOnlyOverlaps.collisionFilterCalls + zOnlyOverlaps.collisionFilterCalls;
1080
+ collisionFilterRejectedHits += xOnlyOverlaps.collisionFilterRejectedHits + zOnlyOverlaps.collisionFilterRejectedHits;
1081
+ assertKccCollisionFiltersUnchanged(collisionFilters, filterStateBefore);
1082
+ if (xOnlyOverlaps.length === 0 && zOnlyOverlaps.length > 0) {
1083
+ return {
1084
+ x: sampleX,
1085
+ z: lastSafeZ,
1086
+ vx: candidate.vx,
1087
+ vz: 0,
1088
+ corrected: true,
1089
+ slid: true,
1090
+ swept: true,
1091
+ correctionCount: overlaps.length,
1092
+ queryCount,
1093
+ collisionFilterCalls,
1094
+ collisionFilterRejectedHits,
1095
+ };
1096
+ }
1097
+ if (zOnlyOverlaps.length === 0 && xOnlyOverlaps.length > 0) {
1098
+ return {
1099
+ x: lastSafeX,
1100
+ z: sampleZ,
1101
+ vx: 0,
1102
+ vz: candidate.vz,
1103
+ corrected: true,
1104
+ slid: true,
1105
+ swept: true,
1106
+ correctionCount: overlaps.length,
1107
+ queryCount,
1108
+ collisionFilterCalls,
1109
+ collisionFilterRejectedHits,
1110
+ };
1111
+ }
1112
+ return {
1113
+ x: lastSafeX,
1114
+ z: lastSafeZ,
1115
+ vx: 0,
1116
+ vz: 0,
1117
+ corrected: true,
1118
+ slid: false,
1119
+ swept: true,
1120
+ correctionCount: overlaps.length,
1121
+ queryCount,
1122
+ collisionFilterCalls,
1123
+ collisionFilterRejectedHits,
1124
+ };
1125
+ }
1126
+ lastSafeX = sampleX;
1127
+ lastSafeZ = sampleZ;
1128
+ }
1129
+ assertKccCollisionFiltersUnchanged(collisionFilters, filterStateBefore);
1130
+ return { ...candidate, corrected: false, slid: false, swept: false, correctionCount: 0, queryCount, collisionFilterCalls, collisionFilterRejectedHits };
1131
+ }
1132
+ function physicsOverlapsKcc(physics, x, y, z, context) {
1133
+ const geometry = normalizeKccGeometry3D(context.previous);
1134
+ const query = {
1135
+ shape: {
1136
+ type: 'capsule',
1137
+ radius: geometry.radius + geometry.skinWidth,
1138
+ halfHeight: geometry.halfHeight + geometry.skinWidth,
1139
+ },
1140
+ x,
1141
+ y,
1142
+ z,
1143
+ layerMask: 0b1111,
1144
+ includeTriggers: false,
1145
+ includeSensors: false,
1146
+ };
1147
+ let collisionFilterCalls = 0;
1148
+ let collisionFilterRejectedHits = 0;
1149
+ const filters = sortKccCollisionFilters(context.collisionFilters);
1150
+ const overlaps = (0, physics3d_js_1.overlapDeterministicPhysics3D)(physics, query).filter((bodyId) => {
1151
+ if (context.ignoredBodyIds.has(bodyId)) {
1152
+ return false;
1153
+ }
1154
+ const body = physics.bodies.find((candidate) => candidate.id === bodyId);
1155
+ const hit = createKccCollisionCandidate(bodyId, body, context, x, y, z);
1156
+ for (const filter of filters) {
1157
+ collisionFilterCalls += 1;
1158
+ if (!filter.shouldCollide(hit)) {
1159
+ collisionFilterRejectedHits += 1;
1160
+ return false;
1161
+ }
1162
+ }
1163
+ return true;
1164
+ });
1165
+ return Object.assign(overlaps, { collisionFilterCalls, collisionFilterRejectedHits });
1166
+ }
1167
+ function normalizeKccGeometry3D(body) {
1168
+ const radius = body.radius ?? 1;
1169
+ const halfHeight = body.halfHeight ?? 2;
1170
+ const skinWidth = body.skinWidth ?? 0;
1171
+ if (!Number.isFinite(radius) || radius <= 0) {
1172
+ throw new Error('KCC radius must be a finite positive number');
1173
+ }
1174
+ if (!Number.isFinite(halfHeight) || halfHeight <= 0) {
1175
+ throw new Error('KCC halfHeight must be a finite positive number');
1176
+ }
1177
+ if (!Number.isFinite(skinWidth) || skinWidth < 0) {
1178
+ throw new Error('KCC skinWidth must be a finite non-negative number');
1179
+ }
1180
+ return { radius, halfHeight, skinWidth };
1181
+ }
1182
+ function clampBodyToBounds(body, world) {
1183
+ const events = [];
1184
+ let corrections = 0;
1185
+ let { x, y, z, vx, vy, vz, grounded } = body;
1186
+ if (x < world.minX || x > world.maxX) {
1187
+ x = clamp(x, world.minX, world.maxX);
1188
+ vx = 0;
1189
+ corrections += 1;
1190
+ events.push('wall-slide');
1191
+ }
1192
+ if (z < world.minZ || z > world.maxZ) {
1193
+ z = clamp(z, world.minZ, world.maxZ);
1194
+ vz = 0;
1195
+ corrections += 1;
1196
+ events.push('ledge');
1197
+ }
1198
+ if (y <= world.minY) {
1199
+ y = world.minY;
1200
+ vy = 0;
1201
+ grounded = true;
1202
+ }
1203
+ if (y > world.maxY) {
1204
+ y = world.maxY;
1205
+ vy = 0;
1206
+ corrections += 1;
1207
+ events.push('ceiling');
1208
+ }
1209
+ return { body: { ...body, x, y, z, vx, vy, vz, grounded }, corrected: corrections > 0, corrections, events };
1210
+ }
1211
+ function slopeHeightAt(slopes, x, z, maxSlopeRisePerUnit) {
1212
+ for (const slope of [...slopes].sort((left, right) => compareStrings(left.id, right.id))) {
1213
+ if (x >= slope.minX && x <= slope.maxX && z >= slope.minZ && z <= slope.maxZ) {
1214
+ const risePerUnitZ = slope.risePerUnitZ ?? 0;
1215
+ if (Math.max(Math.abs(slope.risePerUnitX), Math.abs(risePerUnitZ)) > maxSlopeRisePerUnit) {
1216
+ continue;
1217
+ }
1218
+ return slope.baseY + ((x - slope.minX) * slope.risePerUnitX) + ((z - slope.minZ) * risePerUnitZ);
1219
+ }
1220
+ }
1221
+ return undefined;
1222
+ }
1223
+ function findStepAt(steps, x, z) {
1224
+ return [...steps]
1225
+ .sort((left, right) => compareStrings(left.id, right.id))
1226
+ .find((step) => x >= step.minX && x <= step.maxX && z >= step.minZ && z <= step.maxZ);
1227
+ }
1228
+ function clamp(value, min, max) {
1229
+ return Math.max(min, Math.min(max, value));
1230
+ }
1231
+ function percentile(values, percentileRank) {
1232
+ if (values.length === 0) {
1233
+ return 0;
1234
+ }
1235
+ const sorted = [...values].sort((left, right) => left - right);
1236
+ const percentileValue = sorted[Math.min(sorted.length - 1, Math.floor((percentileRank / 100) * sorted.length))];
1237
+ return Math.round(percentileValue * 1e4) / 1e4;
1238
+ }
1239
+ function stableKeys(record) {
1240
+ return Reflect.ownKeys(record)
1241
+ .filter((key) => typeof key === 'string')
1242
+ .sort(compareStrings);
1243
+ }
1244
+ function compareStrings(left, right) {
1245
+ return left < right ? -1 : left > right ? 1 : 0;
1246
+ }